This document describes the internationalization (i18n) architecture, translation workflow, and contribution process for the Scavngr frontend.
| Code | Language | Direction |
|---|---|---|
en |
English | LTR |
es |
Español | LTR |
fr |
Français | LTR |
zh |
中文 | LTR |
ar |
العربية | RTL |
The frontend uses i18next with react-i18next bindings.
frontend/src/i18n/
├── config.ts # i18next init, RTL helpers
├── locales/
│ ├── en.json # English (source of truth)
│ ├── es.json # Spanish
│ ├── fr.json # French
│ ├── zh.json # Chinese (Simplified)
│ └── ar.json # Arabic (RTL)
└── __tests__/
└── i18n.test.ts # Translation coverage tests
All translations live in a single translation namespace, organized by domain:
| Namespace | Description |
|---|---|
common.* |
Shared UI labels (Save, Cancel...) |
nav.* |
Navigation items |
waste.* |
Waste management strings |
auth.* |
Wallet connection strings |
participants.* |
Participant roles and labels |
rewards.* |
Reward-related strings |
profile.* |
User profile strings |
errors.* |
Error messages |
settings.* |
Settings page labels |
import { useTranslation } from 'react-i18next';
function MyComponent() {
const { t } = useTranslation();
return <h1>{t('waste.title')}</h1>;
}Use the LanguageSwitcher component:
import { LanguageSwitcher } from '@/components/LanguageSwitcher';
function Header() {
return <header><LanguageSwitcher /></header>;
}Use the useRTL hook to conditionally apply RTL-aware styles:
import { useRTL } from '@/hooks/useRTL';
function Sidebar() {
const { dir } = useRTL();
return <aside dir={dir}>...</aside>;
}The i18n config also automatically sets document.documentElement.dir on language change, so CSS logical properties (margin-inline-start, padding-inline-end, etc.) work without per-component logic.
- Add to
en.json(source of truth):
{
"section": {
"myNewKey": "My English string"
}
}-
Add to all other locale files (
es.json,fr.json,zh.json,ar.json) with the translated value. -
Add a test in
__tests__/i18n.test.tsverifying the English key resolves correctly. -
Open a PR — CI will catch missing keys via TypeScript if you use the typed
t()helper.
- Create
frontend/src/i18n/locales/<code>.jsonwith all existing keys translated. - Import and register it in
config.ts:
import de from './locales/de.json';
const resources = {
// ...existing
de: { translation: de },
};- Add the language entry to
LANGUAGESinLanguageSwitcher.tsx. - If the language is RTL, add its code to
RTL_LANGUAGESinconfig.ts.
Run unit tests:
cd frontend
npm test -- --testPathPattern=i18nTests verify:
- All supported languages load without errors.
- Key translations return expected strings.
- RTL language detection works for Arabic.
- Language preference persists in localStorage.
- Arabic (
ar) is the only RTL language currently supported. document.documentElement.diris set automatically via thelanguageChangedevent inconfig.ts.- Use CSS logical properties for layouts that must flip in RTL (e.g.,
margin-inline-startinstead ofmargin-left). - Test RTL layouts by switching to Arabic in the UI.
- Fork the repository.
- Add or modify keys in
en.jsonwith your changes. - Update all other locale files with translated strings. If you don't speak a language, add a
TODOcomment or open the PR with onlyen.jsonupdated and tag a native speaker for review. - Run
npm testto ensure all translation tests pass. - Submit a PR targeting
main— include the issue number in the title.