# Smart App City — Internationalization (i18n) Guide ## Supported Languages | Language | Code | Status | Coverage | |----------|------|--------|----------| | Français | fr | ✅ Primary | 100% | | English | en | 🔄 Phase 1 | 100% | | Español | es | 📋 Phase 2 | 80% | | Deutsch | de | 📋 Phase 2 | 80% | ## Architecture ### Frontend (React Native) ```typescript // frontend/src/i18n/index.ts import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; import fr from './locales/fr.json'; import en from './locales/en.json'; import es from './locales/es.json'; import de from './locales/de.json'; i18n.use(initReactI18next).init({ resources: { fr, en, es, de }, lng: 'fr', fallbackLng: 'en', interpolation: { escapeValue: false }, }); ``` ### Translation Files Structure ``` frontend/src/i18n/locales/ ├── fr.json (source of truth) ├── en.json ├── es.json └── de.json ``` ### Example Translation File ```json { "common": { "welcome": "Bienvenue", "loading": "Chargement...", "error": "Erreur", "retry": "Réessayer", "cancel": "Annuler" }, "dashboard": { "title": "Tableau de bord", "weather": "Météo", "airQuality": "Qualité de l'air", "traffic": "Trafic", "notifications": "Notifications" }, "transport": { "title": "Transport", "bus": "Bus", "parking": "Parking", "taxi": "Taxi", "bike": "Vélo", "searchPlaceholder": "Où allez-vous?" }, "report": { "title": "Signalement", "category": "Catégorie", "description": "Description", "photo": "Photo", "location": "Position", "submit": "Envoyer", "success": "Signalement envoyé!" } } ``` ### ICU MessageFormat for Pluralization ```json { "notifications": { "count": "{count, plural, =0 {Aucune notification} =1 {1 notification} other {{count} notifications}}" }, "sensors": { "active": "{count, plural, =0 {Aucun capteur actif} =1 {1 capteur actif} other {{count} capteurs actifs}}" } } ``` ## Backend (NestJS) ### Language Detection优先级 1. User preference (stored in profile) 2. `Accept-Language` header 3. Device locale 4. Default: French ### Response Headers ```typescript // backend/common/interceptors/i18n.interceptor.ts @Injectable() export class I18nInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler) { const request = context.switchToHttp().getRequest(); const lang = request.headers['accept-language']?.split(',')[0] || 'fr'; return next.handle().pipe( map(data => ({ ...data, _meta: { language: lang, timestamp: new Date().toISOString() } })) ); } } ``` ## Content Translation Strategy ### Static Content (UI Strings) - ✅ Manual translation (high quality) - ✅ i18n JSON files in repo - ✅ LLM-assist for initial translation (Phase 2: ES, DE) ### Dynamic Content (Database) - ✅ PostgreSQL `jsonb` multilingual fields - ✅ Example: `{ "fr": "Parc de stationnement", "en": "Parking lot", "es": "Aparcamiento" }` ```sql -- Database schema for multilingual content CREATE TABLE poi ( id UUID PRIMARY KEY, name JSONB NOT NULL, -- {"fr": "...", "en": "...", "es": "..."} description JSONB, location GEOGRAPHY(POINT), category VARCHAR(50), created_at TIMESTAMPTZ DEFAULT NOW() ); -- Query with language fallback SELECT COALESCE(name->>'fr', name->>'en') as name, COALESCE(description->>'fr', description->>'en') as description FROM poi WHERE id = $1; ``` ### API Content (Real-time data) - ⚠️ English (data source language) - ✅ LLM translation for user-facing text - ⚠️ No translation for sensor values (numbers are universal) ## Quality Assurance ### Automated Checks ```bash # Check for missing translations npm run i18n:check # Output: # ❌ Missing ES translation: "report.success" # ❌ Missing DE translation: "transport.searchPlaceholder" ``` ### Community Translation - Crowdin integration for community contributions - GitHub Actions sync PO files - Monthly translation sprints