fluent_gen 0.1.0-dev.0 copy "fluent_gen: ^0.1.0-dev.0" to clipboard
fluent_gen: ^0.1.0-dev.0 copied to clipboard

Typed Dart calls for fluent_bundle — reads your .ftl files at build time and generates methods, so misspelled names and wrong arguments fail to compile. Dev-dependency only.

example/lib/main.dart

// fluent_gen showcase — the generated API end to end in one runnable file.
//
// Everything here calls `lib/i18n/app_messages.g.dart`, which fluent_gen
// emitted from `lib/i18n/*.ftl` (run `dart run build_runner build` to
// regenerate). The tour exercises every kind of thing the generator
// emits; `test/showcase_test.dart` runs it and pins the output.
//
// Run with: dart run fluent_gen_example:main  (or `dart run lib/main.dart`)

import 'package:fluent_bundle/markup.dart';
import 'package:fluent_gen_example/i18n/app_messages.g.dart';
import 'package:fluent_intl/fluent_intl.dart';

/// Every section of the tour, as `label: value` lines. The showcase test
/// asserts on these — keep labels stable.
List<String> runShowcase() => [
  ...localeEnumShowcase(),
  ...accessorShowcase(),
  ...markupAndAttributeShowcase(),
  ...errorShowcase(),
  ...fallbackShowcase(),
];

// ── §1 AppLocale — the generated locale enum + negotiation + loading ────

List<String> localeEnumShowcase() {
  // negotiate: exact tag → truncated subtags → language prefix → fallback.
  final ca = AppLocale.negotiate('fr-CA');
  final unknown = AppLocale.negotiate('ja-JP');
  return [
    'locale.values: ${AppLocale.values.map((l) => l.languageTag).join(',')}',
    'locale.base: ${AppLocale.base.languageTag}',
    'locale.tryParse: ${AppLocale.tryParse('FR')?.languageTag} / '
        '${AppLocale.tryParse('nope')}',
    'locale.negotiate: fr-CA→${ca.languageTag} ja-JP→${unknown.languageTag}',
    'locale.manifest: ${AppMessages.accessorNames.length} accessors',
  ];
}

/// The generated `load()` builds a ready bundle from the embedded FTL
/// (`bundle_ftl: true`); the backend decides CLDR fidelity.
AppMessages messagesFor(AppLocale locale) =>
    AppMessages(locale.load(backend: IntlBackend()));

// ── §2 Typed accessors — inferred parameter types, per-pattern params ──

List<String> accessorShowcase() {
  final en = messagesFor(AppLocale.en);
  final fr = messagesFor(AppLocale.fr);
  return [
    'msg.plain: ${en.hello()}',
    'msg.fr: ${fr.hello()}',
    // A comment pin (`# $name (String)`) narrows text-only usage.
    'msg.pinned: ${en.welcome(name: 'Aria')}',
    // $count drives a plural selector → num, CLDR category via backend.
    'msg.plural: ${en.items(count: 1)} / ${en.items(count: 5)}',
    // String-keyed selector → String.
    'msg.select: ${en.device(platform: 'mac')}',
    // NUMBER(...) usage → num; DATETIME(...) usage → DateTime.
    'msg.number: ${en.price(amount: 42)}',
    'msg.datetime: ${en.launchedAt(d: DateTime(2026, 1, 15))}',
    // Text-only usage with no pin stays Object? — anything formats.
    'msg.unpinned: ${en.greeting(name: 'Aria')}',
    // `when` is a Dart keyword → the parameter is escaped to `$when`;
    // the FTL argument name is unchanged. Also a transitive message
    // reference: welcomeBack pulls greeting's variables into its own.
    'msg.reserved: ${en.welcomeBack(name: 'Aria', $when: DateTime(2026, 1, 15))}',
  ];
}

// ── §3 Attributes + markup — per-attribute methods, span variants ──────

List<String> markupAndAttributeShowcase() {
  final en = messagesFor(AppLocale.en);
  final spans = en.bannerAsSpans(title: 'Pro');
  return [
    // Attributes emit their own methods, each demanding only its own
    // pattern's variables — login() needs nothing, login$helper() needs
    // $name.
    'attr.value: ${en.login()}',
    'attr.title: ${en.login$title()}',
    'attr.helper: ${en.login$helper(name: 'Aria')}',
    // Messages with inline markup also get an AsSpans sibling.
    'markup.flat: ${en.banner(title: 'Pro')}',
    'markup.spans: ${spans.map(_describe).join(' | ')}',
  ];
}

String _describe(FluentSpan span) => switch (span) {
  FluentTextSpan(:final text) => 'text(${text.trim()})',
  FluentMarkupSpan(:final tag, :final children) =>
    '$tag(${children.map(_describe).join(' ')})',
};

// ── §4 Errors — every accessor takes the standard out-list ─────────────

List<String> errorShowcase() {
  final en = messagesFor(AppLocale.en);
  final errors = <FluentError>[];
  // Formatting never throws; problems land in the caller's list.
  final out = en.welcome(name: 'Aria', errors: errors);
  return ['errors.clean: $out (${errors.length} errors)'];
}

// ── §5 Locale fallback — a locale missing a message falls back ─────────

List<String> fallbackShowcase() {
  // fr.ftl deliberately omits `device`; the generated accessor formats
  // through the fr bundle, which reports the miss — the builder already
  // warned about it at generation time. Apps that want silent base-locale
  // fallback chain bundles; the accessor surface is identical.
  final fr = messagesFor(AppLocale.fr);
  final errors = <FluentError>[];
  final out = fr.device(platform: 'mac', errors: errors);
  return ['fallback.missing: $out (${errors.length} error)'];
}

void main() => runShowcase().forEach(print);
1
likes
0
points
106
downloads

Publisher

verified publisherwhuppi.com

Weekly Downloads

Typed Dart calls for fluent_bundle — reads your .ftl files at build time and generates methods, so misspelled names and wrong arguments fail to compile. Dev-dependency only.

Repository (GitHub)
View/report issues

Topics

#i18n #localization #fluent #codegen #build-runner

License

unknown (license)

Dependencies

build, dart_style, fluent_bundle, glob, path

More

Packages that depend on fluent_gen