Сервис для переводов

Архитектурная роль сервиса переводов

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

Основная задача сервиса заключается в унификации доступа к переводам независимо от того, откуда они поступают: из локальных JSON-файлов, удалённого API, базы данных или CDN.

Ключевые функции сервиса:

  • загрузка переводов по языкам и namespace
  • кеширование результатов
  • обработка fallback-языков
  • синхронизация обновлений переводов
  • обеспечение асинхронного доступа к ресурсам

Модель загрузки переводов

В основе системы лежит разделение переводов по двум измерениям:

  • язык (lng)en, ru, de
  • namespacecommon, auth, errors

Сервис переводов работает с запросами вида:

/locales/{lng}/{namespace}.json

или через API:

GET /translations?lng=ru&ns=common

Внутри i18next эти запросы обрабатываются backend-модулем, который подключается как плагин.


Backend-слой как реализация сервиса

Сервис переводов в большинстве случаев реализуется через backend-плагин. Он отвечает за фактическое получение данных.

Пример стандартного HTTP backend:

import i18next from 'i18next'
import HttpBackend from 'i18next-http-backend'

i18next
  .use(HttpBackend)
  .init({
    lng: 'ru',
    ns: ['common', 'auth'],
    defaultNS: 'common',
    backend: {
      loadPath: '/locales/{{lng}}/{{ns}}.json'
    }
  })

Здесь backend выполняет роль сервиса, который:

  • формирует URL запроса
  • выполняет HTTP-запрос
  • возвращает JSON-структуру переводов

Кастомный сервис переводов

Гибкость i18next позволяет реализовать собственный сервис, заменяя стандартный backend.

Кастомный backend реализуется через интерфейс:

  • read(language, namespace, callback)
  • create() (опционально)
  • init() (инициализация)
  • type — идентификатор backend-а

Пример реализации сервиса на основе API:

class TranslationService {
  constructor(services, options = {}) {
    this.services = services
    this.options = options
  }

  read(language, namespace, callback) {
    fetch(`${this.options.api}/translations?lng=${language}&ns=${namespace}`)
      .then(res => res.json())
      .then(data => callback(null, data))
      .catch(err => callback(err, false))
  }

  init(services, backendOptions) {
    this.services = services
    this.options = backendOptions
  }

  type = 'customBackend'
}

Подключение:

import i18next from 'i18next'

i18next.init({
  lng: 'ru',
  ns: ['common'],
  backend: {
    api: 'https://example.com'
  },
  backend: TranslationService
})

Поток данных в сервисе переводов

При запросе строки перевода выполняется последовательность:

  1. Проверка наличия перевода в памяти
  2. Если отсутствует — запрос в сервис
  3. Загрузка JSON через backend
  4. Сохранение в ресурсах i18next
  5. Возврат перевода через t()

Сервис выступает промежуточным слоем между t() и источником данных.


Кеширование переводов

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

Типовые стратегии:

1. Memory cache

Переводы сохраняются в runtime:

i18next.init({
  saveMissing: false,
  cache: {
    enabled: true
  }
})

2. HTTP cache

Использование заголовков:

Cache-Control: max-age=86400
ETag: "translation-v3"

3. LocalStorage cache

Используется для офлайн-режима через плагины.


Fallback-механизм сервиса

Сервис переводов поддерживает цепочку языков:

ru → en → default

Если ключ отсутствует:

  1. выполняется запрос к fallback-языку
  2. затем к базовому языку
  3. затем возвращается ключ

Настройка:

i18next.init({
  fallbackLng: 'en',
  load: 'currentOnly'
})

Fallback является частью логики сервиса, а не только функции t().


Асинхронная природа загрузки

Сервис переводов работает асинхронно, что влияет на инициализацию приложения.

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

i18next
  .init({
    lng: 'ru',
    backend: {
      loadPath: '/locales/{{lng}}/{{ns}}.json'
    }
  })
  .then(() => {
    console.log('translations loaded')
  })

Пока сервис не завершил загрузку, переводы могут быть пустыми или заменяться fallback-значениями.


Интерполяция как часть сервиса

Сервис переводов не только доставляет строки, но и участвует в подготовке данных для интерполяции.

Пример:

{
  "welcome": "Привет, {{name}}"
}

Использование:

t('welcome', { name: 'Alex' })

Внутри i18next происходит:

  • получение строки из сервиса
  • передача в интерполятор
  • подстановка значений
  • возврат финального результата

Мультисервисная архитектура переводов

В сложных системах сервис переводов может объединять несколько источников:

  • HTTP backend
  • локальные JSON ресурсы
  • CMS (например, headless)
  • feature-flag системы

Пример комбинированного подхода:

const backend = {
  read: (lng, ns, cb) => {
    const local = localCache.get(lng, ns)

    if (local) {
      cb(null, local)
      return
    }

    api.fetchTranslations(lng, ns)
      .then(data => cb(null, data))
      .catch(err => cb(err))
  }
}

Такая схема позволяет строить приоритетные цепочки источников.


Ошибки и устойчивость сервиса

Сервис переводов обязан корректно обрабатывать сбои:

  • недоступность API
  • повреждённый JSON
  • несоответствие namespace
  • таймауты

Стратегии обработки:

read(language, namespace, callback) {
  try {
    fetchData(language, namespace)
  } catch (e) {
    callback(null, {})
  }
}

В i18next пустой объект переводов не ломает приложение, а перевод переходит в fallback-режим.


Сервис как слой абстракции

Ключевая концепция заключается в том, что сервис переводов отделяет:

  • потребление переводов (t())
  • источник данных (API, файлы, CMS)
  • стратегию загрузки (lazy, preload, cache)

Это позволяет изменять backend без изменения логики интерфейса.

Пример смены источника:

// Было: HTTP backend
// Стало: CMS backend

i18next.use(CMSBackend).init({...})

При этом интерфейс t() остаётся неизменным.


Производительность сервисного слоя

Оптимизация сервиса переводов включает:

  • ленивую загрузку namespace
  • предварительную загрузку критических языков
  • дедупликацию запросов
  • пакетную загрузку переводов

Пример lazy-loading:

i18next.loadNamespaces(['auth'])

Это уменьшает объём первичной загрузки и ускоряет рендер интерфейса.


Расширяемость сервиса

Сервис переводов в i18next поддерживает расширение через middleware-подобные плагины:

  • postProcessor (обработка строк)
  • languageDetector (определение языка)
  • backend (источник данных)

Каждый слой добавляет функциональность без изменения ядра.

Пример postProcessor:

const uppercase = {
  type: 'postProcessor',
  process: (value) => value.toUpperCase()
}

i18next.use(uppercase)

Роль сервиса в масштабируемых системах

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

  • фронтенд не знает источник переводов
  • backend может меняться динамически
  • переводы версионируются
  • возможна A/B подмена текстов

Такой подход делает систему локализации независимой от архитектуры UI и позволяет управлять переводами как отдельным продуктовым слоем.