Skip to content

Commit e25910c

Browse files
Merge branch 'main' into fix/skill-frontmatter-yaml-859
2 parents 7449df1 + f015dae commit e25910c

341 files changed

Lines changed: 10841 additions & 1342 deletions

File tree

Some content is hidden

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

‎.github/workflows/code-qa.yml‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,12 @@ jobs:
7474
uses: ./.github/actions/setup-node-pnpm
7575
with:
7676
install-args: "--frozen-lockfile"
77+
- name: Test translation check scripts
78+
run: pnpm test:translations-check
7779
- name: Verify all translations are complete
7880
run: node scripts/find-missing-translations.js
81+
- name: Verify plural forms follow CLDR
82+
run: node scripts/validate-plural-forms.js
7983
- name: Verify t() calls pass every interpolation variable
8084
run: node scripts/check-i18n-interpolation.js
8185

‎.github/workflows/visual-regression.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ permissions:
2121
jobs:
2222
webview-visual:
2323
runs-on: ubuntu-latest
24-
timeout-minutes: 10
24+
timeout-minutes: 15
2525
container:
2626
image: mcr.microsoft.com/playwright:v1.64.1-noble@sha256:47b510dd9230bf0b9bfc9b20102241e577ea7a99334ab4c3ca19f19770e9b8ea
2727
options: --ipc=host

‎.roo/rules-translate/001-general-rules.md‎

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@
3030
# 4. WEBVIEW UI LOCALIZATION (webview-ui/src/)
3131

3232
- 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
3437
- All user interface strings should be internationalized
3538
- Always use the Trans component with named components for text with embedded components
3639

@@ -40,6 +43,7 @@
4043

4144
```
4245
<Trans
46+
t={t}
4347
i18nKey="welcome:telemetry.changeSettings"
4448
components={{
4549
settingsLink: <VSCodeLink href="#" onClick={handleOpenSettings} />
@@ -50,14 +54,65 @@
5054
# 5. TECHNICAL IMPLEMENTATION
5155

5256
- 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)
5458
- Implement proper interpolation for variables using {{variable}} syntax
5559
- Don't include defaultValue. The `en` translations are the fallback
5660
- Always use apply_diff instead of write_to_file when editing existing translation files (much faster and more reliable)
5761
- When using apply_diff, carefully identify the exact JSON structure to edit to avoid syntax errors
5862
- Placeholders (like {{variable}}) must remain exactly identical to the English source to maintain code integration and prevent syntax errors
5963

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
61116

62117
- First add or modify English strings, then ask for confirmation before translating to all other languages
63118
- Use this process for each localization task:
@@ -74,16 +129,17 @@
74129
- Tooltip text: Can be slightly more descriptive
75130
- 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
76131

77-
# 7. COMMON PITFALLS TO AVOID
132+
# 8. COMMON PITFALLS TO AVOID
78133

79134
- Switching between formal and informal addressing styles - always stay informal ("du" not "Sie")
80135
- Translating or altering technical terms and brand names that should remain in English
81136
- Modifying or removing placeholders like {{variable}} - these must remain identical
82137
- Translating domain-specific terms that are commonly used in English in the target language
83138
- Changing the meaning or nuance of instructions or error messages
84139
- 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
85141

86-
# 8. QUALITY ASSURANCE
142+
# 9. QUALITY ASSURANCE
87143

88144
- Maintain consistent terminology across all translations
89145
- Respect the JSON structure of translation files
@@ -95,11 +151,16 @@
95151
node scripts/find-missing-translations.js
96152
```
97153
- 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+
```
98158
99-
# 9. TRANSLATOR'S CHECKLIST
159+
# 10. TRANSLATOR'S CHECKLIST
100160
101161
- ✓ Used informal tone consistently ("du" not "Sie")
102162
- ✓ Preserved all placeholders exactly as in the English source
163+
- ✓ Gave each plural key exactly the locale's CLDR forms, each containing `{{count}}`
103164
- ✓ Maintained consistent terminology with existing translations
104165
- ✓ Kept technical terms and brand names unchanged where appropriate
105166
- ✓ Preserved the original perspective (user→system vs system→user)

‎.roo/skills/roo-translation/SKILL.md‎

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,10 @@ This guidance file is loaded at runtime and should be consulted for the latest b
7070
## WebView UI Localization (webview-ui/src/)
7171

7272
- Located in `webview-ui/src/i18n/locales/`
73-
- Uses standard React i18next patterns with the `useTranslation` hook
73+
- 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:
74+
- Always write fully qualified keys (`t("chat:about")`)
75+
- Always pass that `t` to `Trans` (`<Trans t={t} i18nKey="..." />`); `Trans` does not follow language changes on its own (ESLint enforces this)
76+
- 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
7477
- All user interface strings should be internationalized
7578
- Always use the `Trans` component with named components for text with embedded components
7679

@@ -86,6 +89,7 @@ React component usage:
8689

8790
```tsx
8891
<Trans
92+
t={t}
8993
i18nKey="welcome:telemetry.changeSettings"
9094
components={{
9195
settingsLink: <VSCodeLink href="#" onClick={handleOpenSettings} />,
@@ -96,13 +100,64 @@ React component usage:
96100
## Technical Implementation
97101

98102
- Use namespaces to organize translations logically
99-
- Handle pluralization using i18next's built-in capabilities
103+
- Handle pluralization with i18next plural keys that follow CLDR rules (see [Pluralization (CLDR)](#pluralization-cldr))
100104
- Implement proper interpolation for variables using `{{variable}}` syntax
101105
- Don't include `defaultValue`. The `en` translations are the fallback
102106
- Always use `apply_diff` instead of `write_to_file` when editing existing translation files (much faster and more reliable)
103107
- When using `apply_diff`, carefully identify the exact JSON structure to edit to avoid syntax errors
104108
- Placeholders (like `{{variable}}`) must remain exactly identical to the English source to maintain code integration and prevent syntax errors
105109

110+
## Pluralization (CLDR)
111+
112+
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.
113+
114+
- Never fake plurals with "(s)", "файл(ов)", "richiesta/e" or similar. Split the string into plural forms instead.
115+
- Pass the number as `count`: `t("ns:key", { count: n })`. With `<Trans>`, pass `count={n}` or `values={{ count: n }}`.
116+
- 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.
117+
- 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:
118+
119+
| Locales | Forms | What the forms cover |
120+
| ---------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------- |
121+
| en, de, nl, tr | `one`, `other` | `one` = 1 |
122+
| hi | `one`, `other` | `one` = 0, 1 |
123+
| fr, pt-BR | `one`, `many`, `other` | `one` = 0, 1; `many` = exact millions (1000000) |
124+
| ca, es, it | `one`, `many`, `other` | `one` = 1; `many` = exact millions (1000000) |
125+
| 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 |
126+
| pl | `one`, `few`, `many`, `other` | `one` = 1 only; `few` = 2–4, 22–24 (not 12–14); `many` = 0, 5–21, 25–31; `other` = fractions |
127+
| id, ja, ko, vi, zh-CN, zh-TW | `other` | No `_one`: these languages do not inflect for number |
128+
129+
- 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.
130+
- `_many` in ca, es, fr, it and pt-BR only applies to exact millions printed as digits. Copy the `_other` text.
131+
- `_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".
132+
- The form depends on the whole phrase, not just the noun. Check the case the preposition or verb requires:
133+
- ru "удалить {{count}} задачу / задачи / задач"
134+
- ru "до {{count}} запроса / запросов / запросов"
135+
- pl negation "nie udało się odłączyć {{count}} serwera / serwerów / serwerów"
136+
- If another number governs the noun (e.g. "{{count}} of {{total}}"), rephrase so the noun no longer depends on a number: "Импортировано историй задач: {{count}} из {{total}}".
137+
- `_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`.
138+
- 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").
139+
140+
Example:
141+
142+
```jsonc
143+
// en
144+
"subtasks_one": "{{count}} subtask",
145+
"subtasks_other": "{{count}} subtasks",
146+
147+
// ru
148+
"subtasks_one": "{{count}} подзадача",
149+
"subtasks_few": "{{count}} подзадачи",
150+
"subtasks_many": "{{count}} подзадач",
151+
"subtasks_other": "{{count}} подзадачи",
152+
153+
// ja
154+
"subtasks_other": "{{count}} サブタスク",
155+
```
156+
157+
To list the plural forms of any locale, run: `node -p 'new Intl.PluralRules("<locale>").resolvedOptions().pluralCategories'`.
158+
159+
`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.
160+
106161
## Translation Workflow
107162

108163
1. First add or modify English strings, then ask for confirmation before translating to all other languages
@@ -133,6 +188,12 @@ node scripts/find-missing-translations.js
133188

134189
Address any missing translations identified by the script to ensure complete coverage across all locales.
135190

191+
When you add or change plural keys, also run the plural forms script and fix every problem it reports:
192+
193+
```bash
194+
node scripts/validate-plural-forms.js
195+
```
196+
136197
## Common Pitfalls to Avoid
137198

138199
- Switching between formal and informal addressing styles - always stay informal ("du" not "Sie")
@@ -141,11 +202,13 @@ Address any missing translations identified by the script to ensure complete cov
141202
- Translating domain-specific terms that are commonly used in English in the target language
142203
- Changing the meaning or nuance of instructions or error messages
143204
- Forgetting to maintain consistent terminology throughout the translation
205+
- Copying the English `_one`/`_other` pair into a locale with different plural forms, or writing "(s)"-style plurals
144206

145207
## Translator's Checklist
146208

147209
- ✓ Used informal tone consistently ("du" not "Sie")
148210
- ✓ Preserved all placeholders exactly as in the English source
211+
- ✓ Gave each plural key exactly the locale's CLDR forms, each containing `{{count}}`
149212
- ✓ Maintained consistent terminology with existing translations
150213
- ✓ Kept technical terms and brand names unchanged where appropriate
151214
- ✓ Preserved the original perspective (user→system vs system→user)

‎apps/vscode-e2e/src/suite/index.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ export async function run() {
2727
openRouterApiKey: aimockUrl && !isRecord ? "mock-key" : process.env.OPENROUTER_API_KEY!,
2828
openRouterModelId: "openai/gpt-4.1",
2929
...(aimockUrl && { openRouterBaseUrl: `${aimockUrl}/v1` }),
30+
// Title requests embed the task prompt, so they match prompt-substring fixtures and
31+
// consume their sequenceIndex slots ahead of the task's own requests.
32+
taskTitleGenerationEnabled: false,
3033
})
3134

3235
await vscode.commands.executeCommand("zoo-code.SidebarProvider.focus")

‎apps/vscode-e2e/src/visual/sceneController.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ export async function run(): Promise<void> {
108108
openRouterModelId: "openai/gpt-4.1",
109109
openRouterBaseUrl: `${aimockUrl}/v1`,
110110
telemetrySetting: "disabled",
111+
// A generated title lands asynchronously and would race the task header screenshot.
112+
taskTitleGenerationEnabled: false,
111113
})
112114
await log(`aimock=${aimockUrl}`)
113115
let completionHandler: ((event: { message: ClineMessage }) => void) | undefined

‎package.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@
4141
"knip": "knip --treat-config-hints-as-errors",
4242
"npm:publish:types": "pnpm --filter @roo-code/types npm:publish",
4343
"knip:production": "knip --production --treat-config-hints-as-errors",
44-
"test:knip-policy": "node --test scripts/knip-policy.test.mjs"
44+
"test:knip-policy": "node --test scripts/knip-policy.test.mjs",
45+
"test:translations-check": "node --test scripts/find-missing-translations.test.mjs scripts/validate-plural-forms.test.mjs"
4546
},
4647
"devDependencies": {
4748
"@changesets/cli": "2.31.1",

0 commit comments

Comments
 (0)