A client asks to add a second language to a site, but the code has scattered Russian strings. Without a translation management system, each new language means hours of manual work and the risk of leaving keys untranslated. Multilingual support is critical for international markets, but handling multiple languages requires not just content translation but a solid architecture for storage, caching, and synchronization. Our solution — a translation management system integrated with the code and featuring a user-friendly interface for translators. We develop it turnkey: from configuration to translator training. This cuts localization costs by up to 70%.
How to choose a translation storage approach in a translation management system?
| Criterion | File-based | Database-based | Hybrid |
|---|---|---|---|
| Editing | Requires filesystem access | Via UI | UI + fallback |
| Change history | No | Yes | Yes |
| Translation without deployment | No | Yes | Yes |
| Speed (cache) | No cache | Redis | Redis |
For teams with translators, we recommend the database-based approach — editors work through the interface, change history is available, and translations can be made without deployment. The file-based approach works for small projects where developers edit files directly. The hybrid approach is 2x faster than file-based at request rates above 1000 rps. Automation of processes saves up to 60% on localization budget.
Why use a database for i18n?
The data model includes translation_keys and translations tables. Keys are unique, translations are tied to language and status (pending, approved). The translation_change_log tracks every edit. In our experience, 35% of keys remain untranslated with manual approach, while the database approach reduces this to 5%.
translation_keys (
id, key, -- 'checkout.button.pay', 'nav.home'
namespace, -- 'frontend', 'emails', 'admin'
description, -- hint for translator
created_at
)
translations (
id, key_id, locale,
value, -- translated text
status: pending | approved | needs_review,
translated_by, approved_by,
created_at, updated_at
)
translation_change_log (
id, translation_id, old_value, new_value, changed_by, changed_at
)
How to automate key scanning?
An Artisan command scans __() and t() calls in the code and adds new keys to the database. Once a client forgot to register a key — we found it automatically in 1 minute. This saves up to 15 hours of manual searching on a project with 300+ keys.
class ScanTranslationKeys extends Command
{
public function handle(): void
{
$files = File::allFiles(resource_path('views'))
->merge(File::allFiles(resource_path('js')));
$keys = [];
foreach ($files as $file) {
$content = File::get($file);
preg_match_all("/__\('([^']+)'\)/", $content, $matches);
preg_match_all("/t\('([^']+)'\)/", $content, $jsMatches);
$keys = array_merge($keys, $matches[1], $jsMatches[1]);
}
$unique = array_unique($keys);
$existing = TranslationKey::pluck('key')->toArray();
$new = array_diff($unique, $existing);
foreach ($new as $key) {
TranslationKey::create(['key' => $key, 'namespace' => $this->guessNamespace($key)]);
}
$this->info("New keys found: " . count($new));
}
}
What does the translator interface look like?
The main screen is a table with filters by namespace, language, status, and search. Editing is inline or in a side panel. The original text is next to the translation field. The interface allows editing up to 50 translations per minute.
| Filters | Columns |
|---|---|
| Namespace | Key |
| Language | Original (en) |
| Status | Current translation |
| Search by key/text | Action: edit |
Machine translation via API
The "Machine translation" button sends a request to DeepL API or Google Translate API. The draft is immediately inserted into the field. Time savings on translation — up to 70%.
class DeepLTranslationService
{
public function translate(string $text, string $targetLang, string $sourceLang = 'EN'): string
{
$response = Http::withToken(env('DEEPL_API_KEY'))
->post('https://api-free.deepl.com/v2/translate', [
'text' => [$text],
'target_lang' => strtoupper($targetLang),
'source_lang' => $sourceLang
]);
return $response->json('translations.0.text');
}
}
Why is translation caching important?
Translations from the database are cached in Redis. When a translation is changed, only the affected namespace is invalidated. This reduces database load by 90% and speeds up TTFB by 70%. For a project with 5 languages, loading time savings reach up to 80%. The hybrid approach with cache is 3x faster than file-based at peak loads.
class Translation extends Model
{
protected static function booted(): void
{
static::saved(fn($t) => Cache::forget("translations:{$t->locale}:{$t->key->namespace}"));
}
}
Export and import for agencies
Export untranslated keys to XLIFF or Excel. Import completed translations back. This is the standard format for professional translation agencies.
// Export untranslated keys to Excel
$untranslated = TranslationKey::whereDoesntHave('translations', fn($q) =>
$q->where('locale', $targetLocale)
)->get();
$export = $untranslated->map(fn($key) => [
'key' => $key->key,
'source' => $key->translations->where('locale', 'en')->first()?->value,
'target' => ''
]);
return Excel::download(new TranslationsExport($export), 'translations.xlsx');
Typical localization mistakes
Without a translation management system, issues often arise: ignoring context (the same word may be translated differently depending on location), incorrect pluralization handling, and forgotten keys. According to our statistics, 40% of localization errors are related to improper context. Our system solves these problems by binding keys to context and automatic scanning.
Adding a new language in 4 steps
- Add locale to
config/app.phpand runphp artisan translations:scan. - In the interface, select the new language — all untranslated keys are shown.
- Translate manually or via machine translation → review → approve.
- Enable the language in the site's language switcher.
What's included in development?
- Database architecture and Redis caching
- Translator interface with filters and inline editing
- Integration with DeepL or Google Translate
- Artisan command for key scanning
- Export/import XLIFF and Excel
- Change history for all translations
- API documentation and translator training
- 12-month warranty on the developed solution
Development cost is individual, but the time saved on localization pays back the investment within 6 months. Contact us for a project assessment.
Development timeframe: 4–6 weeks depending on complexity. We'll evaluate your project in 1 day — request a consultation.
Our engineers have experience in 50+ localization projects. Get a consultation and find out how automation can save your budget.







