Skip to content

Integrations

Platform Integrations Overview

langctl writes flat JSON, nested JSON, Android XML, iOS .strings and Flutter ARB files, so it fits most stacks. Pull translations into your repo, use them in your app, and check them in CI.

Supported Platforms

Web Frameworks

  • React - i18next, react-i18next
  • Vue.js - vue-i18n
  • Angular - ngx-translate, Transloco (JSON)
  • Next.js - next-i18next
  • Svelte - svelte-i18n
  • Generic - Flat (json) or nested (nested-json) JSON

Learn more ->

Mobile (Native)

  • iOS - Localizable.strings, NSLocalizedString
  • Android - strings.xml, Resources
  • Swift / SwiftUI - NSLocalizedString, LocalizedStringKey
  • Kotlin / Java - Android resources

Learn more ->

Cross-Platform

  • Flutter - ARB files, intl package
  • React Native - i18next, react-i18next (json format)
  • Expo - expo-localization
  • Ionic - JSON with i18next or ngx-translate

Learn more ->

Integration Workflow

Set up the repository once with langctl init, then pull translations whenever they change:

terminal
langctl init    # pick project, format and path → langctl.json
langctl pull    # write one file per language

git add langctl.json src/locales
git commit -m "Update translations"

npm run build
npm run deploy

React (i18next)

typescript
npm install i18next react-i18next

langctl pull -f json -o "src/locales/{lang}.json"

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';

i18n
.use(initReactI18next)
.init({
  resources: {
    en: { translation: require('./locales/en.json') },
    es: { translation: require('./locales/es.json') }
  },
  lng: 'en',
  fallbackLng: 'en'
});

// Use in components
import { useTranslation } from 'react-i18next';

function App() {
const { t } = useTranslation();
return <h1>{t('home.welcome')}</h1>;
}

Android

Key names become valid resource names (home.welcome → home_welcome), and each language goes to its own values-xx folder:

kotlin
langctl pull -f android -o "app/src/main/res/{android}/strings.xml"
# → app/src/main/res/values/strings.xml, values-es/strings.xml, values-pt-rBR/strings.xml, …

// Kotlin
val welcome = getString(R.string.home_welcome)
binding.textView.text = getString(R.string.home_welcome, username)

// Java
String welcome = getString(R.string.home_welcome);
textView.setText(getString(R.string.home_welcome, username));

iOS

swift
langctl pull -f ios -o "Resources/{lang}.lproj/Localizable.strings"

// Swift
let welcome = NSLocalizedString("home.welcome", comment: "")
let greeting = String(format: NSLocalizedString("home.welcome", comment: ""), username)

// SwiftUI
Text(LocalizedStringKey("home.welcome"))

Flutter

ARB message ids become Dart identifiers (home.welcome → homeWelcome) and placeholders are declared for you:

dart
langctl pull -f arb    # → lib/l10n/app_en.arb, lib/l10n/app_es.arb, …

# pubspec.yaml
flutter:
generate: true

# l10n.yaml
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart

import 'package:flutter_gen/gen_l10n/app_localizations.dart';

AppLocalizations.of(context)!.homeWelcome

Parameter Conversion

langctl automatically converts parameter syntax for each platform:

PlatformInputOutput
Web (json, nested-json){{username}}{{username}}
Android (android){{username}}%1$s
iOS (ios){{username}}%1$@
Flutter (arb){{username}}{username}

A placeholder used twice gets the same position on every platform, and literal % is escaped where needed.

Note

Always write translations using {{param}} syntax in langctl. langctl pull handles platform-specific conversion automatically.

CI/CD

Check on every pull request that the committed translation files are up to date. No login step is needed — set the key as a secret environment variable:

yaml
name: translations
on: [pull_request]
jobs:
i18n:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with: { node-version: 22 }
    - run: npx langctl@0 pull --check            # fail if committed files are out of date
      env:
        LANGCTL_API_KEY: ${{ secrets.LANGCTL_API_KEY }}

Learn more about CI/CD ->

Best Practices

1. Commit Translation Files

Commit pulled translation files (and langctl.json) to git:

  • Track changes over time
  • Enable code review for translations
  • Rollback capability
  • Consistent across environments

Output is sorted and has no timestamps, so a pull only changes files when translations actually change.

2. Pull in Build Scripts

Use the CLI in build scripts:

json
{
"scripts": {
  "build": "vite build",
  "prebuild": "npm run i18n:pull",
  "i18n:pull": "langctl pull"
}
}

3. Ship Only Published Keys

pull and export include only published keys by default — drafts stay out of production until someone publishes them. To preview drafts locally:

terminal
langctl pull --include-drafts

4. Split by Module

Export specific modules for code-splitting:

terminal
langctl export my-app -l en --module auth -o ./auth-en.json
langctl export my-app -l en --module dashboard -o ./dashboard-en.json

5. Ship Translation Files with Your App

langctl writes files into your repository; your app loads them like any other asset:

  • Bundle translations with the app
  • Lazy-load language files your users don’t need up front
  • Rebuild and deploy to ship translation changes

Platform-Specific Guides

Choose your platform for detailed integration instructions:

Need Help?