> For the complete documentation index, see [llms.txt](https://docs.ipcheck.ing/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ipcheck.ing/developer/ru/development/i18n.md).

# i18n

MyIP поставляется на четырёх языках, и все четыре — первоклассные. Нет «основной» локали, которая получает функции первой.

| Код  | Язык                  | Файл                       |
| ---- | --------------------- | -------------------------- |
| `en` | Английский (запасной) | `frontend/locales/en.json` |
| `zh` | Упрощённый китайский  | `frontend/locales/zh.json` |
| `fr` | Французский           | `frontend/locales/fr.json` |
| `ru` | Русский               | `frontend/locales/ru.json` |

## Настройка

Приложение использует **vue-i18n** в режиме Composition API. Экземпляр создаётся в `frontend/locales/i18n.js` с `legacy: false` и `fallbackLocale: 'en'`, затем регистрируется в `main.js`.

В компоненте вы вызываете `t()` из `useI18n()`:

```vue
<script setup>
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
</script>

<template>
  <p>{{ t('macchecker.Note') }}</p>
</template>
```

Ничего видимого пользователю не захардкожено. Каждая строка проходит через `t()`.

## Файлы локалей

Каждый файл локали — это один JSON-объект, разбитый по пространствам имён в зависимости от функции. Пространства имён верхнего уровня включают `nav`, `advancedtools`, `page`, `changelog`, а также по одному для каждого инструмента — `macchecker`, `whois`, `dnsresolver`, `censorshipcheck`, и так далее.

Пространство имён инструмента обычно совпадает с его slug в реестре:

{% code title="frontend/locales/en.json" %}

```json
"macchecker": {
  "Title": "Поиск MAC-адреса",
  "Note": "Запросите производителя физического адреса (MAC-адреса)…",
  "Note2": "Пожалуйста, введите физический адрес, чтобы начать запрос:",
  "Placeholder": "F0:2F:4B:01:0A:AA",
  "invalidMAC": "Недействительный физический адрес",
  "fetchError": "Не удалось получить результаты запроса"
}
```

{% endcode %}

Описание карточки на главной странице хранится отдельно, в общем `advancedtools` пространстве имён, потому что именно на него указывает реестр инструментов `noteKey` на:

```json
"advancedtools": {
  "MacChecker": "Запрос информации о физическом адресе"
}
```

**Имена ключей одинаковы во всех четырёх файлах. Отличаются только значения.**

## Загрузка выполняется по требованию

Все четыре пакета никогда не загружаются вместе. Ранняя упаковка обходилась примерно в 44 КБ gzip-«мёртвого» веса — три неиспользуемые локали при каждой загрузке страницы.

Вместо этого `frontend/locales/i18n.js` содержит карту динамических импортов:

{% code title="frontend/locales/i18n.js" %}

```js
const localeLoaders = {
  en: () => import('./en.json'),
  zh: () => import('./zh.json'),
  fr: () => import('./fr.json'),
  ru: () => import('./ru.json'),
};
```

{% endcode %}

Экземпляр i18n начинает с **пустыми** сообщений. `loadActiveLocaleMessages()` подставляет активную локаль плюс `en` запасную (параллельно, с мемоизацией), и `main.js` ожидает его до монтирования — так что первый рендер уже переведён. Переключение языка сохраняет выбор и перезапускает приложение, а это значит, что при каждой загрузке страницы активна ровно одна локаль.

### Как выбирается язык

`setLanguage()` в `frontend/locales/i18n.js` разрешает по порядку:

1. **Сохранённая настройка** в `localStorage` — текущий ключ настроек, затем устаревшие ключи, так что недавно обновлённый ключ всё равно находит старый выбор.
2. **`?hl=` параметр запроса**, если он указывает на поддерживаемую локаль.
3. **Язык браузера** (`navigator.language`), сопоставляемый по первым двум символам.
4. **`en`.**

Плагин Vite на этапе сборки (`localePreloadPlugin` в `vite.config.js`) воспроизводит этот же порядок в небольшом встроенном `<head>` скрипте и добавляет `<link rel="modulepreload">` для выбранного пакета, пока HTML ещё передаётся — так локаль скачивается параллельно с основным бандлом, а не после него. Неверное предположение лишь тратит один preload; окончательное решение всё равно принимает реальный импорт.

После загрузки сообщений, `updateMeta()` устанавливает `document.documentElement.lang` (с `zh` объявленным как `zh-CN`, поскольку пакет только для упрощённого китайского) и обновляет `title`, `keywords`, и `description` метатеги из `page.*` ключей.

## Подпакеты

Два набора данных настолько велики, что не входят в основной пакет локали и загружаются по требованию только для активной локали:

<table><thead><tr><th width="300">Подпакет</th><th>Загружается через</th></tr></thead><tbody><tr><td><code>frontend/locales/security-checklist/{en,zh,fr,ru}.json</code></td><td><code>SecurityChecklist.vue</code> — собственная карта загрузчиков; набор данных примерно 30 КБ в gzip на язык, и читает его только этот один инструмент.</td></tr><tr><td><code>frontend/locales/privacy/{en,zh,fr,ru}.json</code></td><td><code>PrivacyPolicy.vue</code> — объединяется с i18n через <code>mergeLocaleMessage()</code> так что <code>t()</code> и <code>tm()</code> обычно находят нужный текст.</td></tr></tbody></table>

Оба следуют одной и той же схеме:  `{ en, zh, fr, ru }` карта динамических импортов с запасным вариантом `en` для неизвестной локали.

{% hint style="info" %}
**Когда добавлять подпакет:** набор данных, который велик, принадлежит ровно одному лениво загружаемому виду и иначе лежал бы в бандле первого отрисованного кадра у каждого посетителя. Обычные строки интерфейса всегда идут в основной пакет.
{% endhint %}

## Правило четырёх локалей

{% hint style="warning" %}
**Любое изменение, затрагивающее текст, должно попадать во все четыре локали в том же самом изменении.** Не отдельным PR позже, не TODO.
{% endhint %}

Это означает:

* Новый ключ добавляется в `en.json`, `zh.json`, `fr.json`, **и** `ru.json`.
* Переформулированная строка переформулируется во всех четырёх.
* Удалённый ключ удаляется из всех четырёх.
* Сопровождающий `changelog.json` запись содержит все четыре перевода.

`fallbackLocale: 'en'` означает, что отсутствующий ключ откатывается к английскому, а не показывает сырой путь ключа — именно поэтому частичное покрытие легко пропустить при ревью. Не полагайтесь на это.

Если вы действительно не можете подготовить перевод, укажите это в PR. Сопровождающий предпочёл бы исправить формулировку, чем обнаружить пропущенную локаль после релиза.

## Журнал изменений

Примечания к выпуску находятся в `frontend/data/changelog.json`, а не в файлах локалей. Файл — это массив блоков версий, **от старых к новым** — панель «О программе» отображает его в обратном порядке, поэтому новые записи добавляются в последний блок.

{% code title="frontend/data/changelog.json" %}

```json
{
  "version": "v7.2.0",
  "date": "Бета",
  "content": [
    {
      "type": "добавить",
      "change": {
        "en": "Rebuilt the Censorship Check: see where a website is blocked worldwide",
        "zh": "Полностью переработана проверка цензуры: можно узнать, где сайт заблокирован по всему миру",
        "fr": "Полностью переработана проверка цензуры: узнайте, где сайт заблокирован по всему миру",
        "ru": "Полностью переработана проверка цензуры: видно, где в мире сайт заблокирован"
      }
    }
  ]
}
```

{% endcode %}

Правила формы:

| Поле         | Правило                                                                          |
| ------------ | -------------------------------------------------------------------------------- |
| `версия`     | Сопоставление строк `vX.Y…`                                                      |
| `дата`       | Строка — дата выпуска или заполнитель вроде `"Бета"`                             |
| `содержимое` | Непустой массив элементов изменений                                              |
| `тип`        | Ровно одно из `добавить`, `улучшить`, `исправить`                                |
| `изменение`  | Объект с **всеми четырьмя** ключами локалей, каждый из которых — непустая строка |

### Проверяется тестами

`tests/changelog.test.js` выполняется при каждом `pnpm test` и валит сборку при:

* отсутствии или пустом переводе любого из `en` / `zh` / `fr` / `ru`
* a `тип` вне допустимых трёх
* отсутствии блока версии `версия`, `дата`, или непустой `содержимое` массив
* файл локали, который снова вводит `changelog.versions` (эти данные теперь находятся только в `changelog.json`)
* файл локали, который утратил `changelog.Title` / `добавить` / `улучшить` / `исправить` UI-метки

Именно эту последнюю пару стоит запомнить как разделение: **текст примечаний к выпуску находится в `changelog.json`; подписи бейджей и заголовок панели вокруг него остаются в файлах локалей** как обычные элементы UI.

## Добавление языка

В кодовой базе ничто не запрещает пятую локаль, но это уже серьёзное обязательство — каждое будущее изменение текста тогда потребует пяти переводов, и `tests/changelog.test.js` жёстко зашиты требуемые четыре. Откройте issue и обсудите это с сопровождающими, прежде чем начинать.

## Далее

* [Добавление нового инструмента](/developer/ru/development/adding-a-new-tool.md) — где шаг i18n вписывается в полноценную функцию.
* [Тестирование](/developer/ru/development/testing.md) — что ещё `pnpm test` требует.
* [Как внести вклад](/developer/ru/contributing/how-to-contribute.md) — ожидания к PR.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.ipcheck.ing/developer/ru/development/i18n.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
