Архитектура тестирования переводов в i18next строится вокруг проверки не только наличия строк, но и корректности всей цепочки интернационализации: загрузки ресурсов, работы интерполяции, правил плюрализации, обработки контекста и поведения при отсутствии ключей. Ошибки в i18n часто не проявляются как падение приложения — вместо этого возникают «тихие» дефекты интерфейса, что делает тестирование критически важным элементом качества.
Базовый уровень тестирования заключается в валидации переводческих файлов. Основная цель — обнаружить:
Типовой подход — построение эталонной локали (обычно en)
и сравнение всех остальных языков с её структурой.
import en from './locales/en.json';
import ru from './locales/ru.json';
function getKeys(obj, prefix = '') {
return Object.keys(obj).flatMap(key => {
const fullKey = prefix ? `${prefix}.${key}` : key;
return typeof obj[key] === 'object'
? getKeys(obj[key], fullKey)
: fullKey;
});
}
test('ru locale should match en structure', () => {
const enKeys = getKeys(en).sort();
const ruKeys = getKeys(ru).sort();
expect(ruKeys).toEqual(enKeys);
});
Такой подход не проверяет смысл переводов, но гарантирует структурную согласованность, что критично для i18next.
Инициализация i18next часто включает загрузчики ресурсов, fallback-локали и плагины. Ошибки здесь приводят к каскадным сбоям в приложении.
Тестирование конфигурации должно покрывать:
import i18n from './i18n';
test('i18n initializes with fallback language', async () => {
await i18n.init();
expect(i18n.language).toBeDefined();
expect(i18n.options.fallbackLng).toEqual(['en']);
});
Важный аспект — проверка асинхронной инициализации. При использовании backend-плагинов i18next может загружать переводы динамически, что требует ожидания завершения загрузки перед проверками.
Одна из ключевых стратегий тестирования — контроль поведения при отсутствии перевода. i18next может:
Тест должен фиксировать выбранную стратегию и предотвращать её случайное изменение.
test('missing key returns fallback or key itself', () => {
const result = i18n.t('non.existing.key');
expect(
result === 'non.existing.key' || typeof result === 'string'
).toBe(true);
});
Для зрелых систем предпочтительно явно фиксировать ожидаемое поведение.
Интерполяция — один из наиболее частых источников ошибок. i18next
поддерживает переменные вида {{value}}, которые должны
корректно подставляться в разных контекстах.
Ключевые сценарии:
test('interpolation works correctly', () => {
i18n.addResourceBundle('en', 'translation', {
welcome: 'Hello {{name}}'
});
const result = i18n.t('welcome', { name: 'Alex' });
expect(result).toBe('Hello Alex');
});
Отдельное внимание уделяется безопасности: проверяется, что опасные
символы экранируются при включённой опции escapeValue.
Плюрализация — сложный слой логики, зависящий от языка. Ошибки здесь часто незаметны визуально, но критичны лингвистически.
Тестируются:
test('pluralization in russian', () => {
i18n.addResourceBundle('ru', 'translation', {
apple_one: '{{count}} яблоко',
apple_few: '{{count}} яблока',
apple_many: '{{count}} яблок'
});
expect(i18n.t('apple', { count: 1 })).toBe('1 яблоко');
expect(i18n.t('apple', { count: 5 })).toBe('5 яблок');
});
Сложность возрастает при использовании ICU-подобных форматов или кастомных pluralResolvers.
Контекст позволяет изменять перевод в зависимости от состояния объекта или действия.
Примеры:
i18n.addResourceBundle('en', 'translation', {
friend_male: 'He is my friend',
friend_female: 'She is my friend'
});
test('context-based translation', () => {
expect(i18n.t('friend', { context: 'male' }))
.toBe('He is my friend');
});
Контекст часто ломается при рефакторинге ключей, поэтому тесты здесь выполняют роль защитного слоя.
При тестировании UI-компонентов (React, Vue, Svelte) важно изолировать логику перевода от рендеринга.
Стратегии:
tПример для React Testing Library:
jest.mock('./i18n', () => ({
t: key => key
}));
Это позволяет проверять структуру интерфейса без зависимости от переводов, ускоряя тесты и уменьшая их хрупкость.
Snapshot-тесты применяются для фиксации визуального состояния переведённых компонентов. Однако их использование требует осторожности: изменения текста могут приводить к ложным падениям тестов.
Хорошая практика:
test('renders translated header', () => {
const { container } = render(<Header />);
expect(container).toMatchSnapshot();
});
В i18next часто используется разделение на namespaces:
auth, common, dashboard,
errors.
Тестирование включает:
test('namespaces are loaded', async () => {
await i18n.loadNamespaces(['common', 'auth']);
expect(i18n.hasResourceBundle('en', 'common')).toBe(true);
expect(i18n.hasResourceBundle('en', 'auth')).toBe(true);
});
При динамической загрузке переводов важно проверять:
Используется мок backend-а:
i18n.services.backendConnector.load = jest.fn((lng, ns, cb) => {
cb(null, { hello: 'world' });
});
Псевдолокализация позволяет выявить UI-проблемы:
Пример трансформации:
В тестах проверяется, что UI корректно отображает расширенные строки.
Контрактный подход фиксирует правила:
Такие тесты часто интегрируются в CI как отдельный шаг, предотвращающий деградацию переводов.
End-to-end тесты проверяют реальное поведение приложения с включёнными локалями:
cy.visit('/settings');
cy.contains('Language').click();
cy.contains('Deutsch').click();
cy.contains('Einstellungen');
Такие проверки подтверждают, что i18next корректно интегрирован в полный цикл приложения.
В крупных приложениях переводы могут становиться причиной задержек. Тестирование включает:
Проверка обычно выполняется на уровне CI или профилировщика, фиксируя регрессии в локализации.