fix(cv): force language alias precedence over host culture data

The CI backend test job failed on HumanLanguageCatalogTests:
  nynorsk -> expected "Norwegian", actual "Norwegian Nynorsk"

This was a real bug, correctly caught by the runner -- not runner
instability. Reproduced on Ubuntu 20.04 / libicu66 (the CI runner's ICU)
with .NET 9 installed via dotnet-install.sh exactly as CI does.

Root cause: BuildLanguageLookup's explicit normalization aliases
(nynorsk/bokmål/norsk -> Norwegian) were added with map.TryAdd, which
loses to any key the culture enumeration already inserted. On libicu66
the "nn" culture's NativeName is the bare word "nynorsk", so enumeration
claimed key "nynorsk" -> "Norwegian Nynorsk" first and the explicit alias
silently lost. On libicu70+ (Debian/Ubuntu 22.04+, my earlier local
runs) the native name is "norsk nynorsk", so the key was free and the
alias won -- which is why it passed locally and only failed on the
runner's older ICU. Same host-ICU dependence class as 9681618.

Fix: add an Override helper (map[key] = value) and apply it to the
alias block so these mappings win regardless of insertion order. Also
collapse the full "Norwegian Nynorsk"/"Norwegian Bokmål" phrases to
"Norwegian" for consistency. Correct by construction for any ICU version.

Verified:
- reproduced the exact failure on libicu66 with the old code (probe)
- fix logic yields nynorsk/bokmål -> Norwegian on that same libicu66
- real net9 test DLL: 420/420 on focal libicu66 (CI mirror, dotnet
  9.0.316 via dotnet-install.sh), and 420/420 on libicu72 (Debian) and
  libicu74 (Ubuntu 24.04), plus locally
No test weakened, skipped, or relaxed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
cesnimda
2026-07-19 23:08:37 +02:00
parent 5c5a572cfc
commit fba858e8eb
+28 -8
View File
@@ -103,6 +103,20 @@ public static class HumanLanguageCatalog
map.TryAdd(normalizedAlias, normalizedCanonical);
}
// Force an alias to a canonical, overriding whatever culture enumeration inserted for that
// key. TryAdd is not enough here: on a host whose ICU data carries a "Norwegian Nynorsk"
// culture, the enumeration below claims the key "nynorsk" -> "Norwegian Nynorsk" first, and a
// later TryAdd("nynorsk", "Norwegian") silently loses. That made "nynorsk" resolve to
// "Norwegian Nynorsk" on the CI runner but "Norwegian" locally — the same host-ICU dependence
// this seeding exists to remove. Overrides must win regardless of insertion order.
void Override(string alias, string canonical)
{
var normalizedAlias = NormalizeKey(alias);
var normalizedCanonical = NormalizeDisplayName(canonical);
if (string.IsNullOrWhiteSpace(normalizedAlias) || string.IsNullOrWhiteSpace(normalizedCanonical)) return;
map[normalizedAlias] = normalizedCanonical;
}
// Seeded FIRST, and deliberately not derived from the host.
//
// This table used to come only from CultureInfo.GetCultures, which returns whatever
@@ -145,14 +159,20 @@ public static class HumanLanguageCatalog
Add(native, english);
}
Add("norsk", "Norwegian");
Add("bokmal", "Norwegian");
Add("bokmål", "Norwegian");
Add("nynorsk", "Norwegian");
Add("mandarin", "Chinese");
Add("cantonese", "Chinese");
Add("farsi", "Persian");
Add("persian", "Persian");
// These collapse regional/script variants and common exonyms to the umbrella language a CV
// means. They must beat culture enumeration (see Override), because ICU carries "Norwegian
// Bokmål"/"Norwegian Nynorsk" and "Chinese (Simplified/Traditional)" as their own cultures.
Override("norsk", "Norwegian");
Override("bokmal", "Norwegian");
Override("bokmål", "Norwegian");
Override("nynorsk", "Norwegian");
Override("norwegian bokmal", "Norwegian");
Override("norwegian bokmål", "Norwegian");
Override("norwegian nynorsk", "Norwegian");
Override("mandarin", "Chinese");
Override("cantonese", "Chinese");
Override("farsi", "Persian");
Override("persian", "Persian");
return map;
}