-Опыт отделения многоязычных ресурсов (i18n) в отдельный репозиторий и добавления автоматической проверки и развертывания-
1. Введение
Сервис туристического контента, за который я отвечал, поддерживает четыре языка и всего пять языковых пакетов: корейский в качестве базового языка, английский, узбекский (с латинской и кириллической письменностью) и каракалпакский.
Весь текст на экранах — от подписей кнопок до информационных сообщений и сообщений валидации — зависит от JSON-файлов переводов для конкретных языков.
В этой статье я обобщаю свой опыт отделения этих файлов переводов, которые хранились в кодовой базе frontend, в отдельный репозиторий, а также создания скриптов проверки и развертывания, автоматически обнаруживающих отсутствующие переводы.
2. Ограничения существующего подхода
До разделения мы хранили JSON-файлы для конкретных языков в проекте frontend. Когда требовалось изменить текст, мы редактировали файл базового языка, а затем вручную добавляли тот же ключ в каждый из четырех остальных языковых файлов.
У этого подхода было три основные проблемы.
-
Даже такая небольшая задача, как изменение одного фрагмента текста, требовала пересборки и развертывания всего frontend, прежде чем изменение вступало в силу.
-
Даже для исправления одной опечатки приходилось ждать завершения сборки CI.
-
Поскольку ключи копировались вручную для каждого языка, некоторые ключи часто отсутствовали только в отдельных языках. В результате на экранах, используемых носителями этих языков, отображался сам ключ перевода или пустая строка.
-
Процесса проверки, который обнаруживал бы такие пропуски, не было, поэтому узнать о проблеме можно было только после развертывания — когда ее находила команда QA или реальный пользователь.
-
Поскольку узбекский язык хранился в отдельных файлах для латинской и кириллической письменности, несоответствия между двумя вариантами встречались особенно часто.
3. Улучшение: отделение репозитория языковых пакетов
Исходя из того, что ресурсы переводов являются статическими файлами и не обязательно должны использовать одну кодовую базу или цикл развертывания с приложением, мы создали репозиторий, предназначенный исключительно для управления JSON-файлами переводов.
В фактической инфраструктуре сервиса есть Kubernetes nginx Pod, который обслуживает статические файлы. Обновление только JSON-файлов в этом Pod означает, что новые переводы начинают отображаться со следующего запроса, без повторного развертывания frontend.
Отделение ресурсов в собственный репозиторий также значительно упростило добавление логики проверки.
Поддерживаемые языки и структура представлены ниже.
|
Код языка |
Язык |
Примечания |
|---|---|---|
|
ko-KR |
Корейский |
Базовый язык |
|
en-US |
Английский |
- |
|
uz-Latn / uz-Cyrl |
Узбекский (латинская/кириллическая письменность) |
Управляется отдельно для каждой письменности |
|
kaa-Latn |
Каракалпакский (латинская письменность) |
- |
Папка каждого языка разделена на четыре файла по области экрана: common, admin, validation и user.
Это уменьшает количество конфликтов, возникающих, когда несколько человек одновременно редактируют один большой файл, и позволяет сразу понять, какая область затронута при возникновении проблемы.
4. Автоматическая проверка ключей переводов: validate.js
Мы написали скрипт проверки, используя только встроенные модули Node.js (fs, path).
Основная идея заключается в том, чтобы использовать корейский язык в качестве эталона и сравнивать с ним наборы ключей в остальных языковых файлах. Поскольку JSON содержит вложенные структуры, такие как button.save, сначала мы преобразуем структуры в плоский список путей с точечной нотацией, а затем сравниваем их.
function collectKeys(obj, prefix = "") {
const keys = [];
Object.keys(obj).forEach((key) => {
const currentPath = prefix ? `${prefix}.${key}` : key;
if (typeof obj[key] === "object" && obj[key] !== null) {
keys.push(...collectKeys(obj[key], currentPath));
} else {
keys.push(currentPath);
}
});
return keys;
}
Ключи, существующие в базовом языке, но отсутствующие в целевом языке, классифицируются как Error, а ключи, существующие только в целевом языке, — как Warning.
Если обнаруживается хотя бы один Error, представляющий реальную проблему, способную нарушить работу экрана, скрипт возвращает код завершения через process.exit(1). Поскольку этот скрипт вызывается на первом этапе скрипта развертывания, аварийное завершение на этом этапе полностью предотвращает выполнение последующих этапов развертывания.
Такая структура делает развертывание физически невозможным при отсутствии переводов.
5. Автоматизированный конвейер развертывания
Развертывание состоит из пяти этапов: проверка → сжатие tar → автоматическое обнаружение nginx Pod → копирование в Pod с помощью kubectl cp → распаковка и очистка внутри Pod.
Поскольку в команде есть пользователи macOS и Windows, мы создали отдельные bash- и PowerShell-скрипты и позаботились о том, чтобы оба всегда выполнялись в одном и том же порядке.
Поскольку имя Pod меняется при каждом повторном развертывании, мы не задавали его жестко, а запрашивали каждый раз.
$POD = kubectl get pod -n $NAMESPACE --no-headers |
Where-Object { $_ -match "^nginx" } |
ForEach-Object { ($_ -split "\s+")[0] } |
Select-Object -First 1
Для справки: запуск tar в macOS также создает файлы метаданных в формате ._*. Если эти файлы остаются внутри Pod, они засоряют каталог статических файлов.
Bash-скрипт удаляет их с помощью переменной окружения COPYFILE_DISABLE=1 и параметра --exclude. Windows tar изначально не создает эти файлы метаданных, поэтому в PowerShell-скрипте соответствующая логика отсутствует.
Вместо того чтобы принудительно объединять два скрипта, мы решили сохранить для каждой платформы только те операции, которые действительно ей необходимы, после запуска и непосредственной проверки скриптов в соответствующей среде.
Устранение неполадок: развертывание, завершавшееся ошибкой только в Windows
Изначально мы объединяли несколько команд оболочки, которые должны были выполняться внутри Pod — смену каталогов, распаковку архивов, удаление файлов и вывод содержимого каталогов, — в одну here-string и целиком передавали ее в kubectl exec.
# 최초 버전 — 여러 명령을 하나의 here-string으로 묶어 전달
$cleanupScript = @"
cd $NGINX_HTML
tar -xvf $TAR_NAME
rm $TAR_NAME
"@
kubectl exec -n $NAMESPACE $POD -- /bin/sh -c $cleanupScript
Этот подход работал без проблем в bash на macOS, но Windows PowerShell иначе обрабатывает передачу аргументов внешним процессам (kubectl.exe), поэтому строки, содержащие переводы строк, обрезались посередине или разделялись построчно. В результате возникала ошибка: внутри Pod выполнялась только половина команды.
Выявив причину, мы отказались от объединения нескольких команд в одну и разделили вызовы kubectl exec по командам.
# 수정 버전 — 명령을 kubectl exec 호출 단위로 분리
kubectl exec -n $NAMESPACE $POD -- rm "${NGINX_HTML}/locales"
kubectl exec -n $NAMESPACE $POD -- tar -xvf "${NGINX_HTML}/${TAR_NAME}" -C $NGINX_HTML
kubectl exec -n $NAMESPACE $POD -- rm "${NGINX_HTML}/${TAR_NAME}"
Разделение команд на отдельные вызовы устранило проблему с обрезанием аргументов в PowerShell.
Количество вызовов увеличилось до четырёх, но поскольку это были локальные операции в одном Pod, затраты были незначительными. Этот опыт также напомнил мне, что кроссплатформенные скрипты нельзя считать полностью готовыми, пока они не запущены непосредственно в обеих средах.
6. Преимущества и заключение
После разделения репозитория и добавления автоматизации обновление текстов было полностью отделено от развёртывания фронтенда и теперь может выполняться за считанные секунды одним запуском скрипта. Отсутствующие переводы также обнаруживаются автоматически до развёртывания, что исключает ситуации, когда некорректный экран видят только пользователи определённого языка.
Кроме того, теперь развёртывание можно выполнять по одной и той же процедуре как на macOS, так и на Windows, что снижает нагрузку, связанную с концентрацией знаний о развёртывании у одного человека.
Это был не проект, в котором внедрялась какая-то новая и впечатляющая технология.
Он полностью состоял из сочетания знакомых инструментов, таких как скрипт проверки примерно на 100 строк, kubectl и tar. Тем не менее он оказался значимым, поскольку позволил точно выявить и устранить действительно регулярно возникавшие проблемы: необходимость повторного развёртывания, отсутствующие переводы и ошибки скриптов, связанные с конкретной платформой.
В частности, устраняя одну за другой проблемы, которые становятся очевидными только при непосредственном выполнении, например проблему передачи аргументов в kubectl exec, воспроизводимую только в Windows, я хотел бы в дальнейшем улучшить процесс, связав этап проверки с GitLab CI, чтобы проблемы автоматически отсеивались до слияния.
ноль