Браузерные расширения работают в особом окружении: изолированные контексты (background/service worker, content scripts, popup, options page), строгие политики безопасности (CSP), ограниченный доступ к DOM и асинхронная модель сообщений между частями расширения. Это напрямую влияет на архитектуру интернационализации.
Ключевая сложность — необходимость синхронного отображения переведённого интерфейса в нескольких изолированных контекстах при едином источнике состояния языка.
i18next в этом сценарии выступает как централизованный слой управления переводами, но требует адаптации под распределённую архитектуру расширений.
Типовая структура расширения:
Основная идея — единый экземпляр i18next или синхронизированные экземпляры.
Язык хранится в chrome.storage:
chrome.storage.local.set({ language: 'ru' });
Каждый контекст при инициализации читает значение:
import i18next from 'i18next';
chrome.storage.local.get(['language'], (res) => {
i18next.init({
lng: res.language || 'en',
fallbackLng: 'en',
resources: {
en: { translation: { hello: "Hello" } },
ru: { translation: { hello: "Привет" } }
}
});
});
Особенность расширений — необходимость отложенной инициализации до получения конфигурации.
Ключевые параметры:
lng — текущий языкfallbackLng — резервный языкresources или backend-загрузкаns — пространства имёнdefaultNS — основной namespaceПример конфигурации:
i18next.init({
lng: 'en',
fallbackLng: 'en',
ns: ['common', 'popup'],
defaultNS: 'common',
interpolation: {
escapeValue: false
}
});
В расширениях предпочтительно не хранить все переводы в bundle. Используется динамическая загрузка:
chrome.runtime.getURLПример загрузки:
fetch(chrome.runtime.getURL('locales/ru/translation.json'))
.then(res => res.json())
.then(data => {
i18next.addResourceBundle('ru', 'translation', data);
});
Основная проблема расширений — несогласованность состояний.
Решение — message passing:
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.type === 'LANG_CHANGE') {
chrome.storage.local.set({ language: msg.language });
sendResponse({ ok: true });
}
});
chrome.runtime.sendMessage({
type: 'LANG_CHANGE',
language: 'ru'
});
Каждый контекст должен реагировать на изменение языка:
chrome.storage.onChanged.addListener((changes) => {
if (changes.language) {
i18next.changeLanguage(changes.language.newValue);
}
});
Это обеспечивает мгновенное обновление UI без перезагрузки расширения.
Content scripts часто используют i18next для перевода страницы.
HTML-элементы помечаются атрибутами:
<span data-i18n="hello"></span>
Рендер:
document.querySelectorAll('[data-i18n]').forEach(el => {
el.textContent = i18next.t(el.getAttribute('data-i18n'));
});
Для SPA-страниц применяется MutationObserver:
const observer = new MutationObserver(() => {
document.querySelectorAll('[data-i18n]').forEach(el => {
el.textContent = i18next.t(el.dataset.i18n);
});
});
observer.observe(document.body, { childList: true, subtree: true });
Popup — краткоживущий контекст, поэтому инициализация должна быть лёгкой.
document.getElementById('title').textContent = i18next.t('popup.title');
Options page часто содержит переключатель языка:
document.getElementById('lang').addEventListener('change', (e) => {
const lng = e.target.value;
chrome.runtime.sendMessage({
type: 'LANG_CHANGE',
language: lng
});
i18next.changeLanguage(lng);
});
Используются:
chrome.storage.local — основной вариантchrome.storage.sync — синхронизация между
устройствамиРекомендуемая структура:
{
language: "ru",
fallback: "en"
}
Namespaces позволяют разделять переводы по функциональным блокам:
Инициализация:
i18next.init({
ns: ['popup', 'settings'],
defaultNS: 'popup'
});
Использование:
i18next.t('title', { ns: 'settings' });
Важные оптимизации:
Пример ленивой загрузки:
async function loadNamespace(lng, ns) {
const res = await fetch(
chrome.runtime.getURL(`locales/${lng}/${ns}.json`)
);
const data = await res.json();
i18next.addResourceBundle(lng, ns, data);
}
Manifest V3 накладывает строгие ограничения:
Следствие: все переводы должны быть статическими файлами или
загружаться через chrome.runtime.getURL.
Типичные проблемы:
Практика диагностики:
console.log(i18next.language);
console.log(i18next.options);
console.log(i18next.store.data);
i18next поддерживает debug-режим:
i18next.init({
debug: true
});
В расширениях это полезно для:
Plural rules критичны в UI расширений:
i18next.t('items', { count: 5 });
Ресурс:
{
"items": "{{count}} item",
"items_plural": "{{count}} items"
}
Интерполяция:
i18next.t('welcome', { name: 'Alex' });
{
"welcome": "Hello, {{name}}"
}
Такая модель обеспечивает устойчивую работу интернационализации в распределённой среде расширения браузера.