feat: add smart-app-city sub-project architecture
- 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
This commit is contained in:
167
smart-app-city/docs/I18N.md
Normal file
167
smart-app-city/docs/I18N.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user