Skip to main content

Build-time JSON Export

Export translations to static JSON files for build-time bundling

When to Use This Approach

This pattern works well when you want LocaleLens as your source of truth, but prefer bundling translations as static JSON files rather than fetching at runtime.

  • Static sites or SSG builds
  • Apps with infrequent translation changes
  • Teams preferring explicit deploy cycles for copy changes
  • Early-stage apps that don't need OTA updates yet
Example Repository

A minimal working example using React, Vite, and i18next:

→ View the example on GitHub

Includes a fetch script, i18next configuration, and sample translations.

How It Works
LocaleLens (source of truth)
fetch script (API call at build time)
local JSON files (locales/*.json)
bundled with your app

No runtime API calls, server-side proxy, or caching logic required. JSON files are generated artifacts and should not be edited manually.

The Fetch Script

A simple Node.js script fetches all translations from LocaleLens and writes them to JSON files:

// scripts/fetch-translations.ts
import 'dotenv/config'
import { writeFileSync, mkdirSync } from 'node:fs'

const API_BASE = process.env.LOCALELENS_API_BASE || 'https://localelens.ai/api/v1'
const PROJECT_ID = process.env.LOCALELENS_PROJECT_ID
const API_KEY = process.env.LOCALELENS_API_KEY

async function main() {
  // Fetch available locales
  const localesRes = await fetch(
    `${API_BASE}/projects/${PROJECT_ID}/locales`,
    { headers: { Authorization: `Bearer ${API_KEY}` } }
  )
  const { locales } = await localesRes.json()

  mkdirSync('locales', { recursive: true })

  // Fetch and write translations for each locale
  for (const { code } of locales) {
    const res = await fetch(
      `${API_BASE}/projects/${PROJECT_ID}/translations/${code}`,
      { headers: { Authorization: `Bearer ${API_KEY}` } }
    )
    const translations = await res.json()
    writeFileSync(`locales/${code}.json`, JSON.stringify(translations, null, 2))
    console.log(`  ${code}.json - ${Object.keys(translations).length} keys`)
  }
}

main()

Run with npx tsx scripts/fetch-translations.ts or add to your build pipeline.

i18next Configuration

Import the JSON files directly — they're bundled at build time:

// i18n.ts
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'

// Import locale JSON files directly (bundled at build time)
import en from '../locales/en.json'
import de from '../locales/de.json'
import fr from '../locales/fr.json'

i18n.use(initReactI18next).init({
  resources: {
    en: { translation: en },
    de: { translation: de },
    fr: { translation: fr },
  },
  lng: 'en',
  fallbackLng: 'en',
  interpolation: { escapeValue: false },
})

export { i18n }
Tradeoffs

Be aware: Translation updates require a rebuild and redeploy. There are no over-the-air updates with this approach.

AspectBehavior
UpdatesRequire rebuild and redeploy
OTA updatesNot supported
Runtime locale switchingSupported (between exported locales only)
New localesRequire rebuild
API key exposureNone (build-time only)
When to Consider Runtime APIs

If you need over-the-air translation updates, environment-specific translations, or frequently changing copy, consider using the runtime API instead:

Security Notes
  • The fetch script runs at build time only — API keys are never exposed to clients
  • Store API keys in CI secrets or local .env.local files
  • The fetch script is safe to run in CI pipelines with read-only API access