Организация файлов переводов

Организация переводов в экосистеме FormatJS строится вокруг разделения сообщений по локалям и строгого соблюдения структуры идентификаторов. Основой служат JSON-файлы, содержащие пары «ключ — значение», где значение представляет собой строку в формате ICU Message Syntax, поддерживающую интерполяцию, плюрализацию и условные конструкции.

Базовый принцип организации заключается в том, что каждый язык (локаль) хранится отдельно, а структура ключей остаётся идентичной между всеми локалями. Это обеспечивает предсказуемость и упрощает масштабирование.

Простейший вариант структуры:

/locales
  /en.json
  /ru.json
  /kk.json

Каждый файл содержит одинаковые ключи:

{
  "app.title": "Application",
  "app.description": "Modern interface for managing data",
  "user.login": "Log in",
  "user.logout": "Log out"
}
{
  "app.title": "Приложение",
  "app.description": "Современный интерфейс для управления данными",
  "user.login": "Войти",
  "user.logout": "Выйти"
}

Ключевая характеристика такого подхода — полная синхронизация структуры между локалями. Любое расхождение приводит к падению fallback-механизмов или отсутствию перевода.


Иерархическая организация ключей

По мере роста приложения плоская структура становится неудобной. В FormatJS принято использовать точечную нотацию для группировки сообщений по доменам:

  • auth.login.title
  • auth.login.button
  • dashboard.stats.users
  • dashboard.stats.revenue

Такая организация позволяет логически группировать переводы, не разрывая их по отдельным файлам:

{
  "auth.login.title": "Welcome back",
  "auth.login.button": "Sign in"
}
{
  "auth.login.title": "С возвращением",
  "auth.login.button": "Войти"
}

При этом структура остаётся линейной внутри файла, что важно для совместимости с большинством загрузчиков FormatJS, не требующих вложенных объектов.


Разделение переводов по модулям

В крупных приложениях предпочтительно разделять переводы не только по локалям, но и по функциональным модулям. Это снижает размер файлов и упрощает сопровождение.

Типичная структура:

/locales
  /en
    common.json
    auth.json
    dashboard.json
  /ru
    common.json
    auth.json
    dashboard.json

Пример auth.json:

{
  "login.title": "Sign in to your account",
  "login.error": "Invalid credentials",
  "register.title": "Create account"
}

Такой подход позволяет:

  • загружать переводы по требованию;
  • уменьшать initial bundle size;
  • распределять ответственность между командами.

ICU-сообщения как часть структуры файлов

FormatJS использует ICU Message Format, что напрямую влияет на организацию содержимого файлов переводов. Вместо простых строк значения могут содержать выражения:

{
  "messages.inbox": "You have {count, plural, one {# message} other {# messages}}"
}
{
  "messages.inbox": "У вас {count, plural, one {# сообщение} few {# сообщения} many {# сообщений} other {# сообщения}}"
}

Такие конструкции требуют строгого единообразия ключей между локалями, поскольку изменение структуры ICU-сообщения в одной локали без соответствующих изменений в других приводит к неконсистентности UI.


Группировка по контексту использования

Практика контекстной группировки предполагает разделение сообщений по их назначению в интерфейсе:

  • интерфейсные элементы (button, label, placeholder)
  • уведомления (toast, alert)
  • ошибки (error)
  • системные сообщения (system)

Пример:

{
  "button.save": "Save",
  "button.cancel": "Cancel",
  "error.required": "This field is required",
  "toast.success": "Saved successfully"
}

Такой подход упрощает поиск и предотвращает дублирование ключей.


Файловая стратегия: flat vs modular

Существует два основных подхода:

Плоский файл (flat structure) Все переводы находятся в одном JSON на локаль.

Преимущества:

  • простота загрузки;
  • отсутствие сложной сборки.

Недостатки:

  • сложность масштабирования;
  • конфликты при работе команды.

Модульная структура (modular structure) Переводы разбиваются по доменам и частям приложения.

Преимущества:

  • масштабируемость;
  • независимая разработка модулей;
  • возможность lazy-loading.

Недостатки:

  • необходимость сборки или динамической агрегации;
  • более сложная конфигурация загрузчика.

Версионирование переводов

В долгоживущих проектах структура файлов переводов может изменяться. Для контроля совместимости применяется версионирование:

/locales/v1/en.json
/locales/v2/en.json

или через метаданные:

{
  "_version": "2.3",
  "app.title": "Dashboard"
}

Это позволяет отслеживать изменения контрактов сообщений между релизами.


Fallback-механизмы и их влияние на структуру

FormatJS поддерживает fallback локалей, что влияет на организацию файлов. Обычно формируется цепочка:

  • ru-KZ
  • ru
  • en

При отсутствии ключа в целевой локали происходит поиск в родительской.

Структура файлов должна обеспечивать:

  • наличие базовой локали (en);
  • неполные локали, наследующие базовую структуру;
  • отсутствие «пустых» ключей без fallback.

Генерация и синхронизация ключей

При использовании FormatJS часто применяется автоматическая генерация списка сообщений из кода. В этом случае структура файлов переводов становится отражением исходного кода, а не ручной разметкой.

Типичный процесс:

  1. Извлечение сообщений (babel-plugin-react-intl);
  2. Формирование master-файла;
  3. Распространение ключей по локалям.

Результирующий файл приобретает строгую форму:

{
  "nav.home": "Home",
  "nav.about": "About",
  "nav.contact": "Contact"
}

Консистентность структуры и предотвращение дрейфа

Основная проблема больших систем переводов — дрейф структуры, при котором разные локали начинают расходиться по набору ключей.

Для предотвращения используются:

  • строгие схемы JSON;
  • линтеры переводов;
  • автоматическая синхронизация ключей;
  • CI-проверки на отсутствие недостающих сообщений.

Структурная целостность становится критическим свойством системы, так как FormatJS предполагает идентичность ключей как базовый контракт между кодом и локализацией.


Оптимизация хранения переводов

При увеличении объёма сообщений применяется нормализация структуры:

  • вынос повторяющихся фраз в shared-файлы;
  • использование namespace-ключей;
  • разделение по продуктовым доменам.

Пример shared:

common.json
{
  "ok": "OK",
  "cancel": "Cancel",
  "loading": "Loading..."
}

Эти значения импортируются во все модули без дублирования, что снижает риск расхождения формулировок и упрощает поддержку интерфейса.