Автоматизация тестов локализации в JavaScript-проектах с использованием i18next направлена на выявление ошибок перевода, несоответствий ключей, пропущенных строк и регрессионных проблем, возникающих при изменении словарей или логики переключения языков. При росте числа поддерживаемых языков ручная проверка становится неприемлемой по стоимости и ненадёжности, поэтому тесты становятся частью конвейера сборки.
Ключевая цель автоматизации заключается не в проверке качества перевода как такового, а в гарантии целостности системы интернационализации: корректности ключей, наличия ресурсов, правильной интерполяции и стабильности поведения переключения языков.
В типичном проекте на i18next присутствуют следующие элементы:
t,
i18n.t)Автоматизация должна покрывать все уровни, начиная от чистой логики i18next и заканчивая компонентами интерфейса.
Первый уровень проверки — корректная инициализация библиотеки. Это позволяет выявить ошибки конфигурации до запуска приложения.
Пример теста на Jest:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
beforeAll(async () => {
await i18n
.use(initReactI18next)
.init({
lng: 'en',
fallbackLng: 'en',
resources: {
en: {
translation: {
hello: 'Hello'
}
}
}
});
});
test('i18next initialized correctly', () => {
expect(i18n.isInitialized).toBe(true);
});
Проверка состояния isInitialized позволяет убедиться,
что конфигурация не содержит критических ошибок, блокирующих работу
системы перевода.
Одна из наиболее частых проблем — отсутствие ключей в одном из языков. Автоматизация решает эту задачу через сравнение ресурсов.
import en from '../locales/en/translation.json';
import ru from '../locales/ru/translation.json';
test('translation keys match between locales', () => {
const enKeys = Object.keys(en);
const ruKeys = Object.keys(ru);
expect(ruKeys.sort()).toEqual(enKeys.sort());
});
Этот подход обеспечивает базовую синхронизацию словарей, предотвращая ситуацию, когда интерфейс частично отображается на fallback-языке.
Для вложенных структур используется рекурсивное извлечение ключей:
function extractKeys(obj, prefix = '') {
return Object.keys(obj).reduce((acc, key) => {
const fullKey = prefix ? `${prefix}.${key}` : key;
if (typeof obj[key] === 'object') {
return acc.concat(extractKeys(obj[key], fullKey));
}
return acc.concat(fullKey);
}, []);
}
Функция перевода является ядром i18next. Её тестирование позволяет гарантировать корректность интерполяции и fallback-логики.
test('translation function returns correct value', () => {
i18n.changeLanguage('en');
expect(i18n.t('hello')).toBe('Hello');
});
Особое внимание уделяется параметрам интерполяции:
test('interpolation works correctly', () => {
i18n.init({
lng: 'en',
resources: {
en: {
translation: {
welcome: 'Hello {{name}}'
}
}
}
});
expect(i18n.t('welcome', { name: 'Alex' })).toBe('Hello Alex');
});
Ошибки интерполяции часто возникают при изменении шаблонов переводов, поэтому такие тесты предотвращают скрытые регрессии.
Fallback-механизм критически важен для стабильности интерфейса. Тестирование заключается в эмуляции отсутствующего ключа.
test('fallback language is used when key is missing', () => {
i18n.init({
lng: 'ru',
fallbackLng: 'en',
resources: {
en: {
translation: {
hello: 'Hello'
}
},
ru: {
translation: {}
}
}
});
expect(i18n.t('hello')).toBe('Hello');
});
Такой тест фиксирует корректность поведения цепочки поиска переводов.
При использовании React интеграция с react-i18next
требует проверки рендеринга компонентов в разных языковых
состояниях.
import { render } from '@testing-library/react';
import { I18nextProvider } from 'react-i18next';
test('component renders translated text', () => {
const { getByText } = render(
<I18nextProvider i18n={i18n}>
<MyComponent />
</I18nextProvider>
);
expect(getByText('Hello')).toBeInTheDocument();
});
Для изоляции тестов часто используется мок i18next:
jest.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key) => key
})
}));
Такой подход позволяет проверять структуру компонентов без зависимости от реальных переводов.
Snapshot-тесты позволяют фиксировать визуальное состояние компонентов при разных языках.
test('matches snapshot in English', () => {
i18n.changeLanguage('en');
const tree = render(<MyComponent />);
expect(tree).toMatchSnapshot();
});
test('matches snapshot in Russian', () => {
i18n.changeLanguage('ru');
const tree = render(<MyComponent />);
expect(tree).toMatchSnapshot();
});
Сравнение снапшотов выявляет не только ошибки перевода, но и проблемы верстки, возникающие из-за разной длины строк.
Переключение языка — динамический процесс, который должен корректно обновлять состояние приложения.
test('language switching updates translations', async () => {
await i18n.changeLanguage('ru');
expect(i18n.language).toBe('ru');
expect(i18n.t('hello')).toBeDefined();
});
В UI-тестах проверяется реактивность интерфейса после смены языка.
E2E-тесты с использованием Cypress или Playwright позволяют проверить поведение системы в реальном браузере.
describe('Localization flow', () => {
it('changes language on UI interaction', () => {
cy.visit('/');
cy.get('[data-testid="lang-switch"]').click();
cy.contains('Привет');
});
});
E2E-уровень особенно важен для проверки динамически загружаемых переводов и асинхронных backend-плагинов i18next.
При использовании i18next-http-backend или кастомных
загрузчиков важно тестировать корректность получения переводов.
import Backend from 'i18next-http-backend';
test('loads translations from backend', async () => {
await i18n.use(Backend).init({
lng: 'en',
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json'
}
});
const value = i18n.t('hello');
expect(value).toBeDefined();
});
Мокирование HTTP-запросов через msw позволяет избежать
нестабильности тестов.
Регрессии в локализации часто возникают при удалении или переименовании ключей. Для их контроля применяется статический анализ ресурсов.
function findMissingKeys(base, target) {
return base.filter(key => !target.includes(key));
}
test('no missing translation keys', () => {
const missing = findMissingKeys(
Object.keys(en),
Object.keys(ru)
);
expect(missing).toEqual([]);
});
Дополнительно используется линтинг JSON-файлов переводов, проверяющий структуру и запрещающий пустые строки.
Автоматизация тестирования локализации приобретает практический смысл только при включении в конвейер сборки. Типовой пайплайн включает:
Каждый этап выполняется при изменении переводов или логики интернационализации. Это позволяет выявлять ошибки до попадания в продакшен.
Помимо runtime-тестов используются статические инструменты:
t()Пример простого анализа неиспользуемых ключей:
function findUnusedKeys(translations, usedKeys) {
return Object.keys(translations).filter(
key => !usedKeys.includes(key)
);
}
Такие проверки помогают поддерживать словари в актуальном состоянии и избегать технического долга в локализации.