Install
$ agentstack add skill-tale-project-tale-write-translations ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
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/](../../../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//. Then read [locales//AGENTS.md](locales/) 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 DE Sie (third-person feminine) is built into the check. (enforced by pronouns-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/.json exactly. 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 by terminology-ui-label)
- Compound terms are whole or kept whole.
Pull Requeststays English in DE/FR;Knowledge Base
translates whole to Wissensdatenbank / Base de connaissances. Half is always wrong — Pull Anfrage, Code Review-Prozess, Merge-Anfrage fail. Whether a compound stays English or translates is a bucket decision (see [BUCKETS.md](BUCKETS.md)); whether it's a half is the rule. (enforced by terminology-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 main is 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 main ist erst möglich, wenn die Pipeline grün ist. > > Pull Request, Feature-Branch, CI, Pipeline, Merge, Branch stay English (Git-domain > loanwords; bucket 2). du, never Sie. No erfolgreich, no Wird 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](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](../../../packages/ui/src/i18n/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](../../../packages/ui/src/i18n/tests/) — 26 checks (the registry's 28 entries in [tests/registry.ts](../../../packages/ui/src/i18n/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_LOCALESin
[packages/ui/src/i18n/locales.ts](../../../packages/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 a planted/ folder of positive/negative fixtures per applicable check; register it. The startup-drift assertion in locales/index.ts keeps the runtime and test registries in sync. Optionally extend glossary.json with it forms 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/.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//AGENTS.md](locales/) — read when editing that locale's JSON or docs: the voice
doctrine and language-specific drift catalogue (en, de, fr, and the de-CH Swiss overlay of differences-from-DE only).
- [BUCKETS.md](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](CONVENTIONS.md) — read when handling quotes, apostrophes, dates, numbers, currency,
percent, NBSP, dashes, or ß: the 14-row conventions template every locale fills.
- [GLOSSARYGUIDE.md](GLOSSARYGUIDE.md) — read when adding a glossary term, choosing its category, or
using _lintExclude to defer a UI-vs-bucket mismatch; also documents the role table and the audit script.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: tale-project
- Source: tale-project/tale
- License: MIT
- Homepage: https://tale.dev
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.