|
30 | 30 | # 4. WEBVIEW UI LOCALIZATION (webview-ui/src/) |
31 | 31 |
|
32 | 32 | - Located in webview-ui/src/i18n/locales/ |
33 | | -- Uses standard React i18next patterns with the useTranslation hook |
| 33 | +- Use `useAppTranslation` from `@src/i18n/TranslationContext`, not react-i18next's `useTranslation` (ESLint enforces this). It returns `{ t, language }`; switching languages belongs to the provider, so the i18next instance is not exposed: |
| 34 | + - Always write fully qualified keys (`t("chat:about")`) |
| 35 | + - Always pass that `t` to `Trans` (`<Trans t={t} i18nKey="..." />`); `Trans` does not follow language changes on its own (ESLint enforces this) |
| 36 | + - Never use the global `t` from `i18next` (`i18next.t`); memoized output would keep showing the previous language after a switch (ESLint enforces this). Pass `t` (and `language` for dates) to helpers instead, as `utils/format.ts` and `utils/validate.ts` do |
34 | 37 | - All user interface strings should be internationalized |
35 | 38 | - Always use the Trans component with named components for text with embedded components |
36 | 39 |
|
|
40 | 43 |
|
41 | 44 | ``` |
42 | 45 | <Trans |
| 46 | + t={t} |
43 | 47 | i18nKey="welcome:telemetry.changeSettings" |
44 | 48 | components={{ |
45 | 49 | settingsLink: <VSCodeLink href="#" onClick={handleOpenSettings} /> |
|
50 | 54 | # 5. TECHNICAL IMPLEMENTATION |
51 | 55 |
|
52 | 56 | - Use namespaces to organize translations logically |
53 | | -- Handle pluralization using i18next's built-in capabilities |
| 57 | +- Handle pluralization with i18next plural keys that follow CLDR rules (see section 6) |
54 | 58 | - Implement proper interpolation for variables using {{variable}} syntax |
55 | 59 | - Don't include defaultValue. The `en` translations are the fallback |
56 | 60 | - Always use apply_diff instead of write_to_file when editing existing translation files (much faster and more reliable) |
57 | 61 | - When using apply_diff, carefully identify the exact JSON structure to edit to avoid syntax errors |
58 | 62 | - Placeholders (like {{variable}}) must remain exactly identical to the English source to maintain code integration and prevent syntax errors |
59 | 63 |
|
60 | | -# 6. WORKFLOW AND APPROACH |
| 64 | +# 6. PLURALIZATION (CLDR) |
| 65 | + |
| 66 | +i18next v25 picks a plural form with `Intl.PluralRules`, which implements the [CLDR plural rules](https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html). Any string that counts something must use plural keys. |
| 67 | + |
| 68 | +- Never fake plurals with "(s)", "файл(ов)", "richiesta/e" or similar. Split the string into plural forms instead. |
| 69 | +- Pass the number as `count`: `t("ns:key", { count: n })`. With `<Trans>`, pass `count={n}` or `values={{ count: n }}`. |
| 70 | +- Name the keys `key_one`, `key_few`, `key_many`, `key_other` (plus an optional `key_zero`). Do not keep a bare `key` next to them: i18next uses it in place of any missing form, which hides the gap. |
| 71 | +- Each locale must have exactly its CLDR forms. When a form is missing, i18next falls back to the bare key and then to English. Extra forms are never shown: |
| 72 | + |
| 73 | +| Locales | Forms | What the forms cover | |
| 74 | +| ---------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------- | |
| 75 | +| en, de, nl, tr | `one`, `other` | `one` = 1 | |
| 76 | +| hi | `one`, `other` | `one` = 0, 1 | |
| 77 | +| fr, pt-BR | `one`, `many`, `other` | `one` = 0, 1; `many` = exact millions (1000000) | |
| 78 | +| ca, es, it | `one`, `many`, `other` | `one` = 1; `many` = exact millions (1000000) | |
| 79 | +| ru | `one`, `few`, `many`, `other` | `one` = 1, 21, 31, 101 (not 11); `few` = 2–4, 22–24 (not 12–14); `many` = 0, 5–20, 25–30; `other` = fractions | |
| 80 | +| pl | `one`, `few`, `many`, `other` | `one` = 1 only; `few` = 2–4, 22–24 (not 12–14); `many` = 0, 5–21, 25–31; `other` = fractions | |
| 81 | +| id, ja, ko, vi, zh-CN, zh-TW | `other` | No `_one`: these languages do not inflect for number | |
| 82 | + |
| 83 | +- Every form, including `_one`, must contain `{{count}}`. Do not hardcode "1": `_one` also covers 21 in ru and 0 in fr, pt-BR and hi. |
| 84 | +- `_many` in ca, es, fr, it and pt-BR only applies to exact millions printed as digits. Copy the `_other` text. |
| 85 | +- `_other` in ru and pl only applies to fractions (1.5). Use the genitive singular: ru "1,5 минуты" (same as `_few`), pl "1,5 minuty", "1,5 wyniku". |
| 86 | +- The form depends on the whole phrase, not just the noun. Check the case the preposition or verb requires: |
| 87 | + - ru "удалить {{count}} задачу / задачи / задач" |
| 88 | + - ru "до {{count}} запроса / запросов / запросов" |
| 89 | + - pl negation "nie udało się odłączyć {{count}} serwera / serwerów / serwerów" |
| 90 | +- If another number governs the noun (e.g. "{{count}} of {{total}}"), rephrase so the noun no longer depends on a number: "Импортировано историй задач: {{count}} из {{total}}". |
| 91 | +- `_zero` is an i18next override for count 0 in every language. Add it in every locale when the English key has it. In ru and pl, 0 otherwise uses `_many`. |
| 92 | +- Do not pluralize money amounts, unit abbreviations ("{{count}}s"), numbers that JavaScript updates live in the DOM, or phrases with no counted noun ("and {{count}} more"). |
| 93 | + |
| 94 | +Example: |
| 95 | + |
| 96 | +```jsonc |
| 97 | +// en |
| 98 | +"subtasks_one": "{{count}} subtask", |
| 99 | +"subtasks_other": "{{count}} subtasks", |
| 100 | + |
| 101 | +// ru |
| 102 | +"subtasks_one": "{{count}} подзадача", |
| 103 | +"subtasks_few": "{{count}} подзадачи", |
| 104 | +"subtasks_many": "{{count}} подзадач", |
| 105 | +"subtasks_other": "{{count}} подзадачи", |
| 106 | + |
| 107 | +// ja |
| 108 | +"subtasks_other": "{{count}} サブタスク", |
| 109 | +``` |
| 110 | + |
| 111 | +To list the plural forms of any locale, run: `node -p 'new Intl.PluralRules("<locale>").resolvedOptions().pluralCategories'` |
| 112 | + |
| 113 | +`node scripts/find-missing-translations.js` reports a plural key only when a locale has no key with its base name (`key` or `key_*`). `node scripts/validate-plural-forms.js` reports missing forms, forms without `{{count}}`, extra forms, bare keys next to plural forms and plural mistakes in English. |
| 114 | + |
| 115 | +# 7. WORKFLOW AND APPROACH |
61 | 116 |
|
62 | 117 | - First add or modify English strings, then ask for confirmation before translating to all other languages |
63 | 118 | - Use this process for each localization task: |
|
74 | 129 | - Tooltip text: Can be slightly more descriptive |
75 | 130 | - Preserve the original perspective: If text is a user command directed at the software, ensure the translation maintains this direction, avoiding language that makes it sound like an instruction from the system to the user |
76 | 131 |
|
77 | | -# 7. COMMON PITFALLS TO AVOID |
| 132 | +# 8. COMMON PITFALLS TO AVOID |
78 | 133 |
|
79 | 134 | - Switching between formal and informal addressing styles - always stay informal ("du" not "Sie") |
80 | 135 | - Translating or altering technical terms and brand names that should remain in English |
81 | 136 | - Modifying or removing placeholders like {{variable}} - these must remain identical |
82 | 137 | - Translating domain-specific terms that are commonly used in English in the target language |
83 | 138 | - Changing the meaning or nuance of instructions or error messages |
84 | 139 | - Forgetting to maintain consistent terminology throughout the translation |
| 140 | +- Copying the English `_one`/`_other` pair into a locale with different plural forms, or writing "(s)"-style plurals |
85 | 141 |
|
86 | | -# 8. QUALITY ASSURANCE |
| 142 | +# 9. QUALITY ASSURANCE |
87 | 143 |
|
88 | 144 | - Maintain consistent terminology across all translations |
89 | 145 | - Respect the JSON structure of translation files |
|
95 | 151 | node scripts/find-missing-translations.js |
96 | 152 | ``` |
97 | 153 | - Address any missing translations identified by the script to ensure complete coverage across all locales |
| 154 | +- When you add or change plural keys, also run the plural forms script and fix every problem it reports: |
| 155 | + ``` |
| 156 | + node scripts/validate-plural-forms.js |
| 157 | + ``` |
98 | 158 |
|
99 | | -# 9. TRANSLATOR'S CHECKLIST |
| 159 | +# 10. TRANSLATOR'S CHECKLIST |
100 | 160 |
|
101 | 161 | - ✓ Used informal tone consistently ("du" not "Sie") |
102 | 162 | - ✓ Preserved all placeholders exactly as in the English source |
| 163 | +- ✓ Gave each plural key exactly the locale's CLDR forms, each containing `{{count}}` |
103 | 164 | - ✓ Maintained consistent terminology with existing translations |
104 | 165 | - ✓ Kept technical terms and brand names unchanged where appropriate |
105 | 166 | - ✓ Preserved the original perspective (user→system vs system→user) |
|
0 commit comments