translocale_delivery 0.1.2
translocale_delivery: ^0.1.2 copied to clipboard
Read-only approved translation delivery with offline fallback for Dart and Flutter apps.
TransLocale delivery for Dart and Flutter #
Fetch approved translations for Dart and Flutter apps with a read-only client. Supply your app version, fallback messages, and storage; the client validates releases and retains the last valid wording when an update fails.
Delivery guide · API reference · Changelog
Building a Flutter app? Start with translocale_flutter_runtime. It adds automatic app-version detection and platform caching, with a complete gen-l10n setup example.
Quick start · Flutter adapter · Configuration · Troubleshooting
This package is separate from translocale_flutter, which provides build-time tooling. It does not restore the legacy 0.0.1 OTA API or accept old credentials.
Platform support #
Requires Dart 3.12+. The main translocale_delivery.dart library supports native Dart, Flutter, and web clients. translocale_delivery_io.dart provides a native file cache and cannot be imported on web. Browser clients can supply a custom cache or use the Flutter runtime's browser cache.
Quick start #
1. Install #
dart pub add translocale_delivery:^0.1.2
Delivery is available on TransLocale. Fetching requires a compatible release published to your channel and a delivery credential for that channel.
2. Prepare a release #
Install @translocale/cli 0.5.0 or later as a development dependency, then generate the app's catalog schema from a source manifest with npx translocale release-schema --file release-schema.json --out schema.json. For Flutter, prefer the runtime guide's flutter-init and flutter-setup workflow. An owner then publishes an approved release and creates a read-only credential for that project, channel, and schema. See release management.
3. Create and start the client #
import 'package:translocale_delivery/translocale_delivery.dart';
final delivery = DeliveryClient(
projectId: const String.fromEnvironment('DELIVERY_PROJECT'),
channel: 'production',
schemaHash: const String.fromEnvironment('DELIVERY_SCHEMA'),
appVersion: '1.0.0',
token: const String.fromEnvironment('DELIVERY_TOKEN'),
);
final removeListener = delivery.addListener(() {
// Notify your UI or state container to read the updated messages.
print('Delivery: ${delivery.state.status}, source: ${delivery.state.source}');
});
delivery.start(); // Do not wait for delivery before rendering built-in wording.
final greeting = delivery.resolve('app.arb', 'fr', 'hello', 'Hello!');
print(greeting); // Replace with your UI update.
// Read again inside your listener/UI rebuild to pick up delivered wording.
// Keep this client alive for as long as its translations are used.
// On teardown, call removeListener(), then delivery.dispose().
The example is initialization code to place in your app's lifecycle, not a standalone Dart script. The hello key is a plain-text message; parameterized messages need a formatter. Set DELIVERY_PROJECT to the project UUID, DELIVERY_SCHEMA to the computed schema hash, and DELIVERY_TOKEN to the scoped delivery credential. For Flutter's generated adapter, use translocaleSchemaHash directly instead of a separate schema build setting. appVersion must be the installed app's three-part version; replace 1.0.0 for your build.
resolve uses exact file, locale, and key identities. getCatalog returns an immutable raw message map. Both return unformatted messages. Use the generated adapter below to retain Flutter's typed localization methods, parameter formatting, and built-in wording.
Use Flutter's generated API #
Run flutter gen-l10n, then use the Flutter tooling to generate an adapter:
dart run translocale_flutter:translocale ota-adapter --catalog app.arb
The command reads paths and class names from l10n.yaml and the source language from the template ARB. It writes translocale_localizations.dart beside the generated base. --catalog is its catalog identity in TransLocale. Regional sources select the correct generated subclass and inherit missing implementations. Explicit --base, --reference, --reference-class, --out, and --class options remain available. Generation is offline, preserves positional/named signatures and number/date formatting, and refuses to overwrite an existing file. Schema and delivery configuration remain separate for this lower-level command.
Add --check to verify an existing adapter in CI. It checks source fingerprints and generated behavior while allowing dart format changes. Regenerate after changing the generated inputs; remove the previous generated adapter before writing its replacement.
Wrap the app with the generated builder:
TransLocaleAppLocalizationsBuilder(
delivery: delivery,
builder: (context, delegates) => MaterialApp(
localizationsDelegates: delegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const HomePage(),
),
)
The builder starts delivery after the first frame, pauses automatic checks while inactive, and refreshes the delegates when delivery changes. It owns the supplied client and disposes it when removed or replaced; give each client one owner. Passing null uses the generated bundled translations. Set automaticUpdates: false for manual-only updates through delivery.check().
Existing calls such as AppLocalizations.of(context).welcome(name) keep their generated types and fallback behavior. Keep your app's existing locale-data initialization for date formatting. Advanced integrations can use TransLocaleAppLocalizationsDelegate(delivery) directly and manage listeners, lifecycle and disposal themselves.
The adapter selects the exact language of Flutter's loaded fallback implementation. If that release lacks the language or key, it uses the generated method. Unsupported or malformed delivered messages also use generated wording.
Message formatting #
FlutterMessageFormatter supports plain text, arguments, ICU apostrophe escaping, selects, nested branches, and cardinal plurals using the same Intl.pluralLogic behavior as gen-l10n. It accepts =0, =1, and =2 as Flutter's explicit cases. Date and number display strings come from the generated adapter, while selectors receive the original values.
Plural offsets, selectordinal, plural #, inline number/date formats, rich-text tags, overlapping exact/category selectors, and malformed syntax fall back to generated wording. This matches the supported dialect of translocale flutter-export; use explicit count placeholders and ARB format metadata. Parsing is limited to 100,000 characters and 32 nested branches, with a bounded cache of parsed messages. Custom renderers can still use the raw delivery API.
Use setActive(false) when the app is backgrounded and setActive(true) on resume. This pauses automatic polling. check() performs a manual refresh, sharing an in-flight request and respecting server cooldowns.
Only tld_ delivery credentials are accepted. App credentials are extractable and grant no editing or publishing access. Never include authoring credentials in a consumer. Production origins require HTTPS; HTTP is allowed on loopback for local verification.
Configuration and lifecycle #
| Option or method | Behavior |
|---|---|
projectId, schemaHash, token, appVersion |
Required. Token scope and release compatibility must match these values. |
channel |
Required: production or preview; must match the credential. |
apiBaseUrl |
Defaults to https://translocale.io. |
cache |
Optional. Supply storage if updates must survive process restarts. |
pollInterval |
Five minutes by default; 30 seconds to one hour. |
start() |
Loads valid cache and starts automatic delivery checks. |
check() |
Refreshes manually, respecting an in-flight request and server cooldowns. |
addListener(callback) |
Returns a function that removes the listener. |
setActive(false/true) |
Pauses automatic polling or resumes it. |
dispose() |
Stops the client; create a new client to start again. |
Do not call start() yourself when using the generated Flutter builder; the builder owns startup and disposal. When integrating directly, own both lifecycle and cleanup in your app.
Native cache #
import 'dart:io';
import 'package:translocale_delivery/translocale_delivery_io.dart';
final cache = FileDeliveryCache(Directory(privateCacheDirectory));
// Pass cache: cache to DeliveryClient.
Supply a dedicated directory inside the app's private application-support storage. The file cache writes a temporary file, flushes it, and renames it over the previous entry. Entries contain release wording without credentials and are scoped by origin, project, channel, and schema. translocale_delivery_io.dart requires native file access; it is not a Flutter web cache.
Custom DeliveryCache adapters must provide atomic writes. The runtime serializes writes and keeps only the latest queued release, so a slow earlier write cannot overwrite a later one. state.cache reports writing until the queued snapshot is stored. A write that takes more than five seconds reports a cache error without delaying translation use. A storage operation already started may finish after disposal; disposal prevents further queued writes and UI updates.
Runtime behavior #
- Built-in lookups return immediately. Startup validates the cached release before its first request, allowing an unchanged release to return
304. - Invalid responses, network failures, unsupported app versions, and revoked credentials retain the last valid wording. Missing messages use the caller's fallback.
- Rollback uses a newer channel revision pointing to an earlier compatible release. Older or conflicting revisions are rejected.
- Cache reads and network checks each have a five-second deadline.
start()can take up to ten seconds to settle; synchronous JSON/hash work remains on the calling isolate. Responses are bounded to 1,510,000 bytes. - Polling defaults to five minutes, configurable between 30 seconds and one hour. Numeric and HTTP-date
Retry-Aftervalues delay retries. Authentication failure stops automatic polling; a manual successful check can resume it. - Requests reject redirects. Disposing cancels requests and timers, clears in-memory wording, and ignores late responses. The persisted cache remains available for the next start.
state reports the active release, channel revision, wording source, delivery status, and cache status without credentials. A delivery error does not imply that the app has lost its current wording.
Troubleshooting #
| Symptom | What to check |
|---|---|
resolve returns fallback |
Match the exact catalog filename, locale, key, schema, and published channel. Raw messages still need formatting. |
| Wording is lost after restart | Supply a persistent cache or use the Flutter runtime package. |
| A new release is ignored | Check app-version bounds, schema, checksum, and state. Invalid updates retain the last valid wording. |
| Automatic requests stop | Check token expiry/revocation and server cooldowns. A manual successful check can resume polling. |
| Offline first launch has no translated data | Ship bundled translations; a cache exists only after a valid release was saved. |
Keep credentials out of logs. App delivery credentials are read-only, but authoring credentials must never ship in the app.
See the Flutter demo #
The Flutter journal demo shows delivered French wording and Arabic right-to-left layout. These captures use a local delivery service. Follow the Flutter runtime walkthrough to add the same integration to your app.
View the Flutter web screenshot.
Contributing and verification #
In this package:
dart pub get
dart analyze
dart test
From the repository root, after building the JavaScript client and CLI:
npm run test:delivery:dart
Set DART_BIN if Dart is not on PATH. The verifier installs this package into an isolated consumer outside the workspace and exercises real HTTP delivery against ephemeral D1. It checks promotion/rollback, native cache writes, offline restart, incompatible app fallback, and conditional refresh. No paid translation jobs run.
Service-generated ARB fixtures verify shared schema and checksum rules using payloads with Arabic plural messages, Unicode, and escaped characters. npm run test:delivery:flutter separately generates reference output with Flutter, compares formatting in English/French/Arabic, and tests typed overrides, delegate reload, fallback, and RTL in Flutter widget tests. The Flutter journal example adds application lifecycle handling and private native storage. Verify it with npm run test:delivery:flutter-app. Set FLUTTER_BIN if needed. Device and installed-app verification remain separate gates.
License #
MIT. The hosted service remains private and cloud-only.