mirror of
https://github.com/reactos/reactos.git
synced 2025-04-22 05:00:27 +00:00

The locale table is currently hardcoded in rtl. On Vista+ (and Wine) this table is part of locale.nls, but we don't have that yet.
954 lines
29 KiB
C
954 lines
29 KiB
C
/*
|
|
* PROJECT: ReactOS runtime library
|
|
* LICENSE: MIT (https://spdx.org/licenses/MIT)
|
|
* PURPOSE: Rtl locale functions
|
|
* COPYRIGHT: Copyright 2025 Timo Kreuzer <timo.kreuzer@reactos.org>
|
|
*/
|
|
|
|
#include <rtl.h>
|
|
|
|
#define NDEBUG
|
|
#include <debug.h>
|
|
|
|
// See https://winprotocoldoc.z19.web.core.windows.net/MS-LCID/%5bMS-LCID%5d.pdf
|
|
// For special LCIDs see https://learn.microsoft.com/en-us/windows/win32/intl/locale-custom-constants
|
|
// and https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/926e694f-1797-4418-a922-343d1c5e91a6
|
|
// and https://learn.microsoft.com/en-us/windows/win32/intl/locale-identifiers
|
|
|
|
#define MAX_PRIMARY_LANGUAGE 0x3FF
|
|
#define MAX_BASIC_LCID 0x5FFF
|
|
|
|
#define LCID_ALT_NAME 0x00100000 // Alternative name, OR'ed with LCID
|
|
|
|
LCID RtlpUserDefaultLcid;
|
|
LCID RtlpSystemDefaultLcid;
|
|
|
|
typedef struct _LOCALE_ENTRY
|
|
{
|
|
const CHAR Locale[16];
|
|
LCID Lcid : 24;
|
|
ULONG Flags : 8;
|
|
} LOCALE_ENTRY;
|
|
|
|
// This table is sorted alphabetically to allow binary search
|
|
static const LOCALE_ENTRY RtlpLocaleTable[] =
|
|
{
|
|
{ "", 0x0007F }, // LOCALE_INVARIANT
|
|
{ "af", 0x00036 },
|
|
{ "af-ZA", 0x00436 },
|
|
{ "am", 0x0005E },
|
|
{ "am-ET", 0x0045E },
|
|
{ "ar", 0x00001 },
|
|
// { "ar-145", 0x04801 }, // reserved
|
|
{ "ar-AE", 0x03801 },
|
|
{ "ar-BH", 0x03C01 },
|
|
{ "ar-DZ", 0x01401 },
|
|
{ "ar-EG", 0x00C01 },
|
|
{ "ar-IQ", 0x00801 },
|
|
{ "ar-JO", 0x02C01 },
|
|
{ "ar-KW", 0x03401 },
|
|
{ "ar-LB", 0x03001 },
|
|
{ "ar-LY", 0x01001 },
|
|
{ "ar-MA", 0x01801 },
|
|
{ "ar-OM", 0x02001 },
|
|
// { "ar-Ploc-SA", 0x04401 }, // reserved
|
|
{ "ar-QA", 0x04001 },
|
|
{ "ar-SA", 0x00401 },
|
|
{ "ar-SY", 0x02801 },
|
|
{ "ar-TN", 0x01C01 },
|
|
{ "ar-YE", 0x02401 },
|
|
{ "arn", 0x0007A },
|
|
{ "arn-CL", 0x0047A },
|
|
{ "as", 0x0004D },
|
|
{ "as-IN", 0x0044D },
|
|
{ "az", 0x0002C },
|
|
{ "az-Cyrl", 0x0742C },
|
|
{ "az-Cyrl-AZ", 0x0082C }, // Doc: reserved
|
|
{ "az-Latn", 0x0782C },
|
|
{ "az-Latn-AZ", 0x0042C },
|
|
{ "ba", 0x0006D },
|
|
{ "ba-RU", 0x0046D },
|
|
{ "be", 0x00023 },
|
|
{ "be-BY", 0x00423 },
|
|
{ "bg", 0x00002 },
|
|
{ "bg-BG", 0x00402 },
|
|
{ "bin", 0x00066 }, // Doc: reserved
|
|
{ "bin-NG", 0x00466 }, // Doc: reserved
|
|
{ "bn", 0x00045 },
|
|
{ "bn-BD", 0x00845 },
|
|
{ "bn-IN", 0x00445 },
|
|
{ "bo", 0x00051 },
|
|
// { "bo-BT", 0x00851 }, // reserved
|
|
{ "bo-CN", 0x00451 },
|
|
{ "br", 0x0007E },
|
|
{ "br-FR", 0x0047E },
|
|
{ "bs", 0x0781A },
|
|
{ "bs-Cyrl", 0x0641A },
|
|
{ "bs-Cyrl-BA", 0x0201A },
|
|
{ "bs-Latn", 0x0681A },
|
|
{ "bs-Latn-BA", 0x0141A },
|
|
{ "ca", 0x00003 },
|
|
{ "ca-ES", 0x00403 },
|
|
{ "ca-ES-valencia", 0x00803 },
|
|
{ "chr", 0x0005C },
|
|
{ "chr-Cher", 0x07C5C },
|
|
{ "chr-Cher-US", 0x0045C },
|
|
{ "co", 0x00083 },
|
|
{ "co-FR", 0x00483 },
|
|
{ "cs", 0x00005 },
|
|
{ "cs-CZ", 0x00405 },
|
|
{ "cy", 0x00052 },
|
|
{ "cy-GB", 0x00452 },
|
|
{ "da", 0x00006 },
|
|
{ "da-DK", 0x00406 },
|
|
{ "de", 0x00007 },
|
|
{ "de-AT", 0x00C07 },
|
|
{ "de-CH", 0x00807 },
|
|
{ "de-DE", 0x00407 },
|
|
{ "de-DE_phoneb", 0x10407 },
|
|
{ "de-LI", 0x01407 },
|
|
{ "de-LU", 0x01007 },
|
|
{ "dsb", 0x07C2E },
|
|
{ "dsb-DE", 0x0082E },
|
|
{ "dv", 0x00065 },
|
|
{ "dv-MV", 0x00465 },
|
|
{ "dz-BT", 0x00C51 },
|
|
{ "el", 0x00008 },
|
|
{ "el-GR", 0x00408 },
|
|
{ "en", 0x00009 },
|
|
{ "en-029", 0x02409 }, // Doc: reserved
|
|
{ "en-AE", 0x04C09 },
|
|
{ "en-AU", 0x00C09 },
|
|
// { "en-BH", 0x05009 }, // reserved
|
|
{ "en-BZ", 0x02809 },
|
|
{ "en-CA", 0x01009 },
|
|
// { "en-EG", 0x05409 }, // reserved
|
|
{ "en-GB", 0x00809 },
|
|
{ "en-HK", 0x03C09 },
|
|
{ "en-ID", 0x03809 }, // reserved
|
|
{ "en-IE", 0x01809 },
|
|
{ "en-IN", 0x04009 },
|
|
{ "en-JM", 0x02009 },
|
|
// { "en-JO", 0x05809 }, // reserved
|
|
// { "en-KW", 0x05C09 }, // reserved
|
|
{ "en-MY", 0x04409 },
|
|
{ "en-NZ", 0x01409 },
|
|
{ "en-PH", 0x03409 },
|
|
{ "en-SG", 0x04809 },
|
|
// { "en-TR", 0x6009 }, // reserved
|
|
{ "en-TT", 0x02C09 },
|
|
{ "en-US", 0x00409 },
|
|
// { "en-YE", 0x6409 }, // reserved
|
|
{ "en-ZA", 0x01C09 },
|
|
{ "en-ZW", 0x03009 },
|
|
{ "es", 0x0000A },
|
|
{ "es-419", 0x0580A }, // Doc: reserved
|
|
{ "es-AR", 0x02C0A },
|
|
{ "es-BO", 0x0400A },
|
|
{ "es-CL", 0x0340A },
|
|
{ "es-CO", 0x0240A },
|
|
{ "es-CR", 0x0140A },
|
|
{ "es-CU", 0x05C0A },
|
|
{ "es-DO", 0x01C0A },
|
|
{ "es-EC", 0x0300A },
|
|
{ "es-ES", 0x00C0A },
|
|
{ "es-ES_tradnl", 0x0040A },
|
|
{ "es-GT", 0x0100A },
|
|
{ "es-HN", 0x0480A },
|
|
{ "es-MX", 0x0080A },
|
|
{ "es-NI", 0x04C0A },
|
|
{ "es-PA", 0x0180A },
|
|
{ "es-PE", 0x0280A },
|
|
{ "es-PR", 0x0500A },
|
|
{ "es-PY", 0x03C0A },
|
|
{ "es-SV", 0x0440A },
|
|
{ "es-US", 0x0540A },
|
|
{ "es-UY", 0x0380A },
|
|
{ "es-VE", 0x0200A },
|
|
{ "et", 0x00025 },
|
|
{ "et-EE", 0x00425 },
|
|
{ "eu", 0x0002D },
|
|
{ "eu-ES", 0x0042D },
|
|
{ "fa", 0x00029 },
|
|
{ "fa-IR", 0x00429 },
|
|
{ "ff", 0x00067 },
|
|
{ "ff-Latn", 0x07C67 },
|
|
{ "ff-Latn-NG", 0x00467 },
|
|
{ "ff-Latn-SN", 0x00867 },
|
|
{ "ff-NG", 0x00467 | LCID_ALT_NAME },
|
|
{ "fi", 0x0000B },
|
|
{ "fi-FI", 0x0040B },
|
|
{ "fil", 0x00064 },
|
|
{ "fil-PH", 0x00464 },
|
|
{ "fo", 0x00038 },
|
|
{ "fo-FO", 0x00438 },
|
|
{ "fr", 0x0000C },
|
|
// { "fr-015", 0x0E40C }, // reserved
|
|
{ "fr-029", 0x01C0C },
|
|
{ "fr-BE", 0x0080C },
|
|
{ "fr-CA", 0x00C0C },
|
|
{ "fr-CD", 0x0240C },
|
|
{ "fr-CH", 0x0100C },
|
|
{ "fr-CI", 0x0300C },
|
|
{ "fr-CM", 0x02C0C },
|
|
{ "fr-FR", 0x0040C },
|
|
{ "fr-HT", 0x03C0C },
|
|
{ "fr-LU", 0x0140C },
|
|
{ "fr-MA", 0x0380C },
|
|
{ "fr-MC", 0x0180C },
|
|
{ "fr-ML", 0x0340C },
|
|
{ "fr-RE", 0x0200C },
|
|
{ "fr-SN", 0x0280C },
|
|
{ "fy", 0x00062 },
|
|
{ "fy-NL", 0x00462 },
|
|
{ "ga", 0x0003C },
|
|
{ "ga-IE", 0x0083C },
|
|
{ "gd", 0x00091 },
|
|
{ "gd-GB", 0x00491 },
|
|
{ "gl", 0x00056 },
|
|
{ "gl-ES", 0x00456 },
|
|
{ "gn", 0x00074 },
|
|
{ "gn-PY", 0x00474 },
|
|
{ "gsw", 0x00084 },
|
|
{ "gsw-FR", 0x00484 },
|
|
{ "gu", 0x00047 },
|
|
{ "gu-IN", 0x00447 },
|
|
{ "ha", 0x00068 },
|
|
{ "ha-Latn", 0x07C68 },
|
|
{ "ha-Latn-NG", 0x00468 },
|
|
{ "haw", 0x00075 },
|
|
{ "haw-US", 0x00475 },
|
|
{ "he", 0x0000D },
|
|
{ "he-IL", 0x0040D },
|
|
{ "hi", 0x00039 },
|
|
{ "hi-IN", 0x00439 },
|
|
{ "hr", 0x0001A },
|
|
{ "hr-BA", 0x0101A },
|
|
{ "hr-HR", 0x0041A },
|
|
{ "hsb", 0x0002E },
|
|
{ "hsb-DE", 0x0042E },
|
|
{ "hu", 0x0000E },
|
|
{ "hu-HU", 0x0040E },
|
|
{ "hu-HU_technl", 0x1040E },
|
|
{ "hy", 0x0002B },
|
|
{ "hy-AM", 0x0042B },
|
|
{ "ibb", 0x00069 }, // Doc: reserved
|
|
{ "ibb-NG", 0x00469 }, // Doc: reserved
|
|
{ "id", 0x00021 },
|
|
{ "id-ID", 0x00421 },
|
|
{ "ig", 0x00070 },
|
|
{ "ig-NG", 0x00470 },
|
|
{ "ii", 0x00078 },
|
|
{ "ii-CN", 0x00478 },
|
|
{ "is", 0x0000F },
|
|
{ "is-IS", 0x0040F },
|
|
{ "it", 0x00010 },
|
|
{ "it-CH", 0x00810 },
|
|
{ "it-IT", 0x00410 },
|
|
{ "iu", 0x0005D },
|
|
{ "iu-Cans", 0x0785D },
|
|
{ "iu-Cans-CA", 0x0045D },
|
|
{ "iu-Latn", 0x07C5D },
|
|
{ "iu-Latn-CA", 0x0085D },
|
|
{ "ja", 0x00011 },
|
|
{ "ja-JP", 0x00411 },
|
|
{ "ja-JP_radstr", 0x040411 },
|
|
// { "ja-Ploc-JP", 0x00811 }, // reserved
|
|
{ "ka", 0x00037 },
|
|
{ "ka-GE", 0x00437 },
|
|
{ "ka-GE_modern", 0x10437 },
|
|
// { "khb-Talu-CN", 0x00490 }, // reserved
|
|
{ "kk", 0x0003F },
|
|
{ "kk-Cyrl", 0x0003F | LCID_ALT_NAME }, // Doc: 0x0783F, reserved
|
|
{ "kk-KZ", 0x0043F },
|
|
// { "kk-Latn", 0x07C3F }, // reserved
|
|
// { "kk-Latn-KZ", 0x0083F }, // reserved
|
|
{ "kl", 0x0006F },
|
|
{ "kl-GL", 0x0046F },
|
|
{ "km", 0x00053 },
|
|
{ "km-KH", 0x00453 },
|
|
{ "kn", 0x0004B },
|
|
{ "kn-IN", 0x0044B },
|
|
{ "ko", 0x00012 },
|
|
{ "ko-KR", 0x00412 },
|
|
{ "kok", 0x00057 },
|
|
{ "kok-IN", 0x00457 },
|
|
{ "kr", 0x00071 }, // Doc: reserved
|
|
{ "kr-Latn-NG", 0x00471 },
|
|
{ "ks", 0x00060 },
|
|
{ "ks-Arab", 0x00460 }, // Neutral locale
|
|
{ "ks-Deva-IN", 0x00860 },
|
|
{ "ku", 0x00092 },
|
|
{ "ku-Arab", 0x07C92 },
|
|
{ "ku-Arab-IQ", 0x00492 },
|
|
{ "ky", 0x00040 },
|
|
{ "ky-KG", 0x00440 },
|
|
{ "la", 0x00076 }, // reserved
|
|
{ "la-001", 0x00476 }, // Doc: la-VA
|
|
{ "lb", 0x0006E },
|
|
{ "lb-LU", 0x0046E },
|
|
{ "lo", 0x00054 },
|
|
{ "lo-LA", 0x00454 },
|
|
{ "lt", 0x00027 },
|
|
{ "lt-LT", 0x00427 },
|
|
{ "lv", 0x00026 },
|
|
{ "lv-LV", 0x00426 },
|
|
{ "mi", 0x00081 },
|
|
{ "mi-NZ", 0x00481 },
|
|
{ "mk", 0x0002F },
|
|
{ "mk-MK", 0x0042F },
|
|
{ "ml", 0x0004C },
|
|
{ "ml-IN", 0x0044C },
|
|
{ "mn", 0x00050 },
|
|
{ "mn-Cyrl", 0x07850 },
|
|
{ "mn-MN", 0x00450 },
|
|
{ "mn-Mong", 0x07C50 },
|
|
{ "mn-Mong-CN", 0x00850 }, // Doc: reserved
|
|
{ "mn-Mong-MN", 0x00C50 },
|
|
{ "mni", 0x00058 }, // Doc: reserved
|
|
{ "mni-IN", 0x00458 }, // Doc: reserved
|
|
{ "moh", 0x0007C },
|
|
{ "moh-CA", 0x0047C },
|
|
{ "mr", 0x0004E },
|
|
{ "mr-IN", 0x0044E },
|
|
{ "ms", 0x0003E },
|
|
{ "ms-BN", 0x0083E },
|
|
{ "ms-MY", 0x0043E },
|
|
{ "mt", 0x0003A },
|
|
{ "mt-MT", 0x0043A },
|
|
{ "my", 0x00055 },
|
|
{ "my-MM", 0x00455 },
|
|
{ "nb", 0x07C14 },
|
|
{ "nb-NO", 0x00414 },
|
|
{ "ne", 0x00061 },
|
|
{ "ne-IN", 0x00861 },
|
|
{ "ne-NP", 0x00461 },
|
|
{ "nl", 0x00013 },
|
|
{ "nl-BE", 0x00813 },
|
|
{ "nl-NL", 0x00413 },
|
|
{ "nn", 0x07814 },
|
|
{ "nn-NO", 0x00814 },
|
|
{ "no", 0x00014 },
|
|
{ "nso", 0x0006C },
|
|
{ "nso-ZA", 0x0046C },
|
|
{ "oc", 0x00082 },
|
|
{ "oc-FR", 0x00482 },
|
|
{ "om", 0x00072 },
|
|
{ "om-ET", 0x00472 },
|
|
{ "or", 0x00048 },
|
|
{ "or-IN", 0x00448 },
|
|
{ "pa", 0x00046 },
|
|
{ "pa-Arab", 0x07C46 },
|
|
{ "pa-Arab-PK", 0x00846 },
|
|
{ "pa-IN", 0x00446 },
|
|
{ "pap", 0x00079 }, // Doc: reserved
|
|
{ "pap-029", 0x00479 }, // Doc: reserved
|
|
{ "pl", 0x00015 },
|
|
{ "pl-PL", 0x00415 },
|
|
// { "plt-MG", 0x0048D }, // reserved
|
|
{ "prs", 0x0008C },
|
|
{ "prs-AF", 0x0048C },
|
|
{ "ps", 0x00063 },
|
|
{ "ps-AF", 0x00463 },
|
|
{ "pt", 0x00016 },
|
|
{ "pt-BR", 0x00416 },
|
|
{ "pt-PT", 0x00816 },
|
|
{ "qps-ploc", 0x00501 },
|
|
{ "qps-ploca", 0x005FE },
|
|
{ "qps-plocm", 0x009FF },
|
|
{ "quc", 0x00086 }, // Doc: 0x00093, reserved
|
|
// { "quc-CO", 0x00493 }, // reserved
|
|
{ "quc-Latn-GT", 0x00486 }, // Doc: qut-GT, reserved
|
|
{ "qut", 0x00086 | LCID_ALT_NAME },
|
|
{ "qut-GT", 0x00486 | LCID_ALT_NAME }, // reserved
|
|
{ "quz", 0x0006B },
|
|
{ "quz-BO", 0x0046B },
|
|
{ "quz-EC", 0x0086B },
|
|
{ "quz-PE", 0x00C6b },
|
|
{ "rm", 0x00017 },
|
|
{ "rm-CH", 0x00417 },
|
|
{ "ro", 0x00018 },
|
|
{ "ro-MD", 0x00818 },
|
|
{ "ro-RO", 0x00418 },
|
|
{ "ru", 0x00019 },
|
|
{ "ru-MD", 0x00819 },
|
|
{ "ru-RU", 0x00419 },
|
|
{ "rw", 0x00087 },
|
|
{ "rw-RW", 0x00487 },
|
|
{ "sa", 0x0004F },
|
|
{ "sa-IN", 0x0044F },
|
|
{ "sah", 0x00085 },
|
|
{ "sah-RU", 0x00485 },
|
|
{ "sd", 0x00059 },
|
|
{ "sd-Arab", 0x07C59 },
|
|
{ "sd-Arab-PK", 0x00859 },
|
|
{ "sd-Deva-IN", 0x00459 }, // Doc: reserved
|
|
{ "se", 0x0003B },
|
|
{ "se-FI", 0x00C3B },
|
|
{ "se-NO", 0x0043B },
|
|
{ "se-SE", 0x0083B },
|
|
{ "si", 0x0005B },
|
|
{ "si-LK", 0x0045B },
|
|
{ "sk", 0x0001B },
|
|
{ "sk-SK", 0x0041B },
|
|
{ "sl", 0x00024 },
|
|
{ "sl-SI", 0x00424 },
|
|
{ "sma", 0x0783B },
|
|
{ "sma-NO", 0x0183B },
|
|
{ "sma-SE", 0x01C3B },
|
|
{ "smj", 0x07C3B },
|
|
{ "smj-NO", 0x0103B },
|
|
{ "smj-SE", 0x0143B },
|
|
{ "smn", 0x0703B },
|
|
{ "smn-FI", 0x0243B },
|
|
{ "sms", 0x0743B },
|
|
{ "sms-FI", 0x0203B },
|
|
{ "so", 0x00077 }, // Doc: reserved
|
|
{ "so-SO", 0x00477 },
|
|
{ "sq", 0x0001C },
|
|
{ "sq-AL", 0x0041C },
|
|
{ "sr", 0x07C1A },
|
|
{ "sr-Cyrl", 0x06C1A },
|
|
{ "sr-Cyrl-BA", 0x01C1A },
|
|
{ "sr-Cyrl-CS", 0x00C1A },
|
|
{ "sr-Cyrl-ME", 0x0301A },
|
|
{ "sr-Cyrl-RS", 0x0281A },
|
|
{ "sr-Latn", 0x0701A },
|
|
{ "sr-Latn-BA", 0x0181A },
|
|
{ "sr-Latn-CS", 0x0081A },
|
|
{ "sr-Latn-ME", 0x02C1A },
|
|
{ "sr-Latn-RS", 0x0241A },
|
|
{ "st", 0x00030 },
|
|
{ "st-ZA", 0x00430 },
|
|
{ "sv", 0x0001D },
|
|
{ "sv-FI", 0x0081D },
|
|
{ "sv-SE", 0x0041D },
|
|
{ "sw", 0x00041 },
|
|
{ "sw-KE", 0x00441 },
|
|
{ "syr", 0x0005A },
|
|
{ "syr-SY", 0x0045A },
|
|
{ "ta", 0x00049 },
|
|
{ "ta-IN", 0x00449 },
|
|
{ "ta-LK", 0x00849 },
|
|
// { "tdd-Tale-CN", 0x0048F }, // reserved
|
|
{ "te", 0x0004A },
|
|
{ "te-IN", 0x0044A },
|
|
{ "tg", 0x00028 },
|
|
{ "tg-Cyrl", 0x07C28 },
|
|
{ "tg-Cyrl-TJ", 0x00428 },
|
|
{ "th", 0x0001E },
|
|
{ "th-TH", 0x0041E },
|
|
{ "ti", 0x00073 },
|
|
{ "ti-ER", 0x00873 },
|
|
{ "ti-ET", 0x00473 },
|
|
{ "tk", 0x00042 },
|
|
{ "tk-TM", 0x00442 },
|
|
// { "tmz-MA", 0x00C5F }, // reserved
|
|
{ "tn", 0x00032 },
|
|
{ "tn-BW", 0x00832 },
|
|
{ "tn-ZA", 0x00432 },
|
|
{ "tr", 0x0001F },
|
|
{ "tr-TR", 0x0041F },
|
|
{ "ts", 0x00031 },
|
|
{ "ts-ZA", 0x00431 },
|
|
{ "tt", 0x00044 },
|
|
{ "tt-RU", 0x00444 },
|
|
{ "tzm", 0x0005F },
|
|
{ "tzm-Arab-MA", 0x0045F },
|
|
{ "tzm-Latn", 0x07C5F },
|
|
{ "tzm-Latn-DZ", 0x0085F },
|
|
{ "tzm-Tfng", 0x0785F },
|
|
{ "tzm-Tfng-MA", 0x0105F },
|
|
{ "ug", 0x00080 },
|
|
{ "ug-CN", 0x00480 },
|
|
{ "uk", 0x00022 },
|
|
{ "uk-UA", 0x00422 },
|
|
{ "ur", 0x00020 },
|
|
{ "ur-IN", 0x00820 },
|
|
{ "ur-PK", 0x00420 },
|
|
{ "uz", 0x00043 },
|
|
{ "uz-Cyrl", 0x07843 },
|
|
{ "uz-Cyrl-UZ", 0x00843 }, // Doc: reserved
|
|
{ "uz-Latn", 0x07C43 },
|
|
{ "uz-Latn-UZ", 0x00443 },
|
|
{ "ve", 0x00033 },
|
|
{ "ve-ZA", 0x00433 },
|
|
{ "vi", 0x0002A },
|
|
{ "vi-VN", 0x0042A },
|
|
{ "wo", 0x00088 },
|
|
{ "wo-SN", 0x00488 },
|
|
{ "xh", 0x00034 },
|
|
{ "xh-ZA", 0x00434 },
|
|
{ "yi", 0x0003D }, // Doc: reserved
|
|
{ "yi-001", 0x0043D },
|
|
{ "yo", 0x0006A },
|
|
{ "yo-NG", 0x0046A },
|
|
{ "zh", 0x07804 },
|
|
{ "zh-CN", 0x00804 },
|
|
{ "zh-CN_stroke", 0x20804 },
|
|
{ "zh-Hans", 0x00004 },
|
|
{ "zh-Hant", 0x07C04 },
|
|
{ "zh-HK", 0x00C04 },
|
|
{ "zh-HK_radstr", 0x40C04 },
|
|
{ "zh-MO", 0x01404 },
|
|
{ "zh-MO_radstr", 0x41404 },
|
|
{ "zh-SG", 0x01004 },
|
|
{ "zh-SG_stroke", 0x21004 },
|
|
{ "zh-TW", 0x00404 },
|
|
{ "zh-TW_pronun", 0x30404 },
|
|
{ "zh-TW_radstr", 0x40404 },
|
|
// { "zh-yue-HK", 0x0048E }, // reserved
|
|
{ "zu", 0x00035 },
|
|
{ "zu-ZA", 0x00435 },
|
|
};
|
|
|
|
// This table will be sorted by LCID at runtime
|
|
static USHORT RtlpLocaleIndexTable[_ARRAYSIZE(RtlpLocaleTable)];
|
|
|
|
typedef struct _SORT_ENTRY
|
|
{
|
|
LCID Lcid;
|
|
USHORT Index;
|
|
} SORT_ENTRY, *PSORT_ENTRY;
|
|
|
|
// Callback function for qsort to sort the SORT_ENTRY table by LCID
|
|
static int __cdecl LcidSortEntryCompare(const void* a, const void* b)
|
|
{
|
|
PSORT_ENTRY SortEntryA = (PSORT_ENTRY)a;
|
|
PSORT_ENTRY SortEntryB = (PSORT_ENTRY)b;
|
|
return SortEntryA->Lcid - SortEntryB->Lcid;
|
|
}
|
|
|
|
//
|
|
// Creates a temporary table, that maps the LCIDs to the index in the
|
|
// alphabetical table. This table is then sorted by LCID and the indices
|
|
// are used to create the final table.
|
|
//
|
|
NTSTATUS
|
|
NTAPI
|
|
RtlpInitializeLocaleTable(VOID)
|
|
{
|
|
PSORT_ENTRY SortTable;
|
|
SIZE_T SortTableSize;
|
|
|
|
NtQueryDefaultLocale(TRUE, &RtlpUserDefaultLcid);
|
|
NtQueryDefaultLocale(FALSE, &RtlpSystemDefaultLcid);
|
|
|
|
SortTableSize = sizeof(SortTable[0]) * ARRAYSIZE(RtlpLocaleTable);
|
|
SortTable = RtlAllocateHeap(RtlGetProcessHeap(), 0, SortTableSize);
|
|
if (SortTable == NULL)
|
|
{
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
/* Copy the LCIDs and the index */
|
|
for (USHORT i = 0; i < ARRAYSIZE(RtlpLocaleTable); i++)
|
|
{
|
|
SortTable[i].Lcid = RtlpLocaleTable[i].Lcid;
|
|
SortTable[i].Index = i;
|
|
}
|
|
|
|
/* Sort the table by LCID */
|
|
qsort(SortTable,
|
|
ARRAYSIZE(RtlpLocaleTable),
|
|
sizeof(SortTable[0]),
|
|
LcidSortEntryCompare);
|
|
|
|
/* Copy the sorted indices to the global table */
|
|
for (USHORT i = 0; i < ARRAYSIZE(RtlpLocaleTable); i++)
|
|
{
|
|
RtlpLocaleIndexTable[i] = SortTable[i].Index;
|
|
}
|
|
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, SortTable);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
_Must_inspect_result_
|
|
static
|
|
ULONG
|
|
FindIndexByLcid(
|
|
_In_ LCID Lcid)
|
|
{
|
|
USHORT TableIndex;
|
|
LONG Low = 0;
|
|
LONG High = ARRAYSIZE(RtlpLocaleTable) - 1;
|
|
LONG Middle;
|
|
LCID CurrentLcid;
|
|
|
|
while (Low <= High)
|
|
{
|
|
Middle = (Low + High) / 2;
|
|
|
|
/* Use the indirection table to get the real table entry */
|
|
TableIndex = RtlpLocaleIndexTable[Middle];
|
|
ASSERT(TableIndex < ARRAYSIZE(RtlpLocaleTable));
|
|
|
|
/* Compare the LCID (including the alternative name flag!) */
|
|
CurrentLcid = RtlpLocaleTable[TableIndex].Lcid;
|
|
if (CurrentLcid < Lcid)
|
|
{
|
|
Low = Middle + 1;
|
|
}
|
|
else if (CurrentLcid > Lcid)
|
|
{
|
|
High = Middle - 1;
|
|
}
|
|
else /* CurrentLcid == Lcid */
|
|
{
|
|
return RtlpLocaleIndexTable[Middle];
|
|
}
|
|
}
|
|
|
|
return MAXULONG;
|
|
}
|
|
|
|
#define LOWERCASE_CHAR(Char) \
|
|
(((Char) >= 'A' && (Char) <= 'Z') ? ((Char) + ('a' - 'A')) : (Char))
|
|
|
|
_Must_inspect_result_
|
|
static
|
|
INT
|
|
CompareLocaleNames(
|
|
_In_ PCSTR AsciiLocaleName,
|
|
_In_ PCUNICODE_STRING UnicodeLocaleName)
|
|
{
|
|
ULONG i;
|
|
|
|
/* Do a case-insensitive comparison. */
|
|
for (i = 0; i < UnicodeLocaleName->Length / sizeof(WCHAR); i++)
|
|
{
|
|
/* Make the 2 chars lower case for comparison */
|
|
CHAR Char1 = LOWERCASE_CHAR(AsciiLocaleName[i]);
|
|
WCHAR Char2 = LOWERCASE_CHAR(UnicodeLocaleName->Buffer[i]);
|
|
|
|
/* Keep comparing, while they are equal */
|
|
if (Char1 == Char2)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/* Check if the ASCII string ends first */
|
|
if (AsciiLocaleName[i] == '\0')
|
|
{
|
|
/* The 1st string is shorter than the 2nd, i.e. smaller */
|
|
return -1;
|
|
}
|
|
|
|
/* Return the difference between the two characters */
|
|
return (INT)Char1 - (INT)Char2;
|
|
}
|
|
|
|
/* The strings match up to the lenth of the unicode string.
|
|
If the ASCII string ends here, they are equal. */
|
|
if (AsciiLocaleName[i] == '\0')
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* The 1st string is longer than the 2nd, i.e. larger */
|
|
return 1;
|
|
}
|
|
|
|
_Must_inspect_result_
|
|
static
|
|
ULONG
|
|
FindIndexByLocaleName(
|
|
_In_ PCUNICODE_STRING LocaleName)
|
|
{
|
|
LONG Low = 0;
|
|
LONG High = ARRAYSIZE(RtlpLocaleTable) - 1;
|
|
LONG Middle;
|
|
PCSTR CurrentLocaleName;
|
|
INT CompareResult;
|
|
|
|
while (Low <= High)
|
|
{
|
|
Middle = (Low + High) / 2;
|
|
|
|
CurrentLocaleName = RtlpLocaleTable[Middle].Locale;
|
|
CompareResult = CompareLocaleNames(CurrentLocaleName, LocaleName);
|
|
if (CompareResult < 0)
|
|
{
|
|
Low = Middle + 1;
|
|
}
|
|
else if (CompareResult > 0)
|
|
{
|
|
High = Middle - 1;
|
|
}
|
|
else /* CompareResult == 0 */
|
|
{
|
|
return Middle;
|
|
}
|
|
}
|
|
|
|
return MAXULONG;
|
|
}
|
|
|
|
_Must_inspect_result_
|
|
static
|
|
BOOLEAN
|
|
CopyAsciizToUnicodeString(
|
|
_Inout_ PUNICODE_STRING UnicodeString,
|
|
_In_ PCSTR AsciiString)
|
|
{
|
|
SIZE_T AsciiLength = strlen(AsciiString);
|
|
|
|
/* Make sure we can copy the full string, including the terminating 0 */
|
|
if (UnicodeString->MaximumLength < (AsciiLength + 1) * sizeof(WCHAR))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
/* Copy the string manually */
|
|
for (SIZE_T i = 0; i < AsciiLength; i++)
|
|
{
|
|
UnicodeString->Buffer[i] = (WCHAR)AsciiString[i];
|
|
}
|
|
|
|
/* Add the terminating 0 and update the Length */
|
|
UnicodeString->Buffer[AsciiLength] = UNICODE_NULL;
|
|
UnicodeString->Length = (USHORT)(AsciiLength * sizeof(WCHAR));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static
|
|
BOOLEAN
|
|
IsNeutralLocale(
|
|
_In_ LCID Lcid)
|
|
{
|
|
/* Check if the LCID is within the neutral locale range */
|
|
if (((Lcid <= MAX_PRIMARY_LANGUAGE) && (Lcid != LOCALE_INVARIANT)) ||
|
|
(Lcid == 0x0460) /* ks-Arab */ ||
|
|
((Lcid & 0xFFFF) > MAX_BASIC_LCID))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
RtlLcidToLocaleName(
|
|
_In_ LCID Lcid,
|
|
_Inout_ PUNICODE_STRING LocaleName,
|
|
_In_ ULONG Flags,
|
|
_In_ BOOLEAN AllocateDestinationString)
|
|
{
|
|
ULONG LocaleIndex;
|
|
|
|
/* Check for invalid flags */
|
|
if (Flags & ~0x2)
|
|
{
|
|
DPRINT1("RtlLcidToLocaleName: Invalid flags: 0x%lx\n", Flags);
|
|
return STATUS_INVALID_PARAMETER_3;
|
|
}
|
|
|
|
/* Check if the LocaleName buffer is valid */
|
|
if ((LocaleName == NULL) || (LocaleName->Buffer == NULL))
|
|
{
|
|
DPRINT1("RtlLcidToLocaleName: Invalid buffer\n");
|
|
return STATUS_INVALID_PARAMETER_2;
|
|
}
|
|
|
|
/* Validate LCID */
|
|
if (Lcid & ~NLS_VALID_LOCALE_MASK)
|
|
{
|
|
DPRINT1("RtlLcidToLocaleName: Invalid LCID: 0x%lx\n", Lcid);
|
|
return STATUS_INVALID_PARAMETER_1;
|
|
}
|
|
|
|
/* Check if neutral locales were requested */
|
|
if ((Flags & RTL_LOCALE_ALLOW_NEUTRAL_NAMES) == 0)
|
|
{
|
|
/* Check if this is a neutral locale */
|
|
if (IsNeutralLocale(Lcid))
|
|
{
|
|
DPRINT("RtlLcidToLocaleName: Neutral LCID: 0x%lx\n", Lcid);
|
|
return STATUS_INVALID_PARAMETER_1;
|
|
}
|
|
}
|
|
|
|
/* Handle special LCIDs */
|
|
switch (Lcid)
|
|
{
|
|
case LOCALE_USER_DEFAULT:
|
|
Lcid = RtlpUserDefaultLcid;
|
|
break;
|
|
|
|
case LOCALE_SYSTEM_DEFAULT:
|
|
case LOCALE_CUSTOM_DEFAULT:
|
|
Lcid = RtlpSystemDefaultLcid;
|
|
break;
|
|
|
|
case LOCALE_CUSTOM_UI_DEFAULT:
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
/* Try to find the locale by LCID */
|
|
LocaleIndex = FindIndexByLcid(Lcid);
|
|
if (LocaleIndex == MAXULONG)
|
|
{
|
|
DPRINT("RtlLcidToLocaleName: LCID 0x%lx not found\n", Lcid);
|
|
return STATUS_INVALID_PARAMETER_1;
|
|
}
|
|
|
|
/* Copy the locale name to the buffer */
|
|
if (!CopyAsciizToUnicodeString(LocaleName, RtlpLocaleTable[LocaleIndex].Locale))
|
|
{
|
|
DPRINT("RtlLcidToLocaleName: Buffer too small\n");
|
|
return STATUS_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
_Must_inspect_result_
|
|
static
|
|
NTSTATUS
|
|
RtlpLocaleNameToLcidInternal(
|
|
_In_ PCUNICODE_STRING LocaleName,
|
|
_Out_ PLCID Lcid,
|
|
_In_ ULONG Flags)
|
|
{
|
|
ULONG LocaleIndex;
|
|
LCID FoundLcid;
|
|
|
|
/* Check if LocaleName points to a valid unicode string */
|
|
if ((LocaleName == NULL) || (LocaleName->Buffer == NULL))
|
|
{
|
|
DPRINT1("RtlpLocaleNameToLcidInternal: Invalid buffer\n");
|
|
return STATUS_INVALID_PARAMETER_1;
|
|
}
|
|
|
|
/* Check if the Lcid pointer is valid */
|
|
if (Lcid == NULL)
|
|
{
|
|
DPRINT1("RtlpLocaleNameToLcidInternal: Lcid is NULL\n");
|
|
return STATUS_INVALID_PARAMETER_2;
|
|
}
|
|
|
|
/* Check for invalid flags */
|
|
if (Flags & ~0x3)
|
|
{
|
|
DPRINT1("RtlpLocaleNameToLcidInternal: Invalid flags: 0x%lx\n", Flags);
|
|
return STATUS_INVALID_PARAMETER_3;
|
|
}
|
|
|
|
/* Try to find the locale */
|
|
LocaleIndex = FindIndexByLocaleName(LocaleName);
|
|
if (LocaleIndex == MAXULONG)
|
|
{
|
|
DPRINT("RtlpLocaleNameToLcidInternal: Locale name not found\n");
|
|
return STATUS_INVALID_PARAMETER_1;
|
|
}
|
|
|
|
/* Extract the LCID without the flags */
|
|
FoundLcid = RtlpLocaleTable[LocaleIndex].Lcid & NLS_VALID_LOCALE_MASK;
|
|
|
|
/* Check if neutral locales were requested */
|
|
if ((Flags & RTL_LOCALE_ALLOW_NEUTRAL_NAMES) == 0)
|
|
{
|
|
/* Check if this is a neutral locale */
|
|
if (IsNeutralLocale(FoundLcid))
|
|
{
|
|
DPRINT("RtlpLocaleNameToLcidInternal: Neutral LCID: 0x%lx\n", FoundLcid);
|
|
return STATUS_INVALID_PARAMETER_1;
|
|
}
|
|
}
|
|
|
|
/* Copy the LCID to the output buffer */
|
|
*Lcid = FoundLcid;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
RtlLocaleNameToLcid(
|
|
_In_ PCWSTR LocaleName,
|
|
_Out_ PLCID Lcid,
|
|
_In_ ULONG Flags)
|
|
{
|
|
UNICODE_STRING LocaleNameString;
|
|
|
|
/* Convert the string to a UNICODE_STRING */
|
|
RtlInitUnicodeString(&LocaleNameString, LocaleName);
|
|
|
|
/* Forward to internal function */
|
|
return RtlpLocaleNameToLcidInternal(&LocaleNameString, Lcid, Flags);
|
|
}
|
|
|
|
_Success_(return != FALSE)
|
|
BOOLEAN
|
|
NTAPI
|
|
RtlLCIDToCultureName(
|
|
_In_ LCID Lcid,
|
|
_Inout_ PUNICODE_STRING String)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
/* Forward to RtlLcidToLocaleName, include neutral names */
|
|
Status = RtlLcidToLocaleName(Lcid, String, RTL_LOCALE_ALLOW_NEUTRAL_NAMES, FALSE);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("RtlLcidToLocaleName failed with status 0x%lx\n", Status);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
_Success_(return != FALSE)
|
|
BOOLEAN
|
|
NTAPI
|
|
RtlCultureNameToLCID(
|
|
_In_ PCUNICODE_STRING String,
|
|
_Out_ PLCID Lcid)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
if ((String == NULL) ||
|
|
(String->Buffer == NULL) ||
|
|
(String->Buffer[0] == UNICODE_NULL))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
/* Forward to internal function, include neutral names */
|
|
Status = RtlpLocaleNameToLcidInternal(String, Lcid, RTL_LOCALE_ALLOW_NEUTRAL_NAMES);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("RtlpLocaleNameToLcidInternal failed with status 0x%lx\n", Status);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOLEAN
|
|
NTAPI
|
|
RtlIsValidLocaleName(
|
|
_In_ LPCWSTR LocaleName,
|
|
_In_ ULONG Flags)
|
|
{
|
|
UNIMPLEMENTED;
|
|
return TRUE;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
RtlConvertLCIDToString(
|
|
_In_ LCID LcidValue,
|
|
_In_ ULONG Base,
|
|
_In_ ULONG Padding,
|
|
_Out_writes_(Size) PWSTR pResultBuf,
|
|
_In_ ULONG Size)
|
|
{
|
|
UNIMPLEMENTED;
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|