Win32 · MFC · Delphi Reference

Resource-only DLLs: one EXE, one language DLL per language.

A resource-only DLL holds the dialogs, menus, and strings of one language and no code. The application keeps a single EXE and takes its user interface from the DLL that matches the user's language. This page shows how the DLLs are named, how MFC, Delphi, and plain Win32 code load them, and how to build them from a compiled application.

Release folderEXEMyApp.exeunchangedDLLMyAppDEU.dllDLLMyAppFRA.dllDLLMyAppJPN.dllUse when: one executable shouldsupport several languages.At run timeNeue BestellungKunde:Menge:ErstellenMFC loads it by itselfother Win32 code: your own Release folderEXEMyApp.exeunchangedDLLMyAppDEU.dllDLLMyAppFRA.dllDLLMyAppJPN.dllUse when: one executable shouldsupport several languages.Neue BestellungKunde:Menge:ErstellenMFC loads it by itselfother Win32 code: your own
MFC
MyAppDEU.dll, loaded by MFC
Delphi, C++Builder
MyApp.DEU, loaded by the runtime
Plain Win32
Any name, loaded by your code
No DLL for the language
The EXE's own resources
On this page

Language DLLs or a localized EXE per language?

Both keep the code untouched. A localized EXE is a full copy of the application in each language folder. Resource-only DLLs ship one EXE and add a small DLL for each language next to it.

Resource-only DLLs fit when

  • One installer and one EXE should serve every language.
  • Languages are added or corrected after release: ship one more DLL, the EXE stays the same.
  • The EXE is signed, and you do not want a signed copy for each language.

A localized EXE fits when

  • The application has no loading code and is not MFC or Delphi.
  • Each language ships as its own package anyway.
  • You want each language tested and shipped as one complete file.

Names: the framework decides.

MFC and Delphi look for a fixed name built from the Windows language abbreviation (LOCALE_SABBREVLANGNAME): DEU for German (Germany), FRA for French (France), JPN for Japanese. Plain Win32 code can use any convention, as long as the code and the files agree.

Resource-only DLL names by framework
FrameworkGerman (Germany)Also triedWhere
MFCMyAppDEU.dllMyAppLOC.dllFolder of MyApp.exe
Delphi, C++Builder (VCL)MyApp.DEUMyApp.DEFolder of MyApp.exe
Plain Win32Your choice, for example lang\de-DE.dllWhat your code triesWhere your code looks

The resource IDs in every language DLL must match the EXE: IDD_ABOUT in MyAppDEU.dll is the same number as in MyApp.exe. Windows' own MUI system uses .mui files in language folders instead; that is a separate mechanism.

Reference

How the DLL gets loaded.

MFC: by CWinApp::InitInstance

At startup, CWinApp::InitInstance goes through the user's UI languages and loads MyAppDEU.dll from the folder of MyApp.exe, then MyAppLOC.dll. Without a matching DLL, the application uses the resources in its own EXE. Your InitInstance must call the base class; to pick a language other than the Windows one, call SetThreadPreferredUILanguages before it.

WindowsGerman (Germany)de-DE MFC looks forMyAppDEU.dllloadedMyApp.exeif no DLL Your application Neue BestellungKunde:Menge: MFC loads the DLL in CWinApp::InitInstance; no code of yours is needed. WindowsGerman (Germany)de-DE MFC looks forMyAppDEU.dllloadedMyApp.exeif no DLL Neue BestellungKunde:Menge: MFC loads the DLL by itself;no code of yours is needed.

Delphi and C++Builder: by the runtime

Before your code runs, the runtime looks next to MyApp.exe for MyApp.DEU on a German (Germany) system, then MyApp.DE. A value in the Locales key under HKEY_CURRENT_USER, named after the full path of MyApp.exe, selects another language; the key name depends on the Delphi version (example).

WindowsGerman (Germany)de-DE Delphi looks forMyApp.DEUloadedMyApp.DEnextMyApp.exeif neither Your application Neue BestellungKunde:Menge:Erstellen The Delphi runtime loads the module at startup; no code of yours is needed. WindowsGerman (Germany)de-DE Delphi looks forMyApp.DEUloadedMyApp.DEnextMyApp.exeif neither Neue BestellungKunde:Menge: The Delphi runtime loads the moduleat startup; no code of yours is needed.

Plain Win32: in your code

Load the DLL as a data file and pass its handle wherever the code passes the module's own handle today: LoadString, DialogBoxParam, LoadMenu, LoadIcon. Fall back to the EXE when the file is missing.

// Pick the file from the user's language or a saved setting
HMODULE ui = LoadLibraryExW(L"MyAppDEU.dll", nullptr,
    LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE);
if (!ui) ui = hInstance;                    // the EXE's own resources

LoadStringW(ui, IDS_SAVED, text, _countof(text));
DialogBoxParamW(ui, MAKEINTRESOURCEW(IDD_ABOUT), hwnd, AboutProc, 0);

Build

Two ways to build resource-only DLLs.

Pick by what you have: translated .rc files, or only the compiled application.

1. From a translated .rc file, by hand

Compile the translated resource script and link it into a DLL without an entry point. Build it for the same platform as the EXE, and repeat for every language and every release:

rc /fo MyAppDEU.res MyAppDEU.rc
link /DLL /NOENTRY /MACHINE:X64 /OUT:MyAppDEU.dll MyAppDEU.res

The work here is keeping each translated .rc in step with the original: every new dialog, control, and string has to reach every language.

2. From the compiled application, with Lingobit Localizer

Add MyApp.exe to a project and set the output type to Resource-Only DLLs (MFC Output for Win32, VCL Binary Output for Delphi). Translate the dialogs, menus, and strings in the visual editor, then run Create Localized Files. Lingobit writes MyAppDEU.dll or MyApp.DEU next to your files; the EXE is not changed and nothing is rebuilt.

  • Next release: Scan for Changes shows the new and changed resources; only those need translation.
  • Check the layout: Validation reports text cut off in dialogs and duplicate hotkeys.
  • See it run: Run Localized Application starts the EXE with the chosen language.
  • Existing DLLs: Import Translations from Localized Files reads language DLLs you already have, including ones made by other tools.

The application stays in English.

Symptoms, causes, and what to do
CheckWhy it matters
The file is next to MyApp.exeMFC and Delphi look only in the folder of the EXE, not in a de subfolder.
The name has the right abbreviationMyAppDEU.dll, not MyAppDE.dll or MyApp_de.dll: MFC ignores other names.
The language is the user's UI languageA German DLL on an English Windows is not loaded unless the application selects German itself.
MFC: the base InitInstance is calledThe DLL is loaded there; an override that skips it never loads one.
The DLL matches the EXEBuilt from an older release, it lacks new dialogs and strings, or has different IDs.

Questions.

Can I add a language without rebuilding the application?

Yes. That is the main reason to use language DLLs: copy MyAppFRA.dll or MyApp.FRA next to the EXE and French users get French.

Can users switch the language while the application runs?

Not with the built-in loading: MFC and Delphi pick the DLL at startup, so a new choice takes effect after a restart. Plain Win32 code that keeps the DLL handle in one place can reload it. For MFC, Multilingual Library adds a language menu.

Do I need the source code?

Not with Lingobit: it reads the resources from the compiled EXE or DLL. The application only needs a way to load the language DLL, which MFC and Delphi have built in.

What about .NET applications?

.NET uses satellite assemblies instead: de\MyApp.resources.dll in a culture folder. See .NET satellite assemblies.