Skip to main content

Internationalization system (i18n)

E-Cashpoint Web uses react-i18next with 3 languages supported: German (default), Spanish and English.

Configuration​

The system is initialized in src/i18n/index.js:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import es from './locales/es.json';
import de from './locales/de.json';
import en from './locales/en.json';

const resources = {
es: { translation: es },
de: { translation: de },
en: { translation: en },
};

i18n
.use(initReactI18next)
.init({
resources,
lng: 'de', // idioma por defecto: alemán
fallbackLng: 'de',
interpolation: { escapeValue: false },
});

export default i18n;

File structure​

src/i18n/
├── index.js
└── locales/
├── de.json (alemán - fuente de verdad)
├── en.json (inglés)
└── es.json (español)

Each file has +1700 keys organized in namespaces:

{
"common": { ... },
"auth": { ... },
"client": { ... },
"comercio": { ... },
"setup": { ... },
"demoMarketing": { ... },
"handelsPanel": { ... },
"servicesPanel": { ... },
"restaurantCatalog": { ... },
...
}

Use in components​

Basic hook​

import { useTranslation } from 'react-i18next';

function MiComponente() {
const { t, i18n } = useTranslation();

return (
<div>
<h1>{t('common.welcome')}</h1>
<p>Idioma actual: {i18n.language}</p>
<button onClick={() => i18n.changeLanguage('es')}>
Español
</button>
</div>
);
}

With interpolation​

// de.json
{
"user": {
"greeting": "Hallo, {name}!"
}
}
<h1>{t('user.greeting', { name: 'María' })}</h1>
// Result: "Hallo, María!"

With pluralization​

{
"cart": {
"items": "{{count}} Artikel",
"items_plural": "{{count}} Artikel"
}
}
<span>{t('cart.items', { count: items.length })}</span>

###Fallback

If a key does not exist in the current language, fallbackLng (German) is used:

t('non.existent.key')
// Devuelve "non.existent.key" si no existe en ningún idioma
// O usa el valor de fallbackLng si está disponible

Language change in runtime​

import { useTranslation } from 'react-i18next';

const { i18n } = useTranslation();

// Cambiar idioma
i18n.changeLanguage('es'); // a español
i18n.changeLanguage('en'); // a inglés
i18n.changeLanguage('de'); // a alemán

// Idioma actual
console.log(i18n.language); // 'es'

Add new translations​

Step 1: Add keys to the 3 files​

// de.json
{
"miModulo": {
"saludo": "Willkommen",
"despedida": "Auf Wiedersehen"
}
}

// en.json
{
"miModulo": {
"saludo": "Welcome",
"despedida": "Goodbye"
}
}

// es.json
{
"miModulo": {
"saludo": "Bienvenido",
"despedida": "Adiós"
}
}

Step 2: Use in the component​

const { t } = useTranslation();
return <h1>{t('miModulo.saludo')}</h1>;

Important rules​

✅ Do​

  • ✅ Use descriptive namespace: handelsPanel.modal.title instead of title1
  • ✅ Group by module/component: all keys of a modal go under modal.*
  • ✅ Reuse common keys: common.cancel, common.save instead of duplicating
  • ✅ Keep the 3 files synchronized (same keys in all 3)
  • ✅ Use parameters for dynamic values: t('user.greeting', { name })
  • ✅ Camelcase in keys: myModule.myKey (not snake_case)
  • ✅ Use fallback in German when a text is very specific

❌ Avoid​

  • ❌ Hardcode strings in German/Spanish/English in JSX
  • ❌ Create keys with special characters (only letters, numbers, periods, hyphens)
  • ❌ Mix languages in the same namespace
  • ❌ Forget keys in one of the 3 files
  • ❌ AI prompt marketing strings on i18n (they are data, not UI)

Pattern for arrays in data​

If you have arrays of objects that are rendered, use labelKey:

// En constants.js
export const MY_OPTIONS = [
{ value: 'a', labelKey: 'options.a' },
{ value: 'b', labelKey: 'options.b' },
];

// En el componente
{MY_OPTIONS.map(opt => (
<option key={opt.value} value={opt.value}>
{t(opt.labelKey)}
</option>
))}

Next steps​