From b82d197d76db30ac82d72eb1ff3b5bdba70c4b15 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 22 Feb 2026 15:07:36 +0100 Subject: [PATCH 1/5] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor=20culture=20a?= =?UTF-8?q?nd=20region=20handling=20in=20World=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Cuemon.Core/Globalization/World.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Cuemon.Core/Globalization/World.cs b/src/Cuemon.Core/Globalization/World.cs index 4901e2e1..efd284c0 100644 --- a/src/Cuemon.Core/Globalization/World.cs +++ b/src/Cuemon.Core/Globalization/World.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using Cuemon.Collections.Generic; namespace Cuemon.Globalization { @@ -14,9 +15,9 @@ public static class World { var cultures = new SortedList(); var specificCultures = CultureInfo.GetCultures(CultureTypes.SpecificCultures); - foreach (var c in specificCultures.Where(ci => ci.LCID != 127)) + foreach (var c in specificCultures.Where(ci => ci.LCID != 127)) // *should* not happen for specific cultures, but on some linux based systems there are some cultures with LCID 127 (invariant culture) that are incorrectly categorized as specific cultures, so we need to filter those out. { - if (!cultures.ContainsKey(c.DisplayName)) { cultures.Add(c.DisplayName, c); } + Decorator.Enclose(cultures).TryAdd(c.Name, c); } return cultures.Values; }); @@ -27,9 +28,10 @@ public static class World foreach (var c in SpecificCultures.Value) { var region = new RegionInfo(c.Name); - if (!regions.ContainsKey(region.EnglishName)) { regions.Add(region.EnglishName, region); } + if (int.TryParse(region.Name, out _)) { continue; } // Skip statistical regions (why would Microsoft even consider having these as part of RegionInfo? No valid ISO 3166-1 alpha-2/3 code can be all digits, so these are not actual regions or countries.) + Decorator.Enclose(regions).TryAdd(c.Name, region); } - return regions.Values; + return regions.Values.OrderBy(info => info.Name); }); private static readonly Lazy UnM49Data = new(() => new UnM49DataContainer()); From 5e0fb1744f81e33961d0116c508546eccc156a62 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 22 Feb 2026 15:07:53 +0100 Subject: [PATCH 2/5] =?UTF-8?q?=E2=9C=85=20ensure=20backward=20compatibili?= =?UTF-8?q?ty=20test=20for=20iso=20region=20codes=20in=20World=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Globalization/WorldTest.cs | 58 ++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/test/Cuemon.Core.Tests/Globalization/WorldTest.cs b/test/Cuemon.Core.Tests/Globalization/WorldTest.cs index d40c6414..15ae09f4 100644 --- a/test/Cuemon.Core.Tests/Globalization/WorldTest.cs +++ b/test/Cuemon.Core.Tests/Globalization/WorldTest.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using Codebelt.Extensions.Xunit; @@ -12,6 +13,61 @@ public WorldTest(ITestOutputHelper output) : base(output) { } + [Fact] + public void Regions_ShouldContainAllExpectedIsoRegionCodes_ForBackwardCompatibility() + { + var expectedTwoLetterIsoCodes = new HashSet(StringComparer.Ordinal) + { + "AD", "AE", "AF", "AG", "AI", "AL", "AM", "AO", "AR", "AS", "AT", "AU", "AW", "AX", "AZ", + "BA", "BB", "BD", "BE", "BF", "BG", "BH", "BI", "BJ", "BL", "BM", "BN", "BO", "BQ", "BR", "BS", "BT", "BW", "BY", "BZ", + "CA", "CC", "CD", "CF", "CG", "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", "CU", "CV", "CW", "CX", "CY", "CZ", + "DE", "DJ", "DK", "DM", "DO", "DZ", + "EC", "EE", "EG", "ER", "ES", "ET", + "FI", "FJ", "FK", "FM", "FO", "FR", + "GA", "GB", "GD", "GE", "GF", "GG", "GH", "GI", "GL", "GM", "GN", "GP", "GQ", "GR", "GT", "GU", "GW", "GY", + "HK", "HN", "HR", "HT", "HU", + "ID", "IE", "IL", "IM", "IN", "IO", "IQ", "IR", "IS", "IT", + "JE", "JM", "JO", "JP", + "KE", "KG", "KH", "KI", "KM", "KN", "KP", "KR", "KW", "KY", "KZ", + "LA", "LB", "LC", "LI", "LK", "LR", "LS", "LT", "LU", "LV", "LY", + "MA", "MC", "MD", "ME", "MF", "MG", "MH", "MK", "ML", "MM", "MN", "MO", "MP", "MQ", "MR", "MS", "MT", "MU", "MV", "MW", "MX", "MY", "MZ", + "NA", "NC", "NE", "NF", "NG", "NI", "NL", "NO", "NP", "NR", "NU", "NZ", + "OM", + "PA", "PE", "PF", "PG", "PH", "PK", "PL", "PM", "PN", "PR", "PS", "PT", "PW", "PY", + "QA", + "RE", "RO", "RS", "RU", "RW", + "SA", "SB", "SC", "SD", "SE", "SG", "SH", "SI", "SJ", "SK", "SL", "SM", "SN", "SO", "SR", "SS", "ST", "SV", "SX", "SY", "SZ", + "TC", "TD", "TG", "TH", "TJ", "TK", "TL", "TM", "TN", "TO", "TR", "TT", "TV", "TW", "TZ", + "UA", "UG", "UM", "US", "UY", "UZ", + "VA", "VC", "VE", "VG", "VI", "VN", "VU", + "WF", "WS", + "XK", + "YE", "YT", + "ZA", "ZM", "ZW" + }; + + var sut1 = World.Regions.ToList(); + var actualCodes = new HashSet(sut1.Select(r => r.Name), StringComparer.Ordinal); + +#if NET48_OR_GREATER + Assert.NotEmpty(actualCodes); + Assert.True(actualCodes.Count > 100, "actualCodes.Count > 100"); +#else + var missing = expectedTwoLetterIsoCodes.Except(actualCodes).OrderBy(c => c).ToList(); + var added = actualCodes.Except(expectedTwoLetterIsoCodes).OrderBy(c => c).ToList(); + foreach (var code in missing) + { + TestOutput.WriteLine($"Missing: {code} - {World.Regions.SingleOrDefault(info => info.Name == code).EnglishName}"); + } + foreach (var code in added) + { + TestOutput.WriteLine($"Added: {code} - {World.Regions.Last(info => info.Name == code).EnglishName}"); + } + TestOutput.WriteLine($"Expected: {expectedTwoLetterIsoCodes.Count}, Actual: {actualCodes.Count}, Missing: {missing.Count}, Added: {added.Count}"); + Assert.Empty(missing); +#endif + } + [Fact] public void Regions_ShouldReturnAllRegions() { From c45bd4aef79145f777005d19a951c0e871bc58d9 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 22 Feb 2026 15:45:30 +0100 Subject: [PATCH 3/5] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20update=20region=20hand?= =?UTF-8?q?ling=20to=20exclude=20redundancies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Cuemon.Core/Globalization/World.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Cuemon.Core/Globalization/World.cs b/src/Cuemon.Core/Globalization/World.cs index efd284c0..eb06ba00 100644 --- a/src/Cuemon.Core/Globalization/World.cs +++ b/src/Cuemon.Core/Globalization/World.cs @@ -29,9 +29,9 @@ public static class World { var region = new RegionInfo(c.Name); if (int.TryParse(region.Name, out _)) { continue; } // Skip statistical regions (why would Microsoft even consider having these as part of RegionInfo? No valid ISO 3166-1 alpha-2/3 code can be all digits, so these are not actual regions or countries.) - Decorator.Enclose(regions).TryAdd(c.Name, region); + Decorator.Enclose(regions).TryAdd($"{region.Name}:{region.NativeName}", region); } - return regions.Values.OrderBy(info => info.Name); + return regions.Values.OrderBy(info => info.Name).ThenBy(info => info.NativeName); }); private static readonly Lazy UnM49Data = new(() => new UnM49DataContainer()); From 0c6e1f718dedba631da14ac28de3cc131c513d9e Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 22 Feb 2026 15:46:26 +0100 Subject: [PATCH 4/5] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20optimize=20region=20lo?= =?UTF-8?q?okup=20by=20caching=20iso=20alpha-2=20codes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Globalization/UnM49DataContainer.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Cuemon.Core/Globalization/UnM49DataContainer.cs b/src/Cuemon.Core/Globalization/UnM49DataContainer.cs index 9daef7c1..dc84abf4 100644 --- a/src/Cuemon.Core/Globalization/UnM49DataContainer.cs +++ b/src/Cuemon.Core/Globalization/UnM49DataContainer.cs @@ -142,6 +142,15 @@ private void BuildHierarchy(List regions, List(StringComparer.OrdinalIgnoreCase); + foreach (var r in World.Regions) + { + if (!regionsByIsoAlpha2.ContainsKey(r.TwoLetterISORegionName)) + { + regionsByIsoAlpha2[r.TwoLetterISORegionName] = r; + } + } + foreach (var countryData in countries) { if (RegionsByCode.TryGetValue(countryData.ParentCode, out var parent)) @@ -154,15 +163,10 @@ private void BuildHierarchy(List regions, List string.Equals(r.TwoLetterISORegionName, countryData.IsoAlpha2, StringComparison.OrdinalIgnoreCase)); - } - catch + if (!string.IsNullOrEmpty(countryData.IsoAlpha2)) { - // Some territories may not be supported by the OS + regionsByIsoAlpha2.TryGetValue(countryData.IsoAlpha2, out regionInfo); } var country = new StatisticalRegionInfo( From 80b86a5e72bdc1be4c2405d9d630c4910efb437f Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 22 Feb 2026 15:47:26 +0100 Subject: [PATCH 5/5] =?UTF-8?q?=E2=9C=85=20update=20regions=20test=20to=20?= =?UTF-8?q?print=20details=20and=20assert=20unique=20iso=20codes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Globalization/WorldTest.cs | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/test/Cuemon.Core.Tests/Globalization/WorldTest.cs b/test/Cuemon.Core.Tests/Globalization/WorldTest.cs index 15ae09f4..12e7ce38 100644 --- a/test/Cuemon.Core.Tests/Globalization/WorldTest.cs +++ b/test/Cuemon.Core.Tests/Globalization/WorldTest.cs @@ -69,17 +69,40 @@ public void Regions_ShouldContainAllExpectedIsoRegionCodes_ForBackwardCompatibil } [Fact] - public void Regions_ShouldReturnAllRegions() + public void Regions_ShouldPrintAllRegionsAndHighlightIsoCodeFrequency() { var sut1 = World.Regions.ToList(); - TestOutput.WriteLine(sut1.Count.ToString()); + foreach (var r in sut1) + { + TestOutput.WriteLine($"{r.Name,-5} {r.EnglishName}"); + } + + var grouped = sut1.GroupBy(r => r.Name).OrderBy(g => g.Key).ToList(); + var multiEntry = grouped.Where(g => g.Count() > 1).OrderByDescending(g => g.Count()).ThenBy(g => g.Key).ToList(); + + TestOutput.WriteLine($"Total: {sut1.Count}, Unique ISO codes: {grouped.Count}, ISO codes with multiple entries: {multiEntry.Count}"); + + foreach (var g in multiEntry) + { + var first = g.First(); + var allEqual = g.All(r => r.Equals(first)); + var distinctNativeNames = g.Select(r => r.NativeName).Distinct().ToList(); + TestOutput.WriteLine($" {g.Key} ({first.EnglishName}): {g.Count()} entries | all Equals: {allEqual} | distinct NativeNames: {distinctNativeNames.Count}"); + if (distinctNativeNames.Count > 1) + { + foreach (var name in distinctNativeNames) + { + TestOutput.WriteLine($" NativeName: {name}"); + } + } + } - Assert.NotNull(sut1); + Assert.NotEmpty(sut1); #if NET48_OR_GREATER Assert.True(sut1.Count > 100, "sut1.Count > 100"); #else - Assert.True(sut1.Count > 220, "sut1.Count > 220"); + Assert.True(sut1.Count > 400, "sut1.Count > 400"); #endif }