@intlayer/docs 7.5.12 → 7.5.14
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/per-component_vs_centralized_i18n.md +248 -0
- package/blog/de/per-component_vs_centralized_i18n.md +248 -0
- package/blog/en/_per-component_vs_centralized_i18n.md +252 -0
- package/blog/en/per-component_vs_centralized_i18n.md +248 -0
- package/blog/en-GB/per-component_vs_centralized_i18n.md +247 -0
- package/blog/es/per-component_vs_centralized_i18n.md +245 -0
- package/blog/fr/per-component_vs_centralized_i18n.md +245 -0
- package/blog/hi/per-component_vs_centralized_i18n.md +249 -0
- package/blog/id/per-component_vs_centralized_i18n.md +248 -0
- package/blog/it/per-component_vs_centralized_i18n.md +247 -0
- package/blog/ja/per-component_vs_centralized_i18n.md +247 -0
- package/blog/ko/per-component_vs_centralized_i18n.md +246 -0
- package/blog/pl/per-component_vs_centralized_i18n.md +247 -0
- package/blog/pt/per-component_vs_centralized_i18n.md +246 -0
- package/blog/ru/per-component_vs_centralized_i18n.md +251 -0
- package/blog/tr/per-component_vs_centralized_i18n.md +244 -0
- package/blog/uk/compiler_vs_declarative_i18n.md +224 -0
- package/blog/uk/i18n_using_next-i18next.md +1086 -0
- package/blog/uk/i18n_using_next-intl.md +760 -0
- package/blog/uk/index.md +69 -0
- package/blog/uk/internationalization_and_SEO.md +273 -0
- package/blog/uk/intlayer_with_i18next.md +211 -0
- package/blog/uk/intlayer_with_next-i18next.md +202 -0
- package/blog/uk/intlayer_with_next-intl.md +203 -0
- package/blog/uk/intlayer_with_react-i18next.md +200 -0
- package/blog/uk/intlayer_with_react-intl.md +202 -0
- package/blog/uk/intlayer_with_vue-i18n.md +206 -0
- package/blog/uk/l10n_platform_alternative/Lokalise.md +80 -0
- package/blog/uk/l10n_platform_alternative/crowdin.md +80 -0
- package/blog/uk/l10n_platform_alternative/phrase.md +78 -0
- package/blog/uk/list_i18n_technologies/CMS/drupal.md +143 -0
- package/blog/uk/list_i18n_technologies/CMS/wix.md +167 -0
- package/blog/uk/list_i18n_technologies/CMS/wordpress.md +189 -0
- package/blog/uk/list_i18n_technologies/frameworks/angular.md +125 -0
- package/blog/uk/list_i18n_technologies/frameworks/flutter.md +128 -0
- package/blog/uk/list_i18n_technologies/frameworks/react-native.md +217 -0
- package/blog/uk/list_i18n_technologies/frameworks/react.md +155 -0
- package/blog/uk/list_i18n_technologies/frameworks/svelte.md +145 -0
- package/blog/uk/list_i18n_technologies/frameworks/vue.md +144 -0
- package/blog/uk/next-i18next_vs_next-intl_vs_intlayer.md +1499 -0
- package/blog/uk/nextjs-multilingual-seo-comparison.md +360 -0
- package/blog/uk/per-component_vs_centralized_i18n.md +248 -0
- package/blog/uk/rag_powered_documentation_assistant.md +288 -0
- package/blog/uk/react-i18next_vs_react-intl_vs_intlayer.md +164 -0
- package/blog/uk/vue-i18n_vs_intlayer.md +279 -0
- package/blog/uk/what_is_internationalization.md +167 -0
- package/blog/vi/per-component_vs_centralized_i18n.md +246 -0
- package/blog/zh/per-component_vs_centralized_i18n.md +248 -0
- package/dist/cjs/common.cjs.map +1 -1
- package/dist/cjs/generated/blog.entry.cjs +20 -0
- package/dist/cjs/generated/blog.entry.cjs.map +1 -1
- package/dist/cjs/generated/docs.entry.cjs.map +1 -1
- package/dist/cjs/generated/frequentQuestions.entry.cjs +20 -0
- package/dist/cjs/generated/frequentQuestions.entry.cjs.map +1 -1
- package/dist/cjs/generated/legal.entry.cjs.map +1 -1
- package/dist/esm/common.mjs.map +1 -1
- package/dist/esm/generated/blog.entry.mjs +20 -0
- package/dist/esm/generated/blog.entry.mjs.map +1 -1
- package/dist/esm/generated/docs.entry.mjs.map +1 -1
- package/dist/esm/generated/frequentQuestions.entry.mjs +20 -0
- package/dist/esm/generated/frequentQuestions.entry.mjs.map +1 -1
- package/dist/esm/generated/legal.entry.mjs.map +1 -1
- package/dist/types/generated/blog.entry.d.ts +1 -0
- package/dist/types/generated/blog.entry.d.ts.map +1 -1
- package/dist/types/generated/frequentQuestions.entry.d.ts +1 -0
- package/dist/types/generated/frequentQuestions.entry.d.ts.map +1 -1
- package/docs/ar/configuration.md +6 -1
- package/docs/ar/dictionary/content_file.md +6 -1
- package/docs/de/configuration.md +6 -1
- package/docs/de/dictionary/content_file.md +6 -1
- package/docs/en/configuration.md +6 -1
- package/docs/en/dictionary/content_file.md +6 -1
- package/docs/en-GB/configuration.md +6 -1
- package/docs/en-GB/dictionary/content_file.md +3 -1
- package/docs/es/configuration.md +6 -1
- package/docs/es/dictionary/content_file.md +6 -1
- package/docs/fr/configuration.md +6 -1
- package/docs/fr/dictionary/content_file.md +3 -1
- package/docs/hi/configuration.md +6 -1
- package/docs/hi/dictionary/content_file.md +3 -1
- package/docs/id/configuration.md +6 -1
- package/docs/id/dictionary/content_file.md +3 -1
- package/docs/it/configuration.md +6 -1
- package/docs/it/dictionary/content_file.md +3 -1
- package/docs/ja/configuration.md +6 -1
- package/docs/ja/dictionary/content_file.md +3 -1
- package/docs/ko/configuration.md +6 -1
- package/docs/ko/dictionary/content_file.md +3 -1
- package/docs/pl/configuration.md +3 -1
- package/docs/pl/dictionary/content_file.md +3 -1
- package/docs/pt/configuration.md +6 -1
- package/docs/pt/dictionary/content_file.md +3 -1
- package/docs/ru/configuration.md +6 -1
- package/docs/ru/dictionary/content_file.md +6 -1
- package/docs/tr/configuration.md +6 -1
- package/docs/tr/dictionary/content_file.md +3 -1
- package/docs/uk/CI_CD.md +198 -0
- package/docs/uk/autoFill.md +307 -0
- package/docs/uk/bundle_optimization.md +185 -0
- package/docs/uk/cli/build.md +64 -0
- package/docs/uk/cli/ci.md +137 -0
- package/docs/uk/cli/configuration.md +63 -0
- package/docs/uk/cli/debug.md +46 -0
- package/docs/uk/cli/doc-review.md +43 -0
- package/docs/uk/cli/doc-translate.md +132 -0
- package/docs/uk/cli/editor.md +28 -0
- package/docs/uk/cli/fill.md +130 -0
- package/docs/uk/cli/index.md +190 -0
- package/docs/uk/cli/init.md +84 -0
- package/docs/uk/cli/list.md +90 -0
- package/docs/uk/cli/list_projects.md +128 -0
- package/docs/uk/cli/live.md +41 -0
- package/docs/uk/cli/login.md +157 -0
- package/docs/uk/cli/pull.md +78 -0
- package/docs/uk/cli/push.md +98 -0
- package/docs/uk/cli/sdk.md +71 -0
- package/docs/uk/cli/test.md +76 -0
- package/docs/uk/cli/transform.md +65 -0
- package/docs/uk/cli/version.md +24 -0
- package/docs/uk/cli/watch.md +37 -0
- package/docs/uk/configuration.md +742 -0
- package/docs/uk/dictionary/condition.md +237 -0
- package/docs/uk/dictionary/content_file.md +1134 -0
- package/docs/uk/dictionary/enumeration.md +245 -0
- package/docs/uk/dictionary/file.md +232 -0
- package/docs/uk/dictionary/function_fetching.md +212 -0
- package/docs/uk/dictionary/gender.md +273 -0
- package/docs/uk/dictionary/insertion.md +187 -0
- package/docs/uk/dictionary/markdown.md +383 -0
- package/docs/uk/dictionary/nesting.md +273 -0
- package/docs/uk/dictionary/translation.md +332 -0
- package/docs/uk/formatters.md +595 -0
- package/docs/uk/how_works_intlayer.md +256 -0
- package/docs/uk/index.md +175 -0
- package/docs/uk/interest_of_intlayer.md +297 -0
- package/docs/uk/intlayer_CMS.md +569 -0
- package/docs/uk/intlayer_visual_editor.md +292 -0
- package/docs/uk/intlayer_with_angular.md +710 -0
- package/docs/uk/intlayer_with_astro.md +256 -0
- package/docs/uk/intlayer_with_create_react_app.md +1258 -0
- package/docs/uk/intlayer_with_express.md +429 -0
- package/docs/uk/intlayer_with_fastify.md +446 -0
- package/docs/uk/intlayer_with_lynx+react.md +548 -0
- package/docs/uk/intlayer_with_nestjs.md +283 -0
- package/docs/uk/intlayer_with_next-i18next.md +640 -0
- package/docs/uk/intlayer_with_next-intl.md +456 -0
- package/docs/uk/intlayer_with_nextjs_page_router.md +1541 -0
- package/docs/uk/intlayer_with_nuxt.md +711 -0
- package/docs/uk/intlayer_with_react_router_v7.md +600 -0
- package/docs/uk/intlayer_with_react_router_v7_fs_routes.md +669 -0
- package/docs/uk/intlayer_with_svelte_kit.md +579 -0
- package/docs/uk/intlayer_with_tanstack.md +818 -0
- package/docs/uk/intlayer_with_vite+preact.md +1748 -0
- package/docs/uk/intlayer_with_vite+react.md +1449 -0
- package/docs/uk/intlayer_with_vite+solid.md +302 -0
- package/docs/uk/intlayer_with_vite+svelte.md +520 -0
- package/docs/uk/intlayer_with_vite+vue.md +1113 -0
- package/docs/uk/introduction.md +222 -0
- package/docs/uk/locale_mapper.md +242 -0
- package/docs/uk/mcp_server.md +211 -0
- package/docs/uk/packages/express-intlayer/t.md +465 -0
- package/docs/uk/packages/intlayer/getEnumeration.md +159 -0
- package/docs/uk/packages/intlayer/getHTMLTextDir.md +121 -0
- package/docs/uk/packages/intlayer/getLocaleLang.md +81 -0
- package/docs/uk/packages/intlayer/getLocaleName.md +135 -0
- package/docs/uk/packages/intlayer/getLocalizedUrl.md +338 -0
- package/docs/uk/packages/intlayer/getMultilingualUrls.md +359 -0
- package/docs/uk/packages/intlayer/getPathWithoutLocale.md +75 -0
- package/docs/uk/packages/intlayer/getPrefix.md +213 -0
- package/docs/uk/packages/intlayer/getTranslation.md +190 -0
- package/docs/uk/packages/intlayer/getTranslationContent.md +189 -0
- package/docs/uk/packages/next-intlayer/t.md +365 -0
- package/docs/uk/packages/next-intlayer/useDictionary.md +276 -0
- package/docs/uk/packages/next-intlayer/useIntlayer.md +263 -0
- package/docs/uk/packages/next-intlayer/useLocale.md +166 -0
- package/docs/uk/packages/react-intlayer/t.md +311 -0
- package/docs/uk/packages/react-intlayer/useDictionary.md +295 -0
- package/docs/uk/packages/react-intlayer/useI18n.md +250 -0
- package/docs/uk/packages/react-intlayer/useIntlayer.md +251 -0
- package/docs/uk/packages/react-intlayer/useLocale.md +210 -0
- package/docs/uk/per_locale_file.md +345 -0
- package/docs/uk/plugins/sync-json.md +398 -0
- package/docs/uk/readme.md +265 -0
- package/docs/uk/releases/v6.md +305 -0
- package/docs/uk/releases/v7.md +624 -0
- package/docs/uk/roadmap.md +346 -0
- package/docs/uk/testing.md +204 -0
- package/docs/vi/configuration.md +6 -1
- package/docs/vi/dictionary/content_file.md +6 -1
- package/docs/zh/configuration.md +6 -1
- package/docs/zh/dictionary/content_file.md +6 -1
- package/frequent_questions/ar/error-vite-env-only.md +77 -0
- package/frequent_questions/de/error-vite-env-only.md +77 -0
- package/frequent_questions/en/error-vite-env-only.md +77 -0
- package/frequent_questions/en-GB/error-vite-env-only.md +77 -0
- package/frequent_questions/es/error-vite-env-only.md +76 -0
- package/frequent_questions/fr/error-vite-env-only.md +77 -0
- package/frequent_questions/hi/error-vite-env-only.md +77 -0
- package/frequent_questions/id/error-vite-env-only.md +77 -0
- package/frequent_questions/it/error-vite-env-only.md +77 -0
- package/frequent_questions/ja/error-vite-env-only.md +77 -0
- package/frequent_questions/ko/error-vite-env-only.md +77 -0
- package/frequent_questions/pl/error-vite-env-only.md +77 -0
- package/frequent_questions/pt/error-vite-env-only.md +77 -0
- package/frequent_questions/ru/error-vite-env-only.md +77 -0
- package/frequent_questions/tr/error-vite-env-only.md +77 -0
- package/frequent_questions/uk/SSR_Next_no_[locale].md +104 -0
- package/frequent_questions/uk/array_as_content_declaration.md +72 -0
- package/frequent_questions/uk/build_dictionaries.md +58 -0
- package/frequent_questions/uk/build_error_CI_CD.md +74 -0
- package/frequent_questions/uk/bun_set_up.md +53 -0
- package/frequent_questions/uk/customized_locale_list.md +64 -0
- package/frequent_questions/uk/domain_routing.md +113 -0
- package/frequent_questions/uk/error-vite-env-only.md +77 -0
- package/frequent_questions/uk/esbuild_error.md +29 -0
- package/frequent_questions/uk/get_locale_cookie.md +142 -0
- package/frequent_questions/uk/intlayer_command_undefined.md +155 -0
- package/frequent_questions/uk/locale_incorect_in_url.md +73 -0
- package/frequent_questions/uk/package_version_error.md +181 -0
- package/frequent_questions/uk/static_rendering.md +44 -0
- package/frequent_questions/uk/translated_path_url.md +55 -0
- package/frequent_questions/uk/unknown_command.md +97 -0
- package/frequent_questions/vi/error-vite-env-only.md +77 -0
- package/frequent_questions/zh/error-vite-env-only.md +77 -0
- package/legal/uk/privacy_notice.md +83 -0
- package/legal/uk/terms_of_service.md +55 -0
- package/package.json +9 -9
- package/src/generated/blog.entry.ts +20 -0
- package/src/generated/frequentQuestions.entry.ts +20 -0
|
@@ -0,0 +1,760 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2025-11-01
|
|
3
|
+
updatedAt: 2025-11-01
|
|
4
|
+
title: Як інтернаціоналізувати ваш додаток Next.js за допомогою next-intl у 2025 році
|
|
5
|
+
description: Налаштування i18n за допомогою next-intl — найкращі практики та поради з SEO для багатомовних додатків Next.js, що охоплюють інтернаціоналізацію, організацію контенту та технічну конфігурацію.
|
|
6
|
+
keywords:
|
|
7
|
+
- next-intl
|
|
8
|
+
- Інтернаціоналізація
|
|
9
|
+
- Блог
|
|
10
|
+
- Next.js
|
|
11
|
+
- JavaScript
|
|
12
|
+
- React
|
|
13
|
+
slugs:
|
|
14
|
+
- blog
|
|
15
|
+
- nextjs-internationalization-using-next-intl
|
|
16
|
+
applicationTemplate: https://github.com/aymericzip/next-intl-template
|
|
17
|
+
history:
|
|
18
|
+
- version: 7.0.0
|
|
19
|
+
date: 2025-11-01
|
|
20
|
+
changes: Початкова версія
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# Як інтернаціоналізувати ваш додаток Next.js за допомогою next-intl у 2025 році
|
|
24
|
+
|
|
25
|
+
## Зміст
|
|
26
|
+
|
|
27
|
+
<TOC/>
|
|
28
|
+
|
|
29
|
+
## Що таке next-intl?
|
|
30
|
+
|
|
31
|
+
**next-intl** — популярна бібліотека для інтернаціоналізації (i18n), спеціально створена для Next.js App Router. Вона забезпечує зручний спосіб створення багатомовних додатків на Next.js з відмінною підтримкою TypeScript і вбудованими оптимізаціями.
|
|
32
|
+
|
|
33
|
+
> Якщо бажаєте, також можете звернутися до [посібника next-i18next](https://github.com/aymericzip/intlayer/blob/main/docs/blog/uk/i18n_using_next-i18next.md), або безпосередньо використати [Intlayer](https://github.com/aymericzip/intlayer/blob/main/docs/docs/uk/intlayer_with_next-intl.md).
|
|
34
|
+
|
|
35
|
+
> Див. порівняння у [next-i18next vs next-intl vs Intlayer](https://github.com/aymericzip/intlayer/blob/main/docs/blog/uk/next-i18next_vs_next-intl_vs_intlayer.md).
|
|
36
|
+
|
|
37
|
+
## Практики, яких слід дотримуватися
|
|
38
|
+
|
|
39
|
+
Перш ніж приступити до реалізації, ось кілька практик, яких варто дотримуватись:
|
|
40
|
+
|
|
41
|
+
- **Set HTML `lang` and `dir` attributes**
|
|
42
|
+
У вашому layout обчислюйте `dir` за допомогою `getLocaleDirection(locale)` і встановлюйте `<html lang={locale} dir={dir}>` для коректної доступності та SEO.
|
|
43
|
+
- **Split messages by namespace**
|
|
44
|
+
Організовуйте файли JSON за локаллю та namespace (наприклад, `common.json`, `about.json`), щоб завантажувати лише те, що потрібно.
|
|
45
|
+
- **Minimize client payload**
|
|
46
|
+
На сторінках надсилайте до `NextIntlClientProvider` лише потрібні namespace (наприклад, `pick(messages, ['common', 'about'])`).
|
|
47
|
+
- **Prefer static pages**
|
|
48
|
+
Якнайчастіше використовуйте статичні сторінки для кращої продуктивності та SEO.
|
|
49
|
+
- **I18n in server components**
|
|
50
|
+
I18n у серверних компонентах
|
|
51
|
+
Серверні компоненти, наприклад сторінки або всі компоненти, які не позначені як `client`, є статичними і можуть бути попередньо відрендерені під час збірки. Тому нам доведеться передавати функції перекладу їм як props.
|
|
52
|
+
- **Set up TypeScript types**
|
|
53
|
+
Налаштуйте типи TypeScript для ваших локалей, щоб забезпечити типобезпеку в усьому додатку.
|
|
54
|
+
- **Proxy for redirection**
|
|
55
|
+
Використовуйте проксі для обробки визначення локалі та маршрутизації і перенаправлення користувача на відповідний URL з префіксом локалі.
|
|
56
|
+
- **Internationalization of your metadata, sitemap, robots.txt**
|
|
57
|
+
Інтернаціоналізуйте ваші метадані, sitemap, robots.txt, використовуючи функцію `generateMetadata`, надану Next.js, щоб забезпечити краще індексування пошуковими системами у всіх локалях.
|
|
58
|
+
- **Localize Links**
|
|
59
|
+
Локалізуйте посилання
|
|
60
|
+
Локалізуйте посилання, використовуючи компонент `Link`, щоб перенаправляти користувача на URL із відповідним префіксом локалі. Це важливо для забезпечення індексації ваших сторінок у всіх локалях.
|
|
61
|
+
- **Автоматизуйте тести та переклади**
|
|
62
|
+
Автоматизація тестів і перекладів допомагає економити час на підтримці вашого багатомовного додатка.
|
|
63
|
+
|
|
64
|
+
> Дивіться нашу документацію з переліком усього, що потрібно знати про інтернаціоналізацію та SEO: [Інтернаціоналізація (i18n) з next-intl](https://github.com/aymericzip/intlayer/blob/main/docs/blog/uk/internationalization_and_SEO.md).
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Покроковий посібник з налаштування next-intl у застосунку Next.js
|
|
69
|
+
|
|
70
|
+
<iframe
|
|
71
|
+
src="https://stackblitz.com/github/aymericzip/next-intl-template?embed=1&ctl=1&file=src/i18n.ts"
|
|
72
|
+
className="m-auto overflow-hidden rounded-lg border-0 max-md:size-full max-md:h-[700px] md:aspect-16/9 md:w-full"
|
|
73
|
+
title="Демо CodeSandbox — Як інтернаціоналізувати ваш застосунок за допомогою Intlayer"
|
|
74
|
+
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
|
75
|
+
loading="lazy"
|
|
76
|
+
/>
|
|
77
|
+
|
|
78
|
+
> Див. [Шаблон застосунку](https://github.com/aymericzip/next-intl-template) на GitHub.
|
|
79
|
+
|
|
80
|
+
Ось структура проєкту, яку ми створимо:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
.
|
|
84
|
+
├── global.ts
|
|
85
|
+
├── locales
|
|
86
|
+
│ ├── en
|
|
87
|
+
│ │ ├── common.json
|
|
88
|
+
│ │ └── about.json
|
|
89
|
+
│ ├── fr
|
|
90
|
+
│ │ ├── common.json
|
|
91
|
+
│ │ └── about.json
|
|
92
|
+
│ └── es
|
|
93
|
+
│ ├── common.json
|
|
94
|
+
│ └── about.json
|
|
95
|
+
└── src # Src необов'язковий
|
|
96
|
+
├── proxy.ts
|
|
97
|
+
├── app
|
|
98
|
+
│ ├── i18n.ts
|
|
99
|
+
│ └── [locale]
|
|
100
|
+
│ ├── layout.tsx
|
|
101
|
+
│ ├── (home) # / (Група маршрутів, щоб не засмічувати всі сторінки ресурсами home)
|
|
102
|
+
│ │ ├── layout.tsx
|
|
103
|
+
│ │ └── page.tsx
|
|
104
|
+
│ └── about # /about
|
|
105
|
+
│ ├── layout.tsx
|
|
106
|
+
│ └── page.tsx
|
|
107
|
+
└── components
|
|
108
|
+
├── ClientComponent.tsx
|
|
109
|
+
└── ServerComponent.tsx
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Крок 1: Встановіть залежності
|
|
113
|
+
|
|
114
|
+
Встановіть необхідні пакети, використовуючи:
|
|
115
|
+
|
|
116
|
+
```bash packageManager="npm"
|
|
117
|
+
npm install next-intl
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```bash packageManager="pnpm"
|
|
121
|
+
pnpm add next-intl
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
```bash packageManager="yarn"
|
|
125
|
+
yarn add next-intl
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
- **next-intl**: Основна бібліотека інтернаціоналізації для Next.js App Router, яка надає хуки, server-функції та клієнтські провайдери для керування перекладами.
|
|
129
|
+
|
|
130
|
+
### Крок 2: Налаштуйте ваш проєкт
|
|
131
|
+
|
|
132
|
+
Створіть файл конфігурації, який визначає підтримувані локалі та налаштовує конфігурацію запитів next-intl. Цей файл слугує єдиним джерелом правди для вашої налаштування i18n і забезпечує безпеку типів у всьому застосунку.
|
|
133
|
+
|
|
134
|
+
Централізація конфігурації локалей запобігає невідповідностям і полегшує додавання або видалення локалей у майбутньому. Функція `getRequestConfig` виконується для кожного запиту й завантажує лише ті переклади, які потрібні для конкретної сторінки, що дозволяє застосувати code-splitting і зменшити розмір бандла.
|
|
135
|
+
|
|
136
|
+
```tsx fileName="src/i18n.ts"
|
|
137
|
+
import { notFound } from "next/navigation";
|
|
138
|
+
import createMiddleware from "next-intl/middleware";
|
|
139
|
+
import { createNavigation } from "next-intl/navigation";
|
|
140
|
+
|
|
141
|
+
// Визначте підтримувані локалі з безпекою типів
|
|
142
|
+
export const locales = ["en", "fr", "es"] as const;
|
|
143
|
+
export type Locale = (typeof locales)[number];
|
|
144
|
+
export const defaultLocale: Locale = "en";
|
|
145
|
+
|
|
146
|
+
export function isRTL(locale: Locale | (string & {})) {
|
|
147
|
+
return /^(ar|fa|he|iw|ur|ps|sd|ug|yi|ckb|ku)(-|$)/i.test(locale);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Динамічно завантажує повідомлення для кожної локалі для підтримки code-splitting
|
|
151
|
+
// Promise.all завантажує простори імен паралельно для кращої продуктивності
|
|
152
|
+
async function loadMessages(locale: Locale) {
|
|
153
|
+
// Завантажуйте лише ті простори імен, які потрібні вашому layout та сторінкам
|
|
154
|
+
const [common, home, about] = await Promise.all([
|
|
155
|
+
import(`../locales/${locale}/common.json`).then((m) => m.default),
|
|
156
|
+
import(`../locales/${locale}/home.json`).then((m) => m.default),
|
|
157
|
+
import(`../locales/${locale}/about.json`).then((m) => m.default),
|
|
158
|
+
// ... Майбутні JSON-файли слід додати тут
|
|
159
|
+
]);
|
|
160
|
+
|
|
161
|
+
return { common, home, about } as const;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Допоміжна функція для генерації локалізованих URL (наприклад, /about проти /fr/about)
|
|
165
|
+
export function localizedPath(locale: string, path: string) {
|
|
166
|
+
return locale === defaultLocale ? path : `/${locale}${path}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// getRequestConfig виконується на кожен запит і надає повідомлення для серверних компонентів
|
|
170
|
+
// Це місце, де next-intl підключається до server-side rendering у Next.js
|
|
171
|
+
export default async function getRequestConfig({
|
|
172
|
+
requestLocale,
|
|
173
|
+
}: {
|
|
174
|
+
requestLocale: Promise<string | undefined>;
|
|
175
|
+
}) {
|
|
176
|
+
const requested: Locale = ((await requestLocale) as Locale) ?? defaultLocale;
|
|
177
|
+
|
|
178
|
+
if (!locales.includes(requested)) notFound();
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
locale: requested,
|
|
182
|
+
messages: await loadMessages(requested),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function getCookie(locale: Locale) {
|
|
187
|
+
return [
|
|
188
|
+
`NEXT_LOCALE=${locale}`,
|
|
189
|
+
"Path=/",
|
|
190
|
+
`Max-Age=${60 * 60 * 24 * 365}`, // 1 рік
|
|
191
|
+
"SameSite=Lax",
|
|
192
|
+
].join("; ");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const routingOptions = {
|
|
196
|
+
locales,
|
|
197
|
+
defaultLocale,
|
|
198
|
+
localePrefix: "as-needed", // Змінити маршрут /en/... на /...
|
|
199
|
+
// Опціонально: локалізовані шляхи
|
|
200
|
+
// pathnames: {
|
|
201
|
+
// '/': '/',
|
|
202
|
+
// '/about': {en: '/about', fr: '/a-propos', es: '/acerca-de'},
|
|
203
|
+
// '/blog/[slug]': '/blog/[slug]'
|
|
204
|
+
// }
|
|
205
|
+
// localeDetection: true, // запобігати редіректу "/" -> "/en" через cookie
|
|
206
|
+
} as const;
|
|
207
|
+
|
|
208
|
+
export const { Link, redirect, usePathname, useRouter, getPathname } =
|
|
209
|
+
createNavigation(routingOptions);
|
|
210
|
+
|
|
211
|
+
export const proxy = createMiddleware(routingOptions);
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Крок 3: Визначте динамічні маршрути локалей
|
|
215
|
+
|
|
216
|
+
Налаштуйте динамічний роутинг для локалей, створивши директорію `[locale]` у вашій папці app. Це дозволяє Next.js обробляти маршрутизацію за локаллю, де кожна локаль стає сегментом URL (наприклад, `/en/about`, `/fr/about`).
|
|
217
|
+
|
|
218
|
+
Використання динамічних маршрутів дозволяє Next.js генерувати статичні сторінки для всіх локалей під час збірки, що покращує продуктивність і SEO. Компонент layout встановлює HTML-атрибути `lang` та `dir` на основі локалі, що є критично важливим для доступності та правильного розуміння сторінки пошуковими системами.
|
|
219
|
+
|
|
220
|
+
```tsx fileName="src/app/[locale]/layout.tsx"
|
|
221
|
+
import type { ReactNode } from "react";
|
|
222
|
+
import { locales } from "@/i18n";
|
|
223
|
+
import { getLocaleDirection, setRequestLocale } from "next-intl/server";
|
|
224
|
+
|
|
225
|
+
// Попередньо згенерувати статичні сторінки для всіх локалей під час збірки (SSG)
|
|
226
|
+
// Це покращує продуктивність і SEO
|
|
227
|
+
export function generateStaticParams() {
|
|
228
|
+
return locales.map((locale) => ({ locale }));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export default function LocaleLayout({
|
|
232
|
+
children,
|
|
233
|
+
params,
|
|
234
|
+
}: {
|
|
235
|
+
children: ReactNode;
|
|
236
|
+
params: Promise<{ locale: string }>;
|
|
237
|
+
}) {
|
|
238
|
+
// У Next.js App Router, params — це Promise (можна використовувати await)
|
|
239
|
+
// Це дозволяє асинхронно вирішувати динамічні сегменти маршруту
|
|
240
|
+
const { locale } = await params;
|
|
241
|
+
|
|
242
|
+
// Критично: setRequestLocale повідомляє next-intl, яку локаль використовувати для цього запиту
|
|
243
|
+
// Без цього getTranslations() не знатиме, яку локаль використовувати в серверних компонентах
|
|
244
|
+
setRequestLocale(locale);
|
|
245
|
+
|
|
246
|
+
// Отримати напрямок тексту (LTR/RTL) для правильного відображення HTML
|
|
247
|
+
const dir = getLocaleDirection(locale);
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<html lang={locale} dir={dir}>
|
|
251
|
+
<body>{children}</body>
|
|
252
|
+
</html>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
```tsx fileName="src/app/[locale]/about/page.tsx"
|
|
258
|
+
import { getTranslations, getMessages, getFormatter } from "next-intl/server";
|
|
259
|
+
import { NextIntlClientProvider } from "next-intl";
|
|
260
|
+
import pick from "lodash/pick";
|
|
261
|
+
import ServerComponent from "@/components/ServerComponent";
|
|
262
|
+
import ClientComponent from "@/components/ClientComponent";
|
|
263
|
+
|
|
264
|
+
export default async function AboutPage({
|
|
265
|
+
params,
|
|
266
|
+
}: {
|
|
267
|
+
params: Promise<{ locale: string }>;
|
|
268
|
+
}) {
|
|
269
|
+
const { locale } = await params;
|
|
270
|
+
|
|
271
|
+
// Повідомлення завантажуються на сервері. Передавайте на клієнт лише те, що потрібно
|
|
272
|
+
// Це мінімізує розмір JavaScript-пакета, відправленого в браузер
|
|
273
|
+
const messages = await getMessages();
|
|
274
|
+
const clientMessages = pick(messages, ["common", "about"]);
|
|
275
|
+
|
|
276
|
+
// Переклади/форматування, що виконуються виключно на сервері
|
|
277
|
+
// Ці виклики виконуються на сервері і можуть передаватися як пропси компонентам
|
|
278
|
+
const tAbout = await getTranslations("about");
|
|
279
|
+
const tCounter = await getTranslations("about.counter");
|
|
280
|
+
const format = await getFormatter();
|
|
281
|
+
|
|
282
|
+
const initialFormattedCount = format.number(0);
|
|
283
|
+
|
|
284
|
+
return (
|
|
285
|
+
// NextIntlClientProvider робить переклади доступними для клієнтських компонентів
|
|
286
|
+
// Передавайте лише ті неймспейси, які реально використовують ваші клієнтські компоненти
|
|
287
|
+
<NextIntlClientProvider locale={locale} messages={clientMessages}>
|
|
288
|
+
<main>
|
|
289
|
+
<h1>{tAbout("title")}</h1>
|
|
290
|
+
<ClientComponent />
|
|
291
|
+
<ServerComponent
|
|
292
|
+
formattedCount={initialFormattedCount}
|
|
293
|
+
label={tCounter("label")}
|
|
294
|
+
increment={tCounter("increment")}
|
|
295
|
+
/>
|
|
296
|
+
</main>
|
|
297
|
+
</NextIntlClientProvider>
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Крок 4: Створіть файли перекладів
|
|
303
|
+
|
|
304
|
+
Створіть JSON-файли для кожної локалі та простору імен. Така структура дозволяє логічно організувати переклади та завантажувати лише те, що потрібно для кожної сторінки.
|
|
305
|
+
|
|
306
|
+
Організація перекладів за просторами імен (наприклад, `common.json`, `about.json`) дозволяє робити code splitting і зменшувати розмір бандла. Ви завантажуєте лише переклади, потрібні для конкретної сторінки, що покращує продуктивність.
|
|
307
|
+
|
|
308
|
+
```json fileName="locales/en/common.json"
|
|
309
|
+
{
|
|
310
|
+
"welcome": "Ласкаво просимо",
|
|
311
|
+
"greeting": "Привіт, світ!"
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
```json fileName="locales/fr/common.json"
|
|
316
|
+
{
|
|
317
|
+
"welcome": "Ласкаво просимо",
|
|
318
|
+
"greeting": "Привіт, світ!"
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
```json fileName="locales/en/about.json"
|
|
323
|
+
{
|
|
324
|
+
"title": "Про",
|
|
325
|
+
"description": "Опис сторінки «Про»",
|
|
326
|
+
"counter": {
|
|
327
|
+
"label": "Лічильник",
|
|
328
|
+
"increment": "Збільшити"
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
```json fileName="locales/fr/about.json"
|
|
334
|
+
{
|
|
335
|
+
"title": "Про",
|
|
336
|
+
"description": "Опис сторінки «Про»",
|
|
337
|
+
"counter": {
|
|
338
|
+
"label": "Лічильник",
|
|
339
|
+
"increment": "Збільшити"
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Крок 5: Використання перекладів на ваших сторінках
|
|
345
|
+
|
|
346
|
+
Створіть компонент сторінки, який завантажує переклади на сервері та передає їх як серверним, так і клієнтським компонентам. Це гарантує, що переклади завантажені перед рендерингом і запобігає блиманню (content flashing).
|
|
347
|
+
|
|
348
|
+
Завантаження перекладів на сервері покращує SEO та запобігає FOUC (Flash of Untranslated Content). Використовуючи `pick` для відправки тільки необхідних просторів імен до клієнтського провайдера, ми мінімізуємо JavaScript-бандл, що відправляється в браузер.
|
|
349
|
+
|
|
350
|
+
```tsx fileName="src/app/[locale]/about/page.tsx"
|
|
351
|
+
import { getTranslations, getMessages, getFormatter } from "next-intl/server";
|
|
352
|
+
import { NextIntlClientProvider } from "next-intl";
|
|
353
|
+
import pick from "lodash/pick";
|
|
354
|
+
import ServerComponent from "@/components/ServerComponent";
|
|
355
|
+
import ClientComponent from "@/components/ClientComponent";
|
|
356
|
+
|
|
357
|
+
export default async function AboutPage({
|
|
358
|
+
params,
|
|
359
|
+
}: {
|
|
360
|
+
params: Promise<{ locale: string }>;
|
|
361
|
+
}) {
|
|
362
|
+
const { locale } = await params;
|
|
363
|
+
|
|
364
|
+
// Повідомлення завантажуються на сервері. Надсилайте в клієнт лише те, що потрібно.
|
|
365
|
+
// Це мінімізує розмір JavaScript-бандла, який надсилається до браузера
|
|
366
|
+
const messages = await getMessages();
|
|
367
|
+
const clientMessages = pick(messages, ["common", "about"]);
|
|
368
|
+
|
|
369
|
+
// Суто серверні переклади/форматування
|
|
370
|
+
// Ці функції виконуються на сервері і можуть передаватися як props до компонентів
|
|
371
|
+
const tAbout = await getTranslations("about");
|
|
372
|
+
const tCounter = await getTranslations("about.counter");
|
|
373
|
+
const format = await getFormatter();
|
|
374
|
+
|
|
375
|
+
const initialFormattedCount = format.number(0);
|
|
376
|
+
|
|
377
|
+
return (
|
|
378
|
+
// NextIntlClientProvider робить переклади доступними для клієнтських компонентів
|
|
379
|
+
// Передавайте лише ті простори імен (namespaces), які дійсно використовують ваші клієнтські компоненти
|
|
380
|
+
<NextIntlClientProvider locale={locale} messages={clientMessages}>
|
|
381
|
+
<main>
|
|
382
|
+
<h1>{tAbout("title")}</h1>
|
|
383
|
+
<ClientComponent />
|
|
384
|
+
<ServerComponent
|
|
385
|
+
formattedCount={initialFormattedCount}
|
|
386
|
+
label={tCounter("label")}
|
|
387
|
+
increment={tCounter("increment")}
|
|
388
|
+
/>
|
|
389
|
+
</main>
|
|
390
|
+
</NextIntlClientProvider>
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Крок 6: Використання перекладів у клієнтських компонентах
|
|
396
|
+
|
|
397
|
+
Клієнтські компоненти можуть використовувати хуки `useTranslations` та `useFormatter` для доступу до перекладів і функцій форматування. Ці хуки читають дані з контексту `NextIntlClientProvider`.
|
|
398
|
+
|
|
399
|
+
Клієнтські компоненти потребують React-хуків для доступу до перекладів. Хуки `useTranslations` та `useFormatter` безшовно інтегруються з next-intl і забезпечують реактивні оновлення при зміні локалі.
|
|
400
|
+
|
|
401
|
+
> Не забудьте додати необхідні namespaces до client messages сторінки (включайте лише ті namespaces, які дійсно потрібні вашим клієнтським компонентам).
|
|
402
|
+
|
|
403
|
+
```tsx fileName="src/components/ClientComponent.tsx"
|
|
404
|
+
"use client";
|
|
405
|
+
|
|
406
|
+
import React, { useState } from "react";
|
|
407
|
+
import { useTranslations, useFormatter } from "next-intl";
|
|
408
|
+
|
|
409
|
+
const ClientComponent = () => {
|
|
410
|
+
// Застосувати область безпосередньо до вкладеного об'єкта
|
|
411
|
+
// useTranslations/useFormatter — це хуки, які читають контекст NextIntlClientProvider
|
|
412
|
+
// Вони працюють лише якщо компонент обгорнутий у NextIntlClientProvider
|
|
413
|
+
const t = useTranslations("about.counter");
|
|
414
|
+
const format = useFormatter();
|
|
415
|
+
const [count, setCount] = useState(0);
|
|
416
|
+
|
|
417
|
+
return (
|
|
418
|
+
<div>
|
|
419
|
+
<p>{format.number(count)}</p>
|
|
420
|
+
<button
|
|
421
|
+
aria-label={t("label")}
|
|
422
|
+
onClick={() => setCount((count) => count + 1)}
|
|
423
|
+
>
|
|
424
|
+
{t("increment")}
|
|
425
|
+
</button>
|
|
426
|
+
</div>
|
|
427
|
+
);
|
|
428
|
+
};
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Крок 7: Використання перекладів у серверних компонентах
|
|
432
|
+
|
|
433
|
+
Серверні компоненти не можуть використовувати React‑хуки, тому вони отримують переклади та функції форматування як props від батьківських компонентів. Такий підхід зберігає серверні компоненти синхронними й дозволяє вкладати їх усередину клієнтських компонентів.
|
|
434
|
+
|
|
435
|
+
Серверні компоненти, які можуть бути вкладені в клієнтські межі, мають бути синхронними. Передаючи перекладені рядки та відформатовані значення як props, ми уникаємо асинхронних операцій і забезпечуємо правильне рендерення. Попередньо обчисліть переклади та форматування в батьківському компоненті сторінки.
|
|
436
|
+
|
|
437
|
+
```tsx fileName="src/components/ServerComponent.tsx"
|
|
438
|
+
// Серверні компоненти, вкладені всередині клієнтських компонентів, мають бути синхронними
|
|
439
|
+
// React не може серіалізувати асинхронні функції через межу сервер/клієнт
|
|
440
|
+
// Рішення: попередньо обчислювати переклади/формати в батьківському компоненті та передавати їх як props
|
|
441
|
+
type ServerComponentProps = {
|
|
442
|
+
formattedCount: string;
|
|
443
|
+
label: string;
|
|
444
|
+
increment: string;
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
const ServerComponent = ({
|
|
448
|
+
formattedCount,
|
|
449
|
+
label,
|
|
450
|
+
increment,
|
|
451
|
+
}: ServerComponentProps) => {
|
|
452
|
+
return (
|
|
453
|
+
<div>
|
|
454
|
+
<p>{formattedCount}</p>
|
|
455
|
+
<button aria-label={label}>{increment}</button>
|
|
456
|
+
</div>
|
|
457
|
+
);
|
|
458
|
+
};
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
> У вашому page/layout використовуйте `getTranslations` та `getFormatter` з `next-intl/server`, щоб попередньо обчислити переклади та форматування, а потім передайте їх як props серверним компонентам.
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
### (Необов'язково) Крок 8: Змініть мову вашого контенту
|
|
466
|
+
|
|
467
|
+
Щоб змінити мову контенту за допомогою next-intl, рендерте посилання з урахуванням локалі (locale-aware links), які вказують на той самий pathname при перемиканні локалі. Провайдер автоматично переписує URL-адреси, тож вам потрібно лише націлитися на поточний маршрут.
|
|
468
|
+
|
|
469
|
+
```tsx fileName="src/components/LocaleSwitcher.tsx"
|
|
470
|
+
"use client";
|
|
471
|
+
|
|
472
|
+
import Link from "next/link";
|
|
473
|
+
import { usePathname } from "next/navigation";
|
|
474
|
+
import { useLocale } from "next-intl";
|
|
475
|
+
import { defaultLocale, getCookie, type Locale, locales } from "@/i18n";
|
|
476
|
+
|
|
477
|
+
const getLocaleLabel = (locale: Locale): string => {
|
|
478
|
+
try {
|
|
479
|
+
const displayNames = new Intl.DisplayNames([locale], { type: "language" });
|
|
480
|
+
return displayNames.of(locale) ?? locale.toUpperCase();
|
|
481
|
+
} catch {
|
|
482
|
+
return locale.toUpperCase();
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
const localeFlags: Record<Locale, string> = {
|
|
487
|
+
en: "🇬🇧",
|
|
488
|
+
fr: "🇫🇷",
|
|
489
|
+
es: "🇪🇸",
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
export default function LocaleSwitcher() {
|
|
493
|
+
const activeLocale = useLocale();
|
|
494
|
+
const pathname = usePathname();
|
|
495
|
+
|
|
496
|
+
// Видаляє префікс локалі з pathname, щоб отримати базовий шлях
|
|
497
|
+
const getBasePath = (path: string) => {
|
|
498
|
+
for (const locale of locales) {
|
|
499
|
+
if (path.startsWith(`/${locale}`)) {
|
|
500
|
+
return path.slice(locale.length + 1) || "/";
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return path;
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
const basePath = getBasePath(pathname);
|
|
507
|
+
|
|
508
|
+
return (
|
|
509
|
+
<nav aria-label="Вибір мови">
|
|
510
|
+
<div>
|
|
511
|
+
{(locales as readonly Locale[]).map((locale) => {
|
|
512
|
+
const isActive = locale === activeLocale;
|
|
513
|
+
// Побудова href залежно від того, чи це локаль за замовчуванням
|
|
514
|
+
const href =
|
|
515
|
+
locale === defaultLocale ? basePath : `/${locale}${basePath}`;
|
|
516
|
+
return (
|
|
517
|
+
<Link
|
|
518
|
+
key={locale}
|
|
519
|
+
href={href}
|
|
520
|
+
aria-current={isActive ? "page" : undefined}
|
|
521
|
+
onClick={() => {
|
|
522
|
+
document.cookie = getCookie(locale);
|
|
523
|
+
}}
|
|
524
|
+
>
|
|
525
|
+
<span>{localeFlags[locale]}</span>
|
|
526
|
+
<span>{getLocaleLabel(locale)}</span>
|
|
527
|
+
<span>{locale.toUpperCase()}</span>
|
|
528
|
+
</Link>
|
|
529
|
+
);
|
|
530
|
+
})}
|
|
531
|
+
</div>
|
|
532
|
+
</nav>
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### (Необов'язковий) Крок 9: Використовуйте локалізований компонент Link
|
|
538
|
+
|
|
539
|
+
`next-intl` надає сабпакет `next-intl/navigation`, який містить локалізований компонент Link, що автоматично застосовує активну локаль. Ми вже експортували його для вас у файлі `@/i18n`, тож ви можете використовувати його так:
|
|
540
|
+
|
|
541
|
+
```tsx fileName="src/components/MyComponent.tsx"
|
|
542
|
+
import { Link } from "@/i18n";
|
|
543
|
+
|
|
544
|
+
return <Link href="/about">t("about.title")</Link>;
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### (Необов'язковий) Крок 10: Отримання активної локалі всередині Server Actions
|
|
548
|
+
|
|
549
|
+
Server Actions можуть зчитувати поточну локаль за допомогою `next-intl/server`. Це корисно для надсилання локалізованих електронних листів або збереження мовних налаштувань разом із надісланими даними.
|
|
550
|
+
|
|
551
|
+
```ts fileName="src/app/actions/get-current-locale.ts"
|
|
552
|
+
"use server";
|
|
553
|
+
|
|
554
|
+
import { getLocale } from "next-intl/server";
|
|
555
|
+
|
|
556
|
+
export async function getCurrentLocale() {
|
|
557
|
+
return getLocale();
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
export async function handleContactForm(formData: FormData) {
|
|
561
|
+
const locale = await getCurrentLocale();
|
|
562
|
+
|
|
563
|
+
// Використовуйте локаль для вибору шаблонів, міток аналітики тощо.
|
|
564
|
+
console.log(`Received contact form from locale ${locale}`);
|
|
565
|
+
}
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
> `getLocale` читає локаль, встановлену проксі `next-intl`, тож функція працює у будь-якому місці на сервері: Route Handlers, Server Actions та edge functions.
|
|
569
|
+
|
|
570
|
+
### (Необов'язково) Крок 11: Інтернаціоналізуйте свої метадані
|
|
571
|
+
|
|
572
|
+
Переклад контенту важливий, але головна мета інтернаціоналізації — зробити ваш вебсайт більш помітним у світі. I18n — неймовірний важіль для підвищення видимості вашого вебсайту за допомогою правильної SEO.
|
|
573
|
+
|
|
574
|
+
Коректно інтернаціоналізовані метадані допомагають пошуковим системам зрозуміти, якими мовами доступні ваші сторінки. Це включає встановлення meta-тегів hreflang, переклад заголовків і описів, а також забезпечення правильності canonical URL для кожної локалі.
|
|
575
|
+
|
|
576
|
+
```tsx fileName="src/app/[locale]/about/layout.tsx"
|
|
577
|
+
import type { Metadata } from "next";
|
|
578
|
+
import { locales, defaultLocale, localizedPath } from "@/i18n";
|
|
579
|
+
import { getTranslations } from "next-intl/server";
|
|
580
|
+
|
|
581
|
+
// generateMetadata виконується для кожної локалі, генеруючи метадані, оптимізовані для SEO
|
|
582
|
+
// Це допомагає пошуковим системам зрозуміти альтернативні мовні версії
|
|
583
|
+
export async function generateMetadata({
|
|
584
|
+
params,
|
|
585
|
+
}: {
|
|
586
|
+
params: { locale: string };
|
|
587
|
+
}): Promise<Metadata> {
|
|
588
|
+
const { locale } = params;
|
|
589
|
+
const t = await getTranslations({ locale, namespace: "about" });
|
|
590
|
+
|
|
591
|
+
const url = "/about";
|
|
592
|
+
const languages = Object.fromEntries(
|
|
593
|
+
locales.map((locale) => [locale, localizedPath(locale, url)])
|
|
594
|
+
);
|
|
595
|
+
|
|
596
|
+
return {
|
|
597
|
+
title: t("title"),
|
|
598
|
+
description: t("description"),
|
|
599
|
+
alternates: {
|
|
600
|
+
canonical: localizedPath(locale, url),
|
|
601
|
+
languages: { ...languages, "x-default": url },
|
|
602
|
+
},
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// ... Решта коду сторінки
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
### (Необов'язково) Крок 12: Інтернаціоналізуйте свій sitemap
|
|
610
|
+
|
|
611
|
+
Створіть sitemap, який включає всі мовні версії ваших сторінок. Це допомагає пошуковим системам виявляти та індексувати всі мовні версії вашого контенту.
|
|
612
|
+
|
|
613
|
+
Правильно інтернаціоналізований sitemap гарантує, що пошукові системи зможуть знайти та індексувати всі мовні версії ваших сторінок. Це покращує видимість у міжнародних результатах пошуку.
|
|
614
|
+
|
|
615
|
+
```tsx fileName="src/app/sitemap.ts"
|
|
616
|
+
import type { MetadataRoute } from "next";
|
|
617
|
+
import { defaultLocale, locales } from "@/i18n";
|
|
618
|
+
|
|
619
|
+
const origin = "https://example.com";
|
|
620
|
+
|
|
621
|
+
const formatterLocalizedPath = (locale: string, path: string) =>
|
|
622
|
+
locale === defaultLocale ? `${origin}${path}` : `${origin}/${locale}${path}`;
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Отримати карту всіх локалей та їх локалізованих шляхів
|
|
626
|
+
*
|
|
627
|
+
* Приклад результату:
|
|
628
|
+
* {
|
|
629
|
+
* "en": "https://example.com",
|
|
630
|
+
* "fr": "https://example.com/fr",
|
|
631
|
+
* "es": "https://example.com/es",
|
|
632
|
+
* "x-default": "https://example.com"
|
|
633
|
+
* }
|
|
634
|
+
*/
|
|
635
|
+
// Генерує sitemap, який містить усі локалізовані версії сторінок для кращого SEO
|
|
636
|
+
// Поле alternates повідомляє пошуковим системам про мовні версії
|
|
637
|
+
export default function sitemap(): MetadataRoute.Sitemap {
|
|
638
|
+
return [
|
|
639
|
+
{
|
|
640
|
+
url: formatterLocalizedPath(defaultLocale, "/"),
|
|
641
|
+
lastModified: new Date(),
|
|
642
|
+
changeFrequency: "monthly",
|
|
643
|
+
priority: 1.0,
|
|
644
|
+
alternates: { languages: getLocalizedMap("/") },
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
url: formatterLocalizedPath(defaultLocale, "/about"),
|
|
648
|
+
lastModified: new Date(),
|
|
649
|
+
changeFrequency: "monthly",
|
|
650
|
+
priority: 0.7,
|
|
651
|
+
alternates: { languages: getLocalizedMap("/about") },
|
|
652
|
+
},
|
|
653
|
+
];
|
|
654
|
+
}
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
### (Необов'язково) Крок 13: Інтернаціоналізуйте ваш robots.txt
|
|
658
|
+
|
|
659
|
+
Створіть файл robots.txt, який правильно обробляє всі мовні версії ваших захищених маршрутів. Це гарантує, що пошукові системи не індексують сторінки admin або dashboard жодною мовою.
|
|
660
|
+
|
|
661
|
+
Правильна конфігурація robots.txt для всіх локалей запобігає індексації конфіденційних сторінок пошуковими системами, коли ваші маршрути відрізняються для кожної локалі.
|
|
662
|
+
|
|
663
|
+
```tsx fileName="src/app/robots.ts"
|
|
664
|
+
import type { MetadataRoute } from "next";
|
|
665
|
+
import { locales, defaultLocale } from "@/i18n";
|
|
666
|
+
|
|
667
|
+
const origin = "https://example.com";
|
|
668
|
+
// Генерує шляхи для всіх локалей (наприклад, /admin, /fr/admin, /es/admin)
|
|
669
|
+
const withAllLocales = (path: string) => [
|
|
670
|
+
path,
|
|
671
|
+
...locales
|
|
672
|
+
.filter((locale) => locale !== defaultLocale)
|
|
673
|
+
.map((locale) => "/" + locale + path),
|
|
674
|
+
];
|
|
675
|
+
|
|
676
|
+
export default function robots(): MetadataRoute.Robots {
|
|
677
|
+
const disallow = [
|
|
678
|
+
...withAllLocales("/dashboard"),
|
|
679
|
+
...withAllLocales("/admin"),
|
|
680
|
+
];
|
|
681
|
+
|
|
682
|
+
return {
|
|
683
|
+
rules: { userAgent: "*", allow: ["/"], disallow },
|
|
684
|
+
host: origin,
|
|
685
|
+
sitemap: origin + "/sitemap.xml",
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
### (Необов'язково) Крок 14: Налаштування proxy для маршрутизації локалі
|
|
691
|
+
|
|
692
|
+
Створіть proxy, щоб автоматично визначати бажану користувачем локаль та перенаправляти його на відповідний URL з префіксом локалі. next-intl надає зручну функцію proxy, яка обробляє це автоматично.
|
|
693
|
+
|
|
694
|
+
Проксі забезпечує автоматичне перенаправлення користувачів на їхню пріоритетну мову під час відвідування сайту. Воно також зберігає мовні налаштування користувача для майбутніх відвідувань, покращуючи досвід користувача.
|
|
695
|
+
|
|
696
|
+
```ts fileName="src/proxy.ts"
|
|
697
|
+
import { proxy } from "@/i18n";
|
|
698
|
+
|
|
699
|
+
// Middleware виконується перед маршрутами, обробляючи визначення локалі та маршрутизацію
|
|
700
|
+
// localeDetection: true використовує заголовок Accept-Language для автоматичного визначення локалі
|
|
701
|
+
export default proxy;
|
|
702
|
+
|
|
703
|
+
export const config = {
|
|
704
|
+
// Пропустити API, внутрішні маршрути Next та статичні ресурси
|
|
705
|
+
// Regex: відповідає всім маршрутам, крім тих, що починаються з api, _next або містять крапку (файли)
|
|
706
|
+
matcher: ["/((?!api|_next|.*\\..*).*)"],
|
|
707
|
+
};
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
### (Необов'язково) Крок 15: Налаштуйте типи TypeScript для локалі
|
|
711
|
+
|
|
712
|
+
Налаштування TypeScript допоможе отримати автозаповнення та типобезпеку для ваших ключів.
|
|
713
|
+
|
|
714
|
+
Для цього ви можете створити файл global.ts у корені вашого проєкту та додати наступний код:
|
|
715
|
+
|
|
716
|
+
```ts fileName="global.ts"
|
|
717
|
+
import type { locales } from "@/i18n";
|
|
718
|
+
|
|
719
|
+
type Messages = {
|
|
720
|
+
common: typeof import("./locales/en/common.json");
|
|
721
|
+
home: typeof import("./locales/en/home.json");
|
|
722
|
+
about: typeof import("./locales/en/about.json");
|
|
723
|
+
// ... Future JSON files should be added here too
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
declare module "next-intl" {
|
|
727
|
+
interface AppConfig {
|
|
728
|
+
Locale: (typeof locales)[number];
|
|
729
|
+
Messages: Messages;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
Цей код використовує Module Augmentation, щоб додати locales і messages до типу AppConfig з next-intl.
|
|
735
|
+
|
|
736
|
+
### (Необов'язково) Крок 15: Автоматизуйте переклади за допомогою Intlayer
|
|
737
|
+
|
|
738
|
+
Intlayer — це **безкоштовна** та **відкрита** бібліотека, створена для полегшення процесу локалізації у вашому застосунку. У той час як next-intl відповідає за завантаження та управління перекладами, Intlayer допомагає автоматизувати робочий процес перекладів.
|
|
739
|
+
|
|
740
|
+
Ручне керування перекладами може займати багато часу та бути схильним до помилок. Intlayer автоматизує тестування, генерацію та управління перекладами, заощаджуючи ваш час і забезпечуючи узгодженість у всьому застосунку.
|
|
741
|
+
|
|
742
|
+
Intlayer дозволить вам:
|
|
743
|
+
|
|
744
|
+
- **Оголошувати контент там, де вам зручно у вашій codebase**
|
|
745
|
+
Intlayer дозволяє оголошувати контент там, де вам зручно в codebase, використовуючи файли `.content.{ts|js|json}`. Це забезпечує кращу організацію контенту, покращуючи читабельність та підтримуваність вашої codebase.
|
|
746
|
+
|
|
747
|
+
- **Тестувати відсутні переклади**
|
|
748
|
+
Intlayer надає функції тестування, які можна інтегрувати у ваші CI/CD-пайплайни або модульні тести. Дізнайтеся більше про [тестування ваших перекладів](https://github.com/aymericzip/intlayer/blob/main/docs/docs/uk/testing.md).
|
|
749
|
+
|
|
750
|
+
- **Автоматизуйте ваші переклади**,
|
|
751
|
+
Intlayer надає CLI і розширення для VSCode для автоматизації ваших перекладів. Це можна інтегрувати у ваш CI/CD-пайплайн. Дізнайтеся більше про [автоматизацію ваших перекладів](https://github.com/aymericzip/intlayer/blob/main/docs/docs/uk/cli/index.md).
|
|
752
|
+
Ви можете використовувати ваш **власний API-ключ та постачальника AI на ваш вибір**. Intlayer також забезпечує контекстно-залежні переклади, див. [автозаповнення контенту](https://github.com/aymericzip/intlayer/blob/main/docs/docs/uk/autoFill.md).
|
|
753
|
+
|
|
754
|
+
- **Підключайте зовнішній контент**
|
|
755
|
+
Intlayer дозволяє підключати ваш контент до зовнішньої системи управління контентом (CMS), отримувати його оптимізовано та вставляти у ваші JSON-ресурси. Дізнайтеся більше про [отримання зовнішнього контенту](https://github.com/aymericzip/intlayer/blob/main/docs/docs/uk/dictionary/function_fetching.md).
|
|
756
|
+
|
|
757
|
+
- **Візуальний редактор**
|
|
758
|
+
Intlayer пропонує безкоштовний візуальний редактор для редагування вашого контенту. Дізнайтеся більше про [візуальне редагування ваших перекладів](https://github.com/aymericzip/intlayer/blob/main/docs/docs/uk/intlayer_visual_editor.md).
|
|
759
|
+
|
|
760
|
+
І це ще не все. Щоб ознайомитися з усіма можливостями, які надає Intlayer, зверніться до документації про [переваги Intlayer](https://github.com/aymericzip/intlayer/blob/main/docs/docs/uk/interest_of_intlayer.md).
|