Импорт переводов обратно

В экосистеме i18next переводами управляют как набором ресурсов, обычно представленных JSON-структурами вида:

{
  "common": {
    "save": "Сохранить",
    "cancel": "Отмена"
  }
}

В классическом сценарии такие файлы считаются источником истины, однако в реальных проектах часто возникает обратный поток данных: переводы редактируются вне приложения (в CMS, сервисах локализации или через команду переводчиков) и затем должны быть импортированы обратно в приложение без потери структуры, ключей и fallback-логики.

Обратный импорт — это процесс синхронизации внешних переводов с внутренними ресурсами i18next.


Форматы источников переводов при обратном импорте

При возврате переводов в приложение встречаются несколько распространённых форматов:

JSON (i18next resource bundles) Наиболее прямой формат, совместимый с загрузчиками i18next.

{
  "common": {
    "save": "Save",
    "cancel": "Cancel"
  }
}

CSV (табличные выгрузки) Используется в системах локализации и при работе с переводчиками.

key en ru
common.save Save Сохранить
common.cancel Cancel Отмена

XLIFF / PO Форматы, характерные для профессиональных переводческих пайплайнов.

При обратном импорте все они должны быть нормализованы в структуру ресурсов i18next.


Архитектура процесса обратного импорта

Типовой процесс включает несколько стадий:

  1. Получение внешнего источника

    • API переводческой платформы
    • экспорт файлов (CSV/JSON/XLIFF)
    • webhook-обновления
  2. Нормализация данных

    • преобразование плоских ключей (common.save) в вложенные объекты
    • приведение кодов языков к стандарту BCP-47 (en, ru, pt-BR)
  3. Слияние с существующими ресурсами

    • merge стратегий: overwrite, deep-merge, fallback-preserve
    • защита системных ключей
  4. Загрузка в i18next resource store

    • динамическое обновление через addResourceBundle
    • или полная перезагрузка через backend loader

Программная загрузка перевода обратно в runtime

i18next предоставляет API для динамического обновления ресурсов без перезапуска приложения.

Добавление нового набора переводов

i18next.addResourceBundle(
  'ru',
  'common',
  {
    save: 'Сохранить',
    cancel: 'Отмена'
  },
  true,
  true
);

Параметры:

  • lng — язык
  • namespace — пространство имён
  • resources — объект переводов
  • deep — глубокое слияние
  • overwrite — перезапись существующих ключей

Обновление переводов через внешние backend-загрузчики

При использовании backend-модулей (например, файлового или HTTP) обратный импорт часто реализуется как перезапись файлов или обновление источника данных.

Пример с файловым backend:

import Backend from 'i18next-fs-backend';

i18next.use(Backend).init({
  backend: {
    loadPath: './locales/{{lng}}/{{ns}}.json'
  }
});

После импорта внешняя система может перезаписать:

locales/ru/common.json

Далее i18next либо автоматически подхватит изменения (watch mode), либо выполнит повторную загрузку ресурсов.


Стратегии слияния переводов

При обратном импорте ключевая проблема — конфликт версий переводов.

1. Полная перезапись (overwrite)

Используется, когда внешняя система считается единственным источником истины.

i18next.addResourceBundle('ru', 'common', newTranslations, true, true);

Недостаток — возможная потеря локальных изменений.


2. Глубокое слияние (deep merge)

Сохраняет существующие ключи и добавляет новые.

i18next.addResourceBundle('ru', 'common', newTranslations, false, true);

Поведение:

  • новые ключи добавляются
  • существующие перезаписываются точечно
  • отсутствующие сохраняются

3. Приоритет fallback-структур

Используется в системах с несколькими уровнями переводов:

  • локальные (dev)
  • импортированные (CMS)
  • глобальные fallback

i18next автоматически использует fallbackLng:

i18next.init({
  fallbackLng: 'en',
  lng: 'ru'
});

Нормализация ключей при обратном импорте

Внешние системы часто хранят ключи в плоском виде:

{
  "common.save": "Сохранить"
}

Для i18next требуется вложенная структура:

{
  "common": {
    "save": "Сохранить"
  }
}

Функция нормализации:

function normalize(flat) {
  const result = {};

  for (const key in flat) {
    const parts = key.split('.');
    let current = result;

    parts.forEach((part, index) => {
      if (index === parts.length - 1) {
        current[part] = flat[key];
      } else {
        current[part] = current[part] || {};
        current = current[part];
      }
    });
  }

  return result;
}

Обратный импорт через HTTP API

В архитектуре микросервисов переводы часто приходят через API:

async function loadTranslations(lng) {
  const res = await fetch(`/api/translations/${lng}`);
  const data = await res.json();

  Object.keys(data).forEach(ns => {
    i18next.addResourceBundle(lng, ns, data[ns], true, true);
  });
}

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

  • обновлять переводы без деплоя
  • централизовать хранение локализаций
  • синхронизировать несколько приложений

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

При интеграции внешних переводов важно учитывать версионность:

{
  "version": 12,
  "translations": {
    "common": {
      "save": "Сохранить"
    }
  }
}

Логика обработки:

  • если версия выше текущей — применить изменения
  • если ниже — игнорировать или логировать конфликт
  • при равенстве — выполнять merge

Очистка устаревших ключей

Обратный импорт часто приносит избыточные ключи. Для синхронизации используется процедура pruning:

function pruneTranslations(current, incoming) {
  const result = {};

  for (const key in current) {
    if (incoming[key]) {
      result[key] = incoming[key];
    }
  }

  return result;
}

Это позволяет:

  • удалять устаревшие строки
  • предотвращать накопление мусора
  • поддерживать актуальную структуру ресурсов

Инвалидация кэша переводов

При динамическом импорте важно учитывать кэширование:

i18next.services.resourceStore.data = {};
i18next.reloadResources();

Также в HTTP-бэкендах применяются:

  • cache-busting query params
  • ETag
  • version hashes

Поток данных в CI/CD при обратном импорте

Типичная схема:

  1. Переводчики обновляют строки в системе локализации
  2. Система экспортирует JSON/CSV
  3. CI pipeline загружает файл
  4. Скрипт нормализует и сливает данные
  5. Результат коммитится в репозиторий
  6. Приложение получает обновлённые ресурсы на этапе сборки или runtime

Обработка конфликтов переводов

Конфликты возникают при одновременных изменениях:

  • локальные правки разработчика
  • внешние правки переводчика

Стратегии:

Timestamp-based

последняя модификация выигрывает

Namespace isolation

разные источники → разные namespaces

Manual resolution layer

конфликтные ключи помещаются в отдельный файл review.json

Инкрементальный импорт переводов

Для больших проектов используется частичный импорт:

i18next.addResourceBundle('ru', 'common', {
  save: 'Сохранить'
}, true, true);

или выборочное обновление:

i18next.addResource('ru', 'common', 'save', 'Сохранить');

Это снижает риск перезаписи и позволяет обновлять только изменённые строки.


Асинхронная синхронизация ресурсов

При работе в браузере или Node.js можно организовать фоновую синхронизацию:

setInterval(async () => {
  const updates = await fetchUpdates();

  updates.forEach(u => {
    i18next.addResourceBundle(u.lng, u.ns, u.data, true, true);
  });
}, 60000);

Такой подход применяется для:

  • live-редактирования интерфейса
  • A/B тестирования текстов
  • динамических маркетинговых сообщений