@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
@@ -165,9 +165,9 @@ slugs:
165
165
 
166
166
  فيما يلي مثال على تأثير تحسين حجم الحزمة باستخدام `intlayer` في تطبيق vite + react:
167
167
 
168
- | الحزمة المحسنة | الحزمة غير المحسنة |
169
- | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
170
- | ![حزمة محسنة](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle.png) | ![حزمة غير محسنة](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle_no_optimization.png) |
168
+ | الحزمة المحسنة | الحزمة غير المحسنة |
169
+ | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
170
+ | ![حزمة محسنة](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle.png?raw=true) | ![حزمة غير محسنة](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle_no_optimization.png?raw=true) |
171
171
 
172
172
  ---
173
173
 
@@ -340,25 +340,25 @@ slugs:
340
340
 
341
341
  ```bash
342
342
  .
343
- ├── public
344
- │ └── locales
345
- │ ├── en
346
- │ │ ├── home.json
347
- │ │ └── navbar.json
348
- │ ├── fr
349
- │ │ ├── home.json
350
- │ │ └── navbar.json
351
- │ └── es
352
- │ ├── home.json
353
- │ └── navbar.json
354
- ├── next-i18next.config.js
343
+ ├── i18n.config.ts
355
344
  └── src
356
- ├── middleware.ts
345
+ ├── locales
346
+ │ ├── en
347
+ │ │ ├── common.json
348
+ │ │ └── about.json
349
+ │ └── fr
350
+ │ ├── common.json
351
+ │ └── about.json
357
352
  ├── app
358
- └── home.tsx
353
+ ├── i18n
354
+ │ │ └── server.ts
355
+ │ └── [locale]
356
+ │ ├── layout.tsx
357
+ │ └── about.tsx
359
358
  └── components
360
- └── Navbar
361
- └── index.tsx
359
+ ├── I18nProvider.tsx
360
+ ├── ClientComponent.tsx
361
+ └── ServerComponent.tsx
362
362
  ```
363
363
 
364
364
  </TabItem>
@@ -366,6 +366,7 @@ slugs:
366
366
 
367
367
  ```bash
368
368
  .
369
+ ├── i18n.ts
369
370
  ├── locales
370
371
  │ ├── en
371
372
  │ │ ├── home.json
@@ -376,18 +377,20 @@ slugs:
376
377
  │ └── es
377
378
  │ ├── home.json
378
379
  │ └── navbar.json
379
- ├── i18n.ts
380
380
  └── src
381
381
  ├── middleware.ts
382
382
  ├── app
383
- └── home.tsx
383
+ ├── i18n
384
+ │ │ └── server.ts
385
+ │ └── [locale]
386
+ │ └── home.tsx
384
387
  └── components
385
388
  └── Navbar
386
389
  └── index.tsx
387
390
  ```
388
391
 
389
392
  </TabItem>
390
- <TabItem label="intlayer" value="intlayer">
393
+ <TabItem label="intlayer" value="intlayer">
391
394
 
392
395
  ```bash
393
396
  .
@@ -395,9 +398,11 @@ slugs:
395
398
  └── src
396
399
  ├── middleware.ts
397
400
  ├── app
398
- │ └── home
399
- └── index.tsx
400
- │ └── index.content.ts
401
+ │ └── [locale]
402
+ ├── layout.tsx
403
+ │ └── home
404
+ │ ├── index.tsx
405
+ │ └── index.content.ts
401
406
  └── components
402
407
  └── Navbar
403
408
  ├── index.tsx
@@ -420,155 +425,294 @@ slugs:
420
425
  <Tab defaultTab="next-intl" group='techno'>
421
426
  <TabItem label="next-i18next" value="next-i18next">
422
427
 
423
- ```tsx fileName="next-i18next.config.js"
424
- module.exports = {
425
- i18n: {
426
- locales: ["en", "fr", "es"],
427
- defaultLocale: "en",
428
- },
429
- };
430
- ```
428
+ ```ts fileName="i18n.config.ts"
429
+ export const locales = ["en", "fr"] as const;
430
+ export type Locale = (typeof locales)[number];
431
+
432
+ export const defaultLocale: Locale = "en";
431
433
 
432
- ```tsx fileName="src/app/_app.tsx"
433
- import { appWithTranslation } from "next-i18next";
434
+ export const rtlLocales = ["ar", "he", "fa", "ur"] as const;
435
+ export const isRtl = (locale: string) =>
436
+ (rtlLocales as readonly string[]).includes(locale);
434
437
 
435
- const MyApp = ({ Component, pageProps }) => <Component {...pageProps} />;
438
+ export function localizedPath(locale: string, path: string) {
439
+ return locale === defaultLocale ? path : "/" + locale + path;
440
+ }
436
441
 
437
- export default appWithTranslation(MyApp);
442
+ const ORIGIN = "https://example.com";
443
+ export function abs(locale: string, path: string) {
444
+ return ORIGIN + localizedPath(locale, path);
445
+ }
438
446
  ```
439
447
 
440
- ```tsx fileName="src/app/[locale]/about/page.tsx"
441
- import type { GetStaticProps } from "next";
442
- import { serverSideTranslations } from "next-i18next/serverSideTranslations";
443
- import { useTranslation } from "next-i18next";
444
- import { I18nextProvider, initReactI18next } from "react-i18next";
448
+ ```ts fileName="src/app/i18n/server.ts"
445
449
  import { createInstance } from "i18next";
446
- import { ClientComponent, ServerComponent } from "@components";
450
+ import { initReactI18next } from "react-i18next/initReactI18next";
451
+ import resourcesToBackend from "i18next-resources-to-backend";
452
+ import { defaultLocale } from "@/i18n.config";
453
+
454
+ // Load JSON resources from src/locales/<locale>/<namespace>.json
455
+ const backend = resourcesToBackend(
456
+ (locale: string, namespace: string) =>
457
+ import(`../../locales/${locale}/${namespace}.json`)
458
+ );
459
+
460
+ export async function initI18next(
461
+ locale: string,
462
+ namespaces: string[] = ["common"]
463
+ ) {
464
+ const i18n = createInstance();
465
+ await i18n
466
+ .use(initReactI18next)
467
+ .use(backend)
468
+ .init({
469
+ lng: locale,
470
+ fallbackLng: defaultLocale,
471
+ ns: namespaces,
472
+ defaultNS: "common",
473
+ interpolation: { escapeValue: false },
474
+ react: { useSuspense: false },
475
+ });
476
+ return i18n;
477
+ }
478
+ ```
447
479
 
448
- export default function HomePage({ locale }: { locale: string }) {
449
- // أعلن صراحة عن مساحة الأسماء المستخدمة من قبل هذا المكون
450
- const resources = await loadMessagesFor(locale); // محملك (JSON، إلخ)
480
+ ```tsx fileName="src/components/I18nProvider.tsx"
481
+ "use client";
451
482
 
452
- const i18n = createInstance();
453
- i18n.use(initReactI18next).init({
454
- lng: locale,
455
- fallbackLng: "en",
456
- resources,
457
- ns: ["common", "about"],
458
- defaultNS: "common",
459
- interpolation: { escapeValue: false },
483
+ import * as React from "react";
484
+ import { I18nextProvider } from "react-i18next";
485
+ import { createInstance } from "i18next";
486
+ import { initReactI18next } from "react-i18next/initReactI18next";
487
+ import resourcesToBackend from "i18next-resources-to-backend";
488
+ import { defaultLocale } from "@/i18n.config";
489
+
490
+ const backend = resourcesToBackend(
491
+ (locale: string, namespace: string) =>
492
+ import(`../../locales/${locale}/${namespace}.json`)
493
+ );
494
+
495
+ type Props = {
496
+ locale: string;
497
+ namespaces?: string[];
498
+ resources?: Record<string, any>; // { ns: bundle }
499
+ children: React.ReactNode;
500
+ };
501
+
502
+ export default function I18nProvider({
503
+ locale,
504
+ namespaces = ["common"],
505
+ resources,
506
+ children,
507
+ }: Props) {
508
+ const [i18n] = React.useState(() => {
509
+ const i = createInstance();
510
+
511
+ i.use(initReactI18next)
512
+ .use(backend)
513
+ .init({
514
+ lng: locale,
515
+ fallbackLng: defaultLocale,
516
+ ns: namespaces,
517
+ resources: resources ? { [locale]: resources } : undefined,
518
+ defaultNS: "common",
519
+ interpolation: { escapeValue: false },
520
+ react: { useSuspense: false },
521
+ });
522
+
523
+ return i;
460
524
  });
461
525
 
462
- const { t } = useTranslation("about");
526
+ return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>;
527
+ }
528
+ ```
529
+
530
+ ```tsx fileName="src/app/[locale]/layout.tsx"
531
+ import type { ReactNode } from "react";
532
+ import { locales, defaultLocale, isRtl, type Locale } from "@/i18n.config";
533
+
534
+ export const dynamicParams = false;
535
+
536
+ export function generateStaticParams() {
537
+ return locales.map((locale) => ({ locale }));
538
+ }
539
+
540
+ export default function LocaleLayout({
541
+ children,
542
+ params,
543
+ }: {
544
+ children: ReactNode;
545
+ params: { locale: string };
546
+ }) {
547
+ const locale: Locale = (locales as readonly string[]).includes(params.locale)
548
+ ? (params.locale as any)
549
+ : defaultLocale;
550
+
551
+ const dir = isRtl(locale) ? "rtl" : "ltr";
552
+
553
+ return (
554
+ <html lang={locale} dir={dir}>
555
+ <body>{children}</body>
556
+ </html>
557
+ );
558
+ }
559
+ ```
560
+
561
+ ```tsx fileName="src/app/[locale]/about.tsx"
562
+ import I18nProvider from "@/components/I18nProvider";
563
+ import { initI18next } from "@/app/i18n/server";
564
+ import type { Locale } from "@/i18n.config";
565
+ import ClientComponent from "@/components/ClientComponent";
566
+ import ServerComponent from "@/components/ServerComponent";
567
+
568
+ // Force static rendering for the page
569
+ export const dynamic = "force-static";
570
+
571
+ export default async function AboutPage({
572
+ params: { locale },
573
+ }: {
574
+ params: { locale: Locale };
575
+ }) {
576
+ const namespaces = ["common", "about"] as const;
577
+
578
+ const i18n = await initI18next(locale, [...namespaces]);
579
+ const tAbout = i18n.getFixedT(locale, "about");
463
580
 
464
581
  return (
465
- <I18nextProvider i18n={i18n}>
582
+ <I18nProvider locale={locale} namespaces={[...namespaces]}>
466
583
  <main>
467
- <h1>{t("title")}</h1>
584
+ <h1>{tAbout("title")}</h1>
585
+
468
586
  <ClientComponent />
469
- <ServerComponent />
587
+ <ServerComponent t={tAbout} locale={locale} count={0} />
470
588
  </main>
471
- </I18nextProvider>
589
+ </I18nProvider>
472
590
  );
473
591
  }
474
-
475
- export const getStaticProps: GetStaticProps = async ({ locale }) => {
476
- // قم بتحميل مساحات الأسماء الضرورية فقط لهذه الصفحة
477
- return {
478
- props: {
479
- ...(await serverSideTranslations(locale ?? "en", ["common", "about"])),
480
- },
481
- };
482
- };
483
592
  ```
484
593
 
485
594
  </TabItem>
486
595
  <TabItem label="next-intl" value="next-intl">
487
596
 
488
- ```tsx fileName="i18n.ts"
597
+ ```tsx fileName="src/i18n.ts"
489
598
  import { getRequestConfig } from "next-intl/server";
490
599
  import { notFound } from "next/navigation";
491
600
 
492
- // يمكن استيرادها من إعداد مشترك
493
- const locales = ["en", "fr", "es"];
601
+ export const locales = ["en", "fr", "es"] as const;
602
+ export const defaultLocale = "en" as const;
603
+
604
+ async function loadMessages(locale: string) {
605
+ // Load only the namespaces your layout/pages need
606
+ const [common, about] = await Promise.all([
607
+ import(`../locales/${locale}/common.json`).then((m) => m.default),
608
+ import(`../locales/${locale}/about.json`).then((m) => m.default),
609
+ ]);
610
+
611
+ return { common, about } as const;
612
+ }
494
613
 
495
614
  export default getRequestConfig(async ({ locale }) => {
496
- // تحقق من أن معامل `locale` الوارد صالح
497
615
  if (!locales.includes(locale as any)) notFound();
498
616
 
499
617
  return {
500
- messages: (await import(`../messages/${locale}.json`)).default,
618
+ messages: await loadMessages(locale),
501
619
  };
502
620
  });
503
621
  ```
504
622
 
505
- ```tsx fileName="src/app/[locale]/about/layout.tsx"
506
- import { NextIntlClientProvider } from "next-intl";
507
- import { getMessages, unstable_setRequestLocale } from "next-intl/server";
508
- import pick from "lodash/pick";
623
+ ```tsx fileName="src/app/[locale]/layout.tsx"
624
+ import type { ReactNode } from "react";
625
+ import { locales } from "@/i18n";
626
+ import {
627
+ getLocaleDirection,
628
+ unstable_setRequestLocale,
629
+ } from "next-intl/server";
630
+
631
+ export const dynamic = "force-static";
632
+
633
+ export function generateStaticParams() {
634
+ return locales.map((locale) => ({ locale }));
635
+ }
509
636
 
510
637
  export default async function LocaleLayout({
511
638
  children,
512
639
  params,
513
640
  }: {
514
- children: React.ReactNode;
515
- params: { locale: string };
641
+ children: ReactNode;
642
+ params: Promise<{ locale: string }>;
516
643
  }) {
517
- const { locale } = params;
644
+ const { locale } = await params;
518
645
 
519
- // تعيين اللغة النشطة للطلب لهذا العرض على الخادم (RSC)
646
+ // Set the active request locale for this server render (RSC)
520
647
  unstable_setRequestLocale(locale);
521
648
 
522
- // يتم تحميل الرسائل على جانب الخادم عبر src/i18n/request.ts
523
- // (انظر وثائق next-intl). هنا ندفع فقط جزءًا فرعيًا إلى العميل
524
- // اللازم لمكونات العميل (تحسين الحمولة).
525
- const messages = await getMessages();
526
- const clientMessages = pick(messages, ["common", "about"]);
649
+ const dir = getLocaleDirection(locale);
527
650
 
528
651
  return (
529
- <html lang={locale}>
530
- <body>
531
- <NextIntlClientProvider locale={locale} messages={clientMessages}>
532
- {children}
533
- </NextIntlClientProvider>
534
- </body>
652
+ <html lang={locale} dir={dir}>
653
+ <body>{children}</body>
535
654
  </html>
536
655
  );
537
656
  }
538
657
  ```
539
658
 
540
659
  ```tsx fileName="src/app/[locale]/about/page.tsx"
541
- import { getTranslations } from "next-intl/server";
542
- import { ClientComponent, ServerComponent } from "@components";
660
+ import { getTranslations, getMessages, getFormatter } from "next-intl/server";
661
+ import { NextIntlClientProvider } from "next-intl";
662
+ import pick from "lodash/pick";
663
+ import ServerComponent from "@/components/ServerComponent";
664
+ import ClientComponentExample from "@/components/ClientComponentExample";
543
665
 
544
- export default async function LandingPage({
666
+ export const dynamic = "force-static";
667
+
668
+ export default async function AboutPage({
545
669
  params,
546
670
  }: {
547
- params: { locale: string };
671
+ params: Promise<{ locale: string }>;
548
672
  }) {
549
- // تحميل صارم على جانب الخادم فقط (غير مفعّل على العميل)
550
- const t = await getTranslations("about");
673
+ const { locale } = await params;
674
+
675
+ // Messages are loaded server-side. Push only what's needed to the client.
676
+ const messages = await getMessages();
677
+ const clientMessages = pick(messages, ["common", "about"]);
678
+
679
+ // Strictly server-side translations/formatting
680
+ const tAbout = await getTranslations("about");
681
+ const tCounter = await getTranslations("about.counter");
682
+ const format = await getFormatter();
683
+
684
+ const initialFormattedCount = format.number(0);
551
685
 
552
686
  return (
553
- <main>
554
- <h1>{t("title")}</h1>
555
- <ClientComponent />
556
- <ServerComponent />
557
- </main>
687
+ <NextIntlClientProvider locale={locale} messages={clientMessages}>
688
+ <main>
689
+ <h1>{tAbout("title")}</h1>
690
+ <ClientComponentExample />
691
+ <ServerComponent
692
+ formattedCount={initialFormattedCount}
693
+ label={tCounter("label")}
694
+ increment={tCounter("increment")}
695
+ />
696
+ </main>
697
+ </NextIntlClientProvider>
558
698
  );
559
699
  }
560
700
  ```
561
701
 
562
702
  </TabItem>
563
- <TabItem label="intlayer" value="intlayer">
703
+ <TabItem label="intlayer" value="intlayer">
564
704
 
565
705
  ```tsx fileName="intlayer.config.ts"
566
- export default {
706
+ import { type IntlayerConfig, Locales } from "intlayer";
707
+
708
+ const config: IntlayerConfig = {
567
709
  internationalization: {
568
- locales: ["en", "fr", "es"],
569
- defaultLocale: "en",
710
+ locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],
711
+ defaultLocale: Locales.ENGLISH,
570
712
  },
571
713
  };
714
+
715
+ export default config;
572
716
  ```
573
717
 
574
718
  ```tsx fileName="src/app/[locale]/layout.tsx"
@@ -581,14 +725,16 @@ import {
581
725
 
582
726
  export const dynamic = "force-static";
583
727
 
584
- const LandingLayout: NextLayoutIntlayer = async ({ children, params }) => {
728
+ const LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => {
585
729
  const { locale } = await params;
586
730
 
587
731
  return (
588
732
  <html lang={locale} dir={getHTMLTextDir(locale)}>
589
- <IntlayerClientProvider locale={locale}>
590
- {children}
591
- </IntlayerClientProvider>
733
+ <body>
734
+ <IntlayerClientProvider locale={locale}>
735
+ {children}
736
+ </IntlayerClientProvider>
737
+ </body>
592
738
  </html>
593
739
  );
594
740
  };
@@ -640,10 +786,12 @@ export default LandingPage;
640
786
  <Tab defaultTab="next-intl" group='techno'>
641
787
  <TabItem label="next-i18next" value="next-i18next">
642
788
 
643
- **الترجمات (يجب أن تكون JSON حقيقية في `public/locales/...`)**
789
+ **الترجمات (one JSON per namespace under `src/locales/...`)**
644
790
 
645
- ```json fileName="public/locales/en/about.json"
791
+ ```json fileName="src/locales/en/about.json"
646
792
  {
793
+ "title": "About",
794
+ "description": "About page description",
647
795
  "counter": {
648
796
  "label": "Counter",
649
797
  "increment": "Increment"
@@ -651,28 +799,29 @@ export default LandingPage;
651
799
  }
652
800
  ```
653
801
 
654
- ```json fileName="public/locales/fr/about.json"
802
+ ```json fileName="src/locales/fr/about.json"
655
803
  {
804
+ "title": "À propos",
805
+ "description": "Description de la page À propos",
656
806
  "counter": {
657
- "label": "عداد",
658
- "increment": "زيادة"
807
+ "label": "Compteur",
808
+ "increment": "Incrémenter"
659
809
  }
660
810
  }
661
811
  ```
662
812
 
663
- **مكون العميل**
813
+ **مكون العميل (loads only the required namespace)**
664
814
 
665
- ```tsx fileName="src/components/ClientComponentExample.tsx"
815
+ ```tsx fileName="src/components/ClientComponent.tsx"
666
816
  "use client";
667
817
 
668
- import React, { useMemo, useState } from "react";
669
- import { useTranslation } from "next-i18next";
818
+ import React, { useState } from "react";
819
+ import { useTranslation } from "react-i18next";
670
820
 
671
- const ClientComponentExample = () => {
821
+ const ClientComponent = () => {
672
822
  const { t, i18n } = useTranslation("about");
673
823
  const [count, setCount] = useState(0);
674
824
 
675
- // next-i18next لا يوفر useNumber؛ استخدم Intl.NumberFormat
676
825
  const numberFormat = new Intl.NumberFormat(i18n.language);
677
826
 
678
827
  return (
@@ -680,22 +829,24 @@ const ClientComponentExample = () => {
680
829
  <p>{numberFormat.format(count)}</p>
681
830
  <button
682
831
  aria-label={t("counter.label")}
683
- onClick={() => setCount((count) => count + 1)}
832
+ onClick={() => setCount((c) => c + 1)}
684
833
  >
685
834
  {t("counter.increment")}
686
835
  </button>
687
836
  </div>
688
837
  );
689
838
  };
839
+
840
+ export default ClientComponent;
690
841
  ```
691
842
 
692
- > لا تنسَ إضافة مساحة الأسماء "about" في serverSideTranslations للصفحة
693
- > نستخدم هنا إصدار React 19.x.x، ولكن للإصدارات الأقدم، ستحتاج إلى استخدام useMemo لتخزين نسخة من المُنسق لأنه دالة ثقيلة
843
+ > Ensure the page/provider includes only the namespaces you need (e.g. `about`).
844
+ > If you use React < 19, memoize heavy formatters like `Intl.NumberFormat`.
694
845
 
695
846
  </TabItem>
696
847
  <TabItem label="next-intl" value="next-intl">
697
848
 
698
- **الترجمات (تم إعادة استخدام الشكل؛ قم بتحميلها في رسائل next-intl كما تفضل)**
849
+ **الترجمات (shape reused; load them into next-intl messages as you prefer)**
699
850
 
700
851
  ```json fileName="locales/en/about.json"
701
852
  {
@@ -743,7 +894,7 @@ const ClientComponentExample = () => {
743
894
  };
744
895
  ```
745
896
 
746
- > لا تنسَ إضافة رسالة "about" في رسالة العميل للصفحة
897
+ > Don't forget to add "about" message on the page client message
747
898
 
748
899
  </TabItem>
749
900
  <TabItem label="intlayer" value="intlayer">
@@ -756,8 +907,8 @@ import { t, type Dictionary } from "intlayer";
756
907
  const counterContent = {
757
908
  key: "counter",
758
909
  content: {
759
- label: t({ ar: "عداد", en: "Counter", fr: "Compteur" }),
760
- increment: t({ ar: "زيادة", en: "Increment", fr: "Incrémenter" }),
910
+ label: t({ en: "Counter", fr: "Compteur" }),
911
+ increment: t({ en: "Increment", fr: "Incrémenter" }),
761
912
  },
762
913
  } satisfies Dictionary;
763
914
 
@@ -774,7 +925,7 @@ import { useNumber, useIntlayer } from "next-intlayer";
774
925
 
775
926
  const ClientComponentExample = () => {
776
927
  const [count, setCount] = useState(0);
777
- const { label, increment } = useIntlayer("counter"); // يعيد سلاسل نصية
928
+ const { label, increment } = useIntlayer("counter"); // returns strings
778
929
  const { number } = useNumber();
779
930
 
780
931
  return (
@@ -802,30 +953,28 @@ const ClientComponentExample = () => {
802
953
  - احتفظ ببنية متداخلة (`about.counter.label`) وحدد نطاق الخطاف الخاص بك وفقًا لذلك (`useTranslation("about")` + `t("counter.label")` أو `useTranslations("about.counter")` + `t("label")`).
803
954
 
804
955
  - **مواقع الملفات**
805
- - **next-i18next** يتوقع JSON في `public/locales/{lng}/{ns}.json`.
806
- - **next-intl** مرن؛ يمكنك تحميل الرسائل كيفما تريد.
807
- - **Intlayer** يخزن المحتوى في قواميس TS/JS ويحلها حسب المفتاح.
956
+ - **next-i18next** expects JSON in `public/locales/{lng}/{ns}.json`.
957
+ - **next-intl** is flexible; load messages however you configure.
958
+ - **Intlayer** stores content in TS/JS dictionaries and resolves by key.
808
959
 
809
960
  ---
810
961
 
811
962
  ### الاستخدام في مكون الخادم
812
963
 
813
- سوف نأخذ حالة مكون واجهة مستخدم. هذا المكون هو مكون خادم، ويجب أن يكون قادرًا على الإدراج كطفل لمكون عميل. (صفحة (مكون خادم) -> مكون عميل -> مكون خادم). بما أن هذا المكون يمكن إدراجه كطفل لمكون عميل، فلا يمكن أن يكون غير متزامن (async).
964
+ We will take the case of a UI component. This component is a server component, and should be able to be inserted as a child of a client component. (page (server component) -> client component -> server component). As this component can be inserted as a child of a client component, it cannot be async.
814
965
 
815
966
  <Tab defaultTab="next-intl" group='techno'>
816
967
  <TabItem label="next-i18next" value="next-i18next">
817
968
 
818
- ```tsx fileName="src/pages/about.tsx"
819
- import type { GetStaticProps } from "next";
820
- import { useTranslation } from "next-i18next";
821
-
969
+ ```tsx fileName="src/components/ServerComponent.tsx"
822
970
  type ServerComponentProps = {
971
+ t: (key: string) => string;
972
+ locale: string;
823
973
  count: number;
824
974
  };
825
975
 
826
- const ServerComponent = ({ count }: ServerComponentProps) => {
827
- const { t, i18n } = useTranslation("about");
828
- const formatted = new Intl.NumberFormat(i18n.language).format(count);
976
+ const ServerComponent = ({ t, locale, count }: ServerComponentProps) => {
977
+ const formatted = new Intl.NumberFormat(locale).format(count);
829
978
 
830
979
  return (
831
980
  <div>
@@ -834,35 +983,43 @@ const ServerComponent = ({ count }: ServerComponentProps) => {
834
983
  </div>
835
984
  );
836
985
  };
837
- ```
838
986
 
839
- > بما أن مكون الخادم لا يمكن أن يكون غير متزامن (async)، يجب عليك تمرير الترجمات ودالة التنسيق كخصائص (props).
987
+ export default ServerComponent;
988
+ ```
840
989
 
841
990
  </TabItem>
842
991
  <TabItem label="next-intl" value="next-intl">
843
992
 
844
993
  ```tsx fileName="src/components/ServerComponent.tsx"
845
994
  type ServerComponentProps = {
846
- count: number;
847
- t: (key: string) => string;
995
+ formattedCount: string;
996
+ label: string;
997
+ increment: string;
848
998
  };
849
999
 
850
- const ServerComponent = ({ t, count }: ServerComponentProps) => {
851
- const formatted = new Intl.NumberFormat(i18n.language).format(count);
852
-
1000
+ const ServerComponent = ({
1001
+ formattedCount,
1002
+ label,
1003
+ increment,
1004
+ }: ServerComponentProps) => {
853
1005
  return (
854
1006
  <div>
855
- <p>{formatted}</p>
856
- <button aria-label={t("label")}>{t("increment")}</button>
1007
+ <p>{formattedCount}</p>
1008
+ <button aria-label={label}>{increment}</button>
857
1009
  </div>
858
1010
  );
859
1011
  };
1012
+
1013
+ export default ServerComponent;
860
1014
  ```
861
1015
 
862
- > بما أن مكون الخادم لا يمكن أن يكون غير متزامن، تحتاج إلى تمرير الترجمات ودالة التنسيق كخصائص.
1016
+ > As the server component cannot be async, you need to pass the translations and formatter function as props.
1017
+ >
1018
+ > In your page / layout:
863
1019
  >
1020
+ > - `import { getTranslations, getFormatter } from "next-intl/server";`
864
1021
  > - `const t = await getTranslations("about.counter");`
865
- > - `const format = await getFormatter();`
1022
+ > - `const formatter = await getFormatter().then((formatter) => formatter.number());`
866
1023
 
867
1024
  </TabItem>
868
1025
  <TabItem label="intlayer" value="intlayer">
@@ -870,7 +1027,11 @@ const ServerComponent = ({ t, count }: ServerComponentProps) => {
870
1027
  ```tsx fileName="src/components/ServerComponent.tsx"
871
1028
  import { useIntlayer, useNumber } from "next-intlayer/server";
872
1029
 
873
- const ServerComponent = ({ count }: { count: number }) => {
1030
+ type ServerComponentProps = {
1031
+ count: number;
1032
+ };
1033
+
1034
+ const ServerComponent = ({ count }: ServerComponentProps) => {
874
1035
  const { label, increment } = useIntlayer("counter");
875
1036
  const { number } = useNumber();
876
1037
 
@@ -886,24 +1047,24 @@ const ServerComponent = ({ count }: { count: number }) => {
886
1047
  </TabItem>
887
1048
  </Tab>
888
1049
 
889
- > تعرض Intlayer خطافات **آمنة للخادم** عبر `next-intlayer/server`. للعمل، يستخدم كل من `useIntlayer` و `useNumber` صيغة تشبه الخطافات، مشابهة لخطافات العميل، لكنها تعتمد في الأساس على سياق الخادم (`IntlayerServerProvider`).
1050
+ > Intlayer exposes **server-safe** hooks via `next-intlayer/server`. To work, `useIntlayer` and `useNumber` use hooks-like syntax, similar to the client hooks, but depend under the hood on the server context (`IntlayerServerProvider`).
890
1051
 
891
1052
  ### البيانات الوصفية / خريطة الموقع / روبوتات البحث
892
1053
 
893
1054
  ترجمة المحتوى أمر رائع. لكن الناس عادةً ما ينسون أن الهدف الرئيسي من التدويل هو جعل موقعك الإلكتروني أكثر ظهورًا للعالم. التدويل هو رافعة مذهلة لتحسين ظهور موقعك الإلكتروني.
894
1055
 
895
- إليك قائمة بالممارسات الجيدة المتعلقة بتحسين محركات البحث متعددة اللغات (SEO).
1056
+ Here's a list of good practices regarding multilingual SEO.
896
1057
 
897
- - تعيين علامات hreflang الوصفية داخل وسم `<head>`
898
- > يساعد هذا محركات البحث على فهم اللغات المتاحة في الصفحة
899
- - قم بإدراج جميع ترجمات الصفحات في ملف sitemap.xml باستخدام مخطط XML `http://www.w3.org/1999/xhtml`
1058
+ - set hreflang meta tags in the `<head>` tag
1059
+ > It helps search engines to understand what languages are available on the page
1060
+ - list all pages translations in the sitemap.xml using `http://www.w3.org/1999/xhtml` XML schema
900
1061
  >
901
- - لا تنسَ استبعاد الصفحات ذات البادئة من ملف robots.txt (مثل `/dashboard و `/fr/dashboard و `/es/dashboard`)
1062
+ - do not forget to exclude prefixed pages from the robots.txt (e.g. `/dashboard`, and `/fr/dashboard`, `/es/dashboard`)
902
1063
  >
903
- - استخدم مكون Link مخصص لإعادة التوجيه إلى الصفحة الأكثر تخصيصًا للغة (مثلًا بالفرنسية `<a href="/fr/about">A propos</a>`)
1064
+ - use custom Link component to redirect to the most localized page (e.g. in french `<a href="/fr/about">A propos</a>` )
904
1065
  >
905
1066
 
906
- غالبًا ما ينسى المطورون الإشارة بشكل صحيح إلى صفحاتهم عبر اللغات المختلفة.
1067
+ Developers often forget to properly reference their pages across locales.
907
1068
 
908
1069
  <Tab defaultTab="next-intl" group='techno'>
909
1070
 
@@ -935,10 +1096,9 @@ export async function generateMetadata({
935
1096
  }): Promise<Metadata> {
936
1097
  const { locale } = params;
937
1098
 
938
- // استيراد ملف JSON الصحيح بشكل ديناميكي
939
- const messages = (
940
- await import("@/../public/locales/" + locale + "/about.json")
941
- ).default;
1099
+ // Import the correct JSON bundle from src/locales
1100
+ const messages = (await import("@/locales/" + locale + "/about.json"))
1101
+ .default;
942
1102
 
943
1103
  const languages = Object.fromEntries(
944
1104
  locales.map((locale) => [locale, localizedPath(locale, "/about")])
@@ -955,7 +1115,7 @@ export async function generateMetadata({
955
1115
  }
956
1116
 
957
1117
  export default async function AboutPage() {
958
- return <h1>حول</h1>;
1118
+ return <h1>About</h1>;
959
1119
  }
960
1120
  ```
961
1121
 
@@ -971,7 +1131,7 @@ export default function sitemap(): MetadataRoute.Sitemap {
971
1131
  {
972
1132
  url: abs(defaultLocale, "/about"),
973
1133
  lastModified: new Date(),
974
- changeFrequency: "شهري",
1134
+ changeFrequency: "monthly",
975
1135
  priority: 0.7,
976
1136
  alternates: { languages },
977
1137
  },
@@ -1041,7 +1201,7 @@ export async function generateMetadata({
1041
1201
  };
1042
1202
  }
1043
1203
 
1044
- // ... بقية كود الصفحة
1204
+ // ... Rest of the page code
1045
1205
  ```
1046
1206
 
1047
1207
  ```tsx fileName="src/app/sitemap.ts"
@@ -1122,7 +1282,7 @@ export const generateMetadata = async ({
1122
1282
  };
1123
1283
  };
1124
1284
 
1125
- // ... بقية كود الصفحة
1285
+ // ... Rest of the page code
1126
1286
  ```
1127
1287
 
1128
1288
  ```tsx fileName="src/app/sitemap.ts"
@@ -1143,16 +1303,14 @@ const sitemap = (): MetadataRoute.Sitemap => [
1143
1303
  import { getMultilingualUrls } from "intlayer";
1144
1304
  import type { MetadataRoute } from "next";
1145
1305
 
1146
- // دالة لجلب جميع الروابط متعددة اللغات من قائمة الروابط
1147
1306
  const getAllMultilingualUrls = (urls: string[]) =>
1148
1307
  urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);
1149
1308
 
1150
- // إعدادات ملف robots.txt مع قواعد السماح والمنع للزواحف
1151
1309
  const robots = (): MetadataRoute.Robots => ({
1152
1310
  rules: {
1153
1311
  userAgent: "*",
1154
1312
  allow: ["/"],
1155
- disallow: getAllMultilingualUrls(["/dashboard"]), // منع الوصول إلى لوحة التحكم بجميع اللغات
1313
+ disallow: getAllMultilingualUrls(["/dashboard"]),
1156
1314
  },
1157
1315
  host: "https://example.com",
1158
1316
  sitemap: "https://example.com/sitemap.xml",
@@ -1164,10 +1322,83 @@ export default robots;
1164
1322
  </TabItem>
1165
1323
  </Tab>
1166
1324
 
1167
- > توفر Intlayer دالة `getMultilingualUrls` لتوليد روابط متعددة اللغات لخريطة موقعك.
1325
+ > Intlayer provides a `getMultilingualUrls` function to generate multilingual URLs for your sitemap.
1168
1326
 
1169
1327
  ---
1170
1328
 
1329
+ ### Middleware for locale routing
1330
+
1331
+ <Tab defaultTab="next-intl" group='techno'>
1332
+ <TabItem label="next-i18next" value="next-i18next">
1333
+
1334
+ Add a middleware to handle locale detection and routing:
1335
+
1336
+ ```ts fileName="src/middleware.ts"
1337
+ import { NextResponse, type NextRequest } from "next/server";
1338
+ import { defaultLocale, locales } from "@/i18n.config";
1339
+
1340
+ const PUBLIC_FILE = /\.[^/]+$/; // exclude files with extensions
1341
+
1342
+ export function middleware(request: NextRequest) {
1343
+ const { pathname } = request.nextUrl;
1344
+
1345
+ if (
1346
+ pathname.startsWith("/_next") ||
1347
+ pathname.startsWith("/api") ||
1348
+ pathname.startsWith("/static") ||
1349
+ PUBLIC_FILE.test(pathname)
1350
+ ) {
1351
+ return;
1352
+ }
1353
+
1354
+ const hasLocale = locales.some(
1355
+ (l) => pathname === "/" + l || pathname.startsWith("/" + l + "/")
1356
+ );
1357
+ if (!hasLocale) {
1358
+ const locale = defaultLocale;
1359
+ const url = request.nextUrl.clone();
1360
+ url.pathname = "/" + locale + (pathname === "/" ? "" : pathname);
1361
+ return NextResponse.redirect(url);
1362
+ }
1363
+ }
1364
+
1365
+ export const config = {
1366
+ matcher: [
1367
+ // Match all paths except the ones starting with these and files with an extension
1368
+ "/((?!api|_next|static|.*\\..*).*)",
1369
+ ],
1370
+ };
1371
+ ```
1372
+
1373
+ </TabItem>
1374
+ <TabItem label="next-intl" value="next-intl">
1375
+
1376
+ Add a middleware to handle locale detection and routing:
1377
+
1378
+ ```ts fileName="src/middleware.ts"
1379
+ import createMiddleware from "next-intl/middleware";
1380
+ import { locales, defaultLocale } from "@/i18n";
1381
+
1382
+ export default createMiddleware({
1383
+ locales: [...locales],
1384
+ defaultLocale,
1385
+ localeDetection: true,
1386
+ });
1387
+
1388
+ export const config = {
1389
+ // Skip API, Next internals and static assets
1390
+ matcher: ["/((?!api|_next|.*\\..*).*)"],
1391
+ };
1392
+ ```
1393
+
1394
+ </TabItem>
1395
+ <TabItem label="intlayer" value="intlayer">
1396
+
1397
+ Intlayer provides built-in middleware handling through the `next-intlayer` package configuration.
1398
+
1399
+ </TabItem>
1400
+ </Tab>
1401
+
1171
1402
  ---
1172
1403
 
1173
1404
  ## والفائز هو…