CLAUDE LABJP
BILLING — Day two after the Jun 15 change: Agent SDK, headless runs, GitHub Actions, and third-party agents now bill against separate monthly credits ($20/$100/$200) at full API rates with no rollover, making first-day cost measurements the basis for any reworkREGULATED — TCS partnered with Anthropic to bring Claude to banks, airlines, and other regulated industries, while DXC integrates Claude into the core systems those sectors rely onRETIRED — Sonnet 4 and Opus 4 left the API on Jun 15; confirm via your logs that scripts referencing them have moved to the latest generation such as Opus 4.8EXPORT — Claude Fable 5 and Mythos 5 remain suspended under a US export-control directive (since Jun 12); Anthropic says it is working to restore accessSAFE — Only the two new Mythos-class models are affected; every other model including Opus 4.8 keeps running normallySUBAGENTS — Claude Code sub-agents can spawn their own sub-agents up to five levels deep, widening the design space for multi-stage delegationBILLING — Day two after the Jun 15 change: Agent SDK, headless runs, GitHub Actions, and third-party agents now bill against separate monthly credits ($20/$100/$200) at full API rates with no rollover, making first-day cost measurements the basis for any reworkREGULATED — TCS partnered with Anthropic to bring Claude to banks, airlines, and other regulated industries, while DXC integrates Claude into the core systems those sectors rely onRETIRED — Sonnet 4 and Opus 4 left the API on Jun 15; confirm via your logs that scripts referencing them have moved to the latest generation such as Opus 4.8EXPORT — Claude Fable 5 and Mythos 5 remain suspended under a US export-control directive (since Jun 12); Anthropic says it is working to restore accessSAFE — Only the two new Mythos-class models are affected; every other model including Opus 4.8 keeps running normallySUBAGENTS — Claude Code sub-agents can spawn their own sub-agents up to five levels deep, widening the design space for multi-stage delegation
Articles/API & SDK
API & SDK/2026-06-17Advanced

Stop Terminology Drift in Localized Apps: A Consistent Localizable.strings Pipeline with the Batch API and a Cached Glossary

Translating UI strings one at a time invites inconsistency. Pair Claude's Message Batches API with a prompt-cached glossary to translate Localizable.strings across 10+ languages consistently, with measured costs and the pitfalls I hit in production.

api-sdk10batch-api3prompt-caching7localization2ios14

Premium Article

I learned this the hard way through an App Store rejection. The iOS wallpaper app I run as an indie developer supports more than ten languages, and on the settings screen I had translated "壁紙" as Wallpaper, but in the widget hint it had come out as Background. The cause was mundane: every time I added a string, I translated just that one string. Translate them one at a time and each one is done at a different moment, in a different context, so the same concept ends up with different words.

This kind of drift is less a translation-quality problem than a translation-procedure problem. Hire a human translator and the same thing happens if you never hand them a glossary. So the approach I settled on was to let Claude cache a glossary and a style guide, then translate every entry in Localizable.strings in one pass through the Message Batches API. This article walks through that design and implementation, the cost I actually measured when I pushed thousands of strings through it, and the pitfalls I hit when writing the results back.

Why I stopped translating one string at a time

A Localizable.strings file is just a flat list of key-value pairs:

"settings.wallpaper.title" = "壁紙の品質";
"widget.hint.background" = "ウィジェットに壁紙を設定";
"paywall.cta.primary" = "プレミアムを始める";

The trouble is that translating these in separate requests gives Claude (or a person) the freedom to render "壁紙" as Wallpaper here and Background there. UI consistency is not the sum of individually correct translations. The constraint "how do we unify the word wallpaper across the whole app" has to be fixed at the input stage of translation, not patched up afterward.

I chose to express that constraint in three layers. First, a glossary that declares "this source term must always map to this target term." Second, a style guide that sets tone — buttons in the imperative, settings titles as noun phrases. Third, an explicit placeholder-protection rule: never move %@ or %1$d. All three layers are identical for every string, so resending them with each request is pure waste. That is exactly where prompt caching earns its keep.

Turning the glossary and style guide into a cacheable system prompt

Prompt caching lets you attach cache_control to a system block so the prefix up to that point is cached. A glossary can run to hundreds of lines, but once it is cached, later requests reuse it at cache-read pricing — far cheaper than ordinary input tokens. Even with thousands of strings to translate, the glossary is read at full cost exactly once and then referenced repeatedly at the discounted rate.

# glossary.py — build the glossary and style guide as a cacheable system block
import anthropic
 
client = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY from the environment
 
# glossary: source term -> fixed translation per language; the app must always use these
GLOSSARY = {
    "壁紙": {"en": "Wallpaper", "fr": "Fond d'écran", "de": "Hintergrundbild"},
    "プレミアム": {"en": "Premium", "fr": "Premium", "de": "Premium"},
    "広告を非表示": {"en": "Remove Ads", "fr": "Supprimer les pubs", "de": "Werbung entfernen"},
}
 
STYLE_GUIDE = """\
- Buttons and CTAs: imperative and short (e.g. Remove Ads, Start Premium).
- Settings titles: noun phrases (e.g. Wallpaper Quality).
- Match politeness level to each language's conventions; do not over-formalize.
- Never alter format specifiers like %@ %1$@ %d %1$d, including their order.
- Preserve \\n and \\t exactly as they appear.
"""
 
def build_cached_system(target_lang: str) -> list:
    glossary_lines = "\n".join(
        f'  "{src}" => "{langs.get(target_lang, "")}"'
        for src, langs in GLOSSARY.items()
        if langs.get(target_lang)
    )
    instructions = (
        f"You translate app UI strings from Japanese into {target_lang}.\n\n"
        f"## Glossary (always use these target terms)\n{glossary_lines}\n\n"
        f"## Style guide\n{STYLE_GUIDE}"
    )
    # the prefix of a block carrying cache_control becomes cacheable
    return [{
        "type": "text",
        "text": instructions,
        "cache_control": {"type": "ephemeral"},
    }]

The key idea is to keep the glossary and style guide as a fixed, unchanging preamble that is separate from the strings being translated. By placing the variable part — the actual strings — in messages rather than system, the system block keeps hitting the cache. Rewrite system per string and the cache misses every time. I failed to honor that separation at first and spent a frustrating afternoon convinced "caching doesn't work" while my hit rate sat at zero.

The pricing dynamics of Message Batches and prompt caching move quickly, so I build my cost reasoning on top of async cost design for the Batch API and halving your monthly bill with prompt caching.

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
If your translations drift from screen to screen, you'll learn to cache a glossary that forces the same term everywhere across every string
You can decide how far the Batch API's 50% discount stacked with prompt caching actually cuts the cost of thousands of strings, from measured numbers
You'll get a validated pipeline that writes translations back into Localizable.strings without breaking %@, plural variants, or newline codes
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-05-15
Cutting Claude API Costs in Half with Messages Batches API — Design Patterns from an Indie Developer
How to reduce Claude API costs by up to 50% using the Messages Batches API. Includes async design patterns, real cost calculations, and production-ready error handling from an indie developer who runs four AI blogs on autopilot.
API & SDK2026-03-26
Claude API Cost Optimization Production Guide — Combining Batch API, Prompt Caching, and Adaptive Thinking for Up to 90% Savings
Learn practical implementation patterns to cut Claude API costs by up to 90%. Covers Batch API, Prompt Caching, and Adaptive Thinking strategies, plus production monitoring and budget management.
API & SDK2026-06-13
Auditing pinned model IDs before claude-sonnet-4 and claude-opus-4 retire from the API
On June 15, claude-sonnet-4 and claude-opus-4 retire from the API. Here is how to find every pinned model ID before then, measure output parity, and cut over safely with an alias layer and a fallback.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →