syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

Windows言語設定の取得API

(これは2013-05-17に書いた故OCNブログの記事を移植したものです)
Windowsで各種言語設定を取得するとき、いくつかのよく似たWin32 APIがあるんですが、それぞれ微妙に結果が異なるので注意が必要です。

代表的なのは下記の6つ。

  1. LANGID GetSystemDefaultLangID();
  2. LANGID GetUserDefaultLangID();
  3. LANGID GetSystemDefaultUILanguage();
  4. LANGID GetUserDefaultUILanguage();
  5. LCID GetSystemDefaultLCID();
  6. LCID GetUserDefaultLCID();

C++サンプルコードを下記に書いておきます。上記APIが返す値の型は、実際にはLANGID == WORD == unsigned short型、LCID == DWORD == unsigned long型です。分かりにくいのでサンプルではあえてWORDとDWORDを使いました。LCってのはLocaleかLocale Codeの略だと思われます。

#include <windows.h>
#include <cstdio>
#include <clocale>
#include <typeinfo>
#include <conio.h>
#include <crtdbg.h>

int main()
{
    //setlocale(LC_ALL, "");

    const WORD LangID_ja_JP = MAKELANGID(LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN);
    const WORD LangID_en_US = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);

    printf("LangID_ja_JP = 0x%04hx\n", LangID_ja_JP); // 0x0411
    printf("LangID_en_US = 0x%04hx\n", LangID_en_US); // 0x0409

    _ASSERTE(PRIMARYLANGID(LangID_ja_JP) == LANG_JAPANESE);
    _ASSERTE(PRIMARYLANGID(LangID_en_US) == LANG_ENGLISH);

    const WORD sysDefLangId = ::GetSystemDefaultLangID();
    const WORD usrDefLangId = ::GetUserDefaultLangID();
    const WORD sysDefUILang = ::GetSystemDefaultUILanguage();
    const WORD usrDefUILang = ::GetUserDefaultUILanguage();
    const DWORD sysDefLcid = ::GetSystemDefaultLCID();
    const DWORD usrDefLcid = ::GetUserDefaultLCID();

    _ASSERTE(typeid(LANGID) == typeid(WORD));
    _ASSERTE(typeid(LCID) == typeid(DWORD));

    printf("GetSystemDefaultLangID()     = 0x%04hx\n", sysDefLangId);
    printf("GetUserDefaultLangID()       = 0x%04hx\n", usrDefLangId);
    printf("GetSystemDefaultUILanguage() = 0x%04hx\n", sysDefUILang);
    printf("GetUserDefaultUILanguage()   = 0x%04hx\n", usrDefUILang); // 日本語 OS + MUI 英語パックでは、コレだけが LangID_en_US を返す。
    printf("GetSystemDefaultLCID()       = 0x%08lx\n", sysDefLcid);
    printf("GetUserDefaultLCID()         = 0x%08lx\n", usrDefLcid);

    // なお、日本語 OS をベースとして言語パックを適用しただけでは、
    // ロケールは日本語のまま(ANSI コードページは CP932, Shift_JIS のまま)なので、
    // コマンドプロンプトではデフォルト設定で日本語文字列を表示できる。

    puts("Press any...");
    _getch();
    return 0;
}

もしWindowsのUI(表示言語)を、あるログイン ユーザーがMUI言語パックを使ってOS言語とは別の言語に設定した場合(あるいは別の言語パックLIPを適用した場合)、そのログイン ユーザーのUI言語を取得するには、GetUserDefaultUILanguage()を使います。WindowsではUIとロケールの設定(通貨や単位の表記、ANSIコードページ設定に影響)は別物なんですが、ロケールを取得する場合はたぶんGetUserDefaultLCID()を使えば良いはずです。

ロケールの話は表示言語以上にやっかいな話で、特に浮動小数点数CSVファイルを扱うアプリの国際化対応をする*1ときは頭痛の種だったりするんですが、まあ興味がある人は各自調査して試してみてください。ちなみにもともと「ロケール」という用語は、C言語規格を制定するときに「ローカル」ではローカル変数と紛らわしいということで代わりに使われるようになった言葉らしいです*2。.NETでは「カルチャー」という用語が相当します。しかしヨーロッパ(の一部)も米国も、先進国でありながら国際標準を無視して時代遅れな単位系を未だに使ってるのは個人的に許せないというかまったくもって解せないですね……ヤード・ポンドみたいなゴミはさっさとドブに捨てればいいのに……

なお、MUI自体はXPの頃からある機能らしいんですが、XPのは使ったことはないです。Vista/7ではEnterprise/Ultimateエディションで使用できる機能なんですが、Windows Update経由で言語パックを無条件にインストールできるのはUltimateのみらしいですね。Enterprise版はシステム管理者によってWindows Update経由のインストール可否を制御されるらしいんですが、うちの会社では配信されてませんでした。ちなみにおうちのWin7 Ultimateにはテスト用に英語、ドイツ語、フランス語を入れてますが、正直ドイツ語とフランス語は分かりません(汗

*1:ヨーロッパなどでは、小数点にコンマ0x2cを使い、桁区切りにピリオド0x2eを使い、さらにCSVの区切り文字にはセミコロン0x3bを使います。個人的には区切り文字にコンマ0x2cを使うCSVよりもタブ文字0x09を使うTSV形式のほうがお勧めです。なお、画面表示はローカライズせざるを得ない場合でも、データをテキスト形式でファイルにシリアライズするときはローカライズしないほうが無難です。

*2:http://www.slideshare.net/ishisaka/windows-14269305