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
Mobile (Native)
- iOS - Localizable.strings, NSLocalizedString
- Android - strings.xml, Resources
- Swift / SwiftUI - NSLocalizedString, LocalizedStringKey
- Kotlin / Java - Android resources
Cross-Platform
- Flutter - ARB files, intl package
- React Native - i18next, react-i18next (
jsonformat) - Expo - expo-localization
- Ionic - JSON with i18next or ngx-translate
Integration Workflow
Set up the repository once with langctl init, then pull translations whenever they change:
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)
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:
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
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:
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:
| Platform | Input | Output |
|---|---|---|
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:
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 }} 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:
{
"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:
langctl pull --include-drafts 4. Split by Module
Export specific modules for code-splitting:
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:
- Web Frameworks - React, Vue, Angular, Next.js
- iOS & Android - Native mobile development
- Cross-Platform - Flutter, React Native
- CI/CD - Automated workflows
- Version Control - Git integration
Need Help?
- File Formats - Formats, default paths and placeholders
- CLI Export & Import - Command-line pull, push, export and import
- CLI Troubleshooting - Common problems and fixes