checkLocaleChangelogs function

List<ReleaseProblem> checkLocaleChangelogs(
  1. String changelog, {
  2. required Set<String> locales,
  3. String? version,
  4. Map<String, int> limits = defaultReleaseNotesLimits,
})

Checks the per-locale release notes beside changelog against the locales a repository declares.

CHANGELOG.md is the notes for every locale without a file of its own, and CHANGELOG.<locale>.md is that locale's — see release_notes.dart. Four things are reported, each a way for a store to show the wrong text with nothing having said so:

  • a locale file with no section for version — the "forgot to translate this release" case, refused by the uploaders exactly as the English one is. Skipped when version is null, which is a project that does not say what it is about to ship;
  • a locale file over a store's cap, by checkChangelog and named as itself. A German section is routinely longer than its English source, so it meets Play's 500 first;
  • a locale file for a locale nothing declares, which publishes nowhere — most likely a misspelt filename, CHANGELOG.de.md beside de-DE;
  • a dangling symlink where a locale file would be, which the filesystem reports as absent and which would therefore publish the default text in silence.

A declared locale with no file is not a problem, and that is the design rather than a gap: it is how a listing gaining a language costs nothing, and the stores themselves fall back the same way. verify names every such locale on every run instead, so the choice stays visible.

Implementation

List<ReleaseProblem> checkLocaleChangelogs(
  String changelog, {
  required Set<String> locales,
  String? version,
  Map<String, int> limits = defaultReleaseNotesLimits,
}) {
  final problems = <ReleaseProblem>[];
  final defaultName = _fileName(changelog);

  for (final locale in locales) {
    switch (localeNotesSource(changelog, locale)) {
      case DefaultFile():
        break;
      case DanglingLink(:final path):
        problems.add(
          ReleaseProblem(
            path,
            'is a symlink to nothing, so $locale would silently publish '
            "$defaultName's notes — point it at a file, or delete it to "
            'publish those notes deliberately',
          ),
        );
      case OwnFile(:final path):
        final name = _fileName(path);
        final markdown = File(path).readAsStringSync();
        problems.addAll(checkChangelog(markdown, limits: limits, name: name));
        if (version != null &&
            changelogNotes(markdown, version, platform: 'ios') is NoSection) {
          problems.add(
            ReleaseProblem(
              '$name § $version',
              'no section — $name is $locale\'s release notes, and the '
                  'uploaders refuse this version without one. Add '
                  '"## $version" (empty if nothing changed for $locale '
                  "readers), or delete the file to publish $defaultName's "
                  'notes to $locale',
            ),
          );
        }
    }
  }

  final beside = localeChangelogsBeside(changelog);
  for (final MapEntry(key: locale, value: path) in beside.entries) {
    if (!locales.contains(locale)) {
      problems.add(
        ReleaseProblem(
          path,
          'exists and no store declares $locale, so it publishes nowhere — '
          'rename it to a declared locale '
          '(${locales.isEmpty ? 'none are declared' : (locales.toList()..sort()).join(', ')}), '
          'or declare $locale in .cux-ship.yaml',
        ),
      );
    }
  }

  return problems;
}