dotnet/runtime · error · Error
Locale or culture name is null or empty. localeName=
Error message
Locale or culture name is null or empty. localeName=${localeName}, cultureName=${cultureName} What it means
SystemJS_GetLocaleInfo is the interop entry the runtime calls to resolve locale display names via Intl.DisplayNames. After normalizeLocale() runs on both the locale and culture strings, if either result is falsy this error is thrown. normalizeLocale() returns undefined for empty input or for strings Intl.getCanonicalLocales() rejects (e.g. 'xx-u-xx' handled only as a fallback earlier).
Solutions
- Ensure the .NET app sets an explicit, valid culture (e.g. new CultureInfo("en-US")) rather than relying on invariant/empty defaults before code that triggers locale lookup runs.
- If running under Node.js, use a full-icu build (npm i full-icu) so Intl.getCanonicalLocales and DisplayNames resolve real locales.
- Check that user-supplied locale strings from headers/URLs are validated against a known list before being assigned to CurrentCulture/CurrentUICulture.
- If a genuinely non-standard locale is expected, pass it in a form normalizeLocale can canonicalize (BCP-47 language-region), not an empty string.
Example fix
// before
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; // empty name path
// after
CultureInfo.CurrentCulture = new CultureInfo("en-US"); Defensive patterns
Strategy: validation
Validate before calling
// Validate a locale string before the runtime uses it for display-name lookup.
function isValidLocaleName(s: string | undefined | null): s is string {
if (!s || !s.trim()) return false;
try {
const c = (Intl as any).getCanonicalLocales(s.replace("_", "-"));
return Array.isArray(c) && c.length > 0;
} catch {
return false;
}
}
if (!isValidLocaleName(userLocale)) {
userLocale = "en-US"; // safe fallback
} Type guard
function isNonEmptyLocale(s: any): s is string {
return typeof s === "string" && s.trim().length > 0;
} Try / catch
null
Prevention
- Set explicit, valid CultureInfo values rather than invariant/empty defaults.
- Under Node, use a full-icu build so Intl can resolve real locales.
- Validate any user/header/URL-sourced locale against a known BCP-47 list before assigning it.
- Avoid passing private-use or empty locale strings to interop that triggers locale lookup.
When it happens
Trigger: The .NET runtime passes a locale or culture string that is empty, whitespace-only, or so malformed that Intl.getCanonicalLocales() throws and the catch returns undefined for both. The malformed-locale forwarding at lines 13-18 only triggers when localeNameOriginal is truthy; if the original was empty the guard at line 22 fires.
Common situations: Thread.CurrentCulture or CurrentUICulture set to a CultureInvariant/empty culture on the server then serialized to the WASM client; a locale string arriving as '' from configuration; ICU/globalization data not loaded so the runtime resolves to an invariant culture; headless/Node host where Intl data is incomplete.
Related errors
- Locale info for locale=
- Locale info for locale=
- Locale info for locale=
- Locale info for locale=
- Locale or culture name is null or empty. localeName=
AI-assisted analysis of dotnet/runtime@60108ba66e (2026-08-10).
Data as JSON: /api/errors/a8d98c28ea12a30a.
Report an issue: GitHub.
Appendix: source
Thrown at src/native/libs/System.Native.Browser/native/globalization-locale.ts:23
import { _ems_ } from "../../Common/JavaScript/ems-ambient";
// char16_t* SystemJS_GetLocaleInfo (const uint16_t* locale, int32_t localeLength, const uint16_t* culture, int32_t cultureLength, const uint16_t* result, int32_t resultMaxLength, int *resultLength);
export function SystemJS_GetLocaleInfo(culture: number, cultureLength: number, locale: number, localeLength: number, dst: number, dstMaxLength: number, dstLength: Int32Ptr): VoidPtr {
const OUTER_SEPARATOR = "##";
try {
const localeNameOriginal = _ems_.dotnetBrowserUtilsExports.utf16ToString(<any>locale, <any>(locale + 2 * localeLength));
const localeName = normalizeLocale(localeNameOriginal);
if (!localeName && localeNameOriginal) {
// handle non-standard or malformed locales by forwarding the locale code
_ems_.dotnetBrowserUtilsExports.stringToUTF16(dst, dst + 2 * localeNameOriginal.length, localeNameOriginal);
_ems_.dotnetApi.setHeapI32(dstLength, localeNameOriginal.length);
return 0 as any;
}
const cultureNameOriginal = _ems_.dotnetBrowserUtilsExports.utf16ToString(<any>culture, <any>(culture + 2 * cultureLength));
const cultureName = normalizeLocale(cultureNameOriginal);
if (!localeName || !cultureName)
throw new Error(`Locale or culture name is null or empty. localeName=${localeName}, cultureName=${cultureName}`);
const localeParts = localeName.split("-");
// cultureName can be in a form of:
// 1) "language", e.g. "zh"
// 2) "language-region", e.g. "zn-CN"
// 3) "language-script-region", e.g. "zh-Hans-CN"
// 4) "language-script", e.g. "zh-Hans" (served in the catch block below)
let languageName, regionName;
try {
const region = localeParts.length > 1 ? localeParts.pop() : undefined;
// this line might fail if form 4 from the comment above is used:
regionName = region ? new Intl.DisplayNames([cultureName], { type: "region" }).of(region) : undefined;
const language = localeParts.join("-");
languageName = new Intl.DisplayNames([cultureName], { type: "language" }).of(language);
} catch (error) {
if (error instanceof RangeError) {
// if it failed from this reason then cultureName is in a form "language-script", without region
try {View on GitHub (pinned to 60108ba66e)