Translation with Claude Code Skills
How custom skills and subagents make multilingual publishing faster, cheaper, and better than manual translation I can now translate every blog post into French and Japanese in under a minute, with no manual edits. The translations read incredibly natural, even for my most technical content. And with a Claude subscription, the marginal cost is zero. Hard not to be AI-pilled these days. This post explains how I built this using Claude Code skills and subagents. Two Claude Code features make this possible. A skill is a reusable prompt that teaches Claude how to perform a specific task. Skills can read files, run shell scripts, and coordinate complex workflows. You can invoke them with a slash command (like A subagent is a separate Claude instance that the main agent can spawn. Each subagent has its own system prompt, starts with a fresh context window, and doesn't pollute the main agent's context with its work. Multiple subagents can run in parallel. All of these properties turn out to matter for quality. This blog runs on Zola with markdown files. The approach generalizes to any blog with human-readable source files. Here's the relevant structure: The main agent orchestrates everything using the sync-translations skill. It runs the The first step is the shell script.1 For each English post and target language, it outputs one of three states: NEW (no translation file exists), SYNC (translation exists but English changed), or ABORT (translation is current). NEW and ABORT are straightforward file checks. SYNC is trickier. We need git history—not just file modification times—because the agent needs to know what changed, not just that something changed. Without the exact diff, it would re-translate the entire post, making translations unstable as previously-polished sections get unnecessarily rewritten. The script finds when the translation content was last updated, then extracts the English diff since. For a post like Any diff output means the English source diverged and the translation needs syncing. The script uses different labels internally: The main agent drafts translations, but it doesn't review its own work. A separate editor subagent handles that—and the separation matters. When you've just written something, you're biased toward it. The phrasing looks fine because you just chose it. Awkward constructions slip through. A fresh reader catches what the writer misses. This is true for humans; it's true for LLMs too. The editor starts with a clean context. It sees only the English source, the translation to review, and a shared learnings file (more on that below). It checks for naturalness (does this sound translated or native?), idiom adaptation (were English expressions translated by meaning, not literally?), technical terminology (standard terms in the target language?), and voice (does it still sound like me?). The handoff depends on context. For new translations, the editor does a full review. For syncs, it focuses on changed sections—the rest was already reviewed. And since each language gets its own editor working on an independent file, they run in parallel. Both agents share a learnings file per language. The main agent reads it before drafting; the editor reads it during review and appends new discoveries afterward. This creates a feedback loop: each translation makes the next one better. For French, the file now captures that "proof by contradiction" should be "par l'absurde" (not the literal "en vue d'une contradiction"), that "attends to" in attention mechanisms means "prête attention à" (not "assiste à"), and that technical terms like "forward pass" should stay in English. For Japanese, it records to avoid em dashes entirely (they're not standard in Japanese text), that "sent me down a rabbit hole" becomes 「沼にはまってしまった」 (fell into a swamp—a natural Japanese idiom for obsessive exploration), and that series numbering should use 「第N回/全M回」 format. These aren't rules I wrote upfront. They emerged from editing sessions and compound with each post. I'm a native French speaker who spent a decade living and working in Japan. Writing this blog in English made sense for reach, but it meant leaving readers (friends and family) behind. The alternatives weren't great. Manual translation would take 3-4 hours per post per language, and the quality would be mediocre: I learned my technical vocabulary in English, my math in French, and while I'm fluent in Japanese, writing polished prose is a skill I haven't practiced much. Professional translation is expensive. And anyone who's read Google Translate output for technical content knows the cringe: awkward phrasing, wrong terminology, prose that screams "I am a robot." This changes the game. The translations aren't perfect, but darn close. They cost me nothing but a minute of waiting. I wouldn't have bothered translating this blog without that option available to me. I suspect we'll see a lot more multilingual content online in the months to come. The system took a few hours to build. Now I just write in English, commit, and run This post was written in collaboration with Claude (Opus 4.5).
Skills and Subagents
/sync-translations as we'll see in the next section), or Claude Code can trigger them automatically based on context.The System at a Glance
.claude/
├── skills/
│ └── sync-translations/
│ ├── SKILL.md # the skill definition
│ └── check-sync.sh # detects what needs work
├── agents/
│ └── translation-editor.md # reviews translations
└── translation-learnings/
├── fr.md # French terminology & style
└── ja.md # Japanese terminology & style
content/blog/
├── kv-cache-invalidation.md # English original
├── kv-cache-invalidation.fr.md # French translation
├── kv-cache-invalidation.ja.md # Japanese translation
├── blog-translation-with-claude-code.md # this post (no translations yet)
└── ...
check-sync.sh shell script to detect what needs work, reads the {fr.md, ja.md} learnings files for terminology guidance, drafts translations, then spawns two translation-editor subagents (one for each language) to review them with fresh eyes. The editors feed discoveries back into the learnings files, so the system improves over time:Detecting What Needs Work
kv-cache-invalidation.md with French translation kv-cache-invalidation.fr.md:# Find the commit where French content last changed (not just renamed)
baseline=
# Check if English changed since that baseline
NEEDS TRANSLATION, NEEDS SYNC, and UP TO DATE.Drafter and Editor
Learnings That Accumulate
Bottom Line
/sync-translations. This post was translated using it—if you read the French or Japanese version, you're seeing the result. If you want to build something similar, follow the links throughout this post or explore the full repo.