Your product supports twelve locales, but your translation pipeline is still a batch job that runs once a sprint. Meanwhile, users submit content in languages you never planned for, UI copy changes land daily, and your mobile app ships stale strings for weeks. Dynamic localization, translating content at runtime through translation APIs, solves this, but introduces its own class of engineering challenges: latency budgets, cache invalidation, placeholder corruption, and cascading failures when a third-party API goes down.
This guide walks through the architecture decisions, request patterns, failure-handling strategies, and concrete code you need to ship runtime localization that actually works in production. If you're evaluating how a managed localization layer can accelerate this work, explore how Ollang handles API-driven translation at scale.
Runtime Locale Negotiation
Before you translate anything, you need to know what language to translate into. Getting this wrong means wasted API calls, cache misses, and a jarring user experience.
Parsing Accept-Language and User Preferences
The Accept-Language HTTP header is your first signal. Browsers send a weighted list of locale preferences, and your server or edge function should parse it according to RFC 9110 Β§12.5.4: https://httpwg.org/specs/rfc9110.html#field.accept-language. A typical header looks like:
Accept-Language: de-CH, de;q=0.9, en;q=0.5
Parse the quality values, then match against your supported locales using a lookup algorithm (exact match β language match β default fallback). Libraries like @formatjs/intl-localematcher in JavaScript or Python's babel.core.negotiate_locale handle this reliably.
However, Accept-Language alone is insufficient. User-level preferences stored in your database should always override the header. A common precedence chain is:
- Explicit user preference (profile setting, cookie, or query parameter like ?lang=ja)
- Accept-Language header negotiation
- Geo-IP inference (least reliable, use only as a last resort)
- Application default locale
Store the resolved locale in your session or JWT claims so downstream services don't re-negotiate on every request.
Locale Fallback Chains for Partial Coverage
Not every locale has full translation coverage, especially for user-generated content or newly added UI strings. Define explicit fallback chains per locale rather than always falling back to English:
| Requested Locale | Fallback Chain |
|---|---|
| pt-BR | pt-BR β pt β en |
| zh-Hant-HK | zh-Hant-HK β zh-Hant β zh β en |
| fr-CA | fr-CA β fr β en |
When a translation API returns an empty result or an error for a specific locale, walk the chain automatically. This prevents users from seeing raw translation keys while still respecting regional preferences as closely as possible.
Caching Layers That Actually Help
Translation API calls cost money and add latency. A well-designed caching strategy eliminates the majority of redundant requests while keeping translations fresh.
Edge and CDN Caching with ETag and TTL
For pre-translated UI strings served as JSON bundles, cache at the edge. Set a Cache-Control header with a reasonable TTL, something like max-age=300, stale-while-revalidate=60 works well for strings that change a few times per day. Use ETag headers so CDN nodes can revalidate without re-fetching the full payload.
Structure your cache keys to include the locale and a content version hash:
/api/strings/v3/de-DE.json
When strings change, bump the version or invalidate the specific locale key. Cloudflare's Cache API and AWS CloudFront both support programmatic purging by key prefix, which lets you invalidate a single locale without flushing your entire cache.
For per-request translations of dynamic content (user-generated text, product descriptions), use an application-level cache like Redis. Key by a hash of the source text, source language, target language, and any glossary version:
translation:{sha256(source_text)}:{src}:{tgt}:{glossary_v}
Set TTLs based on content volatility. Product descriptions might cache for hours; chat messages might not cache at all.
When to Pretranslate vs Translate at Request Time
This is the most consequential architectural decision. The answer depends on content type, volume, and latency tolerance.
Pretranslate when:
- Content is known ahead of time (CMS pages, product catalogs, UI strings)
- You need sub-50ms response times
- The content changes infrequently relative to how often it's read
- You can afford the storage for all locale variants
Translate at request time when:
- Content is user-generated and unpredictable (reviews, comments, support tickets)
- The volume of source content is too large to pretranslate into every locale
- Freshness matters more than latency (live chat, real-time feeds)
- Only a fraction of content will ever be requested in a given locale
A hybrid approach is common: pretranslate your top locales and highest-traffic content, then translate on demand for the long tail. Use request analytics to identify which locale-content pairs should graduate from on-demand to pretranslated. Want this promotion logic automated across providers, locales, and cache layers? Schedule a guided walkthrough of how Ollangβs policy engine promotes content based on real traffic.
Request Patterns for Different Content Types
A two-word button label and a 2,000-word product description have fundamentally different translation requirements. Treating them the same wastes budget and degrades quality.
Short UI Strings: Batching and Context
UI strings are short, numerous, and context-dependent. Translating them one at a time is wasteful. Batch them into a single API request grouped by screen or feature area.
Most translation APIs accept arrays. A typical batched request payload looks like:
{
"source_lang": "EN",
"target_lang": "JA",
"texts": [
"Save changes",
"Discard",
"Are you sure you want to leave?"
],
"context": "Settings page confirmation dialog"
}
Including a context field (where supported) dramatically improves quality for ambiguous strings. The word "Post" translates differently as a noun (blog post) versus a verb (submit). Some APIs support this natively; others let you prepend context in the source text with a separator you strip from the response.
Keep batch sizes reasonable, typically under 50 strings or 5,000 characters per request, to stay within rate limits and avoid timeouts on large payloads.
Long-Form UGC: Chunking and Streaming
User-generated content like reviews, forum posts, or knowledge base articles requires a different approach. For content exceeding a few thousand characters, consider:
- Chunking by paragraph: Split on natural boundaries (double newlines, <p> tags) and translate chunks in parallel. Reassemble in order.
- Streaming responses: Some APIs support server-sent events or chunked transfer encoding for long texts. Use these to display partial translations progressively rather than blocking on the full response.
When chunking, never split mid-sentence. Sentence boundary detection (using libraries like nltk.sent_tokenize or ICU's BreakIterator) preserves translation quality that arbitrary character-count splits destroy.
Preserving Placeholders and ICU Message Syntax
Placeholder corruption is the most common dynamic localization bug. A string like Hello, {userName}! You have {count, plural, one {# item} other {# items}}. contains ICU MessageFormat syntax that the translation engine must not translate or restructure.
Strategies for preservation:
- Tag handling parameters: DeepL's tag_handling parameter set to xml or html instructs the engine to treat tagged content as non-translatable. Google Cloud Translation's mimeType field set to text/html achieves similar results.
- Placeholder wrapping: Before sending to the API, wrap placeholders in XML tags that the engine will preserve:
{
"text": "Hello, <x id='1'>{userName}</x>! You have <x id='2'>{count}</x> items.",
"tag_handling": "xml",
"ignore_tags": ["x"]
}
- Post-processing validation: After receiving the translation, verify that every placeholder present in the source also appears in the target. Flag or reject translations where placeholders are missing, duplicated, or reordered in ways that break your template engine.
Build a validation function that runs on every translated string before it reaches your UI layer. This is non-negotiable for production systems.
Glossary Injection and Terminology Control
Consistent terminology is what separates professional localization from raw machine translation. Your brand name, product features, and domain-specific terms need to translate the same way every time.
Attaching Glossaries to API Requests
Most enterprise translation APIs support glossary attachment. The typical workflow is:
1. Create a glossary resource via the API, uploading term pairs (source β target) for each language pair.
2. Reference the glossary ID in each translation request.
{
"source_lang": "EN",
"target_lang": "DE",
"text": "Enable the Workflow Builder to automate tasks.",
"glossary_id": "gl-abc123"
}
Glossaries enforce that "Workflow Builder" stays untranslated (or translates to your approved German term) rather than being rendered as a generic translation like "Arbeitsablauf-Ersteller."
Version your glossaries. When terminology changes, create a new glossary version and update the reference, don't mutate the existing glossary, or you'll invalidate cached translations without knowing it.
Translation Memory Integration
Translation memory (TM) complements glossaries by reusing previously approved translations at the segment level. If your localization platform maintains a TM, query it before hitting the translation API:
1. Look up the source segment in TM.
2. If a 100% match exists with approved status, use it directly (zero API cost, zero latency).
3. If a fuzzy match exists (typically above 75% similarity), send it to the API as a suggestion or reference to improve output quality.
4. If no match, translate via API and store the result back into TM after review.
This pattern reduces API costs significantly over time as your TM grows, and it ensures that human-reviewed translations are preferred over raw machine output.
If you're looking for a platform that orchestrates glossary enforcement, translation memory, and API routing in a single layer, see how Ollang's localization pipeline integrates these controls.
