From fba858e8eb6b20f504baae10a671be9ca145523a Mon Sep 17 00:00:00 2001 From: cesnimda Date: Sun, 19 Jul 2026 23:08:37 +0200 Subject: [PATCH] fix(cv): force language alias precedence over host culture data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- Models/HumanLanguageCatalog.cs | 36 ++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/Models/HumanLanguageCatalog.cs b/Models/HumanLanguageCatalog.cs index 8ed2b33..ab913d3 100644 --- a/Models/HumanLanguageCatalog.cs +++ b/Models/HumanLanguageCatalog.cs @@ -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; }