Skip to content

Commit 55dad8c

Browse files
committed
feat(i18n): implement localization foundation and phased rollout
Add Vue I18n infrastructure with locale registry, browser/system locale detection, user language override in preferences, Quasar language synchronization, modular locale domains, and broad UI localization across critical and functional modules. Also include i18n docs/check tooling and cleanup updates needed for rollout readiness. Note: some hardcoded user-facing strings may still remain in low-traffic paths; this establishes the baseline and process for incremental completion.
1 parent dc9ec6c commit 55dad8c

File tree

212 files changed

+14312
-4055
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

212 files changed

+14312
-4055
lines changed

.github/workflows/frontend-linting.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@ jobs:
2424

2525
- name: Run ESLint
2626
run: npm run lint -- --max-warnings=0
27+
28+
- name: Check i18n key parity and placeholders
29+
run: npm run i18n:check

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,9 @@ yarn-error.log*
3434

3535
.env
3636
/public/env-config.js
37+
38+
# TypeScript build info
39+
tsconfig.tsbuildinfo
40+
41+
# Kilo local config
42+
.kilo/

.vscode/settings.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99
"eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"],
1010
"typescript.tsdk": "node_modules/typescript/lib",
1111
"files.watcherExclude": {
12-
"**/.git/objects/**": true,
13-
"**/.git/subtree-cache/**": true,
14-
"**/node_modules/": true,
15-
"/node_modules/**": true,
16-
"**/env/": true,
17-
"/env/**": true
12+
"**/.git/objects/**": true,
13+
"**/.git/subtree-cache/**": true,
14+
"**/node_modules/": true,
15+
"/node_modules/**": true,
16+
"**/env/": true,
17+
"/env/**": true
1818
},
1919
"prettier.prettierPath": "./node_modules/prettier"
2020
}

docs/i18n/CONTRIBUTING.i18n.md

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
# Contributing Translations (i18n)
2+
3+
## Golden Rule
4+
5+
**Every new UI string must be added through i18n.** No hardcoded strings in templates, components, or composables.
6+
7+
---
8+
9+
## Adding a New Translation String
10+
11+
### 1. Replace the hardcoded string with an i18n key
12+
13+
In a Vue template:
14+
15+
```vue
16+
<!-- bad -->
17+
<q-btn label="Delete Agent" />
18+
19+
<!-- good -->
20+
<q-btn :label="$t('agents.actions.delete')" />
21+
```
22+
23+
In a script / composable:
24+
25+
```ts
26+
// bad
27+
Notify.create({ message: "Agent deleted" });
28+
29+
// good — via useI18n
30+
import { useI18n } from "vue-i18n";
31+
const { t } = useI18n();
32+
Notify.create({ message: t("agents.actions.deleted") });
33+
```
34+
35+
In boot files (no `setup` context):
36+
37+
```ts
38+
import { i18n } from "@/i18n";
39+
Notify.create({ message: i18n.global.t("common.saved") });
40+
```
41+
42+
### 2. Add the key to the source locale (`en`)
43+
44+
`en` is the source of truth. Add the key there first:
45+
46+
```json
47+
// src/i18n/en/agents.json
48+
{
49+
"actions": {
50+
"delete": "Delete Agent",
51+
"deleted": "Agent deleted successfully"
52+
}
53+
}
54+
```
55+
56+
Then add translations in any locale that you are working on (for example `ru`).
57+
If a key is missing in a locale, fallback to `en` is used.
58+
59+
### 3. Run the integrity check
60+
61+
```bash
62+
npm run i18n:check
63+
```
64+
65+
This reports:
66+
67+
- **Key parity** — missing/extra keys in each locale vs `en`.
68+
- **Placeholder consistency**`{var}`, `%s`, `%d` etc. match for shared keys.
69+
70+
### 4. Commit
71+
72+
Follow the conventional commit format:
73+
74+
```
75+
i18n: localize <module> for <locale> and align key parity
76+
```
77+
78+
---
79+
80+
## Key Naming Conventions
81+
82+
| Pattern | Example |
83+
| ------------------------------- | --------------------- |
84+
| `module.section.action` | `agents.tabs.summary` |
85+
| Verbs for actions | `agents.actions.run` |
86+
| Nouns for titles | `agents.metaTitle` |
87+
| `shared.*` for reusable strings | `shared.areYouSure` |
88+
89+
## Locale Namespaces (Domains)
90+
91+
| File | Area |
92+
| ----------------- | ------------------------------------------------- |
93+
| `agents.json` | Agents, remote background, file browser, services |
94+
| `alerts.json` | Alerts, alert templates |
95+
| `auth.json` | Authentication, SSO |
96+
| `checks.json` | Monitoring checks |
97+
| `common.json` | Shared strings, buttons, statuses |
98+
| `dashboard.json` | Dashboard |
99+
| `navigation.json` | Navigation menu, routes |
100+
| `reporting.json` | Reports (EE) |
101+
| `scripts.json` | Scripts, snippets |
102+
| `settings.json` | Server and core settings |
103+
| `software.json` | Software install/uninstall |
104+
| `tasks.json` | Automated tasks |
105+
106+
---
107+
108+
## Placeholders
109+
110+
Preserve placeholders **exactly as they appear** in the English source:
111+
112+
| Type | Examples |
113+
| -------------- | -------------------------- |
114+
| ICU / vue-i18n | `{name}`, `{count}`, `{n}` |
115+
| printf | `%s`, `%d`, `%f` |
116+
117+
```json
118+
// en
119+
"greeting": "Hello, {name}! You have %d messages."
120+
121+
// target locale (example: ru) — same placeholders, translated text
122+
"greeting": "Привет, {name}! У вас %d сообщений."
123+
```
124+
125+
---
126+
127+
## Pluralization
128+
129+
Use `vue-i18n` pipe syntax for plural forms:
130+
131+
```json
132+
{
133+
"agents": {
134+
"selected": "No agents selected | {count} agent selected | {count} agents selected"
135+
}
136+
}
137+
```
138+
139+
For locales with complex plural rules (example: Russian), use full form sets (0, 1, 2-4, 5+):
140+
141+
```json
142+
{
143+
"agents": {
144+
"selected": "Нет выбранных агентов | Выбран {count} агент | Выбрано {count} агента | Выбрано {count} агентов"
145+
}
146+
}
147+
```
148+
149+
---
150+
151+
## Translation Rules
152+
153+
### Must
154+
155+
- Translate the **meaning**, not the literal word form.
156+
- Keep functional accuracy — translation must not change the action or status meaning.
157+
- Keep strings short and clear for an admin interface.
158+
- Preserve all placeholders, HTML/Markdown, and special tokens.
159+
- Use neutral business tone, avoid colloquialisms.
160+
161+
### Never
162+
163+
- Translate technical identifiers: `agent_id`, `site_id`, `SMTP`, `SSH`, `RDP`, `URL`, `DNS`.
164+
- Translate product names, brands, URLs, API endpoints, variable names, JSON keys, terminal commands.
165+
- Change logic through translation (e.g. `Disable``Включить`).
166+
- Lose negation (`not`, `failed`, `denied`).
167+
- Split or merge strings if it breaks interpolation.
168+
- Add new information not present in the source string.
169+
170+
### Do Not Translate
171+
172+
- `Tactical RMM`, product names, brands.
173+
- Protocol names: `SSH`, `RDP`, `SMTP`, `DNS`, `HTTP`, `WebSocket`.
174+
- Technical IDs: `agent_id`, `site_id`, `client_id`.
175+
- File paths, URLs, API endpoints.
176+
- Code blocks, commands, JSON keys.
177+
178+
---
179+
180+
## Glossary (Example: RU)
181+
182+
Use consistent terminology across all modules.
183+
184+
| English Term | Russian Translation | Note |
185+
| ------------ | ------------------- | ----------------------------------- |
186+
| Agent | Агент | Not "клиент" |
187+
| Client | Клиент | Organization/customer |
188+
| Site | Сайт | Client location |
189+
| Check | Проверка | Monitoring check |
190+
| Alert | Оповещение | Not "тревога" |
191+
| Policy | Политика | Set of rules |
192+
| Task | Задача | Scheduled or manual action |
193+
| Script | Скрипт | Not "сценарий" in UI |
194+
| Dashboard | Панель мониторинга | Short: "Панель" if space is limited |
195+
| Service | Служба | Windows service |
196+
| Event Log | Журнал событий | |
197+
| Run | Запустить | For action buttons |
198+
| Retry | Повторить | |
199+
| Success | Успешно | Execution status |
200+
| Failed | Ошибка | For status/notification |
201+
| Pending | В ожидании | |
202+
203+
---
204+
205+
## Quality Checklist Before Merge
206+
207+
1. [ ] New key exists in `en`.
208+
2. [ ] If locale key exists, placeholders/tokens match `en`.
209+
3. [ ] No accidental edits/removals in `en`.
210+
4. [ ] Terms follow the glossary above.
211+
5. [ ] UI is visually readable for the target locale (no text overflow in buttons, dialogs, tooltips).
212+
6. [ ] `npm run i18n:check` passes.
213+
7. [ ] `npm run lint` and `npm run build` pass.
214+
215+
---
216+
217+
## CI Checks
218+
219+
Every push / PR to `develop` automatically runs:
220+
221+
1. **Lint + Build**`frontend-linting.yml`
222+
2. **i18n parity + placeholders (informational)**`npm run i18n:check`
223+
224+
`i18n:check` is non-blocking by design (coverage/progress signal).
225+
226+
---
227+
228+
## Available npm Scripts
229+
230+
| Script | Purpose |
231+
| --------------------------- | -------------------------------------------------- |
232+
| `npm run i18n:check` | Run all i18n integrity checks |
233+
| `npm run i18n:parity` | Check key parity between `en` and non-`en` locales |
234+
| `npm run i18n:placeholders` | Check placeholder consistency |
235+
236+
---
237+
238+
## What If a String Seems "Technical"?
239+
240+
When in doubt — leave it in English and add a comment in the PR explaining why.
241+
242+
---
243+
244+
## Adding a New Language
245+
246+
Locales are discovered automatically from `src/i18n/<code>/index.ts`.
247+
Each locale controls what it imports — **partial translations are fully supported**.
248+
Missing domains fall back to `en` automatically via `fallbackLocale`.
249+
250+
### Step 1: Create the locale directory and JSON domains
251+
252+
```
253+
src/i18n/de/common.json
254+
```
255+
256+
You can add only the domains you have translated.
257+
258+
### Step 2: Register locale in app runtime
259+
260+
Edit `src/i18n/index.ts` and add locale to:
261+
262+
- `supportedLocales`
263+
- `localeAliases`
264+
- `messages`
265+
- `quasarLanguagePacks`
266+
267+
### Step 3: Add translation files
268+
269+
```
270+
src/i18n/de/auth.json
271+
src/i18n/de/common.json
272+
```
273+
274+
### That's it
275+
276+
- Add language label to the locale picker options (if shown in UI).
277+
- `npm run i18n:check` will report coverage for the new locale.
278+
- Missing keys automatically fall back to `en`.

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!DOCTYPE html>
1+
<!doctype html>
22
<html>
33
<head>
44
<title><%= productName %></title>

0 commit comments

Comments
 (0)