@intlayer/docs 7.0.0-canary.2 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/cjs/common.cjs.map +1 -1
  2. package/dist/esm/common.mjs.map +1 -1
  3. package/dist/types/common.d.ts +5 -0
  4. package/dist/types/common.d.ts.map +1 -1
  5. package/docs/ar/configuration.md +0 -24
  6. package/docs/ar/intlayer_with_nextjs_16.md +1652 -0
  7. package/docs/ar/releases/v7.md +485 -0
  8. package/docs/de/configuration.md +0 -24
  9. package/docs/de/intlayer_with_nextjs_16.md +1662 -0
  10. package/docs/de/releases/v7.md +502 -0
  11. package/docs/en/autoFill.md +3 -1
  12. package/docs/en/configuration.md +53 -58
  13. package/docs/en/intlayer_with_nextjs_15.md +5 -2
  14. package/docs/en/intlayer_with_nextjs_16.md +4 -4
  15. package/docs/en/releases/v7.md +142 -2
  16. package/docs/en-GB/configuration.md +9 -30
  17. package/docs/en-GB/intlayer_with_nextjs_16.md +1642 -0
  18. package/docs/en-GB/releases/v7.md +485 -0
  19. package/docs/es/configuration.md +0 -24
  20. package/docs/es/intlayer_with_nextjs_16.md +1670 -0
  21. package/docs/es/releases/v7.md +502 -0
  22. package/docs/fr/configuration.md +0 -24
  23. package/docs/fr/intlayer_with_nextjs_16.md +1692 -0
  24. package/docs/fr/releases/v7.md +503 -0
  25. package/docs/hi/configuration.md +0 -24
  26. package/docs/hi/intlayer_with_nextjs_16.md +1618 -0
  27. package/docs/hi/releases/v7.md +485 -0
  28. package/docs/id/intlayer_with_nextjs_16.md +1604 -0
  29. package/docs/id/releases/v7.md +502 -0
  30. package/docs/it/configuration.md +0 -24
  31. package/docs/it/intlayer_with_nextjs_16.md +1600 -0
  32. package/docs/it/releases/v7.md +504 -0
  33. package/docs/ja/configuration.md +0 -24
  34. package/docs/ja/intlayer_CMS.md +0 -9
  35. package/docs/ja/intlayer_with_nextjs_16.md +1788 -0
  36. package/docs/ja/releases/v7.md +503 -0
  37. package/docs/ko/configuration.md +0 -24
  38. package/docs/ko/intlayer_with_nextjs_16.md +1641 -0
  39. package/docs/ko/releases/v7.md +503 -0
  40. package/docs/pl/intlayer_with_nextjs_16.md +1645 -0
  41. package/docs/pl/releases/v7.md +485 -0
  42. package/docs/pt/configuration.md +0 -24
  43. package/docs/pt/intlayer_with_nextjs_16.md +1646 -0
  44. package/docs/pt/introduction.md +0 -15
  45. package/docs/pt/releases/v7.md +485 -0
  46. package/docs/ru/configuration.md +0 -24
  47. package/docs/ru/intlayer_with_nextjs_16.md +1610 -0
  48. package/docs/ru/releases/v7.md +485 -0
  49. package/docs/tr/configuration.md +0 -24
  50. package/docs/tr/intlayer_with_nextjs_16.md +1599 -0
  51. package/docs/tr/releases/v7.md +485 -0
  52. package/docs/vi/intlayer_with_nextjs_16.md +1597 -0
  53. package/docs/vi/releases/v7.md +485 -0
  54. package/docs/zh/configuration.md +0 -24
  55. package/docs/zh/intlayer_CMS.md +0 -23
  56. package/docs/zh/intlayer_with_nextjs_16.md +1628 -0
  57. package/docs/zh/releases/v7.md +486 -0
  58. package/package.json +14 -14
  59. package/src/common.ts +5 -0
@@ -0,0 +1,1628 @@
1
+ ---
2
+ createdAt: 2025-10-25
3
+ updatedAt: 2025-10-25
4
+ title: 如何翻译您的 Next.js 16 应用 – 2025 年国际化 (i18n) 指南
5
+ description: 了解如何让您的 Next.js 16 网站支持多语言。按照文档进行国际化 (i18n) 和翻译。
6
+ keywords:
7
+ - 国际化
8
+ - 文档
9
+ - Intlayer
10
+ - Next.js 16
11
+ - JavaScript
12
+ - React
13
+ slugs:
14
+ - doc
15
+ - environment
16
+ - nextjs
17
+ applicationTemplate: https://github.com/aymericzip/intlayer-next-16-template
18
+ youtubeVideo: https://www.youtube.com/watch?v=e_PPG7PTqGU
19
+ history:
20
+ - version: 7.0.0
21
+ date: 2025-06-29
22
+ changes: 初始化历史记录
23
+ ---
24
+
25
+ # 使用 Intlayer 翻译您的 Next.js 16 网站 | 国际化 (i18n)
26
+
27
+ <iframe title="Next.js 最佳 i18n 解决方案?探索 Intlayer" class="m-auto aspect-[16/9] w-full overflow-hidden rounded-lg border-0" allow="autoplay; gyroscope;" loading="lazy" width="1080" height="auto" src="https://www.youtube.com/embed/e_PPG7PTqGU?autoplay=0&amp;origin=http://intlayer.org&amp;controls=0&amp;rel=1"/>
28
+
29
+ 请查看 GitHub 上的[应用模板](https://github.com/aymericzip/intlayer-next-16-template)。
30
+
31
+ ## 什么是 Intlayer?
32
+
33
+ **Intlayer** 是一个创新的开源国际化(i18n)库,旨在简化现代 Web 应用中的多语言支持。Intlayer 无缝集成了最新的 **Next.js 16** 框架,包括其强大的 **App Router**。它针对 **Server Components** 进行了优化,以实现高效渲染,并且完全兼容 [**Turbopack**](https://nextjs.org/docs/architecture/turbopack)。
34
+
35
+ 使用 Intlayer,您可以:
36
+
37
+ - **通过组件级声明式字典轻松管理翻译**。
38
+ - **动态本地化元数据、路由和内容**。
39
+ - **在客户端和服务器端组件中访问翻译**。
40
+ - **确保 TypeScript 支持**,通过自动生成类型,提升自动补全和错误检测能力。
41
+ - **享受高级功能**,如动态语言环境检测和切换。
42
+
43
+ > Intlayer 兼容 Next.js 12、13、14 和 16。如果您使用的是 Next.js Page Router,可以参考此[指南](https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/intlayer_with_nextjs_page_router.md)。对于使用 App Router 的 Next.js 12、13、14,请参考此[指南](https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/intlayer_with_nextjs_14.md)。
44
+
45
+ ---
46
+
47
+ ## 在 Next.js 应用中逐步设置 Intlayer 指南
48
+
49
+ ### 第一步:安装依赖
50
+
51
+ 使用 npm 安装必要的包:
52
+
53
+ ```bash packageManager="npm"
54
+ npm install intlayer next-intlayer
55
+ ```
56
+
57
+ ```bash packageManager="pnpm"
58
+ pnpm add intlayer next-intlayer
59
+ ```
60
+
61
+ ```bash packageManager="yarn"
62
+ yarn add intlayer next-intlayer
63
+ ```
64
+
65
+ - **intlayer**
66
+
67
+ 核心包,提供用于配置管理、翻译、[内容声明](https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/dictionary/content_file.md)、转译和[命令行工具](https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/intlayer_cli.md)的国际化工具。
68
+
69
+ - **next-intlayer**
70
+
71
+ 集成 Intlayer 与 Next.js 的包。它为 Next.js 国际化提供上下文提供者和钩子。此外,它还包括用于将 Intlayer 集成到 [Webpack](https://webpack.js.org/) 或 [Turbopack](https://nextjs.org/docs/app/api-reference/turbopack) 的 Next.js 插件,以及用于检测用户首选语言环境、管理 Cookie 和处理 URL 重定向的代理。
72
+
73
+ ### 第 2 步:配置您的项目
74
+
75
+ 创建一个配置文件来配置您的应用程序语言:
76
+
77
+ ```typescript fileName="intlayer.config.ts" codeFormat="typescript"
78
+ import { Locales, type IntlayerConfig } from "intlayer";
79
+
80
+ const config: IntlayerConfig = {
81
+ internationalization: {
82
+ locales: [
83
+ Locales.ENGLISH,
84
+ Locales.FRENCH,
85
+ Locales.SPANISH,
86
+ // 你的其他语言
87
+ ],
88
+ defaultLocale: Locales.ENGLISH,
89
+ },
90
+ };
91
+
92
+ export default config;
93
+ ```
94
+
95
+ ```javascript fileName="intlayer.config.mjs" codeFormat="esm"
96
+ import { Locales } from "intlayer";
97
+
98
+ /** @type {import('intlayer').IntlayerConfig} */
99
+ const config = {
100
+ internationalization: {
101
+ locales: [
102
+ Locales.ENGLISH,
103
+ Locales.FRENCH,
104
+ Locales.SPANISH,
105
+ // 你的其他语言
106
+ ],
107
+ defaultLocale: Locales.ENGLISH,
108
+ },
109
+ };
110
+
111
+ export default config;
112
+ ```
113
+
114
+ ```javascript fileName="intlayer.config.cjs" codeFormat="commonjs"
115
+ const { Locales } = require("intlayer");
116
+
117
+ /** @type {import('intlayer').IntlayerConfig} */
118
+ const config = {
119
+ internationalization: {
120
+ locales: [
121
+ Locales.ENGLISH,
122
+ Locales.FRENCH,
123
+ Locales.SPANISH,
124
+ // 你的其他语言环境
125
+ ],
126
+ defaultLocale: Locales.ENGLISH,
127
+ },
128
+ };
129
+
130
+ module.exports = config;
131
+ ```
132
+
133
+ > 通过此配置文件,您可以设置本地化 URL、代理重定向、cookie 名称、内容声明的位置和扩展名,禁用控制台中的 Intlayer 日志等。有关可用参数的完整列表,请参阅[配置文档](https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/configuration.md)。
134
+
135
+ ### 第三步:在 Next.js 配置中集成 Intlayer
136
+
137
+ 配置您的 Next.js 设置以使用 Intlayer:
138
+
139
+ ```typescript fileName="next.config.ts" codeFormat="typescript"
140
+ import type { NextConfig } from "next";
141
+ import { withIntlayer } from "next-intlayer/server";
142
+
143
+ const nextConfig: NextConfig = {
144
+ /* 这里是配置选项 */
145
+ };
146
+
147
+ export default withIntlayer(nextConfig);
148
+ ```
149
+
150
+ ```typescript fileName="next.config.mjs" codeFormat="esm"
151
+ import { withIntlayer } from "next-intlayer/server";
152
+
153
+ /** @type {import('next').NextConfig} */
154
+ const nextConfig = {
155
+ /* 这里是配置选项 */
156
+ };
157
+
158
+ export default withIntlayer(nextConfig);
159
+ ```
160
+
161
+ ```typescript fileName="next.config.cjs" codeFormat="commonjs"
162
+ const { withIntlayer } = require("next-intlayer/server");
163
+
164
+ /** @type {import('next').NextConfig} */
165
+ const nextConfig = {
166
+ /* 这里是配置选项 */
167
+ };
168
+
169
+ module.exports = withIntlayer(nextConfig);
170
+ ```
171
+
172
+ > `withIntlayer()` 是用于将 Intlayer 集成到 Next.js 的插件。它确保内容声明文件的构建,并在开发模式下进行监控。它在 [Webpack](https://webpack.js.org/) 或 [Turbopack](https://nextjs.org/docs/app/api-reference/turbopack) 环境中定义 Intlayer 环境变量。此外,它还提供别名以优化性能,并确保与服务器组件的兼容性。
173
+
174
+ > `withIntlayer()` 函数是一个 Promise 函数。它允许在构建开始之前准备 Intlayer 字典。如果你想与其他插件一起使用它,可以使用 await。示例:
175
+ >
176
+ > ```tsx
177
+ > const nextConfig = await withIntlayer(nextConfig);
178
+ > const nextConfigWithOtherPlugins = withOtherPlugins(nextConfig);
179
+ >
180
+ > export default nextConfigWithOtherPlugins;
181
+ > ```
182
+ >
183
+ > 如果你想同步使用它,可以使用 `withIntlayerSync()` 函数。示例:
184
+ >
185
+ > ```tsx
186
+ > const nextConfig = withIntlayerSync(nextConfig);
187
+ > const nextConfigWithOtherPlugins = withOtherPlugins(nextConfig);
188
+ >
189
+ > export default nextConfigWithOtherPlugins;
190
+ > ```
191
+
192
+ ### 第4步:定义动态语言环境路由
193
+
194
+ 删除 `RootLayout` 中的所有内容,并替换为以下代码:
195
+
196
+ ```tsx {3} fileName="src/app/layout.tsx" codeFormat="typescript"
197
+ import type { PropsWithChildren, FC } from "react";
198
+ import "./globals.css";
199
+
200
+ const RootLayout: FC<PropsWithChildren> = ({ children }) => (
201
+ // 你仍然可以用其他提供者包裹 children,比如 `next-themes`、`react-query`、`framer-motion` 等。
202
+ <>{children}</>
203
+ );
204
+
205
+ export default RootLayout;
206
+ ```
207
+
208
+ ```jsx {3} fileName="src/app/layout.mjx" codeFormat="esm"
209
+ import "./globals.css";
210
+
211
+ const RootLayout = ({ children }) => (
212
+ // 你仍然可以用其他提供者包裹子组件,比如 `next-themes`、`react-query`、`framer-motion` 等。
213
+ <>{children}</>
214
+ );
215
+
216
+ export default RootLayout;
217
+ ```
218
+
219
+ ```jsx {1,8} fileName="src/app/layout.csx" codeFormat="commonjs"
220
+ require("./globals.css");
221
+
222
+ const RootLayout = ({ children }) => (
223
+ // 你仍然可以用其他提供者包裹子组件,比如 `next-themes`、`react-query`、`framer-motion` 等。
224
+ <>{children}</>
225
+ );
226
+
227
+ module.exports = {
228
+ default: RootLayout,
229
+ generateStaticParams,
230
+ };
231
+ ```
232
+
233
+ > 保持 `RootLayout` 组件为空允许设置 `<html>` 标签的 [`lang`](https://developer.mozilla.org/fr/docs/Web/HTML/Global_attributes/lang) 和 [`dir`](https://developer.mozilla.org/fr/docs/Web/HTML/Global_attributes/dir) 属性。
234
+
235
+ 要实现动态路由,通过在 `[locale]` 目录中添加新的布局来提供语言环境路径:
236
+
237
+ ````tsx fileName="src/app/[locale]/layout.tsx" codeFormat="typescript"
238
+ import type { NextLayoutIntlayer } from "next-intlayer";
239
+ import { Inter } from "next/font/google";
240
+ import { getHTMLTextDir } from "intlayer";
241
+
242
+ const inter = Inter({ subsets: ["latin"] });
243
+
244
+ const LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => {
245
+ const { locale } = await params;
246
+ return (
247
+ > 保持 `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>` 标签上。
248
+
249
+ 要实现动态路由,通过在你的 `[locale]` 目录中添加一个新的布局来提供本地化路径:
250
+
251
+ ```tsx fileName="src/app/[locale]/layout.tsx" codeFormat="typescript"
252
+ import type { NextLayoutIntlayer } from "next-intlayer";
253
+ import { Inter } from "next/font/google";
254
+ import { getHTMLTextDir } from "intlayer";
255
+
256
+ const inter = Inter({ subsets: ["latin"] });
257
+
258
+ const LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => {
259
+ const { locale } = await params;
260
+ return (
261
+ <html lang={locale} dir={getHTMLTextDir(locale)}>
262
+ <body className={inter.className}>{children}</body>
263
+ </html>
264
+ );
265
+ };
266
+
267
+ export default LocaleLayout;
268
+ ````
269
+
270
+ ```jsx fileName="src/app/[locale]/layout.mjx" codeFormat="esm"
271
+ import { getHTMLTextDir } from "intlayer";
272
+
273
+ const inter = Inter({ subsets: ["latin"] });
274
+
275
+ const LocaleLayout = async ({ children, params: { locale } }) => {
276
+ const { locale } = await params;
277
+ return (
278
+ <html lang={locale} dir={getHTMLTextDir(locale)}>
279
+ <body className={inter.className}>{children}</body>
280
+ </html>
281
+ );
282
+ };
283
+
284
+ export default LocaleLayout;
285
+ ```
286
+
287
+ ```jsx fileName="src/app/[locale]/layout.csx" codeFormat="commonjs"
288
+ const { Inter } = require("next/font/google");
289
+ const { getHTMLTextDir } = require("intlayer");
290
+
291
+ const inter = Inter({ subsets: ["latin"] });
292
+
293
+ const LocaleLayout = async ({ children, params: { locale } }) => {
294
+ const { locale } = await params;
295
+ return (
296
+ <html lang={locale} dir={getHTMLTextDir(locale)}>
297
+ <body className={inter.className}>{children}</body>
298
+ </html>
299
+ );
300
+ };
301
+
302
+ module.exports = LocaleLayout;
303
+ ```
304
+
305
+ > `[locale]` 路径段用于定义语言环境。例如:`/en-US/about` 指向 `en-US`,`/fr/about` 指向 `fr`。
306
+
307
+ const inter = Inter({ subsets: ["latin"] });
308
+
309
+ const LocaleLayout = async ({ children, params: { locale } }) => {
310
+ const { locale } = await params;
311
+ return (
312
+
313
+ <html lang={locale} dir={getHTMLTextDir(locale)}>
314
+ <body className={inter.className}>{children}</body>
315
+ </html>
316
+ );
317
+ };
318
+
319
+ module.exports = LocaleLayout;
320
+
321
+ ````
322
+
323
+ > `[locale]` 路径段用于定义语言环境。例如:`/en-US/about` 指向 `en-US`,`/fr/about` 指向 `fr`。
324
+
325
+ > 在此阶段,您将遇到错误:`Error: Missing <html> and <body> tags in the root layout.`。这是预期的,因为 `/app/page.tsx` 文件不再使用,可以删除。取而代之的是,`[locale]` 路径段将激活 `/app/[locale]/page.tsx` 页面。因此,页面将可以通过浏览器中的 `/en`、`/fr`、`/es` 等路径访问。要将默认语言设置为根页面,请参阅第7步中的 `proxy` 配置。
326
+
327
+ 然后,在您的应用布局中实现 `generateStaticParams` 函数。
328
+
329
+ ```tsx {1} fileName="src/app/[locale]/layout.tsx" codeFormat="typescript"
330
+ export { generateStaticParams } from "next-intlayer"; // 插入的代码行
331
+
332
+ const LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => {
333
+ /*... 其余代码 */
334
+ };
335
+
336
+ export default LocaleLayout;
337
+ ````
338
+
339
+ ```jsx {1} fileName="src/app/[locale]/layout.mjx" codeFormat="esm"
340
+ export { generateStaticParams } from "next-intlayer"; // 插入的代码行
341
+
342
+ const LocaleLayout = async ({ children, params: { locale } }) => {
343
+ /*... 其余代码 */
344
+ };
345
+
346
+ // ... 其余代码
347
+ ```
348
+
349
+ ```jsx {1,7} fileName="src/app/[locale]/layout.csx" codeFormat="commonjs"
350
+ const { generateStaticParams } = require("next-intlayer"); // 插入的代码行
351
+
352
+ const LocaleLayout = async ({ children, params: { locale } }) => {
353
+ /*... 其余代码 */
354
+ };
355
+
356
+ module.exports = { default: LocaleLayout, generateStaticParams };
357
+ ```
358
+
359
+ > `generateStaticParams` 确保您的应用程序为所有语言预构建必要的页面,从而减少运行时计算并提升用户体验。更多详情,请参阅 [Next.js 关于 generateStaticParams 的文档](https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic-rendering#generate-static-params)。
360
+
361
+ > Intlayer 配合 `export const dynamic = 'force-static';` 使用,以确保所有语言的页面均被预构建。
362
+
363
+ ### 第5步:声明您的内容
364
+
365
+ 创建并管理您的内容声明以存储翻译:
366
+
367
+ ```tsx fileName="src/app/[locale]/page.content.ts" contentDeclarationFormat="typescript"
368
+ import { t, type Dictionary } from "intlayer";
369
+
370
+ const pageContent = {
371
+ key: "page",
372
+ content: {
373
+ getStarted: {
374
+ main: t({
375
+ en: "Get started by editing",
376
+ fr: "Commencez par éditer",
377
+ es: "Comience por editar",
378
+ }),
379
+ pageLink: "src/app/page.tsx",
380
+ },
381
+ },
382
+ } satisfies Dictionary;
383
+
384
+ export default pageContent;
385
+ ```
386
+
387
+ ```javascript fileName="src/app/[locale]/page.content.mjs" contentDeclarationFormat="esm"
388
+ import { t } from "intlayer";
389
+
390
+ /** @type {import('intlayer').Dictionary} */
391
+ const pageContent = {
392
+ key: "page",
393
+ content: {
394
+ getStarted: {
395
+ main: t({
396
+ en: "Get started by editing",
397
+ fr: "Commencez par éditer",
398
+ es: "Comience por editar",
399
+ }),
400
+ pageLink: "src/app/page.tsx",
401
+ },
402
+ },
403
+ };
404
+
405
+ export default pageContent;
406
+ ```
407
+
408
+ ```javascript fileName="src/app/[locale]/page.content.cjs" contentDeclarationFormat="commonjs"
409
+ const { t } = require("intlayer");
410
+
411
+ /** @type {import('intlayer').Dictionary} */
412
+ // 页面内容字典定义
413
+ const pageContent = {
414
+ key: "page",
415
+ content: {
416
+ getStarted: {
417
+ main: t({
418
+ en: "Get started by editing",
419
+ fr: "Commencez par éditer",
420
+ es: "Comience por editar",
421
+ }),
422
+ pageLink: "src/app/page.tsx",
423
+ },
424
+ },
425
+ };
426
+
427
+ module.exports = pageContent;
428
+ ```
429
+
430
+ ```json fileName="src/app/[locale]/page.content.json" contentDeclarationFormat="json"
431
+ {
432
+ "$schema": "https://intlayer.org/schema.json",
433
+ "key": "page",
434
+ "content": {
435
+ "getStarted": {
436
+ "nodeType": "translation",
437
+ "translation": {
438
+ "en": "Get started by editing",
439
+ "fr": "Commencez par éditer",
440
+ "es": "Comience por editar"
441
+ }
442
+ },
443
+ "pageLink": "src/app/page.tsx"
444
+ }
445
+ }
446
+ ```
447
+
448
+ > 您的内容声明可以定义在应用程序的任何位置,只要它们被包含在 `contentDir` 目录中(默认是 `./src`)。并且文件扩展名需匹配内容声明文件扩展名(默认是 `.content.{json,ts,tsx,js,jsx,mjs,mjx,cjs,cjx}`)。
449
+
450
+ > 更多详情,请参考[内容声明文档](https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/dictionary/content_file.md)。
451
+
452
+ ### 第6步:在代码中使用内容
453
+
454
+ 在整个应用程序中访问您的内容字典:
455
+
456
+ ```tsx fileName="src/app/[locale]/page.tsx" codeFormat="typescript"
457
+ import type { FC } from "react";
458
+ import { ClientComponentExample } from "@components/ClientComponentExample";
459
+ import { ServerComponentExample } from "@components/ServerComponentExample";
460
+ import { type NextPageIntlayer, IntlayerClientProvider } from "next-intlayer";
461
+ import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";
462
+
463
+ const PageContent: FC = () => {
464
+ const content = useIntlayer("page");
465
+
466
+ return (
467
+ <>
468
+ <p>{content.getStarted.main}</p> {/* 使用内容字典中的主要介绍文本 */}
469
+ <code>{content.getStarted.pageLink}</code> {/* 显示页面链接内容 */}
470
+ </>
471
+ );
472
+ };
473
+
474
+ const Page: NextPageIntlayer = async ({ params }) => {
475
+ const { locale } = await params;
476
+
477
+ return (
478
+ <IntlayerServerProvider locale={locale}>
479
+ <PageContent />
480
+ <ServerComponentExample />
481
+
482
+ <IntlayerClientProvider locale={locale}>
483
+ <ClientComponentExample />
484
+ </IntlayerClientProvider>
485
+ </IntlayerServerProvider>
486
+ );
487
+ };
488
+
489
+ export default Page;
490
+ ```
491
+
492
+ ```jsx fileName="src/app/[locale]/page.mjx" codeFormat="esm"
493
+ import { ClientComponentExample } from "@components/ClientComponentExample";
494
+ import { ServerComponentExample } from "@components/ServerComponentExample";
495
+ import { IntlayerClientProvider } from "next-intlayer";
496
+ import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";
497
+
498
+ const PageContent = () => {
499
+ const content = useIntlayer("page");
500
+
501
+ return (
502
+ <>
503
+ <p>{content.getStarted.main}</p> {/* 获取开始的主要内容 */}
504
+ <code>{content.getStarted.pageLink}</code> {/* 获取开始的页面链接 */}
505
+ </>
506
+ );
507
+ };
508
+
509
+ const Page = async ({ params }) => {
510
+ const { locale } = await params;
511
+
512
+ return (
513
+ <IntlayerServerProvider locale={locale}>
514
+ <PageContent />
515
+ <ServerComponentExample />
516
+
517
+ <IntlayerClientProvider locale={locale}>
518
+ <ClientComponentExample />
519
+ </IntlayerClientProvider>
520
+ </IntlayerServerProvider>
521
+ );
522
+ };
523
+
524
+ export default Page;
525
+ ```
526
+
527
+ ```jsx fileName="src/app/[locale]/page.csx" codeFormat="commonjs"
528
+ import { ClientComponentExample } from "@components/ClientComponentExample";
529
+ import { ServerComponentExample } from "@components/ServerComponentExample";
530
+ import { IntlayerClientProvider } from "next-intlayer";
531
+ import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";
532
+
533
+ const PageContent = () => {
534
+ const content = useIntlayer("page");
535
+
536
+ return (
537
+ <>
538
+ <p>{content.getStarted.main}</p>
539
+ <code>{content.getStarted.pageLink}</code>
540
+ </>
541
+ );
542
+ };
543
+
544
+ const Page = async ({ params }) => {
545
+ const { locale } = await params;
546
+
547
+ return (
548
+ <IntlayerServerProvider locale={locale}>
549
+ <PageContent />
550
+ <ServerComponentExample />
551
+
552
+ <IntlayerClientProvider locale={locale}>
553
+ <ClientComponentExample />
554
+ </IntlayerClientProvider>
555
+ </IntlayerServerProvider>
556
+ );
557
+ };
558
+ ```
559
+
560
+ - **`IntlayerClientProvider`** 用于为客户端组件提供语言环境。它可以放置在任何父组件中,包括布局组件中。然而,推荐将其放置在布局中,因为 Next.js 会在页面之间共享布局代码,这样更高效。通过在布局中使用 `IntlayerClientProvider`,可以避免每个页面都重新初始化它,从而提升性能并在整个应用中保持一致的本地化上下文。
561
+ - **`IntlayerServerProvider`** 用于为服务器端子组件提供语言环境。它不能设置在布局中。
562
+
563
+ > 布局和页面不能共享公共的服务器上下文,因为服务器上下文系统基于每次请求的数据存储(通过 [React 的缓存](https://react.dev/reference/react/cache) 机制),这导致每个“上下文”会为应用程序的不同部分重新创建。在共享布局中放置提供者会破坏这种隔离,阻止服务器上下文值正确传播到你的服务器组件。
564
+
565
+ ```tsx {4,7} fileName="src/components/ClientComponentExample.tsx" codeFormat="typescript"
566
+ "use client";
567
+
568
+ import type { FC } from "react";
569
+ import { useIntlayer } from "next-intlayer";
570
+
571
+ export const ClientComponentExample: FC = () => {
572
+ const content = useIntlayer("client-component-example"); // 创建相关内容声明
573
+
574
+ return (
575
+ <div>
576
+ <h2>{content.title}</h2>
577
+ <p>{content.content}</p>
578
+ </div>
579
+ );
580
+ };
581
+ ```
582
+
583
+ ```jsx {3,6} fileName="src/components/ClientComponentExample.mjx" codeFormat="esm"
584
+ "use client";
585
+
586
+ import { useIntlayer } from "next-intlayer";
587
+
588
+ const ClientComponentExample = () => {
589
+ const content = useIntlayer("client-component-example"); // 创建相关内容声明
590
+
591
+ return (
592
+ <div>
593
+ <h2>{content.title}</h2>
594
+ <p>{content.content}</p>
595
+ </div>
596
+ );
597
+ };
598
+ ```
599
+
600
+ ```jsx {3,6} fileName="src/components/ClientComponentExample.csx" codeFormat="commonjs"
601
+ "use client";
602
+
603
+ const { useIntlayer } = require("next-intlayer");
604
+
605
+ const ClientComponentExample = () => {
606
+ const content = useIntlayer("client-component-example"); // 创建相关内容声明
607
+
608
+ return (
609
+ <div>
610
+ <h2>{content.title}</h2>
611
+ <p>{content.content}</p>
612
+ </div>
613
+ );
614
+ };
615
+ ```
616
+
617
+ ```tsx {2} fileName="src/components/ServerComponentExample.tsx" codeFormat="typescript"
618
+ import type { FC } from "react";
619
+ import { useIntlayer } from "next-intlayer/server";
620
+
621
+ export const ServerComponentExample: FC = () => {
622
+ const content = useIntlayer("server-component-example"); // 创建相关内容声明
623
+
624
+ return (
625
+ <div>
626
+ <h2>{content.title}</h2>
627
+ <p>{content.content}</p>
628
+ </div>
629
+ );
630
+ };
631
+ ```
632
+
633
+ ```jsx {1} fileName="src/components/ServerComponentExample.mjx" codeFormat="esm"
634
+ import { useIntlayer } from "next-intlayer/server";
635
+
636
+ const ServerComponentExample = () => {
637
+ const content = useIntlayer("server-component-example"); // 创建相关内容声明
638
+
639
+ return (
640
+ <div>
641
+ <h2>{content.title}</h2>
642
+ <p>{content.content}</p>
643
+ </div>
644
+ );
645
+ };
646
+ ```
647
+
648
+ ```jsx {1} fileName="src/components/ServerComponentExample.csx" codeFormat="commonjs"
649
+ const { useIntlayer } = require("next-intlayer/server");
650
+
651
+ const ServerComponentExample = () => {
652
+ const content = useIntlayer("server-component-example"); // 创建相关内容声明
653
+
654
+ return (
655
+ <div>
656
+ <h2>{content.title}</h2>
657
+ <p>{content.content}</p>
658
+ </div>
659
+ );
660
+ };
661
+ ```
662
+
663
+ > 如果您想在字符串属性中使用内容,例如 `alt`、`title`、`href`、`aria-label` 等,必须调用函数的值,如:
664
+
665
+ > ```jsx
666
+ > <img src={content.image.src.value} alt={content.image.value} />
667
+ > ```
668
+
669
+ > 要了解有关 `useIntlayer` 钩子的更多信息,请参阅[文档](https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/packages/next-intlayer/useIntlayer.md)。
670
+
671
+ ### (可选)步骤 7:配置用于语言环境检测的代理
672
+
673
+ 设置代理以检测用户的首选语言环境:
674
+
675
+ ```typescript fileName="src/proxy.ts" codeFormat="typescript"
676
+ export { intlayerProxy as proxy } from "next-intlayer/proxy";
677
+
678
+ export const config = {
679
+ matcher:
680
+ "/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\..*|_next).*)",
681
+ };
682
+ ```
683
+
684
+ ```javascript fileName="src/proxy.mjs" codeFormat="esm"
685
+ export { intlayerProxy as proxy } from "next-intlayer/proxy";
686
+
687
+ export const config = {
688
+ matcher:
689
+ "/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\..*|_next).*)",
690
+ };
691
+ ```
692
+
693
+ ```javascript fileName="src/proxy.cjs" codeFormat="commonjs"
694
+ const { intlayerProxy } = require("next-intlayer/proxy");
695
+
696
+ const config = {
697
+ matcher:
698
+ "/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\..*|_next).*)",
699
+ };
700
+
701
+ module.exports = { proxy: intlayerProxy, config };
702
+ ```
703
+
704
+ > `intlayerProxy` 用于检测用户的首选语言环境,并根据[配置](https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/configuration.md)将用户重定向到相应的 URL。此外,它还支持将用户的首选语言环境保存在 cookie 中。
705
+
706
+ > 如果您需要将多个代理串联使用(例如,将 `intlayerProxy` 与身份验证或自定义代理一起使用),Intlayer 现在提供了一个名为 `multipleProxies` 的辅助工具。
707
+
708
+ ```ts
709
+ import { multipleProxies, intlayerProxy } from "next-intlayer/proxy";
710
+ import { customProxy } from "@utils/customProxy";
711
+
712
+ export const proxy = multipleProxies([intlayerProxy, customProxy]);
713
+ ```
714
+
715
+ ### (可选)步骤 8:元数据的国际化
716
+
717
+ 如果您想要对元数据进行国际化,比如页面标题,可以使用 Next.js 提供的 `generateMetadata` 函数。在其中,您可以通过 `getIntlayer` 函数获取内容来翻译您的元数据。
718
+
719
+ ```typescript fileName="src/app/[locale]/metadata.content.ts" contentDeclarationFormat="typescript"
720
+ import { type Dictionary, t } from "intlayer";
721
+ import { Metadata } from "next";
722
+
723
+ const metadataContent = {
724
+ key: "page-metadata",
725
+ content: {
726
+ title: t({
727
+ en: "Create Next App",
728
+ fr: "Créer une application Next.js",
729
+ es: "Crear una aplicación Next.js",
730
+ }),
731
+ description: t({
732
+ zh: "由 create next app 生成",
733
+ en: "Generated by create next app",
734
+ fr: "Généré par create next app",
735
+ es: "Generado por create next app",
736
+ }),
737
+ },
738
+ } satisfies Dictionary<Metadata>;
739
+
740
+ export default metadataContent;
741
+ ```
742
+
743
+ ```javascript fileName="src/app/[locale]/metadata.content.mjs" contentDeclarationFormat="esm"
744
+ import { t } from "intlayer";
745
+
746
+ /** @type {import('intlayer').Dictionary<import('next').Metadata>} */
747
+ const metadataContent = {
748
+ key: "page-metadata",
749
+ content: {
750
+ title: t({
751
+ zh: "创建 Next 应用",
752
+ en: "Create Next App",
753
+ fr: "Créer une application Next.js",
754
+ es: "Crear una aplicación Next.js",
755
+ }),
756
+ description: t({
757
+ zh: "由 create next app 生成",
758
+ en: "Generated by create next app",
759
+ fr: "Généré par create next app",
760
+ es: "Generado por create next app",
761
+ }),
762
+ },
763
+ };
764
+
765
+ export default metadataContent;
766
+ ```
767
+
768
+ ```javascript fileName="src/app/[locale]/metadata.content.cjs" contentDeclarationFormat="commonjs"
769
+ const { t } = require("intlayer");
770
+
771
+ /** @type {import('intlayer').Dictionary<import('next').Metadata>} */
772
+ const metadataContent = {
773
+ key: "page-metadata",
774
+ content: {
775
+ title: t({
776
+ en: "Create Next App",
777
+ fr: "Créer une application Next.js",
778
+ es: "Crear una aplicación Next.js",
779
+ }),
780
+ description: t({
781
+ en: "Generated by create next app",
782
+ fr: "Généré par create next app",
783
+ es: "Generado por create next app",
784
+ }),
785
+ },
786
+ };
787
+
788
+ module.exports = metadataContent;
789
+ ```
790
+
791
+ ```json fileName="src/app/[locale]/metadata.content.json" contentDeclarationFormat="json"
792
+ {
793
+ "key": "page-metadata",
794
+ "content": {
795
+ "title": {
796
+ "nodeType": "translation",
797
+ "translation": {
798
+ "en": "Preact logo",
799
+ "fr": "Logo Preact",
800
+ "es": "Logo Preact",
801
+ "zh": "Preact 标志"
802
+ },
803
+ },
804
+ "description": {
805
+ "nodeType": "translation",
806
+ "translation": {
807
+ "en": "Generated by create next app",
808
+ "fr": "Généré par create next app",
809
+ "es": "Generado por create next app",
810
+ "zh": "由 create next app 生成"
811
+ },
812
+ },
813
+ },
814
+ };
815
+ ```
816
+
817
+ ````typescript fileName="src/app/[locale]/layout.tsx or src/app/[locale]/page.tsx" codeFormat="typescript"
818
+ import { getIntlayer, getMultilingualUrls } from "intlayer";
819
+ import type { Metadata } from "next";
820
+ import type { LocalPromiseParams } from "next-intlayer";
821
+
822
+ export const generateMetadata = async ({
823
+ params,
824
+ }: LocalPromiseParams): Promise<Metadata> => {
825
+ const { locale } = await params;
826
+
827
+ const metadata = getIntlayer("page-metadata", locale);
828
+
829
+ /**
830
+ * 生成一个包含每个语言环境所有 URL 的对象。
831
+ *
832
+ * 示例:
833
+ * ```ts
834
+ * getMultilingualUrls('/about');
835
+ *
836
+ * // 返回
837
+ * // {
838
+ * // en: '/about',
839
+ * // fr: '/fr/about',
840
+ * // es: '/es/about',
841
+ * // }
842
+ * ```
843
+ */
844
+ const multilingualUrls = getMultilingualUrls("/");
845
+
846
+ return {
847
+ ...metadata,
848
+ alternates: {
849
+ canonical: multilingualUrls[locale as keyof typeof multilingualUrls],
850
+ languages: { ...multilingualUrls, "x-default": "/" },
851
+ },
852
+ openGraph: {
853
+ url: multilingualUrls[locale],
854
+ },
855
+ };
856
+ };
857
+
858
+ // ... 其余代码
859
+ ````
860
+
861
+ ````javascript fileName="src/app/[locale]/layout.mjs or src/app/[locale]/page.mjs" codeFormat="esm"
862
+ import { getIntlayer, getMultilingualUrls } from "intlayer";
863
+
864
+ export const generateMetadata = async ({ params }) => {
865
+ const { locale } = await params;
866
+
867
+ const metadata = getIntlayer("page-metadata", locale);
868
+
869
+ /**
870
+ * 生成一个包含每个语言环境对应 URL 的对象。
871
+ *
872
+ * 示例:
873
+ * ```ts
874
+ * getMultilingualUrls('/about');
875
+ *
876
+ * // 返回
877
+ * // {
878
+ * // en: '/about',
879
+ * // fr: '/fr/about',
880
+ * // es: '/es/about'
881
+ * // }
882
+ * ```
883
+ */
884
+ const multilingualUrls = getMultilingualUrls("/");
885
+
886
+ return {
887
+ ...metadata,
888
+ alternates: {
889
+ canonical: multilingualUrls[locale],
890
+ languages: { ...multilingualUrls, "x-default": "/" },
891
+ },
892
+ openGraph: {
893
+ url: multilingualUrls[locale],
894
+ },
895
+ };
896
+ };
897
+
898
+ // ... 代码的其余部分
899
+ ````
900
+
901
+ ````javascript fileName="src/app/[locale]/layout.cjs or src/app/[locale]/page.cjs" codeFormat="commonjs"
902
+ const { getIntlayer, getMultilingualUrls } = require("intlayer");
903
+
904
+ const generateMetadata = async ({ params }) => {
905
+ const { locale } = await params;
906
+
907
+ const metadata = getIntlayer("page-metadata", locale);
908
+
909
+ /**
910
+ * 生成一个包含每个语言环境所有 URL 的对象。
911
+ *
912
+ * 示例:
913
+ * ```ts
914
+ * getMultilingualUrls('/about');
915
+ *
916
+ * // 返回
917
+ * // {
918
+ * // en: '/about',
919
+ * // fr: '/fr/about',
920
+ * // es: '/es/about'
921
+ * // }
922
+ * ```
923
+ */
924
+ const multilingualUrls = getMultilingualUrls("/");
925
+
926
+ return {
927
+ ...metadata,
928
+ alternates: {
929
+ canonical: multilingualUrls[locale],
930
+ languages: { ...multilingualUrls, "x-default": "/" },
931
+ },
932
+ openGraph: {
933
+ url: multilingualUrls[locale],
934
+ },
935
+ };
936
+ };
937
+
938
+ module.exports = { generateMetadata };
939
+
940
+ // ... 其余代码
941
+ ````
942
+
943
+ > 注意,从 `next-intlayer` 导入的 `getIntlayer` 函数会将您的内容包装在一个 `IntlayerNode` 中,从而允许与可视化编辑器集成。相比之下,从 `intlayer` 导入的 `getIntlayer` 函数则直接返回您的内容,不带额外属性。
944
+
945
+ 或者,您可以使用 `getTranslation` 函数来声明您的元数据。然而,建议使用内容声明文件,以便在某个阶段自动翻译您的元数据并将内容外部化。
946
+
947
+ ```typescript fileName="src/app/[locale]/layout.tsx or src/app/[locale]/page.tsx" codeFormat="typescript"
948
+ import {
949
+ type IConfigLocales,
950
+ getTranslation,
951
+ getMultilingualUrls,
952
+ } from "intlayer";
953
+ import type { Metadata } from "next";
954
+ import type { LocalPromiseParams } from "next-intlayer";
955
+
956
+ export const generateMetadata = async ({
957
+ params,
958
+ }: LocalPromiseParams): Promise<Metadata> => {
959
+ const { locale } = await params;
960
+ const t = <T>(content: IConfigLocales<T>) => getTranslation(content, locale);
961
+
962
+ return {
963
+ title: t<string>({
964
+ en: "My title",
965
+ fr: "Mon titre",
966
+ es: "Mi título",
967
+ }),
968
+ description: t({
969
+ en: "我的描述",
970
+ fr: "Ma description",
971
+ es: "Mi descripción",
972
+ }),
973
+ };
974
+ };
975
+
976
+ // ... 代码的其余部分
977
+ ```
978
+
979
+ ```javascript fileName="src/app/[locale]/layout.mjs or src/app/[locale]/page.mjs" codeFormat="esm"
980
+ import { getTranslation, getMultilingualUrls } from "intlayer";
981
+
982
+ export const generateMetadata = async ({ params }) => {
983
+ const { locale } = await params;
984
+ const t = (content) => getTranslation(content, locale);
985
+
986
+ return {
987
+ title: t({
988
+ en: "我的标题",
989
+ fr: "Mon titre",
990
+ es: "Mi título",
991
+ }),
992
+ description: t({
993
+ en: "我的描述",
994
+ fr: "Ma description",
995
+ es: "Mi descripción",
996
+ }),
997
+ };
998
+ };
999
+
1000
+ // ... 代码的其余部分
1001
+ ```
1002
+
1003
+ ```javascript fileName="src/app/[locale]/layout.cjs or src/app/[locale]/page.cjs" codeFormat="commonjs"
1004
+ const { getTranslation, getMultilingualUrls } = require("intlayer");
1005
+
1006
+ const generateMetadata = async ({ params }) => {
1007
+ const { locale } = await params;
1008
+
1009
+ const t = (content) => getTranslation(content, locale);
1010
+
1011
+ return {
1012
+ title: t({
1013
+ en: "My title",
1014
+ fr: "Mon titre",
1015
+ es: "Mi título",
1016
+ }),
1017
+ description: t({
1018
+ en: "My description",
1019
+ fr: "Ma description",
1020
+ es: "Mi descripción",
1021
+ }),
1022
+ };
1023
+ };
1024
+
1025
+ module.exports = { generateMetadata };
1026
+
1027
+ // ... 代码的其余部分
1028
+ ```
1029
+
1030
+ > 了解有关元数据优化的更多信息,请参阅 [Next.js 官方文档](https://nextjs.org/docs/app/building-your-application/optimizing/metadata)。
1031
+
1032
+ ### (可选)步骤 9:国际化您的 sitemap.xml 和 robots.txt
1033
+
1034
+ 要实现 `sitemap.xml` 和 `robots.txt` 的国际化,您可以使用 Intlayer 提供的 `getMultilingualUrls` 函数。该函数允许您为站点地图生成多语言 URL。
1035
+
1036
+ ```tsx fileName="src/app/sitemap.ts" codeFormat="typescript"
1037
+ import { getMultilingualUrls } from "intlayer";
1038
+ import type { MetadataRoute } from "next";
1039
+
1040
+ const sitemap = (): MetadataRoute.Sitemap => [
1041
+ {
1042
+ url: "https://example.com",
1043
+ alternates: {
1044
+ languages: { ...getMultilingualUrls("https://example.com") },
1045
+ },
1046
+ },
1047
+ {
1048
+ url: "https://example.com/login",
1049
+ alternates: {
1050
+ languages: { ...getMultilingualUrls("https://example.com/login") },
1051
+ },
1052
+ },
1053
+ {
1054
+ url: "https://example.com/register",
1055
+ alternates: {
1056
+ languages: { ...getMultilingualUrls("https://example.com/register") },
1057
+ },
1058
+ },
1059
+ ];
1060
+
1061
+ export default sitemap;
1062
+ ```
1063
+
1064
+ ```jsx fileName="src/app/sitemap.mjx" codeFormat="esm"
1065
+ import { getMultilingualUrls } from "intlayer";
1066
+
1067
+ const sitemap = () => [
1068
+ {
1069
+ url: "https://example.com",
1070
+ alternates: {
1071
+ languages: { ...getMultilingualUrls("https://example.com") },
1072
+ },
1073
+ },
1074
+ {
1075
+ url: "https://example.com/login",
1076
+ alternates: {
1077
+ languages: { ...getMultilingualUrls("https://example.com/login") },
1078
+ },
1079
+ },
1080
+ {
1081
+ url: "https://example.com/register",
1082
+ alternates: {
1083
+ languages: { ...getMultilingualUrls("https://example.com/register") },
1084
+ },
1085
+ },
1086
+ ];
1087
+
1088
+ export default sitemap;
1089
+ ```
1090
+
1091
+ ```jsx fileName="src/app/sitemap.csx" codeFormat="commonjs"
1092
+ const { getMultilingualUrls } = require("intlayer");
1093
+
1094
+ const sitemap = () => [
1095
+ {
1096
+ url: "https://example.com",
1097
+ alternates: {
1098
+ languages: { ...getMultilingualUrls("https://example.com") },
1099
+ },
1100
+ },
1101
+ {
1102
+ url: "https://example.com/login",
1103
+ alternates: {
1104
+ languages: { ...getMultilingualUrls("https://example.com/login") },
1105
+ },
1106
+ },
1107
+ {
1108
+ url: "https://example.com/register",
1109
+ alternates: {
1110
+ languages: { ...getMultilingualUrls("https://example.com/register") },
1111
+ },
1112
+ },
1113
+ ];
1114
+
1115
+ module.exports = sitemap;
1116
+ ```
1117
+
1118
+ ```tsx fileName="src/app/robots.ts" codeFormat="typescript"
1119
+ import type { MetadataRoute } from "next";
1120
+ import { getMultilingualUrls } from "intlayer";
1121
+
1122
+ tsx fileName="src/app/robots.ts" codeFormat="typescript"
1123
+ import type { MetadataRoute } from "next";
1124
+ import { getMultilingualUrls } from "intlayer";
1125
+
1126
+ // 获取所有多语言版本的 URL
1127
+ const getAllMultilingualUrls = (urls: string[]) =>
1128
+ urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);
1129
+
1130
+ const robots = (): MetadataRoute.Robots => ({
1131
+ rules: {
1132
+ userAgent: "*", // 适用于所有用户代理
1133
+ allow: ["/"], // 允许访问根路径
1134
+ disallow: getAllMultilingualUrls(["/login", "/register"]), // 禁止访问登录和注册页面的所有语言版本
1135
+ },
1136
+ host: "https://example.com",
1137
+ sitemap: `https://example.com/sitemap.xml`,
1138
+ });
1139
+
1140
+ export default robots;
1141
+ ```
1142
+
1143
+ ```jsx fileName="src/app/robots.mjx" codeFormat="esm"
1144
+ import { getMultilingualUrls } from "intlayer";
1145
+
1146
+ // 获取所有多语言版本的 URL
1147
+ const getAllMultilingualUrls = (urls) =>
1148
+ urls.flatMap((url) => Object.values(getMultilingualUrls(url)));
1149
+
1150
+ const robots = () => ({
1151
+ rules: {
1152
+ userAgent: "*", // 适用于所有用户代理
1153
+ allow: ["/"], // 允许访问根路径
1154
+ disallow: getAllMultilingualUrls(["/login", "/register"]), // 禁止访问登录和注册页面的所有语言版本
1155
+ },
1156
+ host: "https://example.com",
1157
+ sitemap: `https://example.com/sitemap.xml`,
1158
+ });
1159
+
1160
+ export default robots;
1161
+ ```
1162
+
1163
+ ```jsx fileName="src/app/robots.csx" codeFormat="commonjs"
1164
+ const { getMultilingualUrls } = require("intlayer");
1165
+
1166
+ const getAllMultilingualUrls = (urls) =>
1167
+ urls.flatMap((url) => Object.values(getMultilingualUrls(url)));
1168
+
1169
+ const robots = () => ({
1170
+ rules: {
1171
+ userAgent: "*",
1172
+ allow: ["/"],
1173
+ disallow: getAllMultilingualUrls(["/login", "/register"]),
1174
+ },
1175
+ host: "https://example.com",
1176
+ sitemap: `https://example.com/sitemap.xml`,
1177
+ });
1178
+
1179
+ module.exports = robots;
1180
+ ```
1181
+
1182
+ > 了解有关站点地图优化的更多信息,请参阅[官方 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)。
1183
+
1184
+ ### (可选)步骤 10:更改内容语言
1185
+
1186
+ 在 Next.js 中更改内容语言,推荐的方式是使用 `Link` 组件将用户重定向到相应的本地化页面。`Link` 组件支持页面预加载,有助于避免完整页面重新加载。
1187
+
1188
+ ```tsx fileName="src/components/LocaleSwitcher.tsx" codeFormat="typescript"
1189
+ "use client";
1190
+
1191
+ import type { FC } from "react";
1192
+ import {
1193
+ Locales,
1194
+ getHTMLTextDir,
1195
+ getLocaleName,
1196
+ getLocalizedUrl,
1197
+ } from "intlayer";
1198
+ import { useLocale } from "next-intlayer";
1199
+ import Link from "next/link";
1200
+
1201
+ export const LocaleSwitcher: FC = () => {
1202
+ const { locale, pathWithoutLocale, availableLocales, setLocale } =
1203
+ useLocale();
1204
+
1205
+ return (
1206
+ <div>
1207
+ <button popoverTarget="localePopover">{getLocaleName(locale)}</button>
1208
+ <div id="localePopover" popover="auto">
1209
+ {availableLocales.map((localeItem) => (
1210
+ <Link
1211
+ href={getLocalizedUrl(pathWithoutLocale, localeItem)}
1212
+ key={localeItem}
1213
+ aria-current={locale === localeItem ? "page" : undefined}
1214
+ onClick={() => setLocale(localeItem)}
1215
+ replace // 将确保“后退”浏览器按钮会重定向到前一个页面
1216
+ >
1217
+ <span>
1218
+ {/* 语言环境 - 例如 FR */}
1219
+ {localeItem}
1220
+ </span>
1221
+ <span>
1222
+ {/* 该语言环境中的语言名称 - 例如 Français */}
1223
+ {getLocaleName(localeItem, locale)}
1224
+ </span>
1225
+ <span dir={getHTMLTextDir(localeItem)} lang={localeItem}>
1226
+ {/* 当前语言环境中的语言名称 - 例如当当前语言环境设置为 Locales.SPANISH 时显示 Francés */}
1227
+ {getLocaleName(localeItem)}
1228
+ </span>
1229
+ <span dir="ltr" lang={Locales.ENGLISH}>
1230
+ {/* 英文中的语言名称 - 例如 French */}
1231
+ {getLocaleName(localeItem, Locales.ENGLISH)}
1232
+ </span>
1233
+ </Link>
1234
+ ))}
1235
+ </div>
1236
+ </div>
1237
+ );
1238
+ };
1239
+ ```
1240
+
1241
+ ```jsx fileName="src/components/LocaleSwitcher.msx" codeFormat="esm"
1242
+ "use client";
1243
+
1244
+ import {
1245
+ Locales,
1246
+ getHTMLTextDir,
1247
+ getLocaleName,
1248
+ getLocalizedUrl,
1249
+ } from "intlayer";
1250
+ import { useLocale } from "next-intlayer";
1251
+ import Link from "next/link";
1252
+
1253
+ export const LocaleSwitcher = () => {
1254
+ const { locale, pathWithoutLocale, availableLocales, setLocale } =
1255
+ useLocale();
1256
+
1257
+ return (
1258
+ <div>
1259
+ <button popoverTarget="localePopover">{getLocaleName(locale)}</button>
1260
+ <div id="localePopover" popover="auto">
1261
+ {availableLocales.map((localeItem) => (
1262
+ <Link
1263
+ href={getLocalizedUrl(pathWithoutLocale, localeItem)}
1264
+ key={localeItem}
1265
+ aria-current={locale === localeItem ? "page" : undefined}
1266
+ onClick={() => setLocale(localeItem)}
1267
+ replace // 将确保浏览器的“后退”按钮会重定向到前一个页面
1268
+ >
1269
+ <span>
1270
+ {/* 语言区域 - 例如 FR */}
1271
+ {localeItem}
1272
+ </span>
1273
+ <span>
1274
+ {/* 语言在其自身的语言区域中 - 例如 Français */}
1275
+ {getLocaleName(localeItem, locale)}
1276
+ </span>
1277
+ <span dir={getHTMLTextDir(localeItem)} lang={localeItem}>
1278
+ {/* 语言在当前语言区域中 - 例如当前语言区域设置为 Locales.SPANISH 时显示 Francés */}
1279
+ {getLocaleName(localeItem)}
1280
+ </span>
1281
+ <span dir="ltr" lang={Locales.ENGLISH}>
1282
+ {/* 英语中的语言 - 例如 French */}
1283
+ {getLocaleName(localeItem, Locales.ENGLISH)}
1284
+ </span>
1285
+ </Link>
1286
+ ))}
1287
+ </div>
1288
+ </div>
1289
+ );
1290
+ };
1291
+ ```
1292
+
1293
+ ```jsx fileName="src/components/LocaleSwitcher.csx" codeFormat="commonjs"
1294
+ "use client";
1295
+
1296
+ const {
1297
+ Locales,
1298
+ getHTMLTextDir,
1299
+ getLocaleName,
1300
+ getLocalizedUrl,
1301
+ } = require("intlayer");
1302
+ const { useLocale } = require("next-intlayer");
1303
+ const Link = require("next/link");
1304
+
1305
+ export const LocaleSwitcher = () => {
1306
+ const { locale, pathWithoutLocale, availableLocales, setLocale } =
1307
+ useLocale();
1308
+
1309
+ return (
1310
+ <div>
1311
+ <button popoverTarget="localePopover">{getLocaleName(locale)}</button>
1312
+ <div id="localePopover" popover="auto">
1313
+ {availableLocales.map((localeItem) => (
1314
+ <Link
1315
+ href={getLocalizedUrl(pathWithoutLocale, localeItem)}
1316
+ key={localeItem}
1317
+ aria-current={locale === localeItem ? "page" : undefined}
1318
+ onClick={() => setLocale(localeItem)}
1319
+ replace // 将确保“后退”浏览器按钮会重定向到前一个页面
1320
+ >
1321
+ <span>
1322
+ {/* 语言环境 - 例如 FR */}
1323
+ {localeItem}
1324
+ </span>
1325
+ <span>
1326
+ {/* 语言以其自身的语言环境显示 - 例如 Français */}
1327
+ {getLocaleName(localeItem, locale)}
1328
+ </span>
1329
+ <span dir={getHTMLTextDir(localeItem)} lang={localeItem}>
1330
+ {/* 语言以当前语言环境显示 - 例如当前语言环境设置为 Locales.SPANISH 时显示 Francés */}
1331
+ {getLocaleName(localeItem)}
1332
+ </span>
1333
+ <span dir="ltr" lang={Locales.ENGLISH}>
1334
+ {/* 语言的英文名称 - 例如 French */}
1335
+ {getLocaleName(localeItem, Locales.ENGLISH)}
1336
+ </span>
1337
+ </Link>
1338
+ ))}
1339
+ </div>
1340
+ </div>
1341
+ );
1342
+ };
1343
+ ```
1344
+
1345
+ > 另一种方法是使用 `useLocale` 钩子提供的 `setLocale` 函数。该函数不支持页面预取。更多详情请参阅 [`useLocale` 钩子文档](https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/packages/next-intlayer/useLocale.md)。
1346
+
1347
+ > 你也可以在 `onLocaleChange` 选项中设置一个函数,以在语言环境变化时触发自定义函数。
1348
+
1349
+ ```tsx fileName="src/components/LocaleSwitcher.tsx"
1350
+ "use client";
1351
+
1352
+ import { useRouter } from "next/navigation";
1353
+ import { useLocale } from "next-intlayer";
1354
+ import { getLocalizedUrl } from "intlayer";
1355
+
1356
+ // ... 其余代码
1357
+
1358
+ const router = useRouter();
1359
+ const { setLocale } = useLocale({
1360
+ onLocaleChange: (locale) => {
1361
+ router.push(getLocalizedUrl(pathWithoutLocale, locale));
1362
+ },
1363
+ });
1364
+
1365
+ return <button onClick={() => setLocale(Locales.FRENCH)}>切换到法语</button>;
1366
+ ```
1367
+
1368
+ > 文档参考:
1369
+ >
1370
+ > - [`useLocale` 钩子](https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/packages/next-intlayer/useLocale.md)
1371
+ > - [`getLocaleName` 钩子](https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/packages/intlayer/getLocaleName.md)
1372
+ > - [`getLocalizedUrl` 钩子](https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/packages/intlayer/getLocalizedUrl.md)
1373
+ > - [`getHTMLTextDir` 钩子](https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/packages/intlayer/getHTMLTextDir.md)
1374
+ > - [`hrefLang` 属性](https://developers.google.com/search/docs/specialty/international/localized-versions?hl=fr)
1375
+ > - [`lang` 属性](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Global_attributes/lang)
1376
+ > - [`dir` 属性](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Global_attributes/dir)
1377
+ > - [`aria-current` 属性](https://developer.mozilla.org/zh-CN/docs/Web/Accessibility/ARIA/Attributes/aria-current)
1378
+
1379
+ ### (可选)步骤 11:创建本地化链接组件
1380
+
1381
+ 为了确保您的应用程序导航遵循当前的语言环境,您可以创建一个自定义的 `Link` 组件。该组件会自动在内部 URL 前添加当前语言的前缀。例如,当讲法语的用户点击“关于”页面的链接时,他们会被重定向到 `/fr/about`,而不是 `/about`。
1382
+
1383
+ 这种行为有几个好处:
1384
+
1385
+ - **SEO 和用户体验**:本地化的 URL 有助于搜索引擎正确索引特定语言的页面,并为用户提供其偏好的语言内容。
1386
+ - **一致性**:通过在整个应用程序中使用本地化链接,您可以确保导航始终保持在当前语言环境内,防止意外的语言切换。
1387
+ - **可维护性**:将本地化逻辑集中在单个组件中简化了 URL 的管理,使您的代码库更易于维护和扩展,随着应用程序的增长。
1388
+
1389
+ 下面是一个使用 TypeScript 实现的本地化 `Link` 组件示例:
1390
+
1391
+ ```tsx fileName="src/components/Link.tsx" codeFormat="typescript"
1392
+ "use client";
1393
+
1394
+ import { getLocalizedUrl } from "intlayer";
1395
+ import NextLink, { type LinkProps as NextLinkProps } from "next/link";
1396
+ import { useLocale } from "next-intlayer";
1397
+ import type { PropsWithChildren, FC } from "react";
1398
+
1399
+ /**
1400
+ * 工具函数,用于检查给定的 URL 是否为外部链接。
1401
+ * 如果 URL 以 http:// 或 https:// 开头,则视为外部链接。
1402
+ */
1403
+ export const checkIsExternalLink = (href?: string): boolean =>
1404
+ /^https?:\/\//.test(href ?? "");
1405
+
1406
+ /**
1407
+ * 一个自定义的 Link 组件,根据当前语言环境调整 href 属性。
1408
+ * 对于内部链接,使用 `getLocalizedUrl` 在 URL 前加上语言前缀(例如 /fr/about)。
1409
+ * 这样可以确保导航保持在相同的语言环境上下文中。
1410
+ */
1411
+ export const Link: FC<PropsWithChildren<NextLinkProps>> = ({
1412
+ href,
1413
+ children,
1414
+ ...props
1415
+ }) => {
1416
+ const { locale } = useLocale();
1417
+ const isExternalLink = checkIsExternalLink(href.toString());
1418
+
1419
+ // 如果链接是内部链接且 href 有效,则获取本地化的 URL。
1420
+ const hrefI18n: NextLinkProps["href"] =
1421
+ href && !isExternalLink ? getLocalizedUrl(href.toString(), locale) : href;
1422
+
1423
+ return (
1424
+ <NextLink href={hrefI18n} {...props}>
1425
+ {children}
1426
+ </NextLink>
1427
+ );
1428
+ };
1429
+ ```
1430
+
1431
+ ```jsx fileName="src/components/Link.mjx" codeFormat="esm"
1432
+ "use client";
1433
+
1434
+ import { getLocalizedUrl } from "intlayer";
1435
+ import NextLink from "next/link";
1436
+ import { useLocale } from "next-intlayer";
1437
+
1438
+ /**
1439
+ * 工具函数,用于检查给定的 URL 是否为外部链接。
1440
+ * 如果 URL 以 http:// 或 https:// 开头,则被视为外部链接。
1441
+ */
1442
+ export const checkIsExternalLink = (href) => /^https?:\/\//.test(href ?? "");
1443
+
1444
+ /**
1445
+ * 一个自定义的 Link 组件,根据当前语言环境调整 href 属性。
1446
+ * 对于内部链接,它使用 `getLocalizedUrl` 在 URL 前加上语言前缀(例如 /fr/about)。
1447
+ * 这确保导航保持在相同的语言环境上下文中。
1448
+ */
1449
+ export const Link = ({ href, children, ...props }) => {
1450
+ const { locale } = useLocale();
1451
+ const isExternalLink = checkIsExternalLink(href.toString());
1452
+
1453
+ // 如果链接是内部链接且提供了有效的 href,则获取本地化的 URL。
1454
+ const hrefI18n =
1455
+ href && !isExternalLink ? getLocalizedUrl(href.toString(), locale) : href;
1456
+
1457
+ return (
1458
+ <NextLink href={hrefI18n} {...props}>
1459
+ {children}
1460
+ </NextLink>
1461
+ );
1462
+ };
1463
+ ```
1464
+
1465
+ ```jsx fileName="src/components/Link.csx" codeFormat="commonjs"
1466
+ "use client";
1467
+
1468
+ const { getLocalizedUrl } = require("intlayer");
1469
+ const NextLink = require("next/link");
1470
+ const { useLocale } = require("next-intlayer");
1471
+
1472
+ /**
1473
+ * 工具函数,用于检查给定的 URL 是否为外部链接。
1474
+ * 如果 URL 以 http:// 或 https:// 开头,则视为外部链接。
1475
+ */
1476
+ const checkIsExternalLink = (href) => /^https?:\/\//.test(href ?? "");
1477
+
1478
+ /**
1479
+ * 一个自定义的 Link 组件,根据当前语言环境动态调整 href 属性。
1480
+ * 对于内部链接,它使用 `getLocalizedUrl` 在 URL 前添加语言前缀(例如 /fr/about)。
1481
+ * 这样可以确保导航保持在相同的语言环境上下文中。
1482
+ */
1483
+ const Link = ({ href, children, ...props }) => {
1484
+ const { locale } = useLocale();
1485
+ const isExternalLink = checkIsExternalLink(href.toString());
1486
+
1487
+ // 如果链接是内部链接且 href 有效,则获取本地化的 URL。
1488
+ const hrefI18n =
1489
+ href && !isExternalLink ? getLocalizedUrl(href.toString(), locale) : href;
1490
+
1491
+ return (
1492
+ <NextLink href={hrefI18n} {...props}>
1493
+ {children}
1494
+ </NextLink>
1495
+ );
1496
+ };
1497
+ ```
1498
+
1499
+ #### 工作原理
1500
+
1501
+ - **检测外部链接**:
1502
+ 辅助函数 `checkIsExternalLink` 用于判断一个 URL 是否为外部链接。外部链接保持不变,因为它们不需要本地化。
1503
+
1504
+ - **获取当前语言环境**:
1505
+ `useLocale` 钩子提供当前的语言环境(例如,法语为 `fr`)。
1506
+
1507
+ - **本地化 URL**:
1508
+ 对于内部链接(即非外部链接),使用 `getLocalizedUrl` 自动在 URL 前添加当前语言环境前缀。这意味着如果用户使用的是法语,传入的 `/about` 会被转换为 `/fr/about`。
1509
+
1510
+ - **返回链接**:
1511
+ 组件返回带有本地化 URL 的 `<a>` 元素,确保导航与当前语言环境保持一致。
1512
+
1513
+ 通过在您的应用程序中集成此 `Link` 组件,您可以保持一致且具语言感知的用户体验,同时还可提升 SEO 和可用性。
1514
+
1515
+ ### (可选)步骤 12:在服务器操作中获取当前语言环境
1516
+
1517
+ 如果您需要在服务器操作中获取活动语言环境(例如,用于本地化电子邮件或执行语言感知逻辑),请从 `next-intlayer/server` 调用 `getLocale`:
1518
+
1519
+ ```tsx fileName="src/app/actions/getLocale.ts" codeFormat="typescript"
1520
+ "use server";
1521
+
1522
+ import { getLocale } from "next-intlayer/server";
1523
+
1524
+ export const myServerAction = async () => {
1525
+ const locale = await getLocale();
1526
+
1527
+ // 使用该语言环境执行某些操作
1528
+ };
1529
+ ```
1530
+
1531
+ > `getLocale` 函数遵循级联策略来确定用户的语言环境:
1532
+ >
1533
+ > 1. 首先,它会检查请求头中是否有代理可能设置的语言环境值
1534
+ > 2. 如果请求头中没有找到语言环境,则查找存储在 Cookie 中的语言环境
1535
+ > 3. 如果没有找到 Cookie,则尝试从用户的浏览器设置中检测其首选语言
1536
+ > 4. 最后,作为最后手段,它会回退到应用程序配置的默认语言环境
1537
+ >
1538
+ > 这确保了根据可用的上下文选择最合适的语言环境。
1539
+
1540
+ ### (可选)步骤 13:优化你的包大小
1541
+
1542
+ 使用 `next-intlayer` 时,字典默认会包含在每个页面的打包文件中。为了优化打包体积,Intlayer 提供了一个可选的 SWC 插件,该插件通过宏智能地替换 `useIntlayer` 调用。这确保字典只包含在实际使用它们的页面的打包文件中。
1543
+
1544
+ 要启用此优化,请安装 `@intlayer/swc` 包。安装完成后,`next-intlayer` 会自动检测并使用该插件:
1545
+
1546
+ ```bash packageManager="npm"
1547
+ npm install @intlayer/swc --save-dev
1548
+ ```
1549
+
1550
+ ```bash packageManager="pnpm"
1551
+ pnpm add @intlayer/swc --save-dev
1552
+ ```
1553
+
1554
+ ```bash packageManager="yarn"
1555
+ yarn add @intlayer/swc --save-dev
1556
+ ```
1557
+
1558
+ > 注意:此优化仅适用于 Next.js 13 及以上版本。
1559
+
1560
+ > 注意:此包默认未安装,因为 SWC 插件在 Next.js 中仍处于实验阶段,未来可能会有所变化。
1561
+
1562
+ ### 在 Turbopack 上监视字典更改
1563
+
1564
+ 当使用 Turbopack 作为开发服务器并通过 `next dev` 命令启动时,默认情况下不会自动检测字典的更改。
1565
+
1566
+ 此限制的原因是 Turbopack 无法并行运行 webpack 插件来监视内容文件的更改。为了解决此问题,您需要使用 `intlayer watch` 命令,同时运行开发服务器和 Intlayer 构建监视器。
1567
+
1568
+ ```json5 fileName="package.json"
1569
+ {
1570
+ // ... 您现有的 package.json 配置
1571
+ "scripts": {
1572
+ // ... 您现有的脚本配置
1573
+ "dev": "intlayer watch --with 'next dev'",
1574
+ },
1575
+ }
1576
+ ```
1577
+
1578
+ > 如果您使用的是 next-intlayer@<=6.x.x,您需要保留 `--turbopack` 标志,以使 Next.js 16 应用程序能够正确地与 Turbopack 一起工作。我们建议使用 next-intlayer@>=7.x.x 以避免此限制。
1579
+
1580
+ ### 配置 TypeScript
1581
+
1582
+ Intlayer 使用模块增强来利用 TypeScript 的优势,使您的代码库更强大。
1583
+
1584
+ ![自动补全](https://github.com/aymericzip/intlayer/blob/main/docs/assets/autocompletion.png?raw=true)
1585
+
1586
+ ![翻译错误](https://github.com/aymericzip/intlayer/blob/main/docs/assets/translation_error.png?raw=true)
1587
+
1588
+ 确保您的 TypeScript 配置包含自动生成的类型。
1589
+
1590
+ ```json5 fileName="tsconfig.json"
1591
+ {
1592
+ // ... 您现有的 TypeScript 配置
1593
+ "include": [
1594
+ // ... 您现有的 TypeScript 配置
1595
+ ".intlayer/**/*.ts", // 包含自动生成的类型
1596
+ ],
1597
+ }
1598
+ ```
1599
+
1600
+ ### Git 配置
1601
+
1602
+ 建议忽略 Intlayer 生成的文件,这样可以避免将它们提交到你的 Git 仓库中。
1603
+
1604
+ 为此,你可以在 `.gitignore` 文件中添加以下内容:
1605
+
1606
+ ```plaintext fileName=".gitignore"
1607
+ # 忽略 Intlayer 生成的文件
1608
+ .intlayer
1609
+ ```
1610
+
1611
+ ### VS Code 扩展
1612
+
1613
+ 为了提升你使用 Intlayer 的开发体验,你可以安装官方的 **Intlayer VS Code 扩展**。
1614
+
1615
+ [从 VS Code 市场安装](https://marketplace.visualstudio.com/items?itemName=intlayer.intlayer-vs-code-extension)
1616
+
1617
+ 该扩展提供:
1618
+
1619
+ - 翻译键的 **自动补全**。
1620
+ - 缺失翻译的 **实时错误检测**。
1621
+ - **已翻译内容的内联预览**。
1622
+ - **快速操作**,轻松创建和更新翻译。
1623
+
1624
+ 有关如何使用该扩展的更多详细信息,请参阅[Intlayer VS Code 扩展文档](https://intlayer.org/doc/vs-code-extension)。
1625
+
1626
+ ### 深入了解
1627
+
1628
+ 要进一步使用,您可以实现[可视化编辑器](https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/intlayer_visual_editor.md)或使用[内容管理系统(CMS)](https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/intlayer_CMS.md)将内容外部化。