TransLocale Flutter runtime

Deliver approved translation updates to your Flutter app without rebuilding it. Keep using AppLocalizations.of(context) with typed placeholders, plurals, and bundled translations for offline fallback.

Setup guide · API reference · Changelog

  • Show bundled translations on the first frame, then refresh wording when a compatible release arrives.
  • Keep validated translations across restarts with native storage or browser caching.
  • Let the generated wrapper handle updates, app lifecycle, and cleanup.

Quick start · Configuration · Troubleshooting

Choose a package

Package Use it for
translocale_flutter_runtime Flutter app integration, app-version detection, and persistent caching
translocale_flutter Development commands and typed adapter generation; install as a dev dependency
translocale_delivery Lower-level delivery when you supply version, cache, and lifecycle behavior

OTA means over-the-air wording updates. It does not install Dart code or add new localization methods to an existing app build. Keep bundled translations in every app release.

Platform support

Android iOS macOS Windows Linux Web
Native cache Native cache Native cache Native cache Native cache Browser cache

Requires Dart 3.12+, Flutter 3.38.1+, and the platform requirements of package_info_plus and path_provider. Current mobile minimums are Android SDK 24+ and iOS 13+; macOS requires 10.15+ and Windows requires Windows 10+. The setup tooling runs on macOS or Linux. See platform setup before shipping.

Quick start

Before you start

Use Dart 3.12 or later, Flutter with gen-l10n configured, and Node.js 22.16 or later for the setup CLI. Run commands from the Flutter app directory. The examples assume AppLocalizations is generated in lib/l10n/app_localizations.dart; adjust imports and the generated wrapper name if your l10n.yaml uses different paths or class names.

You also need a TransLocale project with matching languages and catalog identity. app.arb below is the catalog's name in TransLocale, not necessarily the local source filename. Each target language needs a bundled ARB file.

Live updates require an approved release published to your channel and a read-only delivery credential. Installation and adapter generation run locally and do not start paid translation jobs.

Starting without ARB files? Use this minimal Flutter setup.

Add Flutter's localization dependency:

flutter pub add flutter_localizations --sdk=flutter

Enable generation in your existing pubspec.yaml:

flutter:
  generate: true

Create l10n.yaml in the app root:

arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
output-class: AppLocalizations

Create lib/l10n/app_en.arb:

{
  "@@locale": "en",
  "welcome": "Hello, {name}!",
  "@welcome": {
    "description": "Greeting on the home screen",
    "placeholders": { "name": { "type": "String" } }
  }
}

Create lib/l10n/app_fr.arb:

{ "@@locale": "fr", "welcome": "Bonjour, {name} !" }

Create lib/l10n/app_ar.arb:

{ "@@locale": "ar", "welcome": "مرحبًا، {name}!" }

The steps below generate the Dart classes. Use the same source, catalog identity, and languages in your TransLocale project. For a larger app, follow Flutter's localization guide.

1. Install

flutter pub add translocale_flutter_runtime:^0.1.2 translocale_delivery:^0.1.2
flutter pub add --dev translocale_flutter:^0.2.2
npm install --save-dev @translocale/cli@^0.5.0

Declare translocale_delivery directly because the generated adapter imports it. The CLI is a development tool and is not included in your Flutter app.

2. Generate the adapter

npx translocale flutter-init --catalog app.arb --targets fr,ar
npx translocale flutter-setup --file release-schema.json --catalog app.arb

Replace fr,ar with the target languages in your project. Initialization writes release-schema.json, a list of source files and languages. Setup runs flutter gen-l10n and writes translocale_localizations.dart beside Flutter's generated base class. It includes translocaleSchemaHash, which identifies the catalog shape this app can use.

Both commands run locally and preserve an existing TransLocale manifest or adapter. After changing source inputs, remove only the previous generated TransLocale adapter, run setup again, and review the result. For strict escaping, keep your original service ARB separately and pass it with flutter-init --source; see the Flutter setup guide.

3. Connect a release

In the dashboard, create a release snapshot from approved translations that match this app's catalog schema. Publish it to preview for testing. Sign in as the project owner and create a delivery credential:

npx translocale login --api https://translocale.io --project PROJECT_UUID --scope write
npx translocale flutter-connect --api https://translocale.io --project PROJECT_UUID \
  --channel preview --name "Flutter preview" --file release-schema.json \
  --out translocale.delivery.json

Replace PROJECT_UUID with the cloud project's ID. The command creates a read-only tld_ credential for this project, schema and channel, then writes DELIVERY_PROJECT, DELIVERY_CHANNEL, DELIVERY_TOKEN, and DELIVERY_ORIGIN into the settings file. It does not publish wording or start translation.

Add these patterns to .gitignore before connecting:

translocale.delivery*.json
.translocale-delivery-*.tmp

The read-only token is included in the app build and can be extracted from it. It grants delivery access only. Authoring credentials must stay in development tooling or CI.

4. Wire the app

Use this in lib/main.dart with the starter ARB files above. For an existing app, keep your routes and theme, adjust the generated imports, and replace welcome with one of your localization methods.

import 'package:flutter/material.dart';
import 'package:translocale_flutter_runtime/translocale_flutter_runtime.dart';
import 'l10n/app_localizations.dart';
import 'l10n/translocale_localizations.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const TranslationApp());
}

class TranslationApp extends StatefulWidget {
  const TranslationApp({super.key});

  @override
  State<TranslationApp> createState() => _TranslationAppState();
}

class _TranslationAppState extends State<TranslationApp> {
  late final delivery = FlutterDelivery(
    projectId: const String.fromEnvironment('DELIVERY_PROJECT'),
    schemaHash: translocaleSchemaHash,
    token: const String.fromEnvironment('DELIVERY_TOKEN'),
    channel: const String.fromEnvironment('DELIVERY_CHANNEL'),
    apiBaseUrl: const String.fromEnvironment('DELIVERY_ORIGIN'),
  );

  @override
  Widget build(BuildContext context) {
    return TransLocaleAppLocalizationsBuilder(
      delivery: delivery,
      builder: (context, delegates) => MaterialApp(
        localizationsDelegates: delegates,
        supportedLocales: AppLocalizations.supportedLocales,
        home: const HomePage(),
      ),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;
    return Scaffold(
      body: Center(child: Text(l10n.welcome('Sam'))),
    );
  }
}

Create one runtime per wrapper, outside build. The generated wrapper starts it after the first frame, listens for changes, and disposes it when removed or replaced. Do not dispose the same runtime a second time or share it between wrappers.

Delivered wording uses the same generated methods as bundled translations. If a message cannot be used, the adapter falls back to its bundled implementation.

5. Run and verify

flutter run --dart-define-from-file=translocale.delivery.json

The example follows the device language. To try French, temporarily add locale: const Locale('fr') to MaterialApp; use Locale('ar') to check Arabic and right-to-left layout.

  1. Confirm the bundled greeting appears before delivery finishes.
  2. Approve a wording change for the same welcome key and publish a compatible release to preview.
  3. Resume the app or call await delivery.check(). The greeting should update without an app rebuild.
  4. Restart offline to check the saved translation on each platform you ship.

Inspect delivery.state.source, delivery.state.status, and delivery.state.error when diagnosing delivery. A failed update retains the last valid wording.

For production, publish a compatible release to the production channel and run flutter-connect with --channel production and a new output filename. Build with that file using the same --dart-define-from-file flag. A preview token cannot fetch the production channel.

Example app

The journal demo shows delivered French wording and Arabic right-to-left layout. These captures use a local delivery service.

iOS journal app showing updated French wording and two existing entries The same iOS journal app showing delivered Arabic wording in a right-to-left layout

View the Flutter web screenshot.

Configuration reference

Setting Default or requirement
projectId Required cloud project UUID
schemaHash Required generated translocaleSchemaHash
token Required scoped tld_ delivery credential
channel production; must match the credential
apiBaseUrl https://translocale.io
appVersion Detected from the installed app; override with a three-part version such as 1.2.3
appVersionProvider Optional custom asynchronous version lookup; an explicit appVersion wins
persistentCache true; an explicit cache takes precedence
cache Optional DeliveryCache implementation
pollInterval Five minutes; allowed range is 30 seconds to one hour
Wrapper automaticUpdates true; set to false and call delivery.check() for manual refresh

Caching, refresh, and fallback

  • Startup begins after the wrapper's first frame. Version lookup, cache reads and network requests do not delay bundled translations.
  • The installed app version comes from package_info_plus. Detection has a three-second deadline. Supply appVersion: '1.2.3' to override it; appVersionProvider supports tests and custom version sources. Versions must have three numeric components. Build numbers are not part of the release compatibility check.
  • Native platforms use private application-support storage through path_provider. Cache entries are validated, written atomically, and contain no credentials. Storage failure permits network delivery; offline fallback uses valid cached or bundled wording.
  • Flutter web uses browser localStorage, scoped by service origin, project, channel and schema. It validates cached releases before use and stores no credentials. Blocked or full storage keeps delivery working with network or bundled wording; a failed write preserves the previous entry.
  • Pass cache to use another DeliveryCache, or persistentCache: false to disable the default cache. An explicit cache takes precedence.
  • The wrapper pauses polling while inactive and refreshes on resume. Set automaticUpdates: false on the wrapper for manual-only checks through delivery.check().
  • Setup errors use state.error values app_version or configuration, retain bundled wording and permit a manual retry. Disposal cancels waiting for version detection; a late result cannot start delivery.

FlutterDelivery and the lower-level DeliveryClient implement DeliveryRuntime, so either works with the generated adapter. The underlying client retains its integrity checks, cache validation, server cooldowns, version bounds and compatible rollback behavior. See delivery behavior and formatting limits.

For offline web reloads, the app itself must be available offline and its version must resolve. package_info_plus reads version.json on web; make that file available offline or provide the current build's appVersion. Translation caching does not cache app assets or version metadata. Browser settings can clear stored translations; bundled wording remains the fallback.

Platform setup

For Android release builds, grant Internet access in android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />

Production delivery uses HTTPS. Network entitlements, plugin setup and deployment targets remain the app's responsibility. The plugin dependencies currently require Flutter 3.38.1 or later, Dart 3.12 for this package, iOS 13+, macOS 10.15+, and Android SDK 24+. Check their linked requirements for Android build-tool versions.

Updating the integration and CI

Run these after changing localizations:

npx translocale flutter-setup --file release-schema.json --catalog app.arb --check
flutter analyze
flutter test

--check validates the existing adapter without generating files or making cloud requests. If it reports stale inputs, regenerate locally and review the changes before committing. Schema changes require a matching release and delivery credential for the new app build; keep older releases available for older builds.

Troubleshooting

Symptom What to check
Only bundled wording appears Verify build settings, enabled delivery, published channel, exact locale/catalog name, schema and app-version range.
state.error is configuration Check all four build settings and use a scoped delivery token.
state.error is app_version Supply a valid appVersion or make platform version metadata available.
A new key does not appear New generated methods require a new app build. OTA updates compatible wording.
Offline web reload fails Cache the app assets and version metadata too; translation caching covers only translation data.
Setup refuses to overwrite Inspect the existing manifest; remove only an adapter you intend to regenerate.
Credential creation was interrupted Retry the same flutter-connect output path. If the one-time token was lost, revoke the reported credential ID and connect with a new output filename.
Updates stop after revocation or expiry Distribute a replacement credential. Previously cached wording can remain usable.

Cache validation rejects corrupt or incompatible updates and retains valid wording. Revoking access stops future fetches; it does not erase wording already downloaded to a device.

License

MIT. The hosted service remains proprietary.

Libraries

translocale_flutter_runtime
Flutter defaults for read-only translation delivery.