Мемоизация переводов в i18next — это совокупность механизмов и
практик, направленных на уменьшение повторных вычислений результата
функции t при одинаковых входных данных. В отличие от
классической мемоизации чистых функций, здесь учитываются дополнительные
факторы: язык, namespace, параметры интерполяции, контекст, формы
множественного числа и постобработка.
Функция перевода формально выглядит как:
t(key, options)
Результат зависит не только от key, но и от текущего
языка, конфигурации ресурсов и параметров options. Поэтому
мемоизация в i18next — это многомерное кеширование.
i18next использует несколько уровней хранения данных, которые уже обеспечивают частичную мемоизацию:
resources = {
ru: {
translation: {
hello: "Привет"
}
}
}
Доступ к ресурсу При вызове
t('hello') происходит прямой доступ к объекту словаря без
повторной загрузки или пересборки структуры.
Кеширование разрешения ключей Внутри i18next используется оптимизация поиска по ключам, чтобы не обходить дерево переводов при каждом вызове.
Результат t() можно рассматривать как функцию от набора
параметров:
lng)Условная модель кеш-ключа:
cacheKey = key + lng + ns + JSON.stringify(optionsSubset)
При этом важно, что разные вызовы:
t('item', { count: 1 })
t('item', { count: 2 })
дают разные результаты и не могут быть объединены в один кеш-элемент.
Интерполяция — одна из самых затратных частей в процессе перевода, особенно при больших объектах:
t('welcome_user', { name: 'Alex', age: 30 })
Шаблон:
{
"welcome_user": "Hello {{name}}, age {{age}}"
}
Каждый вызов требует:
i18next частично оптимизирует этот процесс через внутренние кеши интерполяции шаблонов, чтобы не пересобирать парсер для одного и того же ключа.
Смена языка является глобальным триггером инвалидирования результатов:
i18next.changeLanguage('en')
После этого:
t() начинает использовать другой набор ресурсовТипичная ошибка:
const cache = new Map();
function translate(key) {
if (cache.has(key)) return cache.get(key);
const value = i18next.t(key);
cache.set(key, value);
return value;
}
Такой кеш некорректен, поскольку игнорирует lng.
Корректный вариант:
function makeCacheKey(key, lng) {
return `${lng}:${key}`;
}
В связке с React наиболее важный аспект — контроль перерисовок.
Хук:
const { t, i18n } = useTranslation();
Возвращает t, который зависит от текущего языка через
контекст i18next.
function Title() {
const { t } = useTranslation();
return <h1>{t('title')}</h1>;
}
При смене языка компонент перерисуется, даже если ключи не изменились.
При сложных вычислениях на основе переводов используется мемоизация результата:
const label = useMemo(() => {
return t('complex_label', { value });
}, [t, value]);
Однако важно учитывать: t меняется при смене языка,
поэтому это корректно инвалидирует кеш.
Частая задача — рендер списков:
const items = ['home', 'about', 'contact'];
Без мемоизации:
items.map(key => <li key={key}>{t(key)}</li>);
Оптимизированный вариант:
const translatedItems = useMemo(() => {
return items.map(key => t(key));
}, [i18n.language]);
Здесь зависимость от языка важнее, чем от t.
Разделение переводов на namespaces снижает объем повторных операций:
i18next.init({
ns: ['common', 'auth', 'profile'],
defaultNS: 'common'
});
При загрузке:
t('key', { ns: 'auth' }) ограничивает область
поискаПомимо внутреннего механизма i18next, применяются внешние слои кеширования.
import Backend from 'i18next-http-backend';
i18next.use(Backend).init({
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
requestOptions: {
cache: 'force-cache'
}
}
});
Часто используется плагин:
i18next поддерживает постпроцессоры:
t('price', { postProcess: 'currency' })
Такие операции не кешируются автоматически, так как:
Поэтому мемоизация таких результатов требует отдельного слоя:
const formatCache = new Map();
function formatPrice(value, lng) {
const key = `${lng}:${value}`;
if (formatCache.has(key)) return formatCache.get(key);
const result = new Intl.NumberFormat(lng, {
style: 'currency',
currency: 'USD'
}).format(value);
formatCache.set(key, result);
return result;
}
Наибольшую сложность представляют динамические переводы:
t('items_in_cart', { count, itemName })
Проблемы:
Поэтому используется стратегия:
Корректные подходы учитывают язык и namespace:
const translationCache = new Map();
function translate(key, lng, options = {}) {
const cacheKey = `${lng}:${key}:${JSON.stringify(options)}`;
if (translationCache.has(cacheKey)) {
return translationCache.get(cacheKey);
}
const result = i18next.t(key, options);
translationCache.set(cacheKey, result);
return result;
}
Более устойчивый вариант — ограничение кеширования только статических ключей:
const staticCache = new Map();
function tStatic(key) {
const lng = i18next.language;
const cacheKey = `${lng}:${key}`;
if (staticCache.has(cacheKey)) return staticCache.get(cacheKey);
const result = i18next.t(key);
staticCache.set(cacheKey, result);
return result;
}
При lazy-loading namespaces:
t() может быть асинхронно “дорогим”Типичный сценарий:
await i18next.loadNamespaces('profile');
t('profile:title');
До загрузки кеширование ограничено отсутствием данных, после — работает в полном объеме.
При использовании множественных форм:
t('item', { count: 3 })
i18next применяет:
countФактически формируется отдельный кеш по каждому значению
count, что увеличивает гранулярность кеша.