One article in 19 languages: file structure, translation prompt, 5 real traps, how to POST them all at once.
Anthropic's and OpenAI's official docs are English only. That's a massive gap in the global AI-developer content ecosystem. how2claude is filling it — every article ships in 19 languages at once: zh / en / zh-TW / ja / ko / es / pt-BR / id / vi / tr / ar / fr / de / it / ru / uk / pl / he / th.
Hiring humans for this is a nonstarter (20 translations per article, thousands of dollars each). Letting Claude do it drives marginal cost close to zero — but tossing "translate to Japanese" over the fence gets you machine-translation-flavored prose with the author's voice stripped out. What follows is the complete workflow for doing this right: file structure, how to write the translation prompt, where Claude will definitely break, how to POST 19 languages plus per-language tweets to an API in one shot.
Two very different things.
Translation moves meaning from language A to B — what Google Translate does. Reads "correct" but "flat."
Localization preserves the author's register, rhythm, and technical level, rewriting in the target language's own "tech-blog voice." Reads like it was written natively.
Translation is a 5-minute job for Claude; localization needs the prompt to spell out:
how2claude's /write-article command bakes a style requirement per language:
| Language file | Style requirement |
|---|---|
ja.md |
Tech-blog voice, natural Japanese |
ko.md |
Tech-blog voice, natural Korean |
zh-TW.md |
Taiwanese usage, traditional characters |
ar.md |
Modern Standard Arabic (written) |
id.md |
Standard Indonesian |
Looks like a one-liner. But for Claude it's pivotal — without it the default is "textbook translation."
One directory per draft, 20 files:
docs/drafts/let-claude-translate-articles/
├── meta.json # title + summary for all 19 languages
├── zh.md # Chinese (source, written first)
├── en.md # English (first batch of translations)
├── zh-TW.md
├── ja.md
├── ko.md
├── ...(14 more)
└── th.md
meta.json:
{
"category_slug": "use-cases",
"series_slug": "writing",
"free": true,
"title": {
"zh": "让 Claude 把一篇文章翻译成 19 种语言",
"en": "Letting Claude Translate One Article Into 19 Languages",
"ja": "Claude に 1 本の記事を 19 言語に翻訳させる",
"...": "..."
},
"summary": {
"zh": "一篇文章 19 种语言:文件结构、翻译 prompt、5 个真实翻车点、如何一次 POST。",
"...": "..."
}
}
Each .md file's first line is # title, the rest is body. Title doesn't come from the md first line — it comes from meta.json, so each language's title is a polished short phrase instead of the article's opening sentence that may have stretched during translation.
Two flows to avoid:
Wrong: chain translation. zh → en → ja → ko → ...
Problem: ja is based on en, ko on ja — every hop bleeds a little meaning. By th (Thai) you've got fourth-hand information.
Right: radial translation. Finalize zh → generate en / ja / ko / ar / id / ... all directly from the source.
Every language comes straight from the original, no intermediate hops.
Prompt shape (packaged inside /write-article):
Here's a tech blog article about X, Chinese source:
[full zh.md]
Translate to these languages, with these per-language requirements:
- ja.md: tech-blog voice, natural Japanese
- ko.md: tech-blog voice, natural Korean
- ar.md: Modern Standard Arabic (written)
...
Rules:
1. Preserve the author's direct, professional, slightly self-deprecating register
2. Don't translate code blocks, commands, API names, URLs
3. Titles, subheadings, paragraph flow may be recomposed
4. Produce a separate title per language — short, with a hook, not a literal translation
Translate 18 languages in parallel, one Claude session. One key detail: do it in the same session / same model version — voice drifts across sessions and model generations.
X counts CJK characters (Chinese/Japanese/Korean) more strictly. I had Claude generate tweets with this constraint:
First time through, three Chinese tweets ran long (160, 164, 152). Claude had translated the English tweets literally — an English tweet at 260 chars turns into Chinese right at the limit.
Rule: Chinese tweets must be rewritten, not translated. Requirements: 140-char cap, must have a hook, some detail loss allowed.
zh → zh-TW invites laziness: pipe it through a simplified-to-traditional converter. Characters match, vocabulary doesn't:
| zh | zh-TW (wrong) | zh-TW (right) |
|---|---|---|
| 文件 | 文件 | 檔案 |
| 信息 | 信息 | 資訊 |
| 软件 | 軟件 | 軟體 |
| 视频 | 視頻 | 影片 |
When asking Claude for zh-TW, say both: "Taiwanese usage, traditional characters." "Traditional only" yields a character conversion.
Arabic and Hebrew read right-to-left. Rails-side you need <html dir="rtl"> and Tailwind's rtl: variants. But the translation itself has pitfalls:
unicode-bidi: isolate or the code gets "sucked" into the RTL flow، instead of ,, ؟ instead of ?. Claude defaults to English punctuation; spell it out2026) inside a right-to-left paragraph. Browsers handle this, but Claude often mixes it up in raw textThe original's casual phrases ("knock out in one shot," "stepped in it," "ate the fall") Claude translates into Japanese/Korean as formal written forms (「一気に実装」「落とし穴」「失敗」) — meaning matches, tone evaporates.
Fix: spell it out — "preserve the direct, slightly self-deprecating, informal tech-blog voice; don't academize." Even then, some languages (German, Russian) will land a touch more "formal" than the source — the languages themselves have a written-register bias.
Strings in code sit in two buckets — UI text (translate) and placeholders/variable names (don't):
t("pricing.page_title") # don't translate (i18n key)
"user_id" # don't translate (variable name)
"Monthly subscription" # shown as an example in the text → don't translate
For anything adjacent to code, "don't translate" is the safer default. Translating i18n locale files (config/locales/xx.yml) is a separate job.
/publish-article POSTs the draft to how2claude.com/api/articles:
{
"category_slug": "use-cases",
"series_slug": "writing",
"free": true,
"thread": true,
"title": { "zh": "...", "en": "...", "ja": "...", ... },
"summary": { "zh": "...", "en": "...", ... },
"content": { "zh": "<md>", "en": "<md>", ... },
"tweets": { "en": ["...", "..."], "zh": [...], ... }
}
One request is ~300KB (19 languages × ~15KB). Server splits into DB tables:
articles table, title / summary / content are jsonb keyed by localex_queue_tweets table, per locale + account, dispatched into a scheduling queueTweets only ship to locales with a connected X account (currently en/zh/ja/ko/ar/id — 6). Languages without an account send []. Querying accounts:
Account.all.each { |a| puts "#{a.locale}: #{a.name}" }
# en: @how2claude
# zh: @howtoclaude
# ja: @how2claude_ja
# ko: @how2claude_ko
# ar: @how2claude_ar
# id: @how2claude_id
Letting Claude translate one article into 19 languages — full checklist:
dir="rtl" + Tailwind rtl: variants on the Rails side. Mixed punctuation and code-block isolation need handling separately.The real bottleneck isn't translation quality — Claude-4-class models translate into Japanese/Korean/Arabic/Russian at near-native quality. The bottleneck is whether you're willing to put one article in front of readers across 19 languages. Technically it takes 5 minutes. Content-wise it takes a willingness to serve 200x the audience at once.