---
createdAt: 2025-09-10
updatedAt: 2025-09-10
title: i18n на уровне компонента против централизованного: новый подход с Intlayer
description: Глубокое погружение в стратегии интернационализации в React, сравнение централизованного, по-ключевого и по-компонентного подходов, и представление Intlayer.
keywords:
- i18n
- React
- Интернационализация
- Intlayer
- Оптимизация
- Размер бандла
slugs:
- blog
- per-component-vs-centralized-i18n
---
# По-компонентный vs централизованный i18n
Подход по компонентам — не новое понятие. Например, в экосистеме Vue `vue-i18n` поддерживает [i18n SFC (Single File Component)](https://vue-i18n.intlify.dev/guide/advanced/sfc.html). Nuxt также предлагает [переводы на уровне компонента](https://i18n.nuxtjs.org/docs/guide/per-component-translations), а Angular использует похожий паттерн через свои [Feature Modules](https://v17.angular.io/guide/feature-modules).
Даже в Flutter-приложении часто встречается следующий паттерн:
```bash
lib/
└── features/
└── login/
├── login_screen.dart
└── login_screen.i18n.dart # <- Переводы находятся здесь
```
```dart fileName='lib/features/login/login_screen.i18n.dart'
import 'package:i18n_extension/i18n_extension.dart';
extension Localization on String {
static var _t = Translations.byText("en") +
{
"Hello": {
"en": "Hello",
"fr": "Bonjour",
},
};
String get i18n => localize(this, _t);
}
```
Однако в мире React мы в основном видим разные подходы, которые я сгруппирую в три категории:
<Columns>
<Column>
**Централизованный подход** (i18next, next-intl, react-intl, lingui)
- (без пространств имён) рассматривает единый источник для получения контента. По умолчанию вы загружаете контент со всех страниц при загрузке приложения.
</Column>
<Column>
**Гранулярный подход** (intlayer, inlang)
- более тонко настраивает получение контента — по ключу или по компоненту.
</Column>
</Columns>
> В этом блоге я не буду фокусироваться на решениях, основанных на компиляции, которые я уже рассмотрел здесь: [Компилятор vs декларативный i18n](https://github.com/aymericzip/intlayer/blob/main/docs/blog/ru/compiler_vs_declarative_i18n.md).
> Обратите внимание, что i18n, основанный на компиляции (например, Lingui), просто автоматизирует извлечение и загрузку контента. Под капотом они часто имеют те же ограничения, что и другие подходы.
> Обратите внимание: чем тоньше вы разбиваете механизм получения контента, тем больше риск внедрения дополнительного состояния и логики в ваши компоненты.
Гранулярные подходы более гибкие, чем централизованные, но часто это компромисс. Даже если библиотеки рекламируют "tree shaking", на практике вы часто в итоге будете загружать страницу на каждом языке.
В целом решение сводится к следующему:
- Если в вашем приложении страниц больше, чем языков, стоит отдавать предпочтение гранулярному подходу.
- Если языков больше, чем страниц, стоит склоняться к централизованному подходу.
Разработчики библиотек, разумеется, знают об этих ограничениях и предлагают обходные решения. Среди них: разделение на namespaces, динамическая загрузка JSON-файлов (`await import()`), или очистка контента на этапе сборки.
В то же время следует учитывать, что при динамической подгрузке контента вы создаёте дополнительные запросы к серверу. Каждое дополнительное `useState` или hook означает ещё один запрос к серверу.
> Чтобы решить эту проблему, Intlayer предлагает группировать несколько определений контента под одним и тем же ключом; Intlayer затем объединит этот контент.
Исходя из всего этого, очевидно, что наиболее популярным подходом является централизованный.
### Почему же централизованный подход так популярен?
/// Во-первых, i18next был первым решением, ставшим широко используемым, следуя философии, вдохновлённой архитектурами PHP и Java (MVC), которые опираются на строгую сегрегацию обязанностей (содержание отделено от кода). Он появился в 2011 году, установив свои стандарты ещё до масштабного перехода к компонентно-ориентированным архитектурам (например, React).
- Затем, как только библиотека становится широко принятой, переход экосистемы на другие паттерны становится затруднительным.
- Использование централизованного подхода также упрощает работу с системами управления переводами, такими как Crowdin, Phrase или Localized.
- Логика подхода per-component более сложна, чем у централизованного, и требует дополнительного времени на разработку, особенно когда приходится решать такие задачи, как определение местоположения контента.
### Хорошо, но почему просто не придерживаться централизованного подхода?
Давайте я объясню, почему это может стать проблемой для вашего приложения:
- **Неиспользуемые данные:**
Когда загружается страница, вы часто загружаете содержимое со всех остальных страниц. (В приложении из 10 страниц это 90% загруженного, но неиспользуемого контента). Ленивая загрузка модального окна? Библиотека i18n всё равно не обращает внимания — она сначала загружает строки.
- **Производительность:**
При каждом повторном рендере каждый ваш компонент гидратируется массивной JSON‑нагрузкой, что негативно влияет на реактивность приложения по мере его роста.
- **Поддержка:**
Поддерживать большие JSON‑файлы болезненно. Приходится переходить между файлами, чтобы вставить перевод, при этом нужно следить, чтобы не было отсутствующих переводов и чтобы не осталось **orphan keys**.
- **Дизайн‑система:**
Это создаёт несовместимость с design systems (например, компонент `LoginForm`) и ограничивает дублирование компонентов между разными приложениями.
**"Но мы придумали Namespaces!"**
Конечно, и это значительный шаг вперёд. Давайте сравним размер основного бандла для конфигурации `Vite + React + React Router v7 + Intlayer`. Мы симулировали 20-страничное приложение.
Первый пример не включает ленивую загрузку переводов по локалям и не содержит разделения на namespaces. Во втором примере используется content purging + динамическая загрузка переводов.
| Оптимизированный бандл | Неоптимизированный бандл |
| ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
|  |  |
Таким образом, благодаря namespaces, мы перешли от этой структуры:
```bash
locale/
├── en.json
├── fr.json
└── es.json
```
To this one:
```bash
locale/
├── en/
│ ├── common.json
│ ├── navbar.json
│ ├── footer.json
│ ├── home.json
│ └── about.json
├── fr/
│ └── ...
└── es/
└── ...
```
Теперь вам нужно тонко управлять тем, какую часть контента вашего приложения следует загружать и где. В результате подавляющее большинство проектов просто пропускают эту часть из‑за сложности (см. руководство [next-i18next](https://github.com/aymericzip/intlayer/blob/main/docs/blog/ru/i18n_using_next-i18next.md), чтобы увидеть, какие вызовы представляет собой (даже) следование лучшим практикам).
Вследствие этого такие проекты в конечном итоге сталкиваются с проблемой массовой загрузки JSON, описанной ранее.
> Обратите внимание, что эта проблема не является специфичной для i18next, а относится ко всем централизованным подходам, перечисленным выше.
Однако хочу напомнить, что не все гранулярные подходы решают эту проблему. Например, подходы `vue-i18n SFC` или `inlang` по сути не выполняют ленивую подгрузку переводов по локалям, поэтому вы просто меняете проблему размера бандла (bundle size) на другую.
Кроме того, без правильного разделения ответственности становится гораздо сложнее извлечь и передать ваши переводы переводчикам для проверки.
### Как покомпонентный подход Intlayer решает эту проблему
Intlayer работает в несколько шагов:
1. **Declaration:** Объявляйте ваш контент в любом месте codebase, используя файлы `*.content.{ts|jsx|cjs|json|json5|...}`. Это обеспечивает разделение ответственности при одновременном размещении контента рядом с кодом (colocated). Файл контента может быть для одной локали или мультиязычным.
2. **Обработка:** Intlayer выполняет шаг сборки для обработки JS-логики, обработки отсутствующих переводов (fallbacks), генерации типов TypeScript, управления дублирующимся контентом, получения контента из вашей CMS и прочего.
3. **Очистка:** Когда ваше приложение собирается, Intlayer удаляет неиспользуемый контент (немного похожим образом на то, как Tailwind управляет вашими классами), заменяя контент следующим образом:
**Декларация:**
```tsx
// src/MyComponent.tsx
export const MyComponent = () => {
const content = useIntlayer("my-key");
return <h1>{content.title}</h1>;
};
```
```tsx
// src/myComponent.content.ts
export const {
key: "my-key",
content: t({
ru: { title: "Мой заголовок" },
en: { title: "My title" },
fr: { title: "Mon titre" }
})
}
```
**Обработка:** Intlayer собирает словарь на основе файла `.content` и генерирует:
```json5
// .intlayer/dynamic_dictionary/en/my-key.json
{
"key": "my-key",
"content": { "title": "Мой заголовок" },
}
```
**Замена:** Intlayer преобразует ваш компонент во время сборки приложения.
**- Режим статического импорта:**
```tsx
// Representation of the component in JSX-like syntax
export const MyComponent = () => {
const content = useDictionary({
key: "my-key",
content: {
nodeType: "translation",
translation: {
en: { title: "Мой заголовок" },
fr: { title: "Мой заголовок" },
},
},
});
return <h1>{content.title}</h1>;
};
```
**- Режим динамического импорта:**
```tsx
// Representation of the component in JSX-like syntax
export const MyComponent = () => {
const content = useDictionaryAsync({
en: () =>
import(".intlayer/dynamic_dictionary/en/my-key.json", {
with: { type: "json" },
}).then((mod) => mod.default),
// То же самое для других языков
});
return <h1>{content.title}</h1>;
};
```
> `useDictionaryAsync` использует механизм, похожий на Suspense, чтобы загружать локализованный JSON только по мере необходимости.
**Ключевые преимущества этого подхода на уровне компонентов:**
- Держать объявление контента рядом с компонентами повышает удобство сопровождения (например, при переносе компонентов в другое приложение или дизайн-систему. Удаление папки компонента также удаляет связанный контент, как вы, вероятно, уже делаете для ваших `.test`, `.stories`)
- Подход «по компонентам» избавляет AI-агентов от необходимости перескакивать между множеством разных файлов. Он собирает все переводы в одном месте, снижая сложность задачи и количество используемых токенов.
### Ограничения
Разумеется, у такого подхода есть компромиссы:
- Сложнее интегрироваться с другими l10n‑системами и дополнительным tooling'ом (инструментариями).
- Возникает привязка к выбранному решению (что, по сути, уже характерно для любых i18n‑решений из‑за их специфического синтаксиса).
Именно поэтому Intlayer стремится предоставить полный набор инструментов для i18n (100% бесплатно и с открытым исходным кодом), включая AI‑перевод с использованием вашего собственного AI Provider и API‑ключей. Intlayer также предоставляет tooling для синхронизации ваших JSON‑файлов, функционируя как message formatters ICU / vue-i18n / i18next для сопоставления контента с их специфичными форматами.