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 results = argResults!;
  final auto = results['auto'] as bool;
  final rest = results.rest;

  if (rest.length > 1) {
    stderr.writeln(
      'dialect translate takes at most one positional argument.',
    );
    return 64;
  }
  final root = rest.isEmpty ? Directory.current.path : rest.first;

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

  if (auto) {
    // The direct-LLM path is on the roadmap (v1.2) but not built. Fail
    // loudly rather than silently no-op in CI.
    stderr.writeln(
      '--auto is not available yet. The AI-pointer flow is the primary '
      'path: run `dialect translate` (no flags) and have your AI agent '
      'execute the generated plan.',
    );
    return 70;
  }

  if (project.config.targetLocales.isEmpty) {
    stdout.writeln(
      '! dialect translate: no `target_locales` configured in dialect.yaml.',
    );
    stdout.writeln('  Add a target locale and re-run.');
    return 0;
  }

  final work = _computeWork(project);
  final tokens = {
    ...commonPlanTokens(project),
    'WORKLIST': _renderWorklist(work),
  };
  final rendered = renderPlanTemplate(translatePlanMdTemplate, tokens);

  final planPath = p.join(root, '.dialect', 'translate-plan.md');
  final planFile = File(planPath);
  planFile.parent.createSync(recursive: true);
  planFile.writeAsStringSync(rendered);

  final relativePath = p.relative(planPath, from: Directory.current.path);
  final totalToTranslate = work.fold<int>(
    0,
    (n, w) => n + w.missing.length + w.staleUnlocked.length,
  );
  final totalStaleLocked = work.fold<int>(
    0,
    (n, w) => n + w.staleLocked.length,
  );

  if (totalToTranslate == 0 && totalStaleLocked == 0) {
    stdout.writeln(
      '✓ dialect translate: every target locale is fully translated and '
      'fresh — nothing to do.',
    );
    stdout.writeln('  (Wrote $relativePath anyway, for the record.)');
    return 0;
  }

  stdout.writeln(
    'Wrote translate plan to $relativePath '
    '($totalToTranslate key(s) to (re)translate'
    '${totalStaleLocked > 0 ? ', $totalStaleLocked stale lock(s) to review' : ''}).',
  );
  stdout.writeln(
    '  Next: open your AI tool (Claude Code, Cursor, Cline, Copilot, …)',
  );
  stdout.writeln('  and ask it to follow the plan:');
  stdout.writeln('    "Read $relativePath and execute the steps."');
  return 0;
}