Skip to content

Native iOS & Android

TransLocale for DartNative.

Add offline ARB translations to your DartNative app, switch languages, and deliver approved wording updates without a new app release.

This guide uses the typed API in translocale_dartnative 0.2.0.

Watch an OTA update

See a French greeting change in a running app, then stay available after restarting with the server stopped. This 45-second demo uses an iOS simulator and a local delivery server.

Captions are included in the video. Read the transcript.

Before you start

You need an existing DartNative app and the DartNative SDK with Dart 3.12.0-192.0.dev or later. This package supports iOS and Android; Android requires API 26 or later. Follow the SDK's platform requirements. With Xcode 27, set the iOS deployment target to at least 15 for the app and its pods.

You can use bundled translations without a TransLocale account. An account is only needed for the hosted service, including OTA updates.

Get started

1. Install the package

Add these entries under dependencies in your app's pubspec.yaml. Keep your existing DartNative dependencies.

pubspec.yaml
dependencies:
  dartnative_system: ^1.0.0
  dartnative_path_provider: ^1.0.0
  translocale_dartnative:
    hosted: https://dartpub.dev
    version: ^0.2.0

Include all three packages. The two native packages let TransLocale read the app version and save downloaded translations.

From your app directory, run:

Terminal
dn pub get

Use dn pub get so DartNative can resolve its native packages.

2. Add your translation files

Create a l10n folder in your app. Add one ARB file for each language. ARB files are JSON files that hold your translation keys and messages.

Create l10n/app_en.arb:

l10n/app_en.arb
{
  "@@locale": "en",
  "hello": "Hello, {name}!",
  "@hello": {
    "placeholders": {
      "name": {
        "type": "String"
      }
    }
  },
  "items": "{count, plural, one {One entry} other {{count} entries}}",
  "@items": {
    "placeholders": {
      "count": {
        "type": "int"
      }
    }
  }
}

Create l10n/app_fr.arb:

l10n/app_fr.arb
{
  "@@locale": "fr",
  "hello": "Bonjour, {name} !",
  "items": "{count, plural, one {Une entrée} other {{count} entrées}}"
}

Here, hello and items are the keys your app uses. {name} and {count} are values supplied by your app. The @hello and @items entries define the generated parameter types.

3. Generate the Dart file

Create tool/bundle.dart:

tool/bundle.dart
import 'package:translocale_dartnative/bundle.dart';

void main(List<String> arguments) => runBundleCommand(arguments);

Run this from your app directory, using the Dart executable supplied by DartNative:

Terminal
dart --packages=.dart_tool/package_config.json tool/bundle.dart \
  --arb-dir l10n --source en --catalog app.arb

This creates lib/translocale_bundle.g.dart. Commit it along with your ARB files. app.arb is the name that identifies this catalog in TransLocale; use the same name when setting up OTA updates.

The command only reads local files. It does not upload your messages or start a translation job.

4. Show translations in your app

Use this in lib/main.dart:

lib/main.dart
import 'package:dartnative/dartnative.dart';
import 'package:translocale_dartnative/translocale_dartnative.dart';
import 'dartnative_plugin_registrant.dart';
import 'translocale_bundle.g.dart';

void main() {
  DartNativePluginRegistrant.registerAll();
  final translations = TransLocale(catalog: translocaleCatalog, locale: 'en');
  runApp(
    App(
      title: 'My app',
      home: TransLocaleBuilder(
        translations: translations,
        builder: (context, strings) => Scaffold(
          body: SafeArea(
            child: Column(
              children: [
                Text(strings.hello(name: 'Sam')),
                Text(strings.items(count: 3)),
                Button(
                  onPressed: () => translations.setLocale('fr'),
                  child: const Text('Français'),
                ),
              ],
            ),
          ),
        ),
      ),
    ),
  );
}

dn pub get generates the plugin registration file. Keep the call to registerAll() before runApp.

Create TransLocale once, outside build. Put TransLocaleBuilder inside App, above the screens that need translations. The builder disposes of the controller when it is removed, so do not share one controller between builders.

5. Run the app

Terminal
dn run

You should see Hello, Sam! and 3 entries. Tap Français to see Bonjour, Sam ! and 3 entrées.

After changing an ARB file, run the bundle command again with --replace:

Terminal
dart --packages=.dart_tool/package_config.json tool/bundle.dart \
  --arb-dir l10n --source en --catalog app.arb --replace

Use --check instead of --replace in CI to check that the generated file is up to date. Do not format the generated file.

Set up OTA updates (optional)

OTA means over-the-air updates: your app downloads approved translations from TransLocale. It shows bundled messages immediately, then applies compatible updates. Downloaded messages are saved for offline use and later app launches.

1. Create a TransLocale project

Open TransLocale and create a project. Use English as the source language and French as the target language for this example. Add the same source ARB content with the catalog name app.arb.

The source keys, placeholder types, catalog name, and target languages must match the app.

2. Get your app's schema hash

The schema hash identifies the translation structure your app understands. It prevents the app from loading an incompatible release.

With Node.js 22.16 or later installed, run:

Terminal
npm install --save-dev @translocale/cli@^0.6.1

Create release-schema.json in your app directory:

release-schema.json
{
  "sourceLocale": "en",
  "locales": [
    "fr"
  ],
  "catalogs": [
    {
      "file": "app.arb",
      "format": "arb",
      "path": "l10n/app_en.arb"
    }
  ]
}

Then run:

Terminal
npx translocale release-schema --file release-schema.json

Copy the returned schemaHash. This command runs locally and does not start a translation job. Run it again when you change source keys, placeholders, or supported languages.

3. Publish a preview release

In your project's dashboard, review and approve the translations, then publish a release to the preview channel. In the project's delivery settings, create a read-only delivery credential for the same schema and channel.

Keep the project ID, schema hash, and delivery token for the next step. The delivery token starts with tld_.

4. Connect your app

First, add translocale.delivery*.json to your app's .gitignore. Then create translocale.delivery.json with your own values:

translocale.delivery.json
{
  "DELIVERY_PROJECT": "your-project-id",
  "DELIVERY_SCHEMA": "the-schemaHash-from-step-2",
  "DELIVERY_TOKEN": "your-tld-delivery-token"
}

In lib/main.dart, replace the TransLocale creation from the quick start with:

lib/main.dart · OTA configuration
final translations = TransLocale(
  catalog: translocaleCatalog,
  locale: 'fr',
  delivery: DartNativeDelivery(
    projectId: const String.fromEnvironment('DELIVERY_PROJECT'),
    schemaHash: const String.fromEnvironment('DELIVERY_SCHEMA'),
    token: const String.fromEnvironment('DELIVERY_TOKEN'),
    channel: 'preview',
  ),
);

The delivery token is included in the built app and can be extracted. Only use a read-only delivery token here. Keep authoring credentials in development tools, CI, or the dashboard.

5. Try an update

Run the app with your delivery settings:

Terminal
dn run --dart-define-from-file=translocale.delivery.json

Publish a different French message to the same preview channel. The app checks after the first frame and then every five minutes while it is in the foreground. You can call await t.check() to refresh sooner. If an update does not appear, inspect t.state.status and t.state.error.

For production, publish to the production channel, create a delivery credential for that channel, and change both the token and channel in your app configuration.

Installation, bundle generation, schema checks, and delivery requests do not start paid translation jobs.

Use with Codex or Claude Code

Our AI skills guide explains how to install the TransLocale skill and connect your assistant to a project through MCP.

Tell your assistant that your app uses DartNative and share this page. Follow the AI skills guide to install the skill and connect to your project, then use the setup steps above for your app. Translation jobs still need your approval and a character cap.

Supported messages and limits

Version 0.1.0 supports one catalog per controller, up to 200 source messages, and ten target languages. Each ARB file needs @@locale and must be at most 100 KB.

Messages can use placeholders, select, and cardinal plurals with zero, one, two, few, many, and other branches. The formatter also accepts =0, =1, and =2; prefer category names for portability.

Ordinals, plural offsets, other exact-number branches, rich text, and inline date or number formatting are not supported. For dates and numbers, supply raw values in arguments and display strings in formatted. ARB metadata is used for schema checks, not bundled as display text or used to generate formatting code.

This package uses DartNative widgets and key-based lookups. It does not use Flutter's gen-l10n, localization delegates, or generated typed getters.