Internationalization

Jumbo React Vite uses i18next and react-i18next to support six languages. All translations are bundled at build time as TypeScript modules — no server required.

Supported languages

CodeLanguageRTL
enEnglishNo
arArabicYes
esSpanishNo
frFrenchNo
itItalianNo
zhChineseNo

i18next configuration

i18next is initialised in src/i18n.ts and imported in src/main.tsx before the app mounts:

typescript
// src/i18n.ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import { arTranslation } from './translations/ar';
import { enTranslation } from './translations/en';
// ... other imports
 
i18n.use(initReactI18next).init({
  lng: 'en',
  fallbackLng: 'en',
  interpolation: { escapeValue: false },
  resources: {
    en: { translation: enTranslation },
    ar: { translation: arTranslation },
    es: { translation: esTranslation },
    fr: { translation: frTranslation },
    it: { translation: itTranslation },
    zh: { translation: zhTranslation },
  },
});
 
export default i18n;

All languages share a single translation namespace with nested key objects.

Translation file structure

Translation files are TypeScript modules that export a plain object. Keys follow a nested structure grouping related strings by feature:

typescript
// src/translations/en.ts (excerpt)
export const enTranslation = {
  sidebar: {
    menu: {
      dashboards: 'Dashboards',
      apps: 'Apps',
      chat: 'Chat',
      contacts: 'Contacts',
    },
  },
  pages: {
    login: {
      signIn: 'Sign In',
      email: 'Email address',
      password: 'Password',
      forgotPassword: 'Forgot password?',
    },
  },
  widgets: {
    // ...
  },
};

Using translations in components

Import and call the useTranslation hook from react-i18next:

tsx
import { useTranslation } from 'react-i18next';
 
export function LoginForm() {
  const { t } = useTranslation();
 
  return (
    <form>
      <label>{t('pages.login.email')}</label>
      <input type='email' />
      <label>{t('pages.login.password')}</label>
      <input type='password' />
      <button type='submit'>{t('pages.login.signIn')}</button>
    </form>
  );
}

Changing the active language at runtime

Call i18n.changeLanguage() with the language code:

typescript
import i18n from 'i18next';
 
// Switch to French
i18n.changeLanguage('fr');

The SelectLanguage components inside each layout's header demonstrate this pattern. They render a <MenuItem> for each supported locale and call i18n.changeLanguage() on selection.

Adding a new language

  1. Create the translation file

    typescript
    // src/translations/de.ts
    export const deTranslation = {
      sidebar: {
        menu: {
          dashboards: 'Dashboards',
          apps: 'Apps',
          chat: 'Chat',
          contacts: 'Kontakte',
        },
      },
      pages: {
        login: {
          signIn: 'Anmelden',
          email: 'E-Mail-Adresse',
          password: 'Passwort',
          forgotPassword: 'Passwort vergessen?',
        },
      },
    };
  2. Register the language in i18n.ts

    typescript
    // src/i18n.ts
    import { deTranslation } from './translations/de';
     
    i18n.use(initReactI18next).init({
      // ... existing config
      resources: {
        // ... existing languages
        de: { translation: deTranslation },
      },
    });
  3. Add to the language switcher UI

    Add the new locale to the language options array in the header SelectLanguage component for each layout that has one.

RTL support for Arabic

When ar is selected, the layout needs to switch to RTL. This requires two changes:

  1. Set theme.direction: 'rtl' on the active theme (handled by JumboRTL — see JumboRTL docs)
  2. Update the dir attribute on the root <html> element:
typescript
i18n.on('languageChanged', (lng) => {
  document.documentElement.setAttribute('dir', lng === 'ar' ? 'rtl' : 'ltr');
});