It seems I will answer my own question because I believe others have hit this problem as well.
The correct and official way to change the language of a Windows GUI applications is:
SetThreadUILanguage
Using this function, and the exact thing I did in my question; the function will apply the resources in that language at runtime. ( menu, dialogs, everything )
I my case it was as simple as:
SetThreadUILanguage(MAKELANGID(LANG_VIETNAMESE, SUBLANG_VIETNAMESE_VIETNAM));
However in my case I am creating builds per language, so if you want to allow the user to change the language at runtime please see the notes and this article.
The un-official way of doing this is a bit more work, however quite stable and not error prone.
- You must translate and rebuild your menu in that language
- You must translate and rename each dialog ID per language, and show that dialog ID per language
For the community and other developers, I will share the code to rebuild the menu:
// Find our Menu resource based on desired language
HRSRC hRes = FindResourceExW(hInstance, RT_MENU, MAKEINTRESOURCE(IDC_APPLICATION), MAKELANGID(LANG_VIETNAMESE, SUBLANG_VIETNAMESE_VIETNAM));
if (!hRes) {
wchar_t buf[MAX_PATH] = { 0 };
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, (sizeof(buf) / sizeof(wchar_t)), NULL);
MessageBoxW(0, buf, _TEXT(L"FindResourceExW Error"), MB_OK | MB_ICONERROR);
return FALSE;
}
// Load our Menu resource based on desired language
HGLOBAL hGlo = LoadResource(hInstance, hRes);
if (!hGlo) {
wchar_t buf[MAX_PATH] = { 0 };
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, (sizeof(buf) / sizeof(wchar_t)), NULL);
MessageBoxW(0, buf, _TEXT(L"LoadResource Error"), MB_OK | MB_ICONERROR);
return FALSE;
}
// Lock the resource
LPVOID pData = LockResource(hGlo);
if (pData == NULL) {
wchar_t buf[MAX_PATH] = { 0 };
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, (sizeof(buf) / sizeof(wchar_t)), NULL);
MessageBoxW(0, buf, _TEXT(L"LockResource Error"), MB_OK | MB_ICONERROR);
return FALSE;
}
// Load the new Menu into memory
HMENU hMenu = LoadMenuIndirectW((MENUTEMPLATE*)pData);
if (!hMenu) {
wchar_t buf[MAX_PATH] = { 0 };
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, (sizeof(buf) / sizeof(wchar_t)), NULL);
MessageBoxW(0, buf, _TEXT(L"LoadMenuIndirectW Error"), MB_OK | MB_ICONERROR);
return FALSE;
}
// Get our default Menu
HMENU hMenu_old = GetMenu(g_Hwnd);
// Set no Menu
SetMenu(g_Hwnd, NULL);
// Erase default Menu
DestroyMenu(hMenu_old);
// Set our new Menu
SetMenu(g_Hwnd, hMenu);
// Draw our new Menu
DrawMenuBar(g_Hwnd);
NOTE: If you want to use FindResourceEx to search for strings, it's more complicated because of RT_STRING so please see this before you waste your time.
Enjoy!