parse static method

ArbFile parse(
  1. String content, {
  2. String? sourcePath,
})

Parse ARB content from a UTF-8 string. sourcePath is recorded on the returned ArbFile so the check-report formatter can produce file:line hints.

Implementation

static ArbFile parse(String content, {String? sourcePath}) {
  final root = jsonDecode(content);
  if (root is! Map) {
    throw const FormatException('ARB root must be a JSON object.');
  }

  String? locale;
  final fileMetadata = <String, Object?>{};
  final partials = <String, _PartialEntry>{};
  final insertionOrder = <String>[];

  root.forEach((rawKey, value) {
    if (rawKey is! String) {
      throw FormatException(
        'ARB key must be a string, got ${rawKey.runtimeType}.',
      );
    }

    if (rawKey == '@@locale') {
      if (value is! String) {
        throw const FormatException('@@locale must be a string.');
      }
      locale = _nfc(value);
      return;
    }

    if (rawKey.startsWith('@@')) {
      // Other ARB-level metadata (e.g. Flutter gen_l10n's @@last_modified,
      // custom @@x-context). Preserve verbatim so the writer round-trips
      // them — silent stripping would corrupt user data on `dialect sync`.
      fileMetadata[rawKey] = value;
      return;
    }

    if (rawKey.startsWith('@')) {
      final dataKey = rawKey.substring(1);
      final partial = partials.putIfAbsent(
        dataKey,
        () => _PartialEntry(dataKey),
      );
      if (value is! Map) {
        throw FormatException(
          'Metadata for "$dataKey" must be a JSON object, got '
          '${value.runtimeType}.',
        );
      }
      partial.metadata = _parseMetadata(value);
      if (!insertionOrder.contains(dataKey)) insertionOrder.add(dataKey);
      return;
    }

    if (value is! String) {
      throw FormatException(
        'Value for "$rawKey" must be a string, got ${value.runtimeType}.',
      );
    }
    final partial = partials.putIfAbsent(rawKey, () => _PartialEntry(rawKey));
    partial.value = _nfc(value);
    if (!insertionOrder.contains(rawKey)) insertionOrder.add(rawKey);
  });

  if (locale == null) {
    throw const FormatException('ARB file missing @@locale.');
  }

  final entries = <ArbEntry>[];
  final orphans = <String, ArbMetadata>{};
  for (final key in insertionOrder) {
    final p = partials[key]!;
    if (p.value == null) {
      // Metadata-only entry — `@key` block without a corresponding
      // key/value pair. Preserve so `dialect check` (M4) can surface it
      // as a structural error. `dialect check --fix` strips orphans by
      // construction — the writer never emits orphanMetadata.
      if (p.metadata != null) orphans[key] = p.metadata!;
      continue;
    }
    entries.add(ArbEntry(key: key, value: p.value!, metadata: p.metadata));
  }

  return ArbFile(
    locale: locale!,
    entries: entries,
    fileMetadata: fileMetadata,
    orphanMetadata: orphans,
    entryLines: _lineMap(content),
    sourcePath: sourcePath,
  );
}