Розробка системи управління перекладами (i18n)
Система управління перекладами — необхідна для багатомовних сайтів, де потрібно регулярно додавати та оновлювати тексти на кількох мовах без редагування коду або JSON-файлів вручну. Перекладачі працюють у зручному інтерфейсі, розробники додають нові ключі через код.
Варіанти зберігання перекладів
Файловий підхід (Laravel lang, i18next) — переклади у PHP/JSON-файлах. Мінуси: редагування вимагає доступу до файлової системи, немає історії змін, складно для нетехнічних перекладачів.
Підхід БД — переклади у БД. Редактори працюють через UI, історія змін доступна, можливість перекладання без развертування. Це рекомендований підхід для команд із перекладачами.
Гібридний — переклади у файлах як резервна копія, перевизначення через БД.
Модель даних
translation_keys (
id, key, -- 'checkout.button.pay', 'nav.home'
namespace, -- 'frontend', 'emails', 'admin'
description, -- підказка для перекладача
created_at
)
translations (
id, key_id, locale,
value, -- перекладений текст
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
)
Збір неперекладених ключів
Код використовує ключі через __() або t(). Artisan-команда сканує код і додає нові ключі у БД:
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("Знайдено нових ключів: " . count($new));
}
}
Інтерфейс перекладача
Основний екран — таблиця з фільтрами:
| Фільтри | Колонки |
|---|---|
| Namespace | Ключ |
| Мова | Оригінал (ru) |
| Статус | Поточний переклад |
| Пошук по ключу/тексту | Дія: редагувати |
Форма редагування — вбудована в таблицю або бічна панель. Поруч з полем перекладу — оригінальний текст на базовій мові. Кнопка "Машинний переклад" робить запит до DeepL або Google Translate API для чернетки.
API машинного перекладу
class DeepLTranslationService
{
public function translate(string $text, string $targetLang, string $sourceLang = 'RU'): 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');
}
}
Кешування перекладів
Переклади з БД кешуються у Redis. При зміні — інвалідація лише задіяного namespace:
class Translation extends Model
{
protected static function booted(): void
{
static::saved(fn($t) => Cache::forget("translations:{$t->locale}:{$t->key->namespace}"));
}
}
Експорт/імпорт для перекладацьких агенцій
Для роботи з агенціями — експорт у XLIFF (стандартний формат для CAT-інструментів) або Excel:
// Експорт неперекладених ключів у 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', 'ru')->first()?->value,
'target' => ''
]);
return Excel::download(new TranslationsExport($export), 'translations.xlsx');
Процес додавання нової мови
- Додати locale в
config/app.phpта запуститиphp artisan translations:scan - У інтерфейсі перекладача вибрати нову мову — показані всі неперекладені ключи
- Перекласти вручну або через машинний переклад → перевірити → затвердити
- Увімкнути мову в мовному перемикачі сайту
Термін розробки: 4–6 тижнів для повної системи з інтерфейсом перекладачів, машинним перекладом, історією змін та експортом/імпортом.







