Multilingual support with react-i18next#

When your application serves multiple language communities — a citizen portal with Hindi and English, an ERP with local + English — add react-i18next alongside the Palmyra component stack. Form labels, error messages, button text, and grid headers all pull from translation files.

Install#

npm install react-i18next i18next i18next-browser-languagedetector

Setup#

// src/i18n/i18n.ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';

import en from './locales/en.json';
import hi from './locales/hi.json';

i18n
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    resources: { en: { translation: en }, hi: { translation: hi } },
    fallbackLng: 'en',
    interpolation: { escapeValue: false },
    detection: {
      order: ['localStorage', 'navigator'],
      caches: ['localStorage'],
    },
  });

export default i18n;

Import it once in main.tsx:

import './i18n/i18n';

Translation files#

// src/i18n/locales/en.json
{
  "app.title": "Employee Management",
  "nav.departments": "Departments",
  "nav.employees": "Employees",
  "form.email": "Email",
  "form.firstName": "First Name",
  "form.lastName": "Last Name",
  "form.department": "Department",
  "form.save": "Save",
  "form.cancel": "Cancel",
  "form.required": "This field is required",
  "grid.search": "Search",
  "grid.add": "Add"
}
// src/i18n/locales/hi.json
{
  "app.title": "कर्मचारी प्रबंधन",
  "nav.departments": "विभाग",
  "nav.employees": "कर्मचारी",
  "form.email": "ईमेल",
  "form.firstName": "पहला नाम",
  "form.lastName": "उपनाम",
  "form.department": "विभाग",
  "form.save": "सहेजें",
  "form.cancel": "रद्द करें",
  "form.required": "यह फ़ील्ड आवश्यक है",
  "grid.search": "खोजें",
  "grid.add": "जोड़ें"
}

Language switcher#

// src/components/LanguageSwitcher.tsx
import { Button, Group, Popover } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import { useDisclosure } from '@mantine/hooks';

const languages = [
  { code: 'en', label: 'English' },
  { code: 'hi', label: 'हिन्दी' },
];

export default function LanguageSwitcher() {
  const { i18n } = useTranslation();
  const [opened, { toggle, close }] = useDisclosure(false);

  return (
    <Popover opened={opened} onClose={close}>
      <Popover.Target>
        <Button variant="subtle" size="xs" onClick={toggle}>
          {languages.find(l => l.code === i18n.language)?.label ?? 'Language'}
        </Button>
      </Popover.Target>
      <Popover.Dropdown>
        <Group>
          {languages.map(l => (
            <Button key={l.code} size="xs"
                    variant={i18n.language === l.code ? 'filled' : 'subtle'}
                    onClick={() => { i18n.changeLanguage(l.code); close(); }}>
              {l.label}
            </Button>
          ))}
        </Group>
      </Popover.Dropdown>
    </Popover>
  );
}

Using in Palmyra form fields#

Palmyra’s MantineTextField accepts a label prop — pass the translated string:

import { useTranslation } from 'react-i18next';
import { MantineTextField } from '@palmyralabs/rt-forms-mantine';

function EmployeeForm() {
  const { t } = useTranslation();

  return (
    <FieldGroupContainer columns={2}>
      <MantineTextField attribute="loginName" label={t('form.email')} required
                        invalidMessage={t('form.required')} />
      <MantineTextField attribute="firstName" label={t('form.firstName')} required />
      <MantineTextField attribute="lastName"  label={t('form.lastName')}  required />
    </FieldGroupContainer>
  );
}

Grid column labels and toolbar buttons follow the same pattern — the t() call is the only change.

Guidelines#

  • Keep attribute in English. Only label, placeholder, invalidMessage, and button text go through t(). The attribute prop must match the backend’s field name.
  • Persist the choice. i18next-browser-languagedetector + localStorage means the user picks once and it sticks.
  • Add languages incrementally. Start with two; each new language is one JSON file.

See also: MantineTextField.