◉CLAUDE LABJP
●2.1.302 — Fork subagents are now on by default in claude -p, the Agent SDK and the desktop app. Turn them off with CLAUDE_CODE_FORK_SUBAGENT=0 (10/16)●2.1.301 — Unattended retries now stop on their own when a usage limit is hit instead of waiting (10/15). Latest is 2.1.304 (10/19)●SONNET 4.5 — Retires on the Claude API on 11/30, 40 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…●2.1.302 — Fork subagents are now on by default in claude -p, the Agent SDK and the desktop app. Turn them off with CLAUDE_CODE_FORK_SUBAGENT=0 (10/16)●2.1.301 — Unattended retries now stop on their own when a usage limit is hit instead of waiting (10/15). Latest is 2.1.304 (10/19)●SONNET 4.5 — Retires on the Claude API on 11/30, 40 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…
Articles/API & SDK
⬡ API & SDK/2026-09-06Intermediate

The translation read perfectly and still crashed at runtime

When you translate app strings with the Claude API, the meaning can be right while the format specifiers quietly break. Here is a severity-aware acceptance check and a repair loop that only re-translates the broken lines.

Claude API130localization4i18nquality gateindie developer21

✦ Premium Article

I was a few days into a 5% staged rollout of a wallpaper app when crash reports started arriving from one language and nowhere else. The only thing I had touched was a batch of in-app strings. No code had changed.

I read the translations top to bottom. Every line was fluent and said the right thing. Nothing looked wrong as prose.

The problem only surfaced when I stopped reading sentences and lined up the symbols instead. The source had %1$@. The translation had %@.

As long as you evaluate a translation as writing, format-specifier damage stays invisible. It isn't a vocabulary problem. It's a syntax problem.

What broke was argument binding, not meaning

A format specifier is a hole where a value gets injected at runtime. Both String(format:) on iOS and String.format on Android assign arguments by counting those holes and checking their types.

A translation model translates sentences. The holes are part of the sentence, so when word order gets rearranged, positional indexes get dropped, or the token gets rewritten to whichever platform convention the model considers idiomatic. There is no malice and no carelessness involved — it falls out of producing a natural sentence.

I started by checking what actually happens when arguments are short or mistyped. Here is real output from Python's % formatting:

"%s / %s" with one argument: TypeError: not enough arguments for format string
"%d" with a string:          TypeError: %d format: a real number is required, not str
"%(n)s" with an empty dict:  KeyError: 'n'
"%s" with two arguments:     succeeded -> 'a'   <- no exception

That last line is the important one. Extra holes crash. Extra arguments do not. Android's String.format has the same asymmetry: referencing an argument that was never supplied raises MissingFormatArgumentException, a type mismatch raises IllegalFormatConversionException, but leaving an argument unused raises nothing at all.

So "a specifier disappeared" and "a specifier appeared" are two different incidents. The first silently loses information. The second takes the device down. Collapsing both into a single "mismatch" verdict means you either fail to stop the dangerous one, or you block releases over the harmless one.

Six ways it breaks

Everything I collected fell into six shapes.

ShapeSourceTranslationEffect
Positional index dropped%1$@ items in %2$d s%@ items in %d sOrder becomes language-dependent
Platform convention swapped%1$@%1$sInvalid on iOS
Specifier missing%1$@ to %2$@Moved %1$@Information disappears
Specifier addedSaved %1$@Saved %1$@ to %2$@Crashes at runtime
Glyph corrupted%1$@%1$@ / % 1$@No longer parsed as a specifier
Escape lost%1$d%%%1$d%A bare % survives into the format string

Glyph corruption shows up more often than I expected whenever a Japanese keyboard sits anywhere in the pipeline. A full-width % is essentially indistinguishable by eye.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦You'll be able to stop format-specifier damage before shipping, even when the translated text reads perfectly
✦You'll be able to treat a dropped specifier and an added one as different incidents, because only one of them crashes
✦You'll be able to add a masked, line-scoped repair loop to a translation pipeline you already run
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

⬡ API & SDK2026-04-30
Building a Production Multilingual Translation SaaS with Claude API — Glossaries, Style, and Domain Adaptation in Practice
A practical, code-first design guide for running a translation SaaS on Claude API: glossaries, style guides, domain adaptation, quality gates, and cost controls that survive real production traffic.
⬡ API & SDK2026-10-20
Moving Structured Outputs to output_config.format — What to Check Before You Drop the Beta Header
A checklist for moving structured outputs from output_format and the beta header to output_config.format, drawn from the official docs: the Python SDK TypeError, prompt-cache invalidation, schema limits, and the cases where output won't match your schema.
⬡ API & SDK2026-10-18
43 Days Until Sonnet 4.5 Retires: I Counted Callers From the Usage CSV, Not From grep
claude-sonnet-4-5 retires on November 30. grep only shows what is written in code. Here is a single script that cross-checks model IDs in your code against the Console's Usage CSV, with the results from running it.