Skip to content

Commit db97521

Browse files
merge: carry main f015dae forward by merge rather than replay
Main moved 13 commits past this branches base. This branch carries 6 merge commits inside it, so the trunk was brought forward with a merge commit and not by replaying history linearly. One content conflict was reconciled by hand in src/core/task/__tests__/observationRegistry.spec.ts: the branches indented statements plus its try/finally real-timers teardown were kept, and the trunks five unindented duplicate statements were dropped because every line of them survives in the kept copy and the trunks own afterEach teardown landed outside the conflict region.
2 parents a8a2021 + f015dae commit db97521

335 files changed

Lines changed: 10372 additions & 1344 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)