Автор:
    Создание:2026-09-02Последнее обновление:2026-09-02

    Как тестировать переводы без создания хрупких тестов

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

    Содержание

    Паттерны не зависят от библиотеки

    Каждый приведенный ниже паттерн работает на любом стеке i18n. Замените провайдер на I18nextProvider, NextIntlClientProvider или IntlProvider, и тесты останутся идентичными, поскольку они проверяют отрендеренный вывод, а не API конкретной библиотеки.

    Инструменты проверки покрытия также переносимы: с плагином Sync JSON, направленным на ваши каталоги, или адаптером совместимости, создающим псевдонимы для ваших текущих импортов, проверка покрытия выполняется прямо по существующему JSON.

    Определите, что вы на самом деле тестируете

    Качество перевода не проверяется тестами кода. Никакой ассерт не определит, звучит ли немецкий язык естественно, и попытка проверить это лишь заполнит тесты захардкоженными строками.

    Механические вещи, которые действительно стоит тестировать:

    Стоит тестировать Не стоит тестировать
    Каждая обязательная локаль имеет значение Красоту формулировок
    Правильная локаль передается в компонент Точный текст каждого лейбла
    Формы множественного числа для категорий Насколько переводчик был прилежен
    RTL-локали задают направление и зеркальность Каждую строку во всех языках
    Форматированные даты и числа учитывают локаль Внутреннюю корректность Intl

    Проверка покрытия должна проводиться в отдельном тесте на основе данных, а не в компонентных тестах. Это подробно описано в статье как находить недостающие переводы; данный материал посвящен остальным аспектам.

    Рендеринг внутри провайдера и поиск по ролям

    Основной паттерн состоит в том, чтобы монтировать компонент внутри провайдера локали и искать элементы по роли или test-id, а не по тексту.

    CartSummary.test.tsx
    import { render, screen } from "@testing-library/react";
    import { IntlayerProvider } from "react-intlayer/client";
    import { CartSummary } from "./CartSummary";
    
    test("рендерит заголовок сводки на французском", () => {
      render(
        <IntlayerProvider locale="fr-FR">
          <CartSummary />
        </IntlayerProvider>
      );
    
      expect(screen.getByRole("heading")).toBeInTheDocument();
    });
    

    Поиск getByRole("heading") устойчив к изменениям текста. getByText("Récapitulatif") сломается при первой же правке. Используйте точный текст только тогда, когда сама строка является объектом проверки, что бывает редко.

    Для таких атрибутов, как aria-label, вам понадобится необработанная строка, а не React-узел. В React записи useIntlayer предоставляют поле .value для этой цели.

    Параметризуйте тесты по локалям

    Один универсальный тест, запускаемый для каждой локали, гораздо полезнее десятка отдельных тестов для каждого языка.

    direction.test.tsx
    import { getHTMLTextDir } from "intlayer";
    import { render } from "@testing-library/react";
    import { IntlayerProvider } from "react-intlayer/client";
    
    describe.each(["en", "fr", "ja", "ar"])("локаль %s", (locale) => {
      it("рендерится без отката к ключу", () => {
        const { container } = render(
          <IntlayerProvider locale={locale}>
            <CartSummary />
          </IntlayerProvider>
        );
    
        // Если отрендерился ключ, значит поиск не удался.
        expect(container.textContent).not.toMatch(/^[a-z]+(\.[a-z]+)+$/);
      });
    
      it("устанавливает правильное направление текста", () => {
        expect(getHTMLTextDir(locale)).toBe(locale === "ar" ? "rtl" : "ltr");
      });
    });
    

    Первая проверка дает отличный универсальный результат: если поиск значения завершился неудачей и библиотека вывела ключ, в DOM окажется строка формата cart.summary.title. Это отлавливает целый класс ошибок без привязки к конкретным фразам.

    Псевдолокализация находит то, что скрыто от каталогов

    Добавьте тестовую локаль, которая видоизменяет каждую строку, например превращая Checkout в [!!! Çĥéçķöũţ !!!]. Затем отрендерите страницу на этом языке.

    Всё, что осталось на обычном английском, захардкожено в коде. Никакой аудит каталогов этого не заметит, так как для инструментов этой строки просто не существует. Скобки решают вторую задачу: они удлиняют текст примерно на 30 процентов, выявляя переполнения верстки еще до того, как они проявятся на немецком.

    Эту проверку лучше запускать в виде визуального или end-to-end прогона, а не юнит-теста, поскольку дефекты видны визуально.

    Для множественных чисел нужен тест на категорию, а не на язык

    Ошибки плюрализации часто остаются незамеченными, потому что в английском языке всего две формы, и большинство разработчиков тестируют только их. В польском языке четыре формы, в арабском шесть.

    plural.test.ts
    // Арабский проверяет zero, one, two, few, many, other.
    describe.each([0, 1, 2, 3, 11, 100])("число %i", (count) => {
      it("возвращает непустую строку на арабском", () => {
        expect(formatItems(count, "ar")).not.toBe("");
      });
    });
    

    Выбирайте значения, попадающие во все категории CLDR для самого сложного языка, вместо того чтобы проверять только 1 и 2. Intl.PluralRules сообщает, в какую категорию попадает число, позволяя составить выборку без угадывания. Подробнее о категориях в статье о формате сообщений ICU.

    Ловушка снэпшот-тестов (Snapshots)

    Снэпшоты и i18n плохо сочетаются. Снэпшот локализованного компонента фиксирует каждую строку: когда переводчик исправляет опечатку в португальском, зеленый тест становится красным в файле, который ревьюер не может объективно оценить. После нескольких ложных тревог кто-то запускает -u не глядя, и снэпшоты теряют всякий смысл.

    Если вы хотите использовать снэпшоты, делайте их только для одной локали и воспринимайте как структурную, а не контентную проверку. Всё языкозависимое должно проверяться явными ассертами.

    Тестируйте согласование локали, а не только рендеринг

    Самая частая ошибка i18n в продакшене, не отсутствие перевода строки. Это выбор неверной локали: URL указывает на /fr/, клиент считывает navigator.language, и они конфликтуют.

    Тестируйте порядок разрешения напрямую как чистую функцию, отдельно от компонентов:

    locale-resolution.test.ts
    it("отдает приоритет URL перед сохраненными настройками", () => {
      expect(resolveLocale({ url: "/fr/about", stored: "de", header: "ja" })).toBe(
        "fr"
      );
    });
    
    it("откатывается к заголовку, когда в URL нет префикса", () => {
      expect(resolveLocale({ url: "/about", stored: null, header: "ja" })).toBe(
        "ja"
      );
    });
    

    Это самый ценный i18n-тест, которого не хватает большинству проектов, и ему не требуется DOM.

    Что и где запускать

    • Unit: согласование локалей, форматтеры, категории множественного числа. Быстро, без DOM.
    • Component: один рендеринг с провайдером на локаль с проверкой ролей и отсутствия сырых ключей.
    • Coverage: проверка на основе данных, гарантирующая отсутствие пропусков в обязательных локалях.
    • Visual или E2E: прогон псевдолокализации и страницы с RTL, так как эти проблемы визуальные.

    Первые три запускайте в пайплайне на каждый коммит. Последний экономичнее запускать в ночных сборках.

    Распространенные ошибки

    • Проверка точного текста во всех тестах. Гарантирует удаление набора тестов через пару месяцев.
    • Снэпшоты локализованных компонентов. Переводчики ломают билд, а ревьюеры одобряют изменения вслепую.
    • Тестирование только локали по умолчанию. Единственная локаль, в которой перевод гарантированно есть.
    • Тестирование только 1 и 2 для множественного числа. Пропускает категории, отсутствующие в английском.
    • Мокирование библиотеки i18n. В итоге вы тестируете лишь то, что ваш мок возвращает строки.
    • Игнорирование тестов согласования. Самый частый сбой в продакшене и самый легкий для проверки.

    Полезные материалы

    Комментарии

    Пока нет комментариев. Будьте первым, кто поделится своими мыслями.

    Похожие сообщения

    Последние сообщения