|
| 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`. |
0 commit comments