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

  1. 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.
  2. If running under Node.js, use a full-icu build (npm i full-icu) so Intl.getCanonicalLocales and DisplayNames resolve real locales.
  3. Check that user-supplied locale strings from headers/URLs are validated against a known list before being assigned to CurrentCulture/CurrentUICulture.
  4. 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

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


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)