Обзор и установка
ts-intl — это библиотека интернационализации (i18n) без сторонних зависимостей, не привязанная к конкретному фреймворку (framework-agnostic), со строгой типобезопасностью.
Она предлагает API, спроектированный в стиле next-intl, но не зависит от Next.js или какого-либо конкретного UI-фреймворка, что делает её идеальным выбором для современной разработки на TypeScript.
Принципы архитектуры
- Ноль зависимостей: Ноль зависимостей во время выполнения (zero runtime dependencies) и отсутствие этапа генерации кода при сборке.
- Независимость от фреймворков: Поддержка Astro, React, Vue, Svelte, Node.js, Bun и браузерных окружений.
- Строгая типобезопасность: Статическая проверка типов для ключей перевода, параметров динамической интерполяции, пространств имён и структуры многоязычных словарей.
Установка
Установите @aaakul/ts-intl с помощью пакетного менеджера:
# pnpm
pnpm add @aaakul/ts-intl
# npm
npm install @aaakul/ts-intl
# yarn
yarn add @aaakul/ts-intl
# bun
bun add @aaakul/ts-intl
Быстрый старт
1. Определение словарей сообщений
Объявляйте TypeScript-объекты с утверждением as const, чтобы система типов могла вывести имена ключей и типы параметров:
// messages/ru-RU.ts
export default {
common: {
title: "Панель управления",
greeting: "Привет, {name: string}!",
items: {
one: "1 элемент в списке",
few: "{count} элемента в списке",
many: "{count} элементов в списке",
other: "{count} элемента в списке",
},
},
} as const;
// messages/en-US.ts
export default {
common: {
title: "System Dashboard",
greeting: "Hello, {name: string}!",
items: {
one: "1 item in total",
other: "{count} items in total",
},
},
} as const;
2. Инициализация экземпляра i18n
// i18n.ts
import { createI18n } from "@aaakul/ts-intl";
import ruRU from "./messages/ru-RU";
import enUS from "./messages/en-US";
export const {
getTranslations,
getFormatter,
isSupportedLanguage,
languages,
defaultLanguage,
} = createI18n({
defaultLanguage: "ru-RU",
messages: {
"ru-RU": ruRU,
"en-US": enUS,
},
});
export type Language = (typeof languages)[number];
3. Получение и вызов функций перевода
import { getTranslations } from "./i18n";
const t = getTranslations("ru-RU", "common");
// 1. Статический ключ
const title = t("title");
// Выведенный тип: (key: "title") => string
// Вывод: "Панель управления"
// 2. Интерполяция с параметрами
const greeting = t("greeting", { name: "Разработчик" });
// Выведенный тип: (key: "greeting", params: { name: string }) => string
// Вывод: "Привет, Разработчик!"
// 3. Обработка правил множественного числа
const items = t("items", { count: 5 });
// Выведенный тип: (key: "items", params: { count: number }) => string
// Вывод: "5 элементов в списке"
Форматы словарей
Статические определения в TypeScript (рекомендуется)
Определяйте словари с использованием as const, чтобы обеспечить валидацию ключей на этапе компиляции и извлечение типов параметров:
export default {
auth: {
login: "Войти",
welcome: "С возвращением, {username: string}!",
},
} as const;
Словари JSON
Примечание: Формат JSON также поддерживает автодополнение ключей и проверку орфографии. Поскольку JSON не поддерживает as const, параметры переменных шаблона будут выводиться с нестрогими (широкими) типами.
import ruRU from "./messages/ru-RU.json";
import enUS from "./messages/en-US.json";
export const { getTranslations } = createI18n({
defaultLanguage: "ru-RU",
messages: { "ru-RU": ruRU, "en-US": enUS },
});
Динамический импорт с помощью await import
В современных средах выполнения с поддержкой Top-Level Await (таких как Astro, Vite, Node 22+, Bun и др.), файлы словарей можно импортировать динамически через await import(...):
import { createI18n } from "@aaakul/ts-intl";
const ruRU = (await import("./messages/ru-RU.ts")).default;
const enUS = (await import("./messages/en-US.ts")).default;
export const { getTranslations } = createI18n({
defaultLanguage: "ru-RU",
messages: { "ru-RU": ruRU, "en-US": enUS },
});
Разделение кода по локалям (Locale Code Splitting)
В приложениях с поддержкой нескольких локалей сопоставление функций-загрузчиков (loader map) позволяет сборщикам разделить словарь каждого языка в отдельный чанк, загружая только активную локаль по требованию:
import { createI18n } from "@aaakul/ts-intl";
const loaders = {
"ru-RU": () => import("./messages/ru-RU.ts"),
"en-US": () => import("./messages/en-US.ts"),
} as const;
export type SupportedLanguage = keyof typeof loaders;
export async function loadI18n<L extends SupportedLanguage>(lang: L) {
const messages = (await loaders[lang]()).default;
return createI18n({
defaultLanguage: lang,
messages: { [lang]: messages } as Record<L, typeof messages>,
});
}
Типобезопасность: До тех пор, пока файлы словарей экспортируют
as constили используют JSON, динамически импортированные словари черезawait importполностью сохраняют автодополнение пространств имён, проверку строковых литералов ключей и извлечение типов параметров ({param: type}).