@intlayer/docs 6.1.5 → 6.1.6-canary.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 (53) hide show
  1. package/blog/ar/next-i18next_vs_next-intl_vs_intlayer.md +404 -173
  2. package/blog/de/next-i18next_vs_next-intl_vs_intlayer.md +262 -113
  3. package/blog/en/intlayer_with_next-i18next.mdx +431 -0
  4. package/blog/en/intlayer_with_next-intl.mdx +335 -0
  5. package/blog/en/next-i18next_vs_next-intl_vs_intlayer.md +403 -140
  6. package/blog/en-GB/next-i18next_vs_next-intl_vs_intlayer.md +38 -28
  7. package/blog/es/next-i18next_vs_next-intl_vs_intlayer.md +185 -71
  8. package/blog/fr/next-i18next_vs_next-intl_vs_intlayer.md +38 -28
  9. package/blog/it/next-i18next_vs_next-intl_vs_intlayer.md +38 -28
  10. package/blog/ja/next-i18next_vs_next-intl_vs_intlayer.md +38 -28
  11. package/blog/ko/next-i18next_vs_next-intl_vs_intlayer.md +38 -28
  12. package/blog/pt/next-i18next_vs_next-intl_vs_intlayer.md +38 -28
  13. package/blog/ru/next-i18next_vs_next-intl_vs_intlayer.md +36 -28
  14. package/blog/tr/next-i18next_vs_next-intl_vs_intlayer.md +2 -0
  15. package/blog/zh/next-i18next_vs_next-intl_vs_intlayer.md +38 -28
  16. package/dist/cjs/generated/docs.entry.cjs +16 -0
  17. package/dist/cjs/generated/docs.entry.cjs.map +1 -1
  18. package/dist/esm/generated/docs.entry.mjs +16 -0
  19. package/dist/esm/generated/docs.entry.mjs.map +1 -1
  20. package/dist/types/generated/docs.entry.d.ts +1 -0
  21. package/dist/types/generated/docs.entry.d.ts.map +1 -1
  22. package/docs/ar/component_i18n.md +186 -0
  23. package/docs/ar/vs_code_extension.md +48 -109
  24. package/docs/de/component_i18n.md +186 -0
  25. package/docs/de/vs_code_extension.md +46 -107
  26. package/docs/en/component_i18n.md +186 -0
  27. package/docs/en/intlayer_with_nextjs_14.md +18 -1
  28. package/docs/en/intlayer_with_nextjs_15.md +18 -1
  29. package/docs/en/vs_code_extension.md +24 -114
  30. package/docs/en-GB/component_i18n.md +186 -0
  31. package/docs/en-GB/vs_code_extension.md +42 -103
  32. package/docs/es/component_i18n.md +182 -0
  33. package/docs/es/vs_code_extension.md +53 -114
  34. package/docs/fr/component_i18n.md +186 -0
  35. package/docs/fr/vs_code_extension.md +50 -111
  36. package/docs/hi/component_i18n.md +186 -0
  37. package/docs/hi/vs_code_extension.md +49 -110
  38. package/docs/it/component_i18n.md +186 -0
  39. package/docs/it/vs_code_extension.md +50 -111
  40. package/docs/ja/component_i18n.md +186 -0
  41. package/docs/ja/vs_code_extension.md +50 -111
  42. package/docs/ko/component_i18n.md +186 -0
  43. package/docs/ko/vs_code_extension.md +48 -109
  44. package/docs/pt/component_i18n.md +186 -0
  45. package/docs/pt/vs_code_extension.md +46 -107
  46. package/docs/ru/component_i18n.md +186 -0
  47. package/docs/ru/vs_code_extension.md +48 -109
  48. package/docs/tr/component_i18n.md +186 -0
  49. package/docs/tr/vs_code_extension.md +54 -115
  50. package/docs/zh/component_i18n.md +186 -0
  51. package/docs/zh/vs_code_extension.md +51 -105
  52. package/package.json +11 -11
  53. package/src/generated/docs.entry.ts +16 -0
@@ -0,0 +1,431 @@
1
+ ```bash
2
+ .
3
+ ├── i18n.config.ts
4
+ └── src
5
+ ├── locales
6
+ │ ├── en
7
+ │ │ ├── common.json
8
+ │ │ └── about.json
9
+ │ └── fr
10
+ │ ├── common.json
11
+ │ └── about.json
12
+ ├── app
13
+ │ ├── i18n
14
+ │ │ └── server.ts
15
+ │ └── [locale]
16
+ │ ├── layout.tsx
17
+ │ └── about.tsx
18
+ └── components
19
+ ├── I18nProvider.tsx
20
+ ├── ClientComponent.tsx
21
+ └── ServerComponent.tsx
22
+ ```
23
+
24
+ ```ts fileName="i18n.config.ts"
25
+ export const locales = ['en', 'fr'] as const;
26
+ export type Locale = (typeof locales)[number];
27
+
28
+ export const defaultLocale: Locale = 'en';
29
+
30
+ export const rtlLocales = ['ar', 'he', 'fa', 'ur'] as const;
31
+ export const isRtl = (locale: string) =>
32
+ (rtlLocales as readonly string[]).includes(locale);
33
+
34
+ export function localizedPath(locale: string, path: string) {
35
+ return locale === defaultLocale ? path : '/' + locale + path;
36
+ }
37
+
38
+ const ORIGIN = 'https://example.com';
39
+ export function abs(locale: string, path: string) {
40
+ return ORIGIN + localizedPath(locale, path);
41
+ }
42
+ ```
43
+
44
+ ```ts fileName="src/app/i18n/server.ts"
45
+ import { createInstance } from 'i18next';
46
+ import { initReactI18next } from 'react-i18next/initReactI18next';
47
+ import resourcesToBackend from 'i18next-resources-to-backend';
48
+ import { defaultLocale } from '@/i18n.config';
49
+
50
+ // Load JSON resources from src/locales/<locale>/<namespace>.json
51
+ const backend = resourcesToBackend(
52
+ (locale: string, namespace: string) =>
53
+ import(`../../locales/${locale}/${namespace}.json`)
54
+ );
55
+
56
+ export async function initI18next(
57
+ locale: string,
58
+ namespaces: string[] = ['common']
59
+ ) {
60
+ const i18n = createInstance();
61
+ await i18n
62
+ .use(initReactI18next)
63
+ .use(backend)
64
+ .init({
65
+ lng: locale,
66
+ fallbackLng: defaultLocale,
67
+ ns: namespaces,
68
+ defaultNS: 'common',
69
+ interpolation: { escapeValue: false },
70
+ react: { useSuspense: false },
71
+ });
72
+ return i18n;
73
+ }
74
+ ```
75
+
76
+ ```tsx fileName="src/components/I18nProvider.tsx"
77
+ 'use client';
78
+
79
+ import * as React from 'react';
80
+ import { I18nextProvider } from 'react-i18next';
81
+ import { createInstance } from 'i18next';
82
+ import { initReactI18next } from 'react-i18next/initReactI18next';
83
+ import resourcesToBackend from 'i18next-resources-to-backend';
84
+ import { defaultLocale } from '@/i18n.config';
85
+
86
+ const backend = resourcesToBackend(
87
+ (locale: string, namespace: string) =>
88
+ import(`../../locales/${locale}/${namespace}.json`)
89
+ );
90
+
91
+ type Props = {
92
+ locale: string;
93
+ namespaces?: string[];
94
+ resources?: Record<string, any>; // { ns: bundle }
95
+ children: React.ReactNode;
96
+ };
97
+
98
+ export default function I18nProvider({
99
+ locale,
100
+ namespaces = ['common'],
101
+ resources,
102
+ children,
103
+ }: Props) {
104
+ const [i18n] = React.useState(() => {
105
+ const i = createInstance();
106
+
107
+ i.use(initReactI18next)
108
+ .use(backend)
109
+ .init({
110
+ lng: locale,
111
+ fallbackLng: defaultLocale,
112
+ ns: namespaces,
113
+ resources: resources ? { [locale]: resources } : undefined,
114
+ defaultNS: 'common',
115
+ interpolation: { escapeValue: false },
116
+ react: { useSuspense: false },
117
+ });
118
+
119
+ return i;
120
+ });
121
+
122
+ return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>;
123
+ }
124
+ ```
125
+
126
+ ```tsx fileName="src/app/[locale]/layout.tsx"
127
+ import type { ReactNode } from 'react';
128
+ import { locales, defaultLocale, isRtl, type Locale } from '@/i18n.config';
129
+
130
+ export const dynamicParams = false;
131
+
132
+ export function generateStaticParams() {
133
+ return locales.map((locale) => ({ locale }));
134
+ }
135
+
136
+ export default function LocaleLayout({
137
+ children,
138
+ params,
139
+ }: {
140
+ children: ReactNode;
141
+ params: { locale: string };
142
+ }) {
143
+ const locale: Locale = (locales as readonly string[]).includes(params.locale)
144
+ ? (params.locale as any)
145
+ : defaultLocale;
146
+
147
+ const dir = isRtl(locale) ? 'rtl' : 'ltr';
148
+
149
+ return (
150
+ <html lang={locale} dir={dir}>
151
+ <body>{children}</body>
152
+ </html>
153
+ );
154
+ }
155
+ ```
156
+
157
+ ```tsx fileName="src/app/[locale]/about.tsx"
158
+ import I18nProvider from '@/components/I18nProvider';
159
+ import { initI18next } from '@/app/i18n/server';
160
+ import type { Locale } from '@/i18n.config';
161
+ import ClientComponent from '@/components/ClientComponent';
162
+ import ServerComponent from '@/components/ServerComponent';
163
+
164
+ // Force static rendering for the page
165
+ export const dynamic = 'force-static';
166
+
167
+ export default async function AboutPage({
168
+ params: { locale },
169
+ }: {
170
+ params: { locale: Locale };
171
+ }) {
172
+ const namespaces = ['common', 'about'] as const;
173
+
174
+ const i18n = await initI18next(locale, [...namespaces]);
175
+ const tAbout = i18n.getFixedT(locale, 'about');
176
+
177
+ return (
178
+ <I18nProvider locale={locale} namespaces={[...namespaces]}>
179
+ <main>
180
+ <h1>{tAbout('title')}</h1>
181
+
182
+ <ClientComponent />
183
+ <ServerComponent t={tAbout} locale={locale} count={0} />
184
+ </main>
185
+ </I18nProvider>
186
+ );
187
+ }
188
+ ```
189
+
190
+ **Translations (one JSON per namespace under `src/locales/...`)**
191
+
192
+ ```json fileName="src/locales/en/about.json"
193
+ {
194
+ "title": "About",
195
+ "description": "About page description",
196
+ "counter": {
197
+ "label": "Counter",
198
+ "increment": "Increment"
199
+ }
200
+ }
201
+ ```
202
+
203
+ ```json fileName="src/locales/fr/about.json"
204
+ {
205
+ "title": "À propos",
206
+ "description": "Description de la page À propos",
207
+ "counter": {
208
+ "label": "Compteur",
209
+ "increment": "Incrémenter"
210
+ }
211
+ }
212
+ ```
213
+
214
+ **Client component (loads only the required namespace)**
215
+
216
+ ```tsx fileName="src/components/ClientComponent.tsx"
217
+ 'use client';
218
+
219
+ import React, { useState } from 'react';
220
+ import { useTranslation } from 'react-i18next';
221
+
222
+ const ClientComponent = () => {
223
+ const { t, i18n } = useTranslation('about');
224
+ const [count, setCount] = useState(0);
225
+
226
+ const numberFormat = new Intl.NumberFormat(i18n.language);
227
+
228
+ return (
229
+ <div>
230
+ <p>{numberFormat.format(count)}</p>
231
+ <button
232
+ aria-label={t('counter.label')}
233
+ onClick={() => setCount((c) => c + 1)}
234
+ >
235
+ {t('counter.increment')}
236
+ </button>
237
+ </div>
238
+ );
239
+ };
240
+
241
+ export default ClientComponent;
242
+ ```
243
+
244
+ > Ensure the page/provider includes only the namespaces you need (e.g. `about`).
245
+ > If you use React < 19, memoize heavy formatters like `Intl.NumberFormat`.
246
+
247
+ ---
248
+
249
+ ### Usage in a sync server component
250
+
251
+ This server component can be inserted under a client component. Because it might be nested under a client boundary, it should be synchronous. Compute translations and locale-dependent formatting in the parent (or pass the `t` function) and provide data via props.
252
+
253
+ ```tsx fileName="src/components/ServerComponent.tsx"
254
+ type ServerComponentProps = {
255
+ t: (key: string) => string;
256
+ locale: string;
257
+ count: number;
258
+ };
259
+
260
+ const ServerComponent = ({ t, locale, count }: ServerComponentProps) => {
261
+ const formatted = new Intl.NumberFormat(locale).format(count);
262
+
263
+ return (
264
+ <div>
265
+ <p>{formatted}</p>
266
+ <button aria-label={t('counter.label')}>{t('counter.increment')}</button>
267
+ </div>
268
+ );
269
+ };
270
+
271
+ export default ServerComponent;
272
+ ```
273
+
274
+ ### Metadata / Sitemap / Robots
275
+
276
+ Translating content is great. But people usually forget that the main goal of internationalization is to make your website more visible to the world. I18n is an incredible lever to improve your website visibility.
277
+
278
+ Here's a list of good practices regarding multilingual SEO.
279
+
280
+ - set hreflang meta tags in the `<head>` tag
281
+ > It helps search engines to understand what languages are available on the page
282
+ - list all pages translations in the sitemap.xml using `http://www.w3.org/1999/xhtml` XML schema
283
+ >
284
+ - do not forget to exclude prefixed pages from the robots.txt (e.g. `/dashboard`, and `/fr/dashboard`, `/es/dashboard`)
285
+ >
286
+ - use custom Link component to redirect to the most localized page (e.g. in french `<a href="/fr/about">A propos</a>` )
287
+ >
288
+
289
+ Developers often forget to properly reference their pages across locales.
290
+
291
+ ```ts fileName="i18n.config.ts"
292
+ export const locales = ['en', 'fr'] as const;
293
+ export type Locale = (typeof locales)[number];
294
+ export const defaultLocale: Locale = 'en';
295
+ export const rtlLocales = ['ar', 'he', 'fa', 'ur'] as const;
296
+ export const isRtl = (locale: string) =>
297
+ (rtlLocales as readonly string[]).includes(locale);
298
+ export function localizedPath(locale: string, path: string) {
299
+ return locale === defaultLocale ? path : '/' + locale + path;
300
+ }
301
+ const ORIGIN = 'https://example.com';
302
+ export function abs(locale: string, path: string) {
303
+ return ORIGIN + localizedPath(locale, path);
304
+ }
305
+ ```
306
+
307
+ ```tsx fileName="src/app/[locale]/about/layout.tsx"
308
+ import type { Metadata } from 'next';
309
+ import { locales, defaultLocale, localizedPath } from '@/i18n.config';
310
+
311
+ export async function generateMetadata({
312
+ params,
313
+ }: {
314
+ params: { locale: string };
315
+ }): Promise<Metadata> {
316
+ const { locale } = params;
317
+
318
+ // Import the correct JSON bundle from src/locales
319
+ const messages = (await import('@/locales/' + locale + '/about.json'))
320
+ .default;
321
+
322
+ const languages = Object.fromEntries(
323
+ locales.map((locale) => [locale, localizedPath(locale, '/about')])
324
+ );
325
+
326
+ return {
327
+ title: messages.title,
328
+ description: messages.description,
329
+ alternates: {
330
+ canonical: localizedPath(locale, '/about'),
331
+ languages: { ...languages, 'x-default': '/about' },
332
+ },
333
+ };
334
+ }
335
+
336
+ export default async function AboutPage() {
337
+ return <h1>About</h1>;
338
+ }
339
+ ```
340
+
341
+ ```ts fileName="src/app/sitemap.ts"
342
+ import type { MetadataRoute } from 'next';
343
+ import { locales, defaultLocale, abs } from '@/i18n.config';
344
+
345
+ export default function sitemap(): MetadataRoute.Sitemap {
346
+ const languages = Object.fromEntries(
347
+ locales.map((locale) => [locale, abs(locale, '/about')])
348
+ );
349
+
350
+ return [
351
+ {
352
+ url: abs(defaultLocale, '/about'),
353
+ lastModified: new Date(),
354
+ changeFrequency: 'monthly',
355
+ priority: 0.7,
356
+ alternates: { languages },
357
+ },
358
+ ];
359
+ }
360
+ ```
361
+
362
+ ```ts fileName="src/app/robots.ts"
363
+ import type { MetadataRoute } from 'next';
364
+ import { locales, defaultLocale, localizedPath } from '@/i18n.config';
365
+
366
+ const ORIGIN = 'https://example.com';
367
+
368
+ const expandAllLocales = (path: string) => [
369
+ localizedPath(defaultLocale, path),
370
+ ...locales
371
+ .filter((locale) => locale !== defaultLocale)
372
+ .map((locale) => localizedPath(locale, path)),
373
+ ];
374
+
375
+ export default function robots(): MetadataRoute.Robots {
376
+ const disallow = [
377
+ ...expandAllLocales('/dashboard'),
378
+ ...expandAllLocales('/admin'),
379
+ ];
380
+
381
+ return {
382
+ rules: { userAgent: '*', allow: ['/'], disallow },
383
+ host: ORIGIN,
384
+ sitemap: ORIGIN + '/sitemap.xml',
385
+ };
386
+ }
387
+ ```
388
+
389
+ ---
390
+
391
+ ### Middleware for locale routing
392
+
393
+ Add a middleware to handle locale detection and routing:
394
+
395
+ ```ts fileName="src/middleware.ts"
396
+ import { NextResponse, type NextRequest } from 'next/server';
397
+ import { defaultLocale, locales } from '@/i18n.config';
398
+
399
+ const PUBLIC_FILE = /\.[^/]+$/; // exclude files with extensions
400
+
401
+ export function middleware(request: NextRequest) {
402
+ const { pathname } = request.nextUrl;
403
+
404
+ if (
405
+ pathname.startsWith('/_next') ||
406
+ pathname.startsWith('/api') ||
407
+ pathname.startsWith('/static') ||
408
+ PUBLIC_FILE.test(pathname)
409
+ ) {
410
+ return;
411
+ }
412
+
413
+ const hasLocale = locales.some(
414
+ (locale) =>
415
+ pathname === '/' + locale || pathname.startsWith('/' + locale + '/')
416
+ );
417
+ if (!hasLocale) {
418
+ const locale = defaultLocale;
419
+ const url = request.nextUrl.clone();
420
+ url.pathname = '/' + locale + (pathname === '/' ? '' : pathname);
421
+ return NextResponse.redirect(url);
422
+ }
423
+ }
424
+
425
+ export const config = {
426
+ matcher: [
427
+ // Match all paths except the ones starting with these and files with an extension
428
+ '/((?!api|_next|static|.*\\..*).*)',
429
+ ],
430
+ };
431
+ ```