9

I'm trying to retrieve the complete list of the user's preferred languages from a C++/Qt application, as configured in the "Region & language" page in the user's preferences:

Preferred languages

For that, I am trying with the WinAPI function GetUserPreferredUILanguages(), on an up-to-date Windows 10 Pro system.

However, the function always only returns the first entry (the main Windows display language), and "en-US". If English is configured as the main language, then only "en-US" is returned. E.g., if I have (German, French, English) configured, ["de-de", "en-US"] is returned, French is omitted. If I add more languages to the list, they are omitted as well. I also looked at User Interface Language Management, but to no avail. GetSystemPreferredUILanguages() for example only returns "en-US". GetUILanguageFallbackList() returns ["de-de", "de", "en-US", "en"].

The code I use:

// calling GetUserPreferredUILanguages() twice, once to get number of 
// languages and required buffer size, then to get the actual data

ULONG numberOfLanguages = 0;
DWORD bufferLength = 0;
const auto result1 = GetUserPreferredUILanguages(MUI_LANGUAGE_NAME,
                                                 &numberOfLanguages,
                                                 nullptr,
                                                 &bufferLength);
// result1 is true, numberOfLanguages=2

QVector<wchar_t> languagesBuffer(static_cast<int>(bufferLength));
const auto result2 = GetUserPreferredUILanguages(MUI_LANGUAGE_NAME,
                                                 &numberOfLanguages,
                                                 languagesBuffer.data(),
                                                 &bufferLength);

// result2 is true, languageBuffer contains "de-de", "en-US"

Is this not the right function to use, or am I misunderstanding something about the language configuration in Windows 10? How can I get the complete list of preferred languages? I see UWP API that might do the job, but if possible, I'd like to use C API, as it integrated more easily with the C++ codebase at hand. (unmanaged C++, that is)

Frank Osterfeld
  • 24,815
  • 5
  • 58
  • 70
  • 1
    That's strange. On my computer this returns only 1 language, even though I have installed other languages for testing. – Barmak Shemirani Oct 17 '18 at 20:57
  • It would appear that the language codes are separated by nulls `'\0'.` `floor(bufferLength / 4)` should equate to `numberOfLanguages`. "...this function retrieves an ordered, null-delimited user preferred UI languages list,... This list ends with two null characters" https://learn.microsoft.com/en-us/windows/desktop/api/winnls/nf-winnls-getuserpreferreduilanguages If the null char is seen as the end of the buffer, for example in std::string constructor.. I assume this is why I only see the first 4 chars of the buffer in the Visual Studio text visualizer. – coolhandle01 Jun 10 '19 at 12:37
  • Similar/related question: https://stackoverflow.com/questions/42874699/stdstring-stops-at-0 – coolhandle01 Jun 10 '19 at 12:41
  • see `std::string languages(languagesBuffer.data(), bufferLength)` http://www.cplusplus.com/reference/string/string/string/ – coolhandle01 Jun 10 '19 at 12:55
  • @mynameisnafe that wasn't the problem, i split the data at the null bytes, being aware that is not a single string. Also note that i got two entries, not one. – Frank Osterfeld Jun 10 '19 at 13:02
  • @FrankOsterfeld indeed, your string looks good. It might be worth looking at the MUI_ flags as there is a licencing aspect to installed languages in Windows, so if you've installed 'unlicenced' ones, they might not be in the list by default? – coolhandle01 Jun 15 '19 at 11:45

2 Answers2

6

GlobalizationPreferences.Languages is usable from unmanaged C++ because GlobalizationPreferences has DualApiPartitionAttribute. Here is a C++/WinRT example of using GlobalizationPreferences.Languages:

#pragma once
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.System.UserProfile.h>
#include <iostream>
#pragma comment(lib, "windowsapp")

using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::System::UserProfile;

int main()
{
    winrt::init_apartment();

    for (const auto& lang : GlobalizationPreferences::Languages()) {
        std::wcout << lang.c_str() << std::endl;
    }
}

And a WRL example for those who cannot migrate to C++ 17:

#include <roapi.h>
#include <wrl.h>
#include <Windows.System.UserProfile.h>
#include <iostream>
#include <stdint.h>
#pragma comment(lib, "runtimeobject.lib")

using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
using namespace ABI::Windows::Foundation::Collections;
using namespace ABI::Windows::System::UserProfile;

int main()
{
    RoInitializeWrapper initialize(RO_INIT_MULTITHREADED);
    if (FAILED(initialize)) {
        std::cerr << "RoInitialize failed" << std::endl;
        return 1;
    }

    ComPtr<IGlobalizationPreferencesStatics> gps;
    HRESULT hr = RoGetActivationFactory(
        HStringReference(
            RuntimeClass_Windows_System_UserProfile_GlobalizationPreferences)
            .Get(),
        IID_PPV_ARGS(&gps));
    if (FAILED(hr)) {
        std::cerr << "RoGetActivationFactory failed" << std::endl;
        return 1;
    }

    ComPtr<IVectorView<HSTRING>> langs;
    hr = gps->get_Languages(&langs);
    if (FAILED(hr)) {
        std::cerr << "Could not get Languages" << std::endl;
        return 1;
    }

    uint32_t size;
    hr = langs->get_Size(&size);
    if (FAILED(hr)) {
        std::cerr << "Could not get Size" << std::endl;
        return 1;
    }
    for (uint32_t i = 0; i < size; ++i) {
        HString lang;
        hr = langs->GetAt(i, lang.GetAddressOf());
        if (FAILED(hr)) {
            std::cerr << "Could not get Languages[" << i << "]" << std::endl;
            continue;
        }
        std::wcout << lang.GetRawBuffer(nullptr) << std::endl;
    }
}
emk
  • 221
  • 3
  • 4
  • Thanks! That works perfectly (using MSVC 2019 and C++17). Notes to Qt/QMake users: Make sure to enable C++17 (`CONFIG+=c++17`), link windowsapp.dll (`LIBS += -lwindowsapp`). Do not call `winrt::init_apartment();` (I guess Qt does that internally already). – Frank Osterfeld Nov 22 '19 at 09:09
1

I found out that language list returned by GetUserPreferredUILanguages() matters with your "Windows UI language" setting, and nothing to do with "Input method list order".

For example, in following screenshot from Win10.21H2,

Win10.21H2 metro UI language setting

I can see GetUserPreferredUILanguages() return a list of three langtags:

fr-CA\0fr-FR\0en-US\0\0

Check in VC2010 debugger

In summary, for GetUserPreferredUILanguages() and GetUILanguageFallbackList() their returned langtag list is determined solely by current user's "Windows display language" selection. It is a user-wide single-selection setting. And, for a specific display-language selection, the list-items within and the order of the list-items are hard-coded by Windows itself. Yes, it is even unrelated to what "input methods(IME)" you have added to the control panel -- for example, you add "fr-CA" but not "fr-FR", and the fallback list will still be fr-CA\0fr-FR\0en-US\0\0.

The difference of the two APIs, according to my experiment, is that GetUILanguageFallbackList() returns neutral langtags("fr", "en" etc) as well, so it produces a superset of GetUserPreferredUILanguages().

Jimm Chen
  • 3,411
  • 3
  • 35
  • 59