Skip to content
better-i18n.com
On this page

Download translations from the Better i18n CDN to local JSON files — the offline safety net for mobile apps, and the build-time message files for web frameworks like next-intl.

Why pull? #

Mobile (Expo, React Native, Swift, Flutter) depends on CDN translations at runtime. If the CDN is unreachable — airplane mode, slow network, or App Store Review environment — the app needs a fallback. pull downloads translations so you can bundle them as staticData.

Code
CDN available    → Fresh translations (best case)
CDN unavailable  → Persistent cache (MMKV/AsyncStorage)
First launch, no cache, no network → staticData from pull ✅

Web frameworks that read message files off disk at build time (next-intl, i18next, vue-i18n) use pull as the step that fetches them. No export or transform: the CDN already serves the shape those loaders expect. See Web Workflow.

Usage #

Bash
# Auto-detect project from i18n.config.ts or initBetterI18n() call
better-i18n pull

# Explicit project (no config file needed)
better-i18n pull -p acme/my-app

# Custom output directory
better-i18n pull -o ./src/locales

# Download specific locales only
better-i18n pull -l en,tr,de

Example output #

Text
✔ Project: acme/my-app
✔ Manifest: 3 languages (en, de, tr)
✔ Downloaded 3 locale(s) to locales/

  ✓ en      1867 keys  145.8 KB
  ✓ de      1742 keys  146.3 KB
  ✓ tr      1741 keys  131.0 KB

  Use as offline fallback in your app:
    staticData: { en: require('./locales/en.json'), de: require('./locales/de.json'), tr: require('./locales/tr.json') }

Options #

FlagDescriptionDefault
-p, --project <org/name>Project identifierFrom i18n.config.ts
-o, --output <path>Output directory for JSON files./locales (or config pull.output)
-l, --locales <codes>Comma-separated locale codesAll languages from manifest
-d, --dir <path>Directory to scan for configCurrent directory
--verboseShow per-locale download detailsOff

Configuration #

Add a pull section to your i18n.config.ts to set defaults:

i18n.config.ts
export const i18nConfig = {
  projectId: "acme/my-app",
  defaultLocale: "en",
  pull: { // [!code highlight]
    output: "./locales", // [!code highlight]
    locales: ["en", "tr", "de"], // [!code highlight]
  }, // [!code highlight]
};

CLI flags always override config values.

Mobile Workflow #

Expo / React Native #

After pulling, import the JSON files as staticData:

lib/i18n.ts
import en from './locales/en.json';
import tr from './locales/tr.json';

await initBetterI18n({
  projectId: 'acme/my-app',
  i18n,
  storage: storageAdapter(new MMKV()),
  staticData: { en, tr }, // [!code highlight]
});

Web Workflow #

What the CDN serves is already the shape web i18n libraries expect: top-level namespaces, nested keys underneath, no dotted keys. For next-intl that means no export and no transform step — pull straight into the folder it already reads:

Bash
npx @better-i18n/cli@latest pull -p acme/my-app -o ./messages
Text
messages/
  en.json
  es.json
messages/en.json
{
  "HomePage": { "title": "Hello world!" },
  "Nav": { "pricing": "Pricing", "docs": "Docs" }
}

That file works unchanged with useTranslations("HomePage"). The same holds for i18next (resources) and vue-i18n (messages) — point -o at whatever directory your loader reads.

CI/CD Integration #

Add pull to the build so bundled translations always match the CDN:

CI script
npx @better-i18n/cli@latest pull -p acme/my-app -o ./messages

Without the CLI #

The CDN is public and needs no token, so a plain fetch works if you would rather not add a dependency:

Bash
curl -sf https://cdn.better-i18n.com/acme/my-app/en/translations.json -o messages/en.json
curl -sf https://cdn.better-i18n.com/acme/my-app/es/translations.json -o messages/es.json

Files are served with an ETag and a 60-second cache, so conditional requests are cheap. To avoid hardcoding the locale list, read the manifest — it carries every language plus per-locale keyCount and lastModified, which also makes a good cache key:

Bash
curl -s https://cdn.better-i18n.com/acme/my-app/manifest.json

Keep the -f: without it curl writes an error page over your message file and the build carries on.

Failure Behaviour #

Since 0.6.2, pull fails a locale rather than writing an empty file when the manifest expects keys and the CDN returns none:

Text
  ✗ en     CDN returned no keys, but the manifest expects 140

The reason prints without --verbose and the process exits non-zero, so a broken fetch breaks the build instead of shipping an app with no strings. Nothing is written for a failed locale — the file already on disk stays intact.

Project Detection #

The pull command finds your project in this order:

  1. -p flag (highest priority)
  2. i18n.config.ts in the target directory
  3. Source file scan for initBetterI18n() or createI18n() calls

This means Expo projects work without a config file — the CLI finds projectId: 'acme/my-app' from your initBetterI18n() call automatically.