Engineering

CDN Translation Delivery: A Guide to Edge-Cached i18n Performance

Eray Gündoğmuş
Eray Gündoğmuş
·7 min read
Share
CDN Translation Delivery: A Guide to Edge-Cached i18n Performance

CDN Translation Delivery: A Guide to Edge-Cached i18n Performance

Translation delivery is a performance problem hiding in plain sight. Your app might have sub-second page loads, optimized images, and code-split bundles — but if translations arrive late, users see layout shifts, loading spinners, or worse, untranslated keys flickering on screen.

This guide covers how better-i18n solves translation delivery using Cloudflare R2 and edge caching, and walks through the full set of CDN operations available to engineering teams.


Why Translation Delivery Matters for i18n Performance

Traditional i18n setups bundle translations into the application. Every time you fix a typo or add a new language, you need a full rebuild and redeploy. For mobile apps, that means waiting for app store review. For web apps, it means a deploy pipeline that blocks a non-engineering change.

CDN translation delivery decouples translation content from application code. Translations live on the edge, served from the nearest Cloudflare node to your user. Your app fetches them at runtime over HTTP.

The performance characteristics:

  • Sub-50ms delivery from the nearest edge node (300+ locations worldwide)
  • Zero origin round-trips for cached translations
  • Incremental updates without application redeployment
  • Parallel loading of only the namespaces the current page needs

The URL Structure That Makes It Work

Every translation file in better-i18n follows a deterministic URL pattern:

https://cdn.better-i18n.com/{org}/{project}/{locale}/{namespace}.json

This structure is intentionally simple and predictable. It means:

  1. Browser caches work natively — each locale-namespace pair has a unique, stable URL
  2. Service workers can precache known paths without discovery requests
  3. CDN edge nodes cache independently per path, so a cache miss for fr/auth.json does not affect en/common.json
  4. Any HTTP client can consume it — no SDK, no auth, no special headers

A few real examples:

# English common translations
https://cdn.better-i18n.com/acme/web-app/en/common.json

# Turkish authentication strings
https://cdn.better-i18n.com/acme/web-app/tr/auth.json

# Japanese dashboard translations
https://cdn.better-i18n.com/acme/web-app/ja/dashboard.json

Each project also has a manifest at /{org}/{project}/manifest.json listing all available locales. Mobile SDKs use this to auto-discover languages at runtime without hardcoding locale lists.


Upload vs. Merge: Two Models for CDN Updates

better-i18n provides two distinct approaches to updating translations on the CDN.

Full Upload (cdn.upload and cdn.uploadBatch)

Upload replaces the entire namespace-locale file on R2:

# Single file
better-i18n cdn upload --locale en --namespace common

# Multiple files in one operation
better-i18n cdn upload-batch --locales en,tr,fr --namespaces common,auth

Batch upload is the right choice when:

  • Publishing a new language for the first time
  • Rebuilding CDN files after a major restructuring
  • Deploying a full translation export from your TMS

Batch operations are optimized internally — R2 writes and cache invalidations are batched, reducing total propagation time compared to sequential individual uploads.

Incremental Merge (cdn.merge)

Merge reads the existing file from R2, applies key-level changes, and writes the result back:

better-i18n cdn merge --locale en --namespace common --keys "welcome,nav.home"

Keys not included in the merge remain unchanged. This is the safer option for incremental updates because:

  • No accidental overwrites — other keys in the file are preserved
  • Concurrent safety — multiple team members or CI pipelines can merge different keys without conflict
  • Smaller change surface — only the specified keys are touched

Merge Preview (cdn.mergePreview)

Before executing a merge, preview the result:

better-i18n cdn merge-preview --locale en --namespace common --keys "welcome"

This returns a diff showing which keys would be added, updated, or left unchanged. Use merge preview in CI pipelines to gate CDN deployments behind a review step — the same way you would review a database migration before applying it.


Duplicate Detection: Keeping Your CDN Payload Lean

As translation projects grow, duplicates accumulate. A "Save" button string might exist in common.json, settings.json, and profile.json with identical values across all three. Each duplicate increases total CDN payload and creates maintenance overhead — update one, forget the others.

Detecting Duplicates (cdn.detectDuplicates)

better-i18n cdn detect-duplicates

This scans all CDN files in your project and produces a report of keys that share identical translation values across namespaces. The report shows:

  • Which keys are duplicated
  • Which namespaces contain them
  • The shared value

Cleaning Up (cdn.cleanupDuplicates)

better-i18n cdn cleanup-duplicates --target-namespace common

This consolidates duplicates into a target namespace (typically common) and removes them from source namespaces. The operation uses merge internally, so it is safe for concurrent access.

Best practice: Run cdn.detectDuplicates after every major translation import or namespace reorganization. Schedule periodic cleanups to keep CDN payloads minimal.


CDN Lifecycle Management

Setup (cdn.setup)

Initialize CDN delivery for your project:

better-i18n cdn setup

This creates the R2 bucket structure, configures Cloudflare Worker edge routing, and sets up cache rules. Run this once when first enabling CDN delivery for a project.

File Management

List all deployed files to audit your CDN state:

better-i18n cdn list-files

Returns each file's locale, namespace, size, and last-modified timestamp. Useful for verifying deployments and identifying stale files.

Delete individual files when deprecating a namespace or removing a locale:

better-i18n cdn delete-file --locale en --namespace legacy-feature

Teardown (cdn.uninstall)

Remove CDN delivery entirely:

better-i18n cdn uninstall

Cleans up R2 storage, edge configuration, and cache rules. Use when migrating a project or decommissioning an application.


Performance in Practice

Here is what CDN translation delivery looks like in production:

MetricValue
Edge locations300+ (Cloudflare network)
Cache hit latency< 50ms (typical)
Cache miss latency< 200ms (R2 origin fetch)
Propagation time< 10 seconds after publish
Egress cost$0 (Cloudflare R2)

The zero-egress-cost aspect is worth emphasizing. Traditional CDN setups charge per GB of bandwidth. With R2, your translation delivery costs are fixed regardless of traffic volume. A project serving 10 million translation fetches per month pays the same as one serving 1,000.

Namespace-Based Code Splitting for Translations

The namespace model acts as code splitting for translations. Instead of loading all translations upfront:

❌ /en/all-translations.json (450 KB)

Your app loads only what the current route needs:

✅ /en/common.json (12 KB) + /en/auth.json (4 KB) = 16 KB

This reduces initial page load by 90%+ for apps with large translation sets. Combined with edge caching, subsequent page navigations fetch only the new namespace — dashboard.json is loaded when the user navigates to the dashboard, not before.


Integration Approaches

The official SDKs handle CDN fetching, caching, and fallback automatically:

  • @better-i18n/next — Server-side fetching with ISR/SSG support
  • @better-i18n/use-intl — Client-side fetching for TanStack Router and Vite apps
  • @better-i18n/expo — Offline-first with MMKV/AsyncStorage caching

Each SDK resolves the CDN URL from your i18n.config.ts project identifier. No manual URL construction needed.

Direct HTTP (Any Platform)

The CDN is a plain HTTPS endpoint. No authentication, no special headers:

curl https://cdn.better-i18n.com/acme/web-app/en/common.json

This makes better-i18n translations consumable from any platform: iOS (Swift), Android (Kotlin), backend services (Go, Python, Ruby), game engines, IoT devices — anything that can make an HTTP GET request.


The Publish-to-CDN Pipeline

The full workflow from editing a translation to serving it globally:

  1. Edit — Dashboard, REST API, or MCP tools
  2. Preview pending changesgetPendingChanges shows what will deploy
  3. Preview merge resultcdn.mergePreview shows key-level diff (optional)
  4. PublishpublishTranslations triggers the cdn_upload sync job
  5. MonitorgetSyncs / getSync tracks deployment status
  6. Verifycdn.listFiles confirms the updated file is live

This gives engineering teams the same rigor for translation deployments as code deployments: preview, publish, monitor, verify.


Getting Started

  1. Create your project at dash.better-i18n.com
  2. Run cdn.setup to initialize R2 storage and edge routing
  3. Add translations and publish them to CDN
  4. Integrate with your framework SDK or use direct HTTP
  5. Monitor with cdn.listFiles and cdn.detectDuplicates

CDN delivery is available on all plans, including the free tier. Your translations are edge-cached from day one.