@intlayer/docs 7.0.0-canary.2 → 7.0.0-canary.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/cjs/common.cjs.map +1 -1
  2. package/dist/esm/common.mjs.map +1 -1
  3. package/dist/types/common.d.ts +5 -0
  4. package/dist/types/common.d.ts.map +1 -1
  5. package/docs/ar/intlayer_with_nextjs_16.md +1652 -0
  6. package/docs/ar/releases/v7.md +486 -0
  7. package/docs/de/intlayer_with_nextjs_16.md +1662 -0
  8. package/docs/de/releases/v7.md +503 -0
  9. package/docs/en/intlayer_with_nextjs_15.md +5 -2
  10. package/docs/en/intlayer_with_nextjs_16.md +4 -4
  11. package/docs/en-GB/intlayer_with_nextjs_16.md +1642 -0
  12. package/docs/en-GB/releases/v7.md +486 -0
  13. package/docs/es/intlayer_with_nextjs_16.md +1670 -0
  14. package/docs/es/releases/v7.md +503 -0
  15. package/docs/fr/intlayer_with_nextjs_16.md +1692 -0
  16. package/docs/fr/releases/v7.md +504 -0
  17. package/docs/hi/intlayer_with_nextjs_16.md +1618 -0
  18. package/docs/hi/releases/v7.md +486 -0
  19. package/docs/id/intlayer_with_nextjs_16.md +1604 -0
  20. package/docs/id/releases/v7.md +503 -0
  21. package/docs/it/intlayer_with_nextjs_16.md +1600 -0
  22. package/docs/it/releases/v7.md +505 -0
  23. package/docs/ja/intlayer_CMS.md +0 -9
  24. package/docs/ja/intlayer_with_nextjs_16.md +1788 -0
  25. package/docs/ja/releases/v7.md +504 -0
  26. package/docs/ko/intlayer_with_nextjs_16.md +1641 -0
  27. package/docs/ko/releases/v7.md +504 -0
  28. package/docs/pl/intlayer_with_nextjs_16.md +1645 -0
  29. package/docs/pl/releases/v7.md +486 -0
  30. package/docs/pt/intlayer_with_nextjs_16.md +1646 -0
  31. package/docs/pt/introduction.md +0 -15
  32. package/docs/pt/releases/v7.md +486 -0
  33. package/docs/ru/intlayer_with_nextjs_16.md +1610 -0
  34. package/docs/ru/releases/v7.md +486 -0
  35. package/docs/tr/intlayer_with_nextjs_16.md +1599 -0
  36. package/docs/tr/releases/v7.md +486 -0
  37. package/docs/vi/intlayer_with_nextjs_16.md +1597 -0
  38. package/docs/vi/releases/v7.md +486 -0
  39. package/docs/zh/intlayer_CMS.md +0 -23
  40. package/docs/zh/intlayer_with_nextjs_16.md +1628 -0
  41. package/docs/zh/releases/v7.md +487 -0
  42. package/package.json +14 -14
  43. package/src/common.ts +5 -0
@@ -0,0 +1,1610 @@
1
+ ---
2
+ createdAt: 2025-10-25
3
+ updatedAt: 2025-10-25
4
+ title: Как перевести ваше приложение Next.js 16 – руководство по i18n 2025
5
+ description: Узнайте, как сделать ваш сайт на Next.js 16 многоязычным. Следуйте документации для интернационализации (i18n) и перевода.
6
+ keywords:
7
+ - Интернационализация
8
+ - Документация
9
+ - Intlayer
10
+ - Next.js 16
11
+ - JavaScript
12
+ - React
13
+ slugs:
14
+ - doc
15
+ - environment
16
+ - nextjs
17
+ applicationTemplate: https://github.com/aymericzip/intlayer-next-16-template
18
+ youtubeVideo: https://www.youtube.com/watch?v=e_PPG7PTqGU
19
+ history:
20
+ - version: 7.0.0
21
+ date: 2025-06-29
22
+ changes: Инициализация истории
23
+ ---
24
+
25
+ # Перевод вашего сайта на Next.js 16 с помощью Intlayer | Интернационализация (i18n)
26
+
27
+ <iframe title="Лучшее решение i18n для Next.js? Откройте для себя Intlayer" class="m-auto aspect-[16/9] w-full overflow-hidden rounded-lg border-0" allow="autoplay; gyroscope;" loading="lazy" width="1080" height="auto" src="https://www.youtube.com/embed/e_PPG7PTqGU?autoplay=0&amp;origin=http://intlayer.org&amp;controls=0&amp;rel=1"/>
28
+
29
+ Смотрите [Шаблон приложения](https://github.com/aymericzip/intlayer-next-16-template) на GitHub.
30
+
31
+ ## Что такое Intlayer?
32
+
33
+ **Intlayer** — это инновационная, открытая библиотека интернационализации (i18n), разработанная для упрощения поддержки многоязычности в современных веб-приложениях. Intlayer бесшовно интегрируется с последним фреймворком **Next.js 16**, включая его мощный **App Router**. Она оптимизирована для работы с **Server Components** для эффективного рендеринга и полностью совместима с [**Turbopack**](https://nextjs.org/docs/architecture/turbopack).
34
+
35
+ С помощью Intlayer вы можете:
36
+
37
+ - **Легко управлять переводами** с использованием декларативных словарей на уровне компонентов.
38
+ - **Динамически локализовать метаданные**, маршруты и контент.
39
+ - **Получать доступ к переводам как в клиентских, так и в серверных компонентах**.
40
+ - **Обеспечить поддержку TypeScript** с автогенерируемыми типами, улучшая автодополнение и обнаружение ошибок.
41
+ - **Воспользуйтесь расширенными возможностями**, такими как динамическое определение и переключение локали.
42
+
43
+ > Intlayer совместим с Next.js 12, 13, 14 и 16. Если вы используете Next.js Page Router, вы можете обратиться к этому [руководству](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ru/intlayer_with_nextjs_page_router.md). Для Next.js 12, 13, 14 с App Router обратитесь к этому [руководству](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ru/intlayer_with_nextjs_14.md).
44
+
45
+ ---
46
+
47
+ ## Пошаговое руководство по настройке Intlayer в приложении Next.js
48
+
49
+ ### Шаг 1: Установка зависимостей
50
+
51
+ Установите необходимые пакеты с помощью npm:
52
+
53
+ ```bash packageManager="npm"
54
+ npm install intlayer next-intlayer
55
+ ```
56
+
57
+ ```bash packageManager="pnpm"
58
+ pnpm add intlayer next-intlayer
59
+ ```
60
+
61
+ ```bash packageManager="yarn"
62
+ yarn add intlayer next-intlayer
63
+ ```
64
+
65
+ - **intlayer**
66
+
67
+ Основной пакет, предоставляющий инструменты интернационализации для управления конфигурацией, перевода, [объявления контента](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ru/dictionary/content_file.md), транспиляции и [CLI-команд](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ru/intlayer_cli.md).
68
+
69
+ - **next-intlayer**
70
+
71
+ Пакет, который интегрирует Intlayer с Next.js. Он предоставляет провайдеры контекста и хуки для интернационализации в Next.js. Кроме того, включает плагин Next.js для интеграции Intlayer с [Webpack](https://webpack.js.org/) или [Turbopack](https://nextjs.org/docs/app/api-reference/turbopack), а также прокси для определения предпочтительной локали пользователя, управления куки и обработки перенаправления URL.
72
+
73
+ ### Шаг 2: Настройте ваш проект
74
+
75
+ Создайте конфигурационный файл для настройки языков вашего приложения:
76
+
77
+ ```typescript fileName="intlayer.config.ts" codeFormat="typescript"
78
+ import { Locales, type IntlayerConfig } from "intlayer";
79
+
80
+ const config: IntlayerConfig = {
81
+ internationalization: {
82
+ locales: [
83
+ Locales.ENGLISH,
84
+ Locales.FRENCH,
85
+ Locales.SPANISH,
86
+ // Ваши другие локали
87
+ ],
88
+ defaultLocale: Locales.ENGLISH,
89
+ },
90
+ };
91
+
92
+ export default config;
93
+ ```
94
+
95
+ ```javascript fileName="intlayer.config.mjs" codeFormat="esm"
96
+ import { Locales } from "intlayer";
97
+
98
+ /** @type {import('intlayer').IntlayerConfig} */
99
+ const config = {
100
+ internationalization: {
101
+ locales: [
102
+ Locales.ENGLISH,
103
+ Locales.FRENCH,
104
+ Locales.SPANISH,
105
+ // Ваши другие локали
106
+ ],
107
+ defaultLocale: Locales.ENGLISH,
108
+ },
109
+ };
110
+
111
+ export default config;
112
+ ```
113
+
114
+ ```javascript fileName="intlayer.config.cjs" codeFormat="commonjs"
115
+ const { Locales } = require("intlayer");
116
+
117
+ /** @type {import('intlayer').IntlayerConfig} */
118
+ const config = {
119
+ internationalization: {
120
+ locales: [
121
+ Locales.ENGLISH,
122
+ Locales.FRENCH,
123
+ Locales.SPANISH,
124
+ // Ваши другие локали
125
+ ],
126
+ defaultLocale: Locales.ENGLISH,
127
+ },
128
+ };
129
+
130
+ module.exports = config;
131
+ ```
132
+
133
+ > С помощью этого файла конфигурации вы можете настроить локализованные URL, прокси-перенаправление, имена cookie, расположение и расширение ваших деклараций контента, отключить логи Intlayer в консоли и многое другое. Для полного списка доступных параметров обратитесь к [документации по конфигурации](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ru/configuration.md).
134
+
135
+ ### Шаг 3: Интеграция Intlayer в вашу конфигурацию Next.js
136
+
137
+ Настройте ваш Next.js для использования Intlayer:
138
+
139
+ ```typescript fileName="next.config.ts" codeFormat="typescript"
140
+ import type { NextConfig } from "next";
141
+ import { withIntlayer } from "next-intlayer/server";
142
+
143
+ const nextConfig: NextConfig = {
144
+ /* параметры конфигурации здесь */
145
+ };
146
+
147
+ export default withIntlayer(nextConfig);
148
+ ```
149
+
150
+ ```typescript fileName="next.config.mjs" codeFormat="esm"
151
+ import { withIntlayer } from "next-intlayer/server";
152
+
153
+ /** @type {import('next').NextConfig} */
154
+ const nextConfig = {
155
+ /* параметры конфигурации здесь */
156
+ };
157
+
158
+ export default withIntlayer(nextConfig);
159
+ ```
160
+
161
+ ```typescript fileName="next.config.cjs" codeFormat="commonjs"
162
+ const { withIntlayer } = require("next-intlayer/server");
163
+
164
+ /** @type {import('next').NextConfig} */
165
+ const nextConfig = {
166
+ /* параметры конфигурации здесь */
167
+ };
168
+
169
+ module.exports = withIntlayer(nextConfig);
170
+ ```
171
+
172
+ > Плагин Next.js `withIntlayer()` используется для интеграции Intlayer с Next.js. Он обеспечивает сборку файлов деклараций контента и их мониторинг в режиме разработки. Он определяет переменные окружения Intlayer в средах [Webpack](https://webpack.js.org/) или [Turbopack](https://nextjs.org/docs/app/api-reference/turbopack). Кроме того, он предоставляет алиасы для оптимизации производительности и обеспечивает совместимость с серверными компонентами.
173
+
174
+ > Функция `withIntlayer()` является функцией, возвращающей промис. Она позволяет подготовить словари Intlayer перед началом сборки. Если вы хотите использовать её с другими плагинами, вы можете использовать await. Пример:
175
+ >
176
+ > ```tsx
177
+ > const nextConfig = await withIntlayer(nextConfig);
178
+ > const nextConfigWithOtherPlugins = withOtherPlugins(nextConfig);
179
+ >
180
+ > export default nextConfigWithOtherPlugins;
181
+ > ```
182
+ >
183
+ > Если вы хотите использовать его синхронно, вы можете использовать функцию `withIntlayerSync()`. Пример:
184
+ >
185
+ > ```tsx
186
+ > const nextConfig = withIntlayerSync(nextConfig);
187
+ > const nextConfigWithOtherPlugins = withOtherPlugins(nextConfig);
188
+ >
189
+ > export default nextConfigWithOtherPlugins;
190
+ > ```
191
+
192
+ ### Шаг 4: Определите динамические маршруты локалей
193
+
194
+ Удалите всё из `RootLayout` и замените следующим кодом:
195
+
196
+ ```tsx {3} fileName="src/app/layout.tsx" codeFormat="typescript"
197
+ import type { PropsWithChildren, FC } from "react";
198
+ import "./globals.css";
199
+
200
+ const RootLayout: FC<PropsWithChildren> = ({ children }) => (
201
+ // Вы всё ещё можете обернуть children в другие провайдеры, такие как `next-themes`, `react-query`, `framer-motion` и т.д.
202
+ <>{children}</>
203
+ );
204
+
205
+ export default RootLayout;
206
+ ```
207
+
208
+ ```jsx {3} fileName="src/app/layout.mjx" codeFormat="esm"
209
+ import "./globals.css";
210
+
211
+ const RootLayout = ({ children }) => (
212
+ // Вы всё ещё можете обернуть дочерние элементы другими провайдерами, такими как `next-themes`, `react-query`, `framer-motion` и т.д.
213
+ <>{children}</>
214
+ );
215
+
216
+ export default RootLayout;
217
+ ```
218
+
219
+ ```jsx {1,8} fileName="src/app/layout.csx" codeFormat="commonjs"
220
+ require("./globals.css");
221
+
222
+ const RootLayout = ({ children }) => (
223
+ // Вы всё ещё можете обернуть дочерние элементы другими провайдерами, такими как `next-themes`, `react-query`, `framer-motion` и т.д.
224
+ <>{children}</>
225
+ );
226
+
227
+ module.exports = {
228
+ default: RootLayout,
229
+ generateStaticParams,
230
+ };
231
+ ```
232
+
233
+ > Оставляя компонент `RootLayout` пустым, вы позволяете установить атрибуты [`lang`](https://developer.mozilla.org/fr/docs/Web/HTML/Global_attributes/lang) и [`dir`](https://developer.mozilla.org/fr/docs/Web/HTML/Global_attributes/dir) для тега `<html>`.
234
+
235
+ Для реализации динамической маршрутизации укажите путь для локали, добавив новый layout в ваш каталог `[locale]`:
236
+
237
+ ```tsx fileName="src/app/[locale]/layout.tsx" codeFormat="typescript"
238
+ import type { NextLayoutIntlayer } from "next-intlayer";
239
+ import { Inter } from "next/font/google";
240
+ import { getHTMLTextDir } from "intlayer";
241
+
242
+ const inter = Inter({ subsets: ["latin"] });
243
+
244
+ const LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => {
245
+ const { locale } = await params;
246
+ return (
247
+ <html lang={locale} dir={getHTMLTextDir(locale)}>
248
+ <body className={inter.className}>{children}</body>
249
+ </html>
250
+ );
251
+ };
252
+
253
+ export default LocaleLayout;
254
+ ```
255
+
256
+ ```jsx fileName="src/app/[locale]/layout.mjx" codeFormat="esm"
257
+ import { getHTMLTextDir } from "intlayer";
258
+
259
+ const inter = Inter({ subsets: ["latin"] });
260
+
261
+ const LocaleLayout = async ({ children, params: { locale } }) => {
262
+ const { locale } = await params;
263
+ return (
264
+ <html lang={locale} dir={getHTMLTextDir(locale)}>
265
+ <body className={inter.className}>{children}</body>
266
+ </html>
267
+ );
268
+ };
269
+
270
+ export default LocaleLayout;
271
+ ```
272
+
273
+ ```jsx fileName="src/app/[locale]/layout.csx" codeFormat="commonjs"
274
+ const { Inter } = require("next/font/google");
275
+ const { getHTMLTextDir } = require("intlayer");
276
+
277
+ const inter = Inter({ subsets: ["latin"] });
278
+
279
+ const LocaleLayout = async ({ children, params: { locale } }) => {
280
+ const { locale } = await params;
281
+ return (
282
+ <html lang={locale} dir={getHTMLTextDir(locale)}>
283
+ <body className={inter.className}>{children}</body>
284
+ </html>
285
+ );
286
+ };
287
+
288
+ module.exports = LocaleLayout;
289
+ ```
290
+
291
+ > Сегмент пути `[locale]` используется для определения локали. Пример: `/en-US/about` будет соответствовать `en-US`, а `/fr/about` — `fr`.
292
+
293
+ > На этом этапе вы столкнетесь с ошибкой: `Error: Missing <html> and <body> tags in the root layout.`. Это ожидаемо, так как файл `/app/page.tsx` больше не используется и может быть удален. Вместо этого сегмент пути `[locale]` активирует страницу `/app/[locale]/page.tsx`. Следовательно, страницы будут доступны по путям, таким как `/en`, `/fr`, `/es` в вашем браузере. Чтобы установить локаль по умолчанию как корневую страницу, обратитесь к настройке `proxy` в шаге 7.
294
+
295
+ Затем реализуйте функцию `generateStaticParams` в вашем Layout приложения.
296
+
297
+ ```tsx {1} fileName="src/app/[locale]/layout.tsx" codeFormat="typescript"
298
+ export { generateStaticParams } from "next-intlayer"; // Строка для вставки
299
+
300
+ const LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => {
301
+ /*... Остальная часть кода*/
302
+ };
303
+
304
+ export default LocaleLayout;
305
+ ```
306
+
307
+ ```jsx {1} fileName="src/app/[locale]/layout.mjx" codeFormat="esm"
308
+ export { generateStaticParams } from "next-intlayer"; // Строка для вставки
309
+
310
+ const LocaleLayout = async ({ children, params: { locale } }) => {
311
+ /*... Остальная часть кода */
312
+ };
313
+
314
+ // ... Остальная часть кода
315
+ ```
316
+
317
+ ```jsx {1,7} fileName="src/app/[locale]/layout.csx" codeFormat="commonjs"
318
+ const { generateStaticParams } = require("next-intlayer"); // Строка для вставки
319
+
320
+ const LocaleLayout = async ({ children, params: { locale } }) => {
321
+ /*... Остальная часть кода */
322
+ };
323
+
324
+ module.exports = { default: LocaleLayout, generateStaticParams };
325
+ ```
326
+
327
+ > `generateStaticParams` гарантирует, что ваше приложение предварительно собирает необходимые страницы для всех локалей, уменьшая вычисления во время выполнения и улучшая пользовательский опыт. Для получения дополнительной информации обратитесь к [документации Next.js по generateStaticParams](https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic-rendering#generate-static-params).
328
+
329
+ > Intlayer работает с `export const dynamic = 'force-static';`, чтобы обеспечить предварительную сборку страниц для всех локалей.
330
+
331
+ ### Шаг 5: Объявите Ваш Контент
332
+
333
+ Создайте и управляйте объявлениями контента для хранения переводов:
334
+
335
+ ```tsx fileName="src/app/[locale]/page.content.ts" contentDeclarationFormat="typescript"
336
+ import { t, type Dictionary } from "intlayer";
337
+
338
+ const pageContent = {
339
+ key: "page",
340
+ content: {
341
+ getStarted: {
342
+ main: t({
343
+ en: "Get started by editing",
344
+ fr: "Commencez par éditer",
345
+ es: "Comience por editar",
346
+ }),
347
+ pageLink: "src/app/page.tsx",
348
+ },
349
+ },
350
+ } satisfies Dictionary;
351
+
352
+ export default pageContent;
353
+ ```
354
+
355
+ ```javascript fileName="src/app/[locale]/page.content.mjs" contentDeclarationFormat="esm"
356
+ import { t } from "intlayer";
357
+
358
+ /** @type {import('intlayer').Dictionary} */
359
+ // Контент страницы с переводами
360
+ const pageContent = {
361
+ key: "page",
362
+ content: {
363
+ getStarted: {
364
+ main: t({
365
+ en: "Get started by editing",
366
+ fr: "Commencez par éditer",
367
+ es: "Comience por editar",
368
+ }),
369
+ pageLink: "src/app/page.tsx",
370
+ },
371
+ },
372
+ };
373
+
374
+ export default pageContent;
375
+ ```
376
+
377
+ ```javascript fileName="src/app/[locale]/page.content.cjs" contentDeclarationFormat="commonjs"
378
+ const { t } = require("intlayer");
379
+
380
+ /** @type {import('intlayer').Dictionary} */
381
+ // Контент страницы с переводами
382
+ const pageContent = {
383
+ key: "page",
384
+ content: {
385
+ getStarted: {
386
+ main: t({
387
+ en: "Get started by editing",
388
+ fr: "Commencez par éditer",
389
+ es: "Comience por editar",
390
+ }),
391
+ pageLink: "src/app/page.tsx",
392
+ },
393
+ },
394
+ };
395
+
396
+ module.exports = pageContent;
397
+ ```
398
+
399
+ ```json fileName="src/app/[locale]/page.content.json" contentDeclarationFormat="json"
400
+ {
401
+ "$schema": "https://intlayer.org/schema.json",
402
+ "key": "page",
403
+ "content": {
404
+ "getStarted": {
405
+ "nodeType": "translation",
406
+ "translation": {
407
+ "en": "Get started by editing",
408
+ "fr": "Commencez par éditer",
409
+ "es": "Comience por editar"
410
+ }
411
+ },
412
+ "pageLink": "src/app/page.tsx"
413
+ }
414
+ }
415
+ ```
416
+
417
+ > Ваши объявления контента могут быть определены в любом месте вашего приложения, как только они включены в каталог `contentDir` (по умолчанию, `./src`). И соответствуют расширению файла объявления контента (по умолчанию, `.content.{json,ts,tsx,js,jsx,mjs,mjx,cjs,cjx}`).
418
+
419
+ > Для получения дополнительной информации обратитесь к [документации по объявлениям контента](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ru/dictionary/content_file.md).
420
+
421
+ ### Шаг 6: Использование контента в вашем коде
422
+
423
+ Получайте доступ к вашим словарям контента по всему приложению:
424
+
425
+ ```tsx fileName="src/app/[locale]/page.tsx" codeFormat="typescript"
426
+ import type { FC } from "react";
427
+ import { ClientComponentExample } from "@components/ClientComponentExample";
428
+ import { ServerComponentExample } from "@components/ServerComponentExample";
429
+ import { type NextPageIntlayer, IntlayerClientProvider } from "next-intlayer";
430
+ import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";
431
+
432
+ const PageContent: FC = () => {
433
+ const content = useIntlayer("page");
434
+
435
+ return (
436
+ <>
437
+ <p>{content.getStarted.main}</p> {/* Основной текст начала работы */}
438
+ <code>{content.getStarted.pageLink}</code>{" "}
439
+ {/* Ссылка на страницу начала работы */}
440
+ </>
441
+ );
442
+ };
443
+
444
+ const Page: NextPageIntlayer = async ({ params }) => {
445
+ const { locale } = await params;
446
+
447
+ return (
448
+ <IntlayerServerProvider locale={locale}>
449
+ <PageContent />
450
+ <ServerComponentExample />
451
+
452
+ <IntlayerClientProvider locale={locale}>
453
+ <ClientComponentExample />
454
+ </IntlayerClientProvider>
455
+ </IntlayerServerProvider>
456
+ );
457
+ };
458
+
459
+ export default Page;
460
+ ```
461
+
462
+ ```jsx fileName="src/app/[locale]/page.mjx" codeFormat="esm"
463
+ import { ClientComponentExample } from "@components/ClientComponentExample";
464
+ import { ServerComponentExample } from "@components/ServerComponentExample";
465
+ import { IntlayerClientProvider } from "next-intlayer";
466
+ import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";
467
+
468
+ const PageContent = () => {
469
+ const content = useIntlayer("page");
470
+
471
+ return (
472
+ <>
473
+ <p>{content.getStarted.main}</p> {/* Основной текст начала работы */}
474
+ <code>{content.getStarted.pageLink}</code> {/* Ссылка на страницу */}
475
+ </>
476
+ );
477
+ };
478
+
479
+ const Page = async ({ params }) => {
480
+ const { locale } = await params;
481
+
482
+ return (
483
+ <IntlayerServerProvider locale={locale}>
484
+ <PageContent />
485
+ <ServerComponentExample />
486
+
487
+ <IntlayerClientProvider locale={locale}>
488
+ <ClientComponentExample />
489
+ </IntlayerClientProvider>
490
+ </IntlayerServerProvider>
491
+ );
492
+ };
493
+
494
+ export default Page;
495
+ ```
496
+
497
+ ```jsx fileName="src/app/[locale]/page.csx" codeFormat="commonjs"
498
+ import { ClientComponentExample } from "@components/ClientComponentExample";
499
+ import { ServerComponentExample } from "@components/ServerComponentExample";
500
+ import { IntlayerClientProvider } from "next-intlayer";
501
+ import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";
502
+
503
+ const PageContent = () => {
504
+ const content = useIntlayer("page");
505
+
506
+ return (
507
+ <>
508
+ <p>{content.getStarted.main}</p>
509
+ <code>{content.getStarted.pageLink}</code>
510
+ </>
511
+ );
512
+ };
513
+
514
+ const Page = async ({ params }) => {
515
+ const { locale } = await params;
516
+
517
+ return (
518
+ <IntlayerServerProvider locale={locale}>
519
+ <PageContent />
520
+ <ServerComponentExample />
521
+
522
+ <IntlayerClientProvider locale={locale}>
523
+ <ClientComponentExample />
524
+ </IntlayerClientProvider>
525
+ </IntlayerServerProvider>
526
+ );
527
+ };
528
+ ```
529
+
530
+ - **`IntlayerClientProvider`** используется для предоставления локали компонентам на стороне клиента. Его можно разместить в любом родительском компоненте, включая layout. Однако рекомендуется размещать его в layout, так как Next.js разделяет код layout между страницами, что делает это более эффективным. Используя `IntlayerClientProvider` в layout, вы избегаете повторной инициализации для каждой страницы, улучшая производительность и поддерживая единый контекст локализации во всем приложении.
531
+ - **`IntlayerServerProvider`** используется для предоставления локали дочерним компонентам на сервере. Его нельзя устанавливать в layout.
532
+
533
+ > Layout и страница не могут использовать общий серверный контекст, поскольку система серверного контекста основана на хранилище данных для каждого запроса (через механизм [React's cache](https://react.dev/reference/react/cache)), из-за чего каждый "контекст" создаётся заново для разных сегментов приложения. Размещение провайдера в общем layout нарушит эту изоляцию, препятствуя правильной передаче значений серверного контекста вашим серверным компонентам.
534
+
535
+ > Макет и страница не могут использовать общий серверный контекст, поскольку система серверного контекста основана на хранилище данных для каждого запроса (через механизм [React's cache](https://react.dev/reference/react/cache)), из-за чего каждый "контекст" создаётся заново для разных сегментов приложения. Размещение провайдера в общем макете нарушит эту изоляцию, что помешает правильной передаче значений серверного контекста вашим серверным компонентам.
536
+
537
+ ```tsx {4,7} fileName="src/components/ClientComponentExample.tsx" codeFormat="typescript"
538
+ "use client";
539
+
540
+ import type { FC } from "react";
541
+ import { useIntlayer } from "next-intlayer";
542
+
543
+ export const ClientComponentExample: FC = () => {
544
+ const content = useIntlayer("client-component-example"); // Создаёт декларацию связанного контента
545
+
546
+ return (
547
+ <div>
548
+ <h2>{content.title}</h2>
549
+ <p>{content.content}</p>
550
+ </div>
551
+ );
552
+ };
553
+ ```
554
+
555
+ ```jsx {3,6} fileName="src/components/ClientComponentExample.mjx" codeFormat="esm"
556
+ "use client";
557
+
558
+ import { useIntlayer } from "next-intlayer";
559
+
560
+ const ClientComponentExample = () => {
561
+ const content = useIntlayer("client-component-example"); // Создание связанного объявления контента
562
+
563
+ return (
564
+ <div>
565
+ <h2>{content.title}</h2>
566
+ <p>{content.content}</p>
567
+ </div>
568
+ );
569
+ };
570
+ ```
571
+
572
+ ```jsx {3,6} fileName="src/components/ClientComponentExample.csx" codeFormat="commonjs"
573
+ "use client";
574
+
575
+ const { useIntlayer } = require("next-intlayer");
576
+
577
+ const ClientComponentExample = () => {
578
+ const content = useIntlayer("client-component-example"); // Создание связанного объявления контента
579
+
580
+ return (
581
+ <div>
582
+ <h2>{content.title}</h2>
583
+ <p>{content.content}</p>
584
+ </div>
585
+ );
586
+ };
587
+ ```
588
+
589
+ ```tsx {2} fileName="src/components/ServerComponentExample.tsx" codeFormat="typescript"
590
+ import type { FC } from "react";
591
+ import { useIntlayer } from "next-intlayer/server";
592
+
593
+ export const ServerComponentExample: FC = () => {
594
+ const content = useIntlayer("server-component-example"); // Создание связанного объявления контента
595
+
596
+ return (
597
+ <div>
598
+ <h2>{content.title}</h2>
599
+ <p>{content.content}</p>
600
+ </div>
601
+ );
602
+ };
603
+ ```
604
+
605
+ ```jsx {1} fileName="src/components/ServerComponentExample.mjx" codeFormat="esm"
606
+ import { useIntlayer } from "next-intlayer/server";
607
+
608
+ const ServerComponentExample = () => {
609
+ const content = useIntlayer("server-component-example"); // Создание связанного объявления контента
610
+
611
+ return (
612
+ <div>
613
+ <h2>{content.title}</h2>
614
+ <p>{content.content}</p>
615
+ </div>
616
+ );
617
+ };
618
+ ```
619
+
620
+ ```jsx {1} fileName="src/components/ServerComponentExample.csx" codeFormat="commonjs"
621
+ const { useIntlayer } = require("next-intlayer/server");
622
+
623
+ const ServerComponentExample = () => {
624
+ const content = useIntlayer("server-component-example"); // Создание связанного объявления контента
625
+
626
+ return (
627
+ <div>
628
+ <h2>{content.title}</h2>
629
+ <p>{content.content}</p>
630
+ </div>
631
+ );
632
+ };
633
+ ```
634
+
635
+ > Если вы хотите использовать ваш контент в атрибуте типа `string`, таком как `alt`, `title`, `href`, `aria-label` и т.д., вы должны вызвать значение функции, например:
636
+
637
+ > ```jsx
638
+ > <img src={content.image.src.value} alt={content.image.value} />
639
+ > ```
640
+
641
+ > Чтобы узнать больше о хуке `useIntlayer`, обратитесь к [документации](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ru/packages/next-intlayer/useIntlayer.md).
642
+
643
+ ### (Необязательно) Шаг 7: Настройка прокси для определения локали
644
+
645
+ Настройте прокси для определения предпочитаемой пользователем локали:
646
+
647
+ ```typescript fileName="src/proxy.ts" codeFormat="typescript"
648
+ export { intlayerProxy as proxy } from "next-intlayer/proxy";
649
+
650
+ export const config = {
651
+ matcher:
652
+ "/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\..*|_next).*)",
653
+ };
654
+ ```
655
+
656
+ ```javascript fileName="src/proxy.mjs" codeFormat="esm"
657
+ export { intlayerProxy as proxy } from "next-intlayer/proxy";
658
+
659
+ export const config = {
660
+ matcher:
661
+ "/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\..*|_next).*)",
662
+ };
663
+ ```
664
+
665
+ ```javascript fileName="src/proxy.cjs" codeFormat="commonjs"
666
+ // Импортируем intlayerProxy из пакета next-intlayer/proxy
667
+ const { intlayerProxy } = require("next-intlayer/proxy");
668
+
669
+ // Конфигурация прокси с указанием matcher для исключения определённых путей
670
+ const config = {
671
+ matcher:
672
+ "/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\..*|_next).*)",
673
+ };
674
+
675
+ // Экспортируем proxy и config для использования в приложении
676
+ module.exports = { proxy: intlayerProxy, config };
677
+ ```
678
+
679
+ > `intlayerProxy` используется для определения предпочтительной локали пользователя и перенаправления его на соответствующий URL, как указано в [конфигурации](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ru/configuration.md). Кроме того, он позволяет сохранять предпочтительную локаль пользователя в cookie.
680
+
681
+ > Если необходимо объединить несколько прокси вместе (например, `intlayerProxy` с аутентификацией или кастомными прокси), Intlayer теперь предоставляет помощник под названием `multipleProxies`.
682
+
683
+ ```ts
684
+ import { multipleProxies, intlayerProxy } from "next-intlayer/proxy";
685
+ import { customProxy } from "@utils/customProxy";
686
+
687
+ export const proxy = multipleProxies([intlayerProxy, customProxy]);
688
+ ```
689
+
690
+ ### (Необязательно) Шаг 8: Интернационализация ваших метаданных
691
+
692
+ Если вы хотите интернационализировать ваши метаданные, такие как заголовок страницы, вы можете использовать функцию `generateMetadata`, предоставляемую Next.js. Внутри вы можете получить содержимое из функции `getIntlayer` для перевода ваших метаданных.
693
+
694
+ ```typescript fileName="src/app/[locale]/metadata.content.ts" contentDeclarationFormat="typescript"
695
+ import { type Dictionary, t } from "intlayer";
696
+ import { Metadata } from "next";
697
+
698
+ const metadataContent = {
699
+ key: "page-metadata",
700
+ content: {
701
+ title: t({
702
+ en: "Create Next App",
703
+ fr: "Créer une application Next.js",
704
+ es: "Crear una aplicación Next.js",
705
+ }),
706
+ description: t({
707
+ en: "Generated by create next app",
708
+ fr: "Généré par create next app",
709
+ es: "Generado por create next app",
710
+ }),
711
+ },
712
+ } satisfies Dictionary<Metadata>;
713
+
714
+ export default metadataContent;
715
+ ```
716
+
717
+ ```javascript fileName="src/app/[locale]/metadata.content.mjs" contentDeclarationFormat="esm"
718
+ import { t } from "intlayer";
719
+
720
+ /** @type {import('intlayer').Dictionary<import('next').Metadata>} */
721
+ const metadataContent = {
722
+ key: "page-metadata",
723
+ content: {
724
+ title: t({
725
+ en: "Create Next App",
726
+ fr: "Créer une application Next.js",
727
+ es: "Crear una aplicación Next.js",
728
+ }),
729
+ description: t({
730
+ en: "Generated by create next app",
731
+ ru: "Сгенерировано с помощью create next app",
732
+ fr: "Généré par create next app",
733
+ es: "Generado por create next app",
734
+ }),
735
+ },
736
+ };
737
+
738
+ export default metadataContent;
739
+ fr: "Généré par create next app",
740
+ es: "Generado por create next app",
741
+ }),
742
+ },
743
+ };
744
+
745
+ export default metadataContent;
746
+ ```
747
+
748
+ ```javascript fileName="src/app/[locale]/metadata.content.cjs" contentDeclarationFormat="commonjs"
749
+ const { t } = require("intlayer");
750
+
751
+ /** @type {import('intlayer').Dictionary<import('next').Metadata>} */
752
+ const metadataContent = {
753
+ key: "page-metadata",
754
+ content: {
755
+ title: t({
756
+ en: "Create Next App",
757
+ fr: "Créer une application Next.js",
758
+ es: "Crear una aplicación Next.js",
759
+ }),
760
+ description: t({
761
+ en: "Generated by create next app",
762
+ ru: "Сгенерировано с помощью create next app",
763
+ fr: "Généré par create next app",
764
+ es: "Generado por create next app",
765
+ }),
766
+ },
767
+ };
768
+
769
+ module.exports = metadataContent;
770
+ ```
771
+
772
+ ```json fileName="src/app/[locale]/metadata.content.json" contentDeclarationFormat="json"
773
+ {
774
+ "key": "page-metadata",
775
+ "content": {
776
+ "title": {
777
+ "nodeType": "translation",
778
+ "translation": {
779
+ "ru": "Логотип Preact",
780
+ "en": "Preact logo",
781
+ "fr": "Logo Preact",
782
+ "es": "Logo Preact"
783
+ }
784
+ },
785
+ "description": {
786
+ "nodeType": "translation",
787
+ "translation": {
788
+ "ru": "Сгенерировано с помощью create next app",
789
+ "en": "Generated by create next app",
790
+ "fr": "Généré par create next app",
791
+ "es": "Generado por create next app"
792
+ }
793
+ }
794
+ }
795
+ }
796
+ ```
797
+
798
+ ````typescript fileName="src/app/[locale]/layout.tsx or src/app/[locale]/page.tsx" codeFormat="typescript"
799
+ import { getIntlayer, getMultilingualUrls } from "intlayer";
800
+ import type { Metadata } from "next";
801
+ import type { LocalPromiseParams } from "next-intlayer";
802
+
803
+ export const generateMetadata = async ({
804
+ params,
805
+ }: LocalPromiseParams): Promise<Metadata> => {
806
+ const { locale } = await params;
807
+
808
+ const metadata = getIntlayer("page-metadata", locale);
809
+
810
+ /**
811
+ * Генерирует объект, содержащий все URL для каждой локали.
812
+ *
813
+ * Пример:
814
+ * ```ts
815
+ * getMultilingualUrls('/about');
816
+ *
817
+ * // Возвращает
818
+ * // {
819
+ * // en: '/about',
820
+ * // fr: '/fr/about',
821
+ * // es: '/es/about',
822
+ * // }
823
+ * ```
824
+ */
825
+ const multilingualUrls = getMultilingualUrls("/");
826
+
827
+ return {
828
+ ...metadata,
829
+ alternates: {
830
+ canonical: multilingualUrls[locale as keyof typeof multilingualUrls],
831
+ languages: { ...multilingualUrls, "x-default": "/" },
832
+ },
833
+ openGraph: {
834
+ url: multilingualUrls[locale],
835
+ },
836
+ };
837
+ };
838
+
839
+ // ... Остальная часть кода
840
+ ````
841
+
842
+ ````javascript fileName="src/app/[locale]/layout.mjs or src/app/[locale]/page.mjs" codeFormat="esm"
843
+ import { getIntlayer, getMultilingualUrls } from "intlayer";
844
+
845
+ export const generateMetadata = async ({ params }) => {
846
+ const { locale } = await params;
847
+
848
+ const metadata = getIntlayer("page-metadata", locale);
849
+
850
+ /**
851
+ * Генерирует объект, содержащий все URL для каждого языка.
852
+ *
853
+ * Пример:
854
+ * ```ts
855
+ * getMultilingualUrls('/about');
856
+ *
857
+ * // Возвращает
858
+ * // {
859
+ * // en: '/about',
860
+ * // fr: '/fr/about',
861
+ * // es: '/es/about'
862
+ * // }
863
+ * ```
864
+ */
865
+ const multilingualUrls = getMultilingualUrls("/");
866
+
867
+ return {
868
+ ...metadata,
869
+ alternates: {
870
+ canonical: multilingualUrls[locale],
871
+ languages: { ...multilingualUrls, "x-default": "/" },
872
+ },
873
+ openGraph: {
874
+ url: multilingualUrls[locale],
875
+ },
876
+ };
877
+ };
878
+
879
+ // ... Остальная часть кода
880
+ ````
881
+
882
+ ````javascript fileName="src/app/[locale]/layout.cjs or src/app/[locale]/page.cjs" codeFormat="commonjs"
883
+ const { getIntlayer, getMultilingualUrls } = require("intlayer");
884
+
885
+ const generateMetadata = async ({ params }) => {
886
+ const { locale } = await params;
887
+
888
+ const metadata = getIntlayer("page-metadata", locale);
889
+
890
+ /**
891
+ * Генерирует объект, содержащий все URL для каждого языка.
892
+ *
893
+ * Пример:
894
+ * ```ts
895
+ * getMultilingualUrls('/about');
896
+ *
897
+ * // Возвращает
898
+ * // {
899
+ * // en: '/about',
900
+ * // fr: '/fr/about',
901
+ * // es: '/es/about'
902
+ * // }
903
+ * ```
904
+ */
905
+ const multilingualUrls = getMultilingualUrls("/");
906
+
907
+ return {
908
+ ...metadata,
909
+ alternates: {
910
+ canonical: multilingualUrls[locale],
911
+ languages: { ...multilingualUrls, "x-default": "/" },
912
+ },
913
+ openGraph: {
914
+ url: multilingualUrls[locale],
915
+ },
916
+ };
917
+ };
918
+
919
+ module.exports = { generateMetadata };
920
+
921
+ // ... Остальная часть кода
922
+ ````
923
+
924
+ > Обратите внимание, что функция `getIntlayer`, импортируемая из `next-intlayer`, возвращает ваш контент, обёрнутый в `IntlayerNode`, что позволяет интегрироваться с визуальным редактором. В то время как функция `getIntlayer`, импортируемая из `intlayer`, возвращает ваш контент напрямую без дополнительных свойств.
925
+
926
+ Альтернативно, вы можете использовать функцию `getTranslation` для объявления ваших метаданных. Однако рекомендуется использовать файлы декларации контента, чтобы автоматизировать перевод ваших метаданных и в какой-то момент вынести контент во внешние файлы.
927
+
928
+ ```typescript fileName="src/app/[locale]/layout.tsx or src/app/[locale]/page.tsx" codeFormat="typescript"
929
+ import {
930
+ type IConfigLocales,
931
+ getTranslation,
932
+ getMultilingualUrls,
933
+ } from "intlayer";
934
+ import type { Metadata } from "next";
935
+ import type { LocalPromiseParams } from "next-intlayer";
936
+
937
+ export const generateMetadata = async ({
938
+ params,
939
+ }: LocalPromiseParams): Promise<Metadata> => {
940
+ const { locale } = await params;
941
+ const t = <T>(content: IConfigLocales<T>) => getTranslation(content, locale);
942
+
943
+ return {
944
+ title: t<string>({
945
+ en: "My title",
946
+ fr: "Mon titre",
947
+ es: "Mi título",
948
+ }),
949
+ description: t({
950
+ en: "Моё описание",
951
+ fr: "Ma description",
952
+ es: "Mi descripción",
953
+ }),
954
+ };
955
+ };
956
+
957
+ // ... Остальная часть кода
958
+ ```
959
+
960
+ ```javascript fileName="src/app/[locale]/layout.mjs or src/app/[locale]/page.mjs" codeFormat="esm"
961
+ import { getTranslation, getMultilingualUrls } from "intlayer";
962
+
963
+ export const generateMetadata = async ({ params }) => {
964
+ const { locale } = await params;
965
+ const t = (content) => getTranslation(content, locale);
966
+
967
+ return {
968
+ title: t({
969
+ en: "Мой заголовок",
970
+ fr: "Mon titre",
971
+ es: "Mi título",
972
+ }),
973
+ description: t({
974
+ en: "Моё описание",
975
+ fr: "Ma description",
976
+ es: "Mi descripción",
977
+ }),
978
+ };
979
+ };
980
+
981
+ // ... Остальная часть кода
982
+ ```
983
+
984
+ ```javascript fileName="src/app/[locale]/layout.cjs or src/app/[locale]/page.cjs" codeFormat="commonjs"
985
+ const { getTranslation, getMultilingualUrls } = require("intlayer");
986
+
987
+ const generateMetadata = async ({ params }) => {
988
+ const { locale } = await params;
989
+
990
+ const t = (content) => getTranslation(content, locale);
991
+
992
+ return {
993
+ title: t({
994
+ en: "My title",
995
+ fr: "Mon titre",
996
+ es: "Mi título",
997
+ }),
998
+ description: t({
999
+ en: "My description",
1000
+ fr: "Ma description",
1001
+ es: "Mi descripción",
1002
+ }),
1003
+ };
1004
+ };
1005
+
1006
+ module.exports = { generateMetadata };
1007
+
1008
+ // ... Остальная часть кода
1009
+ ```
1010
+
1011
+ > Узнайте больше об оптимизации метаданных [в официальной документации Next.js](https://nextjs.org/docs/app/building-your-application/optimizing/metadata).
1012
+
1013
+ ### (Необязательно) Шаг 9: Интернационализация вашего sitemap.xml и robots.txt
1014
+
1015
+ Для интернационализации ваших файлов `sitemap.xml` и `robots.txt` вы можете использовать функцию `getMultilingualUrls`, предоставляемую Intlayer. Эта функция позволяет генерировать многоязычные URL-адреса для вашей карты сайта.
1016
+
1017
+ ```tsx fileName="src/app/sitemap.ts" codeFormat="typescript"
1018
+ import { getMultilingualUrls } from "intlayer";
1019
+ import type { MetadataRoute } from "next";
1020
+
1021
+ const sitemap = (): MetadataRoute.Sitemap => [
1022
+ {
1023
+ url: "https://example.com",
1024
+ alternates: {
1025
+ languages: { ...getMultilingualUrls("https://example.com") },
1026
+ },
1027
+ },
1028
+ {
1029
+ url: "https://example.com/login",
1030
+ alternates: {
1031
+ languages: { ...getMultilingualUrls("https://example.com/login") },
1032
+ },
1033
+ },
1034
+ {
1035
+ url: "https://example.com/register",
1036
+ alternates: {
1037
+ languages: { ...getMultilingualUrls("https://example.com/register") },
1038
+ },
1039
+ },
1040
+ ];
1041
+
1042
+ export default sitemap;
1043
+ ```
1044
+
1045
+ ```jsx fileName="src/app/sitemap.mjx" codeFormat="esm"
1046
+ import { getMultilingualUrls } from "intlayer";
1047
+
1048
+ const sitemap = () => [
1049
+ {
1050
+ url: "https://example.com",
1051
+ alternates: {
1052
+ languages: { ...getMultilingualUrls("https://example.com") },
1053
+ },
1054
+ },
1055
+ {
1056
+ url: "https://example.com/login",
1057
+ alternates: {
1058
+ languages: { ...getMultilingualUrls("https://example.com/login") },
1059
+ },
1060
+ },
1061
+ {
1062
+ url: "https://example.com/register",
1063
+ alternates: {
1064
+ languages: { ...getMultilingualUrls("https://example.com/register") },
1065
+ },
1066
+ },
1067
+ ];
1068
+
1069
+ export default sitemap;
1070
+ ```
1071
+
1072
+ ```jsx fileName="src/app/sitemap.csx" codeFormat="commonjs"
1073
+ const { getMultilingualUrls } = require("intlayer");
1074
+
1075
+ const sitemap = () => [
1076
+ {
1077
+ url: "https://example.com",
1078
+ alternates: {
1079
+ languages: { ...getMultilingualUrls("https://example.com") },
1080
+ },
1081
+ },
1082
+ {
1083
+ url: "https://example.com/login",
1084
+ alternates: {
1085
+ languages: { ...getMultilingualUrls("https://example.com/login") },
1086
+ },
1087
+ },
1088
+ {
1089
+ url: "https://example.com/register",
1090
+ alternates: {
1091
+ languages: { ...getMultilingualUrls("https://example.com/register") },
1092
+ },
1093
+ },
1094
+ ];
1095
+
1096
+ module.exports = sitemap;
1097
+ ```
1098
+
1099
+ ```tsx fileName="src/app/robots.ts" codeFormat="typescript"
1100
+ import type { MetadataRoute } from "next";
1101
+ import { getMultilingualUrls } from "intlayer";
1102
+
1103
+ const getAllMultilingualUrls = (urls: string[]) =>
1104
+ urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);
1105
+
1106
+ // Функция для получения всех мультиязычных URL
1107
+ const robots = (): MetadataRoute.Robots => ({
1108
+ rules: {
1109
+ userAgent: "*", // Правила для всех агентов пользователя
1110
+ allow: ["/"], // Разрешенные пути
1111
+ disallow: getAllMultilingualUrls(["/login", "/register"]), // Запрещенные пути (мультиязычные)
1112
+ },
1113
+ host: "https://example.com", // Хост сайта
1114
+ sitemap: `https://example.com/sitemap.xml`, // Путь к карте сайта
1115
+ });
1116
+
1117
+ export default robots;
1118
+ ```
1119
+
1120
+ ```jsx fileName="src/app/robots.mjx" codeFormat="esm"
1121
+ import { getMultilingualUrls } from "intlayer";
1122
+
1123
+ // Функция для получения всех мультиязычных URL
1124
+ const getAllMultilingualUrls = (urls) =>
1125
+ urls.flatMap((url) => Object.values(getMultilingualUrls(url)));
1126
+
1127
+ const robots = () => ({
1128
+ rules: {
1129
+ userAgent: "*", // Правила для всех агентов пользователя
1130
+ allow: ["/"], // Разрешенные пути
1131
+ disallow: getAllMultilingualUrls(["/login", "/register"]), // Запрещенные пути (мультиязычные)
1132
+ },
1133
+ host: "https://example.com", // Хост сайта
1134
+ sitemap: `https://example.com/sitemap.xml`,
1135
+ });
1136
+
1137
+ export default robots;
1138
+ ```
1139
+
1140
+ ```jsx fileName="src/app/robots.csx" codeFormat="commonjs"
1141
+ const { getMultilingualUrls } = require("intlayer");
1142
+
1143
+ // Получить все многоязычные URL-адреса из списка URL
1144
+ const getAllMultilingualUrls = (urls) =>
1145
+ urls.flatMap((url) => Object.values(getMultilingualUrls(url)));
1146
+
1147
+ const robots = () => ({
1148
+ rules: {
1149
+ userAgent: "*", // Правила для всех агентов пользователя
1150
+ allow: ["/"], // Разрешить доступ ко всем страницам
1151
+ disallow: getAllMultilingualUrls(["/login", "/register"]), // Запретить доступ к страницам входа и регистрации на всех языках
1152
+ },
1153
+ host: "https://example.com",
1154
+ sitemap: `https://example.com/sitemap.xml`,
1155
+ });
1156
+
1157
+ module.exports = robots;
1158
+ ```
1159
+
1160
+ > Узнайте больше об оптимизации карты сайта [в официальной документации Next.js](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/sitemap). Узнайте больше об оптимизации robots.txt [в официальной документации Next.js](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/robots).
1161
+
1162
+ ### (Необязательно) Шаг 10: Изменение языка вашего контента
1163
+
1164
+ Для изменения языка вашего контента в Next.js рекомендуется использовать компонент `Link` для перенаправления пользователей на соответствующую локализованную страницу. Компонент `Link` позволяет предварительно загружать страницу, что помогает избежать полной перезагрузки страницы.
1165
+
1166
+ ```tsx fileName="src/components/LocaleSwitcher.tsx" codeFormat="typescript"
1167
+ "use client";
1168
+
1169
+ import type { FC } from "react";
1170
+ import {
1171
+ Locales,
1172
+ getHTMLTextDir,
1173
+ getLocaleName,
1174
+ getLocalizedUrl,
1175
+ } from "intlayer";
1176
+ import { useLocale } from "next-intlayer";
1177
+ import Link from "next/link";
1178
+
1179
+ export const LocaleSwitcher: FC = () => {
1180
+ const { locale, pathWithoutLocale, availableLocales, setLocale } =
1181
+ useLocale();
1182
+
1183
+ return (
1184
+ <div>
1185
+ <button popoverTarget="localePopover">{getLocaleName(locale)}</button>
1186
+ <div id="localePopover" popover="auto">
1187
+ {availableLocales.map((localeItem) => (
1188
+ <Link
1189
+ href={getLocalizedUrl(pathWithoutLocale, localeItem)}
1190
+ key={localeItem}
1191
+ aria-current={locale === localeItem ? "page" : undefined}
1192
+ onClick={() => setLocale(localeItem)}
1193
+ replace // Будет гарантировать, что кнопка "назад" в браузере перенаправит на предыдущую страницу
1194
+ >
1195
+ <span>
1196
+ {/* Локаль - например, FR */}
1197
+ {localeItem}
1198
+ </span>
1199
+ <span>
1200
+ {/* Язык на его собственной локали - например, Français */}
1201
+ {getLocaleName(localeItem, locale)}
1202
+ </span>
1203
+ <span dir={getHTMLTextDir(localeItem)} lang={localeItem}>
1204
+ {/* Язык на текущей локали - например, Francés при установленной локали Locales.SPANISH */}
1205
+ {getLocaleName(localeItem)}
1206
+ </span>
1207
+ <span dir="ltr" lang={Locales.ENGLISH}>
1208
+ {/* Язык на английском - например, French */}
1209
+ {getLocaleName(localeItem, Locales.ENGLISH)}
1210
+ </span>
1211
+ </Link>
1212
+ ))}
1213
+ </div>
1214
+ </div>
1215
+ );
1216
+ };
1217
+ ```
1218
+
1219
+ ```jsx fileName="src/components/LocaleSwitcher.msx" codeFormat="esm"
1220
+ "use client";
1221
+
1222
+ import {
1223
+ Locales,
1224
+ getHTMLTextDir,
1225
+ getLocaleName,
1226
+ getLocalizedUrl,
1227
+ } from "intlayer";
1228
+ import { useLocale } from "next-intlayer";
1229
+ import Link from "next/link";
1230
+
1231
+ export const LocaleSwitcher = () => {
1232
+ const { locale, pathWithoutLocale, availableLocales, setLocale } =
1233
+ useLocale();
1234
+
1235
+ return (
1236
+ <div>
1237
+ <button popoverTarget="localePopover">{getLocaleName(locale)}</button>
1238
+ <div id="localePopover" popover="auto">
1239
+ {availableLocales.map((localeItem) => (
1240
+ <Link
1241
+ href={getLocalizedUrl(pathWithoutLocale, localeItem)}
1242
+ key={localeItem}
1243
+ aria-current={locale === localeItem ? "page" : undefined}
1244
+ onClick={() => setLocale(localeItem)}
1245
+ replace // Обеспечит, что кнопка "назад" в браузере перенаправит на предыдущую страницу
1246
+ >
1247
+ <span>
1248
+ {/* Локаль - например, FR */}
1249
+ {localeItem}
1250
+ </span>
1251
+ <span>
1252
+ {/* Язык на своей локали - например, Français */}
1253
+ {getLocaleName(localeItem, locale)}
1254
+ </span>
1255
+ <span dir={getHTMLTextDir(localeItem)} lang={localeItem}>
1256
+ {/* Язык на текущей локали - например, Francés при текущей локали Locales.SPANISH */}
1257
+ {getLocaleName(localeItem)}
1258
+ </span>
1259
+ <span dir="ltr" lang={Locales.ENGLISH}>
1260
+ {/* Язык на английском - например, French */}
1261
+ {getLocaleName(localeItem, Locales.ENGLISH)}
1262
+ </span>
1263
+ </Link>
1264
+ ))}
1265
+ </div>
1266
+ </div>
1267
+ );
1268
+ };
1269
+ ```
1270
+
1271
+ ```jsx fileName="src/components/LocaleSwitcher.csx" codeFormat="commonjs"
1272
+ "use client";
1273
+
1274
+ const {
1275
+ Locales,
1276
+ getHTMLTextDir,
1277
+ getLocaleName,
1278
+ getLocalizedUrl,
1279
+ } = require("intlayer");
1280
+ const { useLocale } = require("next-intlayer");
1281
+ const Link = require("next/link");
1282
+
1283
+ export const LocaleSwitcher = () => {
1284
+ const { locale, pathWithoutLocale, availableLocales, setLocale } =
1285
+ useLocale();
1286
+
1287
+ return (
1288
+ <div>
1289
+ <button popoverTarget="localePopover">{getLocaleName(locale)}</button>
1290
+ <div id="localePopover" popover="auto">
1291
+ {availableLocales.map((localeItem) => (
1292
+ <Link
1293
+ href={getLocalizedUrl(pathWithoutLocale, localeItem)}
1294
+ key={localeItem}
1295
+ aria-current={locale === localeItem ? "page" : undefined}
1296
+ onClick={() => setLocale(localeItem)}
1297
+ replace // Обеспечит, что кнопка "назад" в браузере вернёт на предыдущую страницу
1298
+ >
1299
+ <span>
1300
+ {/* Локаль - например, FR */}
1301
+ {localeItem}
1302
+ </span>
1303
+ <span>
1304
+ {/* Язык на своей локали - например, Français */}
1305
+ {getLocaleName(localeItem, locale)}
1306
+ </span>
1307
+ <span dir={getHTMLTextDir(localeItem)} lang={localeItem}>
1308
+ {/* Язык на текущей локали - например, Francés при установленной локали Locales.SPANISH */}
1309
+ {getLocaleName(localeItem)}
1310
+ </span>
1311
+ <span dir="ltr" lang={Locales.ENGLISH}>
1312
+ {/* Язык на английском - например, French */}
1313
+ {getLocaleName(localeItem, Locales.ENGLISH)}
1314
+ </span>
1315
+ </Link>
1316
+ ))}
1317
+ </div>
1318
+ </div>
1319
+ );
1320
+ };
1321
+ ```
1322
+
1323
+ > Альтернативным способом является использование функции `setLocale`, предоставляемой хуком `useLocale`. Эта функция не позволит предварительно загружать страницу. Подробнее смотрите в [документации по хуку `useLocale`](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ru/packages/next-intlayer/useLocale.md).
1324
+
1325
+ > Вы также можете задать функцию в опции `onLocaleChange`, чтобы запускать пользовательскую функцию при изменении локали.
1326
+
1327
+ ```tsx fileName="src/components/LocaleSwitcher.tsx"
1328
+ "use client";
1329
+
1330
+ import { useRouter } from "next/navigation";
1331
+ import { useLocale } from "next-intlayer";
1332
+ import { getLocalizedUrl } from "intlayer";
1333
+
1334
+ // ... Остальная часть кода
1335
+
1336
+ const router = useRouter();
1337
+ const { setLocale } = useLocale({
1338
+ onLocaleChange: (locale) => {
1339
+ router.push(getLocalizedUrl(pathWithoutLocale, locale));
1340
+ },
1341
+ });
1342
+
1343
+ return (
1344
+ <button onClick={() => setLocale(Locales.FRENCH)}>
1345
+ Переключить на французский
1346
+ </button>
1347
+ );
1348
+ ```
1349
+
1350
+ > Ссылки на документацию:
1351
+ >
1352
+ > - [`useLocale` hook](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ru/packages/next-intlayer/useLocale.md)
1353
+ > - [`getLocaleName` hook](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ru/packages/intlayer/getLocaleName.md)
1354
+ > - [`getLocalizedUrl` hook](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ru/packages/intlayer/getLocalizedUrl.md)
1355
+ > - [`getHTMLTextDir` хук](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ru/packages/intlayer/getHTMLTextDir.md)
1356
+ > - Атрибут [`hrefLang`](https://developers.google.com/search/docs/specialty/international/localized-versions?hl=fr)
1357
+ > - Атрибут [`lang`](https://developer.mozilla.org/ru/docs/Web/HTML/Global_attributes/lang)
1358
+ > - Атрибут [`dir`](https://developer.mozilla.org/ru/docs/Web/HTML/Global_attributes/dir)
1359
+ > - Атрибут [`aria-current`](https://developer.mozilla.org/ru/docs/Web/Accessibility/ARIA/Attributes/aria-current)
1360
+
1361
+ ### (Необязательно) Шаг 11: Создание локализованного компонента Link
1362
+
1363
+ Чтобы обеспечить навигацию вашего приложения с учетом текущей локали, вы можете создать пользовательский компонент `Link`. Этот компонент автоматически добавляет префикс текущего языка к внутренним URL, так что, например, когда франкоязычный пользователь нажимает на ссылку на страницу "О нас", он перенаправляется на `/fr/about` вместо `/about`.
1364
+
1365
+ Такое поведение полезно по нескольким причинам:
1366
+
1367
+ - **SEO и удобство для пользователя**: Локализованные URL помогают поисковым системам правильно индексировать страницы на разных языках и предоставлять пользователям контент на предпочитаемом языке.
1368
+ - **Последовательность**: Используя локализованную ссылку по всему приложению, вы гарантируете, что навигация остается в рамках текущей локали, предотвращая неожиданные переключения языка.
1369
+ - **Поддерживаемость**: Централизация логики локализации в одном компоненте упрощает управление URL-адресами, делая ваш код более удобным для поддержки и расширения по мере роста приложения.
1370
+
1371
+ Ниже приведена реализация локализованного компонента `Link` на TypeScript:
1372
+
1373
+ ```tsx fileName="src/components/Link.tsx" codeFormat="typescript"
1374
+ "use client";
1375
+
1376
+ import { getLocalizedUrl } from "intlayer";
1377
+ import NextLink, { type LinkProps as NextLinkProps } from "next/link";
1378
+ import { useLocale } from "next-intlayer";
1379
+ import type { PropsWithChildren, FC } from "react";
1380
+
1381
+ /**
1382
+ * Вспомогательная функция для проверки, является ли данный URL внешним.
1383
+ * Если URL начинается с http:// или https://, он считается внешним.
1384
+ */
1385
+ export const checkIsExternalLink = (href?: string): boolean =>
1386
+ /^https?:\/\//.test(href ?? "");
1387
+
1388
+ /**
1389
+ * Кастомный компонент Link, который адаптирует атрибут href в зависимости от текущей локали.
1390
+ * Для внутренних ссылок используется `getLocalizedUrl`, чтобы добавить префикс локали к URL (например, /fr/about).
1391
+ * Это гарантирует, что навигация останется в контексте той же локали.
1392
+ */
1393
+ export const Link: FC<PropsWithChildren<NextLinkProps>> = ({
1394
+ href,
1395
+ children,
1396
+ ...props
1397
+ }) => {
1398
+ const { locale } = useLocale();
1399
+ const isExternalLink = checkIsExternalLink(href.toString());
1400
+
1401
+ // Если ссылка внутренняя и href валиден, получить локализованный URL.
1402
+ const hrefI18n: NextLinkProps["href"] =
1403
+ href && !isExternalLink ? getLocalizedUrl(href.toString(), locale) : href;
1404
+
1405
+ return (
1406
+ <NextLink href={hrefI18n} {...props}>
1407
+ {children}
1408
+ </NextLink>
1409
+ );
1410
+ };
1411
+ ```
1412
+
1413
+ ```jsx fileName="src/components/Link.mjx" codeFormat="esm"
1414
+ "use client";
1415
+
1416
+ import { getLocalizedUrl } from "intlayer";
1417
+ import NextLink from "next/link";
1418
+ import { useLocale } from "next-intlayer";
1419
+
1420
+ /**
1421
+ * Утилита для проверки, является ли данный URL внешним.
1422
+ * Если URL начинается с http:// или https://, он считается внешним.
1423
+ */
1424
+ export const checkIsExternalLink = (href) => /^https?:\/\//.test(href ?? "");
1425
+
1426
+ /**
1427
+ * Кастомный компонент Link, который адаптирует атрибут href в зависимости от текущей локали.
1428
+ * Для внутренних ссылок используется `getLocalizedUrl` для добавления префикса локали к URL (например, /fr/about).
1429
+ * Это гарантирует, что навигация остается в контексте одной локали.
1430
+ */
1431
+ export const Link = ({ href, children, ...props }) => {
1432
+ const { locale } = useLocale();
1433
+ const isExternalLink = checkIsExternalLink(href.toString());
1434
+
1435
+ // Если ссылка внутренняя и предоставлен действительный href, получить локализованный URL.
1436
+ const hrefI18n =
1437
+ href && !isExternalLink ? getLocalizedUrl(href.toString(), locale) : href;
1438
+
1439
+ return (
1440
+ <NextLink href={hrefI18n} {...props}>
1441
+ {children}
1442
+ </NextLink>
1443
+ );
1444
+ };
1445
+ ```
1446
+
1447
+ ```jsx fileName="src/components/Link.csx" codeFormat="commonjs"
1448
+ "use client";
1449
+
1450
+ const { getLocalizedUrl } = require("intlayer");
1451
+ const NextLink = require("next/link");
1452
+ const { useLocale } = require("next-intlayer");
1453
+
1454
+ /**
1455
+ * Вспомогательная функция для проверки, является ли данный URL внешним.
1456
+ * Если URL начинается с http:// или https://, он считается внешним.
1457
+ */
1458
+ const checkIsExternalLink = (href) => /^https?:\/\//.test(href ?? "");
1459
+
1460
+ /**
1461
+ * Пользовательский компонент Link, который адаптирует атрибут href в зависимости от текущей локали.
1462
+ * Для внутренних ссылок используется `getLocalizedUrl` для добавления префикса локали к URL (например, /fr/about).
1463
+ * Это гарантирует, что навигация происходит в рамках одной локали.
1464
+ */
1465
+ const Link = ({ href, children, ...props }) => {
1466
+ const { locale } = useLocale();
1467
+ const isExternalLink = checkIsExternalLink(href.toString());
1468
+
1469
+ // Если ссылка внутренняя и предоставлен корректный href, получить локализованный URL.
1470
+ const hrefI18n =
1471
+ href && !isExternalLink ? getLocalizedUrl(href.toString(), locale) : href;
1472
+
1473
+ return (
1474
+ <NextLink href={hrefI18n} {...props}>
1475
+ {children}
1476
+ </NextLink>
1477
+ );
1478
+ };
1479
+ ```
1480
+
1481
+ #### Как это работает
1482
+
1483
+ - **Определение внешних ссылок**:
1484
+ Вспомогательная функция `checkIsExternalLink` определяет, является ли URL внешним. Внешние ссылки остаются без изменений, так как они не требуют локализации.
1485
+
1486
+ - **Получение текущей локали**:
1487
+ Хук `useLocale` предоставляет текущую локаль (например, `fr` для французского).
1488
+
1489
+ - **Локализация URL**:
1490
+ Для внутренних ссылок (то есть не внешних) используется `getLocalizedUrl`, которая автоматически добавляет префикс локали к URL. Это означает, что если пользователь использует французский язык, передача `/about` в качестве `href` преобразуется в `/fr/about`.
1491
+
1492
+ - **Возврат ссылки**:
1493
+ Компонент возвращает элемент `<a>` с локализованным URL, обеспечивая согласованную навигацию в рамках выбранной локали.
1494
+
1495
+ Интегрируя этот компонент `Link` по всему вашему приложению, вы поддерживаете последовательный и ориентированный на язык пользовательский опыт, а также получаете преимущества улучшенного SEO и удобства использования.
1496
+
1497
+ ### (Необязательно) Шаг 12: Получение текущей локали в Server Actions
1498
+
1499
+ Если вам нужна активная локаль внутри Server Action (например, для локализации писем или выполнения логики, зависящей от локали), вызовите `getLocale` из `next-intlayer/server`:
1500
+
1501
+ ```tsx fileName="src/app/actions/getLocale.ts" codeFormat="typescript"
1502
+ "use server";
1503
+
1504
+ import { getLocale } from "next-intlayer/server";
1505
+
1506
+ export const myServerAction = async () => {
1507
+ const locale = await getLocale();
1508
+
1509
+ // Сделать что-то с локалью
1510
+ };
1511
+ ```
1512
+
1513
+ > Функция `getLocale` использует каскадную стратегию для определения локали пользователя:
1514
+ >
1515
+ > 1. Сначала проверяет заголовки запроса на наличие значения локали, которое могло быть установлено прокси
1516
+ > 2. Если локаль не найдена в заголовках, ищет локаль, сохранённую в cookies
1517
+ > 3. Если cookies отсутствуют, пытается определить предпочитаемый язык пользователя из настроек браузера
1518
+ > 4. В крайнем случае используется локаль по умолчанию, настроенная в приложении
1519
+ >
1520
+ > Это гарантирует выбор наиболее подходящей локали на основе доступного контекста.
1521
+
1522
+ ### (Необязательно) Шаг 13: Оптимизация размера вашего бандла
1523
+
1524
+ При использовании `next-intlayer` словари по умолчанию включаются в бандл для каждой страницы. Чтобы оптимизировать размер бандла, Intlayer предоставляет необязательный плагин SWC, который интеллектуально заменяет вызовы `useIntlayer` с помощью макросов. Это гарантирует, что словари включаются в бандлы только для тех страниц, которые действительно их используют.
1525
+
1526
+ Чтобы включить эту оптимизацию, установите пакет `@intlayer/swc`. После установки `next-intlayer` автоматически обнаружит и использует этот плагин:
1527
+
1528
+ ```bash packageManager="npm"
1529
+ npm install @intlayer/swc --save-dev
1530
+ ```
1531
+
1532
+ ```bash packageManager="pnpm"
1533
+ pnpm add @intlayer/swc --save-dev
1534
+ ```
1535
+
1536
+ ```bash packageManager="yarn"
1537
+ yarn add @intlayer/swc --save-dev
1538
+ ```
1539
+
1540
+ > Примечание: Эта оптимизация доступна только для Next.js версии 13 и выше.
1541
+
1542
+ > Примечание: Этот пакет не устанавливается по умолчанию, поскольку плагины SWC в Next.js всё ещё находятся в экспериментальной стадии. Это может измениться в будущем.
1543
+
1544
+ ### Отслеживание изменений словарей в Turbopack
1545
+
1546
+ При использовании Turbopack в качестве сервера разработки с командой `next dev`, изменения в словарях по умолчанию не будут автоматически обнаруживаться.
1547
+
1548
+ Это ограничение связано с тем, что Turbopack не может параллельно запускать плагины webpack для мониторинга изменений в ваших файлах контента. Чтобы обойти это, необходимо использовать команду `intlayer watch`, которая одновременно запускает сервер разработки и наблюдатель сборки Intlayer.
1549
+
1550
+ ```json5 fileName="package.json"
1551
+ {
1552
+ // ... Ваши существующие настройки package.json
1553
+ "scripts": {
1554
+ // ... Ваши существующие настройки скриптов
1555
+ "dev": "intlayer watch --with 'next dev'",
1556
+ },
1557
+ }
1558
+ ```
1559
+
1560
+ > Если вы используете next-intlayer@<=6.x.x, необходимо сохранять флаг `--turbopack`, чтобы приложение Next.js 16 корректно работало с Turbopack. Мы рекомендуем использовать next-intlayer@>=7.x.x, чтобы избежать этого ограничения.
1561
+
1562
+ ### Настройка TypeScript
1563
+
1564
+ Intlayer использует расширение модулей (module augmentation), чтобы использовать преимущества TypeScript и сделать ваш код более надежным.
1565
+
1566
+ ![Автодополнение](https://github.com/aymericzip/intlayer/blob/main/docs/assets/autocompletion.png?raw=true)
1567
+
1568
+ ![Ошибка перевода](https://github.com/aymericzip/intlayer/blob/main/docs/assets/translation_error.png?raw=true)
1569
+
1570
+ Убедитесь, что ваша конфигурация TypeScript включает автогенерируемые типы.
1571
+
1572
+ ```json5 fileName="tsconfig.json"
1573
+ {
1574
+ // ... Ваши существующие конфигурации TypeScript
1575
+ "include": [
1576
+ // ... Ваши существующие конфигурации TypeScript
1577
+ ".intlayer/**/*.ts", // Включить автоматически сгенерированные типы
1578
+ ],
1579
+ }
1580
+ ```
1581
+
1582
+ ### Конфигурация Git
1583
+
1584
+ Рекомендуется игнорировать файлы, сгенерированные Intlayer. Это позволит избежать их коммита в ваш репозиторий Git.
1585
+
1586
+ Для этого вы можете добавить следующие инструкции в ваш файл `.gitignore`:
1587
+
1588
+ ```plaintext fileName=".gitignore"
1589
+ # Игнорировать файлы, сгенерированные Intlayer
1590
+ .intlayer
1591
+ ```
1592
+
1593
+ ### Расширение VS Code
1594
+
1595
+ Для улучшения вашего опыта разработки с Intlayer вы можете установить официальное **расширение Intlayer для VS Code**.
1596
+
1597
+ [Установить из VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=intlayer.intlayer-vs-code-extension)
1598
+
1599
+ Это расширение предоставляет:
1600
+
1601
+ - **Автодополнение** для ключей переводов.
1602
+ - **Обнаружение ошибок в реальном времени** для отсутствующих переводов.
1603
+ - **Встроенный просмотр** переведённого контента.
1604
+ - **Быстрые действия** для лёгкого создания и обновления переводов.
1605
+
1606
+ Для получения дополнительной информации о том, как использовать расширение, обратитесь к [документации по расширению Intlayer для VS Code](https://intlayer.org/doc/vs-code-extension).
1607
+
1608
+ ### Дальнейшие шаги
1609
+
1610
+ Для дальнейшей работы вы можете реализовать [визуальный редактор](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ru/intlayer_visual_editor.md) или вынести ваш контент с помощью [CMS](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ru/intlayer_CMS.md).