Автор:
    Дата створення:2026-09-02Останнє оновлення:2026-09-02

    Як тестувати переклади без створення крихких тестів

    Більшість наборів тестів i18n зазнають невдачі з однієї з двох причин. Або вони перевіряють буквальний текст, через що будь-яка зміна формулювання ламає п'ятдесят тестів і команда просто видаляє їх. Або вони рендерять усе лише в локалі за замовчуванням, нічого не доводячи щодо решти сімнадцяти мов. Обидва шляхи ведуть до одного результату: набору тестів, якому ніхто не довіряє.

    Зміст

    Патерни не залежать від бібліотеки

    Кожен наведений нижче патерн працює на будь-якому стеку i18n. Замініть провайдер на I18nextProvider, NextIntlClientProvider або IntlProvider, і тести залишаться ідентичними, оскільки вони перевіряють відрендерений результат, а не API конкретної бібліотеки.

    Інструменти перевірки покриття також легко переносяться: із плагіном Sync JSON, спрямованим на ваші каталоги, або адаптером сумісності, що створює псевдоніми для ваших поточних імпортів, перевірка покриття запускається безпосередньо над наявним JSON.

    Визначте, що ви насправді тестуєте

    Якість перекладу неможливо перевірити тестом коду. Жодне твердження не підкаже, чи звучить німецька мова природно, а спроба це зробити призведе до наповнення коду захардкодzoneними рядками.

    Механічні речі, які дійсно варто тестувати:

    Варто тестувати Не варто тестувати
    Кожна обов'язкова локаль має значення Чи вдалим є формулювання
    Правильна локаль передається в компонент Точний текст кожної мітки
    Форми множини працюють для кожної категорії Чи сумлінно працював перекладач
    RTL-локалі задають напрямок та дзеркальність Кожен рядок у кожній мові
    Форматовані дати та числа враховують локаль Внутрішню коректність Intl

    Перевірка покриття має виконуватися в одному тесті на основі даних, а не в тестах окремих компонентів. Це докладно описано у статті як виявляти відсутні переклади; цей матеріал присвячений решті аспектів.

    Рендеринг всередині провайдера та запит за роллю

    Основний патерн полягає в монтуванні компонента всередині провайдера локалі та пошуку елементів за роллю (role) або тестовим 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 записи 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 не читаючи diff, і снепшоти втрачають будь-який сенс.

    Якщо ви бажаєте використовувати снепшоти, робіть їх лише для однієї локалі та сприймайте як структурну, а не смислову перевірку. Усе специфічне для окремих мов має перевірятися явними твердженнями.

    Тестуйте узгодження локалі, а не лише рендеринг

    Найпоширеніша помилка 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: один рендеринг із провайдером на локаль, перевірка ролей і відсутності сирих ключів.
    • Покриття: тест на основі даних, що гарантує відсутність пропусків у необхідних локалях.
    • Visual або E2E: перевірка псевдолокалізації та сторінки з RTL, оскільки ці проблеми є візуальними.

    Залишайте перші три в пайплайні CI на кожен коміт. Останнє дешевше запускати під час нічних збірок.

    Поширені помилки

    • Перевірка точного тексту всюди. Гарантує видалення тестового набору через кілька місяців.
    • Снепшоти локалізованих компонентів. Перекладачі ламають збірку, а рев'юери схвалюють зміни наосліп.
    • Тестування лише локалі за замовчуванням. Єдина локаль, яка точно не може бути відсутня.
    • Тестування лише 1 та 2 для множини. Пропускає категорії, яких немає в англійській мові.
    • Повне мокування бібліотеки i18n. Тоді ви лише перевіряєте, чи повертає мок рядки.
    • Ігнорування тестів узгодження локалей. Найчастіша проблема на практиці, яку найпростіше протестувати.

    Корисні матеріали

    Коментарі

    Поки що немає коментарів. Будьте першим, хто поділиться своїми думками.

    Схожі публікації

    Останні публікації