Automating Flutter Localization in CI/CD Pipelines
Manual translation management doesn't scale. When you're shipping features weekly, waiting days for translations creates bottlenecks. This guide shows you how to integrate Flutter localization into your CI/CD pipeline for seamless, automated deployments.
The CI/CD Localization Challenge
Your team pushes code multiple times daily, but translations lag behind:
- ❌ Developers add new keys manually
- ❌ Translators work in separate spreadsheets
- ❌ ARB files get out of sync
- ❌ Builds fail due to missing translations
- ❌ Releases get blocked waiting for translations
Solution: Automated Localization Pipeline
Here's how to build a robust localization CI/CD workflow:
1. Detect Missing Translation Keys
Create a script to validate translations before builds:
#!/bin/bash
# validate-translations.sh
set -e
echo "🔍 Validating translation files..."
# Get template keys
template_keys=$(jq -r 'keys[]' assets/l10n/app_en.arb | grep -v "^@")
# Check each locale
for file in assets/l10n/app_*.arb; do
locale=$(basename "$file" .arb | sed 's/app_//')
echo "Checking $locale..."
# Get locale keys
locale_keys=$(jq -r 'keys[]' "$file" | grep -v "^@")
# Find missing keys
missing=$(comm -23 <(echo "$template_keys" | sort) <(echo "$locale_keys" | sort))
if [ -n "$missing" ]; then
echo "❌ Missing keys in $locale:"
echo "$missing"
exit 1
fi
done
echo "✅ All translations are complete!"
2. GitHub Actions Workflow
.github/workflows/localization.yml:
name: Localization CI
on:
pull_request:
paths:
- 'assets/l10n/**'
push:
branches: [main, develop]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.16.0'
- name: Install dependencies
run: flutter pub get
- name: Validate translations
run: ./scripts/validate-translations.sh
- name: Generate localizations
run: flutter gen-l10n
- name: Check for uncommitted changes
run: |
if [[ -n $(git status -s) ]]; then
echo "❌ Generated files are not committed"
git diff
exit 1
fi
- name: Run tests with all locales
run: flutter test test/localization_test.dart
auto-translate:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
needs: validate
steps:
- uses: actions/checkout@v3
- name: Auto-translate missing keys
env:
FLUTTER_LOCALISATION_API_KEY: ${{ secrets.FLUTTER_LOCALISATION_API_KEY }}
run: |
# Use FlutterLocalisation API to translate
curl -X POST https://api.flutterlocalisation.com/translate \
-H "Authorization: Bearer $FLUTTER_LOCALISATION_API_KEY" \
-H "Content-Type: application/json" \
-d '{"project_id": "${{ secrets.PROJECT_ID }}"}'
- name: Commit translations
uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: "🌍 Auto-translate missing keys"
file_pattern: assets/l10n/*.arb
3. GitLab CI Pipeline
.gitlab-ci.yml:
stages:
- validate
- translate
- build
validate_translations:
stage: validate
image: cirrusci/flutter:stable
script:
- flutter pub get
- chmod +x scripts/validate-translations.sh
- ./scripts/validate-translations.sh
- flutter gen-l10n
only:
changes:
- assets/l10n/**
auto_translate:
stage: translate
image: curlimages/curl:latest
script:
- |
curl -X POST $FLUTTER_LOCALISATION_API_URL \
-H "Authorization: Bearer $FLUTTER_LOCALISATION_API_KEY" \
-d "{\"project_id\": \"$PROJECT_ID\"}"
only:
- merge_requests
build_app:
stage: build
image: cirrusci/flutter:stable
script:
- flutter pub get
- flutter gen-l10n
- flutter build apk --release
artifacts:
paths:
- build/app/outputs/flutter-apk/
4. Pre-commit Hooks
Catch translation issues before they reach CI:
.git/hooks/pre-commit:
#!/bin/bash
# Check if .arb files were modified
arb_files=$(git diff --cached --name-only --diff-filter=AM | grep "\.arb$")
if [ -n "$arb_files" ]; then
echo "🔍 Validating translation files..."
# Run validation
./scripts/validate-translations.sh
if [ $? -ne 0 ]; then
echo "❌ Translation validation failed"
echo "Fix the issues or use 'git commit --no-verify' to skip"
exit 1
fi
# Regenerate localizations
flutter gen-l10n
# Stage generated files
git add .dart_tool/flutter_gen/gen_l10n/*.dart
echo "✅ Translations validated and generated"
fi
Automated Translation Updates
Using FlutterLocalisation CLI
# Install CLI
dart pub global activate flutter_localisation
# Sync translations in CI/CD
flutter_localisation sync \
--project-key $PROJECT_KEY \
--pull # Pull latest translations
# Or push new keys
flutter_localisation sync \
--project-key $PROJECT_KEY \
--push # Push new keys for translation
GitHub Action Example
- name: Sync with FlutterLocalisation
run: |
dart pub global activate flutter_localisation
flutter_localisation sync \
--project-key ${{ secrets.PROJECT_KEY }} \
--pull
Translation Memory & Reuse
Avoid retranslating the same strings:
#!/bin/bash
# check-translation-memory.sh
# Check if new keys exist in other files
new_key="$1"
new_value="$2"
echo "🔍 Checking translation memory for: $new_value"
# Search all .arb files
for file in assets/l10n/app_*.arb; do
matches=$(jq -r "to_entries[] | select(.value == \"$new_value\") | .key" "$file")
if [ -n "$matches" ]; then
echo "✨ Found existing translations:"
echo "$matches"
fi
done
Monitoring Translation Coverage
Add metrics to track coverage:
- name: Report translation coverage
run: |
total_keys=$(jq 'keys | length' assets/l10n/app_en.arb)
for file in assets/l10n/app_*.arb; do
locale=$(basename "$file" .arb | sed 's/app_//')
translated=$(jq 'keys | length' "$file")
coverage=$(echo "scale=2; $translated / $total_keys * 100" | bc)
echo "$locale: $coverage% ($translated/$total_keys)"
done
Rollback Strategy
Handle translation issues in production:
- name: Create translation backup
run: |
cp -r assets/l10n/ backups/l10n-$(date +%Y%m%d-%H%M%S)/
- name: Rollback on failure
if: failure()
run: |
latest_backup=$(ls -td backups/l10n-* | head -1)
cp -r "$latest_backup"/* assets/l10n/
Continuous Deployment with Translations
deploy:
stage: deploy
script:
# Ensure translations are up to date
- flutter_localisation sync --pull
# Generate final localizations
- flutter gen-l10n
# Build for production
- flutter build appbundle --release
# Deploy
- fastlane deploy
only:
- main
Best Practices
- Block merges with incomplete translations
- Auto-translate in feature branches, review before merge
- Version control all .arb files
- Generate localizations in CI, not locally
- Test all locales in your test suite
- Monitor coverage with metrics
- Use translation memory to avoid duplicates
- Create backups before deployments
Testing Localization in CI
// test/localization_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
void main() {
testWidgets('All locales load successfully', (tester) async {
final supportedLocales = AppLocalizations.supportedLocales;
for (final locale in supportedLocales) {
await tester.pumpWidget(
MaterialApp(
locale: locale,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: supportedLocales,
home: Container(),
),
);
final l10n = AppLocalizations.of(tester.element(find.byType(Container)))!;
// Test key strings load
expect(l10n.appTitle, isNotEmpty);
expect(l10n.welcomeMessage, isNotEmpty);
}
});
}
Conclusion
Automating localization in CI/CD eliminates bottlenecks and ensures translations stay in sync with code. The initial setup investment pays off in faster deployments and fewer translation-related bugs.
Stop blocking releases on manual translation updates. Integrate FlutterLocalisation into your CI/CD pipeline and ship localized features as fast as you write code.
Ready to automate? FlutterLocalisation provides CLI tools, GitHub Actions, and API access for seamless CI/CD integration. Pull translations automatically on every build.