run method

  1. @override
Future<int> run()
override

Runs this command.

The return value is wrapped in a Future if necessary and returned by CommandRunner.runCommand.

Implementation

@override
Future<int> run() async {
  final root = argResults!.option('root') ?? Directory.current.path;

  final DialectProject project;
  try {
    project = DialectProject.load(root);
  } on FileSystemException catch (e) {
    stderr.writeln(e.message);
    stderr.writeln('Run `dialect init` first, or run from the project root.');
    return 66;
  } on FormatException catch (e) {
    stderr.writeln('dialect.yaml or an ARB file is malformed:');
    stderr.writeln('  ${e.message}');
    return 65;
  }

  final KeySelection selection;
  try {
    selection = resolveSelection(
      project: project,
      command: 'accept',
      positionals: argResults!.rest,
      namespace: argResults!.option('namespace'),
      locale: argResults!.option('locale'),
    );
  } on SelectionFailure catch (f) {
    f.lines.forEach(stderr.writeln);
    return f.code;
  }

  // locale -> (key -> rewritten entry). Collected first and flushed once
  // per file: writing inside the loop re-serialized the stale in-memory ARB
  // each time, so with more than one key only the last one survived.
  final pending = <String, Map<String, ArbEntry>>{};

  final restamped = <String, Set<String>>{};
  final alreadyFresh = <String, Set<String>>{};
  final skipped = <String, Set<String>>{};

  for (final key in selection.keys) {
    final hash = computeSourceHash(project.source.entryFor(key)!.value);
    for (final locale in selection.locales) {
      final arb = project.translations[locale]!;
      final entry = arb.entryFor(key);
      if (entry == null || entry.value.isEmpty) {
        // No translation to bless — that's a missing_keys / empty_values
        // concern, not something `accept` invents a value for.
        skipped.putIfAbsent(key, () => <String>{}).add(locale);
        continue;
      }
      if (entry.metadata?.sourceHash == hash) {
        alreadyFresh.putIfAbsent(key, () => <String>{}).add(locale);
        continue;
      }
      pending.putIfAbsent(locale, () => <String, ArbEntry>{})[key] = ArbEntry(
        key: key,
        value: entry.value,
        metadata: ArbMetadata(
          locked: entry.metadata?.locked ?? false,
          sourceHash: hash,
        ),
      );
      restamped.putIfAbsent(key, () => <String>{}).add(locale);
    }
  }

  for (final e in pending.entries) {
    _flush(project.translations[e.key]!, e.value);
  }

  if (restamped.isEmpty && alreadyFresh.isEmpty && skipped.isNotEmpty) {
    stderr.writeln(
      'Nothing to accept: ${describeKeyCount(skipped.length)} '
      '(${previewKeys(skipped.keys)}) have no translation yet in '
      '${selection.locales.join(', ')}. Translate them first.',
    );
    return 65;
  }

  if (restamped.isNotEmpty) {
    final locales = <String>{for (final s in restamped.values) ...s}.toList()
      ..sort();
    stdout.writeln(
      '✓ accepted ${describeKeyCount(restamped.length)} — re-stamped as '
      'current in ${locales.join(', ')}:',
    );
    stdout.writeln('    ${previewKeys(restamped.keys)}');
  }
  if (alreadyFresh.isNotEmpty) {
    stdout.writeln(
      '  already current: ${describeKeyCount(alreadyFresh.length)}',
    );
  }
  if (skipped.isNotEmpty) {
    stdout.writeln(
      '  skipped (no translation yet): '
      '${describeKeyCount(skipped.length)} — ${previewKeys(skipped.keys)}',
    );
  }
  return 0;
}