Aller au contenu principal

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 :

LangueCodeStatut
FrancaisfrPrincipal
PortugaisptComplet
AnglaisenComplet
CrioulocrPartiel

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

  1. Ajoutez la cle dans fr.json (langue principale)
  2. Copiez dans les autres fichiers de langue
  3. 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

OutilUsage
i18next-parserExtraction automatique des cles
BabelEditEdition visuelle JSON
CrowdinPlateforme 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)