1/// Windows LCIDs keyed by BCP-47 language, `language-Script` or `language-REGION`; a bare
2/// language maps to the locale Windows picks for it.
3const LCIDS: &[(&str, u32)] = &[
4 ("af", 1078),
5 ("am", 1118),
6 ("ar", 1025),
7 ("ar-EG", 3073),
8 ("az", 1068),
9 ("be", 1059),
10 ("bg", 1026),
11 ("bn", 1093),
12 ("bn-BD", 2117),
13 ("ca", 1027),
14 ("cs", 1029),
15 ("cy", 1106),
16 ("da", 1030),
17 ("de", 1031),
18 ("de-AT", 3079),
19 ("de-CH", 2055),
20 ("de-LI", 5127),
21 ("de-LU", 4103),
22 ("el", 1032),
23 ("en", 1033),
24 ("en-AU", 3081),
25 ("en-CA", 4105),
26 ("en-GB", 2057),
27 ("en-IE", 6153),
28 ("en-IN", 16393),
29 ("en-NZ", 5129),
30 ("en-SG", 18441),
31 ("en-ZA", 7177),
32 ("es", 3082),
33 ("es-MX", 2058),
34 ("es-US", 21514),
35 ("et", 1061),
36 ("eu", 1069),
37 ("fa", 1065),
38 ("fi", 1035),
39 ("fil", 1124),
40 ("fr", 1036),
41 ("fr-BE", 2060),
42 ("fr-CA", 3084),
43 ("fr-CH", 4108),
44 ("gl", 1110),
45 ("gu", 1095),
46 ("he", 1037),
47 ("hi", 1081),
48 ("hr", 1050),
49 ("hu", 1038),
50 ("hy", 1067),
51 ("id", 1057),
52 ("is", 1039),
53 ("it", 1040),
54 ("it-CH", 2064),
55 ("ja", 1041),
56 ("ka", 1079),
57 ("kk", 1087),
58 ("km", 1107),
59 ("kn", 1099),
60 ("ko", 1042),
61 ("lo", 1108),
62 ("lt", 1063),
63 ("lv", 1062),
64 ("mk", 1071),
65 ("ml", 1100),
66 ("mn", 1104),
67 ("mr", 1102),
68 ("ms", 1086),
69 ("my", 1109),
70 ("nb", 1044),
71 ("ne", 1121),
72 ("nl", 1043),
73 ("nl-BE", 2067),
74 ("nn", 2068),
75 ("no", 1044),
76 ("pa", 1094),
77 ("pl", 1045),
78 ("pt", 1046),
79 ("pt-PT", 2070),
80 ("ro", 1048),
81 ("ru", 1049),
82 ("si", 1115),
83 ("sk", 1051),
84 ("sl", 1060),
85 ("sq", 1052),
86 ("sr", 10266),
87 ("sr-Latn", 9242),
88 ("sv", 1053),
89 ("sv-FI", 2077),
90 ("sw", 1089),
91 ("ta", 1097),
92 ("te", 1098),
93 ("th", 1054),
94 ("tr", 1055),
95 ("uk", 1058),
96 ("ur", 1056),
97 ("vi", 1066),
98 ("zh", 2052),
99 ("zh-HK", 3076),
100 ("zh-Hant", 1028),
101 ("zh-MO", 5124),
102 ("zh-SG", 4100),
103 ("zh-TW", 1028),
104];
105
106/// en-US, what OneNote records when nothing better is known.
107pub(crate) const EN_US: u32 = 1033;
108
109/// The Windows LCID of a BCP-47 tag such as an input source's language (`de`, `en-GB`,
110/// `zh-Hans`), matched by region, then script, then language, else en-US.
111pub fn lcid(tag: &str) -> u32 {
112 let mut subtags = tag.split(['-', '_']);
113 let language = subtags.next().unwrap_or_default();
114 let (mut script, mut region) = (None, None);
115 for subtag in subtags {
116 match subtag.len() {
117 4 => script = Some(subtag),
118 2 | 3 => region = Some(subtag),
119 _ => {}
120 }
121 }
122 [region, script]
123 .into_iter()
124 .flatten()
125 .map(|subtag| format!("{language}-{subtag}"))
126 .chain([language.to_owned()])
127 .find_map(|key| {
128 LCIDS
129 .iter()
130 .find(|(tag, _)| tag.eq_ignore_ascii_case(&key))
131 .map(|(_, lcid)| *lcid)
132 })
133 .unwrap_or(EN_US)
134}
135
136/// The BCP-47 tag of a Windows LCID; a bare language stands for the locale Windows picks
137/// for it. None for LCIDs outside the table, such as math's.
138pub(crate) fn tag(lcid: u32) -> Option<&'static str> {
139 LCIDS
140 .iter()
141 .find(|(_, known)| *known == lcid)
142 .map(|(tag, _)| *tag)
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn tags_map_to_windows_lcids() {
151 for (tag, lcid) in [
152 ("en", 1033),
153 ("en-US", 1033),
154 ("en_GB", 2057),
155 ("de", 1031),
156 ("DE-at", 3079),
157 ("fr-FR", 1036),
158 ("es-419", 3082),
159 ("ja-JP", 1041),
160 ("zh-Hans", 2052),
161 ("zh-Hant", 1028),
162 ("zh-Hant-HK", 3076),
163 ("zh-Hans-SG", 4100),
164 ("sr-Latn-RS", 9242),
165 ("emoji", EN_US),
166 ("", EN_US),
167 ] {
168 assert_eq!(super::lcid(tag), lcid, "{tag}");
169 }
170 for (lcid, tag) in [(1033, Some("en")), (2057, Some("en-GB")), (0x1007f, None)] {
171 assert_eq!(super::tag(lcid), tag);
172 }
173 }
174}