Back to all docs

BeMyWords with Flutter

Integration guide for Flutter apps using the intl package with ARB (.arb) message files.

Shape of the integration:

  1. At build time, fetch translations from BeMyWords and convert to lib/l10n/app_<lang>.arb.
  2. Run flutter gen-l10n to generate typed Dart accessors.
  3. Use AppLocalizations.of(context) in widgets.

Prerequisites

  • A Flutter project with localization enabled (intl + flutter_localizations in pubspec.yaml, a l10n.yaml config, and generate: true under flutter:).
  • A BeMyWords workspace, project, namespace, API key.
  • Node.js available in your build environment.

1. pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
  intl: any

flutter:
  generate: true

2. l10n.yaml

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

3. Environment variables

.env at project root (gitignored):

BEMYWORDS_BASE_URL=https://www.bemywords.no
BEMYWORDS_PROJECT_ID=<your-project-uuid>
BEMYWORDS_API_TOKEN=<your-api-token>
BEMYWORDS_NAMESPACE=flutter

4. Key naming convention

ARB keys become Dart method names and must be valid Dart identifiers (start with letter or underscore, no dots). Use camelCase in BeMyWords (homeHeroTitle) or underscore home_hero_title — pick one and stick to it.

5. Fetch + convert script

scripts/fetch-translations.mjs:

import { writeFileSync, mkdirSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = dirname(fileURLToPath(import.meta.url));
const L10N_DIR = join(__dirname, "..", "lib", "l10n");

const BASE_URL = process.env.BEMYWORDS_BASE_URL || "https://www.bemywords.no";
const PROJECT_ID = process.env.BEMYWORDS_PROJECT_ID;
const API_TOKEN = process.env.BEMYWORDS_API_TOKEN;
const NAMESPACE = process.env.BEMYWORDS_NAMESPACE || "flutter";

const LANGUAGES = ["en", "nb"];

async function fetchLanguage(lang) {
  const url = `${BASE_URL}/api/${PROJECT_ID}/latest/${lang}/${NAMESPACE}`;
  const res = await fetch(url, { headers: { Authorization: `Token token=${API_TOKEN}` } });
  if (!res.ok) throw new Error(`${lang}: HTTP ${res.status}`);
  return res.json();
}

async function main() {
  mkdirSync(L10N_DIR, { recursive: true });
  if (!PROJECT_ID || !API_TOKEN) {
    console.warn("BEMYWORDS_* env vars not set.");
    return;
  }

  for (const lang of LANGUAGES) {
    const data = await fetchLanguage(lang);
    // ARB format is just JSON with a leading "@@locale" meta key.
    const arb = { "@@locale": lang, ...data };
    writeFileSync(
      join(L10N_DIR, `app_${lang}.arb`),
      JSON.stringify(arb, null, 2) + "\n"
    );
    console.log(`  [${lang}] ${Object.keys(data).length} keys → app_${lang}.arb`);
  }
}

main().catch((e) => { console.error(e); process.exit(1); });

Run the script, then flutter gen-l10n produces lib/l10n/app_localizations.dart.

6. Use in Dart

import 'package:flutter_gen/gen_l10n/app_localizations.dart';

final localizations = AppLocalizations.of(context)!;
Text(localizations.homeHeroTitle);

7. Hook into build

In your CI or Makefile:

node scripts/fetch-translations.mjs
flutter gen-l10n
flutter build <target>

No native flutter pre-build hook for arbitrary scripts — you'll drive this from your build runner.

ICU message format

ARB supports ICU directly for plurals, gender, and interpolation:

{
  "apples": "{count, plural, =0 {no apples} one {1 apple} other {{count} apples}}",
  "greeting": "Hello, {name}!"
}

Store ICU strings verbatim in BeMyWords and enable ICU placeholder validation in project settings.

For more complex ARB features (placeholder types, descriptions via @ meta keys), those would need to be handled by the fetch script — either store the meta as BeMyWords key suffixes (apples.@) or as separate project settings data not covered by this basic flow.


API reference

See Astro integration guide.

Status

This guide is a best-effort draft — not yet verified in a production Flutter deployment. The ARB @ metadata (placeholder types, descriptions) handling is sketched but needs real-world validation. Report corrections.