.NET Reference
.NET satellite assemblies: folder layout, naming, and fallback.
A satellite assembly holds the resources of one language and no code. This page shows where .NET Framework 4.8 and .NET 5–10 look for them, how they must be named, what happens when a language is missing, and three ways to build them.
- File
MyApp.resources.dll, the same name for every language- Folder
- A culture folder next to the assembly:
de\,fr-CA\ - Chosen by
CultureInfo.CurrentUICulture- Missing language
- Parent culture, then the neutral resources
One main assembly, one satellite per language.
The main assembly carries the code and the neutral resources, usually English. Every other language ships as a separate assembly with only resources in it. At run time ResourceManager loads the satellite that matches the user's UI culture.
What you gain
- Add a language after release: copy one more folder, the EXE stays the same.
- Fix a translation by replacing one satellite, without a new build of the application.
- One installer for every language, or one package per language.
What you take on
- Each satellite must match its main assembly: name, resource names, and on .NET Framework the version and the key.
- Each language is one more configuration to test.
- A missing or misplaced folder fails quietly: the user sees English.
Folder layout and names.
Every assembly that has resources gets its own satellites, in culture folders next to it. A class library MyLib.dll ships de\MyLib.resources.dll, separate from the application's.
MyApp.exe
MyApp.dll main assembly: code + neutral resources
MyLib.dll
de\
MyApp.resources.dll German, for de, de-DE, de-AT, de-CH
MyLib.resources.dll
fr-CA\
MyApp.resources.dll French (Canada) only
zh-Hans\
MyApp.resources.dll Chinese (Simplified)
- Folder name is the culture name exactly as
CultureInfo.Namewrites it:de,pt-BR,sr-Latn-RS. NotGerman, notde_DE. - File name is the assembly name plus
.resources.dll, in every folder. The folder tells the language, not the file. - Resource names inside carry the culture:
MyApp.Strings.resourcesin the main assembly becomesMyApp.Strings.de.resourcesin the German satellite. - The satellite declares its culture (
AssemblyCulture("de")); the main assembly declares none.
Where .NET looks for a satellite.
The rules differ between .NET Framework and .NET 5–10. Most "it works on my machine" reports come from the last two rows.
| .NET Framework 4.8 | .NET 5–10 | |
|---|---|---|
| Where | The global assembly cache for strong-named assemblies, then <app folder>\de\MyApp.resources.dll and <app folder>\de\MyApp.resources\MyApp.resources.dll | The culture subfolder next to the file the main assembly was loaded from: de\MyApp.resources.dll; on Linux and macOS the folder name is case-sensitive |
| Version | Must equal the main assembly's version, or the version named in SatelliteContractVersion | Build satellites with each release of the main assembly |
| Strong name | A strong-named main assembly needs satellites signed with the same key | Unsigned satellites load |
| Diagnose with | Fusion Log Viewer (fuslogvw.exe) | The AssemblyLoadContext.Default.Resolving event, which fires for every satellite the default probing did not find |
Applications on .NET 1.x–3.5 use the .NET Framework rules with older tooling. To localize them with Lingobit, use Localizer 9.
Fallback: from the exact culture to the neutral resources.
ResourceManager looks for each resource separately: first in the satellite of the UI culture, then in its parent culture, and last in the neutral resources. A string missing from de-AT comes from de, then from English.
- Ship the parent culture. One
de\folder serves German in Germany, Austria, and Switzerland. Addde-CH\only for the strings that differ. - Keep the neutral resources complete. They are the last stop. When a resource is missing there too,
GetStringreturnsnull; when the whole resource file is missing, .NET throwsMissingManifestResourceException. - Mark the neutral language with
[assembly: NeutralResourcesLanguage("en")]. For English users .NET then skips the search for anensatellite. - Neutral resources in a satellite. With
UltimateResourceFallbackLocation.Satellitethe neutral resources live in a satellite too, for exampleen-US\, and that folder must ship. WPF projects with<UICulture>in the project file work this way.
// Set the UI culture before the first resource is read and before any window is created
var culture = CultureInfo.GetCultureInfo("de-DE");
CultureInfo.DefaultThreadCurrentUICulture = culture;
CultureInfo.CurrentUICulture = culture;
var rm = new ResourceManager("MyApp.Strings", typeof(Program).Assembly);
string title = rm.GetString("MainTitle"); // de-DE → de → neutral
Build
Three ways to build satellite assemblies.
Pick by what you have: the source with translated .resx files, single resource files, or only the compiled application.
1. From translated .resx files, with your build
Put Strings.de.resx next to Strings.resx in the project. MSBuild compiles every culture-suffixed .resx into bin\…\de\MyApp.resources.dll. The translated files live in source control and go through your normal build.
2. By hand, for one resource file
On .NET Framework you can compile a .resx and link it with the Assembly Linker. -template copies the version and other metadata of the main assembly; a strong-named application also needs -keyfile with its key:
resgen Strings.de.resx MyApp.Strings.de.resources
al -target:lib -embed:MyApp.Strings.de.resources -culture:de -template:MyApp.exe -out:de\MyApp.resources.dll
3. From the compiled application, with Lingobit Localizer
Add MyApp.exe or MyApp.dll to a project, translate the forms, windows, and strings it contains, and run Create Localized Files. Lingobit writes de\MyApp.resources.dll and the other languages next to your files; the EXE and DLLs are not changed and nothing is rebuilt. The same workflow covers WinForms, WPF (BAML), and plain resources, on .NET Framework 4.8 and .NET 5–10; for WPF it replaces LocBaml, which stops at .NET Framework.
- Version: satellites get the version of the original assembly; another version can be set in the project.
- .NET 5–10: building the satellites needs the .NET SDK of that version, or a newer one if the project allows it.
- .NET Framework, strong-named: the satellites are signed with your key, set in the project.
- Check the result: Run Localized Application starts the application in the chosen language.
Questions.
Can I add a language without rebuilding the application?
Yes. Copy the new culture folder next to the assembly. On .NET Framework the satellite's version must match the main assembly or its SatelliteContractVersion, and a strong-named application needs the satellite signed with its key.
Do NuGet packages ship satellites too?
Many do, for example localized exception messages. <SatelliteResourceLanguages>en;de</SatelliteResourceLanguages> in the project file limits which of the package satellites are copied to the output. It does not affect your own .resx files.
What about single-file and trimmed publishing?
Satellites are part of the published output like any other assembly. After changing publish settings, start the published application in each language once: that is the only check that covers every way a satellite can go missing.
I already have translated satellites from another tool.
Add the current build to a Lingobit project and use Import Translations from Localized Files: it takes the translations from your existing .resources.dll satellites where they still match the current build.
Related paths
- Satellite assembly not loading — A checklist when the application stays in English.
- WinForms — Translate the compiled build in the form editor.
- WPF / BAML — Windows and resources from compiled assemblies.
- LocBaml alternative — WPF satellites from .NET 5–10 builds, where LocBaml stops.
- .NET Resources — Translate .resx files; your build makes the satellites.
- Migration — Bring translations from your existing satellites.