В экосистеме FormatJS управление переводами строится вокруг строгой структуры сообщений, извлечения строк и использования ICU MessageFormat. При росте приложения ключевой проблемой становится синхронизация исходных сообщений и их локализованных версий, особенно когда интерфейс развивается параллельно с переводами. Версионирование переводов решает задачу согласованности между кодом и многоязычными ресурсами, предотвращая рассинхронизацию интерфейса, устаревшие строки и некорректные форматы данных.
FormatJS опирается на концепцию сообщений (messages), где каждая строка имеет:
id)defaultMessage)description, опционально)Пример:
{
"cart.itemCount": {
"defaultMessage": "В корзине {count, plural, one {# товар} few {# товара} many {# товаров} other {# товаров}}",
"description": "Количество товаров в корзине"
}
}
Ключевое свойство такой модели — независимость перевода от конкретной
локали. Однако именно это приводит к необходимости отслеживания
изменений структуры сообщений, поскольку изменение
defaultMessage может ломать смысл перевода, даже если
id остаётся прежним.
Версионирование становится критичным в следующих случаях:
id{count},
{name})Пример конфликтного изменения:
- "cart.itemCount": "В корзине {count} товаров"
+ "cart.itemCount": "В корзине {count} товаров на сумму {price}"
Если переводы для разных языков не обновлены, приложение продолжит использовать старые структуры, что может привести к ошибкам форматирования ICU.
Один из самых строгих подходов — включение версии прямо в
id:
"cart.itemCount.v1"
"cart.itemCount.v2"
Преимущества:
Недостатки:
Такой подход используется редко в больших системах, но полезен при радикальных переработках интерфейса.
Переводы разделяются по версиям приложения:
/locales
/v1
ru.json
en.json
/v2
ru.json
en.json
В коде выбирается версия:
const messages = loadMessages(locale, appVersion);
Преимущества:
Недостатки:
Более гибкий подход — вычисление хэша от
defaultMessage:
import { createHash } from "crypto";
function messageId(defaultMessage) {
return createHash("md5").update(defaultMessage).digest("hex");
}
Пример:
"e4b7c3a91d2f..."
Преимущества:
Недостатки:
Наиболее распространённый подход в FormatJS — стабильные
id с контролем изменений через CI.
"cart.checkout.buttonLabel"
Версионирование осуществляется не через ключи, а через анализ изменений структуры сообщения.
FormatJS использует ICU MessageFormat, где критичны:
Изменение структуры без обновления переводов приводит к ошибкам runtime.
Пример несовместимого изменения:
- "{count, plural, one {# item} other {# items}}"
+ "{count} items"
Для предотвращения таких проблем используется:
Инструменты FormatJS позволяют автоматизировать извлечение и проверку переводов:
formatjs extract "src/**/*.{js,ts,tsx}" --out-file messages.json
Это создаёт единый файл сообщений, который может использоваться как источник истины.
Дальнейшая проверка изменений:
formatjs compile messages.json --out-file compiled.json
В процессе CI можно сравнивать:
При обновлении приложения критически важно различать:
- "Добавить в корзину"
+ "Положить в корзину"
Перевод можно оставить без изменений или обновить без влияния на ICU.
- "В корзине {count} товаров"
+ "В корзине {count, plural, one {# товар} other {# товаров}}"
Требует обязательного обновления всех локалей.
- "Привет, {name}"
+ "Привет, {name}. У вас {count} уведомлений"
Это уже изменение контракта сообщения, требующее синхронизации переводов.
При работе с несколькими версиями важно учитывать fallback-механизм:
const messages = {
ru: {
v2: {...},
v1: {...}
}
};
Если ключ отсутствует в новой версии, система может:
defaultMessageДля выявления проблем совместимости используется псевдолокализация:
"Hello {name}" → "Ħëļļø {name}"
Это позволяет обнаружить:
Типичный пайплайн включает:
extract)Пример логики проверки:
if (removedMessages.length > 0 && env === "production") {
throw new Error("Translation keys removed");
}
При масштабировании системы переводов ключевыми становятся следующие принципы:
id после публикацииПри переходе между версиями интерфейса применяются сценарии:
Стабильный id становится контрактом между:
Любое нарушение этого контракта фактически является изменением версии сообщения, даже если формально ключ не изменился.