Write translations
The Orchestrator for AI Agents — Connect OpenClaw, Hermes Agent, Claude Code, Codex, Cursor, Gemini CLI, OpenCode, Pi, and Qwen Code. Pool their knowledge, delegate tasks, and build your swarm of agents.
npx -y skills add tale-project/tale --skill write-translationsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 17 stars17 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use this skill whenever you edit any non-English file under services/*/messages/ (e.g. de.json, fr.json), packages/ui/src/i18n/messages/, or any page under docs/<locale>/, add a locale, or touch a glossary term — Tale ships as one narrator written natively per language, never a word-for-word render of the English. Load it before touching any non-English string; never translate by rendering the source words. Per-locale voice doctrine lives in locales/<locale>/AGENTS.md; the loanword buckets in BUCKETS.md, the conventions template in CONVENTIONS.md, the glossary workflow in GLOSSARY_GUIDE.md.
SKILL.md
9.8 KB, as published. Nobody here has run it
write-translations
Tale ships one calm, opinionated, second-person-informal narrator in three languages — the German page
is the same voice written natively in German, never a German rendering of the English. This file is the
cross-locale contract; the data that changes between languages (strike lists, drift patterns, gender
maps, formal-pronoun denylists) lives in the framework's per-locale test data under
packages/ui/src/i18n/tests/locales/ and the
per-locale voice files. Read this first, then the locale file for the locale you're in.
When this applies
Editing any non-English value under services/*/messages/ (e.g. de.json, fr.json),
packages/ui/src/i18n/messages/, or any page under docs/<locale>/. Then read
locales/<locale>/AGENTS.md for that language's voice doctrine and drift catalogue. The
two reliable failure modes are bureaucratic German (passive present, sentence-final erfolgreich,
third-person Sie) and marketed French (Découvrez, N'hésitez pas à, stacked nominal phrases) —
both translate the words and lose the voice.
Write a note first
Invoke write-notes and record your answers to this form before you translate:
- Locale & files: Describe the locale and files, and the source meaning you must convey (not the words).
- Voice risk: Describe this locale's drift mode to avoid (bureaucratic German / marketed French) and how you'll keep the native voice.
- Must-match: Describe the UI labels and compound terms that must stay exact, and how you confirmed them against the shipped strings.
- Risks & unknowns: Describe where the translation might read non-natively or drift from the source meaning.
The rules
These four fail review. The first is reviewer-caught (voice doesn't lint cleanly); the rest are enforced by the i18n test suite (see Patterns).
-
Same voice across locales. Translation is a rewrite of the same narrator in another language, not a render of the source words — translate meaning, not words. Sentence structure, idiom, and noun choice all differ: the German equivalent of an English three-clause sentence is often one sentence with a verb-final subordinate clause; the French equivalent of a stacked English noun phrase is often a relative clause. A page that reads calmly in English and bureaucratically in German has a tone bug; fix the wording. The drift modes are language-specific — your per-locale file names yours. (reviewer-caught)
-
Informal pronoun, always.
duin DE and de-CH,tuin FR. NeverSie, nevervous— formal pronouns put distance between Tale and the reader. The carve-out for sentence-initial DESie(third-person feminine) is built into the check. (enforced bypronouns-formal) -
The shipped UI string is the source of truth. Every button, menu, panel, or feature name in a translated page matches
services/platform/messages/<locale>.jsonexactly. When JSON and a glossary or doc disagree, the JSON wins — the contract bends to what ships. Half-translated walkthroughs (Öffne **Settings > Members**) are the most common bug. (enforced byterminology-ui-label) -
Compound terms are whole or kept whole.
Pull Requeststays English in DE/FR;Knowledge Basetranslates whole toWissensdatenbank/Base de connaissances. Half is always wrong —Pull Anfrage,Code Review-Prozess,Merge-Anfragefail. Whether a compound stays English or translates is a bucket decision (see BUCKETS.md); whether it's a half is the rule. (enforced byterminology-half-compound)
Patterns
A correct translation that correctly does not translate one thing:
EN: Open a pull request from your feature branch. The CI pipeline runs against the head of the branch; the merge into
mainis gated on green.DE: Öffne einen Pull Request aus deinem Feature-Branch. Die CI-Pipeline läuft gegen den Kopf des Branches; der Merge in
mainist erst möglich, wenn die Pipeline grün ist.
Pull Request,Feature-Branch,CI,Pipeline,Merge,Branchstay English (Git-domain loanwords; bucket 2).du, neverSie. Noerfolgreich, noWird X…. The English-kept terms are the words a German-speaking developer uses without thinking — not lazy translation.
The shipped UI read back to the reader:
Drift: Open Settings > Members und klicke auf Invite member.
Target: Öffne Einstellungen > Mitglieder und klicke auf Mitglied einladen.
The reader sees the German UI; the page must echo it. Specifics: code identifiers stay English
everywhere (CLI flags tale deploy --detach, env vars TALE_CONFIG_DIR, file paths, API paths POST /api/v1/documents); role names ship per locale (Owner / Inhaber / Propriétaire); parenthetical lists
translate ((Products, Customers, Vendors) → (Produkte, Kunden, Lieferanten)); navigation paths
translate segment by segment (Settings > Members → Einstellungen > Mitglieder, never
Einstellungen > Members).
Three buckets, summary (full lists + assignment in BUCKETS.md):
| Bucket | Examples | Behaviour |
|---|---|---|
| Always English | Tale, Convex, AI, LLM, MCP, env vars, CLI flags | Never translates — brand, acronym, code identifier. |
| Established loanwords | Workflow, Dashboard, Webhook, Pull Request, Branch, Merge | Stays English in DE/FR; hyphenated in DE compounds. |
| Translate-bucket | Header → Kopfzeile, Request → Anfrage, Email → E-Mail | Must translate in DE/FR/de-CH; caught by terminology-loanword. |
The bucket lives on each term's entry in
tests/glossary/glossary.json. Moving a
term between buckets is a glossary PR, not a skill PR.
What the suite catches — two layers run on every bun run check: parity + usage (sibling test
files in each consumer's lib/i18n/ — key parity, orphan detection), and the centralized
@tale/ui/i18n/tests — 26 checks (the registry's 28 entries in
tests/registry.ts minus parity + usage) over
terminology, voice, grammar, style, ICU parity, heuristics, and markdown. Most start in report mode
during rollout; flip to enforce after findings clear. Not caught — reviewer territory: subtler
calques past the small denylist, tone drift inside passing prose, sentence flow across clauses, ICU
plural correctness within branches, idiomatic word choice (Duden-correct ≠ native-sounding).
Adding a locale (e.g. Italian) — three concerns:
- Runtime registry — add
ittoSUPPORTED_LOCALESinpackages/ui/src/i18n/locales.ts. - Test framework data — create
packages/ui/src/i18n/tests/locales/it/withindex.ts,style.ts,voice.ts,terminology.ts,grammar.ts,patterns.ts, and aplanted/folder of positive/negative fixtures per applicable check; register it. The startup-drift assertion inlocales/index.tskeeps the runtime and test registries in sync. Optionally extendglossary.jsonwithitforms on translating terms. - Doctrine — create
locales/it/AGENTS.mdper the template in the existing locale files, and add its row to Companion files below.
Before you call the translation done
Tick every box, or N/A with a reason:
- Same voice as the source — rewritten natively, not word-rendered; no bureaucratic German, no marketed French.
- Informal pronoun throughout —
du(DE/de-CH),tu(FR); neverSie/vous. - Every UI label matches
services/platform/messages/<locale>.jsonexactly — no half-translated walkthroughs. - Compound terms whole or kept whole — no
Pull Anfrage/Merge-Anfrage; bucket decisions honoured. -
en.jsonparity — every key present, dead keys removed everywhere;de-CHholds only overrides. - The i18n suite is green —
bun run check.
Companion files
locales/<locale>/AGENTS.md— read when editing that locale's JSON or docs: the voice doctrine and language-specific drift catalogue (en,de,fr, and thede-CHSwiss overlay of differences-from-DE only).- BUCKETS.md — read when deciding whether an English term translates, stays English, or matches the UI verbatim; holds the full per-bucket lists, the assignment workflow, and the half-compound denylists.
- CONVENTIONS.md — read when handling quotes, apostrophes, dates, numbers, currency, percent, NBSP, dashes, or ß: the 14-row conventions template every locale fills.
- GLOSSARY_GUIDE.md — read when adding a glossary term, choosing its category, or
using
_lintExcludeto defer a UI-vs-bucket mismatch; also documents the role table and the audit script.