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

    Как находить недостающие переводы раньше ваших пользователей

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

    Содержание

    Это работает независимо от используемой библиотеки

    Здесь нет привязки к какому-то одному стеку. Описанные ниже уровни обнаружения одинаково функционируют в i18next, react-i18next, next-intl, react-intl, vue-i18n, next-translate или Lingui, поскольку все они одинаково разрешают ключи и одинаково дают сбои.

    Инструменты также переносимы. Если ваши переводы сейчас хранятся в JSON-каталогах, плагин Sync JSON подключает Intlayer к этим файлам, предоставляя команды аудита, автозаполнения и тестирования без переноса контента и без изменения импортов:

    intlayer.config.ts
    import { syncJSON } from "@intlayer/sync-json-plugin";
    
    const config = {
      plugins: [
        syncJSON({
          source: ({ key, locale }) => `./locales/${locale}/${key}.json`,
          format: "i18next", // или "icu" для next-intl / react-intl
        }),
      ],
    };
    
    export default config;
    

    Если вы хотите сохранить привычный runtime API, адаптеры совместимости создают псевдонимы для useTranslation, $t и других функций на уровне сборщика. В любом случае воспринимайте приведенные команды как наглядную реализацию концепции, а не жесткое требование.

    Почему они незаметны

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

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

    Поэтому вопрос звучит не «как обрабатывать отсутствующие переводы во время выполнения». А «как сделать так, чтобы PR с отсутствующим переводом было невозможно смержить».

    Четыре уровня, на которых их можно поймать

    Каждый уровень отслеживает то, что упускают остальные. Имеет смысл использовать сразу несколько.

    УровеньОбнаруживаетПропускает
    ТипыКлючи, которых вообще не существуетКлюч существует, но не переведен в ja
    ЛинтерЗахардкоженные строки, не переданные в i18nКлючи, отсутствующие в каталоге
    АудитПокрытие локалями всех объявленных ключейТекст, который вообще не был помечен переводом
    Тесты рендераКлючи, которые разрешаются с ошибкамиВсё, что не покрыто тестами

    Чаще всего команды упускают третью строчку: они уверены в валидности ключей, но ничто не проверяет, что во всех восемнадцати локалях действительно есть значения.

    Уровень 1: делайте ключ типом, а не строкой

    t("checkout.summry.total") — это опечатка, которая успешно компилируется. Если ваши ключи — обычные строки, каждое переименование несет риск в продакшене, а каждое удаление оставляет ключ-сироту.

    Типизированные ключи превращают это в ошибку сборки. react-i18next поддерживает это через declaration merging, next-intl выводит типы из структуры сообщений, Lingui генерирует идентификаторы из исходного текста, а Intlayer создает строгие типы из файлов объявлений контента. Все варианты работают; различается лишь объем ручной настройки.

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

    Уровень 2: линтинг строк, которые так и не стали ключами

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

    Плагин ESLint для Intlayer решает эту проблему правилом no-raw-text, дополненным no-unused-content для обратной ситуации: контент объявлен, но больше нигде не используется.

    eslint.config.mjs
    import intlayer from "@intlayer/eslint-plugin";
    
    export default [
      intlayer.configs.recommended,
      {
        rules: {
          "@intlayer/no-raw-text": "error",
          "@intlayer/no-unused-content": "warn",
        },
      },
    ];
    

    no-unused-content предотвращает бесконтрольное разрастание каталогов. Мертвые ключи не ломают сборку, но неоправданно увеличивают счета за услуги переводчиков. Полный список правил смотрите в документации плагина ESLint.

    Уровень 3: аудит покрытия локалей

    Именно этот уровень дает прямой ответ на главный вопрос. Intlayer поставляет его в виде CLI-команды:

    bash
    npx intlayer content test
    

    Она считывает настроенные локали и объявленные словари, после чего выдает отчет: каким ключам не хватает каких языков и в каком файле.

    Важная деталь перед добавлением в скрипты: CLI выводит отчет, но завершается с кодом 0. Если вы вставите команду в пайплайн в надежде сломать билд, вы получите зеленую галочку с простыней текста в логах, которую никто не станет читать. Для остановки билда используйте программный API, описанный ниже.

    Уровень 4: проверки через assertions в тестовом наборе

    listMissingTranslations() возвращает те же данные аудита в виде объекта, что идеально подходит для создания блокирующего барьера (gate).

    i18n.test.ts
    /* @vitest-environment node */
    import { listMissingTranslations } from "intlayer/cli";
    import { describe, expect, it } from "vitest";
    
    describe("translations", () => {
      it("не содержит пропусков в обязательных локалях", async () => {
        const result = await listMissingTranslations();
    
        if (result.missingRequiredLocales.length > 0) {
          console.log(result.missingTranslations);
        }
    
        expect(result.missingRequiredLocales).toHaveLength(0);
      });
    });
    

    Возвращаются три полезных поля:

    • missingTranslations: по каждому ключу указано, каких локалей не хватает и в каком файле. Это выводится при падении теста.
    • missingLocales: объединение всех недостающих локалей по всем ключам.
    • missingRequiredLocales: ограничено списком requiredLocales из конфигурации (или всеми локалями, если параметр не задан).

    requiredLocales делает проверку реалистичной

    Поддержка восемнадцати языков вовсе не означает, что все восемнадцать должны быть переведены на 100% для выката релиза. У большинства команд есть критический уровень языков, блокирующий поставку, и уровень языков, которые допереводятся по мере готовности.

    intlayer.config.ts
    import { Locales, type IntlayerConfig } from "intlayer";
    
    const config: IntlayerConfig = {
      internationalization: {
        locales: [
          Locales.ENGLISH,
          Locales.FRENCH,
          Locales.JAPANESE,
          Locales.POLISH,
        ],
        requiredLocales: [Locales.ENGLISH, Locales.FRENCH],
        defaultLocale: Locales.ENGLISH,
      },
    };
    
    export default config;
    

    Без requiredLocales каждая объявленная локаль считается строго обязательной, и билд будет красным, пока не подоспеет последний перевод. Именно из-за этого проверки часто просто отключают целиком.

    Поиск пробелов, уже попавших в продакшен

    Предыдущие шаги защищают от появления новых проблем. Для приложения, которое уже выкачено, эффективны два приема:

    Псевдолокализация. Запустите тестовую локаль, где каждый символ трансформируется, например [!!! Ĉĥéçķöũţ !!!]. Всё, что останется на чистом английском, захардкожено в коде. Это находит за 10 минут то, что аудит каталогов не способен увидеть по своей архитектуре.

    Краулинг собственного сайта. Если у вас локализованные URL, скачайте выборку страниц для каждого языка и выполните поиск английских строк в HTML. Страница по адресу /ja/, содержащая "Add to cart", указывает либо на пропущенный перевод, либо на непредвиденный фолбэк.

    bash
    curl -s https://example.com/ja/checkout | grep -c "Add to cart"
    

    Заполнение пробелов

    Когда вы знаете, чего не хватает, intlayer fill заполняет пустые значения, а опция autoFill может создавать файлы деклараций для каждой локали по мере их добавления. См. autoFill.

    Здесь важно смотреть на вещи трезво: автоматический машинный перевод превращает видимую брешь в невидимую. Ключ заполнен, тесты зеленые, но текст никто не вычитывал. Используйте это для снятия блокировки релиза, но обязательно отдавайте на проверку человеку тексты, влияющие на финансовые и юридические решения.

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

    • Считать фолбэк инструментом защиты. Это механизм аварийного отображения, а не страховка. Тихо показанная английская страница — это незамеченный баг.
    • Полагаться на вывод CLI для блокировки CI. intlayer content test завершается с кодом 0. Используйте тесты с ассертами.
    • Делать обязательными абсолютно все локали. Проверку отключат при первой же сорванной поставке.
    • Аудит каталогов без проверки реального рендеринга. Захардкоженные строки по определению не видны в каталогах.
    • Тестирование только языка по умолчанию. Это единственный язык, который гарантированно не будет отсутствовать.
    • Завершение процесса лишь машинным заполнением. Зеленые тесты при невычитанных текстах.

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

    Комментарии

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

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

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