frontend_nyla/docs/LANGUAGE_ARCHITECTURE.md

362 lines
13 KiB
Markdown

# Language Architecture - Single Source of Truth
## ✅ Correct Architecture (Current)
### Single Source of Truth
```
User Profile in Database → localStorage('currentUser').language → UI
```
**There is NO separate `localStorage.language` storage!**
---
## 📊 Data Flow
### 1. On Login
```
User logs in
Backend authenticates
GET /api/*/me returns User object
{
username: "user@example.com",
privilege: "admin",
language: "de", ← Language is part of user data
...
}
Store ONCE in localStorage:
localStorage.setItem('currentUser', JSON.stringify(userData))
LanguageContext reads: currentUser.language
UI displays in correct language ✅
```
### 2. When User Changes Language
```
User selects new language in settings
Settings component updates backend:
PUT /api/users/{id} with { language: "fr" }
Backend returns updated user object
Update localStorage('currentUser') with new data ✅
localStorage.setItem('currentUser', JSON.stringify(updatedUser))
Call setLanguage(newLanguage)
LanguageContext loads new translations
Trigger 'userInfoUpdated' event
All components sync with new language ✅
```
### 3. On Page Load/Refresh
```
App initializes
LanguageContext checks:
1. localStorage('currentUser').language ← Primary source
2. Browser language (navigator.language) ← Fallback if no user data
Load translations for selected language
UI displays in correct language ✅
```
---
## 🎯 Priority System
### Language Resolution Order:
```typescript
Priority 1: currentUser.language From database (logged-in users)
Priority 2: Browser language Fallback (before login or no user data)
Priority 3: Default 'de' Ultimate fallback
```
### Why No `localStorage.language`?
**Before (Wrong):**
```typescript
// ❌ Multiple sources of truth - can get out of sync!
localStorage.setItem('language', 'fr'); // UI preference
localStorage.setItem('currentUser', { language: 'de' }); // Backend data
// ^ Which one is correct? 🤔
```
**After (Correct):**
```typescript
// ✅ Single source of truth - always in sync!
localStorage.setItem('currentUser', { language: 'fr' }); // ONLY source
// ^ Always matches backend! 🎯
```
---
## 💻 Code Implementation
### LanguageContext.tsx
```typescript
// On mount: Read from currentUser.language
useEffect(() => {
const currentUserData = localStorage.getItem('currentUser');
if (currentUserData) {
const userData = JSON.parse(currentUserData);
if (userData.language) {
initialLanguage = userData.language; // ✅ From user profile
}
} else {
// Fallback to browser language if no user data
initialLanguage = navigator.language;
}
loadAndSetLanguage(initialLanguage);
}, []);
// When user updates language
const setLanguage = async (language: Language) => {
await loadAndSetLanguage(language);
// Note: This should ONLY be called AFTER:
// 1. Backend is updated
// 2. localStorage('currentUser') is updated
// The settings component handles this flow
};
```
### settingsUser.tsx
```typescript
const handleSaveUserInfo = async () => {
// 1. Update backend
const updatedUser = await updateUser(user.id, {
...userData,
language: newLanguage
});
// 2. Update localStorage (single source of truth!)
localStorage.setItem('currentUser', JSON.stringify(updatedUser));
// 3. Update UI language
if (newLanguage !== currentLanguage) {
await setLanguage(newLanguage);
}
// 4. Notify other components
window.dispatchEvent(new CustomEvent('userInfoUpdated'));
};
```
### useAuthentication.ts
```typescript
// On login: Fetch and cache user data
const userResponse = await api.get('/api/local/me');
if (userResponse.data) {
// Store user data ONCE (includes language)
localStorage.setItem('currentUser', JSON.stringify(userResponse.data));
// ✅ No separate language storage!
}
```
---
## 🔄 Complete Flow Diagram
```
┌──────────────────────────────────────────────────────────┐
│ USER LOGS IN │
└────────────────────┬─────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ GET /api/*/me returns: │
│ { username, privilege, language: "de", ... } │
└────────────────────┬─────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ localStorage('currentUser') = userData │
│ ✅ Language is part of user data │
└────────────────────┬─────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ LanguageContext reads: currentUser.language │
│ Loads translations for 'de' │
└────────────────────┬─────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ UI displays in German ✅ │
└──────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ USER CHANGES LANGUAGE TO FRENCH │
└────────────────────┬─────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ PUT /api/users/{id} │
│ { language: "fr" } │
└────────────────────┬─────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ Backend returns: │
│ { username, privilege, language: "fr", ... } │
└────────────────────┬─────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ localStorage('currentUser') = updatedUserData │
│ ✅ Language updated in user data │
└────────────────────┬─────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ setLanguage('fr') called │
│ Loads French translations │
└────────────────────┬─────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ UI displays in French ✅ │
└──────────────────────────────────────────────────────────┘
```
---
## 🧪 Testing
### Test 1: Login with Different Languages
```bash
# User with language='de'
1. Log in
2. Check console: "🌍 Using language from user profile: de"
3. Check localStorage: currentUser.language === 'de'
4. Verify UI is in German ✅
# User with language='fr'
1. Log in
2. Check console: "🌍 Using language from user profile: fr"
3. Check localStorage: currentUser.language === 'fr'
4. Verify UI is in French ✅
```
### Test 2: Change Language in Settings
```bash
1. Log in with language='de'
2. Go to settings
3. Change language to 'fr'
4. Click Save
5. Check console:
- "✅ User update successful"
- "💾 Updated user data cached in localStorage"
- "🌍 Frontend language updated to: fr"
6. Check localStorage: currentUser.language === 'fr'
7. Verify UI immediately changes to French ✅
8. Refresh page
9. Verify UI is still in French ✅
```
### Test 3: Multiple Browser Tabs
```bash
1. Open app in two tabs
2. In Tab 1: Change language to 'fr'
3. In Tab 2: Reload page
4. Both tabs should display in French ✅
(Because both read from currentUser.language)
```
### Test 4: Before Login (No User Data)
```bash
1. Clear localStorage
2. Open app
3. Should use browser language as fallback
4. After login, should switch to user's profile language ✅
```
---
## 🎯 Benefits of Single Source of Truth
| Aspect | Before (Multiple Sources) | After (Single Source) |
|--------|--------------------------|----------------------|
| **Consistency** | ❌ Can get out of sync | ✅ Always in sync |
| **Simplicity** | ❌ Check multiple places | ✅ One place to check |
| **Reliability** | ❌ Which source is correct? | ✅ Always correct |
| **Maintenance** | ❌ Update multiple places | ✅ Update one place |
| **Debugging** | ❌ Hard to trace issues | ✅ Easy to trace |
---
## 📋 Key Points
1. **User language is part of user data** - stored in `localStorage('currentUser').language`
2. **No separate language storage** - eliminates redundancy and sync issues
3. **Backend is the source of truth** - frontend always syncs with backend
4. **Settings update flow:**
- Update backend → Receive updated user → Cache in localStorage → Update UI
5. **Language changes persist** - because they're stored in the user profile in the database
---
## 🚫 Anti-Patterns to Avoid
### ❌ Don't do this:
```typescript
// Don't store language separately
localStorage.setItem('language', 'fr');
// Don't read from separate storage
const lang = localStorage.getItem('language');
// Don't update UI before backend
setLanguage('fr'); // Then update backend
```
### ✅ Do this instead:
```typescript
// Update backend first
const updatedUser = await updateUser(id, { language: 'fr' });
// Cache the complete user data
localStorage.setItem('currentUser', JSON.stringify(updatedUser));
// Then update UI
setLanguage(updatedUser.language);
```
---
## 📁 Related Files
- `src/contexts/LanguageContext.tsx` - Language context implementation
- `src/components/settings/settingsUser.tsx` - User settings with language update
- `src/hooks/useAuthentication.ts` - Login flow with user data fetch
- `src/hooks/useUsers.ts` - User data management
---
## 🔄 Migration Notes
If you had existing code that used `localStorage.language`:
### Before:
```typescript
const lang = localStorage.getItem('language') || 'de';
```
### After:
```typescript
const currentUser = JSON.parse(localStorage.getItem('currentUser') || '{}');
const lang = currentUser.language || navigator.language || 'de';
```
All existing references should now use `currentUser.language` exclusively.