Programmatically listing all supported locales in Windows 10

Viewed 708

Windows 10 supports custom locales, locales that do not have an LCID like older locales from older versions of Windows. In each update to Windows 10, Microsoft has been adding lesser used locales for minority and indigenous languages.

A recent update to Windows 10 added the the Võro kiil (vro) locale. When using EnumSystemLocalesEx to enumerate all supported locales, vro does not appear in any form. However, in the system settings UI for adding a new language or keyboard, Võro kiil does appear.

However, if the user then enables this language, when you call EnumSystemLocalesEx, vro, vro-Latn and vro-Latn-001 are now listed. If the user then removes this locale from the UI, it no longer appears in the results of this function call.

The question: is there a way (supported or otherwise) to get a list of all the known locales to the operating system regardless of whether the user has enabled them or not?

I find it very bizarre that this output includes other minority languages like Skolt Sami without requiring the user to enable it in advance.

I will quite happily accept an answer that uses the .NET framework if the API does not exist in the C/C++ APIs, so long as I can actually get this data.


Example code to generate the locale output:

#include <cstdio>
#include "stdafx.h"
#include "windows.h"

BOOL CALLBACK Callback(LPWSTR pStr, DWORD dwFlags, LPARAM lparam)
{
    wprintf(L"%ls\n", pStr);
    return TRUE;
}

int main()
{
    EnumSystemLocalesEx(Callback, 0, 0, 0);
    return 0;
}

With Võro kiil enabled from the "Region and Language" screen in System Settings, the final three results are vro-Latn, vro-Latn-001 and vro. When not enabled, they do not appear in the output at all.


Using .NET APIs seems to have the same behaviour.

#include "stdafx.h"

using namespace System;
using namespace System::Globalization;
int main()
{
    System::Collections::IEnumerator^ enum0 = CultureInfo::GetCultures(CultureTypes::AllCultures)->GetEnumerator();
    while (enum0->MoveNext())
    {
        CultureInfo^ ci = safe_cast<CultureInfo^>(enum0->Current);
        Console::WriteLine("{0}", ci->Name);
    }
}
1 Answers

In effect, the new language model for Windows means that there is no "list" beyond those that have historical LCIDs.

The Windows 8.1 and 10 settings tools link to bcp47langs.dll and winlangdb.dll, which provide functions for enabling languages and input methods so long as the provided input is a valid ISO 639-3 language code.

In some cases, if you want your language to appear in the UI or via these APIs, you must provide at least the script and sometimes the region. An example is myv-Cyrl for the Erzya language.

Using these APIs

Using an MSIL disassembler on a cmdlet bundled with PowerShell, I found a p/invoke definition that has allowed me to successfully use these APIs from C# and Rust code.

Here it is, for posterity:

// Decompiled with JetBrains decompiler
// Type: Microsoft.InternationalSettings.Commands.LPAPIWrapper
// Assembly: Microsoft.InternationalSettings.Commands, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
// MVID: E0B49792-544F-4FBD-8C35-D4DA177385AF
// Assembly location: C:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.InternationalSettings.Commands\v4.0_3.0.0.0__31bf3856ad364e35\Microsoft.InternationalSettings.Commands.dll

using System;
using System.Runtime.InteropServices;
using System.Text;

namespace Microsoft.InternationalSettings.Commands
{
  internal class LPAPIWrapper
  {
    public static uint GEO_NATION = 1;
    public static uint GEO_FRIENDLYNAME = 8;
    public static uint GEOCLASS_NATION = 16;
    public static uint GEOCLASS_REGION = 14;

    [DllImport("kernelbase.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int NlsUpdateLocale(string LocaleName, int Flags);

    [DllImport("intl.cpl", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int IntlUpdateSystemLocale(string LocaleName, int dwFlags);

    [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int SystemParametersInfo(
      uint Action,
      uint UnsignedParam,
      IntPtr Param,
      uint WinIni);

    [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int SendNotifyMessage(
      IntPtr wWwnd,
      uint Msg,
      IntPtr wParam,
      string lParam);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int GetSystemDefaultLocaleName(
      StringBuilder LocaleName,
      int LocaleNameSize);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int GetUserGeoID(uint GeoClass);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int SetUserGeoID(int GeoId);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int GetGeoInfo(
      int Location,
      uint GeoType,
      StringBuilder GeoData,
      int Length,
      ushort LangID);

    [DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int GetUserLanguages(char Delimiter, [MarshalAs(UnmanagedType.HString)] ref string UserLanguages);

    [DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int GetUserLanguageInputMethods(
      string Language,
      char Delimiter,
      [MarshalAs(UnmanagedType.HString)] ref string InputMethods);

    [DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int LcidFromBcp47([MarshalAs(UnmanagedType.HString)] string LanguageTag, ref int Lcid);

    [DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int GetPendingUserDisplayLanguage([MarshalAs(UnmanagedType.HString)] ref string language);

    [DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int GetUserDisplayLanguageOverride([MarshalAs(UnmanagedType.HString)] ref string language);

    [DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int SetUserDisplayLanguageOverride(string LanguageTag);

    [DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int ClearUserDisplayLanguageOverride();

    [DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int GetHttpAcceptLanguageOptOut(ref bool IsOptOut);

    [DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int SetHttpAcceptLanguageOptOut();

    [DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int ClearHttpAcceptLanguageOptOut();

    [DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int GetUserLocaleFromLanguageProfileOptOut(ref bool IsOptOut);

    [DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int SetUserLocaleFromLanguageProfileOptOut();

    [DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int ClearUserLocaleFromLanguageProfileOptOut();

    [DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int RemoveInputsForAllLanguagesInternal();

    [DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int SetInputMethodOverride(string TipString);

    [DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int Bcp47GetIsoLanguageCode([MarshalAs(UnmanagedType.HString)] string languageTag, [MarshalAs(UnmanagedType.HString)] ref string isoLanguageCode);

    [DllImport("ext-ms-win-globalization-input-l1-1-2.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int WGIGetDefaultInputMethodForLanguage(
      [MarshalAs(UnmanagedType.HString)] string Language,
      [MarshalAs(UnmanagedType.HString)] ref string DefaultTipString);

    [DllImport("ext-ms-win-globalization-input-l1-1-2.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int WGITransformInputMethodsForLanguage(
      [MarshalAs(UnmanagedType.HString)] string TipString,
      [MarshalAs(UnmanagedType.HString)] string Language,
      [MarshalAs(UnmanagedType.HString)] ref string TransformedTipString);

    [DllImport("winlangdb.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int SetUserLanguages(char Delimiter, [MarshalAs(UnmanagedType.HString)] string UserLanguages);

    [DllImport("winlangdb.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int GetLanguageNames(
      string Language,
      StringBuilder Autonym,
      StringBuilder EnglishName,
      StringBuilder LocalName,
      StringBuilder ScriptName);

    [DllImport("ext-ms-win-globalization-input-l1-1-2.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int WGIIsImeInputMethod([MarshalAs(UnmanagedType.HString)] string TipString, ref int result);

    [DllImport("winlangdb.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int EnsureLanguageProfileExists();

    [DllImport("input.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int InstallLayoutOrTip(string TipString, int Flags);

    [DllImport("input.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int SetDefaultLayoutOrTip(string TipString, int Flags);

    [DllImport("input.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int GetLayoutDescription(
      string LayoutId,
      StringBuilder LayoutDescription,
      ref int DescriptionLength);

    private LPAPIWrapper()
    {
    }
  }
}
 
Related