i18n.ts - Internationalisation
Localisation : frontend/src/lib/i18n.ts
Configuration de l’internationalisation (i18n) avec support 4 langues : néerlandais, français, allemand, anglais.
Configuration
Bibliothèque : svelte-i18n
import { addMessages, init, getLocaleFromNavigator } from "svelte-i18n";
import nl from "../locales/nl.json";
import fr from "../locales/fr.json";
import de from "../locales/de.json";
import en from "../locales/en.json";
// Enregistrer toutes les traductions
addMessages("nl", nl);
addMessages("fr", fr);
addMessages("de", de);
addMessages("en", en);
// Initialiser avec détection navigateur
init({
fallbackLocale: "nl", // Néerlandais par défaut
initialLocale: getLocaleFromNavigator()
});
Fallback Logic :
Détecter langue navigateur (
getLocaleFromNavigator())Si non supportée, fallback sur
nl(néerlandais)
Langues Supportées
export const languages = [
{ code: "nl", name: "Nederlands", flag: "🇳🇱", priority: 1 },
{ code: "fr", name: "Français", flag: "🇫🇷", priority: 2 },
{ code: "de", name: "Deutsch", flag: "🇩🇪", priority: 3 },
{ code: "en", name: "English", flag: "🇬🇧", priority: 4 }
] as const;
export type LanguageCode = (typeof languages)[number]["code"];
Priorités :
Nederlands (nl) : 60% de la Belgique (Flandre)
Français (fr) : 40% de la Belgique (Wallonie + Bruxelles)
Deutsch (de) : Communauté germanophone de Belgique
English (en) : International (syndics multinationales)
Statistiques Belgique :
🇳🇱 Néerlandais : 60% (Flandre)
🇫🇷 Français : 40% (Wallonie + Bruxelles bilingue)
🇩🇪 Allemand : < 1% (Communauté germanophone)
Structure Fichiers Traduction
Localisation : frontend/src/locales/
locales/
├── nl.json # Néerlandais (référence)
├── fr.json # Français
├── de.json # Allemand
└── en.json # Anglais
Format JSON plat :
{
"nav.dashboard": "Dashboard",
"nav.buildings": "Immeubles",
"nav.owners": "Copropriétaires",
"nav.expenses": "Charges",
"nav.meetings": "Assemblées",
"nav.documents": "Documents",
"nav.reports": "Rapports",
"nav.settings": "Paramètres",
"building.create": "Créer un immeuble",
"building.name": "Nom de l'immeuble",
"building.address": "Adresse",
"building.city": "Ville",
"building.total_units": "Nombre de lots",
"error.network": "Erreur réseau",
"error.unauthorized": "Non autorisé",
"success.saved": "Enregistré avec succès"
}
Conventions de Nommage :
[section].[clé]: ex:nav.dashboard,building.createUtiliser snake_case pour les clés :
total_unitspastotalUnitsPréfixes communs :
nav.*,error.*,success.*,button.*
Utilisation dans Components
Import :
<script lang="ts">
import { _ } from 'svelte-i18n';
</script>
Dans le Template :
<h1>{$_('nav.dashboard')}</h1>
<button>{$_('building.create')}</button>
<p>{$_('error.network')}</p>
Avec Paramètres :
{
"welcome.message": "Bienvenue, {name}!",
"building.units_count": "{count} lot(s)"
}
<h1>{$_('welcome.message', { values: { name: user.firstName } })}</h1>
<p>{$_('building.units_count', { values: { count: building.total_units } })}</p>
Pluralisation :
{
"building.units": "{count, plural, =0 {aucun lot} one {1 lot} other {# lots}}"
}
<p>{$_('building.units', { values: { count: totalUnits } })}</p>
Format Dates/Nombres :
<script>
import { date, number } from 'svelte-i18n';
</script>
<p>{$date(new Date(), { format: 'short' })}</p>
<p>{$number(1234.56, { style: 'currency', currency: 'EUR' })}</p>
Changement de Langue
LanguageSelector Component :
<script lang="ts">
import { locale } from 'svelte-i18n';
import { languages } from '../lib/i18n';
function changeLanguage(code: string) {
$locale = code;
localStorage.setItem('koprogo_locale', code);
}
</script>
<select bind:value={$locale} on:change={(e) => changeLanguage(e.target.value)}>
{#each languages as lang}
<option value={lang.code}>
{lang.flag} {lang.name}
</option>
{/each}
</select>
Persistance :
// Sauvegarder préférence
locale.subscribe(value => {
if (value) {
localStorage.setItem('koprogo_locale', value);
}
});
// Restaurer au chargement
const savedLocale = localStorage.getItem('koprogo_locale');
if (savedLocale) {
locale.set(savedLocale);
}
Intégration Backend
Le header Accept-Language est automatiquement envoyé dans api.ts :
// frontend/src/lib/api.ts
function getCurrentLanguage(): string {
const currentLocale = get(locale);
return currentLocale || "nl";
}
function getHeaders(): HeadersInit {
return {
"Accept-Language": getCurrentLanguage(),
// ...
};
}
Le backend peut lire ce header pour renvoyer messages d’erreur localisés :
// backend/src/infrastructure/web/handlers/
use actix_web::HttpRequest;
fn get_language(req: &HttpRequest) -> String {
req.headers()
.get("accept-language")
.and_then(|v| v.to_str().ok())
.unwrap_or("nl")
.to_string()
}
Maintenance Traductions
Workflow :
Ajouter clé dans nl.json (langue référence)
Traduire dans fr.json, de.json, en.json
Utiliser dans composants :
$_('nouvelle.cle')Tester changement de langue : Sélecteur de langue
Script de Vérification :
# Trouver clés manquantes
npm run check-i18n
# Générer rapport différences
npm run i18n-diff
Outil Recommandé : i18n-ally (VS Code extension)
Visualisation inline des traductions
Détection clés manquantes
Édition multi-langues simultanée
Clés Manquantes
Si une clé n’existe pas, svelte-i18n affiche la clé elle-même :
{$_('cle.inexistante')}
<!-- Affiche: "cle.inexistante" -->
En développement : Ajouter warning console
init({
fallbackLocale: "nl",
warnOnMissingMessages: true // Warning si clé manquante
});
Tests i18n
// tests/unit/i18n.test.ts
import { get } from 'svelte/store';
import { _, locale } from 'svelte-i18n';
import '../src/lib/i18n';
describe('i18n', () => {
it('should load French translations', async () => {
locale.set('fr');
await new Promise(resolve => setTimeout(resolve, 100));
const translation = get(_)('nav.dashboard');
expect(translation).toBe('Tableau de bord');
});
it('should fallback to Dutch if key missing', async () => {
locale.set('en');
await new Promise(resolve => setTimeout(resolve, 100));
const translation = get(_)('some.missing.key');
expect(translation).toBeTruthy();
});
});
SEO et HTML lang
Le frontend doit mettre à jour <html lang="..."> :
---
// layouts/Layout.astro
import { locale } from 'svelte-i18n';
const currentLocale = locale.get() || 'nl';
---
<html lang={currentLocale}>
Pour SEO multilingue :
<head>
<link rel="alternate" hreflang="nl" href="https://koprogo.com/nl" />
<link rel="alternate" hreflang="fr" href="https://koprogo.com/fr" />
<link rel="alternate" hreflang="de" href="https://koprogo.com/de" />
<link rel="alternate" hreflang="en" href="https://koprogo.com/en" />
</head>
Extensions Futures
Routes localisées :
/nl/dashboard→ Nederlands/fr/tableau-de-bord→ Français
Traduction dynamique :
Charger traductions depuis API (CMS, base de données).
Traduction automatique :
Utiliser DeepL API pour générer traductions initiales.
Format de dates régional :
nl : dd/mm/yyyy
fr : dd/mm/yyyy
de : dd.mm.yyyy
en : mm/dd/yyyy
Devise régionale :
Belgique : EUR (€)
Format : 1.234,56 € (nl/fr/de) vs 1,234.56 € (en)
Références
Documentation svelte-i18n : https://github.com/kaisermann/svelte-i18n
Traductions :
frontend/src/locales/LanguageSelector :
frontend/src/components/LanguageSelector.svelteAPI Integration :
frontend/src/lib/api.ts