Installs into .claude/skills of the current project.
Are you the author of I18n?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/vitammiin-i18n-agent-vorcl-flow)
---
name: i18n
description: Интернационализация и локализация (i18n/l10n) прикладного кода — запрет языкового хардкода пользовательских строк в мультиязычных проектах. Определить, мультиязычен ли репозиторий (i18n-инфраструктура, несколько локалей), и адаптироваться. Ключи и словари, плюрализация/род (ICU MessageFormat), интерполяция, форматирование дат/чисел/валют (Intl API), RTL и логические CSS-свойства. Библиотеки по стеку — next-intl (Next.js App Router), react-i18next (React SPA), i18next + Accept-Language (Node-бэкенд). Use ВСЕГДА при генерации/рефакторинге кода с пользовательским текстом (UI, сообщения об ошибках/валидации, письма, уведомления) и при аудите на языковой хардкод.
version: 1.0.0
---
# Навык: Интернационализация (i18n / l10n)
Правило проекта: **в мультиязычном коде — ноль языкового хардкода**. Любая строка, которую видит пользователь, идёт через слой перевода, а не литералом в разметке/ответе. Подход — **определять и адаптировать**: сначала пойми, мультиязычен ли репозиторий, потом применяй строгость по ситуации.
**Навигатор.** С чего начать: [режим проекта](#определять-и-адаптировать-первый-шаг--всегда) → [что переводится, что нет](#что-переводится-а-что--нет) → перед сдачей [анти-паттерны](#анти-паттерны-ищи-и-убирай) и [чек-лист](#чек-лист-сдачи). Как писать: [ключи и словари](#ключи-и-словари) · [плюрализация/род (ICU)](#плюрализация-род-интерполяция-icu-messageformat) · [даты/числа/валюты (Intl)](#форматирование-встроенный-intl--не-хардкодь-формат). Справочно: [библиотеки по стеку](#библиотеки-по-стеку-мнение-по-умолчанию) · [frontend](#frontend-специфика) / [backend](#backend-специфика) · [RTL](#rtl-и-направление-текста).
## Определять и адаптировать (первый шаг — всегда)
Прежде чем писать текст в код, определи режим проекта по признакам:
- **i18n-пакет** в `package.json`: `next-intl`, `i18next`/`react-i18next`/`i18next-http-middleware`, `@formatjs/*`, `react-intl`, `vue-i18n`, `svelte-i18n`.
- **Каталоги словарей**: `messages/`, `locales/`, `i18n/`, `translations/`, `lang/`; файлы `<locale>.json`/`.po`/`.ftl`.
- **Роутинг локали**: сегмент `app/[locale]/`, `middleware.ts` с matcher локалей, `next.config` с `i18n`/`localePrefix`.
- **Конфиг локалей**: `SUPPORTED_LOCALES`, `LOCALES`, `LANGUAGES`, `defaultLocale`, `<html lang>`/`dir`.
Режимы:
- **Мультиязычный** (нашёл инфраструктуру и/или ≥2 локалей) → **строгий запрет хардкода**: каждая пользовательская строка через существующий слой перевода (`t()`/`useTranslation`/`getTranslations`), в тот же формат ключей и словарей, что уже в проекте. Нет ключа — заведи его в словари всех активных локалей (недостающие — с TODO-пометкой на перевод), но **не** оставляй литерал в коде.
- **Одноязычный** (инфраструктуры нет) → **не навязывай** полный i18n без запроса, но: строки держи **вынесенными/централизованными** (константы/словарь модуля), а не разбросанными по JSX; помечай найденный хардкод как потенциальный долг; форматирование дат/чисел/валют всё равно через `Intl`. Так проект остаётся i18n-ready без лишнего бойлерплейта.
- **Неоднозначно** (смешанные признаки) → уточни у пользователя целевые локали; по умолчанию считай мультиязычным, если есть хоть один пользователь-facing поток с не-дефолтным языком.
## Что переводится, а что — нет
**Пользовательская строка (переводится):** UI-текст, заголовки, кнопки, лейблы, плейсхолдеры, подсказки/tooltips, `alt`, `aria-label`/`aria-*`-тексты, тосты/снекбары, сообщения об ошибках и валидации, письма/пуши/SMS, тексты PDF/квитанций, пустые состояния, единицы измерения словами.
**НЕ переводится (оставляй как есть):** логи и лог-сообщения (один язык, обычно English), стабильные машинные **коды ошибок** (`AUTH_INVALID_TOKEN`), идентификаторы/enum-значения, имена полей API и ключи JSON, ключи аналитики/событий, технические имена, значения в БД, регэкспы/форматные шаблоны как таковые.
## Ключи и словари
- **Именование ключей** — по смыслу и области, не по тексту: `feature.section.action` (`checkout.summary.payButton`), а не `"Оплатить"` как ключ. Единый стиль (обычно `camelCase`/`dot.notation`) — как уже в проекте.
- **Где хранить**: централизованные `messages/<locale>.json` (next-intl) или feature-level `features/<feature>/locales/<locale>.json` — следуй существующей раскладке проекта, не смешивай две.
- **Типизация ключей**: типобезопасные ключи (генерация типов из словаря / `keyof`-типы next-intl / `i18next` `resources` типизация) — опечатка в ключе должна ловиться компилятором.
- **Никакой конкатенации переведённых кусков.** `t('greeting') + name` и `"Найдено " + n + " шт."` — запрещено: порядок слов и согласование зависят от языка. Используй интерполяцию и ICU (ниже).
- **Фолбэк-локаль** задан явно; отсутствующий ключ не должен рушить UI — фолбэк на дефолт + предупреждение в dev.
## Плюрализация, род, интерполяция (ICU MessageFormat)
- **Множественное число** — через `plural` (не `if (n === 1)`): у языков до 6 категорий (`zero/one/two/few/many/other`), русский/польский/арабский — нетривиальные правила. `{count, plural, one {# товар} few {# товара} many {# товаров} other {# товара}}`.
- **Род/выбор** — через `select`: `{gender, select, female {приглашена} male {приглашён} other {приглашён(а)}}`.
- **Интерполяция** — только именованными плейсхолдерами (`{name}`, `{count}`), значения подставляет библиотека; не собирай предложение из кусков вручную.
- Движки ICU: next-intl и `react-intl`/`@formatjs` — из коробки; для i18next — плагин `i18next-icu`.
## Форматирование (встроенный Intl — не хардкодь формат)
- **Даты/время** — `Intl.DateTimeFormat(locale, …)` (или обёртки библиотеки); не `dd.MM.yyyy` строкой. Учитывай **таймзону** (`timeZone`), храни в UTC, форматируй под пользователя.
- **Числа/валюты** — `Intl.NumberFormat(locale, { style: 'currency', currency })`; разделители, знак валюты и позиция зависят от локали.
- **Относительное время** — `Intl.RelativeTimeFormat`; **списки** — `Intl.ListFormat`; **сортировка/поиск** — `Intl.Collator` (не байтовое сравнение строк).
## Библиотеки по стеку (мнение по умолчанию)
- **Next.js (App Router)** → **next-intl**: локали через `app/[locale]/`, `next-intl/middleware` для роутинга/детекта, серверные переводы `getTranslations` в Server Components, `useTranslations` в Client Components; локализованные `generateMetadata` и `hreflang`.
- **React SPA (Vite/CRA)** → **react-i18next** (`i18next` + `react-i18next`): namespaces, ленивая загрузка, `useTranslation`; ICU через `i18next-icu`, детект через `i18next-browser-languagedetector`.
- **Node-бэкенд (Fastify/Express)** → **i18next** (+ `i18next-http-middleware`) или `@formatjs/intl`: локаль из `Accept-Language`/профиля пользователя, перевод сообщений на границе ответа; письма/уведомления — по локали получателя.
- **Vue** → `vue-i18n`. Общий низкоуровневый слой форматирования во всех стеках — встроенный `Intl`.
## Frontend-специфика
- **Server vs Client**: на сервере (RSC) бери переводы серверным API (`getTranslations`), на клиенте — хук; не тащи весь словарь в клиентский бандл — грузи только нужные namespaces/локаль.
- **SEO**: локализованные `<title>`/`meta`, `<link rel="alternate" hreflang>`, `<html lang dir>` под текущую локаль.
- Строки — только через слой перевода; никаких литералов в JSX (кроме служебных, не видимых пользователю).
## Backend-специфика
- **Локализуй на границе** (в ответе/письме) по `Accept-Language` или предпочтению пользователя, а не в глубине бизнес-логики.
- **API-контракт ошибок**: отдавай стабильный машинный `code` + параметры (`{ code: 'ORDER_LIMIT', params: { max: 5 } }`), а готовый переведённый текст формируй на клиенте/edge — либо, если API сам отдаёт текст, выбирай язык по локали запроса. Не «зашивай» один язык в тело ответа.
- Валидация (zod и т.п.): сообщения — локализуемые ключи, не хардкод-строки.
## RTL и направление текста
- Поддержи RTL-локали (ar, he, fa): `dir="rtl"` на `<html>`/контейнере, зеркалирование раскладки.
- **Логические CSS-свойства** вместо физических: `margin-inline`/`padding-inline`, `inset-inline-start/-end`, `text-align: start/end` (в Tailwind — логические утилиты `ms-*`/`me-*`/`ps-*`/`pe-*`, `start-*`/`end-*`). Иконки направления (стрелки, chevron) — зеркаль под RTL.
## Анти-паттерны (ищи и убирай)
- Литерал пользовательской строки прямо в JSX/шаблоне/HTTP-ответе в мультиязычном проекте.
- Конкатенация переведённых кусков; `"Показано " + n + " элементов"`; ручная плюрализация через `if (n === 1)`.
- Хардкод формата даты/числа/валюты (`toLocaleString()` без локали тоже зависит от среды — задавай локаль явно).
- Перевод того, что переводить нельзя: логов, машинных кодов ошибок, enum/идентификаторов.
- Ключ = сам текст (`t("Оплатить")`); отсутствие фолбэк-локали; несогласованные наборы ключей между локалями.
- Локаль не учтена в ключах кэша (один кэш на все языки) — данные/страницы кэшируются per-locale.
- Физические CSS-отступы, ломающие RTL; отсутствие `lang`/`dir`.
## Чек-лист сдачи
- [ ] Определён режим проекта (мультиязычный / одноязычный) и применена соответствующая строгость.
- [ ] Пользовательские строки вынесены в словарь/через `t()`; литералов в разметке/ответах нет.
- [ ] Ключи типизированы и согласованы между локалями; фолбэк-локаль задан.
- [ ] Плюрализация/род — через ICU; интерполяция — именованными плейсхолдерами.
- [ ] Даты/числа/валюты/списки — через `Intl` с явной локалью и таймзоной.
- [ ] Логи и машинные коды ошибок НЕ локализованы.
- [ ] RTL учтён (логические свойства, `dir`), если среди локалей есть RTL.