Table of Contents
Table of Contents
- CDN Translation Delivery: A Guide to Edge-Cached i18n Performance
- Why Translation Delivery Matters for i18n Performance
- The URL Structure That Makes It Work
- Upload vs. Merge: Two Models for CDN Updates
- Full Upload (cdn.upload and cdn.uploadBatch)
- Incremental Merge (cdn.merge)
- Merge Preview (cdn.mergePreview)
- Duplicate Detection: Keeping Your CDN Payload Lean
- Detecting Duplicates (cdn.detectDuplicates)
- Cleaning Up (cdn.cleanupDuplicates)
- CDN Lifecycle Management
- Setup (cdn.setup)
- File Management
- Teardown (cdn.uninstall)
- Performance in Practice
- Namespace-Based Code Splitting for Translations
- Integration Approaches
- SDK Integration (Recommended)
- Direct HTTP (Any Platform)
- The Publish-to-CDN Pipeline
- Getting Started
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:
- Browser caches work natively — each locale-namespace pair has a unique, stable URL
- Service workers can precache known paths without discovery requests
- CDN edge nodes cache independently per path, so a cache miss for
fr/auth.jsondoes not affecten/common.json - 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:
| Metric | Value |
|---|---|
| Edge locations | 300+ (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
SDK Integration (Recommended)
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:
- Edit — Dashboard, REST API, or MCP tools
- Preview pending changes —
getPendingChangesshows what will deploy - Preview merge result —
cdn.mergePreviewshows key-level diff (optional) - Publish —
publishTranslationstriggers thecdn_uploadsync job - Monitor —
getSyncs/getSynctracks deployment status - Verify —
cdn.listFilesconfirms the updated file is live
This gives engineering teams the same rigor for translation deployments as code deployments: preview, publish, monitor, verify.
Getting Started
- Create your project at dash.better-i18n.com
- Run
cdn.setupto initialize R2 storage and edge routing - Add translations and publish them to CDN
- Integrate with your framework SDK or use direct HTTP
- Monitor with
cdn.listFilesandcdn.detectDuplicates
CDN delivery is available on all plans, including the free tier. Your translations are edge-cached from day one.