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

|
|
1720
|
+
|
|
1721
|
+

|
|
1722
|
+
|
|
1723
|
+
Переконайтеся, що ваша конфігурація TypeScript включає автогенеровані типи.
|
|
1724
|
+
|
|
1725
|
+
```json5 fileName="tsconfig.json"
|
|
1726
|
+
{
|
|
1727
|
+
// ... Ваші існуючі конфігурації TypeScript
|
|
1728
|
+
"include": [
|
|
1729
|
+
// ... Ваші існуючі конфігурації TypeScript
|
|
1730
|
+
".intlayer/**/*.ts", // Включити автогенеровані типи
|
|
1731
|
+
],
|
|
1732
|
+
}
|
|
1733
|
+
```
|
|
1734
|
+
|
|
1735
|
+
### Налаштування Git
|
|
1736
|
+
|
|
1737
|
+
Рекомендується ігнорувати файли, згенеровані Intlayer. Це дозволяє уникнути їх додавання до вашого Git-репозиторію.
|
|
1738
|
+
|
|
1739
|
+
Для цього додайте наступні інструкції у файл `.gitignore`:
|
|
1740
|
+
|
|
1741
|
+
```plaintext fileName=".gitignore"
|
|
1742
|
+
# Ігнорувати файли, згенеровані Intlayer
|
|
1743
|
+
.intlayer
|
|
1744
|
+
```
|
|
1745
|
+
|
|
1746
|
+
### Розширення VS Code
|
|
1747
|
+
|
|
1748
|
+
Щоб покращити досвід розробки з Intlayer, ви можете встановити офіційне **Intlayer VS Code Extension**.
|
|
1749
|
+
|
|
1750
|
+
[Install from the VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=intlayer.intlayer-vs-code-extension)
|
|
1751
|
+
|
|
1752
|
+
Це розширення надає:
|
|
1753
|
+
|
|
1754
|
+
- **Автозаповнення** для ключів перекладу.
|
|
1755
|
+
- **Виявлення помилок у реальному часі** для відсутніх перекладів.
|
|
1756
|
+
- **Інлайн-попередні перегляди** перекладеного вмісту.
|
|
1757
|
+
- **Швидкі дії** для простого створення та оновлення перекладів.
|
|
1758
|
+
|
|
1759
|
+
Для детальнішої інформації про використання розширення зверніться до [документації розширення Intlayer для VS Code](https://intlayer.org/doc/vs-code-extension).
|
|
1760
|
+
|
|
1761
|
+
### Подальші кроки
|
|
1762
|
+
|
|
1763
|
+
Щоб просунутися далі, ви можете реалізувати [візуальний редактор](https://github.com/aymericzip/intlayer/blob/main/docs/docs/uk/intlayer_visual_editor.md) або винести ваш вміст, використовуючи [CMS](https://github.com/aymericzip/intlayer/blob/main/docs/docs/uk/intlayer_CMS.md).
|