diff --git a/docs/README.md b/docs/README.md
index 023cb7b5686099..6989879155dcc5 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -25,6 +25,7 @@ Design Docs
=================
- [.NET Globalization Invariant Mode](design/features/globalization-invariant-mode.md)
+- [WASM Globalization Icu](design/features/globalization-icu-wasm.md)
- [Cross-Platform Cryptography](design/features/cross-platform-cryptography.md)
- Many more under [design/features](design/features/)
diff --git a/docs/design/features/globalization-icu-wasm.md b/docs/design/features/globalization-icu-wasm.md
new file mode 100644
index 00000000000000..dc83b154feff44
--- /dev/null
+++ b/docs/design/features/globalization-icu-wasm.md
@@ -0,0 +1,63 @@
+# WASM Globalization Icu
+
+In WASM applications when [globalization invariant mode](globalization-invariant-mode.md) is switched off, internalization data file is loaded. There are four basic types of these files:
+- `icudt.dat` - full data
+- `icudt_EFIGS.dat` - data for locales: "en-*", "fr-FR", "es-ES", "it-IT", and "de-DE".
+- `icudt_CJK.dat` - for locales: "en" "ja", "ko", and "zh".
+- `icudt_no_CJK.dat` - all locales from `icudt.dat`, excluding "ja", "ko", and "zh".
+
+## Wasm Console, Wasm Browser
+
+We can specify the file we want to load, e.g. `icudt_no_CJK.dat` by adding to .csproj:
+```
+icudt_no_CJK.dat
+```
+Only one value for `WasmIcuDataFileName` can be set. It can also be a custom file, created by the developer. To create a custom ICU file, see `Custom ICU` section below. If no `WasmIcuDataFileName` was specified, the application's culture will be checked and the corresponding file will be loaded, e.g. for `en-US` file `icudt_EFIGS.dat`, and for `zh-CN` - `icudt_CJK.dat`.
+
+## Custom ICU
+
+The easiest way to build ICU is to open https://github.com/dotnet/icu/ it in [Codespaces](docs\workflow\Codespaces.md). See files in https://github.com/dotnet/icu/tree/dotnet/main/icu-filters, and read https://unicode-org.github.io/icu/userguide/icu_data/buildtool.html#locale-slicing. Build your own filter or edit the existing file.
+We advise to edit the filters **only by adding/removing locales** from the `localeFilter/whitelist` to avoid removing important data. We recommend not to remove "en-US" locale from the localeFilter/whitelist because it is used as a fallback. Removing it for when
+- `true`: results in `Encountered infinite recursion while looking for resource in System.Private.Corelib.` exception
+- when predefined cultures only is not set: results in resolving data from ICU's `root.txt` files, e.g. `CultureInfo.DateTimeFormat.GetDayName(DateTime.Today.DayOfWeek)` will return an abbreviated form: `Mon` instead of `Monday`.
+Removing specific feature data might result in an exception that starts with `[CultureData.IcuGetLocaleInfo(LocaleStringData)] Failed`. It means you removed data necessary to extract basic information about the locale.
+
+ In the file `eng/icu.mk`, you can choose what filters to build. Choose the platform:
+
+### Building for Browser:
+* For prerequisites run `.devcontainer/postCreateCommand.sh` (it is run automatically on creation if using Codespaces)
+* Building:
+ ```
+ ./build.sh /p:TargetOS=Browser /p:TargetArchitecture=wasm /p:IcuTracing=true
+ ```
+ Output is located in `artifacts/bin/icu-browser-wasm`.
+
+### Building for Mobiles:
+* For prerequisites run:
+ ```bash
+ export ANDROID_NDK_ROOT=$PWD/artifacts/ndk/
+ mkdir $ANDROID_NDK_ROOT
+ wget https://dl.google.com/android/repository/android-ndk-r25b-linux.zip
+ unzip android-ndk-r25b-linux.zip -d $ANDROID_NDK_ROOT
+ rm android-ndk-r25b-linux.zip
+ mv $ANDROID_NDK_ROOT/*/* $ANDROID_NDK_ROOT
+ rmdir $ANDROID_NDK_ROOT/android-ndk-r25b
+ ```
+* Building:
+ ```bash
+ ./build.sh /p:TargetOS=Android /p:TargetArchitecture=x64 /p:IcuTracing=true
+ ```
+
+Output from both builds will be located in subdirectories of `artifacts/bin`. Copy the generated `.dat` files to a suitable location and provide the full path to it in the `.csproj`, e.g.:
+```xml
+C:\Users\wasmUser\icuSources\customIcu.dat
+```
+
+## Blazor
+
+In Blazor we are loading the file based on the applications's culture.
+To force the full data to be loaded, add this to your `.csproj`:
+```xml
+true
+```
+Custom files loading for Blazor is not possible.
diff --git a/eng/testing/scenarios/BuildWasmAppsJobsList.txt b/eng/testing/scenarios/BuildWasmAppsJobsList.txt
index 6bafc00c1ad435..38e288f81cfa14 100644
--- a/eng/testing/scenarios/BuildWasmAppsJobsList.txt
+++ b/eng/testing/scenarios/BuildWasmAppsJobsList.txt
@@ -4,16 +4,18 @@ Wasm.Build.NativeRebuild.Tests.OptimizationFlagChangeTests
Wasm.Build.NativeRebuild.Tests.ReferenceNewAssemblyRebuildTest
Wasm.Build.NativeRebuild.Tests.SimpleSourceChangeRebuildTest
Wasm.Build.Tests.Blazor.BuildPublishTests
-Wasm.Build.Tests.Blazor.MiscTests2
Wasm.Build.Tests.Blazor.MiscTests
+Wasm.Build.Tests.Blazor.MiscTests2
Wasm.Build.Tests.Blazor.NativeRefTests
Wasm.Build.Tests.BuildPublishTests
Wasm.Build.Tests.CleanTests
Wasm.Build.Tests.ConfigSrcTests
+Wasm.Build.Tests.IcuShardingTests
Wasm.Build.Tests.InvariantGlobalizationTests
Wasm.Build.Tests.MainWithArgsTests
Wasm.Build.Tests.NativeBuildTests
Wasm.Build.Tests.NativeLibraryTests
+Wasm.Build.Tests.NonWasmTemplateBuildTests
Wasm.Build.Tests.PInvokeTableGeneratorTests
Wasm.Build.Tests.RebuildTests
Wasm.Build.Tests.SatelliteAssembliesTests
diff --git a/eng/testing/tests.browser.targets b/eng/testing/tests.browser.targets
index 6f0972fbfa110e..849a12e96a83b4 100644
--- a/eng/testing/tests.browser.targets
+++ b/eng/testing/tests.browser.targets
@@ -8,6 +8,9 @@
true
+
+ true
+
$([MSBuild]::NormalizeDirectory($(MonoProjectRoot), 'wasm', 'emsdk'))
@@ -184,6 +187,8 @@
<_WasmPropertyNames Include="DisableParallelEmccCompile" />
<_WasmPropertyNames Include="EmccCompileOptimizationFlag" />
<_WasmPropertyNames Include="EmccLinkOptimizationFlag" />
+ <_WasmPropertyNames Include="WasmIncludeFullIcuData" />
+ <_WasmPropertyNames Include="WasmIcuDataFileName" />
diff --git a/src/mono/wasi/build/WasiApp.targets b/src/mono/wasi/build/WasiApp.targets
index 0688e932d0e164..56a05ab2506778 100644
--- a/src/mono/wasi/build/WasiApp.targets
+++ b/src/mono/wasi/build/WasiApp.targets
@@ -319,27 +319,29 @@
- icudt.dat
-
<_HasDotnetWasm Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.wasm'">true
-
-
-
+
+
+
+
+
+ DependsOnTargets="_WasmGenerateRuntimeConfig;_GetWasiGenerateAppBundleDependencies;_WasiDefaultGenerateAppBundle;_GenerateRunWasmtimeScript">
+
+
@@ -353,7 +355,7 @@
DefaultHostConfig="$(DefaultWasmHostConfig)"
InvariantGlobalization="$(InvariantGlobalization)"
SatelliteAssemblies="@(_WasmSatelliteAssemblies)"
- IcuDataFileName="$(WasmIcuDataFileName)"
+ IcuDataFileNames="@(WasmIcuDataFileNames)"
ExtraFilesToDeploy="@(WasmExtraFilesToDeploy)"
NativeAssets="@(WasmNativeAsset)"
DebugLevel="$(WasmDebugLevel)"
diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs
index 714ad4075bb4d5..39e02f4c799690 100644
--- a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs
@@ -30,6 +30,7 @@ public abstract class BuildTestBase : IClassFixture skip automatic icu testing with Node
+ // on Linux sharding does not work because we rely on LANG env var to check locale and emcc is overwriting it
+ protected static RunHost s_hostsForOSLocaleSensitiveTests = RunHost.Chrome;
// FIXME: use an envvar to override this
- protected static int s_defaultPerTestTimeoutMs = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 30*60*1000 : 15*60*1000;
+ protected static int s_defaultPerTestTimeoutMs = s_isWindows ? 30*60*1000 : 15*60*1000;
protected static BuildEnvironment s_buildEnv;
private const string s_runtimePackPathPattern = "\\*\\* MicrosoftNetCoreAppRuntimePackDir : '([^ ']*)'";
private const string s_nugetInsertionTag = "";
@@ -139,7 +145,8 @@ protected string RunAndTestWasmApp(BuildArgs buildArgs,
string targetFramework = DefaultTargetFramework,
string? extraXHarnessMonoArgs = null,
string? extraXHarnessArgs = null,
- string jsRelativePath = "test-main.js")
+ string jsRelativePath = "test-main.js",
+ string environmentLocale = DefaultEnvironmentLocale)
{
buildDir ??= _projectDir;
envVars ??= new();
@@ -157,16 +164,15 @@ protected string RunAndTestWasmApp(BuildArgs buildArgs,
}
bundleDir ??= Path.Combine(GetBinDir(baseDir: buildDir, config: buildArgs.Config, targetFramework: targetFramework), "AppBundle");
- if (host is RunHost.V8 && OperatingSystem.IsWindows())
+ IHostRunner hostRunner = GetHostRunnerFromRunHost(host);
+ if (!hostRunner.CanRunWBT())
throw new InvalidOperationException("Running tests with V8 on windows isn't supported");
// Use wasm-console.log to get the xharness output for non-browser cases
- (string testCommand, string xharnessArgs, bool useWasmConsoleOutput) = host switch
- {
- RunHost.V8 => ("wasm test", $"--js-file={jsRelativePath} --engine=V8 -v trace", true),
- RunHost.NodeJS => ("wasm test", $"--js-file={jsRelativePath} --engine=NodeJS -v trace", true),
- _ => ("wasm test-browser", $"-v trace -b {host} --web-server-use-cop", false)
- };
+ string testCommand = hostRunner.GetTestCommand();
+ XHarnessArgsOptions options = new XHarnessArgsOptions(jsRelativePath, environmentLocale, host);
+ string xharnessArgs = s_isWindows ? hostRunner.GetXharnessArgsWindowsOS(options) : hostRunner.GetXharnessArgsOtherOS(options);
+ bool useWasmConsoleOutput = hostRunner.UseWasmConsoleOutput();
extraXHarnessArgs += " " + xharnessArgs;
@@ -336,7 +342,7 @@ protected static BuildArgs ExpandBuildArgs(BuildArgs buildArgs, string extraProp
if (buildArgs.AOT)
{
extraProperties = $"{extraProperties}\ntrue";
- extraProperties += $"\n{RuntimeInformation.IsOSPlatform(OSPlatform.Windows)}\n";
+ extraProperties += $"\n{s_isWindows}\n";
}
if (UseWebcil) {
@@ -429,7 +435,8 @@ protected static BuildArgs ExpandBuildArgs(BuildArgs buildArgs, string extraProp
options.MainJS ?? "test-main.js",
options.HasV8Script,
options.TargetFramework ?? DefaultTargetFramework,
- options.HasIcudt,
+ options.GlobalizationMode,
+ options.PredefinedIcudt ?? "",
options.DotnetWasmFromRuntimePack ?? !buildArgs.AOT,
UseWebcil);
}
@@ -633,7 +640,8 @@ protected static void AssertBasicAppBundle(string bundleDir,
string mainJS,
bool hasV8Script,
string targetFramework,
- bool hasIcudt = true,
+ GlobalizationMode? globalizationMode,
+ string predefinedIcudt = "",
bool dotnetWasmFromRuntimePack = true,
bool useWebcil = true)
{
@@ -648,7 +656,7 @@ protected static void AssertBasicAppBundle(string bundleDir,
});
AssertFilesExist(bundleDir, new[] { "run-v8.sh" }, expectToExist: hasV8Script);
- AssertFilesExist(bundleDir, new[] { "icudt.dat" }, expectToExist: hasIcudt);
+ AssertIcuAssets();
string managedDir = Path.Combine(bundleDir, "managed");
string bundledMainAppAssembly =
@@ -670,6 +678,53 @@ protected static void AssertBasicAppBundle(string bundleDir,
}
AssertDotNetWasmJs(bundleDir, fromRuntimePack: dotnetWasmFromRuntimePack, targetFramework);
+
+ void AssertIcuAssets()
+ {
+ bool expectEFIGS = false;
+ bool expectCJK = false;
+ bool expectNOCJK = false;
+ bool expectFULL = false;
+ switch (globalizationMode)
+ {
+ case GlobalizationMode.Invariant:
+ break;
+ case GlobalizationMode.FullIcu:
+ expectFULL = true;
+ break;
+ case GlobalizationMode.PredefinedIcu:
+ if (string.IsNullOrEmpty(predefinedIcudt))
+ throw new ArgumentException("WasmBuildTest is invalid, value for predefinedIcudt is required when GlobalizationMode=PredefinedIcu.");
+ AssertFilesExist(bundleDir, new[] { predefinedIcudt }, expectToExist: true);
+ // predefined ICU name can be identical with the icu files from runtime pack
+ switch (predefinedIcudt)
+ {
+ case "icudt.dat":
+ expectFULL = true;
+ break;
+ case "icudt_EFIGS.dat":
+ expectEFIGS = true;
+ break;
+ case "icudt_CJK.dat":
+ expectCJK = true;
+ break;
+ case "icudt_no_CJK.dat":
+ expectNOCJK = true;
+ break;
+ }
+ break;
+ default:
+ // icu shard chosen based on the locale
+ expectCJK = true;
+ expectEFIGS = true;
+ expectNOCJK = true;
+ break;
+ }
+ AssertFilesExist(bundleDir, new[] { "icudt.dat" }, expectToExist: expectFULL);
+ AssertFilesExist(bundleDir, new[] { "icudt_EFIGS.dat" }, expectToExist: expectEFIGS);
+ AssertFilesExist(bundleDir, new[] { "icudt_CJK.dat" }, expectToExist: expectCJK);
+ AssertFilesExist(bundleDir, new[] { "icudt_no_CJK.dat" }, expectToExist: expectNOCJK);
+ }
}
protected static void AssertDotNetWasmJs(string bundleDir, bool fromRuntimePack, string targetFramework)
@@ -1093,6 +1148,13 @@ public static int Main()
return 42;
}
}";
+
+ private IHostRunner GetHostRunnerFromRunHost(RunHost host) => host switch
+ {
+ RunHost.V8 => new V8HostRunner(),
+ RunHost.NodeJS => new NodeJSHostRunner(),
+ _ => new BrowserHostRunner(),
+ };
}
public record BuildArgs(string ProjectName,
@@ -1106,20 +1168,21 @@ internal record BuildPaths(string ObjWasmDir, string ObjDir, string BinDir, stri
public record BuildProjectOptions
(
- Action? InitProject = null,
- bool? DotnetWasmFromRuntimePack = null,
- bool HasIcudt = true,
- bool UseCache = true,
- bool ExpectSuccess = true,
- bool AssertAppBundle = true,
- bool CreateProject = true,
- bool Publish = true,
- bool BuildOnlyAfterPublish = true,
- bool HasV8Script = true,
- string? Verbosity = null,
- string? Label = null,
- string? TargetFramework = null,
- string? MainJS = null,
+ Action? InitProject = null,
+ bool? DotnetWasmFromRuntimePack = null,
+ GlobalizationMode? GlobalizationMode = null,
+ string? PredefinedIcudt = null,
+ bool UseCache = true,
+ bool ExpectSuccess = true,
+ bool AssertAppBundle = true,
+ bool CreateProject = true,
+ bool Publish = true,
+ bool BuildOnlyAfterPublish = true,
+ bool HasV8Script = true,
+ string? Verbosity = null,
+ string? Label = null,
+ string? TargetFramework = null,
+ string? MainJS = null,
IDictionary? ExtraBuildEnvironmentVariables = null
);
@@ -1132,5 +1195,12 @@ public record BlazorBuildOptions
bool WarnAsError = true
);
+ public enum GlobalizationMode
+ {
+ Invariant, // no icu
+ FullIcu, // full icu data: icudt.dat is loaded
+ PredefinedIcu // user set WasmIcuDataFileName value and we are loading that file
+ };
+
public enum NativeFilesType { FromRuntimePack, Relinked, AOT };
}
diff --git a/src/mono/wasm/Wasm.Build.Tests/HostRunner/BrowserHostRunner.cs b/src/mono/wasm/Wasm.Build.Tests/HostRunner/BrowserHostRunner.cs
new file mode 100644
index 00000000000000..288048616f6d7a
--- /dev/null
+++ b/src/mono/wasm/Wasm.Build.Tests/HostRunner/BrowserHostRunner.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+namespace Wasm.Build.Tests;
+
+public class BrowserHostRunner : IHostRunner
+{
+ public string GetTestCommand() => "wasm test-browser";
+ public string GetXharnessArgsWindowsOS(XHarnessArgsOptions options) => $"-v trace -b {options.host} --browser-arg=--lang={options.environmentLocale} --web-server-use-cop"; // Windows: chrome.exe --lang=locale
+ public string GetXharnessArgsOtherOS(XHarnessArgsOptions options) => $"-v trace -b {options.host} --locale={options.environmentLocale} --web-server-use-cop"; // Linux: LANGUAGE=locale ./chrome
+ public bool UseWasmConsoleOutput() => false;
+ public bool CanRunWBT() => true;
+}
diff --git a/src/mono/wasm/Wasm.Build.Tests/HostRunner/IHostRunner.cs b/src/mono/wasm/Wasm.Build.Tests/HostRunner/IHostRunner.cs
new file mode 100644
index 00000000000000..0c93a7310ded4a
--- /dev/null
+++ b/src/mono/wasm/Wasm.Build.Tests/HostRunner/IHostRunner.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+namespace Wasm.Build.Tests;
+
+public record XHarnessArgsOptions(string jsRelativePath, string environmentLocale, RunHost host);
+
+interface IHostRunner
+{
+ string GetTestCommand();
+ string GetXharnessArgsWindowsOS(XHarnessArgsOptions options);
+ string GetXharnessArgsOtherOS(XHarnessArgsOptions options);
+ bool UseWasmConsoleOutput();
+ bool CanRunWBT();
+}
diff --git a/src/mono/wasm/Wasm.Build.Tests/HostRunner/NodeJSHostRunner.cs b/src/mono/wasm/Wasm.Build.Tests/HostRunner/NodeJSHostRunner.cs
new file mode 100644
index 00000000000000..cf311557c27f2e
--- /dev/null
+++ b/src/mono/wasm/Wasm.Build.Tests/HostRunner/NodeJSHostRunner.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+namespace Wasm.Build.Tests;
+
+public class NodeJSHostRunner : IHostRunner
+{
+ public string GetTestCommand() => "wasm test";
+ public string GetXharnessArgsWindowsOS(XHarnessArgsOptions options) => $"--js-file={options.jsRelativePath} --engine=NodeJS -v trace";
+ public string GetXharnessArgsOtherOS(XHarnessArgsOptions options) => $"--js-file={options.jsRelativePath} --engine=NodeJS -v trace --locale={options.environmentLocale}";
+ public bool UseWasmConsoleOutput() => true;
+ public bool CanRunWBT() => true;
+}
diff --git a/src/mono/wasm/Wasm.Build.Tests/HostRunner/V8HostRunner.cs b/src/mono/wasm/Wasm.Build.Tests/HostRunner/V8HostRunner.cs
new file mode 100644
index 00000000000000..885aba29ad4634
--- /dev/null
+++ b/src/mono/wasm/Wasm.Build.Tests/HostRunner/V8HostRunner.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+namespace Wasm.Build.Tests;
+
+using System.Runtime.InteropServices;
+
+public class V8HostRunner : IHostRunner
+{
+ private string GetXharnessArgs(string jsRelativePath) => $"--js-file={jsRelativePath} --engine=V8 -v trace";
+
+ public string GetTestCommand() => "wasm test";
+ public string GetXharnessArgsWindowsOS(XHarnessArgsOptions options) => GetXharnessArgs(options.jsRelativePath);
+ public string GetXharnessArgsOtherOS(XHarnessArgsOptions options) => GetXharnessArgs(options.jsRelativePath);
+ public bool UseWasmConsoleOutput() => true;
+ public bool CanRunWBT() => !RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+}
diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs
new file mode 100644
index 00000000000000..19a4ac63434683
--- /dev/null
+++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs
@@ -0,0 +1,276 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.IO;
+using Xunit;
+using Xunit.Abstractions;
+using Xunit.Sdk;
+using System.Collections.Generic;
+
+#nullable enable
+
+namespace Wasm.Build.Tests;
+
+public class IcuShardingTests : BuildTestBase
+{
+ public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext)
+ : base(output, buildContext) { }
+
+ // custom file contains only locales "cy-GB", "is-IS", "bs-BA", "lb-LU" and fallback locale: "en-US":
+ private static string s_customIcuPath = Path.Combine(BuildEnvironment.TestAssetsPath, "icudt_custom.dat");
+ public record SundayNames {
+ public static string English = "Sunday";
+ public static string French = "dimanche";
+ public static string Spanish = "domingo";
+ public static string Chinese = "星期日";
+ public static string Japanese = "日曜日";
+ public static string Slovak = "nedeľa";
+ }
+
+ private const string FallbackSundayNameEnUS = "Sunday";
+
+ private static readonly string s_customIcuTestedLocales = $@"new Locale[] {{
+ new Locale(""cy-GB"", ""Dydd Sul""), new Locale(""is-IS"", ""sunnudagur""), new Locale(""bs-BA"", ""nedjelja""), new Locale(""lb-LU"", ""Sonndeg""),
+ new Locale(""fr-FR"", null), new Locale(""hr-HR"", null), new Locale(""ko-KR"", null)
+ }}";
+ private static string GetEfigsTestedLocales(string fallbackSundayName=FallbackSundayNameEnUS) => $@"new Locale[] {{
+ new Locale(""en-US"", ""{SundayNames.English}""), new Locale(""fr-FR"", ""{SundayNames.French}""), new Locale(""es-ES"", ""{SundayNames.Spanish}""),
+ new Locale(""pl-PL"", ""{fallbackSundayName}""), new Locale(""ko-KR"", ""{fallbackSundayName}""), new Locale(""cs-CZ"", ""{fallbackSundayName}"")
+ }}";
+ private static string GetCjkTestedLocales(string fallbackSundayName=FallbackSundayNameEnUS) => $@"new Locale[] {{
+ new Locale(""en-GB"", ""{SundayNames.English}""), new Locale(""zh-CN"", ""{SundayNames.Chinese}""), new Locale(""ja-JP"", ""{SundayNames.Japanese}""),
+ new Locale(""fr-FR"", ""{fallbackSundayName}""), new Locale(""hr-HR"", ""{fallbackSundayName}""), new Locale(""it-IT"", ""{fallbackSundayName}"")
+ }}";
+ private static string GetNocjkTestedLocales(string fallbackSundayName=FallbackSundayNameEnUS) => $@"new Locale[] {{
+ new Locale(""en-AU"", ""{SundayNames.English}""), new Locale(""fr-FR"", ""{SundayNames.French}""), new Locale(""sk-SK"", ""{SundayNames.Slovak}""),
+ new Locale(""ja-JP"", ""{fallbackSundayName}""), new Locale(""ko-KR"", ""{fallbackSundayName}""), new Locale(""zh-CN"", ""{fallbackSundayName}"")
+ }}";
+ private static readonly string s_fullIcuTestedLocales = $@"new Locale[] {{
+ new Locale(""en-GB"", ""{SundayNames.English}""), new Locale(""sk-SK"", ""{SundayNames.Slovak}""), new Locale(""zh-CN"", ""{SundayNames.Chinese}"")
+ }}";
+
+ public static IEnumerable