@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.
- package/dist/cjs/common.cjs.map +1 -1
- package/dist/esm/common.mjs.map +1 -1
- package/dist/types/common.d.ts +5 -0
- package/dist/types/common.d.ts.map +1 -1
- package/docs/ar/configuration.md +0 -24
- package/docs/ar/intlayer_with_nextjs_16.md +1652 -0
- package/docs/ar/releases/v7.md +485 -0
- package/docs/de/configuration.md +0 -24
- package/docs/de/intlayer_with_nextjs_16.md +1662 -0
- package/docs/de/releases/v7.md +502 -0
- package/docs/en/autoFill.md +3 -1
- package/docs/en/configuration.md +53 -58
- package/docs/en/intlayer_with_nextjs_15.md +5 -2
- package/docs/en/intlayer_with_nextjs_16.md +4 -4
- package/docs/en/releases/v7.md +142 -2
- package/docs/en-GB/configuration.md +9 -30
- package/docs/en-GB/intlayer_with_nextjs_16.md +1642 -0
- package/docs/en-GB/releases/v7.md +485 -0
- package/docs/es/configuration.md +0 -24
- package/docs/es/intlayer_with_nextjs_16.md +1670 -0
- package/docs/es/releases/v7.md +502 -0
- package/docs/fr/configuration.md +0 -24
- package/docs/fr/intlayer_with_nextjs_16.md +1692 -0
- package/docs/fr/releases/v7.md +503 -0
- package/docs/hi/configuration.md +0 -24
- package/docs/hi/intlayer_with_nextjs_16.md +1618 -0
- package/docs/hi/releases/v7.md +485 -0
- package/docs/id/intlayer_with_nextjs_16.md +1604 -0
- package/docs/id/releases/v7.md +502 -0
- package/docs/it/configuration.md +0 -24
- package/docs/it/intlayer_with_nextjs_16.md +1600 -0
- package/docs/it/releases/v7.md +504 -0
- package/docs/ja/configuration.md +0 -24
- package/docs/ja/intlayer_CMS.md +0 -9
- package/docs/ja/intlayer_with_nextjs_16.md +1788 -0
- package/docs/ja/releases/v7.md +503 -0
- package/docs/ko/configuration.md +0 -24
- package/docs/ko/intlayer_with_nextjs_16.md +1641 -0
- package/docs/ko/releases/v7.md +503 -0
- package/docs/pl/intlayer_with_nextjs_16.md +1645 -0
- package/docs/pl/releases/v7.md +485 -0
- package/docs/pt/configuration.md +0 -24
- package/docs/pt/intlayer_with_nextjs_16.md +1646 -0
- package/docs/pt/introduction.md +0 -15
- package/docs/pt/releases/v7.md +485 -0
- package/docs/ru/configuration.md +0 -24
- package/docs/ru/intlayer_with_nextjs_16.md +1610 -0
- package/docs/ru/releases/v7.md +485 -0
- package/docs/tr/configuration.md +0 -24
- package/docs/tr/intlayer_with_nextjs_16.md +1599 -0
- package/docs/tr/releases/v7.md +485 -0
- package/docs/vi/intlayer_with_nextjs_16.md +1597 -0
- package/docs/vi/releases/v7.md +485 -0
- package/docs/zh/configuration.md +0 -24
- package/docs/zh/intlayer_CMS.md +0 -23
- package/docs/zh/intlayer_with_nextjs_16.md +1628 -0
- package/docs/zh/releases/v7.md +486 -0
- package/package.json +14 -14
- package/src/common.ts +5 -0
|
@@ -0,0 +1,1788 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2024-12-06
|
|
3
|
+
updatedAt: 2025-10-09
|
|
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&origin=http://intlayer.org&controls=0&rel=1"/>
|
|
28
|
+
|
|
29
|
+
GitHubの[アプリケーションテンプレート](https://github.com/aymericzip/intlayer-next-16-template)をご覧ください。
|
|
30
|
+
|
|
31
|
+
## Intlayerとは?
|
|
32
|
+
|
|
33
|
+
**Intlayer**は、最新のウェブアプリケーションにおける多言語対応を簡素化するために設計された革新的なオープンソースの国際化(i18n)ライブラリです。Intlayerは、強力な**App Router**を含む最新の**Next.js 16**フレームワークとシームレスに統合されます。効率的なレンダリングのために**サーバーコンポーネント**での動作に最適化されており、[**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/ja/intlayer_with_nextjs_page_router.md)を参照してください。Next.js 12、13、14のApp Routerを使用している場合は、この[ガイド](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ja/intlayer_with_nextjs_14.md)を参照してください。
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Next.jsアプリケーションでIntlayerをセットアップするステップバイステップガイド
|
|
48
|
+
|
|
49
|
+
### ステップ1:依存関係のインストール
|
|
50
|
+
|
|
51
|
+
npmを使って必要なパッケージをインストールします:
|
|
52
|
+
|
|
53
|
+
```bash packageManager="npm"
|
|
54
|
+
npm install intlayer next-intlayer
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```bash packageManager="pnpm"
|
|
58
|
+
pnpm add intlayer next-intlayer
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```bash packageManager="yarn"
|
|
62
|
+
yarn add intlayer next-intlayer
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
- **intlayer**
|
|
66
|
+
|
|
67
|
+
設定管理、翻訳、[コンテンツ宣言](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ja/dictionary/content_file.md)、トランスパイル、および[CLIコマンド](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ja/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プラグイン、ユーザーの優先ロケール検出、クッキー管理、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の設定、プロキシリダイレクト、クッキー名、コンテンツ宣言の場所と拡張子の指定、コンソールでのIntlayerログの無効化などが行えます。利用可能なパラメータの完全なリストについては、[設定ドキュメント](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ja/configuration.md)を参照してください。
|
|
134
|
+
|
|
135
|
+
### ステップ3: 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()` は Next.js プラグインで、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
|
+
// `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`コンポーネントを空のままにしておくことで、`<html>`タグに[`lang`](https://developer.mozilla.org/ja/docs/Web/HTML/Global_attributes/lang)および[`dir`](https://developer.mozilla.org/ja/docs/Web/HTML/Global_attributes/dir)属性を設定できます。
|
|
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
|
+
> この段階で、`Error: Missing <html> and <body> tags in the root layout.` というエラーが発生します。これは予期されたもので、`/app/page.tsx` ファイルはもはや使用されておらず、削除して問題ありません。代わりに、`[locale]` パスセグメントが `/app/[locale]/page.tsx` ページを有効にします。その結果、ブラウザでは `/en`、`/fr`、`/es` のようなパスでページにアクセスできるようになります。デフォルトのロケールをルートページとして設定するには、ステップ7の `proxy` 設定を参照してください。
|
|
337
|
+
|
|
338
|
+
次に、アプリケーションのレイアウト内で `generateStaticParams` 関数を実装します。
|
|
339
|
+
|
|
340
|
+
```tsx {1} fileName="src/app/[locale]/layout.tsx" codeFormat="typescript"
|
|
341
|
+
export { generateStaticParams } from "next-intlayer"; // 挿入する行
|
|
342
|
+
|
|
343
|
+
const LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => {
|
|
344
|
+
/*... 残りのコード */
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
export default LocaleLayout;
|
|
348
|
+
````
|
|
349
|
+
|
|
350
|
+
```jsx {1} fileName="src/app/[locale]/layout.mjx" codeFormat="esm"
|
|
351
|
+
export { generateStaticParams } from "next-intlayer"; // 挿入する行
|
|
352
|
+
|
|
353
|
+
const LocaleLayout = async ({ children, params: { locale } }) => {
|
|
354
|
+
/*... 残りのコード */
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
// ... 残りのコード
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
```jsx {1,7} fileName="src/app/[locale]/layout.csx" codeFormat="commonjs"
|
|
361
|
+
const { generateStaticParams } = require("next-intlayer"); // 挿入する行
|
|
362
|
+
|
|
363
|
+
const LocaleLayout = async ({ children, params: { locale } }) => {
|
|
364
|
+
/*... 残りのコード */
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
module.exports = { default: LocaleLayout, generateStaticParams };
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
> `generateStaticParams` は、アプリケーションがすべてのロケールに対して必要なページを事前にビルドすることを保証し、実行時の計算を減らしユーザー体験を向上させます。詳細は [Next.js の generateStaticParams に関するドキュメント](https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic-rendering#generate-static-params) を参照してください。
|
|
371
|
+
|
|
372
|
+
> Intlayer は `export const dynamic = 'force-static';` と連携して、すべてのロケールのページが事前にビルドされることを保証します。
|
|
373
|
+
|
|
374
|
+
### ステップ5: コンテンツの宣言
|
|
375
|
+
|
|
376
|
+
翻訳を格納するためのコンテンツ宣言を作成・管理します:
|
|
377
|
+
|
|
378
|
+
```tsx fileName="src/app/[locale]/page.content.ts" contentDeclarationFormat="typescript"
|
|
379
|
+
import { t, type Dictionary } from "intlayer";
|
|
380
|
+
|
|
381
|
+
const pageContent = {
|
|
382
|
+
key: "page",
|
|
383
|
+
content: {
|
|
384
|
+
getStarted: {
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
> `generateStaticParams` は、すべてのロケールに必要なページを事前にビルドすることを保証し、実行時の計算を減らしてユーザー体験を向上させます。詳細については、[Next.js の generateStaticParams に関するドキュメント](https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic-rendering#generate-static-params)を参照してください。
|
|
388
|
+
|
|
389
|
+
> Intlayer は `export const dynamic = 'force-static';` と連携して、すべてのロケールのページが事前にビルドされるようにします。
|
|
390
|
+
|
|
391
|
+
### ステップ5: コンテンツを宣言する
|
|
392
|
+
|
|
393
|
+
翻訳を格納するためのコンテンツ宣言を作成および管理します:
|
|
394
|
+
|
|
395
|
+
```tsx fileName="src/app/[locale]/page.content.ts" contentDeclarationFormat="typescript"
|
|
396
|
+
import { t, type Dictionary } from "intlayer";
|
|
397
|
+
|
|
398
|
+
const pageContent = {
|
|
399
|
+
key: "page",
|
|
400
|
+
content: {
|
|
401
|
+
getStarted: {
|
|
402
|
+
main: t({
|
|
403
|
+
en: "Get started by editing",
|
|
404
|
+
fr: "Commencez par éditer",
|
|
405
|
+
es: "Comience por editar",
|
|
406
|
+
}),
|
|
407
|
+
pageLink: "src/app/page.tsx",
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
} satisfies Dictionary;
|
|
411
|
+
|
|
412
|
+
export default pageContent;
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
```javascript fileName="src/app/[locale]/page.content.mjs" contentDeclarationFormat="esm"
|
|
416
|
+
import { t } from "intlayer";
|
|
417
|
+
|
|
418
|
+
/** @type {import('intlayer').Dictionary} */
|
|
419
|
+
const pageContent = {
|
|
420
|
+
key: "page",
|
|
421
|
+
content: {
|
|
422
|
+
getStarted: {
|
|
423
|
+
main: t({
|
|
424
|
+
en: "Get started by editing",
|
|
425
|
+
fr: "Commencez par éditer",
|
|
426
|
+
es: "Comience por editar",
|
|
427
|
+
}),
|
|
428
|
+
pageLink: "src/app/page.tsx",
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
export default pageContent;
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
```javascript fileName="src/app/[locale]/page.content.cjs" contentDeclarationFormat="commonjs"
|
|
437
|
+
const { t } = require("intlayer");
|
|
438
|
+
|
|
439
|
+
/** @type {import('intlayer').Dictionary} */
|
|
440
|
+
const pageContent = {
|
|
441
|
+
key: "page",
|
|
442
|
+
content: {
|
|
443
|
+
getStarted: {
|
|
444
|
+
main: t({
|
|
445
|
+
en: "Get started by editing",
|
|
446
|
+
fr: "Commencez par éditer",
|
|
447
|
+
es: "Comience por editar",
|
|
448
|
+
}),
|
|
449
|
+
pageLink: "src/app/page.tsx",
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
module.exports = pageContent;
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
```json fileName="src/app/[locale]/page.content.json" contentDeclarationFormat="json"
|
|
458
|
+
{
|
|
459
|
+
"$schema": "https://intlayer.org/schema.json",
|
|
460
|
+
"key": "page",
|
|
461
|
+
"content": {
|
|
462
|
+
"getStarted": {
|
|
463
|
+
"nodeType": "translation",
|
|
464
|
+
"translation": {
|
|
465
|
+
"en": "Get started by editing",
|
|
466
|
+
"fr": "Commencez par éditer",
|
|
467
|
+
"es": "Comience por editar"
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
"pageLink": "src/app/page.tsx"
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
const { t } = require("intlayer");
|
|
474
|
+
|
|
475
|
+
/** @type {import('intlayer').Dictionary} */
|
|
476
|
+
const pageContent = {
|
|
477
|
+
key: "page",
|
|
478
|
+
content: {
|
|
479
|
+
getStarted: {
|
|
480
|
+
main: t({
|
|
481
|
+
en: "Get started by editing",
|
|
482
|
+
fr: "Commencez par éditer",
|
|
483
|
+
es: "Comience por editar",
|
|
484
|
+
ja: "編集を始めてください",
|
|
485
|
+
}),
|
|
486
|
+
pageLink: "src/app/page.tsx",
|
|
487
|
+
},
|
|
488
|
+
},
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
module.exports = pageContent;
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
```json fileName="src/app/[locale]/page.content.json" contentDeclarationFormat="json"
|
|
495
|
+
{
|
|
496
|
+
"$schema": "https://intlayer.org/schema.json",
|
|
497
|
+
"key": "page",
|
|
498
|
+
"content": {
|
|
499
|
+
"getStarted": {
|
|
500
|
+
"nodeType": "translation",
|
|
501
|
+
"translation": {
|
|
502
|
+
"en": "Get started by editing",
|
|
503
|
+
"fr": "Commencez par éditer",
|
|
504
|
+
"es": "Comience por editar",
|
|
505
|
+
"ja": "編集を始めてください"
|
|
506
|
+
}
|
|
507
|
+
},
|
|
508
|
+
"pageLink": "src/app/page.tsx"
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
> コンテンツ宣言は、`contentDir` ディレクトリ(デフォルトは `./src`)に含まれている限り、アプリケーションのどこにでも定義できます。また、コンテンツ宣言ファイルの拡張子は(デフォルトで `.content.{json,ts,tsx,js,jsx,mjs,mjx,cjs,cjx}`)に一致する必要があります。
|
|
514
|
+
|
|
515
|
+
> 詳細については、[コンテンツ宣言のドキュメント](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ja/dictionary/content_file.md)を参照してください。
|
|
516
|
+
|
|
517
|
+
### ステップ6: コード内でコンテンツを利用する
|
|
518
|
+
|
|
519
|
+
アプリケーション全体でコンテンツ辞書にアクセスします:
|
|
520
|
+
|
|
521
|
+
```tsx fileName="src/app/[locale]/page.tsx" codeFormat="typescript"
|
|
522
|
+
import type { FC } from "react";
|
|
523
|
+
import { ClientComponentExample } from "@components/ClientComponentExample";
|
|
524
|
+
import { ServerComponentExample } from "@components/ServerComponentExample";
|
|
525
|
+
import { type NextPageIntlayer, IntlayerClientProvider } from "next-intlayer";
|
|
526
|
+
import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";
|
|
527
|
+
|
|
528
|
+
const PageContent: FC = () => {
|
|
529
|
+
const content = useIntlayer("page");
|
|
530
|
+
|
|
531
|
+
return (
|
|
532
|
+
<>
|
|
533
|
+
<p>{content.getStarted.main}</p> {/* メインの開始テキスト */}
|
|
534
|
+
<code>{content.getStarted.pageLink}</code> {/* ページリンク */}
|
|
535
|
+
</>
|
|
536
|
+
);
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
const Page: NextPageIntlayer = async ({ params }) => {
|
|
540
|
+
const { locale } = await params;
|
|
541
|
+
|
|
542
|
+
return (
|
|
543
|
+
<IntlayerServerProvider locale={locale}>
|
|
544
|
+
<PageContent />
|
|
545
|
+
<ServerComponentExample />
|
|
546
|
+
|
|
547
|
+
<IntlayerClientProvider locale={locale}>
|
|
548
|
+
<ClientComponentExample />
|
|
549
|
+
</IntlayerClientProvider>
|
|
550
|
+
</IntlayerServerProvider>
|
|
551
|
+
);
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
export default Page;
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
```jsx fileName="src/app/[locale]/page.mjx" codeFormat="esm"
|
|
558
|
+
import { ClientComponentExample } from "@components/ClientComponentExample";
|
|
559
|
+
import { ServerComponentExample } from "@components/ServerComponentExample";
|
|
560
|
+
import { IntlayerClientProvider } from "next-intlayer";
|
|
561
|
+
import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";
|
|
562
|
+
|
|
563
|
+
const PageContent = () => {
|
|
564
|
+
const content = useIntlayer("page");
|
|
565
|
+
|
|
566
|
+
return (
|
|
567
|
+
<>
|
|
568
|
+
<p>{content.getStarted.main}</p> {/* メインの開始メッセージ */}
|
|
569
|
+
<code>{content.getStarted.pageLink}</code> {/* ページリンク */}
|
|
570
|
+
</>
|
|
571
|
+
);
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
const Page = async ({ params }) => {
|
|
575
|
+
const { locale } = await params;
|
|
576
|
+
|
|
577
|
+
return (
|
|
578
|
+
<IntlayerServerProvider locale={locale}>
|
|
579
|
+
<PageContent />
|
|
580
|
+
<ServerComponentExample />
|
|
581
|
+
|
|
582
|
+
<IntlayerClientProvider locale={locale}>
|
|
583
|
+
<ClientComponentExample />
|
|
584
|
+
</IntlayerClientProvider>
|
|
585
|
+
</IntlayerServerProvider>
|
|
586
|
+
);
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
export default Page;
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
```jsx fileName="src/app/[locale]/page.csx" codeFormat="commonjs"
|
|
593
|
+
import { ClientComponentExample } from "@components/ClientComponentExample";
|
|
594
|
+
import { ServerComponentExample } from "@components/ServerComponentExample";
|
|
595
|
+
import { IntlayerClientProvider } from "next-intlayer";
|
|
596
|
+
import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";
|
|
597
|
+
|
|
598
|
+
const PageContent = () => {
|
|
599
|
+
const content = useIntlayer("page");
|
|
600
|
+
|
|
601
|
+
return (
|
|
602
|
+
<>
|
|
603
|
+
<p>{content.getStarted.main}</p>
|
|
604
|
+
<code>{content.getStarted.pageLink}</code>
|
|
605
|
+
</>
|
|
606
|
+
);
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
const Page = async ({ params }) => {
|
|
610
|
+
const { locale } = await params;
|
|
611
|
+
|
|
612
|
+
return (
|
|
613
|
+
<IntlayerServerProvider locale={locale}>
|
|
614
|
+
<PageContent />
|
|
615
|
+
<ServerComponentExample />
|
|
616
|
+
|
|
617
|
+
<IntlayerClientProvider locale={locale}>
|
|
618
|
+
<ClientComponentExample />
|
|
619
|
+
</IntlayerClientProvider>
|
|
620
|
+
</IntlayerServerProvider>
|
|
621
|
+
);
|
|
622
|
+
};
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
- **`IntlayerClientProvider`** はクライアントサイドのコンポーネントにロケールを提供するために使用されます。レイアウトを含む任意の親コンポーネントに配置できます。ただし、Next.jsはページ間でレイアウトコードを共有するため、レイアウトに配置することが推奨されます。レイアウトで `IntlayerClientProvider` を使用することで、各ページごとに再初期化する必要がなくなり、パフォーマンスが向上し、アプリケーション全体で一貫したローカリゼーションコンテキストを維持できます。
|
|
626
|
+
- **`IntlayerServerProvider`** はサーバー側の子コンポーネントにロケールを提供するために使用されます。レイアウトには設定できません。
|
|
627
|
+
|
|
628
|
+
> レイアウトとページは共通のサーバーコンテキストを共有できません。なぜなら、サーバーコンテキストシステムはリクエストごとのデータストア([Reactのcache](https://react.dev/reference/react/cache) メカニズム)に基づいており、アプリケーションの異なるセグメントごとに「コンテキスト」が再作成されるためです。プロバイダーを共有レイアウトに配置すると、この分離が破られ、サーバーコンポーネントに正しくサーバーコンテキストの値が伝播されなくなります。
|
|
629
|
+
|
|
630
|
+
> レイアウトとページは共通のサーバーコンテキストを共有できません。なぜなら、サーバーコンテキストシステムはリクエストごとのデータストア([Reactのキャッシュ](https://react.dev/reference/react/cache)メカニズム)に基づいているため、アプリケーションの異なるセグメントごとに各「コンテキスト」が再作成されるからです。プロバイダーを共有レイアウトに配置すると、この分離が破られ、サーバーコンポーネントへのサーバーコンテキスト値の正しい伝播が妨げられます。
|
|
631
|
+
|
|
632
|
+
````tsx {4,7} fileName="src/components/ClientComponentExample.tsx" codeFormat="typescript"
|
|
633
|
+
"use client";
|
|
634
|
+
|
|
635
|
+
import type { FC } from "react";
|
|
636
|
+
import { useIntlayer } from "next-intlayer";
|
|
637
|
+
|
|
638
|
+
export const ClientComponentExample: FC = () => {
|
|
639
|
+
const content = useIntlayer("client-component-example"); // 関連するコンテンツ宣言を作成
|
|
640
|
+
|
|
641
|
+
return (
|
|
642
|
+
<div>
|
|
643
|
+
> レイアウトとページは共通のサーバーコンテキストを共有できません。なぜなら、サーバーコンテキストシステムはリクエストごとのデータストア([Reactのcache](https://react.dev/reference/react/cache)メカニズム)に基づいており、アプリケーションの異なるセグメントごとに各「コンテキスト」が再作成されるためです。プロバイダーを共有レイアウトに配置すると、この分離が破壊され、サーバーコンポーネントへのサーバーコンテキスト値の正しい伝播が妨げられます。
|
|
644
|
+
|
|
645
|
+
```tsx {4,7} fileName="src/components/ClientComponentExample.tsx" codeFormat="typescript"
|
|
646
|
+
"use client";
|
|
647
|
+
|
|
648
|
+
import type { FC } from "react";
|
|
649
|
+
import { useIntlayer } from "next-intlayer";
|
|
650
|
+
|
|
651
|
+
export const ClientComponentExample: FC = () => {
|
|
652
|
+
const content = useIntlayer("client-component-example"); // 関連するコンテンツ宣言を作成
|
|
653
|
+
|
|
654
|
+
return (
|
|
655
|
+
<div>
|
|
656
|
+
<h2>{content.title}</h2>
|
|
657
|
+
<p>{content.content}</p>
|
|
658
|
+
</div>
|
|
659
|
+
);
|
|
660
|
+
};
|
|
661
|
+
````
|
|
662
|
+
|
|
663
|
+
```jsx {3,6} fileName="src/components/ClientComponentExample.mjx" codeFormat="esm"
|
|
664
|
+
"use client";
|
|
665
|
+
|
|
666
|
+
import { useIntlayer } from "next-intlayer";
|
|
667
|
+
|
|
668
|
+
const ClientComponentExample = () => {
|
|
669
|
+
const content = useIntlayer("client-component-example"); // 関連するコンテンツ宣言を作成
|
|
670
|
+
|
|
671
|
+
return (
|
|
672
|
+
<div>
|
|
673
|
+
<h2>{content.title}</h2>
|
|
674
|
+
<p>{content.content}</p>
|
|
675
|
+
</div>
|
|
676
|
+
);
|
|
677
|
+
};
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
```jsx {3,6} fileName="src/components/ClientComponentExample.csx" codeFormat="commonjs"
|
|
681
|
+
"use client";
|
|
682
|
+
|
|
683
|
+
const { useIntlayer } = require("next-intlayer");
|
|
684
|
+
|
|
685
|
+
const ClientComponentExample = () => {
|
|
686
|
+
const content = useIntlayer("client-component-example"); // 関連するコンテンツ宣言を作成
|
|
687
|
+
|
|
688
|
+
return (
|
|
689
|
+
<div>
|
|
690
|
+
<h2>{content.title}</h2>
|
|
691
|
+
<p>{content.content}</p>
|
|
692
|
+
</div>
|
|
693
|
+
);
|
|
694
|
+
};
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
```tsx {2} fileName="src/components/ServerComponentExample.tsx" codeFormat="typescript"
|
|
698
|
+
import type { FC } from "react";
|
|
699
|
+
import { useIntlayer } from "next-intlayer/server";
|
|
700
|
+
|
|
701
|
+
export const ServerComponentExample: FC = () => {
|
|
702
|
+
const content = useIntlayer("server-component-example"); // 関連するコンテンツ宣言を作成
|
|
703
|
+
|
|
704
|
+
return (
|
|
705
|
+
<div>
|
|
706
|
+
<h2>{content.title}</h2>
|
|
707
|
+
<p>{content.content}</p>
|
|
708
|
+
</div>
|
|
709
|
+
);
|
|
710
|
+
};
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
```jsx {1} fileName="src/components/ServerComponentExample.mjx" codeFormat="esm"
|
|
714
|
+
import { useIntlayer } from "next-intlayer/server";
|
|
715
|
+
|
|
716
|
+
const ServerComponentExample = () => {
|
|
717
|
+
const content = useIntlayer("server-component-example"); // 関連するコンテンツ宣言を作成
|
|
718
|
+
|
|
719
|
+
return (
|
|
720
|
+
<h2>{content.title}</h2>
|
|
721
|
+
<p>{content.content}</p>
|
|
722
|
+
</div>
|
|
723
|
+
);
|
|
724
|
+
};
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
```tsx {2} fileName="src/components/ServerComponentExample.tsx" codeFormat="typescript"
|
|
728
|
+
import type { FC } from "react";
|
|
729
|
+
import { useIntlayer } from "next-intlayer/server";
|
|
730
|
+
|
|
731
|
+
export const ServerComponentExample: FC = () => {
|
|
732
|
+
const content = useIntlayer("server-component-example"); // 関連コンテンツ宣言を作成
|
|
733
|
+
|
|
734
|
+
return (
|
|
735
|
+
<div>
|
|
736
|
+
<h2>{content.title}</h2>
|
|
737
|
+
<p>{content.content}</p>
|
|
738
|
+
</div>
|
|
739
|
+
);
|
|
740
|
+
};
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
```jsx {1} fileName="src/components/ServerComponentExample.mjx" codeFormat="esm"
|
|
744
|
+
import { useIntlayer } from "next-intlayer/server";
|
|
745
|
+
|
|
746
|
+
const ServerComponentExample = () => {
|
|
747
|
+
const content = useIntlayer("server-component-example"); // 関連コンテンツ宣言を作成
|
|
748
|
+
|
|
749
|
+
return (
|
|
750
|
+
<div>
|
|
751
|
+
<h2>{content.title}</h2>
|
|
752
|
+
<p>{content.content}</p>
|
|
753
|
+
</div>
|
|
754
|
+
);
|
|
755
|
+
};
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
```jsx {1} fileName="src/components/ServerComponentExample.csx" codeFormat="commonjs"
|
|
759
|
+
const { useIntlayer } = require("next-intlayer/server");
|
|
760
|
+
|
|
761
|
+
const ServerComponentExample = () => {
|
|
762
|
+
const content = useIntlayer("server-component-example"); // 関連するコンテンツ宣言を作成
|
|
763
|
+
|
|
764
|
+
return (
|
|
765
|
+
<div>
|
|
766
|
+
<h2>{content.title}</h2>
|
|
767
|
+
<p>{content.content}</p>
|
|
768
|
+
</div>
|
|
769
|
+
);
|
|
770
|
+
};
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
> コンテンツを `alt`、`title`、`href`、`aria-label` などの `string` 属性で使用したい場合は、関数の値を呼び出す必要があります。例えば:
|
|
774
|
+
|
|
775
|
+
> ```jsx
|
|
776
|
+
> <img src={content.image.src.value} alt={content.image.value} />
|
|
777
|
+
> ```
|
|
778
|
+
|
|
779
|
+
> `useIntlayer` フックの詳細については、[ドキュメント](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ja/packages/next-intlayer/useIntlayer.md)を参照してください。
|
|
780
|
+
|
|
781
|
+
### (任意)ステップ7: ロケール検出のためのプロキシ設定
|
|
782
|
+
|
|
783
|
+
ユーザーの優先ロケールを検出するためのプロキシを設定します:
|
|
784
|
+
|
|
785
|
+
```typescript fileName="src/proxy.ts" codeFormat="typescript"
|
|
786
|
+
export { intlayerProxy as proxy } from "next-intlayer/proxy";
|
|
787
|
+
|
|
788
|
+
export const config = {
|
|
789
|
+
matcher:
|
|
790
|
+
"/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\..*|_next).*)",
|
|
791
|
+
};
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
```javascript fileName="src/proxy.mjs" codeFormat="esm"
|
|
795
|
+
export { intlayerProxy as proxy } from "next-intlayer/proxy";
|
|
796
|
+
|
|
797
|
+
export const config = {
|
|
798
|
+
matcher:
|
|
799
|
+
"/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\..*|_next).*)",
|
|
800
|
+
};
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
```javascript fileName="src/proxy.cjs" codeFormat="commonjs"
|
|
804
|
+
const { intlayerProxy } = require("next-intlayer/proxy");
|
|
805
|
+
|
|
806
|
+
const config = {
|
|
807
|
+
matcher:
|
|
808
|
+
"/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\..*|_next).*)",
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
module.exports = { proxy: intlayerProxy, config };
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
> `intlayerProxy` はユーザーの優先ロケールを検出し、[設定](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ja/configuration.md)で指定された適切なURLにリダイレクトするために使用されます。さらに、ユーザーの優先ロケールをクッキーに保存することも可能にします。
|
|
815
|
+
|
|
816
|
+
> 複数のプロキシを連結する必要がある場合(例えば、認証付きの `intlayerProxy` やカスタムプロキシと組み合わせる場合)、Intlayer は `multipleProxies` というヘルパーを提供しています。
|
|
817
|
+
|
|
818
|
+
```ts
|
|
819
|
+
import { multipleProxies, intlayerProxy } from "next-intlayer/proxy";
|
|
820
|
+
import { customProxy } from "@utils/customProxy";
|
|
821
|
+
|
|
822
|
+
export const proxy = multipleProxies([intlayerProxy, customProxy]);
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
### (オプション)ステップ8:メタデータの国際化
|
|
826
|
+
|
|
827
|
+
ページのタイトルなどのメタデータを国際化したい場合は、Next.jsが提供する`generateMetadata`関数を使用できます。その中で、`getIntlayer`関数からコンテンツを取得してメタデータを翻訳できます。
|
|
828
|
+
|
|
829
|
+
```typescript fileName="src/app/[locale]/metadata.content.ts" contentDeclarationFormat="typescript"
|
|
830
|
+
import { type Dictionary, t } from "intlayer";
|
|
831
|
+
import { Metadata } from "next";
|
|
832
|
+
|
|
833
|
+
const metadataContent = {
|
|
834
|
+
key: "page-metadata",
|
|
835
|
+
content: {
|
|
836
|
+
title: t({
|
|
837
|
+
en: "Create Next App",
|
|
838
|
+
fr: "Créer une application Next.js",
|
|
839
|
+
es: "Crear una aplicación Next.js",
|
|
840
|
+
}),
|
|
841
|
+
description: t({
|
|
842
|
+
en: "Generated by create next app",
|
|
843
|
+
fr: "Généré par create next app",
|
|
844
|
+
es: "Generado por create next app",
|
|
845
|
+
}),
|
|
846
|
+
},
|
|
847
|
+
} satisfies Dictionary<Metadata>;
|
|
848
|
+
|
|
849
|
+
export default metadataContent;
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
```javascript fileName="src/app/[locale]/metadata.content.mjs" contentDeclarationFormat="esm"
|
|
853
|
+
import { t } from "intlayer";
|
|
854
|
+
|
|
855
|
+
/** @type {import('intlayer').Dictionary<import('next').Metadata>} */
|
|
856
|
+
const metadataContent = {
|
|
857
|
+
key: "page-metadata",
|
|
858
|
+
content: {
|
|
859
|
+
title: t({
|
|
860
|
+
en: "Create Next App",
|
|
861
|
+
ja: "Next.js アプリを作成",
|
|
862
|
+
fr: "Créer une application Next.js",
|
|
863
|
+
es: "Crear una aplicación Next.js",
|
|
864
|
+
}),
|
|
865
|
+
description: t({
|
|
866
|
+
en: "Generated by create next app",
|
|
867
|
+
ja: "create next app によって生成されました",
|
|
868
|
+
fr: "Généré par create next app",
|
|
869
|
+
es: "Generado por create next app",
|
|
870
|
+
}),
|
|
871
|
+
},
|
|
872
|
+
};
|
|
873
|
+
|
|
874
|
+
export default metadataContent;
|
|
875
|
+
```
|
|
876
|
+
|
|
877
|
+
```javascript fileName="src/app/[locale]/metadata.content.cjs" contentDeclarationFormat="commonjs"
|
|
878
|
+
const { t } = require("intlayer");
|
|
879
|
+
|
|
880
|
+
/** @type {import('intlayer').Dictionary<import('next').Metadata>} */
|
|
881
|
+
const metadataContent = {
|
|
882
|
+
key: "page-metadata",
|
|
883
|
+
content: {
|
|
884
|
+
title: t({
|
|
885
|
+
en: "Create Next App",
|
|
886
|
+
ja: "Next.js アプリを作成",
|
|
887
|
+
fr: "Créer une application Next.js",
|
|
888
|
+
es: "Crear una aplicación Next.js",
|
|
889
|
+
}),
|
|
890
|
+
description: t({
|
|
891
|
+
en: "Generated by create next app",
|
|
892
|
+
ja: "create next app によって生成されました",
|
|
893
|
+
fr: "Généré par create next app",
|
|
894
|
+
es: "Generado por create next app",
|
|
895
|
+
}),
|
|
896
|
+
},
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
module.exports = metadataContent;
|
|
900
|
+
fr: "Généré par create next app",
|
|
901
|
+
es: "Generado por create next app",
|
|
902
|
+
}),
|
|
903
|
+
},
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
export default metadataContent;
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
```javascript fileName="src/app/[locale]/metadata.content.cjs" contentDeclarationFormat="commonjs"
|
|
910
|
+
const { t } = require("intlayer");
|
|
911
|
+
|
|
912
|
+
/** @type {import('intlayer').Dictionary<import('next').Metadata>} */
|
|
913
|
+
const metadataContent = {
|
|
914
|
+
key: "page-metadata",
|
|
915
|
+
content: {
|
|
916
|
+
title: t({
|
|
917
|
+
en: "Create Next App",
|
|
918
|
+
fr: "Créer une application Next.js",
|
|
919
|
+
es: "Crear una aplicación Next.js",
|
|
920
|
+
}),
|
|
921
|
+
description: t({
|
|
922
|
+
en: "Generated by create next app",
|
|
923
|
+
fr: "Généré par create next app",
|
|
924
|
+
es: "Generado por create next app",
|
|
925
|
+
}),
|
|
926
|
+
},
|
|
927
|
+
};
|
|
928
|
+
|
|
929
|
+
module.exports = metadataContent;
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
```json fileName="src/app/[locale]/metadata.content.json" contentDeclarationFormat="json"
|
|
933
|
+
{
|
|
934
|
+
"key": "page-metadata",
|
|
935
|
+
"content": {
|
|
936
|
+
"title": {
|
|
937
|
+
"nodeType": "translation",
|
|
938
|
+
"translation": {
|
|
939
|
+
"ja": "Preact ロゴ",
|
|
940
|
+
"en": "Preact logo",
|
|
941
|
+
"fr": "Logo Preact",
|
|
942
|
+
"es": "Logo Preact"
|
|
943
|
+
}
|
|
944
|
+
},
|
|
945
|
+
"description": {
|
|
946
|
+
"nodeType": "translation",
|
|
947
|
+
"translation": {
|
|
948
|
+
"ja": "create next app によって生成されました",
|
|
949
|
+
"en": "Generated by create next app",
|
|
950
|
+
"fr": "Généré par create next app",
|
|
951
|
+
"es": "Generado por create next app"
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
```
|
|
957
|
+
|
|
958
|
+
````typescript fileName="src/app/[locale]/layout.tsx or src/app/[locale]/page.tsx" codeFormat="typescript"
|
|
959
|
+
import { getIntlayer, getMultilingualUrls } from "intlayer";
|
|
960
|
+
import type { Metadata } from "next";
|
|
961
|
+
import type { LocalPromiseParams } from "next-intlayer";
|
|
962
|
+
|
|
963
|
+
export const generateMetadata = async ({
|
|
964
|
+
params,
|
|
965
|
+
}: LocalPromiseParams): Promise<Metadata> => {
|
|
966
|
+
const { locale } = await params;
|
|
967
|
+
|
|
968
|
+
const metadata = getIntlayer("page-metadata", locale);
|
|
969
|
+
|
|
970
|
+
/**
|
|
971
|
+
* 各ロケールのすべてのURLを含むオブジェクトを生成します。
|
|
972
|
+
*
|
|
973
|
+
* 例:
|
|
974
|
+
* ```ts
|
|
975
|
+
* getMultilingualUrls('/about');
|
|
976
|
+
*
|
|
977
|
+
* // 戻り値
|
|
978
|
+
* // {
|
|
979
|
+
* // en: '/about',
|
|
980
|
+
* // fr: '/fr/about',
|
|
981
|
+
* // es: '/es/about',
|
|
982
|
+
* // }
|
|
983
|
+
* ```
|
|
984
|
+
*/
|
|
985
|
+
const multilingualUrls = getMultilingualUrls("/");
|
|
986
|
+
|
|
987
|
+
return {
|
|
988
|
+
...metadata,
|
|
989
|
+
alternates: {
|
|
990
|
+
canonical: multilingualUrls[locale as keyof typeof multilingualUrls],
|
|
991
|
+
languages: { ...multilingualUrls, "x-default": "/" },
|
|
992
|
+
},
|
|
993
|
+
openGraph: {
|
|
994
|
+
url: multilingualUrls[locale],
|
|
995
|
+
},
|
|
996
|
+
};
|
|
997
|
+
};
|
|
998
|
+
|
|
999
|
+
// ... 残りのコード
|
|
1000
|
+
````
|
|
1001
|
+
|
|
1002
|
+
````javascript fileName="src/app/[locale]/layout.mjs or src/app/[locale]/page.mjs" codeFormat="esm"
|
|
1003
|
+
import { getIntlayer, getMultilingualUrls } from "intlayer";
|
|
1004
|
+
|
|
1005
|
+
export const generateMetadata = async ({ params }) => {
|
|
1006
|
+
const { locale } = await params;
|
|
1007
|
+
|
|
1008
|
+
const metadata = getIntlayer("page-metadata", locale);
|
|
1009
|
+
|
|
1010
|
+
/**
|
|
1011
|
+
* 各ロケールのすべてのURLを含むオブジェクトを生成します。
|
|
1012
|
+
*
|
|
1013
|
+
* 例:
|
|
1014
|
+
* ```ts
|
|
1015
|
+
* getMultilingualUrls('/about');
|
|
1016
|
+
*
|
|
1017
|
+
* // 戻り値
|
|
1018
|
+
* // {
|
|
1019
|
+
* // en: '/about',
|
|
1020
|
+
* // fr: '/fr/about',
|
|
1021
|
+
* // es: '/es/about'
|
|
1022
|
+
* // }
|
|
1023
|
+
* ```
|
|
1024
|
+
*/
|
|
1025
|
+
const multilingualUrls = getMultilingualUrls("/");
|
|
1026
|
+
|
|
1027
|
+
return {
|
|
1028
|
+
...metadata,
|
|
1029
|
+
alternates: {
|
|
1030
|
+
canonical: multilingualUrls[locale],
|
|
1031
|
+
languages: { ...multilingualUrls, "x-default": "/" },
|
|
1032
|
+
},
|
|
1033
|
+
openGraph: {
|
|
1034
|
+
url: multilingualUrls[locale],
|
|
1035
|
+
},
|
|
1036
|
+
};
|
|
1037
|
+
};
|
|
1038
|
+
|
|
1039
|
+
// ... 残りのコード
|
|
1040
|
+
````
|
|
1041
|
+
|
|
1042
|
+
````javascript fileName="src/app/[locale]/layout.cjs or src/app/[locale]/page.cjs" codeFormat="commonjs"
|
|
1043
|
+
const { getIntlayer, getMultilingualUrls } = require("intlayer");
|
|
1044
|
+
|
|
1045
|
+
const generateMetadata = async ({ params }) => {
|
|
1046
|
+
const { locale } = await params;
|
|
1047
|
+
|
|
1048
|
+
const metadata = getIntlayer("page-metadata", locale);
|
|
1049
|
+
|
|
1050
|
+
/**
|
|
1051
|
+
* 各ロケールのすべてのURLを含むオブジェクトを生成します。
|
|
1052
|
+
*
|
|
1053
|
+
* 例:
|
|
1054
|
+
* ```ts
|
|
1055
|
+
* getMultilingualUrls('/about');
|
|
1056
|
+
*
|
|
1057
|
+
* // 戻り値
|
|
1058
|
+
* // {
|
|
1059
|
+
* // en: '/about',
|
|
1060
|
+
* // fr: '/fr/about',
|
|
1061
|
+
* // es: '/es/about'
|
|
1062
|
+
* // }
|
|
1063
|
+
* ```
|
|
1064
|
+
*/
|
|
1065
|
+
const multilingualUrls = getMultilingualUrls("/");
|
|
1066
|
+
|
|
1067
|
+
return {
|
|
1068
|
+
...metadata,
|
|
1069
|
+
alternates: {
|
|
1070
|
+
canonical: multilingualUrls[locale],
|
|
1071
|
+
languages: { ...multilingualUrls, "x-default": "/" },
|
|
1072
|
+
},
|
|
1073
|
+
openGraph: {
|
|
1074
|
+
url: multilingualUrls[locale],
|
|
1075
|
+
},
|
|
1076
|
+
};
|
|
1077
|
+
};
|
|
1078
|
+
|
|
1079
|
+
module.exports = { generateMetadata };
|
|
1080
|
+
|
|
1081
|
+
// ... その他のコード
|
|
1082
|
+
````
|
|
1083
|
+
|
|
1084
|
+
> `next-intlayer` からインポートされた `getIntlayer` 関数は、コンテンツを `IntlayerNode` でラップして返し、ビジュアルエディタとの統合を可能にします。対照的に、`intlayer` からインポートされた `getIntlayer` 関数は、追加のプロパティなしで直接コンテンツを返します。
|
|
1085
|
+
|
|
1086
|
+
代わりに、`getTranslation` 関数を使用してメタデータを宣言することもできます。ただし、メタデータの翻訳を自動化し、コンテンツを外部化するためには、コンテンツ宣言ファイルを使用することが推奨されます。
|
|
1087
|
+
|
|
1088
|
+
````typescript fileName="src/app/[locale]/layout.tsx or src/app/[locale]/page.tsx" codeFormat="typescript"
|
|
1089
|
+
import {
|
|
1090
|
+
type IConfigLocales,
|
|
1091
|
+
getTranslation,
|
|
1092
|
+
getMultilingualUrls,
|
|
1093
|
+
} from "intlayer";
|
|
1094
|
+
import type { Metadata } from "next";
|
|
1095
|
+
import type { LocalPromiseParams } from "next-intlayer";
|
|
1096
|
+
|
|
1097
|
+
export const generateMetadata = async ({
|
|
1098
|
+
params,
|
|
1099
|
+
}: LocalPromiseParams): Promise<Metadata> => {
|
|
1100
|
+
const { locale } = await params;
|
|
1101
|
+
const t = <T>(content: IConfigLocales<T>) => getTranslation(content, locale);
|
|
1102
|
+
|
|
1103
|
+
return {
|
|
1104
|
+
title: t<string>({
|
|
1105
|
+
en: "My title",
|
|
1106
|
+
代わりに、`getTranslation` 関数を使用してメタデータを宣言することもできます。ただし、メタデータの翻訳を自動化し、コンテンツを外部化するためには、コンテンツ宣言ファイルを使用することを推奨します。
|
|
1107
|
+
|
|
1108
|
+
```typescript fileName="src/app/[locale]/layout.tsx or src/app/[locale]/page.tsx" codeFormat="typescript"
|
|
1109
|
+
import {
|
|
1110
|
+
type IConfigLocales,
|
|
1111
|
+
getTranslation,
|
|
1112
|
+
getMultilingualUrls,
|
|
1113
|
+
} from "intlayer";
|
|
1114
|
+
import type { Metadata } from "next";
|
|
1115
|
+
import type { LocalPromiseParams } from "next-intlayer";
|
|
1116
|
+
|
|
1117
|
+
export const generateMetadata = async ({
|
|
1118
|
+
params,
|
|
1119
|
+
}: LocalPromiseParams): Promise<Metadata> => {
|
|
1120
|
+
const { locale } = await params;
|
|
1121
|
+
const t = <T>(content: IConfigLocales<T>) => getTranslation(content, locale);
|
|
1122
|
+
|
|
1123
|
+
return {
|
|
1124
|
+
title: t<string>({
|
|
1125
|
+
en: "My title",
|
|
1126
|
+
fr: "Mon titre",
|
|
1127
|
+
es: "Mi título",
|
|
1128
|
+
}),
|
|
1129
|
+
description: t({
|
|
1130
|
+
en: "My description",
|
|
1131
|
+
fr: "Ma description",
|
|
1132
|
+
es: "Mi descripción",
|
|
1133
|
+
}),
|
|
1134
|
+
};
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1137
|
+
// ... 残りのコード
|
|
1138
|
+
````
|
|
1139
|
+
|
|
1140
|
+
```javascript fileName="src/app/[locale]/layout.mjs or src/app/[locale]/page.mjs" codeFormat="esm"
|
|
1141
|
+
import { getTranslation, getMultilingualUrls } from "intlayer";
|
|
1142
|
+
|
|
1143
|
+
export const generateMetadata = async ({ params }) => {
|
|
1144
|
+
const { locale } = await params;
|
|
1145
|
+
const t = (content) => getTranslation(content, locale);
|
|
1146
|
+
|
|
1147
|
+
return {
|
|
1148
|
+
title: t({
|
|
1149
|
+
en: "My title",
|
|
1150
|
+
fr: "Mon titre",
|
|
1151
|
+
es: "Mi título",
|
|
1152
|
+
}),
|
|
1153
|
+
description: t({
|
|
1154
|
+
en: "My description",
|
|
1155
|
+
fr: "Ma description",
|
|
1156
|
+
es: "Mi descripción",
|
|
1157
|
+
}),
|
|
1158
|
+
};
|
|
1159
|
+
};
|
|
1160
|
+
|
|
1161
|
+
// ... 残りのコード
|
|
1162
|
+
```
|
|
1163
|
+
|
|
1164
|
+
```javascript fileName="src/app/[locale]/layout.cjs or src/app/[locale]/page.cjs" codeFormat="commonjs"
|
|
1165
|
+
const { getTranslation, getMultilingualUrls } = require("intlayer");
|
|
1166
|
+
|
|
1167
|
+
const generateMetadata = async ({ params }) => {
|
|
1168
|
+
const { locale } = await params;
|
|
1169
|
+
|
|
1170
|
+
const t = (content) => getTranslation(content, locale);
|
|
1171
|
+
|
|
1172
|
+
return {
|
|
1173
|
+
title: t({
|
|
1174
|
+
en: "My title", // タイトル(英語)
|
|
1175
|
+
fr: "Mon titre",
|
|
1176
|
+
es: "Mi título",
|
|
1177
|
+
}),
|
|
1178
|
+
description: t({
|
|
1179
|
+
en: "My description", // 説明文(英語)
|
|
1180
|
+
fr: "Ma description",
|
|
1181
|
+
es: "Mi descripción",
|
|
1182
|
+
}),
|
|
1183
|
+
};
|
|
1184
|
+
};
|
|
1185
|
+
|
|
1186
|
+
module.exports = { generateMetadata };
|
|
1187
|
+
|
|
1188
|
+
// ... 残りのコード
|
|
1189
|
+
```
|
|
1190
|
+
|
|
1191
|
+
> メタデータの最適化について詳しくは、[公式Next.jsドキュメント](https://nextjs.org/docs/app/building-your-application/optimizing/metadata)をご覧ください。
|
|
1192
|
+
|
|
1193
|
+
### (オプション)ステップ9: sitemap.xml と robots.txt の多言語対応
|
|
1194
|
+
|
|
1195
|
+
`sitemap.xml` と `robots.txt` を多言語対応にするには、Intlayer が提供する `getMultilingualUrls` 関数を使用できます。この関数を使うことで、サイトマップ用の多言語 URL を生成できます。
|
|
1196
|
+
|
|
1197
|
+
```tsx fileName="src/app/sitemap.ts" codeFormat="typescript"
|
|
1198
|
+
import { getMultilingualUrls } from "intlayer";
|
|
1199
|
+
import type { MetadataRoute } from "next";
|
|
1200
|
+
|
|
1201
|
+
const sitemap = (): MetadataRoute.Sitemap => [
|
|
1202
|
+
{
|
|
1203
|
+
url: "https://example.com",
|
|
1204
|
+
alternates: {
|
|
1205
|
+
languages: { ...getMultilingualUrls("https://example.com") },
|
|
1206
|
+
},
|
|
1207
|
+
},
|
|
1208
|
+
{
|
|
1209
|
+
url: "https://example.com/login",
|
|
1210
|
+
alternates: {
|
|
1211
|
+
languages: { ...getMultilingualUrls("https://example.com/login") },
|
|
1212
|
+
},
|
|
1213
|
+
},
|
|
1214
|
+
{
|
|
1215
|
+
url: "https://example.com/register",
|
|
1216
|
+
alternates: {
|
|
1217
|
+
languages: { ...getMultilingualUrls("https://example.com/register") },
|
|
1218
|
+
},
|
|
1219
|
+
},
|
|
1220
|
+
];
|
|
1221
|
+
|
|
1222
|
+
export default sitemap;
|
|
1223
|
+
```
|
|
1224
|
+
|
|
1225
|
+
```jsx fileName="src/app/sitemap.mjx" codeFormat="esm"
|
|
1226
|
+
import { getMultilingualUrls } from "intlayer";
|
|
1227
|
+
|
|
1228
|
+
const sitemap = () => [
|
|
1229
|
+
{
|
|
1230
|
+
url: "https://example.com",
|
|
1231
|
+
alternates: {
|
|
1232
|
+
languages: { ...getMultilingualUrls("https://example.com") },
|
|
1233
|
+
},
|
|
1234
|
+
},
|
|
1235
|
+
{
|
|
1236
|
+
url: "https://example.com/login",
|
|
1237
|
+
alternates: {
|
|
1238
|
+
languages: { ...getMultilingualUrls("https://example.com/login") },
|
|
1239
|
+
},
|
|
1240
|
+
},
|
|
1241
|
+
{
|
|
1242
|
+
url: "https://example.com/register",
|
|
1243
|
+
alternates: {
|
|
1244
|
+
languages: { ...getMultilingualUrls("https://example.com/register") },
|
|
1245
|
+
},
|
|
1246
|
+
},
|
|
1247
|
+
];
|
|
1248
|
+
|
|
1249
|
+
export default sitemap;
|
|
1250
|
+
```
|
|
1251
|
+
|
|
1252
|
+
```jsx fileName="src/app/sitemap.csx" codeFormat="commonjs"
|
|
1253
|
+
const { getMultilingualUrls } = require("intlayer");
|
|
1254
|
+
|
|
1255
|
+
const sitemap = () => [
|
|
1256
|
+
{
|
|
1257
|
+
url: "https://example.com",
|
|
1258
|
+
alternates: {
|
|
1259
|
+
languages: { ...getMultilingualUrls("https://example.com") },
|
|
1260
|
+
},
|
|
1261
|
+
},
|
|
1262
|
+
{
|
|
1263
|
+
url: "https://example.com/login",
|
|
1264
|
+
alternates: {
|
|
1265
|
+
languages: { ...getMultilingualUrls("https://example.com/login") },
|
|
1266
|
+
},
|
|
1267
|
+
},
|
|
1268
|
+
{
|
|
1269
|
+
url: "https://example.com/register",
|
|
1270
|
+
alternates: {
|
|
1271
|
+
languages: { ...getMultilingualUrls("https://example.com/register") },
|
|
1272
|
+
},
|
|
1273
|
+
},
|
|
1274
|
+
];
|
|
1275
|
+
|
|
1276
|
+
module.exports = sitemap;
|
|
1277
|
+
```
|
|
1278
|
+
|
|
1279
|
+
```tsx fileName="src/app/robots.ts" codeFormat="typescript"
|
|
1280
|
+
import type { MetadataRoute } from "next";
|
|
1281
|
+
import { getMultilingualUrls } from "intlayer";
|
|
1282
|
+
|
|
1283
|
+
const getAllMultilingualUrls = (urls: string[]) =>
|
|
1284
|
+
urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);
|
|
1285
|
+
|
|
1286
|
+
// robots.txtのルールを定義
|
|
1287
|
+
const robots = (): MetadataRoute.Robots => ({
|
|
1288
|
+
rules: {
|
|
1289
|
+
userAgent: "*", // すべてのユーザーエージェントに適用
|
|
1290
|
+
allow: ["/"], // ルートパスは許可
|
|
1291
|
+
disallow: getAllMultilingualUrls(["/login", "/register"]), // ログインと登録ページはクロール禁止
|
|
1292
|
+
},
|
|
1293
|
+
host: "https://example.com",
|
|
1294
|
+
sitemap: `https://example.com/sitemap.xml`, // サイトマップのURL
|
|
1295
|
+
});
|
|
1296
|
+
|
|
1297
|
+
export default robots;
|
|
1298
|
+
```
|
|
1299
|
+
|
|
1300
|
+
```jsx fileName="src/app/robots.mjx" codeFormat="esm"
|
|
1301
|
+
import { getMultilingualUrls } from "intlayer";
|
|
1302
|
+
|
|
1303
|
+
const getAllMultilingualUrls = (urls) =>
|
|
1304
|
+
urls.flatMap((url) => Object.values(getMultilingualUrls(url)));
|
|
1305
|
+
|
|
1306
|
+
const robots = () => ({
|
|
1307
|
+
rules: {
|
|
1308
|
+
userAgent: "*",
|
|
1309
|
+
allow: ["/"],
|
|
1310
|
+
disallow: getAllMultilingualUrls(["/login", "/register"]),
|
|
1311
|
+
},
|
|
1312
|
+
host: "https://example.com",
|
|
1313
|
+
sitemap: `https://example.com/sitemap.xml`,
|
|
1314
|
+
});
|
|
1315
|
+
|
|
1316
|
+
export default robots;
|
|
1317
|
+
```
|
|
1318
|
+
|
|
1319
|
+
```jsx fileName="src/app/robots.csx" codeFormat="commonjs"
|
|
1320
|
+
const { getMultilingualUrls } = require("intlayer");
|
|
1321
|
+
|
|
1322
|
+
// すべての多言語URLを取得する関数
|
|
1323
|
+
const getAllMultilingualUrls = (urls) =>
|
|
1324
|
+
urls.flatMap((url) => Object.values(getMultilingualUrls(url)));
|
|
1325
|
+
|
|
1326
|
+
const robots = () => ({
|
|
1327
|
+
rules: {
|
|
1328
|
+
userAgent: "*",
|
|
1329
|
+
allow: ["/"],
|
|
1330
|
+
disallow: getAllMultilingualUrls(["/login", "/register"]), // ログインと登録ページをクロール禁止に設定
|
|
1331
|
+
},
|
|
1332
|
+
host: "https://example.com",
|
|
1333
|
+
sitemap: `https://example.com/sitemap.xml`,
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
module.exports = robots;
|
|
1337
|
+
```
|
|
1338
|
+
|
|
1339
|
+
> サイトマップの最適化については、[公式の 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)をご覧ください。
|
|
1340
|
+
|
|
1341
|
+
### (オプション)ステップ10:コンテンツの言語を変更する
|
|
1342
|
+
|
|
1343
|
+
Next.js でコンテンツの言語を変更するには、推奨される方法として `Link` コンポーネントを使用してユーザーを適切なローカライズされたページにリダイレクトする方法があります。`Link` コンポーネントはページのプリフェッチを可能にし、完全なページリロードを回避するのに役立ちます。
|
|
1344
|
+
|
|
1345
|
+
```tsx fileName="src/components/LocaleSwitcher.tsx" codeFormat="typescript"
|
|
1346
|
+
"use client";
|
|
1347
|
+
|
|
1348
|
+
import type { FC } from "react";
|
|
1349
|
+
import {
|
|
1350
|
+
Locales,
|
|
1351
|
+
getHTMLTextDir,
|
|
1352
|
+
getLocaleName,
|
|
1353
|
+
getLocalizedUrl,
|
|
1354
|
+
} from "intlayer";
|
|
1355
|
+
import { useLocale } from "next-intlayer";
|
|
1356
|
+
import Link from "next/link";
|
|
1357
|
+
|
|
1358
|
+
export const LocaleSwitcher: FC = () => {
|
|
1359
|
+
const { locale, pathWithoutLocale, availableLocales, setLocale } =
|
|
1360
|
+
useLocale();
|
|
1361
|
+
|
|
1362
|
+
return (
|
|
1363
|
+
<div>
|
|
1364
|
+
<button popoverTarget="localePopover">{getLocaleName(locale)}</button>
|
|
1365
|
+
<div id="localePopover" popover="auto">
|
|
1366
|
+
{availableLocales.map((localeItem) => (
|
|
1367
|
+
<Link
|
|
1368
|
+
href={getLocalizedUrl(pathWithoutLocale, localeItem)}
|
|
1369
|
+
key={localeItem}
|
|
1370
|
+
aria-current={locale === localeItem ? "page" : undefined}
|
|
1371
|
+
onClick={() => setLocale(localeItem)}
|
|
1372
|
+
replace // 「戻る」ブラウザボタンが前のページにリダイレクトすることを保証します
|
|
1373
|
+
>
|
|
1374
|
+
<span>
|
|
1375
|
+
{/* ロケール - 例: FR */}
|
|
1376
|
+
{localeItem}
|
|
1377
|
+
</span>
|
|
1378
|
+
<span>
|
|
1379
|
+
{/* 自身のロケールでの言語名 - 例: Français */}
|
|
1380
|
+
{getLocaleName(localeItem, locale)}
|
|
1381
|
+
</span>
|
|
1382
|
+
<span dir={getHTMLTextDir(localeItem)} lang={localeItem}>
|
|
1383
|
+
{/* 現在のロケールでの言語名 - 例: Francés(現在のロケールが Locales.SPANISH の場合) */}
|
|
1384
|
+
{getLocaleName(localeItem)}
|
|
1385
|
+
</span>
|
|
1386
|
+
<span dir="ltr" lang={Locales.ENGLISH}>
|
|
1387
|
+
{/* 英語での言語名 - 例: French */}
|
|
1388
|
+
{getLocaleName(localeItem, Locales.ENGLISH)}
|
|
1389
|
+
</span>
|
|
1390
|
+
</Link>
|
|
1391
|
+
))}
|
|
1392
|
+
</div>
|
|
1393
|
+
</div>
|
|
1394
|
+
);
|
|
1395
|
+
};
|
|
1396
|
+
```
|
|
1397
|
+
|
|
1398
|
+
```jsx fileName="src/components/LocaleSwitcher.msx" codeFormat="esm"
|
|
1399
|
+
"use client";
|
|
1400
|
+
|
|
1401
|
+
import {
|
|
1402
|
+
Locales,
|
|
1403
|
+
getHTMLTextDir,
|
|
1404
|
+
getLocaleName,
|
|
1405
|
+
getLocalizedUrl,
|
|
1406
|
+
} from "intlayer";
|
|
1407
|
+
import { useLocale } from "next-intlayer";
|
|
1408
|
+
import Link from "next/link";
|
|
1409
|
+
|
|
1410
|
+
export const LocaleSwitcher = () => {
|
|
1411
|
+
const { locale, pathWithoutLocale, availableLocales, setLocale } =
|
|
1412
|
+
useLocale();
|
|
1413
|
+
|
|
1414
|
+
return (
|
|
1415
|
+
<div>
|
|
1416
|
+
<button popoverTarget="localePopover">{getLocaleName(locale)}</button>
|
|
1417
|
+
<div id="localePopover" popover="auto">
|
|
1418
|
+
{availableLocales.map((localeItem) => (
|
|
1419
|
+
<Link
|
|
1420
|
+
href={getLocalizedUrl(pathWithoutLocale, localeItem)}
|
|
1421
|
+
key={localeItem}
|
|
1422
|
+
aria-current={locale === localeItem ? "page" : undefined}
|
|
1423
|
+
onClick={() => setLocale(localeItem)}
|
|
1424
|
+
replace // 「戻る」ブラウザボタンが前のページにリダイレクトされることを保証します
|
|
1425
|
+
>
|
|
1426
|
+
<span>
|
|
1427
|
+
{/* ロケール - 例: FR */}
|
|
1428
|
+
{localeItem}
|
|
1429
|
+
</span>
|
|
1430
|
+
<span>
|
|
1431
|
+
{/* 自身のロケールでの言語名 - 例: Français */}
|
|
1432
|
+
{getLocaleName(localeItem, locale)}
|
|
1433
|
+
</span>
|
|
1434
|
+
<span dir={getHTMLTextDir(localeItem)} lang={localeItem}>
|
|
1435
|
+
{/* 現在のロケールでの言語名 - 例: ロケールがLocales.SPANISHの場合のFrancés */}
|
|
1436
|
+
{getLocaleName(localeItem)}
|
|
1437
|
+
</span>
|
|
1438
|
+
<span dir="ltr" lang={Locales.ENGLISH}>
|
|
1439
|
+
{/* 英語での言語名 - 例: French */}
|
|
1440
|
+
{getLocaleName(localeItem, Locales.ENGLISH)}
|
|
1441
|
+
</span>
|
|
1442
|
+
</Link>
|
|
1443
|
+
))}
|
|
1444
|
+
</div>
|
|
1445
|
+
</div>
|
|
1446
|
+
);
|
|
1447
|
+
};
|
|
1448
|
+
```
|
|
1449
|
+
|
|
1450
|
+
```jsx fileName="src/components/LocaleSwitcher.csx" codeFormat="commonjs"
|
|
1451
|
+
"use client";
|
|
1452
|
+
|
|
1453
|
+
const {
|
|
1454
|
+
Locales,
|
|
1455
|
+
getHTMLTextDir,
|
|
1456
|
+
getLocaleName,
|
|
1457
|
+
getLocalizedUrl,
|
|
1458
|
+
} = require("intlayer");
|
|
1459
|
+
const { useLocale } = require("next-intlayer");
|
|
1460
|
+
const Link = require("next/link");
|
|
1461
|
+
|
|
1462
|
+
export const LocaleSwitcher = () => {
|
|
1463
|
+
const { locale, pathWithoutLocale, availableLocales, setLocale } =
|
|
1464
|
+
useLocale();
|
|
1465
|
+
|
|
1466
|
+
return (
|
|
1467
|
+
<div>
|
|
1468
|
+
<button popoverTarget="localePopover">{getLocaleName(locale)}</button>
|
|
1469
|
+
<div id="localePopover" popover="auto">
|
|
1470
|
+
{availableLocales.map((localeItem) => (
|
|
1471
|
+
<Link
|
|
1472
|
+
href={getLocalizedUrl(pathWithoutLocale, localeItem)}
|
|
1473
|
+
key={localeItem}
|
|
1474
|
+
aria-current={locale === localeItem ? "page" : undefined}
|
|
1475
|
+
onClick={() => setLocale(localeItem)}
|
|
1476
|
+
replace // 「戻る」ブラウザボタンが前のページにリダイレクトすることを保証します
|
|
1477
|
+
>
|
|
1478
|
+
<span>
|
|
1479
|
+
{/* ロケール - 例: FR */}
|
|
1480
|
+
{localeItem}
|
|
1481
|
+
</span>
|
|
1482
|
+
<span>
|
|
1483
|
+
{/* 自身のロケールでの言語名 - 例: Français */}
|
|
1484
|
+
{getLocaleName(localeItem, locale)}
|
|
1485
|
+
</span>
|
|
1486
|
+
<span dir={getHTMLTextDir(localeItem)} lang={localeItem}>
|
|
1487
|
+
{/* 現在のロケールでの言語名 - 例: Francés(現在のロケールが Locales.SPANISH の場合) */}
|
|
1488
|
+
{getLocaleName(localeItem)}
|
|
1489
|
+
</span>
|
|
1490
|
+
<span dir="ltr" lang={Locales.ENGLISH}>
|
|
1491
|
+
{/* 英語での言語名 - 例: French */}
|
|
1492
|
+
{getLocaleName(localeItem, Locales.ENGLISH)}
|
|
1493
|
+
</span>
|
|
1494
|
+
</Link>
|
|
1495
|
+
))}
|
|
1496
|
+
</div>
|
|
1497
|
+
</div>
|
|
1498
|
+
);
|
|
1499
|
+
};
|
|
1500
|
+
```
|
|
1501
|
+
|
|
1502
|
+
> 代替方法として、`useLocale` フックで提供される `setLocale` 関数を使用することもできます。この関数はページのプリフェッチを許可しません。詳細は [`useLocale` フックのドキュメント](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ja/packages/next-intlayer/useLocale.md) を参照してください。
|
|
1503
|
+
|
|
1504
|
+
> また、`onLocaleChange` オプションに関数を設定して、ロケールが変更されたときにカスタム関数をトリガーすることも可能です。
|
|
1505
|
+
|
|
1506
|
+
```tsx fileName="src/components/LocaleSwitcher.tsx"
|
|
1507
|
+
"use client";
|
|
1508
|
+
|
|
1509
|
+
import { useRouter } from "next/navigation";
|
|
1510
|
+
import { useLocale } from "next-intlayer";
|
|
1511
|
+
import { getLocalizedUrl } from "intlayer";
|
|
1512
|
+
|
|
1513
|
+
// ... コードの残りの部分
|
|
1514
|
+
|
|
1515
|
+
const router = useRouter();
|
|
1516
|
+
const { setLocale } = useLocale({
|
|
1517
|
+
onLocaleChange: (locale) => {
|
|
1518
|
+
router.push(getLocalizedUrl(pathWithoutLocale, locale));
|
|
1519
|
+
},
|
|
1520
|
+
});
|
|
1521
|
+
|
|
1522
|
+
return (
|
|
1523
|
+
<button onClick={() => setLocale(Locales.FRENCH)}>フランス語に変更</button>
|
|
1524
|
+
);
|
|
1525
|
+
```
|
|
1526
|
+
|
|
1527
|
+
> ドキュメント参照:
|
|
1528
|
+
>
|
|
1529
|
+
> - [`useLocale` フック](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ja/packages/next-intlayer/useLocale.md)
|
|
1530
|
+
> - [`getLocaleName` フック](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ja/packages/intlayer/getLocaleName.md)
|
|
1531
|
+
> - [`getLocalizedUrl` フック](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ja/packages/intlayer/getLocalizedUrl.md)
|
|
1532
|
+
> - [`getHTMLTextDir` フック](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ja/packages/intlayer/getHTMLTextDir.md)
|
|
1533
|
+
> - [`hrefLang` 属性](https://developers.google.com/search/docs/specialty/international/localized-versions?hl=fr)
|
|
1534
|
+
> - [`lang` 属性](https://developer.mozilla.org/ja/docs/Web/HTML/Global_attributes/lang)
|
|
1535
|
+
> - [`dir` 属性](https://developer.mozilla.org/ja/docs/Web/HTML/Global_attributes/dir)
|
|
1536
|
+
> - [`aria-current` 属性](https://developer.mozilla.org/ja/docs/Web/Accessibility/ARIA/Attributes/aria-current)
|
|
1537
|
+
|
|
1538
|
+
### (オプション)ステップ11:ローカライズされたリンクコンポーネントの作成
|
|
1539
|
+
|
|
1540
|
+
アプリケーションのナビゲーションが現在のロケールを尊重するようにするために、カスタムの `Link` コンポーネントを作成できます。このコンポーネントは内部のURLに自動的に現在の言語をプレフィックスとして付加します。例えば、フランス語ユーザーが「About」ページへのリンクをクリックすると、`/about` ではなく `/fr/about` にリダイレクトされます。
|
|
1541
|
+
|
|
1542
|
+
この動作は以下の理由で有用です:
|
|
1543
|
+
|
|
1544
|
+
- **SEOとユーザー体験**:ローカライズされたURLは検索エンジンが言語別のページを正しくインデックスし、ユーザーに好みの言語でコンテンツを提供するのに役立ちます。
|
|
1545
|
+
- **一貫性**:アプリケーション全体でローカライズされたリンクを使用することで、ナビゲーションが現在のロケール内に留まり、予期しない言語切り替えを防ぎます。
|
|
1546
|
+
- **保守性**: ローカリゼーションのロジックを単一のコンポーネントに集約することで、URLの管理が簡素化され、アプリケーションの成長に伴いコードベースの保守や拡張が容易になります。
|
|
1547
|
+
|
|
1548
|
+
以下は、TypeScriptで実装されたローカライズされた`Link`コンポーネントの例です。
|
|
1549
|
+
|
|
1550
|
+
```tsx fileName="src/components/Link.tsx" codeFormat="typescript"
|
|
1551
|
+
"use client";
|
|
1552
|
+
|
|
1553
|
+
import { getLocalizedUrl } from "intlayer";
|
|
1554
|
+
import NextLink, { type LinkProps as NextLinkProps } from "next/link";
|
|
1555
|
+
import { useLocale } from "next-intlayer";
|
|
1556
|
+
import type { PropsWithChildren, FC } from "react";
|
|
1557
|
+
|
|
1558
|
+
/**
|
|
1559
|
+
* 指定されたURLが外部リンクかどうかを判定するユーティリティ関数。
|
|
1560
|
+
* URLが http:// または https:// で始まる場合、外部リンクとみなされます。
|
|
1561
|
+
*/
|
|
1562
|
+
export const checkIsExternalLink = (href?: string): boolean =>
|
|
1563
|
+
/^https?:\/\//.test(href ?? "");
|
|
1564
|
+
|
|
1565
|
+
/**
|
|
1566
|
+
* 現在のロケールに基づいてhref属性を適応させるカスタムLinkコンポーネント。
|
|
1567
|
+
* 内部リンクの場合、`getLocalizedUrl`を使ってURLの先頭にロケールを付加します(例:/fr/about)。
|
|
1568
|
+
* これにより、ナビゲーションが同じロケールコンテキスト内に留まることを保証します。
|
|
1569
|
+
*/
|
|
1570
|
+
export const Link: FC<PropsWithChildren<NextLinkProps>> = ({
|
|
1571
|
+
href,
|
|
1572
|
+
children,
|
|
1573
|
+
...props
|
|
1574
|
+
}) => {
|
|
1575
|
+
const { locale } = useLocale();
|
|
1576
|
+
const isExternalLink = checkIsExternalLink(href.toString());
|
|
1577
|
+
|
|
1578
|
+
// リンクが内部リンクで有効なhrefが提供されている場合、ローカライズされたURLを取得します。
|
|
1579
|
+
const hrefI18n: NextLinkProps["href"] =
|
|
1580
|
+
href && !isExternalLink ? getLocalizedUrl(href.toString(), locale) : href;
|
|
1581
|
+
|
|
1582
|
+
return (
|
|
1583
|
+
<NextLink href={hrefI18n} {...props}>
|
|
1584
|
+
{children}
|
|
1585
|
+
</NextLink>
|
|
1586
|
+
);
|
|
1587
|
+
};
|
|
1588
|
+
```
|
|
1589
|
+
|
|
1590
|
+
```jsx fileName="src/components/Link.mjx" codeFormat="esm"
|
|
1591
|
+
"use client";
|
|
1592
|
+
|
|
1593
|
+
import { getLocalizedUrl } from "intlayer";
|
|
1594
|
+
import NextLink from "next/link";
|
|
1595
|
+
import { useLocale } from "next-intlayer";
|
|
1596
|
+
|
|
1597
|
+
/**
|
|
1598
|
+
* 与えられたURLが外部リンクかどうかをチェックするユーティリティ関数。
|
|
1599
|
+
* URLが http:// または https:// で始まる場合、外部リンクと見なされます。
|
|
1600
|
+
*/
|
|
1601
|
+
export const checkIsExternalLink = (href) => /^https?:\/\//.test(href ?? "");
|
|
1602
|
+
|
|
1603
|
+
/**
|
|
1604
|
+
* 現在のロケールに基づいて href 属性を適応させるカスタム Link コンポーネント。
|
|
1605
|
+
* 内部リンクの場合、`getLocalizedUrl` を使用してURLにロケールのプレフィックスを付けます(例:/fr/about)。
|
|
1606
|
+
* これにより、ナビゲーションが同じロケールのコンテキスト内に留まることを保証します。
|
|
1607
|
+
*/
|
|
1608
|
+
export const Link = ({ href, children, ...props }) => {
|
|
1609
|
+
const { locale } = useLocale();
|
|
1610
|
+
const isExternalLink = checkIsExternalLink(href.toString());
|
|
1611
|
+
|
|
1612
|
+
// リンクが内部リンクで有効なhrefが提供されている場合、ローカライズされたURLを取得します。
|
|
1613
|
+
const hrefI18n =
|
|
1614
|
+
href && !isExternalLink ? getLocalizedUrl(href.toString(), locale) : href;
|
|
1615
|
+
|
|
1616
|
+
return (
|
|
1617
|
+
<NextLink href={hrefI18n} {...props}>
|
|
1618
|
+
{children}
|
|
1619
|
+
</NextLink>
|
|
1620
|
+
);
|
|
1621
|
+
};
|
|
1622
|
+
```
|
|
1623
|
+
|
|
1624
|
+
```jsx fileName="src/components/Link.csx" codeFormat="commonjs"
|
|
1625
|
+
"use client";
|
|
1626
|
+
|
|
1627
|
+
const { getLocalizedUrl } = require("intlayer");
|
|
1628
|
+
const NextLink = require("next/link");
|
|
1629
|
+
const { useLocale } = require("next-intlayer");
|
|
1630
|
+
|
|
1631
|
+
/**
|
|
1632
|
+
* 指定されたURLが外部リンクかどうかをチェックするユーティリティ関数。
|
|
1633
|
+
* URLが http:// または https:// で始まる場合、外部リンクとみなされます。
|
|
1634
|
+
*/
|
|
1635
|
+
const checkIsExternalLink = (href) => /^https?:\/\//.test(href ?? "");
|
|
1636
|
+
|
|
1637
|
+
/**
|
|
1638
|
+
* 現在のロケールに基づいて href 属性を適応させるカスタム Link コンポーネントです。
|
|
1639
|
+
* 内部リンクの場合、`getLocalizedUrl` を使用して URL にロケールをプレフィックスします(例:/fr/about)。
|
|
1640
|
+
* これにより、ナビゲーションが同じロケールコンテキスト内に留まることが保証されます。
|
|
1641
|
+
*/
|
|
1642
|
+
const Link = ({ href, children, ...props }) => {
|
|
1643
|
+
const { locale } = useLocale();
|
|
1644
|
+
const isExternalLink = checkIsExternalLink(href.toString());
|
|
1645
|
+
|
|
1646
|
+
// リンクが内部リンクで有効な href が提供されている場合、ローカライズされた URL を取得します。
|
|
1647
|
+
const hrefI18n =
|
|
1648
|
+
href && !isExternalLink ? getLocalizedUrl(href.toString(), locale) : href;
|
|
1649
|
+
|
|
1650
|
+
return (
|
|
1651
|
+
<NextLink href={hrefI18n} {...props}>
|
|
1652
|
+
{children}
|
|
1653
|
+
</NextLink>
|
|
1654
|
+
);
|
|
1655
|
+
};
|
|
1656
|
+
```
|
|
1657
|
+
|
|
1658
|
+
#### 動作の仕組み
|
|
1659
|
+
|
|
1660
|
+
- **外部リンクの検出**:
|
|
1661
|
+
- **外部リンクの検出**:
|
|
1662
|
+
ヘルパー関数 `checkIsExternalLink` は、URLが外部リンクかどうかを判定します。外部リンクはローカライズの必要がないため、そのまま変更されません。
|
|
1663
|
+
|
|
1664
|
+
- **現在のロケールの取得**:
|
|
1665
|
+
`useLocale` フックは現在のロケール(例: フランス語の場合は `fr`)を提供します。
|
|
1666
|
+
|
|
1667
|
+
- **URLのローカライズ**:
|
|
1668
|
+
内部リンク(すなわち外部リンクでない場合)には、`getLocalizedUrl` を使用してURLの先頭に現在のロケールを自動的に付加します。つまり、ユーザーがフランス語環境にいる場合、`href` に `/about` を渡すと `/fr/about` に変換されます。
|
|
1669
|
+
|
|
1670
|
+
- **リンクの返却**:
|
|
1671
|
+
コンポーネントはローカライズされたURLを持つ `<a>` 要素を返し、ナビゲーションがロケールに沿って一貫するようにします。
|
|
1672
|
+
|
|
1673
|
+
この `Link` コンポーネントをアプリケーション全体に統合することで、一貫性のある言語対応のユーザー体験を維持しつつ、SEOや使いやすさの向上も期待できます。
|
|
1674
|
+
|
|
1675
|
+
### (オプション)ステップ12:サーバーアクション内で現在のロケールを取得する
|
|
1676
|
+
|
|
1677
|
+
サーバーアクション内でアクティブなロケールが必要な場合(例:メールのローカライズやロケールに応じたロジックの実行)、`next-intlayer/server` から `getLocale` を呼び出します。
|
|
1678
|
+
|
|
1679
|
+
```tsx fileName="src/app/actions/getLocale.ts" codeFormat="typescript"
|
|
1680
|
+
"use server";
|
|
1681
|
+
|
|
1682
|
+
import { getLocale } from "next-intlayer/server";
|
|
1683
|
+
|
|
1684
|
+
export const myServerAction = async () => {
|
|
1685
|
+
const locale = await getLocale();
|
|
1686
|
+
|
|
1687
|
+
// ロケールを使って何か処理を行う
|
|
1688
|
+
};
|
|
1689
|
+
```
|
|
1690
|
+
|
|
1691
|
+
> `getLocale` 関数はユーザーのロケールを決定するためにカスケード戦略を採用しています:
|
|
1692
|
+
>
|
|
1693
|
+
> 1. 最初に、プロキシによって設定されている可能性のあるロケール値をリクエストヘッダーで確認します
|
|
1694
|
+
> 2. ヘッダーにロケールが見つからない場合は、クッキーに保存されているロケールを探します
|
|
1695
|
+
> 3. クッキーも見つからない場合は、ブラウザの設定からユーザーの優先言語を検出しようとします
|
|
1696
|
+
> 4. 最後の手段として、アプリケーションで設定されたデフォルトのロケールにフォールバックします
|
|
1697
|
+
>
|
|
1698
|
+
> これにより、利用可能なコンテキストに基づいて最も適切なロケールが選択されることが保証されます。
|
|
1699
|
+
|
|
1700
|
+
### (オプション)ステップ 13: バンドルサイズの最適化
|
|
1701
|
+
|
|
1702
|
+
`next-intlayer`を使用する場合、辞書はデフォルトで全てのページのバンドルに含まれます。バンドルサイズを最適化するために、Intlayerはマクロを使用して`useIntlayer`の呼び出しを賢く置き換えるオプションのSWCプラグインを提供しています。これにより、実際に辞書を使用するページのバンドルにのみ辞書が含まれるようになります。
|
|
1703
|
+
|
|
1704
|
+
この最適化を有効にするには、`@intlayer/swc`パッケージをインストールしてください。インストール後、`next-intlayer`は自動的にプラグインを検出して使用します。
|
|
1705
|
+
|
|
1706
|
+
```bash packageManager="npm"
|
|
1707
|
+
npm install @intlayer/swc --save-dev
|
|
1708
|
+
```
|
|
1709
|
+
|
|
1710
|
+
```bash packageManager="pnpm"
|
|
1711
|
+
pnpm add @intlayer/swc --save-dev
|
|
1712
|
+
```
|
|
1713
|
+
|
|
1714
|
+
```bash packageManager="yarn"
|
|
1715
|
+
yarn add @intlayer/swc --save-dev
|
|
1716
|
+
```
|
|
1717
|
+
|
|
1718
|
+
> 注意: この最適化はNext.js 13以降でのみ利用可能です。
|
|
1719
|
+
|
|
1720
|
+
> 注意: このパッケージは、Next.jsでのSWCプラグインがまだ実験的であるため、デフォルトではインストールされていません。将来的に変更される可能性があります。
|
|
1721
|
+
|
|
1722
|
+
### Turbopackで辞書の変更を監視する
|
|
1723
|
+
|
|
1724
|
+
`next dev`コマンドで開発サーバーとしてTurbopackを使用している場合、辞書の変更はデフォルトで自動的に検出されません。
|
|
1725
|
+
|
|
1726
|
+
この制限は、Turbopackがコンテンツファイルの変更を監視するためにwebpackプラグインを並行して実行できないために発生します。これを回避するには、`intlayer watch`コマンドを使用して、開発サーバーとIntlayerのビルドウォッチャーを同時に実行する必要があります。
|
|
1727
|
+
|
|
1728
|
+
```json5 fileName="package.json"
|
|
1729
|
+
{
|
|
1730
|
+
// ... 既存のpackage.jsonの設定
|
|
1731
|
+
"scripts": {
|
|
1732
|
+
// ... 既存のスクリプト設定
|
|
1733
|
+
"dev": "intlayer watch --with 'next dev'",
|
|
1734
|
+
},
|
|
1735
|
+
}
|
|
1736
|
+
```
|
|
1737
|
+
|
|
1738
|
+
> next-intlayer@<=6.x.xを使用している場合、Next.js 16アプリケーションをTurbopackで正しく動作させるために`--turbopack`フラグを保持する必要があります。この制限を回避するために、next-intlayer@>=7.x.xの使用を推奨します。
|
|
1739
|
+
|
|
1740
|
+
### TypeScriptの設定
|
|
1741
|
+
|
|
1742
|
+
Intlayerはモジュール拡張を使用してTypeScriptの利点を活かし、コードベースをより強固にします。
|
|
1743
|
+
|
|
1744
|
+

|
|
1745
|
+
|
|
1746
|
+

|
|
1747
|
+
|
|
1748
|
+
TypeScriptの設定に自動生成された型が含まれていることを確認してください。
|
|
1749
|
+
|
|
1750
|
+
```json5 fileName="tsconfig.json"
|
|
1751
|
+
{
|
|
1752
|
+
// ... 既存のTypeScript設定
|
|
1753
|
+
"include": [
|
|
1754
|
+
// ... 既存のTypeScript設定
|
|
1755
|
+
".intlayer/**/*.ts", // 自動生成された型を含める
|
|
1756
|
+
],
|
|
1757
|
+
}
|
|
1758
|
+
```
|
|
1759
|
+
|
|
1760
|
+
### Gitの設定
|
|
1761
|
+
|
|
1762
|
+
Intlayerによって生成されたファイルは無視することを推奨します。これにより、Gitリポジトリへの不要なコミットを避けることができます。
|
|
1763
|
+
|
|
1764
|
+
これを行うには、`.gitignore`ファイルに以下の指示を追加してください。
|
|
1765
|
+
|
|
1766
|
+
```plaintext fileName=".gitignore"
|
|
1767
|
+
# Intlayerによって生成されたファイルを無視する
|
|
1768
|
+
.intlayer
|
|
1769
|
+
```
|
|
1770
|
+
|
|
1771
|
+
### VS Code拡張機能
|
|
1772
|
+
|
|
1773
|
+
Intlayerでの開発体験を向上させるために、公式の**Intlayer VS Code拡張機能**をインストールできます。
|
|
1774
|
+
|
|
1775
|
+
[VS Codeマーケットプレイスからインストール](https://marketplace.visualstudio.com/items?itemName=intlayer.intlayer-vs-code-extension)
|
|
1776
|
+
|
|
1777
|
+
この拡張機能は以下を提供します:
|
|
1778
|
+
|
|
1779
|
+
- 翻訳キーの**オートコンプリート**。
|
|
1780
|
+
- 欠落している翻訳の**リアルタイムエラー検出**。
|
|
1781
|
+
- 翻訳されたコンテンツの**インラインプレビュー**。
|
|
1782
|
+
- 翻訳を簡単に作成および更新するための**クイックアクション**。
|
|
1783
|
+
|
|
1784
|
+
拡張機能の使い方の詳細については、[Intlayer VS Code Extension ドキュメント](https://intlayer.org/doc/vs-code-extension)を参照してください。
|
|
1785
|
+
|
|
1786
|
+
### さらに進むために
|
|
1787
|
+
|
|
1788
|
+
さらに進むには、[ビジュアルエディター](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ja/intlayer_visual_editor.md)を実装するか、[CMS](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ja/intlayer_CMS.md)を使用してコンテンツを外部化することができます。
|