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 
13 static int
is_lcchar(const int c)14 is_lcchar(const int c) {
15   return isalnum(c);
16 }
17 
18 static void
lang_country_variant_from_envstring(const char * str,char ** lang,char ** country,char ** variant)19 lang_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 
90 static int
accumulate_locstring(const char * str,FL_Locale * l)91 accumulate_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 
110 static int
accumulate_env(const char * name,FL_Locale * l)111 accumulate_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 
126 static void
canonise_fl(FL_Locale * l)127 canonise_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)
153 typedef struct {
154   LANGID id;
155   char*  code;
156 } IDToCode;
157 static 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 };
319 static 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 };
389 static int num_primary_to_code =
390   sizeof(primary_to_code) / sizeof(*primary_to_code);
391 static int num_both_to_code =
392   sizeof(both_to_code) / sizeof(*both_to_code);
393 
394 static int
lcid_to_fl(LCID lcid,FL_Locale * rtn)395 lcid_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 
420 FL_Success
FL_FindLocale(FL_Locale ** locale)421 FL_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 
477 void
FL_FreeLocale(FL_Locale ** locale)478 FL_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