1
2#include <stdlib.h>
3#include <string.h>
4#include <ctype.h>
5
6#ifdef WIN32
7#include <windows.h>
8#include <winnt.h>
9#endif
10
11#include "findlocale.h"
12
13static int
14is_lcchar(const int c) {
15 return isalnum(c);
16}
17
18static void
19lang_country_variant_from_envstring(const char *str,
20 char **lang,
21 char **country,
22 char **variant) {
23 int end = 0;
24 int start;
25
26 /* get lang, if any */
27 start = end;
28 while (is_lcchar(str[end])) {
29 ++end;
30 }
31 if (start != end) {
32 int i;
33 int len = end - start;
34 char *s = malloc(len + 1);
35 for (i=0; i<len; ++i) {
36 s[i] = tolower(str[start + i]);
37 }
38 s[i] = '\0';
39 *lang = s;
40 } else {
41 *lang = NULL;
42 }
43
44 if (str[end] && str[end]!=':') { /* not at end of str */
45 ++end;
46 }
47
48 /* get country, if any */
49 start = end;
50 while (is_lcchar(str[end])) {
51 ++end;
52 }
53 if (start != end) {
54 int i;
55 int len = end - start;
56 char *s = malloc(len + 1);
57 for (i=0; i<len; ++i) {
58 s[i] = toupper(str[start + i]);
59 }
60 s[i] = '\0';
61 *country = s;
62 } else {
63 *country = NULL;
64 }
65
66 if (str[end] && str[end]!=':') { /* not at end of str */
67 ++end;
68 }
69
70 /* get variant, if any */
71 start = end;
72 while (str[end] && str[end]!=':') {
73 ++end;
74 }
75 if (start != end) {
76 int i;
77 int len = end - start;
78 char *s = malloc(len + 1);
79 for (i=0; i<len; ++i) {
80 s[i] = str[start + i];
81 }
82 s[i] = '\0';
83 *variant = s;
84 } else {
85 *variant = NULL;
86 }
87}
88
89
90static int
91accumulate_locstring(const char *str, FL_Locale *l) {
92 char *lang = NULL;
93 char *country = NULL;
94 char *variant = NULL;
95 if (str) {
96 lang_country_variant_from_envstring(str, &lang, &country, &variant);
97 if (lang) {
98 l->lang = lang;
99 l->country = country;
100 l->variant = variant;
101 return 1;
102 }
103 }
104 free(lang); free(country); free(variant);
105 return 0;
106}
107
108#ifndef WIN32
109
110static int
111accumulate_env(const char *name, FL_Locale *l) {
112 char *env;
113 char *lang = NULL;
114 char *country = NULL;
115 char *variant = NULL;
116 env = getenv(name);
117 if (env) {
118 return accumulate_locstring(env, l);
119 }
120 free(lang); free(country); free(variant);
121 return 0;
122}
123
124#endif
125
126static void
127canonise_fl(FL_Locale *l) {
128 /* this function fixes some common locale-specifying mistakes */
129 /* en_UK -> en_GB */
130 if (l->lang && 0 == strcmp(l->lang, "en")) {
131 if (l->country && 0 == strcmp(l->country, "UK")) {
132 free((void*)l->country);
133 l->country = (FL_Country)malloc(strlen("GB") + 1);
134 strcpy((char*)l->country, "GB");
135 }
136 }
137 /* ja_JA -> ja_JP */
138 if (l->lang && 0 == strcmp(l->lang, "ja")) {
139 if (l->country && 0 == strcmp(l->country, "JA")) {
140 free((void*)l->country);
141 l->country = (FL_Country)malloc(strlen("JP") + 1);
142 strcpy((char*)l->country, "JP");
143 }
144 }
145}
146
147
148#ifdef WIN32
149#include <stdio.h>
150#define ML(pn,sn) MAKELANGID(LANG_##pn, SUBLANG_##pn##_##sn)
151#define MLN(pn) MAKELANGID(LANG_##pn, SUBLANG_DEFAULT)
152#define RML(pn,sn) MAKELANGID(LANG_##pn, SUBLANG_##sn)
153typedef struct {
154 LANGID id;
155 char* code;
156} IDToCode;
157static const IDToCode both_to_code[] = {
158 {ML(ENGLISH,US), "en_US.ISO_8859-1"},
159 {ML(ENGLISH,CAN), "en_CA"}, /* english / canadian */
160 {ML(ENGLISH,UK), "en_GB"},
161 {ML(ENGLISH,EIRE), "en_IE"},
162 {ML(ENGLISH,AUS), "en_AU"},
163 {MLN(GERMAN), "de_DE"},
164 {MLN(SPANISH), "es_ES"},
165 {ML(SPANISH,MEXICAN), "es_MX"},
166 {MLN(FRENCH), "fr_FR"},
167 {ML(FRENCH,CANADIAN), "fr_CA"},
168 {ML(FRENCH,BELGIAN), "fr_BE"}, /* ? */
169 {ML(DUTCH,BELGIAN), "nl_BE"}, /* ? */
170 {ML(PORTUGUESE,BRAZILIAN), "pt_BR"},
171 {MLN(PORTUGUESE), "pt_PT"},
172 {MLN(SWEDISH), "sv_SE"},
173 {ML(CHINESE,HONGKONG), "zh_HK"},
174 /* these are machine-generated and not yet verified */
175 {RML(AFRIKAANS,DEFAULT), "af_ZA"},
176 {RML(ALBANIAN,DEFAULT), "sq_AL"},
177 {RML(ARABIC,ARABIC_ALGERIA), "ar_DZ"},
178 {RML(ARABIC,ARABIC_BAHRAIN), "ar_BH"},
179 {RML(ARABIC,ARABIC_EGYPT), "ar_EG"},
180 {RML(ARABIC,ARABIC_IRAQ), "ar_IQ"},
181 {RML(ARABIC,ARABIC_JORDAN), "ar_JO"},
182 {RML(ARABIC,ARABIC_KUWAIT), "ar_KW"},
183 {RML(ARABIC,ARABIC_LEBANON), "ar_LB"},
184 {RML(ARABIC,ARABIC_LIBYA), "ar_LY"},
185 {RML(ARABIC,ARABIC_MOROCCO), "ar_MA"},
186 {RML(ARABIC,ARABIC_OMAN), "ar_OM"},
187 {RML(ARABIC,ARABIC_QATAR), "ar_QA"},
188 {RML(ARABIC,ARABIC_SAUDI_ARABIA), "ar_SA"},
189 {RML(ARABIC,ARABIC_SYRIA), "ar_SY"},
190 {RML(ARABIC,ARABIC_TUNISIA), "ar_TN"},
191 {RML(ARABIC,ARABIC_UAE), "ar_AE"},
192 {RML(ARABIC,ARABIC_YEMEN), "ar_YE"},
193 {RML(ARMENIAN,DEFAULT), "hy_AM"},
194 {RML(AZERI,AZERI_CYRILLIC), "az_AZ"},
195 {RML(AZERI,AZERI_LATIN), "az_AZ"},
196 {RML(BASQUE,DEFAULT), "eu_ES"},
197 {RML(BELARUSIAN,DEFAULT), "be_BY"},
198/*{RML(BRETON,DEFAULT), "br_FR"},*/
199 {RML(BULGARIAN,DEFAULT), "bg_BG"},
200 {RML(CATALAN,DEFAULT), "ca_ES"},
201 {RML(CHINESE,CHINESE_HONGKONG), "zh_HK"},
202 {RML(CHINESE,CHINESE_MACAU), "zh_MO"},
203 {RML(CHINESE,CHINESE_SIMPLIFIED), "zh_CN"},
204 {RML(CHINESE,CHINESE_SINGAPORE), "zh_SG"},
205 {RML(CHINESE,CHINESE_TRADITIONAL), "zh_TW"},
206/*{RML(CORNISH,DEFAULT), "kw_GB"},*/
207 {RML(CZECH,DEFAULT), "cs_CZ"},
208 {RML(DANISH,DEFAULT), "da_DK"},
209 {RML(DUTCH,DUTCH), "nl_NL"},
210 {RML(DUTCH,DUTCH_BELGIAN), "nl_BE"},
211/*{RML(DUTCH,DUTCH_SURINAM), "nl_SR"},*/
212 {RML(ENGLISH,ENGLISH_AUS), "en_AU"},
213 {RML(ENGLISH,ENGLISH_BELIZE), "en_BZ"},
214 {RML(ENGLISH,ENGLISH_CAN), "en_CA"},
215 {RML(ENGLISH,ENGLISH_CARIBBEAN), "en_CB"},
216 {RML(ENGLISH,ENGLISH_EIRE), "en_IE"},
217 {RML(ENGLISH,ENGLISH_JAMAICA), "en_JM"},
218 {RML(ENGLISH,ENGLISH_NZ), "en_NZ"},
219 {RML(ENGLISH,ENGLISH_PHILIPPINES), "en_PH"},
220 {RML(ENGLISH,ENGLISH_SOUTH_AFRICA), "en_ZA"},
221 {RML(ENGLISH,ENGLISH_TRINIDAD), "en_TT"},
222 {RML(ENGLISH,ENGLISH_UK), "en_GB"},
223 {RML(ENGLISH,ENGLISH_US), "en_US"},
224 {RML(ENGLISH,ENGLISH_ZIMBABWE), "en_ZW"},
225/*{RML(ESPERANTO,DEFAULT), "eo_"},*/
226 {RML(ESTONIAN,DEFAULT), "et_EE"},
227 {RML(FAEROESE,DEFAULT), "fo_FO"},
228 {RML(FARSI,DEFAULT), "fa_IR"},
229 {RML(FINNISH,DEFAULT), "fi_FI"},
230 {RML(FRENCH,FRENCH), "fr_FR"},
231 {RML(FRENCH,FRENCH_BELGIAN), "fr_BE"},
232 {RML(FRENCH,FRENCH_CANADIAN), "fr_CA"},
233 {RML(FRENCH,FRENCH_LUXEMBOURG), "fr_LU"},
234 {RML(FRENCH,FRENCH_MONACO), "fr_MC"},
235 {RML(FRENCH,FRENCH_SWISS), "fr_CH"},
236/*{RML(GAELIC,GAELIC), "ga_IE"},*/
237/*{RML(GAELIC,GAELIC_MANX), "gv_GB"},*/
238/*{RML(GAELIC,GAELIC_SCOTTISH), "gd_GB"},*/
239/*{RML(GALICIAN,DEFAULT), "gl_ES"},*/
240 {RML(GEORGIAN,DEFAULT), "ka_GE"},
241 {RML(GERMAN,GERMAN), "de_DE"},
242 {RML(GERMAN,GERMAN_AUSTRIAN), "de_AT"},
243 {RML(GERMAN,GERMAN_LIECHTENSTEIN), "de_LI"},
244 {RML(GERMAN,GERMAN_LUXEMBOURG), "de_LU"},
245 {RML(GERMAN,GERMAN_SWISS), "de_CH"},
246 {RML(GREEK,DEFAULT), "el_GR"},
247 {RML(GUJARATI,DEFAULT), "gu_IN"},
248 {RML(HEBREW,DEFAULT), "he_IL"},
249 {RML(HINDI,DEFAULT), "hi_IN"},
250 {RML(HUNGARIAN,DEFAULT), "hu_HU"},
251 {RML(ICELANDIC,DEFAULT), "is_IS"},
252 {RML(INDONESIAN,DEFAULT), "id_ID"},
253 {RML(ITALIAN,ITALIAN), "it_IT"},
254 {RML(ITALIAN,ITALIAN_SWISS), "it_CH"},
255 {RML(JAPANESE,DEFAULT), "ja_JP"},
256 {RML(KANNADA,DEFAULT), "kn_IN"},
257 {RML(KAZAK,DEFAULT), "kk_KZ"},
258 {RML(KONKANI,DEFAULT), "kok_IN"},
259 {RML(KOREAN,KOREAN), "ko_KR"},
260/*{RML(KYRGYZ,DEFAULT), "ky_KG"},*/
261 {RML(LATVIAN,DEFAULT), "lv_LV"},
262 {RML(LITHUANIAN,LITHUANIAN), "lt_LT"},
263 {RML(MACEDONIAN,DEFAULT), "mk_MK"},
264 {RML(MALAY,MALAY_BRUNEI_DARUSSALAM), "ms_BN"},
265 {RML(MALAY,MALAY_MALAYSIA), "ms_MY"},
266 {RML(MARATHI,DEFAULT), "mr_IN"},
267/*{RML(MONGOLIAN,DEFAULT), "mn_MN"},*/
268 {RML(NORWEGIAN,NORWEGIAN_BOKMAL), "nb_NO"},
269 {RML(NORWEGIAN,NORWEGIAN_NYNORSK), "nn_NO"},
270 {RML(POLISH,DEFAULT), "pl_PL"},
271 {RML(PORTUGUESE,PORTUGUESE), "pt_PT"},
272 {RML(PORTUGUESE,PORTUGUESE_BRAZILIAN), "pt_BR"},
273 {RML(PUNJABI,DEFAULT), "pa_IN"},
274 {RML(ROMANIAN,DEFAULT), "ro_RO"},
275 {RML(RUSSIAN,DEFAULT), "ru_RU"},
276 {RML(SANSKRIT,DEFAULT), "sa_IN"},
277 {RML(SERBIAN,DEFAULT), "hr_HR"},
278 {RML(SERBIAN,SERBIAN_CYRILLIC), "sr_SP"},
279 {RML(SERBIAN,SERBIAN_LATIN), "sr_SP"},
280 {RML(SLOVAK,DEFAULT), "sk_SK"},
281 {RML(SLOVENIAN,DEFAULT), "sl_SI"},
282 {RML(SPANISH,SPANISH), "es_ES"},
283 {RML(SPANISH,SPANISH_ARGENTINA), "es_AR"},
284 {RML(SPANISH,SPANISH_BOLIVIA), "es_BO"},
285 {RML(SPANISH,SPANISH_CHILE), "es_CL"},
286 {RML(SPANISH,SPANISH_COLOMBIA), "es_CO"},
287 {RML(SPANISH,SPANISH_COSTA_RICA), "es_CR"},
288 {RML(SPANISH,SPANISH_DOMINICAN_REPUBLIC), "es_DO"},
289 {RML(SPANISH,SPANISH_ECUADOR), "es_EC"},
290 {RML(SPANISH,SPANISH_EL_SALVADOR), "es_SV"},
291 {RML(SPANISH,SPANISH_GUATEMALA), "es_GT"},
292 {RML(SPANISH,SPANISH_HONDURAS), "es_HN"},
293 {RML(SPANISH,SPANISH_MEXICAN), "es_MX"},
294 {RML(SPANISH,SPANISH_MODERN), "es_ES"},
295 {RML(SPANISH,SPANISH_NICARAGUA), "es_NI"},
296 {RML(SPANISH,SPANISH_PANAMA), "es_PA"},
297 {RML(SPANISH,SPANISH_PARAGUAY), "es_PY"},
298 {RML(SPANISH,SPANISH_PERU), "es_PE"},
299 {RML(SPANISH,SPANISH_PUERTO_RICO), "es_PR"},
300 {RML(SPANISH,SPANISH_URUGUAY), "es_UY"},
301 {RML(SPANISH,SPANISH_VENEZUELA), "es_VE"},
302 {RML(SWAHILI,DEFAULT), "sw_KE"},
303 {RML(SWEDISH,SWEDISH), "sv_SE"},
304 {RML(SWEDISH,SWEDISH_FINLAND), "sv_FI"},
305/*{RML(SYRIAC,DEFAULT), "syr_SY"},*/
306 {RML(TAMIL,DEFAULT), "ta_IN"},
307 {RML(TATAR,DEFAULT), "tt_TA"},
308 {RML(TELUGU,DEFAULT), "te_IN"},
309 {RML(THAI,DEFAULT), "th_TH"},
310 {RML(TURKISH,DEFAULT), "tr_TR"},
311 {RML(UKRAINIAN,DEFAULT), "uk_UA"},
312 {RML(URDU,URDU_PAKISTAN), "ur_PK"},
313 {RML(UZBEK,UZBEK_CYRILLIC), "uz_UZ"},
314 {RML(UZBEK,UZBEK_LATIN), "uz_UZ"},
315 {RML(VIETNAMESE,DEFAULT), "vi_VN"},
316/*{RML(WALON,DEFAULT), "wa_BE"},*/
317/*{RML(WELSH,DEFAULT), "cy_GB"},*/
318};
319static const IDToCode primary_to_code[] = {
320 {LANG_AFRIKAANS, "af"},
321 {LANG_ARABIC, "ar"},
322 {LANG_AZERI, "az"},
323 {LANG_BULGARIAN, "bg"},
324/*{LANG_BRETON, "br"},*/
325 {LANG_BELARUSIAN, "by"},
326 {LANG_CATALAN, "ca"},
327 {LANG_CZECH, "cs"},
328/*{LANG_WELSH, "cy"},*/
329 {LANG_DANISH, "da"},
330 {LANG_GERMAN, "de"},
331 {LANG_GREEK, "el"},
332 {LANG_ENGLISH, "en"},
333/*{LANG_ESPERANTO, "eo"},*/
334 {LANG_SPANISH, "es"},
335 {LANG_ESTONIAN, "et"},
336 {LANG_BASQUE, "eu"},
337 {LANG_FARSI, "fa"},
338 {LANG_FINNISH, "fi"},
339 {LANG_FAEROESE, "fo"},
340 {LANG_FRENCH, "fr"},
341/*{LANG_GAELIC, "ga"},*/
342/*{LANG_GALICIAN, "gl"},*/
343 {LANG_GUJARATI, "gu"},
344 {LANG_HEBREW, "he"},
345 {LANG_HINDI, "hi"},
346 {LANG_SERBIAN, "hr"},
347 {LANG_HUNGARIAN, "hu"},
348 {LANG_ARMENIAN, "hy"},
349 {LANG_INDONESIAN, "id"},
350 {LANG_ITALIAN, "it"},
351 {LANG_JAPANESE, "ja"},
352 {LANG_GEORGIAN, "ka"},
353 {LANG_KAZAK, "kk"},
354 {LANG_KANNADA, "kn"},
355 {LANG_KOREAN, "ko"},
356/*{LANG_KYRGYZ, "ky"},*/
357 {LANG_LITHUANIAN, "lt"},
358 {LANG_LATVIAN, "lv"},
359 {LANG_MACEDONIAN, "mk"},
360/*{LANG_MONGOLIAN, "mn"},*/
361 {LANG_MARATHI, "mr"},
362 {LANG_MALAY, "ms"},
363 {LANG_NORWEGIAN, "nb"},
364 {LANG_DUTCH, "nl"},
365 {LANG_NORWEGIAN, "nn"},
366 {LANG_NORWEGIAN, "no"},/* unofficial? */
367 {LANG_PUNJABI, "pa"},
368 {LANG_POLISH, "pl"},
369 {LANG_PORTUGUESE, "pt"},
370 {LANG_ROMANIAN, "ro"},
371 {LANG_RUSSIAN, "ru"},
372 {LANG_SLOVAK, "sk"},
373 {LANG_SLOVENIAN, "sl"},
374 {LANG_ALBANIAN, "sq"},
375 {LANG_SERBIAN, "sr"},
376 {LANG_SWEDISH, "sv"},
377 {LANG_SWAHILI, "sw"},
378 {LANG_TAMIL, "ta"},
379 {LANG_THAI, "th"},
380 {LANG_TURKISH, "tr"},
381 {LANG_TATAR, "tt"},
382 {LANG_UKRAINIAN, "uk"},
383 {LANG_URDU, "ur"},
384 {LANG_UZBEK, "uz"},
385 {LANG_VIETNAMESE, "vi"},
386/*{LANG_WALON, "wa"},*/
387 {LANG_CHINESE, "zh"},
388};
389static int num_primary_to_code =
390 sizeof(primary_to_code) / sizeof(*primary_to_code);
391static int num_both_to_code =
392 sizeof(both_to_code) / sizeof(*both_to_code);
393
394static int
395lcid_to_fl(LCID lcid,
396 FL_Locale *rtn) {
397 LANGID langid = LANGIDFROMLCID(lcid);
398 LANGID primary_lang = PRIMARYLANGID(langid);
399// LANGID sub_lang = SUBLANGID(langid);
400 int i;
401 /* try to find an exact primary/sublanguage combo that we know about */
402 for (i=0; i<num_both_to_code; ++i) {
403 if (both_to_code[i].id == langid) {
404 accumulate_locstring(both_to_code[i].code, rtn);
405 return 1;
406 }
407 }
408 /* fallback to just checking the primary language id */
409 for (i=0; i<num_primary_to_code; ++i) {
410 if (primary_to_code[i].id == primary_lang) {
411 accumulate_locstring(primary_to_code[i].code, rtn);
412 return 1;
413 }
414 }
415 return 0;
416}
417#endif
418
419
420FL_Success
421FL_FindLocale(FL_Locale **locale) {
422 FL_Success success = FL_FAILED;
423 FL_Locale *rtn = malloc(sizeof(FL_Locale));
424 rtn->lang = NULL;
425 rtn->country = NULL;
426 rtn->variant = NULL;
427
428#ifdef WIN32
429 /* win32 >= mswindows95 */
430 {
431 LCID lcid = GetThreadLocale();
432 if (lcid_to_fl(lcid, rtn)) {
433 success = FL_CONFIDENT;
434 }
435 if (success == FL_FAILED) {
436 /* assume US English on mswindows systems unless we know otherwise */
437 if (accumulate_locstring("en_US.ISO_8859-1", rtn)) {
438 success = FL_DEFAULT_GUESS;
439 }
440 }
441 }
442#else
443 /* assume unixoid */
444 {
445 /* examples: */
446 /* sv_SE.ISO_8859-1 */
447 /* fr_FR.ISO8859-1 */
448 /* no_NO_NB */
449 /* no_NO_NY */
450 /* no_NO */
451 /* de_DE */
452 /* try the various vars in decreasing order of authority */
453 if (accumulate_env("LC_ALL", rtn) ||
454 accumulate_env("LC_MESSAGES", rtn) ||
455 accumulate_env("LANG", rtn) ||
456 accumulate_env("LANGUAGE", rtn)) {
457 success = FL_CONFIDENT;
458 }
459 if (success == FL_FAILED) {
460 /* assume US English on unixoid systems unless we know otherwise */
461 if (accumulate_locstring("en_US.ISO_8859-1", rtn)) {
462 success = FL_DEFAULT_GUESS;
463 }
464 }
465 }
466#endif
467
468 if (success != FL_FAILED) {
469 canonise_fl(rtn);
470 }
471
472 *locale = rtn;
473 return success;
474}
475
476
477void
478FL_FreeLocale(FL_Locale **locale) {
479 if (locale) {
480 FL_Locale *l = *locale;
481 if (l) {
482 if (l->lang) {
483 free((void*)l->lang);
484 }
485 if (l->country) {
486 free((void*)l->country);
487 }
488 if (l->variant) {
489 free((void*)l->variant);
490 }
491 free(l);
492 *locale = NULL;
493 }
494 }
495}
496