- Architecture globale (React Native + NestJS + FastAPI) - Beckn Protocol (OTN-DPI) integration docs - AI layer: RAG pipeline + AI Agents (LocalAI + Qdrant) - i18n: FR/EN/ES/DE support - Docker Compose for backend services - Project structure with frontend, backend, ai, beckn directories
4.0 KiB
4.0 KiB
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)
// 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
{
"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
{
"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优先级
- User preference (stored in profile)
Accept-Languageheader- Device locale
- Default: French
Response Headers
// 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
jsonbmultilingual fields - ✅ Example:
{ "fr": "Parc de stationnement", "en": "Parking lot", "es": "Aparcamiento" }
-- 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
# 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