Skip to content

Commit 4d0356c

Browse files
authored
Merge pull request #2184 from bluewave-labs/feat/subscription-table-improvements
Merged as per confirmation from Aryaman.
2 parents c677072 + 297df48 commit 4d0356c

11 files changed

Lines changed: 1201 additions & 124 deletions

File tree

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
# VerifyWise Localization Implementation Guide
2+
3+
## 📋 Overview
4+
This document outlines the complete localization (i18n) implementation strategy for VerifyWise, including technical approach, implementation patterns, and rollback instructions.
5+
6+
## 🎯 Implementation Approach
7+
8+
### Technology Stack
9+
- **Library**: react-i18next (v15.7.3)
10+
- **Language Detection**: i18next-browser-languagedetector (v8.2.0)
11+
- **Core**: i18next (v25.5.2)
12+
- **Languages**: English (en), German (de) - expandable to more
13+
14+
### Architecture Decision
15+
We chose **react-i18next** over other solutions because:
16+
1. **React Integration**: Seamless hooks and component support
17+
2. **Browser Detection**: Automatic language detection from browser settings
18+
3. **Lazy Loading**: Can load translations on-demand (future optimization)
19+
4. **Interpolation**: Supports variables in translations
20+
5. **Pluralization**: Built-in plural forms support
21+
6. **TypeScript**: Full type safety support
22+
23+
## 📁 File Structure
24+
25+
```
26+
src/
27+
├── i18n/
28+
│ ├── i18n.ts # Main configuration file
29+
│ ├── locales/
30+
│ │ ├── en.json # English translations
31+
│ │ └── de.json # German translations
32+
│ └── utils/
33+
│ └── statusTranslations.ts # Dynamic translation utilities
34+
├── presentation/
35+
│ └── components/
36+
│ ├── LanguageSwitcher/ # Language selector component
37+
│ ├── StatusBadge/ # Example localized component
38+
│ └── LocalizationTest/ # Test component (removable)
39+
```
40+
41+
## 🔧 Implementation Patterns
42+
43+
### 1. Basic Component Localization
44+
```typescript
45+
// Import
46+
import { useTranslation } from "react-i18next";
47+
48+
// In component
49+
const MyComponent = () => {
50+
const { t } = useTranslation();
51+
52+
return (
53+
<div>
54+
<h1>{t('pageTitle')}</h1>
55+
<Button>{t('buttons.save')}</Button>
56+
</div>
57+
);
58+
};
59+
```
60+
61+
### 2. Dynamic Status/Value Translation
62+
```typescript
63+
// For dynamic values like status, risk levels
64+
import { useStatusTranslation } from "../../../i18n/utils/statusTranslations";
65+
66+
const Component = () => {
67+
const { translateStatus, translateRiskLevel } = useStatusTranslation();
68+
69+
return (
70+
<span>{translateStatus("In Progress")}</span> // → "In Bearbeitung" in German
71+
);
72+
};
73+
```
74+
75+
### 3. Translation File Structure
76+
```json
77+
// en.json
78+
{
79+
"sidebar": {
80+
"dashboard": "Dashboard",
81+
"riskManagement": "Risk Management"
82+
},
83+
"status": {
84+
"inprogress": "In Progress",
85+
"completed": "Completed"
86+
},
87+
"riskLevel": {
88+
"high": "High Risk",
89+
"medium": "Medium Risk"
90+
}
91+
}
92+
```
93+
94+
### 4. Passing Translation to Non-Hook Functions
95+
```typescript
96+
// When you can't use hooks (e.g., in utility functions)
97+
const getMenuItems = (t: any) => [
98+
{ name: t('sidebar.dashboard') }
99+
];
100+
101+
// In component
102+
const { t } = useTranslation();
103+
const menu = getMenuItems(t);
104+
```
105+
106+
## 📊 Translation Coverage Analysis
107+
108+
### Current Status (What Was Implemented)
109+
| Component | Strings | Status |
110+
|-----------|---------|--------|
111+
| Sidebar Menu | ~20 | ✅ Complete |
112+
| Risk Management Page | ~15 | ✅ Complete |
113+
| Status Values | 9 | ✅ Complete |
114+
| Risk Levels | 7 | ✅ Complete |
115+
| **Total Completed** | **51** | **3.9% of total** |
116+
117+
### Remaining Work
118+
| Page | Estimated Strings | Priority |
119+
|------|-------------------|----------|
120+
| Framework | ~180 | High |
121+
| Tasks | ~150 | High |
122+
| Vendors | ~120 | Medium |
123+
| Model Inventory | ~110 | Medium |
124+
| Dashboard/Home | ~80 | High |
125+
| Others | ~614 | Low-Medium |
126+
| **Total Remaining** | **~1,254** | - |
127+
128+
### Time Estimates
129+
- **Translation**: 20-25 hours (2-3 min/string for German)
130+
- **Implementation**: 40-50 hours (adding t() calls)
131+
- **Testing**: 10-15 hours
132+
- **Total**: ~75-90 hours
133+
134+
## 🚀 Implementation Strategy
135+
136+
### Phase 1: Core Pages (High Priority)
137+
1. Dashboard/Home
138+
2. Framework (ISO compliance)
139+
3. Tasks
140+
4. Project View
141+
142+
### Phase 2: Data Management (Medium Priority)
143+
1. Vendors
144+
2. Model Inventory
145+
3. Policy Dashboard
146+
4. Training Registry
147+
5. File Manager
148+
149+
### Phase 3: Analytics & Settings (Low Priority)
150+
1. AI Trust Center
151+
2. Reporting
152+
3. Fairness Dashboard
153+
4. Event Tracker
154+
5. Settings
155+
156+
## 🔄 Rollback Instructions
157+
158+
To completely remove all localization changes made during this session:
159+
160+
### 1. Remove npm packages
161+
```bash
162+
npm uninstall react-i18next i18next i18next-browser-languagedetector
163+
```
164+
165+
### 2. Delete created files
166+
```bash
167+
rm -rf src/i18n/
168+
rm -rf src/presentation/components/LanguageSwitcher/
169+
rm -rf src/presentation/components/StatusBadge/
170+
rm -rf src/presentation/components/LocalizationTest/
171+
```
172+
173+
### 3. Revert file changes
174+
175+
#### src/App.tsx
176+
- Remove line 28: `import "./i18n/i18n";`
177+
178+
#### src/presentation/components/Sidebar/index.tsx
179+
- Remove line 22: `import { useTranslation } from "react-i18next";`
180+
- Remove line 61: `import LanguageSwitcher from "../LanguageSwitcher";`
181+
- Remove line 198: `const { t } = useTranslation();`
182+
- Change line 74: `const getMenuItems = (openTasksCount: number, t: any): MenuItem[]`
183+
back to: `const getMenuItems = (openTasksCount: number): MenuItem[]`
184+
- Change all `t('sidebar.xxx')` back to hardcoded strings:
185+
- `t('sidebar.dashboard')``"Dashboard"`
186+
- `t('sidebar.riskManagement')``"Risk Management"`
187+
- `t('sidebar.tasks')``"Tasks"`
188+
- etc. for all menu items
189+
- Change line 161-180: Revert `getOtherMenuItems` function to `const other: MenuItem[]` array
190+
- Remove lines 234-235 translation passing, change to:
191+
- `const menu = getMenuItems(openTasksCount);`
192+
- Remove `const other = getOtherMenuItems(t);` and use hardcoded array
193+
- Remove line 828: `<LanguageSwitcher />`
194+
- Change tooltips back to hardcoded strings
195+
196+
#### src/presentation/pages/RiskManagement/index.tsx
197+
- Remove line 2: `import { useTranslation } from "react-i18next";`
198+
- Remove line 58: `const { t } = useTranslation();`
199+
- Change all `t('riskManagement.xxx')` back to hardcoded strings
200+
201+
### 4. Git commands to revert (if committed)
202+
```bash
203+
git diff HEAD~1 -- package.json package-lock.json
204+
git checkout HEAD~1 -- src/App.tsx
205+
git checkout HEAD~1 -- src/presentation/components/Sidebar/index.tsx
206+
git checkout HEAD~1 -- src/presentation/pages/RiskManagement/index.tsx
207+
```
208+
209+
## 💡 Best Practices for Future Implementation
210+
211+
### 1. Translation Keys Naming
212+
- Use nested structure: `page.section.element`
213+
- Example: `vendors.table.headers.name`
214+
- Keep keys lowercase with dots as separators
215+
216+
### 2. Shared Translations
217+
- Common actions: `common.save`, `common.cancel`
218+
- Status values: `status.pending`, `status.completed`
219+
- Error messages: `errors.required`, `errors.invalid`
220+
221+
### 3. Dynamic Content
222+
- Use interpolation: `t('welcome', { name: userName })`
223+
- Translation: `"welcome": "Welcome, {{name}}!"`
224+
225+
### 4. Pluralization
226+
```typescript
227+
t('items', { count: 5 }) // "5 items"
228+
t('items', { count: 1 }) // "1 item"
229+
```
230+
231+
### 5. Missing Translations
232+
- Always provide fallback text
233+
- Use: `t('key', 'Fallback Text')`
234+
235+
### 6. Performance
236+
- Lazy load translations for large apps
237+
- Use namespaces to split translations
238+
- Cache translations in localStorage
239+
240+
## 🧪 Testing Approach
241+
242+
### 1. Manual Testing
243+
- Change browser language settings
244+
- Use language switcher component
245+
- Verify all visible text changes
246+
247+
### 2. Automated Testing
248+
```typescript
249+
// Example test
250+
it('should display German text when language is de', () => {
251+
i18n.changeLanguage('de');
252+
render(<Component />);
253+
expect(screen.getByText('Risikomanagement')).toBeInTheDocument();
254+
});
255+
```
256+
257+
### 3. Translation Validation
258+
- Check for missing keys
259+
- Verify all languages have same keys
260+
- Validate interpolation variables
261+
262+
## 📝 Notes for Developers
263+
264+
### Starting Fresh
265+
When implementing localization from scratch:
266+
1. Start with high-traffic pages
267+
2. Create translation files incrementally
268+
3. Use extraction tools to find all strings
269+
4. Implement language switcher last
270+
5. Test with at least 2 languages
271+
272+
### Common Pitfalls to Avoid
273+
- Don't hardcode language codes
274+
- Don't mix translation keys with display text
275+
- Don't forget to translate error messages
276+
- Don't ignore date/number formatting
277+
- Don't assume text length (German is ~30% longer)
278+
279+
## 🔗 Resources
280+
- [react-i18next Documentation](https://react.i18next.com/)
281+
- [i18next Documentation](https://www.i18next.com/)
282+
- [Language Codes (ISO 639-1)](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
283+
284+
---
285+
*Document created: 2025-09-21*
286+
*Last updated: 2025-09-21*
287+
*Status: Implementation POC completed, ready for rollback and fresh start*

Clients/src/i18n/i18n.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import i18n from 'i18next';
2+
import { initReactI18next } from 'react-i18next';
3+
import LanguageDetector from 'i18next-browser-languagedetector';
4+
5+
import en from './locales/en.json';
6+
import de from './locales/de.json';
7+
8+
const resources = {
9+
en: {
10+
translation: en
11+
},
12+
de: {
13+
translation: de
14+
}
15+
};
16+
17+
i18n
18+
.use(LanguageDetector)
19+
.use(initReactI18next)
20+
.init({
21+
resources,
22+
lng: 'en', // default language
23+
fallbackLng: 'en',
24+
debug: process.env.NODE_ENV === 'development',
25+
26+
interpolation: {
27+
escapeValue: false, // not needed for react as it escapes by default
28+
},
29+
30+
detection: {
31+
order: ['navigator', 'localStorage', 'sessionStorage'],
32+
caches: ['localStorage'],
33+
}
34+
});
35+
36+
export default i18n;

Clients/src/i18n/locales/de.json

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
{
2+
"sidebar": {
3+
"dashboard": "Dashboard",
4+
"projectOrientedView": "Projektansicht",
5+
"organizationalView": "Organisationsansicht",
6+
"riskManagement": "Risikomanagement",
7+
"tasks": "Aufgaben",
8+
"vendors": "Lieferanten",
9+
"evidences": "Nachweise",
10+
"reporting": "Berichte",
11+
"biasAndFairness": "Verzerrung & Fairness",
12+
"trainingRegistry": "Schulungsregister",
13+
"policyManager": "Richtlinien-Manager",
14+
"aiTrustCenter": "KI-Vertrauenszentrum",
15+
"modelInventory": "Modell-Inventar",
16+
"eventTracker": "Ereignis-Tracker",
17+
"settings": "Einstellungen",
18+
"feedback": "Feedback",
19+
"askOnSlackTeams": "Auf Slack/Teams fragen",
20+
"controls": "Steuerung",
21+
"options": "Optionen",
22+
"logOut": "Abmelden"
23+
},
24+
"riskManagement": {
25+
"title": "Risikomanagement",
26+
"description": "Verwalten und überwachen Sie Risiken in allen Ihren Projekten",
27+
"insertFromAiDatabase": "Aus KI-Risikodatenbank einfügen",
28+
"addNewRisk": "Neues Risiko hinzufügen",
29+
"editRisk": "Risiko bearbeiten",
30+
"editProjectRisk": "Projektrisiko bearbeiten",
31+
"addNewRiskTitle": "Ein neues Risiko hinzufügen",
32+
"addNewRiskSubtitle": "Erstellen Sie eine detaillierte Aufschlüsselung von Risiken und ihren Minderungsstrategien, um Ihre Risikomanagement-Aktivitäten effektiv zu dokumentieren.",
33+
"addRiskFromAiDatabase": "Risiko aus KI-Datenbank hinzufügen",
34+
"addRiskFromAiDatabaseTitle": "Ein neues Risiko aus der KI-Datenbank hinzufügen",
35+
"addRiskFromAiDatabaseSubtitle": "Überprüfen und bearbeiten Sie das ausgewählte Risiko aus der KI-Datenbank vor dem Speichern.",
36+
"scope": "Umfang",
37+
"frameworks": "Frameworks",
38+
"riskAssessment": "Risikobewertung",
39+
"riskProfile": "Risikoprofil",
40+
"riskMitigation": "Risikominderung",
41+
"riskMonitoring": "Risikoüberwachung",
42+
"currentProject": "Aktuelles Projekt",
43+
"allProjects": "Alle Projekte",
44+
"iso27001": "ISO 27001",
45+
"iso42001": "ISO 42001",
46+
"nistAi": "NIST AI RMF",
47+
"euAiAct": "EU AI Act"
48+
},
49+
"status": {
50+
"inprogress": "In Bearbeitung",
51+
"completed": "Abgeschlossen",
52+
"pending": "Ausstehend",
53+
"failed": "Fehlgeschlagen",
54+
"open": "Offen",
55+
"closed": "Geschlossen",
56+
"approved": "Genehmigt",
57+
"rejected": "Abgelehnt",
58+
"draft": "Entwurf"
59+
},
60+
"riskLevel": {
61+
"verylow": "Sehr geringes Risiko",
62+
"low": "Geringes Risiko",
63+
"medium": "Mittleres Risiko",
64+
"high": "Hohes Risiko",
65+
"veryhigh": "Sehr hohes Risiko",
66+
"norisk": "Kein Risiko",
67+
"critical": "Kritisches Risiko"
68+
}
69+
}

0 commit comments

Comments
 (0)