Internationalisation (i18n)
Cette page documente le support multilingue de l'application PCH-SIG.
Vue d'ensemble
PCH-SIG supporte plusieurs langues pour s'adapter aux utilisateurs de differentes regions :
| Langue | Code | Statut |
|---|---|---|
| Francais | fr | Principal |
| Portugais | pt | Complet |
| Anglais | en | Complet |
| Crioulo | cr | Partiel |
Architecture
Frontend (React)
src/
├── i18n/
│ ├── index.ts # Configuration i18next
│ └── locales/
│ ├── fr.json # Francais
│ ├── pt.json # Portugais
│ ├── en.json # Anglais
│ └── cr.json # Crioulo
Backend (Symfony)
translations/
├── messages.fr.yaml
├── messages.pt.yaml
├── messages.en.yaml
├── validators.fr.yaml
├── validators.pt.yaml
└── validators.en.yaml
Frontend
Configuration i18next
// src/i18n/index.ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import fr from './locales/fr.json';
import pt from './locales/pt.json';
import en from './locales/en.json';
import cr from './locales/cr.json';
i18n.use(initReactI18next).init({
resources: {
fr: { translation: fr },
pt: { translation: pt },
en: { translation: en },
cr: { translation: cr },
},
lng: localStorage.getItem('language') || 'fr',
fallbackLng: 'fr',
interpolation: {
escapeValue: false,
},
});
export default i18n;
Structure des fichiers de traduction
// fr.json
{
"common": {
"save": "Enregistrer",
"cancel": "Annuler",
"delete": "Supprimer",
"edit": "Modifier",
"loading": "Chargement..."
},
"menu": {
"dashboard": "Tableau de bord",
"menages": "Menages",
"beneficiaires": "Beneficiaires",
"paiements": "Paiements",
"plaintes": "Plaintes"
},
"menages": {
"title": "Liste des menages",
"create": "Nouveau menage",
"fields": {
"code": "Code menage",
"tailleMenage": "Taille du menage",
"region": "Region"
}
}
}
Utilisation dans les composants
import { useTranslation } from 'react-i18next';
function MenagesList() {
const { t } = useTranslation();
return (
<div>
<h1>{t('menages.title')}</h1>
<button>{t('menages.create')}</button>
<table>
<thead>
<tr>
<th>{t('menages.fields.code')}</th>
<th>{t('menages.fields.tailleMenage')}</th>
</tr>
</thead>
</table>
</div>
);
}
Changement de langue
import { useTranslation } from 'react-i18next';
function LanguageSelector() {
const { i18n } = useTranslation();
const changeLanguage = (lang: string) => {
i18n.changeLanguage(lang);
localStorage.setItem('language', lang);
};
return (
<select
value={i18n.language}
onChange={(e) => changeLanguage(e.target.value)}
>
<option value="fr">Francais</option>
<option value="pt">Portugues</option>
<option value="en">English</option>
<option value="cr">Kriol</option>
</select>
);
}
Backend
Configuration Symfony
# config/packages/translation.yaml
framework:
default_locale: fr
translator:
default_path: '%kernel.project_dir%/translations'
fallbacks:
- fr
Fichiers de traduction
# translations/messages.fr.yaml
menage:
created: "Menage cree avec succes"
updated: "Menage mis a jour"
deleted: "Menage supprime"
error:
not_found: "Element non trouve"
validation: "Erreur de validation"
Utilisation dans les controleurs
use Symfony\Contracts\Translation\TranslatorInterface;
class MenageController extends AbstractController
{
public function __construct(
private TranslatorInterface $translator
) {}
public function create(): JsonResponse
{
// ...
return $this->json([
'success' => true,
'message' => $this->translator->trans('menage.created')
]);
}
}
Detection de la langue
class LocaleListener implements EventSubscriberInterface
{
public function onKernelRequest(RequestEvent $event): void
{
$request = $event->getRequest();
// Priorite: Header > Query > Cookie > Default
$locale = $request->headers->get('Accept-Language')
?? $request->query->get('lang')
?? $request->cookies->get('lang')
?? 'fr';
$request->setLocale(substr($locale, 0, 2));
}
}
Documentation (Docusaurus)
Configuration i18n
// docusaurus.config.js
module.exports = {
i18n: {
defaultLocale: 'fr',
locales: ['fr', 'en'],
localeConfigs: {
fr: {
label: 'Francais',
},
en: {
label: 'English',
},
},
},
};
Structure des fichiers
documentation/
├── docs/ # Francais (defaut)
│ └── guide/
│ └── introduction.md
└── i18n/
└── en/
└── docusaurus-plugin-content-docs/
└── current/
└── guide/
└── introduction.md
Bonnes pratiques
Organisation des cles
{
"module": {
"action": {
"label": "Texte"
}
}
}
Exemple :
{
"menages": {
"create": {
"title": "Creer un menage",
"submit": "Enregistrer"
}
}
}
Pluralisation
{
"menages": {
"count": "{{count}} menage",
"count_plural": "{{count}} menages"
}
}
t('menages.count', { count: 5 }) // "5 menages"
Variables
{
"welcome": "Bienvenue, {{name}}!"
}
t('welcome', { name: user.nom }) // "Bienvenue, John!"
Workflow de traduction
1. Ajouter une nouvelle cle
- Ajoutez la cle dans
fr.json(langue principale) - Copiez dans les autres fichiers de langue
- Traduisez chaque fichier
2. Scripts d'aide
# Verifier les cles manquantes
node scripts/check-translations.js
# Ajouter une cle a toutes les langues
node scripts/add-translation.js "menages.new.key" "Texte francais"
3. Validation
# Tests de traduction
npm run test:i18n
Outils recommandes
| Outil | Usage |
|---|---|
| i18next-parser | Extraction automatique des cles |
| BabelEdit | Edition visuelle JSON |
| Crowdin | Plateforme de traduction collaborative |
Checklist nouvelle langue
- Creer le fichier de traduction frontend
- Creer le fichier de traduction backend
- Ajouter l'option dans le selecteur de langue
- Mettre a jour la documentation
- Tester l'affichage RTL si necessaire
- Verifier les formats de date/nombre
Formats regionaux
Dates
// Utiliser date-fns avec locale
import { format } from 'date-fns';
import { fr, pt, enUS } from 'date-fns/locale';
const locales = { fr, pt, en: enUS };
format(new Date(), 'PPP', {
locale: locales[i18n.language]
});
Nombres
new Intl.NumberFormat(i18n.language, {
style: 'currency',
currency: 'XOF'
}).format(15000);
// "15 000 XOF" (fr)
// "15,000 XOF" (en)