Интернационализация TypeScript приложений: полный гайд с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Как перестать бояться и начать локализовать: интернационализация TypeScript приложений

Вы когда-нибудь задумывались, почему одни проекты взлетают до международного уровня, а другие так и остаются "русскоязычными стартапами"? Чаще всего ответ прост: локализация. Да, та самая страшная аббревиатура i18n, от которой у многих разработчиков начинается нервный тик. Но давайте честно: сделать интернационализацию TypeScript приложения — это не rocket science. Это просто набор правильных подходов и инструментов. Сегодня мы разберем это на практике, с живыми примерами кода, без воды и заумных теорий.

Почему TypeScript — идеальный кандидат для i18n?

TypeScript даёт нам то, чего так не хватает обычному JavaScript: статическую типизацию. Когда речь заходит о переводах, это становится суперсилой. Представьте: вы меняете ключ перевода, а TypeScript ещё до запуска приложения кричит: "Эй, ты забыл обновить перевод в испанской локали!". Согласитесь, это лучше, чем ловить баги в продакшене.

Более того, современные библиотеки для i18n в TypeScript позволяют генерировать типы прямо из файлов с переводами. Это значит, что автодополнение в IDE будет работать как часы. Никаких магических строк, никаких опечаток. Только чистый, безопасный код.

Выбираем библиотеку: i18next vs react-intl

На рынке два главных игрока: i18next и react-intl (часть FormatJS). Оба отлично работают с TypeScript, но подходы разные.

  • i18next — швейцарский нож интернационализации. Поддерживает плюрализацию, интерполяцию, контексты, ленивую загрузку. Отлично подходит для больших проектов.
  • react-intl — более легковесный, заточен под React. Использует компонентный подход (<FormattedMessage>) и мощную систему ICU MessageFormat.

В этой статье мы будем использовать i18next с официальным React-адаптером react-i18next. Почему? Потому что это самый популярный стек, и он даёт максимальный контроль.

Пошаговая настройка: от нуля до первого перевода

Давайте сразу к делу. Установим зависимости:

npm install i18next react-i18next i18next-browser-languagedetector

Теперь создадим конфигурационный файл. Это сердце нашей интернационализации TypeScript приложения.

// i18n.ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';

import enTranslation from './locales/en/translation.json';
import ruTranslation from './locales/ru/translation.json';

i18n
  .use(LanguageDetector) // автоопределение языка браузера
  .use(initReactI18next) // интеграция с React
  .init({
    resources: {
      en: { translation: enTranslation },
      ru: { translation: ruTranslation },
    },
    fallbackLng: 'en', // если перевод не найден
    interpolation: {
      escapeValue: false, // React уже экранирует
    },
  });

export default i18n;

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

Структура файлов переводов

Создадим папку locales и внутри неё подпапки для каждого языка. Вот пример файла en/translation.json:

{
  "welcome": "Welcome to our application!",
  "description": "This is a {{framework}} app with i18n support.",
  "items": {
    "zero": "No items",
    "one": "One item",
    "other": "{{count}} items"
  }
}

А вот его русский аналог:

{
  "welcome": "Добро пожаловать в наше приложение!",
  "description": "Это приложение на {{framework}} с поддержкой i18n.",
  "items": {
    "zero": "Нет элементов",
    "one": "Один элемент",
    "other": "{{count}} элемента"
  }
}

Типизация переводов: магия TypeScript

Теперь самое интересное. Чтобы TypeScript знал о наших переводах, нужно создать типы. Есть два пути: ручной (для перфекционистов) и автоматический (для прагматиков).

Автоматический путь: Используем пакет i18next-resources-for-ts или typesafe-i18n. Но мы пойдём ручным — так вы лучше поймёте механику.

// types/i18n.d.ts
import en from '../locales/en/translation.json';

type TranslationKeys = keyof typeof en;

declare module 'i18next' {
  interface CustomTypeOptions {
    resources: {
      translation: typeof en;
    };
  }
}

Теперь, когда вы будете писать t(', IDE будет предлагать только существующие ключи. Никаких опечаток. Красота!

Использование в React-компонентах

Подключим наш конфиг в корне приложения:

// App.tsx
import React from 'react';
import { I18nextProvider } from 'react-i18next';
import i18n from './i18n';
import MainPage from './pages/MainPage';

function App() {
  return (
    <I18nextProvider i18n={i18n}>
      <MainPage />
    </I18nextProvider>
  );
}

А теперь сам компонент с переводами:

// pages/MainPage.tsx
import { useTranslation } from 'react-i18next';

const MainPage: React.FC = () => {
  const { t, i18n } = useTranslation();

  const changeLanguage = (lng: string) => {
    i18n.changeLanguage(lng);
  };

  return (
    <div>
      <h1>{t('welcome')}</h1>
      <p>{t('description', { framework: 'React' })}</p>
      
      <div>
        <button onClick={() => changeLanguage('en')}>English</button>
        <button onClick={() => changeLanguage('ru')}>Русский</button>
      </div>
    </div>
  );
};

Видите { framework: 'React' }? Это интерполяция. В файле перевода у нас есть плейсхолдер {{framework}}, который заменяется на переданное значение. Удобно для динамических текстов.

Работа с плюрализацией

Русский язык — настоящий вызов для плюрализации. В английском есть one/other, а у нас — zero/one/few/many/other. i18next справляется на ура

Блоги

Рекомендаций по книгам