Internationalization

Jumbo React Next implements i18n through the Next.js App Router's [lang] dynamic segment. Each supported locale gets its own URL prefix (e.g. /en-US/, /ar-SA/). Translations are stored in JSON dictionary files and loaded server-side with a getDictionary function.

Supported locales

typescript
// src/proxy/locale.ts
const locales = ['en-US', 'ar-SA', 'es-ES', 'fr-FR', 'it-IT', 'zh-CN'];
const defaultLocale = 'en-US';
CodeLanguageDirection
en-USEnglish (United States)LTR
ar-SAArabic (Saudi Arabia)RTL
es-ESSpanish (Spain)LTR
fr-FRFrench (France)LTR
it-ITItalian (Italy)LTR
zh-CNChinese (Simplified)LTR

URL structure

Every route includes the locale as the first segment:

http://localhost:3000/en-US/dashboards/misc
http://localhost:3000/ar-SA/dashboards/misc
http://localhost:3000/fr-FR/apps/mail/inbox

Requests without a locale prefix are redirected to the default locale by the proxy. See the Proxy & Middleware guide for how locale detection works.

Dictionary structure

Translation files live under src/dictionaries/, one JSON file per locale:

src/dictionaries/
  en.json
  ar.json
  es.json
  fr.json
  it.json
  zh.json
  type.ts      ← TypeScript type for the dictionary shape

Dictionary files are flat JSON key-value maps:

json
// src/dictionaries/en.json (excerpt)
{
  "greeting": "Hello",
  "dashboard": "Dashboard",
  "signIn": "Sign In",
  "signOut": "Sign Out",
  "welcomeMessage": "Welcome to Jumbo React Next"
}

Loading translations (server components)

getDictionary is a server-only async function that dynamically imports the correct JSON file for the requested locale:

typescript
// src/app/[lang]/dictionaries.ts
import 'server-only';
 
type Locale = 'en-US' | 'ar-SA' | 'es-ES' | 'fr-FR' | 'it-IT' | 'zh-CN';
 
const dictionaries: Record<Locale, () => Promise<Record<string, string>>> = {
  'en-US': () => import('@/dictionaries/en.json').then((m) => m.default),
  'ar-SA': () => import('@/dictionaries/ar.json').then((m) => m.default),
  'es-ES': () => import('@/dictionaries/es.json').then((m) => m.default),
  'fr-FR': () => import('@/dictionaries/fr.json').then((m) => m.default),
  'it-IT': () => import('@/dictionaries/it.json').then((m) => m.default),
  'zh-CN': () => import('@/dictionaries/zh.json').then((m) => m.default),
};
 
export async function getDictionary(locale: string) {
  return dictionaries[locale as Locale]?.() ?? dictionaries['en-US']();
}

In a server component, call getDictionary with the lang param:

typescript
// src/app/[lang]/(common)/dashboards/misc/page.tsx
interface Props {
  params: Promise<{ lang: string }>;
}
 
export default async function MiscDashboardPage({ params }: Props) {
  const { lang } = await params;
  const dict = await getDictionary(lang);
 
  return (
    <div>
      <h1>{dict.dashboard}</h1>
      <p>{dict.welcomeMessage}</p>
    </div>
  );
}

RTL support for Arabic

When the lang param is ar-SA, the root layout sets dir="rtl" on the <html> element and activates JumboRTL. All MUI components and Emotion-styled components are automatically mirrored:

tsx
// src/app/[lang]/layout.tsx
const isRTL = lang === 'ar-SA';
 
return (
  <html lang={lang} dir={isRTL ? 'rtl' : 'ltr'}>
    <body>
      <JumboRTL>{children}</JumboRTL>
    </body>
  </html>
);

See the JumboRTL component docs for details.

Adding a new language

  1. Add the locale code to the supported locales list

    typescript
    // src/proxy/locale.ts
    const locales = ['en-US', 'ar-SA', 'es-ES', 'fr-FR', 'it-IT', 'zh-CN', 'de-DE'];
  2. Create the dictionary file

    bash
    cp src/dictionaries/en.json src/dictionaries/de.json

    Then translate the values in de.json.

  3. Add the import to getDictionary

    typescript
    // src/app/[lang]/dictionaries.ts
    const dictionaries = {
      // ...existing locales...
      'de-DE': () => import('@/dictionaries/de.json').then((m) => m.default),
    };
  4. Update the Locale type

    typescript
    // src/dictionaries/type.ts
    export type Locale = 'en-US' | 'ar-SA' | 'es-ES' | 'fr-FR' | 'it-IT' | 'zh-CN' | 'de-DE';
  5. Add RTL detection if needed

    If the new locale is RTL (e.g. Hebrew he-IL), update the RTL check in the root layout:

    typescript
    // src/app/[lang]/layout.tsx
    const RTL_LOCALES = ['ar-SA', 'he-IL'];
    const isRTL = RTL_LOCALES.includes(lang);