Skip to content

Commit 0a77871

Browse files
committed
add translations to Ask AI widget
1 parent 9be1682 commit 0a77871

10 files changed

Lines changed: 703 additions & 81 deletions

File tree

‎.claude/skills/translate-docs/SKILL.md‎

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
name: translate-docs
3-
description: Bring the AI-maintained translations of the Velopack docs (i18n/<locale>/) back in sync with the English source in docs/. Finds stale pages via the content-hash manifest, fans out one translator subagent per stale page, updates the nav/sidebar labels, validates, and re-stamps the manifest. Use when asked to update, refresh, sync or fix the translations, or after editing English docs.
3+
description: Bring the AI-maintained translations of the Velopack docs (i18n/<locale>/) back in sync with the English source in docs/. Finds stale pages via the content-hash manifest, fans out one translator subagent per stale page, updates the nav/sidebar labels and UI component strings, validates, and re-stamps the manifest. Use when asked to update, refresh, sync or fix the translations, or after editing English docs.
44
argument-hint: "[locale] [page ...] [all]"
55
---
66

@@ -42,7 +42,7 @@ node scripts/i18n.mjs status --json [--locale <id>]
4242
```
4343

4444
This takes about a minute (it runs `docusaurus write-translations` to get the English
45-
nav/sidebar labels). For each locale it returns:
45+
nav/sidebar labels and component strings). For each locale it returns:
4646

4747
- `docs[]` — pages needing work, each with a `status`:
4848
- `changed`: the English changed since the translation was last synced. `diffCommand`
@@ -54,7 +54,8 @@ nav/sidebar labels). For each locale it returns:
5454
(`checkErrors`), e.g. a dropped heading or a mangled code block. Fix them.
5555
Any page may also carry `checkErrors` that the translator must resolve.
5656
- `orphaned[]` — translations whose English page no longer exists.
57-
- `ui[]` — nav/sidebar label files with `added` / `changed` / `removed` keys.
57+
- `ui[]` — nav/sidebar label files and `code.json` (our components' strings, e.g. the
58+
"Ask AI" widget) with `added` / `changed` / `removed` keys.
5859

5960
Add any pages named in the arguments (or every page for `all`) with status `review`.
6061

@@ -99,7 +100,7 @@ English-source problems or translation judgment calls the maintainer should know
99100

100101
While the subagents run, handle the UI labels (step 5) yourself.
101102

102-
### 5. Nav and sidebar labels
103+
### 5. Nav/sidebar labels and component strings
103104

104105
For each stale `ui[]` entry, edit `i18n/<locale>/<file>` (the `target`) directly, creating
105106
it if it doesn't exist yet (new locale). The file is a JSON object of
@@ -115,6 +116,19 @@ JavaScript, Python, Rust), and follow the language conventions in `translator-br
115116
and the terminology already used by the locale's pages and labels. Labels are short — prefer the conventional UI wording in the target
116117
language.
117118

119+
`code.json` holds the strings of our own React components (`docsAssist.*` is the "Ask AI"
120+
chat widget); the `description` of each key says where it appears. Only add keys the status
121+
report lists — never copy Docusaurus's own theme keys into it. In addition:
122+
123+
- Keep `{placeholders}` (`{time}`, `{max}`, `{discord}`, …) exactly as written; move them
124+
wherever the sentence needs them.
125+
- Keep the `…` ellipsis on status lines and placeholders, and use the same register as
126+
the locale's pages (see **Register** in `translator-brief.md`).
127+
- Example questions (`docsAssist.intro.example*`) are sent to the assistant verbatim, so
128+
phrase them as a native speaker would ask them.
129+
- Accessible names and tooltips (button labels, `aria-label`s) should be the target
130+
language's conventional UI wording, short.
131+
118132
### 6. Validate
119133

120134
When all subagents have reported:
@@ -134,7 +148,7 @@ Stamp only the pages that were successfully updated and pass `check`:
134148
node scripts/i18n.mjs update --locale <locale> [--ui] <page> <page> ...
135149
```
136150

137-
Add `--ui` if you updated nav/sidebar labels in step 5 (it snapshots the current English
151+
Add `--ui` if you updated nav/sidebar labels or component strings in step 5 (it snapshots the current English
138152
labels; it refuses if a baseline key is still missing from the locale file). Stamping also
139153
drops manifest entries for deleted English pages. Then run
140154
`node scripts/i18n.mjs status --locale <locale>` to confirm only intentionally skipped

‎AGENTS.md‎

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,10 @@ manifest until the skill is run.
218218

219219
- ✅ The guide pages under `docs/` (everything except `docs/reference/**`).
220220
- ✅ Top nav + sidebar category labels (see "Localizing nav & sidebar").
221-
- ❌ Generic theme strings — search box, buttons, pagination, etc. (`code.json`) — **not in
222-
scope**. Docusaurus ships its own bundled translations for most of them; anything it
221+
- ✅ Strings in our own React components, e.g. the "Ask AI" widget (see "Localizing
222+
component strings").
223+
- ❌ Generic theme strings — search box, buttons, pagination, etc. (the rest of
224+
`code.json`) — **not in scope**. Docusaurus ships its own bundled translations for most of them; anything it
223225
doesn't cover falls back to English. We deliberately don't own these (owning them means
224226
maintaining strings that aren't ours and overriding Docusaurus on every upgrade).
225227
- ❌ The auto-generated API reference (`docs/reference/**`) — ~569 files, regenerated
@@ -230,7 +232,8 @@ manifest until the skill is run.
230232
### How it works
231233

232234
- **`i18n/translation-manifest.json`** — per locale, the **git blob id** of the English
233-
source each translated page reflects, plus a snapshot of the English nav/sidebar labels.
235+
source each translated page reflects, plus a snapshot of the English nav/sidebar labels
236+
and component strings.
234237
Because it stores git blob ids, `git diff <old> <new>` shows exactly which English
235238
changes a translation is missing. Maintained by the script; don't edit it by hand.
236239
- **`scripts/i18n.mjs`** (config: `scripts/i18n.config.json`) — dependency-free bookkeeping;
@@ -246,7 +249,7 @@ manifest until the skill is run.
246249
- **`.claude/skills/translate-docs/`** — the skill that does the translating: runs
247250
`status`, fans out one subagent per stale (locale, page) that carries the English diff
248251
over into the translation (natural, idiomatic target-language prose — not word-for-word),
249-
updates nav/sidebar labels, runs `check`, re-stamps the manifest with `update`, and
252+
updates nav/sidebar labels and component strings, runs `check`, re-stamps the manifest with `update`, and
250253
builds the locale. `translator-brief.md` holds the translation rules and the
251254
language-agnostic style/terminology guidance (with examples in several languages, so it
252255
applies to any locale without a per-language guide).
@@ -289,18 +292,37 @@ files directly, and `update --ui` takes a new snapshot.
289292
Per-page sidebar entries (C#, FAQ, …) localize automatically via each doc's translated
290293
`sidebar_label` frontmatter — no JSON needed.
291294

292-
**`code.json` is intentionally NOT committed.** It's generic theme chrome (search box,
293-
buttons, pagination) that Docusaurus already translates via its bundled locale data;
294-
owning it would mean overriding Docusaurus on every upgrade. Anything it doesn't cover
295+
### Localizing component strings
296+
297+
User-facing text in our own React components (`src/components/`, `src/theme/`) is wrapped
298+
in Docusaurus's `translate()` / `<Translate>` (`@docusaurus/Translate`), which puts it in
299+
`code.json`. Rules:
300+
301+
- Give every string an explicit `id` under the component's prefix (`docsAssist.*` for the
302+
"Ask AI" widget) and a `description` that tells a translator where it appears.
303+
- The `id`, `message` and `description` must be string literals (they're extracted
304+
statically). Use `{placeholders}` + `values` for anything dynamic, never string
305+
concatenation, so translators can reorder the sentence.
306+
- A new component with translatable strings needs its prefix added to `includeKeyPrefixes`
307+
of the `code.json` entry in `scripts/i18n.config.json`, or the skill won't see it.
308+
309+
**`i18n/<locale>/code.json` holds only our keys.** The rest of `code.json` is generic theme
310+
chrome (search box, buttons, pagination) that Docusaurus already translates via its bundled
311+
locale data; owning it would mean overriding Docusaurus on every upgrade. Docusaurus merges
312+
the locale file over its bundled translations, so a file with just our keys is enough, and
313+
`includeKeyPrefixes` keeps the theme keys out of the status report. Anything not covered
295314
falls back to English.
296315

316+
Text that comes from the docs-assist Worker (activity lines such as "Searching the docs…",
317+
and the answers themselves) is not part of the site and isn't covered by this.
318+
297319
### Adding a new language
298320

299321
1. Add the locale to `docusaurus.config.ts`: `i18n.locales`, `i18n.localeConfigs`
300322
(label + `htmlLang`), and the search theme's `language: [...]` array.
301323
2. Add the locale to `scripts/i18n.config.json` (`locales` and `localeLabels`).
302324
3. Run `/translate-docs <locale>` in Claude Code. Every page reports as `missing` and gets
303-
translated, and the nav/sidebar label files are created.
325+
translated, and the nav/sidebar label and `code.json` files are created.
304326
4. Verify with `npm run build` (builds all locales, `onBrokenLinks: 'throw'` applies to
305327
every locale).
306328

‎i18n/README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Nothing translates automatically in CI.
1111
|---|---|---|
1212
| Guide pages (`docs/`, minus `docs/reference/**`) | ✅ `translate-docs` skill | `i18n/<locale>/docusaurus-plugin-content-docs/current/` |
1313
| Top nav + sidebar category labels | ✅ `translate-docs` skill | `.../docusaurus-theme-classic/navbar.json`, `.../docusaurus-plugin-content-docs/current.json` |
14+
| Our own component strings (the "Ask AI" widget, `docsAssist.*`) | ✅ `translate-docs` skill | `i18n/<locale>/code.json` (only our keys) |
1415
| Generic theme strings (search box, buttons, pagination) | ❌ Not in scope | Docusaurus's bundled locale translations apply; anything without one falls back to English |
1516
| API reference (auto-generated, `docs/reference/**`) | ❌ No (English fallback) | — |
1617
| Blog | ❌ No (English) | — |
@@ -29,7 +30,7 @@ npm run i18n:update -- <page> # mark a page's translation as in sync (the skill
2930

3031
`translation-manifest.json` records, per locale and page, the **git blob id** of the English
3132
source the translation reflects (so `git diff <old> <new>` shows exactly what changed since),
32-
plus a snapshot of the English nav/sidebar labels. It's maintained by `scripts/i18n.mjs`;
33+
plus a snapshot of the English nav/sidebar labels and component strings. It's maintained by `scripts/i18n.mjs`;
3334
don't edit it by hand.
3435

3536
See the **i18n / Translations** section of [`AGENTS.md`](../AGENTS.md) for the full runbook

‎i18n/es/code.json‎

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
{
2+
"docsAssist.fab.announceNewAnswer": {
3+
"message": "Nueva respuesta de Ask AI",
4+
"description": "Ask AI: screen reader announcement when an answer finishes while the panel is closed"
5+
},
6+
"docsAssist.fab.announceAnswerReady": {
7+
"message": "Respuesta lista",
8+
"description": "Ask AI: screen reader announcement when an answer finishes while the panel is open"
9+
},
10+
"docsAssist.fab.label": {
11+
"message": "Ask AI",
12+
"description": "Ask AI: the floating launcher button's text (keep it short)"
13+
},
14+
"docsAssist.fab.labelUnread": {
15+
"message": "Ask AI (nueva respuesta)",
16+
"description": "Ask AI: the launcher's accessible name when there is an unread answer"
17+
},
18+
"docsAssist.fab.labelBusy": {
19+
"message": "Ask AI (respondiendo…)",
20+
"description": "Ask AI: the launcher's accessible name while an answer is being written in the background"
21+
},
22+
"docsAssist.fab.tooltip": {
23+
"message": "Ask AI ({shortcut})",
24+
"description": "Ask AI: the launcher's tooltip; {shortcut} is the keyboard shortcut, e.g. Ctrl+I"
25+
},
26+
"docsAssist.intro.exampleUpdates": {
27+
"message": "¿Cómo compruebo si hay actualizaciones al iniciar mi aplicación?",
28+
"description": "Ask AI: an example question; clicking it sends it to the assistant as-is"
29+
},
30+
"docsAssist.intro.exampleSigning": {
31+
"message": "¿Cómo firmo el código de mi aplicación en Windows y macOS?",
32+
"description": "Ask AI: an example question; clicking it sends it to the assistant as-is"
33+
},
34+
"docsAssist.intro.exampleAllUsers": {
35+
"message": "¿Puedo instalar para todos los usuarios en lugar de por usuario?",
36+
"description": "Ask AI: an example question; clicking it sends it to the assistant as-is"
37+
},
38+
"docsAssist.status.loading": {
39+
"message": "Cargando tu conversación…",
40+
"description": "Ask AI: status shown while a previous conversation is loaded"
41+
},
42+
"docsAssist.status.working": {
43+
"message": "Trabajando…",
44+
"description": "Ask AI: generic status shown while an answer is being prepared"
45+
},
46+
"docsAssist.limit.reached": {
47+
"message": "Esta conversación ha alcanzado su límite.",
48+
"description": "Ask AI: shown in place of the input when the conversation can't take more messages"
49+
},
50+
"docsAssist.action.startNew": {
51+
"message": "Iniciar una conversación nueva",
52+
"description": "Ask AI: button that discards the current conversation and starts a new one"
53+
},
54+
"docsAssist.header.newConversation": {
55+
"message": "Nueva conversación",
56+
"description": "Ask AI: tooltip of the header button that starts a new conversation"
57+
},
58+
"docsAssist.header.expand": {
59+
"message": "Expandir",
60+
"description": "Ask AI: tooltip of the button that makes the panel wider"
61+
},
62+
"docsAssist.header.close": {
63+
"message": "Cerrar",
64+
"description": "Ask AI: tooltip of the button that closes the panel"
65+
},
66+
"docsAssist.scrollToLatest": {
67+
"message": "Ir al último mensaje",
68+
"description": "Ask AI: button that scrolls the conversation to the newest message"
69+
},
70+
"docsAssist.header.title": {
71+
"message": "Ask AI",
72+
"description": "Ask AI: the chat panel's title"
73+
},
74+
"docsAssist.header.subtitle": {
75+
"message": "Asistente de Velopack",
76+
"description": "Ask AI: the chat panel's subtitle, under the title"
77+
},
78+
"docsAssist.header.collapse": {
79+
"message": "Contraer",
80+
"description": "Ask AI: tooltip of the button that makes the expanded panel narrow again"
81+
},
82+
"docsAssist.input.placeholderFollowUp": {
83+
"message": "Haz otra pregunta…",
84+
"description": "Ask AI: input placeholder once the conversation has messages"
85+
},
86+
"docsAssist.input.placeholder": {
87+
"message": "Haz una pregunta…",
88+
"description": "Ask AI: input placeholder in a new conversation"
89+
},
90+
"docsAssist.input.label": {
91+
"message": "Tu pregunta",
92+
"description": "Ask AI: accessible name of the question input"
93+
},
94+
"docsAssist.input.send": {
95+
"message": "Enviar",
96+
"description": "Ask AI: accessible name of the send button"
97+
},
98+
"docsAssist.input.tooLong": {
99+
"message": "Mensaje demasiado largo",
100+
"description": "Ask AI: screen reader text when the typed message is over the length limit"
101+
},
102+
"docsAssist.input.hint": {
103+
"message": "Pulsa Intro para enviar y Mayús+Intro para añadir una línea nueva.",
104+
"description": "Ask AI: screen reader hint for the question input"
105+
},
106+
"docsAssist.message.sender": {
107+
"message": "Velopack AI",
108+
"description": "Ask AI: the name shown above the assistant's answers"
109+
},
110+
"docsAssist.intro.title": {
111+
"message": "¿Cómo puedo ayudarte a publicar tu aplicación?",
112+
"description": "Ask AI: the greeting in a new conversation"
113+
},
114+
"docsAssist.intro.text": {
115+
"message": "Pregunta lo que quieras sobre Velopack. Las respuestas se elaboran a partir del código fuente de Velopack, esta documentación y los issues de GitHub, por lo que pueden tardar 30 segundos o más.",
116+
"description": "Ask AI: the introduction under the greeting in a new conversation"
117+
},
118+
"docsAssist.intro.examples": {
119+
"message": "Prueba a preguntar",
120+
"description": "Ask AI: the heading above the example questions"
121+
},
122+
"docsAssist.notice.backToPrevious": {
123+
"message": "Volver a la conversación anterior",
124+
"description": "Ask AI: button that returns to the conversation the visitor had before"
125+
},
126+
"docsAssist.notice.ipLimit": {
127+
"message": "Hoy has iniciado muchas conversaciones.",
128+
"description": "Ask AI: notice when the visitor has started too many conversations today"
129+
},
130+
"docsAssist.notice.ipLimitResetsAt": {
131+
"message": "Podrás iniciar una nueva a las {time}.",
132+
"description": "Ask AI: follows the conversation limit notice; {time} is a local time, possibly with a weekday"
133+
},
134+
"docsAssist.notice.ipLimitLater": {
135+
"message": "Vuelve a intentarlo más tarde.",
136+
"description": "Ask AI: follows the conversation limit notice when the reset time is unknown"
137+
},
138+
"docsAssist.notice.ipLimitGoBack": {
139+
"message": "Mientras tanto, puedes continuar con tu conversación anterior.",
140+
"description": "Ask AI: follows the conversation limit notice when the previous conversation can be resumed"
141+
},
142+
"docsAssist.notice.rateLimited": {
143+
"message": "Demasiadas solicitudes. Espera un minuto y vuelve a intentarlo.",
144+
"description": "Ask AI: notice when the visitor sends messages too quickly"
145+
},
146+
"docsAssist.notice.refusal": {
147+
"message": "Lo siento, no puedo ayudarte con eso.",
148+
"description": "Ask AI: notice when the assistant declines to answer a question"
149+
},
150+
"docsAssist.notice.tooLong": {
151+
"message": "Ese mensaje es demasiado largo. Debe tener menos de {max} caracteres.",
152+
"description": "Ask AI: notice when a message is over the length limit; {max} is a number"
153+
},
154+
"docsAssist.notice.ok": {
155+
"message": "Aceptar",
156+
"description": "Ask AI: button that dismisses a notice"
157+
},
158+
"docsAssist.notice.turnstile": {
159+
"message": "No hemos podido verificar tu navegador. Vuelve a enviar tu pregunta.",
160+
"description": "Ask AI: notice when the bot check (Cloudflare Turnstile) fails"
161+
},
162+
"docsAssist.notice.restoreFailed": {
163+
"message": "No se ha podido cargar tu conversación anterior.",
164+
"description": "Ask AI: notice when the stored conversation can't be loaded"
165+
},
166+
"docsAssist.notice.retry": {
167+
"message": "Reintentar",
168+
"description": "Ask AI: button that sends the last question again after an error"
169+
},
170+
"docsAssist.notice.errorAnswering": {
171+
"message": "Algo salió mal al responder. Vuelve a intentarlo.",
172+
"description": "Ask AI: notice when an answer fails part-way"
173+
},
174+
"docsAssist.notice.error": {
175+
"message": "Algo salió mal. Vuelve a intentarlo.",
176+
"description": "Ask AI: notice for an unexpected error"
177+
},
178+
"docsAssist.notice.capDiscord": {
179+
"message": "Discord de Velopack",
180+
"description": "Ask AI: link text to the Velopack Discord server, used in the 'assistant is resting' notice"
181+
},
182+
"docsAssist.notice.capIssues": {
183+
"message": "issues de GitHub",
184+
"description": "Ask AI: link text to the Velopack GitHub issues, used in the 'assistant is resting' notice"
185+
},
186+
"docsAssist.notice.capUntil": {
187+
"message": "El asistente está descansando hasta las {time}. Mientras tanto, prueba en el {discord} o en los {issues}.",
188+
"description": "Ask AI: notice when the assistant's daily budget is used up; {time} is a local time, {discord} and {issues} are links"
189+
},
190+
"docsAssist.notice.cap": {
191+
"message": "El asistente está descansando. Mientras tanto, prueba en el {discord} o en los {issues}.",
192+
"description": "Ask AI: notice when the assistant's daily budget is used up; {discord} and {issues} are links"
193+
},
194+
"docsAssist.notice.timeUnknown": {
195+
"message": "más tarde",
196+
"description": "Ask AI: stands in for {time} in the limit notices when the reset time can't be read"
197+
},
198+
"docsAssist.status.reconnecting": {
199+
"message": "Reconectando…",
200+
"description": "Ask AI: status shown while the connection to the assistant is being re-established"
201+
},
202+
"docsAssist.status.thinking": {
203+
"message": "Pensando…",
204+
"description": "Ask AI: status shown while waiting for the first part of an answer"
205+
},
206+
"docsAssist.status.resuming": {
207+
"message": "Retomando donde lo dejó…",
208+
"description": "Ask AI: status shown when reattaching to an answer that was still being written after a page reload"
209+
}
210+
}

0 commit comments

Comments
 (0)