[browser][coreCLR] set "LANG" env variable - #129221

Merged
pavelsavara merged 2 commits into
dotnet:mainfrom
pavelsavara:browser_lang_fix
Jun 14, 2026
Merged

[browser][coreCLR] set "LANG" env variable#129221
pavelsavara merged 2 commits into
dotnet:mainfrom
pavelsavara:browser_lang_fix

Conversation

@pavelsavara

@pavelsavarapavelsavara commented Jun 10, 2026

Copy link
Copy Markdown
Member

Summary

The CoreCLR WebAssembly browser loader used applicationCultureonly to select the ICU shard in getIcuResourceName(). It never propagated the culture into the runtime's LANG environment variable. As a result, the runtime's default locale (InstalledUICulture / UserDefaultCulture) stayed at the loader/Emscripten default (en_US.UTF-8) even when the app requested a different applicationCulture.

This change sets LANG = "<applicationCulture>.UTF-8" in the loader, mirroring what Mono already does in src/mono/browser/runtime/loader/config.ts.

Problem

On CoreCLR-WASM the runtime default culture is driven by LANG (via uloc_getDefault()getenv("LANG") in pal_locale.c), not by applicationCulture. With LANG left at the default, a Blazor WebAssembly app started with a non-default culture (e.g. Blazor.start({ webAssembly: { applicationCulture: "fr-FR" } }) from ?culture=fr-FR) failed:

InvalidOperationException: Blazor detected a change in the application's culture
that is not supported with the current project configuration...

This is thrown by WebAssemblyCultureProvider.ThrowIfCultureChangeIsUnsupported when sharded ICU is in use (__BLAZOR_SHARDED_ICU == "1", the default) andCultureInfo.CurrentCulture != InitialCulture. InitialCulture came from the configured applicationCulture (fr-FR), but CurrentCulture fell back to the runtime default (en-US) because LANG was never applied.

Fix

In src/native/libs/Common/JavaScript/loader/icu.ts, after finalizing loaderConfig.applicationCulture, set:

if(culture&&loaderConfig.environmentVariables!["LANG"]===undefined){loaderConfig.environmentVariables!["LANG"]=`${culture}.UTF-8`;}

The === undefined guard is conservative: it only sets LANG when the culture is known and the user has not already supplied their own LANG (e.g. via dotnet.withEnvironmentVariable("LANG", ...)), so explicit user configuration is never overridden.

The host applies env vars to the Emscripten ENV in host/index.ts::setupEmscripten, which runs during dotnetInitializeModuleaftergetIcuResourceName() is called in loader/run.ts, so the ordering is correct.

Behavior change (A/B with ?culture=fr-FR)

Before (applicationCulture=fr-FR only):

  • getenv("LANG") = en_US.UTF-8 (default)
  • InstalledUICulture / UserDefaultCulture = en-US
  • CurrentCulture falls back to en-USInitialCulture (fr-FR) → Blazor throws, page fails to render.

After (LANG=fr-FR.UTF-8):

  • getenv("LANG") = fr-FR.UTF-8
  • InstalledUICulture / UserDefaultCulture = fr-FR
  • CurrentCulture == InitialCulture (fr-FR) → no throw, page renders.

This eliminates the Blazor culture-change exception and makes the CoreCLR-WASM runtime default culture match applicationCulture, exactly mirroring Mono.

How satellite assemblies are loaded

In short: Blazor (LoadCurrentCultureResourcesAsync) → INTERNAL.loadSatelliteAssembliesfetchSatelliteAssembliesfetchAssembly/registerDllBytes (download + copy into WASM heap) → external_assembly_probe/BrowserHost_ExternalAssemblyProbe (runtime resolves from memory). This PR only ensures the culture used in step 1 is correct; the download path itself is unchanged.

Related dotnet/aspnetcore#66331

@pavelsavarapavelsavara added this to the 11.0.0 milestone Jun 10, 2026
@pavelsavarapavelsavara self-assigned this Jun 10, 2026
CopilotAI review requested due to automatic review settings June 10, 2026 08:19
@pavelsavarapavelsavara added arch-wasm WebAssembly architecture area-System.Globalization os-browser Browser variant of arch-wasm labels Jun 10, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Common JavaScript loader’s ICU selection path to also initialize the runtime locale environment by setting LANG based on the resolved application culture (explicit applicationCulture or browser/Intl-derived locale). This helps align CoreCLR browser runs with the expected locale-based globalization behavior.

Changes:

  • When a non-invariant globalization mode is used and a culture is resolved, set loaderConfig.environmentVariables["LANG"] to <culture>.UTF-8 while computing the ICU resource to load.

Comment threadsrc/native/libs/Common/JavaScript/loader/icu.ts Outdated

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.

Comment threadsrc/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@pavelsavara

Copy link
Copy Markdown
MemberAuthor

/ba-g unrelated failures

@pavelsavara
pavelsavara merged commit a1e8100 into dotnet:mainJun 14, 2026
135 of 144 checks passed
@pavelsavara
pavelsavara deleted the browser_lang_fix branch June 14, 2026 08:59
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 18, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-wasmWebAssembly architecturearea-System.Globalizationos-browserBrowser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@pavelsavara@jkotas@maraf
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

[browser][coreCLR] set "LANG" env variable - #129221

Merged
pavelsavara merged 2 commits into
dotnet:mainfrom
pavelsavara:browser_lang_fix
Jun 14, 2026
Merged

[browser][coreCLR] set "LANG" env variable#129221
pavelsavara merged 2 commits into
dotnet:mainfrom
pavelsavara:browser_lang_fix

Conversation

@pavelsavara

@pavelsavarapavelsavara commented Jun 10, 2026

Copy link
Copy Markdown
Member

Summary

The CoreCLR WebAssembly browser loader used applicationCultureonly to select the ICU shard in getIcuResourceName(). It never propagated the culture into the runtime's LANG environment variable. As a result, the runtime's default locale (InstalledUICulture / UserDefaultCulture) stayed at the loader/Emscripten default (en_US.UTF-8) even when the app requested a different applicationCulture.

This change sets LANG = "<applicationCulture>.UTF-8" in the loader, mirroring what Mono already does in src/mono/browser/runtime/loader/config.ts.

Problem

On CoreCLR-WASM the runtime default culture is driven by LANG (via uloc_getDefault()getenv("LANG") in pal_locale.c), not by applicationCulture. With LANG left at the default, a Blazor WebAssembly app started with a non-default culture (e.g. Blazor.start({ webAssembly: { applicationCulture: "fr-FR" } }) from ?culture=fr-FR) failed:

InvalidOperationException: Blazor detected a change in the application's culture
that is not supported with the current project configuration...

This is thrown by WebAssemblyCultureProvider.ThrowIfCultureChangeIsUnsupported when sharded ICU is in use (__BLAZOR_SHARDED_ICU == "1", the default) andCultureInfo.CurrentCulture != InitialCulture. InitialCulture came from the configured applicationCulture (fr-FR), but CurrentCulture fell back to the runtime default (en-US) because LANG was never applied.

Fix

In src/native/libs/Common/JavaScript/loader/icu.ts, after finalizing loaderConfig.applicationCulture, set:

if(culture&&loaderConfig.environmentVariables!["LANG"]===undefined){loaderConfig.environmentVariables!["LANG"]=`${culture}.UTF-8`;}

The === undefined guard is conservative: it only sets LANG when the culture is known and the user has not already supplied their own LANG (e.g. via dotnet.withEnvironmentVariable("LANG", ...)), so explicit user configuration is never overridden.

The host applies env vars to the Emscripten ENV in host/index.ts::setupEmscripten, which runs during dotnetInitializeModuleaftergetIcuResourceName() is called in loader/run.ts, so the ordering is correct.

Behavior change (A/B with ?culture=fr-FR)

Before (applicationCulture=fr-FR only):

  • getenv("LANG") = en_US.UTF-8 (default)
  • InstalledUICulture / UserDefaultCulture = en-US
  • CurrentCulture falls back to en-USInitialCulture (fr-FR) → Blazor throws, page fails to render.

After (LANG=fr-FR.UTF-8):

  • getenv("LANG") = fr-FR.UTF-8
  • InstalledUICulture / UserDefaultCulture = fr-FR
  • CurrentCulture == InitialCulture (fr-FR) → no throw, page renders.

This eliminates the Blazor culture-change exception and makes the CoreCLR-WASM runtime default culture match applicationCulture, exactly mirroring Mono.

How satellite assemblies are loaded

In short: Blazor (LoadCurrentCultureResourcesAsync) → INTERNAL.loadSatelliteAssembliesfetchSatelliteAssembliesfetchAssembly/registerDllBytes (download + copy into WASM heap) → external_assembly_probe/BrowserHost_ExternalAssemblyProbe (runtime resolves from memory). This PR only ensures the culture used in step 1 is correct; the download path itself is unchanged.

Related dotnet/aspnetcore#66331

@pavelsavarapavelsavara added this to the 11.0.0 milestone Jun 10, 2026
@pavelsavarapavelsavara self-assigned this Jun 10, 2026
CopilotAI review requested due to automatic review settings June 10, 2026 08:19
@pavelsavarapavelsavara added arch-wasm WebAssembly architecture area-System.Globalization os-browser Browser variant of arch-wasm labels Jun 10, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Common JavaScript loader’s ICU selection path to also initialize the runtime locale environment by setting LANG based on the resolved application culture (explicit applicationCulture or browser/Intl-derived locale). This helps align CoreCLR browser runs with the expected locale-based globalization behavior.

Changes:

  • When a non-invariant globalization mode is used and a culture is resolved, set loaderConfig.environmentVariables["LANG"] to <culture>.UTF-8 while computing the ICU resource to load.

Comment threadsrc/native/libs/Common/JavaScript/loader/icu.ts Outdated

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.

Comment threadsrc/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@pavelsavara

Copy link
Copy Markdown
MemberAuthor

/ba-g unrelated failures

@pavelsavara
pavelsavara merged commit a1e8100 into dotnet:mainJun 14, 2026
135 of 144 checks passed
@pavelsavara
pavelsavara deleted the browser_lang_fix branch June 14, 2026 08:59
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 18, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-wasmWebAssembly architecturearea-System.Globalizationos-browserBrowser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@pavelsavara@jkotas@maraf
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[browser][coreCLR] set "LANG" env variable - #129221

Merged
pavelsavara merged 2 commits into
dotnet:mainfrom
pavelsavara:browser_lang_fix
Jun 14, 2026
Merged

[browser][coreCLR] set "LANG" env variable#129221
pavelsavara merged 2 commits into
dotnet:mainfrom
pavelsavara:browser_lang_fix

Conversation

@pavelsavara

@pavelsavarapavelsavara commented Jun 10, 2026

Copy link
Copy Markdown
Member

Summary

The CoreCLR WebAssembly browser loader used applicationCultureonly to select the ICU shard in getIcuResourceName(). It never propagated the culture into the runtime's LANG environment variable. As a result, the runtime's default locale (InstalledUICulture / UserDefaultCulture) stayed at the loader/Emscripten default (en_US.UTF-8) even when the app requested a different applicationCulture.

This change sets LANG = "<applicationCulture>.UTF-8" in the loader, mirroring what Mono already does in src/mono/browser/runtime/loader/config.ts.

Problem

On CoreCLR-WASM the runtime default culture is driven by LANG (via uloc_getDefault()getenv("LANG") in pal_locale.c), not by applicationCulture. With LANG left at the default, a Blazor WebAssembly app started with a non-default culture (e.g. Blazor.start({ webAssembly: { applicationCulture: "fr-FR" } }) from ?culture=fr-FR) failed:

InvalidOperationException: Blazor detected a change in the application's culture
that is not supported with the current project configuration...

This is thrown by WebAssemblyCultureProvider.ThrowIfCultureChangeIsUnsupported when sharded ICU is in use (__BLAZOR_SHARDED_ICU == "1", the default) andCultureInfo.CurrentCulture != InitialCulture. InitialCulture came from the configured applicationCulture (fr-FR), but CurrentCulture fell back to the runtime default (en-US) because LANG was never applied.

Fix

In src/native/libs/Common/JavaScript/loader/icu.ts, after finalizing loaderConfig.applicationCulture, set:

if(culture&&loaderConfig.environmentVariables!["LANG"]===undefined){loaderConfig.environmentVariables!["LANG"]=`${culture}.UTF-8`;}

The === undefined guard is conservative: it only sets LANG when the culture is known and the user has not already supplied their own LANG (e.g. via dotnet.withEnvironmentVariable("LANG", ...)), so explicit user configuration is never overridden.

The host applies env vars to the Emscripten ENV in host/index.ts::setupEmscripten, which runs during dotnetInitializeModuleaftergetIcuResourceName() is called in loader/run.ts, so the ordering is correct.

Behavior change (A/B with ?culture=fr-FR)

Before (applicationCulture=fr-FR only):

  • getenv("LANG") = en_US.UTF-8 (default)
  • InstalledUICulture / UserDefaultCulture = en-US
  • CurrentCulture falls back to en-USInitialCulture (fr-FR) → Blazor throws, page fails to render.

After (LANG=fr-FR.UTF-8):

  • getenv("LANG") = fr-FR.UTF-8
  • InstalledUICulture / UserDefaultCulture = fr-FR
  • CurrentCulture == InitialCulture (fr-FR) → no throw, page renders.

This eliminates the Blazor culture-change exception and makes the CoreCLR-WASM runtime default culture match applicationCulture, exactly mirroring Mono.

How satellite assemblies are loaded

In short: Blazor (LoadCurrentCultureResourcesAsync) → INTERNAL.loadSatelliteAssembliesfetchSatelliteAssembliesfetchAssembly/registerDllBytes (download + copy into WASM heap) → external_assembly_probe/BrowserHost_ExternalAssemblyProbe (runtime resolves from memory). This PR only ensures the culture used in step 1 is correct; the download path itself is unchanged.

Related dotnet/aspnetcore#66331

@pavelsavarapavelsavara added this to the 11.0.0 milestone Jun 10, 2026
@pavelsavarapavelsavara self-assigned this Jun 10, 2026
CopilotAI review requested due to automatic review settings June 10, 2026 08:19
@pavelsavarapavelsavara added arch-wasm WebAssembly architecture area-System.Globalization os-browser Browser variant of arch-wasm labels Jun 10, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Common JavaScript loader’s ICU selection path to also initialize the runtime locale environment by setting LANG based on the resolved application culture (explicit applicationCulture or browser/Intl-derived locale). This helps align CoreCLR browser runs with the expected locale-based globalization behavior.

Changes:

  • When a non-invariant globalization mode is used and a culture is resolved, set loaderConfig.environmentVariables["LANG"] to <culture>.UTF-8 while computing the ICU resource to load.

Comment threadsrc/native/libs/Common/JavaScript/loader/icu.ts Outdated

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.

Comment threadsrc/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@pavelsavara

Copy link
Copy Markdown
MemberAuthor

/ba-g unrelated failures

@pavelsavara
pavelsavara merged commit a1e8100 into dotnet:mainJun 14, 2026
135 of 144 checks passed
@pavelsavara
pavelsavara deleted the browser_lang_fix branch June 14, 2026 08:59
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 18, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-wasmWebAssembly architecturearea-System.Globalizationos-browserBrowser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@pavelsavara@jkotas@maraf
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[browser][coreCLR] set "LANG" env variable - #129221

Merged
pavelsavara merged 2 commits into
dotnet:mainfrom
pavelsavara:browser_lang_fix
Jun 14, 2026
Merged

[browser][coreCLR] set "LANG" env variable#129221
pavelsavara merged 2 commits into
dotnet:mainfrom
pavelsavara:browser_lang_fix

Conversation

@pavelsavara

@pavelsavarapavelsavara commented Jun 10, 2026

Copy link
Copy Markdown
Member

Summary

The CoreCLR WebAssembly browser loader used applicationCultureonly to select the ICU shard in getIcuResourceName(). It never propagated the culture into the runtime's LANG environment variable. As a result, the runtime's default locale (InstalledUICulture / UserDefaultCulture) stayed at the loader/Emscripten default (en_US.UTF-8) even when the app requested a different applicationCulture.

This change sets LANG = "<applicationCulture>.UTF-8" in the loader, mirroring what Mono already does in src/mono/browser/runtime/loader/config.ts.

Problem

On CoreCLR-WASM the runtime default culture is driven by LANG (via uloc_getDefault()getenv("LANG") in pal_locale.c), not by applicationCulture. With LANG left at the default, a Blazor WebAssembly app started with a non-default culture (e.g. Blazor.start({ webAssembly: { applicationCulture: "fr-FR" } }) from ?culture=fr-FR) failed:

InvalidOperationException: Blazor detected a change in the application's culture
that is not supported with the current project configuration...

This is thrown by WebAssemblyCultureProvider.ThrowIfCultureChangeIsUnsupported when sharded ICU is in use (__BLAZOR_SHARDED_ICU == "1", the default) andCultureInfo.CurrentCulture != InitialCulture. InitialCulture came from the configured applicationCulture (fr-FR), but CurrentCulture fell back to the runtime default (en-US) because LANG was never applied.

Fix

In src/native/libs/Common/JavaScript/loader/icu.ts, after finalizing loaderConfig.applicationCulture, set:

if(culture&&loaderConfig.environmentVariables!["LANG"]===undefined){loaderConfig.environmentVariables!["LANG"]=`${culture}.UTF-8`;}

The === undefined guard is conservative: it only sets LANG when the culture is known and the user has not already supplied their own LANG (e.g. via dotnet.withEnvironmentVariable("LANG", ...)), so explicit user configuration is never overridden.

The host applies env vars to the Emscripten ENV in host/index.ts::setupEmscripten, which runs during dotnetInitializeModuleaftergetIcuResourceName() is called in loader/run.ts, so the ordering is correct.

Behavior change (A/B with ?culture=fr-FR)

Before (applicationCulture=fr-FR only):

  • getenv("LANG") = en_US.UTF-8 (default)
  • InstalledUICulture / UserDefaultCulture = en-US
  • CurrentCulture falls back to en-USInitialCulture (fr-FR) → Blazor throws, page fails to render.

After (LANG=fr-FR.UTF-8):

  • getenv("LANG") = fr-FR.UTF-8
  • InstalledUICulture / UserDefaultCulture = fr-FR
  • CurrentCulture == InitialCulture (fr-FR) → no throw, page renders.

This eliminates the Blazor culture-change exception and makes the CoreCLR-WASM runtime default culture match applicationCulture, exactly mirroring Mono.

How satellite assemblies are loaded

In short: Blazor (LoadCurrentCultureResourcesAsync) → INTERNAL.loadSatelliteAssembliesfetchSatelliteAssembliesfetchAssembly/registerDllBytes (download + copy into WASM heap) → external_assembly_probe/BrowserHost_ExternalAssemblyProbe (runtime resolves from memory). This PR only ensures the culture used in step 1 is correct; the download path itself is unchanged.

Related dotnet/aspnetcore#66331

@pavelsavarapavelsavara added this to the 11.0.0 milestone Jun 10, 2026
@pavelsavarapavelsavara self-assigned this Jun 10, 2026
CopilotAI review requested due to automatic review settings June 10, 2026 08:19
@pavelsavarapavelsavara added arch-wasm WebAssembly architecture area-System.Globalization os-browser Browser variant of arch-wasm labels Jun 10, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Common JavaScript loader’s ICU selection path to also initialize the runtime locale environment by setting LANG based on the resolved application culture (explicit applicationCulture or browser/Intl-derived locale). This helps align CoreCLR browser runs with the expected locale-based globalization behavior.

Changes:

  • When a non-invariant globalization mode is used and a culture is resolved, set loaderConfig.environmentVariables["LANG"] to <culture>.UTF-8 while computing the ICU resource to load.

Comment threadsrc/native/libs/Common/JavaScript/loader/icu.ts Outdated

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.

Comment threadsrc/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@pavelsavara

Copy link
Copy Markdown
MemberAuthor

/ba-g unrelated failures

@pavelsavara
pavelsavara merged commit a1e8100 into dotnet:mainJun 14, 2026
135 of 144 checks passed
@pavelsavara
pavelsavara deleted the browser_lang_fix branch June 14, 2026 08:59
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 18, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-wasmWebAssembly architecturearea-System.Globalizationos-browserBrowser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@pavelsavara@jkotas@maraf
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

[browser][coreCLR] set "LANG" env variable - #129221

Merged
pavelsavara merged 2 commits into
dotnet:mainfrom
pavelsavara:browser_lang_fix
Jun 14, 2026
Merged

[browser][coreCLR] set "LANG" env variable#129221
pavelsavara merged 2 commits into
dotnet:mainfrom
pavelsavara:browser_lang_fix

Conversation

@pavelsavara

@pavelsavarapavelsavara commented Jun 10, 2026

Copy link
Copy Markdown
Member

Summary

The CoreCLR WebAssembly browser loader used applicationCultureonly to select the ICU shard in getIcuResourceName(). It never propagated the culture into the runtime's LANG environment variable. As a result, the runtime's default locale (InstalledUICulture / UserDefaultCulture) stayed at the loader/Emscripten default (en_US.UTF-8) even when the app requested a different applicationCulture.

This change sets LANG = "<applicationCulture>.UTF-8" in the loader, mirroring what Mono already does in src/mono/browser/runtime/loader/config.ts.

Problem

On CoreCLR-WASM the runtime default culture is driven by LANG (via uloc_getDefault()getenv("LANG") in pal_locale.c), not by applicationCulture. With LANG left at the default, a Blazor WebAssembly app started with a non-default culture (e.g. Blazor.start({ webAssembly: { applicationCulture: "fr-FR" } }) from ?culture=fr-FR) failed:

InvalidOperationException: Blazor detected a change in the application's culture
that is not supported with the current project configuration...

This is thrown by WebAssemblyCultureProvider.ThrowIfCultureChangeIsUnsupported when sharded ICU is in use (__BLAZOR_SHARDED_ICU == "1", the default) andCultureInfo.CurrentCulture != InitialCulture. InitialCulture came from the configured applicationCulture (fr-FR), but CurrentCulture fell back to the runtime default (en-US) because LANG was never applied.

Fix

In src/native/libs/Common/JavaScript/loader/icu.ts, after finalizing loaderConfig.applicationCulture, set:

if(culture&&loaderConfig.environmentVariables!["LANG"]===undefined){loaderConfig.environmentVariables!["LANG"]=`${culture}.UTF-8`;}

The === undefined guard is conservative: it only sets LANG when the culture is known and the user has not already supplied their own LANG (e.g. via dotnet.withEnvironmentVariable("LANG", ...)), so explicit user configuration is never overridden.

The host applies env vars to the Emscripten ENV in host/index.ts::setupEmscripten, which runs during dotnetInitializeModuleaftergetIcuResourceName() is called in loader/run.ts, so the ordering is correct.

Behavior change (A/B with ?culture=fr-FR)

Before (applicationCulture=fr-FR only):

  • getenv("LANG") = en_US.UTF-8 (default)
  • InstalledUICulture / UserDefaultCulture = en-US
  • CurrentCulture falls back to en-USInitialCulture (fr-FR) → Blazor throws, page fails to render.

After (LANG=fr-FR.UTF-8):

  • getenv("LANG") = fr-FR.UTF-8
  • InstalledUICulture / UserDefaultCulture = fr-FR
  • CurrentCulture == InitialCulture (fr-FR) → no throw, page renders.

This eliminates the Blazor culture-change exception and makes the CoreCLR-WASM runtime default culture match applicationCulture, exactly mirroring Mono.

How satellite assemblies are loaded

In short: Blazor (LoadCurrentCultureResourcesAsync) → INTERNAL.loadSatelliteAssembliesfetchSatelliteAssembliesfetchAssembly/registerDllBytes (download + copy into WASM heap) → external_assembly_probe/BrowserHost_ExternalAssemblyProbe (runtime resolves from memory). This PR only ensures the culture used in step 1 is correct; the download path itself is unchanged.

Related dotnet/aspnetcore#66331

@pavelsavarapavelsavara added this to the 11.0.0 milestone Jun 10, 2026
@pavelsavarapavelsavara self-assigned this Jun 10, 2026
CopilotAI review requested due to automatic review settings June 10, 2026 08:19
@pavelsavarapavelsavara added arch-wasm WebAssembly architecture area-System.Globalization os-browser Browser variant of arch-wasm labels Jun 10, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Common JavaScript loader’s ICU selection path to also initialize the runtime locale environment by setting LANG based on the resolved application culture (explicit applicationCulture or browser/Intl-derived locale). This helps align CoreCLR browser runs with the expected locale-based globalization behavior.

Changes:

  • When a non-invariant globalization mode is used and a culture is resolved, set loaderConfig.environmentVariables["LANG"] to <culture>.UTF-8 while computing the ICU resource to load.

Comment threadsrc/native/libs/Common/JavaScript/loader/icu.ts Outdated

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.

Comment threadsrc/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@pavelsavara

Copy link
Copy Markdown
MemberAuthor

/ba-g unrelated failures

@pavelsavara
pavelsavara merged commit a1e8100 into dotnet:mainJun 14, 2026
135 of 144 checks passed
@pavelsavara
pavelsavara deleted the browser_lang_fix branch June 14, 2026 08:59
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 18, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-wasmWebAssembly architecturearea-System.Globalizationos-browserBrowser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@pavelsavara@jkotas@maraf
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[browser][coreCLR] set "LANG" env variable - #129221

Merged
pavelsavara merged 2 commits into
dotnet:mainfrom
pavelsavara:browser_lang_fix
Jun 14, 2026
Merged

[browser][coreCLR] set "LANG" env variable#129221
pavelsavara merged 2 commits into
dotnet:mainfrom
pavelsavara:browser_lang_fix

Conversation

@pavelsavara

@pavelsavarapavelsavara commented Jun 10, 2026

Copy link
Copy Markdown
Member

Summary

The CoreCLR WebAssembly browser loader used applicationCultureonly to select the ICU shard in getIcuResourceName(). It never propagated the culture into the runtime's LANG environment variable. As a result, the runtime's default locale (InstalledUICulture / UserDefaultCulture) stayed at the loader/Emscripten default (en_US.UTF-8) even when the app requested a different applicationCulture.

This change sets LANG = "<applicationCulture>.UTF-8" in the loader, mirroring what Mono already does in src/mono/browser/runtime/loader/config.ts.

Problem

On CoreCLR-WASM the runtime default culture is driven by LANG (via uloc_getDefault()getenv("LANG") in pal_locale.c), not by applicationCulture. With LANG left at the default, a Blazor WebAssembly app started with a non-default culture (e.g. Blazor.start({ webAssembly: { applicationCulture: "fr-FR" } }) from ?culture=fr-FR) failed:

InvalidOperationException: Blazor detected a change in the application's culture
that is not supported with the current project configuration...

This is thrown by WebAssemblyCultureProvider.ThrowIfCultureChangeIsUnsupported when sharded ICU is in use (__BLAZOR_SHARDED_ICU == "1", the default) andCultureInfo.CurrentCulture != InitialCulture. InitialCulture came from the configured applicationCulture (fr-FR), but CurrentCulture fell back to the runtime default (en-US) because LANG was never applied.

Fix

In src/native/libs/Common/JavaScript/loader/icu.ts, after finalizing loaderConfig.applicationCulture, set:

if(culture&&loaderConfig.environmentVariables!["LANG"]===undefined){loaderConfig.environmentVariables!["LANG"]=`${culture}.UTF-8`;}

The === undefined guard is conservative: it only sets LANG when the culture is known and the user has not already supplied their own LANG (e.g. via dotnet.withEnvironmentVariable("LANG", ...)), so explicit user configuration is never overridden.

The host applies env vars to the Emscripten ENV in host/index.ts::setupEmscripten, which runs during dotnetInitializeModuleaftergetIcuResourceName() is called in loader/run.ts, so the ordering is correct.

Behavior change (A/B with ?culture=fr-FR)

Before (applicationCulture=fr-FR only):

  • getenv("LANG") = en_US.UTF-8 (default)
  • InstalledUICulture / UserDefaultCulture = en-US
  • CurrentCulture falls back to en-USInitialCulture (fr-FR) → Blazor throws, page fails to render.

After (LANG=fr-FR.UTF-8):

  • getenv("LANG") = fr-FR.UTF-8
  • InstalledUICulture / UserDefaultCulture = fr-FR
  • CurrentCulture == InitialCulture (fr-FR) → no throw, page renders.

This eliminates the Blazor culture-change exception and makes the CoreCLR-WASM runtime default culture match applicationCulture, exactly mirroring Mono.

How satellite assemblies are loaded

In short: Blazor (LoadCurrentCultureResourcesAsync) → INTERNAL.loadSatelliteAssembliesfetchSatelliteAssembliesfetchAssembly/registerDllBytes (download + copy into WASM heap) → external_assembly_probe/BrowserHost_ExternalAssemblyProbe (runtime resolves from memory). This PR only ensures the culture used in step 1 is correct; the download path itself is unchanged.

Related dotnet/aspnetcore#66331

@pavelsavarapavelsavara added this to the 11.0.0 milestone Jun 10, 2026
@pavelsavarapavelsavara self-assigned this Jun 10, 2026
CopilotAI review requested due to automatic review settings June 10, 2026 08:19
@pavelsavarapavelsavara added arch-wasm WebAssembly architecture area-System.Globalization os-browser Browser variant of arch-wasm labels Jun 10, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Common JavaScript loader’s ICU selection path to also initialize the runtime locale environment by setting LANG based on the resolved application culture (explicit applicationCulture or browser/Intl-derived locale). This helps align CoreCLR browser runs with the expected locale-based globalization behavior.

Changes:

  • When a non-invariant globalization mode is used and a culture is resolved, set loaderConfig.environmentVariables["LANG"] to <culture>.UTF-8 while computing the ICU resource to load.

Comment threadsrc/native/libs/Common/JavaScript/loader/icu.ts Outdated

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.

Comment threadsrc/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@pavelsavara

Copy link
Copy Markdown
MemberAuthor

/ba-g unrelated failures

@pavelsavara
pavelsavara merged commit a1e8100 into dotnet:mainJun 14, 2026
135 of 144 checks passed
@pavelsavara
pavelsavara deleted the browser_lang_fix branch June 14, 2026 08:59
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 18, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-wasmWebAssembly architecturearea-System.Globalizationos-browserBrowser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@pavelsavara@jkotas@maraf
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[browser][coreCLR] set "LANG" env variable - #129221

Merged
pavelsavara merged 2 commits into
dotnet:mainfrom
pavelsavara:browser_lang_fix
Jun 14, 2026
Merged

[browser][coreCLR] set "LANG" env variable#129221
pavelsavara merged 2 commits into
dotnet:mainfrom
pavelsavara:browser_lang_fix

Conversation

@pavelsavara

@pavelsavarapavelsavara commented Jun 10, 2026

Copy link
Copy Markdown
Member

Summary

The CoreCLR WebAssembly browser loader used applicationCultureonly to select the ICU shard in getIcuResourceName(). It never propagated the culture into the runtime's LANG environment variable. As a result, the runtime's default locale (InstalledUICulture / UserDefaultCulture) stayed at the loader/Emscripten default (en_US.UTF-8) even when the app requested a different applicationCulture.

This change sets LANG = "<applicationCulture>.UTF-8" in the loader, mirroring what Mono already does in src/mono/browser/runtime/loader/config.ts.

Problem

On CoreCLR-WASM the runtime default culture is driven by LANG (via uloc_getDefault()getenv("LANG") in pal_locale.c), not by applicationCulture. With LANG left at the default, a Blazor WebAssembly app started with a non-default culture (e.g. Blazor.start({ webAssembly: { applicationCulture: "fr-FR" } }) from ?culture=fr-FR) failed:

InvalidOperationException: Blazor detected a change in the application's culture
that is not supported with the current project configuration...

This is thrown by WebAssemblyCultureProvider.ThrowIfCultureChangeIsUnsupported when sharded ICU is in use (__BLAZOR_SHARDED_ICU == "1", the default) andCultureInfo.CurrentCulture != InitialCulture. InitialCulture came from the configured applicationCulture (fr-FR), but CurrentCulture fell back to the runtime default (en-US) because LANG was never applied.

Fix

In src/native/libs/Common/JavaScript/loader/icu.ts, after finalizing loaderConfig.applicationCulture, set:

if(culture&&loaderConfig.environmentVariables!["LANG"]===undefined){loaderConfig.environmentVariables!["LANG"]=`${culture}.UTF-8`;}

The === undefined guard is conservative: it only sets LANG when the culture is known and the user has not already supplied their own LANG (e.g. via dotnet.withEnvironmentVariable("LANG", ...)), so explicit user configuration is never overridden.

The host applies env vars to the Emscripten ENV in host/index.ts::setupEmscripten, which runs during dotnetInitializeModuleaftergetIcuResourceName() is called in loader/run.ts, so the ordering is correct.

Behavior change (A/B with ?culture=fr-FR)

Before (applicationCulture=fr-FR only):

  • getenv("LANG") = en_US.UTF-8 (default)
  • InstalledUICulture / UserDefaultCulture = en-US
  • CurrentCulture falls back to en-USInitialCulture (fr-FR) → Blazor throws, page fails to render.

After (LANG=fr-FR.UTF-8):

  • getenv("LANG") = fr-FR.UTF-8
  • InstalledUICulture / UserDefaultCulture = fr-FR
  • CurrentCulture == InitialCulture (fr-FR) → no throw, page renders.

This eliminates the Blazor culture-change exception and makes the CoreCLR-WASM runtime default culture match applicationCulture, exactly mirroring Mono.

How satellite assemblies are loaded

In short: Blazor (LoadCurrentCultureResourcesAsync) → INTERNAL.loadSatelliteAssembliesfetchSatelliteAssembliesfetchAssembly/registerDllBytes (download + copy into WASM heap) → external_assembly_probe/BrowserHost_ExternalAssemblyProbe (runtime resolves from memory). This PR only ensures the culture used in step 1 is correct; the download path itself is unchanged.

Related dotnet/aspnetcore#66331

@pavelsavarapavelsavara added this to the 11.0.0 milestone Jun 10, 2026
@pavelsavarapavelsavara self-assigned this Jun 10, 2026
CopilotAI review requested due to automatic review settings June 10, 2026 08:19
@pavelsavarapavelsavara added arch-wasm WebAssembly architecture area-System.Globalization os-browser Browser variant of arch-wasm labels Jun 10, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Common JavaScript loader’s ICU selection path to also initialize the runtime locale environment by setting LANG based on the resolved application culture (explicit applicationCulture or browser/Intl-derived locale). This helps align CoreCLR browser runs with the expected locale-based globalization behavior.

Changes:

  • When a non-invariant globalization mode is used and a culture is resolved, set loaderConfig.environmentVariables["LANG"] to <culture>.UTF-8 while computing the ICU resource to load.

Comment threadsrc/native/libs/Common/JavaScript/loader/icu.ts Outdated

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.

Comment threadsrc/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@pavelsavara

Copy link
Copy Markdown
MemberAuthor

/ba-g unrelated failures

@pavelsavara
pavelsavara merged commit a1e8100 into dotnet:mainJun 14, 2026
135 of 144 checks passed
@pavelsavara
pavelsavara deleted the browser_lang_fix branch June 14, 2026 08:59
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 18, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-wasmWebAssembly architecturearea-System.Globalizationos-browserBrowser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@pavelsavara@jkotas@maraf
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

[browser][coreCLR] set "LANG" env variable - #129221

Merged
pavelsavara merged 2 commits into
dotnet:mainfrom
pavelsavara:browser_lang_fix
Jun 14, 2026
Merged

[browser][coreCLR] set "LANG" env variable#129221
pavelsavara merged 2 commits into
dotnet:mainfrom
pavelsavara:browser_lang_fix

Conversation

@pavelsavara

@pavelsavarapavelsavara commented Jun 10, 2026

Copy link
Copy Markdown
Member

Summary

The CoreCLR WebAssembly browser loader used applicationCultureonly to select the ICU shard in getIcuResourceName(). It never propagated the culture into the runtime's LANG environment variable. As a result, the runtime's default locale (InstalledUICulture / UserDefaultCulture) stayed at the loader/Emscripten default (en_US.UTF-8) even when the app requested a different applicationCulture.

This change sets LANG = "<applicationCulture>.UTF-8" in the loader, mirroring what Mono already does in src/mono/browser/runtime/loader/config.ts.

Problem

On CoreCLR-WASM the runtime default culture is driven by LANG (via uloc_getDefault()getenv("LANG") in pal_locale.c), not by applicationCulture. With LANG left at the default, a Blazor WebAssembly app started with a non-default culture (e.g. Blazor.start({ webAssembly: { applicationCulture: "fr-FR" } }) from ?culture=fr-FR) failed:

InvalidOperationException: Blazor detected a change in the application's culture
that is not supported with the current project configuration...

This is thrown by WebAssemblyCultureProvider.ThrowIfCultureChangeIsUnsupported when sharded ICU is in use (__BLAZOR_SHARDED_ICU == "1", the default) andCultureInfo.CurrentCulture != InitialCulture. InitialCulture came from the configured applicationCulture (fr-FR), but CurrentCulture fell back to the runtime default (en-US) because LANG was never applied.

Fix

In src/native/libs/Common/JavaScript/loader/icu.ts, after finalizing loaderConfig.applicationCulture, set:

if(culture&&loaderConfig.environmentVariables!["LANG"]===undefined){loaderConfig.environmentVariables!["LANG"]=`${culture}.UTF-8`;}

The === undefined guard is conservative: it only sets LANG when the culture is known and the user has not already supplied their own LANG (e.g. via dotnet.withEnvironmentVariable("LANG", ...)), so explicit user configuration is never overridden.

The host applies env vars to the Emscripten ENV in host/index.ts::setupEmscripten, which runs during dotnetInitializeModuleaftergetIcuResourceName() is called in loader/run.ts, so the ordering is correct.

Behavior change (A/B with ?culture=fr-FR)

Before (applicationCulture=fr-FR only):

  • getenv("LANG") = en_US.UTF-8 (default)
  • InstalledUICulture / UserDefaultCulture = en-US
  • CurrentCulture falls back to en-USInitialCulture (fr-FR) → Blazor throws, page fails to render.

After (LANG=fr-FR.UTF-8):

  • getenv("LANG") = fr-FR.UTF-8
  • InstalledUICulture / UserDefaultCulture = fr-FR
  • CurrentCulture == InitialCulture (fr-FR) → no throw, page renders.

This eliminates the Blazor culture-change exception and makes the CoreCLR-WASM runtime default culture match applicationCulture, exactly mirroring Mono.

How satellite assemblies are loaded

In short: Blazor (LoadCurrentCultureResourcesAsync) → INTERNAL.loadSatelliteAssembliesfetchSatelliteAssembliesfetchAssembly/registerDllBytes (download + copy into WASM heap) → external_assembly_probe/BrowserHost_ExternalAssemblyProbe (runtime resolves from memory). This PR only ensures the culture used in step 1 is correct; the download path itself is unchanged.

Related dotnet/aspnetcore#66331

@pavelsavarapavelsavara added this to the 11.0.0 milestone Jun 10, 2026
@pavelsavarapavelsavara self-assigned this Jun 10, 2026
CopilotAI review requested due to automatic review settings June 10, 2026 08:19
@pavelsavarapavelsavara added arch-wasm WebAssembly architecture area-System.Globalization os-browser Browser variant of arch-wasm labels Jun 10, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Common JavaScript loader’s ICU selection path to also initialize the runtime locale environment by setting LANG based on the resolved application culture (explicit applicationCulture or browser/Intl-derived locale). This helps align CoreCLR browser runs with the expected locale-based globalization behavior.

Changes:

  • When a non-invariant globalization mode is used and a culture is resolved, set loaderConfig.environmentVariables["LANG"] to <culture>.UTF-8 while computing the ICU resource to load.

Comment threadsrc/native/libs/Common/JavaScript/loader/icu.ts Outdated

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.

Comment threadsrc/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@pavelsavara

Copy link
Copy Markdown
MemberAuthor

/ba-g unrelated failures

@pavelsavara
pavelsavara merged commit a1e8100 into dotnet:mainJun 14, 2026
135 of 144 checks passed
@pavelsavara
pavelsavara deleted the browser_lang_fix branch June 14, 2026 08:59
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 18, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-wasmWebAssembly architecturearea-System.Globalizationos-browserBrowser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@pavelsavara@jkotas@maraf