.NET Troubleshooting
Satellite assembly not loading? Check these in order.
You ship de\MyApp.resources.dll, the user runs German Windows, and the application still shows English. ResourceManager does not throw when a satellite is missing or rejected: it quietly falls back to the neutral resources. Nine causes cover nearly every case.
Two-minute diagnosis.
Put this in the application, run it on the machine where the language is wrong, and compare the output with the checklist below.
var asm = typeof(Program).Assembly;
var ui = CultureInfo.CurrentUICulture;
Console.WriteLine($"UI culture: {ui.Name} (parent: {ui.Parent.Name})");
Console.WriteLine($"Assembly: {asm.Location}");
foreach (var name in asm.GetManifestResourceNames())
Console.WriteLine($" neutral: {name}");
for (var c = ui; c.Name != ""; c = c.Parent)
{
try
{
var sat = asm.GetSatelliteAssembly(c);
Console.WriteLine($"{c.Name}: {sat.Location}");
foreach (var name in sat.GetManifestResourceNames())
Console.WriteLine($" {name}");
}
catch (Exception e)
{
Console.WriteLine($"{c.Name}: {e.GetType().Name}: {e.Message}");
}
}
- UI culture is not the one you expect: see 1.
- No satellite found for any culture in the chain: see 2, 3, and 7.
- Satellite found, wrong resource names inside: see 4.
- A load exception on .NET Framework: see 5 and 6.
The checklist.
| Cause | How to check | Fix |
|---|---|---|
| 1. The UI culture is not what you think | CurrentUICulture decides the language, not CurrentCulture (number and date formats). Windows display language and regional format are different settings. | Set CultureInfo.DefaultThreadCurrentUICulture and CurrentUICulture at startup, before the first resource is read and before any form or window is created. |
| 2. The folder name is not a culture in the chain | For de-AT the runtime tries de-AT\, then de\. A folder named German, de_DE, or de-DE for an Austrian user is never found. On Linux and macOS the name is case-sensitive. | Name folders as CultureInfo.Name writes them, and ship the parent culture, such as de\. |
| 3. The satellite is not next to its assembly | Resources of MyLib.dll load from de\MyLib.resources.dll next to MyLib.dll, not next to the EXE if the library lives elsewhere. Installers and publish profiles often drop the culture folders. | Add the culture folders to the installer or publish output, and check the installed application, not bin\Debug. |
| 4. Resource names do not match | new ResourceManager("MyApp.Strings", …) expects MyApp.Strings.resources in the main assembly and MyApp.Strings.de.resources in the satellite. A changed namespace or RootNamespace breaks the pair. | Compare the names the diagnosis prints. Rebuild the satellites after renaming a namespace or a .resx file. |
| 5. Version mismatch (.NET Framework) | After the main assembly's version changed, satellites built for the old version are ignored. Compare the versions in the file properties. | Rebuild the satellites with the new version, or keep old ones valid with [assembly: SatelliteContractVersion("1.0.0.0")]. |
| 6. Strong name (.NET Framework) | A strong-named main assembly accepts only satellites signed with the same key. sn -T on both files shows the public key tokens. | Sign the satellites with the application's key. .NET 5–10 loads unsigned satellites. |
| 7. Neutral resources live in a satellite | NeutralResourcesLanguage(…, UltimateResourceFallbackLocation.Satellite), or <UICulture> in a WPF project, moves the neutral resources to a folder such as en-US\. Without it, WPF reports IOException: Cannot locate resource 'mainwindow.xaml'. | Ship that folder as well, or keep the neutral resources in the main assembly. See also the WPF guide. |
| 8. Invariant globalization mode (.NET 5–10) | <InvariantGlobalization>true</InvariantGlobalization> in the project or runtimeconfig.json leaves only the invariant culture; by default creating de-DE then throws CultureNotFoundException. | Turn invariant mode off for applications that show more than one language. |
| 9. The string is missing from the satellite | The satellite loads, but one string is in English: ResourceManager took it from the parent culture or the neutral resources. | Translate the missing resource and rebuild the satellite. A localization tool's validation lists untranslated items before release. |
Trace the loader.
When the checklist does not explain it, let the runtime tell you which files it tried.
.NET Framework 4.8
Run Fusion Log Viewer (fuslogvw.exe) as administrator, turn on logging of failed binds, start the application, and open the entry for MyApp.resources. It lists every path probed and the reason a file was rejected, such as a version or public key mismatch.
.NET 5–10
The default context raises Resolving for every satellite it did not find. Log it during startup:
AssemblyLoadContext.Default.Resolving += (ctx, name) =>
{
Console.WriteLine($"Not found: {name.Name}, {name.CultureName}");
return null;
};
Satellites from Lingobit Localizer.
Lingobit builds satellite assemblies from your compiled .NET application: Create Localized Files writes de\MyApp.resources.dll and the other languages next to your files, and the EXE and DLLs stay unchanged. Several causes above are handled in the project settings.
- Version: satellites get the version of the original assembly, so 5 does not happen after you rebuild them for a release.
- Strong name: for strong-named .NET Framework assemblies, set your key in the project and the satellites are signed.
- .NET 5–10: building satellites needs the .NET SDK of that version, or a newer one if the project allows it. The Output panel reports when a newer SDK was used.
- Missing strings: validation lists untranslated items, and Run Localized Application starts the result in the chosen language.
Related paths
- .NET satellite assemblies — Folder layout, names, fallback, and three ways to build them.
- WinForms — Translate the compiled build in the form editor.
- WPF / BAML — Windows and resources from compiled assemblies.
- Localization QA — Find untranslated and broken strings before release.