Free

Getting Claude to Write Multilingual Tweet Threads Without Blowing the Limit

Multilingual threads aren't a translation problem — say 'write', not 'translate', and gate drafts with a local .length script.


Shipped 5 articles today. Each one gets a tweet thread in 6 languages (en / zh / ja / ko / ar / id), 4 tweets per language — 120 tweets total.

4 out of 5 needed trims on the Chinese thread. Raw numbers (printed by my script, format chars/limit):

zh[1]=160/140 OVER
zh[2]=164/140 OVER
zh[3]=152/140 OVER
...
id[3]=286/280 OVER
zh[2]=163/140 OVER
zh[3]=148/140 OVER
zh[2]=152/140 OVER

Same pattern every time: the English four tweets get drafted first (each 230–260 chars, pushing the 280 ceiling), then I ask Claude to "generate the Chinese version from these four." The Chinese version comes back over 140 almost every time.

This isn't a bug, it's the wrong word. The moment you say "translate," Claude starts going line-by-line — but a multilingual thread isn't a translation problem. Each language has to be written fresh against its own character budget. This post is about how to get Claude's first draft near the limit, so you don't burn 3–5 rounds trimming.


Root cause: X weights CJK double × Claude defaults to literal translation

X's character counting

X's limit isn't "character count" — it's weighted characters:
- Latin letters, digits, punctuation: 1 weight each
- CJK chars (Chinese/Japanese/Korean), some emoji: 2 weight each
- Total cap per tweet: 280 weight

So in practice:
- English tweet cap: 280 chars
- Pure-Chinese tweet cap: 140 chars (140 × 2 = 280)
- Mixed text: counted per-character, 1 or 2 each

So "translate a 260-char English tweet into Chinese" doesn't give you a 260-char Chinese version — the Chinese version has ~130 chars of real room. All the punctuation, quotes, backticks from the English original still take space, and every Chinese character doubles. Literal translation almost guarantees an overrun.

The word "translate" flips Claude into literal mode

When Claude sees "translate these 4 EN tweets to ZH," its first move is to preserve the English sentence structure and just swap the words:

  • EN: "Production 400/500 isn't like local. User's already staring at a blank screen after hitting Pay."
  • Claude's literal ZH: "生产环境的 400/500 和本地并不相同。用户已经点击了『付款』并看着一个空白屏幕。" (40 Chinese chars)

The meaning is right, but:
- "并不相同" preserved (one char longer than just "不同")
- "已经点击了" preserved ("刚点完" is shorter)
- "并看着一个空白屏幕" preserved ("看着空白屏" is shorter)

Every preserved redundancy eats chars. Stack that across 4 tweets and you're easily 20–40 chars over budget, and now you're past the limit.

The prompt that works: "rewrite," not "translate"

Compare the two phrasings:

What doesn't work:

"Translate these 4 English tweets to Chinese, Japanese, Korean, Arabic, Indonesian."

What does:

"For each of these 4 points, write a thread in ZH / JA / KO / AR / ID. Each language gets a fresh pass — same argument, adjusted for that language's tweet budget.

  • ZH: ≤ 140 chars per tweet, hook-first, cut detail if needed
  • other languages: ≤ 280 chars
  • use each language's natural phrasing, don't preserve EN sentence structure
  • each tweet must be self-contained but add 🧵 only on tweet 1"

The differences:

  1. "write" replaces "translate" — trips Claude's compose mode, not its translate mode.
  2. "fresh pass" / "same argument" — gives Claude permission to re-sequence the sentences.
  3. "don't preserve EN sentence structure" — blocks the default behavior head-on.
  4. "cut detail if needed" — lets Claude drop content to fit length (without it, it'll preserve every fact and overrun).
  5. Explicit budget per language — "≤ 140 chars" beats "short" 100 times over.

Local sanity check: Ruby + .length

Don't eyeball it. Write a small script that runs on every draft:

tweets.each do |loc, list|
  list.each_with_index do |t, i|
    len = t.length
    limit = loc == "zh" ? 140 : 280
    status = len > limit ? "OVER" : "ok"
    puts "#{loc}[#{i}]=#{len}/#{limit} #{status}"
  end
end

Ruby's String#length returns Unicode codepoints — each CJK character counts as 1. X weights CJK at 2, but a Chinese-only tweet's limit is 140 codepoints (because 140 × 2 = 280 weighted), so .length maps directly to X's cap. Edge cases:

  • Emoji codepoint counts (🧵 is 1 codepoint, but some emojis are multi-codepoint sequences)
  • Zero-width characters (ZWJ, etc.)

For the vast majority of CJK tweets, Ruby's .length is within ±1 of X's count. Using it as a local gate beats test-posting to X by 100×.

Emoji and backtick traps

When Chinese tweets need trimming, these two char types go first:

Backticks

Code references like `kamal exec` eat 1 char per backtick in a Chinese tweet — 2 chars per reference, zero semantic value. Fixes:

  • Use a colon instead: "命令 kamal exec" beats "`kamal exec`"
  • Only keep backticks for the key identifier, not full shell lines

Emoji

🧵 only on tweet 1. Other emojis in Chinese tweets — think twice. Each emoji basically costs 2 chars (1 codepoint + ZWJ modifiers in some cases).

Full-width vs half-width punctuation

  • CJK punctuation (「」、,。——): 2 weight each
  • ASCII punctuation ("",-): 1 weight each
  • When a Chinese tweet mixes quotes: use half-width quotes ('X') — saves 2 chars per pair vs. full-width (「X」)

Four default Claude moves to intercept

When generating multilingual threads, block these four:

1. Literal translation preserving EN structure

Symptom: Chinese sentences with phrases like「并不相同」「已经点击了」「一个空白屏幕」that are clearly calques from English.

Block: write "don't preserve EN sentence structure" / "use natural ZH phrasing" directly in the prompt.

2. No proactive length check

Symptom: Claude finishes and says "done," the output is over limit, and it doesn't notice.

Block: tell Claude to count each tweet's length itself ("count each tweet's char length and flag any over the limit"), or gate it locally with a script. I pick the script — Ruby beats Claude at counting.

3. Ignoring quote / emoji weight

Symptom: When Claude does count, it forgets emoji and full-width quotes carry extra weight.

Block: don't ask Claude "is this over limit" — run .length locally and then have it trim based on the numbers.

4. 🧵 placement inconsistency

Symptom: sometimes every tweet gets 🧵, sometimes only tweet 1, sometimes it lands on tweet 4/4.

Block: pin it in the prompt — "🧵 only on tweet 1." Claude doesn't default-know thread visual conventions.

Checklist

Six rules for getting a first-draft multilingual thread at-limit:

  1. Say "write," not "translate." Translate triggers literal rendering = preserved EN structure = overruns.
  2. Explicit budget per language. "≤ 140 chars" beats "short" 100×.
  3. Permission to cut content. "cut detail if needed" tells Claude it's allowed to lose facts for length.
  4. Local Ruby .length as gate. More accurate than Claude's own count, faster than test-posting.
  5. Trim backticks and emojis first in Chinese. Each backtick eats 1 char, each emoji eats 2.
  6. Pin 🧵 placement in the prompt. Claude doesn't default-know thread conventions.

The real insight: Claude can write multilingual threads correctly — but its default reaction is "translate." Your job is to flip it into "compose independently per language" via the prompt. Add a .length gate and today's last article's Chinese thread was 140/140 on first draft. No trimming.