Tizen 2.1 release
[platform/core/uifw/e17.git] / src / bin / e_intl.c
1 #include "e.h"
2
3 static Ecore_Exe *_e_intl_input_method_exec = NULL;
4 static Ecore_Event_Handler *_e_intl_exit_handler = NULL;
5
6 static char *_e_intl_orig_lang = NULL;
7 static char *_e_intl_orig_language = NULL;
8 static char *_e_intl_language = NULL;
9
10 static char *_e_intl_language_alias = NULL;
11
12 static char *_e_intl_orig_xmodifiers = NULL;
13 static char *_e_intl_orig_qt_im_module = NULL;
14 static char *_e_intl_orig_gtk_im_module = NULL;
15 static char *_e_intl_orig_ecore_imf_module = NULL;
16
17 static const char *_e_intl_imc_personal_path = NULL;
18 static const char *_e_intl_imc_system_path = NULL;
19
20 #define E_EXE_STOP(EXE)     if (EXE) { ecore_exe_terminate(EXE); ecore_exe_free(EXE); EXE = NULL; }
21 #define E_EXE_IS_VALID(EXE) (!((!EXE) || (EXE[0] == 0)))
22
23 /* All locale parts */
24 #define E_INTL_LOC_ALL         E_INTL_LOC_LANG | \
25   E_INTL_LOC_REGION |                            \
26   E_INTL_LOC_CODESET |                           \
27   E_INTL_LOC_MODIFIER
28
29 /* Locale parts which are significant when Validating */
30 #define E_INTL_LOC_SIGNIFICANT E_INTL_LOC_LANG | \
31   E_INTL_LOC_REGION |                            \
32   E_INTL_LOC_CODESET
33
34 /* Language Setting and Listing */
35 static char      *_e_intl_language_path_find(char *language);
36 static Eina_List *_e_intl_language_dir_scan(const char *dir);
37 static int        _e_intl_language_list_find(Eina_List *language_list, char *language);
38
39 /* Locale Validation and Discovery */
40 static Eina_Hash *_e_intl_locale_alias_hash_get(void);
41 static char      *_e_intl_locale_alias_get(const char *language);
42 static Eina_List *_e_intl_locale_system_locales_get(void);
43 static Eina_List *_e_intl_locale_search_order_get(const char *locale);
44 static int        _e_intl_locale_validate(const char *locale);
45 static void       _e_intl_locale_hash_free(Eina_Hash *language_hash);
46 static Eina_Bool  _e_intl_locale_hash_free_cb(const Eina_Hash *hash, const void *key, void *data, void *fdata);
47
48 /* Input Method Configuration and Management */
49 static Eina_Bool  _e_intl_cb_exit(void *data, int type, void *event);
50 static Eina_List *_e_intl_imc_dir_scan(const char *dir);
51
52 EINTERN int
53 e_intl_init(void)
54 {
55    char *s;
56
57    e_intl_data_init();
58
59    if ((s = getenv("LANG"))) _e_intl_orig_lang = strdup(s);
60    if ((s = getenv("LANGUAGE"))) _e_intl_orig_language = strdup(s);
61
62    if ((s = getenv("GTK_IM_MODULE"))) _e_intl_orig_gtk_im_module = strdup(s);
63    if ((s = getenv("QT_IM_MODULE"))) _e_intl_orig_qt_im_module = strdup(s);
64    if ((s = getenv("XMODIFIERS"))) _e_intl_orig_xmodifiers = strdup(s);
65    if ((s = getenv("ECORE_IMF_MODULE"))) _e_intl_orig_ecore_imf_module = strdup(s);
66
67    return 1;
68 }
69
70 EINTERN int
71 e_intl_shutdown(void)
72 {
73    E_FREE(_e_intl_language);
74    E_FREE(_e_intl_orig_lang);
75    E_FREE(_e_intl_orig_language);
76
77    E_FREE(_e_intl_orig_gtk_im_module);
78    E_FREE(_e_intl_orig_qt_im_module);
79    E_FREE(_e_intl_orig_xmodifiers);
80    E_FREE(_e_intl_orig_ecore_imf_module);
81
82    if (_e_intl_imc_personal_path)
83      eina_stringshare_del(_e_intl_imc_personal_path);
84    if (_e_intl_imc_system_path)
85      eina_stringshare_del(_e_intl_imc_system_path);
86
87    e_intl_data_shutdown();
88
89    return 1;
90 }
91
92 /* Setup configuration settings and start services */
93 EINTERN int
94 e_intl_post_init(void)
95 {
96    if ((e_config->language) && (e_config->language[0] != 0))
97      e_intl_language_set(e_config->language);
98    else
99      e_intl_language_set(NULL);
100
101    if ((e_config->input_method) && (e_config->input_method[0] != 0))
102      e_intl_input_method_set(e_config->input_method);
103
104    _e_intl_exit_handler = ecore_event_handler_add(ECORE_EXE_EVENT_DEL,
105                                                   _e_intl_cb_exit, NULL);
106    return 1;
107 }
108
109 EINTERN int
110 e_intl_post_shutdown(void)
111 {
112    if (_e_intl_exit_handler)
113      {
114         ecore_event_handler_del(_e_intl_exit_handler);
115         _e_intl_exit_handler = NULL;
116      }
117
118    e_intl_input_method_set(NULL);
119
120    e_intl_language_set(NULL);
121    E_FREE(_e_intl_language_alias);
122
123    E_EXE_STOP(_e_intl_input_method_exec);
124    return 1;
125 }
126
127 /*
128  * TODO
129  * - Add error dialogs explaining any errors while setting the locale
130  *      * Locale aliases need to be configured
131  *      * Locale is invalid
132  *      * Message files are not found for this locale, then we have (en_US, POSIX, C)
133  * - Add support of compound locales i.e. (en_US;zh_CN;C) ==Defer==
134  * - Add Configuration for to-be-set environment variables
135  */
136 EAPI void
137 e_intl_language_set(const char *lang)
138 {
139    int set_envars;
140    int ok;
141
142    set_envars = 1;
143    /* NULL lang means set everything back to the original environment
144     * defaults
145     */
146    if (!lang)
147      {
148         e_util_env_set("LANG", _e_intl_orig_lang);
149
150         if (!lang) lang = getenv("LC_ALL");
151         if (!lang) lang = getenv("LANG");
152
153         set_envars = 0;
154      }
155
156    E_FREE(_e_intl_language_alias);
157    _e_intl_language_alias = _e_intl_locale_alias_get(lang);
158    E_FREE(_e_intl_language);
159
160    if (lang)
161      _e_intl_language = strdup(lang);
162    else
163      _e_intl_language = NULL;
164
165    ok = 1;
166    if (strcmp(_e_intl_language_alias, "C"))
167      {
168         ok = _e_intl_locale_validate(_e_intl_language_alias);
169         if (!ok)
170           {
171              char *p, *new_lang;
172
173              new_lang = _e_intl_language_alias;
174              p = strchr(new_lang, '.');
175              if (p) *p = 0;
176              _e_intl_language_alias = strdup(new_lang);
177              E_FREE(new_lang);
178              ok = _e_intl_locale_validate(_e_intl_language_alias);
179           }
180      }
181    if (!ok)
182      {
183         fprintf(stderr, "The locale '%s' cannot be found on your "
184                         "system. Please install this locale or try "
185                         "something else.", _e_intl_language_alias);
186         return;
187      }
188    /* Only set env vars is a non NULL locale was passed */
189    if (set_envars)
190      {
191         e_util_env_set("LANG", lang);
192         /* Unset LANGUAGE, apparently causes issues if set */
193         e_util_env_set("LANGUAGE", NULL);
194         efreet_lang_reset();
195         setlocale(LC_ALL, lang);
196      }
197    else
198      {
199         setlocale(LC_ALL, "");
200      }
201
202    if (_e_intl_language)
203      {
204         char *locale_path;
205
206         locale_path = _e_intl_language_path_find(_e_intl_language_alias);
207         if (!locale_path)
208           {
209              E_Locale_Parts *locale_parts;
210
211              locale_parts = e_intl_locale_parts_get(_e_intl_language_alias);
212
213              /* If locale is C or some form of en don't report an error */
214              if ((!locale_parts) &&
215                  (strcmp(_e_intl_language_alias, "C")))
216                {
217                   fprintf(stderr,
218                           "An error occurred setting your locale. \n\n"
219
220                           "The locale you have chosen '%s' appears to \n"
221                           "be an alias, however, it can not be resloved.\n"
222                           "Please make sure you have a 'locale.alias' \n"
223                           "file in your 'messages' path which can resolve\n"
224                           "this alias.\n\n"
225
226                           "Enlightenment will not be translated.\n",
227                           _e_intl_language_alias);
228                }
229              else if ((locale_parts) && (locale_parts->lang) &&
230                       (strcmp(locale_parts->lang, "en")))
231                {
232                   fprintf(stderr,
233                           "An error occurred setting your locale. \n\n"
234
235                           "The translation files for the locale you \n"
236                           "have chosen (%s) cannot be found in your \n"
237                           "'messages' path. \n\n"
238
239                           "Enlightenment will not be translated.\n",
240                           _e_intl_language_alias);
241                }
242              e_intl_locale_parts_free(locale_parts);
243           }
244         else
245           {
246 #ifdef HAVE_GETTEXT
247              bindtextdomain(PACKAGE, locale_path);
248              textdomain(PACKAGE);
249              bind_textdomain_codeset(PACKAGE, "UTF-8");
250 #endif
251              free(locale_path);
252           }
253      }
254 }
255
256 EAPI const char *
257 e_intl_language_get(void)
258 {
259    return _e_intl_language;
260 }
261
262 EAPI const char *
263 e_intl_language_alias_get(void)
264 {
265    return _e_intl_language_alias;
266 }
267
268 EAPI Eina_List *
269 e_intl_language_list(void)
270 {
271    Eina_List *next;
272    Eina_List *dir_list;
273    Eina_List *all_languages;
274    E_Path_Dir *epd;
275
276    all_languages = NULL;
277    dir_list = e_path_dir_list_get(path_messages);
278    EINA_LIST_FOREACH(dir_list, next, epd)
279      {
280         Eina_List *dir_languages;
281         char *language;
282
283         dir_languages = _e_intl_language_dir_scan(epd->dir);
284
285         EINA_LIST_FREE(dir_languages, language)
286           if ((_e_intl_language_list_find(all_languages, language)) ||
287               ((strlen(language) > 2) && (!_e_intl_locale_validate(language))))
288             {
289                free(language);
290             }
291           else
292             all_languages = eina_list_append(all_languages, language);
293      }
294
295    e_path_dir_list_free(dir_list);
296
297    return all_languages;
298 }
299
300 static int
301 _e_intl_language_list_find(Eina_List *language_list, char *language)
302 {
303    Eina_List *l;
304    char *lang;
305
306    if ((!language_list) || (!language)) return 0;
307
308    EINA_LIST_FOREACH(language_list, l, lang)
309      if (!strcmp(lang, language)) return 1;
310
311    return 0;
312 }
313
314 EAPI void
315 e_intl_input_method_set(const char *imc_path)
316 {
317    if (!imc_path)
318      {
319         E_EXE_STOP(_e_intl_input_method_exec);
320         e_util_env_set("GTK_IM_MODULE", _e_intl_orig_gtk_im_module);
321         e_util_env_set("QT_IM_MODULE", _e_intl_orig_qt_im_module);
322         e_util_env_set("XMODIFIERS", _e_intl_orig_xmodifiers);
323         e_util_env_set("ECORE_IMF_MODULE", _e_intl_orig_ecore_imf_module);
324      }
325
326    if (imc_path)
327      {
328         Eet_File *imc_ef;
329         E_Input_Method_Config *imc;
330
331         imc_ef = eet_open(imc_path, EET_FILE_MODE_READ);
332         if (imc_ef)
333           {
334              imc = e_intl_input_method_config_read(imc_ef);
335              eet_close(imc_ef);
336
337              if (imc)
338                {
339                   e_util_env_set("GTK_IM_MODULE", imc->gtk_im_module);
340                   e_util_env_set("QT_IM_MODULE", imc->qt_im_module);
341                   e_util_env_set("XMODIFIERS", imc->xmodifiers);
342                   e_util_env_set("ECORE_IMF_MODULE", imc->ecore_imf_module);
343
344                   E_EXE_STOP(_e_intl_input_method_exec);
345
346                   if (E_EXE_IS_VALID(imc->e_im_exec))
347                     {
348                        e_util_library_path_strip();
349                        _e_intl_input_method_exec = ecore_exe_run(imc->e_im_exec, NULL);
350                        e_util_library_path_restore();
351                        ecore_exe_tag_set(_e_intl_input_method_exec, "E/im_exec");
352
353                        if ((!_e_intl_input_method_exec) ||
354                            (!ecore_exe_pid_get(_e_intl_input_method_exec)))
355                          e_util_dialog_show(_("Input Method Error"),
356                                             _("Error starting the input method executable<br><br>"
357                                               "please make sure that your input<br>"
358                                               "method configuration is correct and<br>"
359                                               "that your configuration's<br>"
360                                               "executable is in your PATH<br>"));
361                     }
362                   e_intl_input_method_config_free(imc);
363                }
364           }
365      }
366 }
367
368 EAPI Eina_List *
369 e_intl_input_method_list(void)
370 {
371    Eina_List *input_methods;
372    Eina_List *im_list;
373
374    im_list = NULL;
375
376    /* Personal Path */
377    im_list = _e_intl_imc_dir_scan(e_intl_imc_personal_path_get());
378
379    /* System Path */
380    input_methods = _e_intl_imc_dir_scan(e_intl_imc_system_path_get());
381    im_list = eina_list_merge(im_list, input_methods);
382
383    return im_list;
384 }
385
386 const char *
387 e_intl_imc_personal_path_get(void)
388 {
389    if (!_e_intl_imc_personal_path)
390      {
391         char buf[4096];
392
393         e_user_dir_concat_static(buf, "input_methods");
394         _e_intl_imc_personal_path = eina_stringshare_add(buf);
395      }
396    return _e_intl_imc_personal_path;
397 }
398
399 const char *
400 e_intl_imc_system_path_get(void)
401 {
402    if (!_e_intl_imc_system_path)
403      {
404         char buf[4096];
405
406         e_prefix_data_concat_static(buf, "data/input_methods");
407         _e_intl_imc_system_path = eina_stringshare_add(buf);
408      }
409    return _e_intl_imc_system_path;
410 }
411
412 static Eina_Bool
413 _e_intl_cb_exit(void *data __UNUSED__, int type __UNUSED__, void *event)
414 {
415    Ecore_Exe_Event_Del *ev;
416
417    ev = event;
418    if (!ev->exe) return ECORE_CALLBACK_PASS_ON;
419
420    if (!(ecore_exe_tag_get(ev->exe) &&
421          (!strcmp(ecore_exe_tag_get(ev->exe), "E/im_exec")))) return 1;
422
423    _e_intl_input_method_exec = NULL;
424    return ECORE_CALLBACK_PASS_ON;
425 }
426
427 static void
428 _e_intl_locale_hash_free(Eina_Hash *locale_hash)
429 {
430    if (!locale_hash) return;
431    eina_hash_foreach(locale_hash, _e_intl_locale_hash_free_cb, NULL);
432    eina_hash_free(locale_hash);
433 }
434
435 static Eina_Bool
436 _e_intl_locale_hash_free_cb(const Eina_Hash *hash __UNUSED__, const void *key __UNUSED__, void *data, void *fdata __UNUSED__)
437 {
438    free(data);
439    return 1;
440 }
441
442 /*
443  * get the directory associated with the language. Language Must be valid alias
444  * i.e. Already validated and already de-aliased.
445  *
446  * NULL means:
447  *  1) The user does not have an enlightenment translation for this locale
448  *  2) The user does not have their locale.aliases configured correctly
449  *
450  * @return NULL if not found.
451  */
452 static char *
453 _e_intl_language_path_find(char *language)
454 {
455    char *directory;
456    char *data;
457    Eina_List *dir_list;
458    Eina_List *search_list;
459    Eina_List *next_dir;
460    Eina_List *next_search;
461    E_Path_Dir *epd;
462    char *search_locale;
463    int found;
464
465    search_list = _e_intl_locale_search_order_get(language);
466
467    if (!search_list) return NULL;
468
469    directory = NULL;
470    found = 0;
471    dir_list = e_path_dir_list_get(path_messages);
472
473    /* For each directory in the path */
474    EINA_LIST_FOREACH(dir_list, next_dir, epd)
475      {
476         if (found) break;
477
478         /* Match canonicalized locale against each possible search */
479         EINA_LIST_FOREACH(search_list, next_search, search_locale)
480           {
481              char message_path[PATH_MAX];
482
483              snprintf(message_path, sizeof(message_path), "%s/%s/LC_MESSAGES/%s.mo", epd->dir, search_locale, PACKAGE);
484
485              if ((ecore_file_exists(message_path)) && (!ecore_file_is_dir(message_path)))
486                {
487                   directory = strdup(epd->dir);
488                   found = 1;
489                }
490           }
491      }
492
493    e_path_dir_list_free(dir_list);
494
495    EINA_LIST_FREE(search_list, data)
496      free(data);
497
498    return directory;
499 }
500
501 static Eina_List *
502 _e_intl_language_dir_scan(const char *dir)
503 {
504    Eina_List *languages;
505    Eina_List *files;
506    char *file;
507
508    languages = NULL;
509
510    files = ecore_file_ls(dir);
511    if (!files) return NULL;
512
513    if (files)
514      {
515         EINA_LIST_FREE(files, file)
516           {
517              char file_path[PATH_MAX];
518
519              snprintf(file_path, sizeof(file_path), "%s/%s/LC_MESSAGES/%s.mo",
520                       dir, file, PACKAGE);
521              if (ecore_file_exists(file_path) && !ecore_file_is_dir(file_path))
522                languages = eina_list_append(languages, file);
523              else
524                free(file);
525           }
526      }
527    return languages;
528 }
529
530 /* get the alias for a locale
531  *
532  * return pointer to allocated alias string. never returns NULL
533  * String will be the same if its a valid locale already or there
534  * is no alias.
535  */
536 static char *
537 _e_intl_locale_alias_get(const char *language)
538 {
539    Eina_Hash *alias_hash;
540    char *alias;
541    char *lower_language;
542    unsigned int i;
543
544    if ((!language) || (!strncmp(language, "POSIX", strlen("POSIX"))))
545      return strdup("C");
546
547    alias_hash = _e_intl_locale_alias_hash_get();
548    if (!alias_hash) /* No alias file available */
549      return strdup(language);
550
551    lower_language = malloc(strlen(language) + 1);
552    for (i = 0; i < strlen(language); i++)
553      lower_language[i] = tolower(language[i]);
554    lower_language[i] = 0;
555
556    alias = eina_hash_find(alias_hash, lower_language);
557    free(lower_language);
558
559    if (alias)
560      alias = strdup(alias);
561    else
562      alias = strdup(language);
563
564    _e_intl_locale_hash_free(alias_hash);
565
566    return alias;
567 }
568
569 static Eina_Hash *
570 _e_intl_locale_alias_hash_get(void)
571 {
572    Eina_List *next;
573    Eina_List *dir_list;
574    E_Path_Dir *epd;
575    Eina_Hash *alias_hash;
576
577    dir_list = e_path_dir_list_get(path_messages);
578    alias_hash = NULL;
579
580    EINA_LIST_FOREACH(dir_list, next, epd)
581      {
582         char buf[4096];
583         FILE *f;
584
585         snprintf(buf, sizeof(buf), "%s/locale.alias", epd->dir);
586         f = fopen(buf, "r");
587         if (f)
588           {
589              char alias[4096], locale[4096];
590
591              /* read locale alias lines */
592              while (fscanf(f, "%4090s %[^\n]\n", alias, locale) == 2)
593                {
594                   /* skip comments */
595                   if ((alias[0] == '!') || (alias[0] == '#'))
596                     continue;
597
598                   /* skip dupes */
599                   if (eina_hash_find(alias_hash, alias))
600                     continue;
601
602                   if (!alias_hash) alias_hash = eina_hash_string_superfast_new(NULL);
603                   eina_hash_add(alias_hash, alias, strdup(locale));
604                }
605              fclose(f);
606           }
607      }
608    e_path_dir_list_free(dir_list);
609
610    return alias_hash;
611 }
612
613 /* return parts of the locale that are requested in the mask
614  * return null if the locale looks to be invalid (Does not have
615  * ll_DD)
616  *
617  * the returned string needs to be freed
618  */
619 /*
620  * Not canonic, just gets the parts
621  */
622 EAPI E_Locale_Parts *
623 e_intl_locale_parts_get(const char *locale)
624 {
625    /* Parse Results */
626    E_Locale_Parts *locale_parts;
627    char language[4];
628    char territory[4];
629    char codeset[32];
630    char modifier[32];
631
632    /* Parse State */
633    int state = 0;   /* start out looking for the language */
634    unsigned int locale_idx;
635    int tmp_idx = 0;
636
637    /* Parse Loop - Seperators are _ . @ */
638    for (locale_idx = 0; locale_idx < strlen(locale); locale_idx++)
639      {
640         char locale_char;
641         locale_char = locale[locale_idx];
642
643         /* we have finished scanning the locale string */
644         if (!locale_char)
645           break;
646
647         /* scan this character based on the current start */
648         switch (state)
649           {
650            case 0: /* Gathering Language */
651              if (tmp_idx == 2 && locale_char == '_')
652                {
653                   state++;
654                   language[tmp_idx] = 0;
655                   tmp_idx = 0;
656                }
657              else if ((tmp_idx < 2) && (islower(locale_char)))
658                language[tmp_idx++] = locale_char;
659              else
660                return NULL;
661              break;
662
663            case 1: /* Gathering Territory */
664              if (tmp_idx == 2 && locale_char == '.')
665                {
666                   state++;
667                   territory[tmp_idx] = 0;
668                   tmp_idx = 0;
669                }
670              else if ((tmp_idx == 2) && (locale_char == '@'))
671                {
672                   state += 2;
673                   territory[tmp_idx] = 0;
674                   codeset[0] = 0;
675                   tmp_idx = 0;
676                }
677              else if ((tmp_idx < 2) && isupper(locale_char))
678                territory[tmp_idx++] = locale_char;
679              else
680                return NULL;
681              break;
682
683            case 2: /* Gathering Codeset */
684              if (locale_char == '@')
685                {
686                   state++;
687                   codeset[tmp_idx] = 0;
688                   tmp_idx = 0;
689                }
690              else if (tmp_idx < 32)
691                codeset[tmp_idx++] = locale_char;
692              else
693                return NULL;
694              break;
695
696            case 3: /* Gathering modifier */
697              if (tmp_idx < 32)
698                modifier[tmp_idx++] = locale_char;
699              else
700                return NULL;
701              break;
702
703            default:
704              break;
705           }
706      }
707
708    /* set end-of-string \0 */
709    /* There are no breaks here on purpose */
710    switch (state)
711      {
712       case 0:
713         language[tmp_idx] = 0;
714         tmp_idx = 0;
715
716       case 1:
717         territory[tmp_idx] = 0;
718         tmp_idx = 0;
719
720       case 2:
721         codeset[tmp_idx] = 0;
722         tmp_idx = 0;
723
724       case 3:
725         modifier[tmp_idx] = 0;
726
727       default:
728         break;
729      }
730
731    locale_parts = E_NEW(E_Locale_Parts, 1);
732
733    /* Put the parts of the string together */
734    if (language[0] != 0)
735      {
736         locale_parts->mask |= E_INTL_LOC_LANG;
737         locale_parts->lang = eina_stringshare_add(language);
738      }
739    if (territory[0] != 0)
740      {
741         locale_parts->mask |= E_INTL_LOC_REGION;
742         locale_parts->region = eina_stringshare_add(territory);
743      }
744    if (codeset[0] != 0)
745      {
746         locale_parts->mask |= E_INTL_LOC_CODESET;
747         locale_parts->codeset = eina_stringshare_add(codeset);
748      }
749    if (modifier[0] != 0)
750      {
751         locale_parts->mask |= E_INTL_LOC_MODIFIER;
752         locale_parts->modifier = eina_stringshare_add(modifier);
753      }
754
755    return locale_parts;
756 }
757
758 EAPI void
759 e_intl_locale_parts_free(E_Locale_Parts *locale_parts)
760 {
761    if (locale_parts)
762      {
763         if (locale_parts->lang) eina_stringshare_del(locale_parts->lang);
764         if (locale_parts->region) eina_stringshare_del(locale_parts->region);
765         if (locale_parts->codeset) eina_stringshare_del(locale_parts->codeset);
766         if (locale_parts->modifier) eina_stringshare_del(locale_parts->modifier);
767         E_FREE(locale_parts);
768      }
769 }
770
771 EAPI char *
772 e_intl_locale_parts_combine(E_Locale_Parts *locale_parts, int mask)
773 {
774    int locale_size;
775    char *locale;
776
777    if (!locale_parts) return NULL;
778
779    if ((mask & locale_parts->mask) != mask) return NULL;
780
781    /* Construct the clean locale string */
782    locale_size = 0;
783
784    /* determine the size */
785    if (mask & E_INTL_LOC_LANG)
786      locale_size = strlen(locale_parts->lang) + 1;
787
788    if (mask & E_INTL_LOC_REGION)
789      locale_size += strlen(locale_parts->region) + 1;
790
791    if (mask & E_INTL_LOC_CODESET)
792      locale_size += strlen(locale_parts->codeset) + 1;
793
794    if (mask & E_INTL_LOC_MODIFIER)
795      locale_size += strlen(locale_parts->modifier) + 1;
796
797    if (!locale_size) return NULL;
798
799    /* Allocate memory */
800    locale = (char *)malloc(locale_size);
801    locale[0] = 0;
802
803    if (mask & E_INTL_LOC_LANG)
804      strcat(locale, locale_parts->lang);
805
806    if (mask & E_INTL_LOC_REGION)
807      {
808         if (locale[0] != 0) strcat(locale, "_");
809         strcat(locale, locale_parts->region);
810      }
811
812    if (mask & E_INTL_LOC_CODESET)
813      {
814         if (locale[0] != 0) strcat(locale, ".");
815         strcat(locale, locale_parts->codeset);
816      }
817
818    if (mask & E_INTL_LOC_MODIFIER)
819      {
820         if (locale[0] != 0) strcat(locale, "@");
821         strcat(locale, locale_parts->modifier);
822      }
823
824    return locale;
825 }
826
827 EAPI char *
828 e_intl_locale_charset_canonic_get(const char *charset)
829 {
830    char charset_canonic[32];
831    char c;
832    int i, i_tmp;
833
834    i = 0;
835    i_tmp = 0;
836    while ((c = charset[i++]) != 0)
837      {
838         if (isalnum(c))
839           charset_canonic[i_tmp++] = tolower(c);
840      }
841    charset_canonic[i_tmp] = 0;
842
843    if (!strcmp(charset, charset_canonic))
844      return NULL;
845
846    return strdup(charset_canonic);
847 }
848
849 static Eina_List *
850 _e_intl_locale_system_locales_get(void)
851 {
852    Eina_List *locales;
853    FILE *output;
854
855    locales = NULL;
856    /* FIXME: Maybe needed for other BSD OS, or even Solaris */
857 #ifdef __OpenBSD__
858    output = popen("ls /usr/share/locale", "r");
859 #else
860    output = popen("locale -a", "r");
861 #endif
862    if (output)
863      {
864         char line[32];
865         while (fscanf(output, "%[^\n]\n", line) == 1)
866           locales = eina_list_append(locales, strdup(line));
867
868         pclose(output);
869      }
870    return locales;
871 }
872
873 /*
874  * must be an un aliased locale;
875  */
876 static int
877 _e_intl_locale_validate(const char *locale)
878 {
879    Eina_List *all_locales;
880    E_Locale_Parts *locale_parts;
881    char *locale_next;
882    char *locale_lr;
883    char *locale_cs_canonic;
884    int found;
885
886    found = 0;
887
888    locale_parts = e_intl_locale_parts_get(locale);
889
890    /* Gather the search information */
891    locale_lr =
892      e_intl_locale_parts_combine(locale_parts,
893                                  E_INTL_LOC_LANG | E_INTL_LOC_REGION);
894    if (!locale_lr)
895      {
896         /* Not valid locale, maybe its an alias */
897         locale_lr = strdup(locale);
898         locale_cs_canonic = NULL;
899      }
900    else
901      {
902         if (locale_parts && locale_parts->codeset)
903           locale_cs_canonic = e_intl_locale_charset_canonic_get(locale_parts->codeset);
904         else
905           locale_cs_canonic = NULL;
906      }
907
908    /* Get list of all available locales and aliases */
909    all_locales = _e_intl_locale_system_locales_get();
910
911    /* Match locale with one from the list */
912    EINA_LIST_FREE(all_locales, locale_next)
913      {
914         if (found == 0)
915           {
916              E_Locale_Parts *locale_parts_next;
917              char *locale_lr_next;
918
919              locale_parts_next = e_intl_locale_parts_get(locale_next);
920              locale_lr_next = e_intl_locale_parts_combine(locale_parts_next,
921                                                           E_INTL_LOC_LANG | E_INTL_LOC_REGION);
922
923              if ((locale_parts) && (locale_lr_next) &&
924                  (!strcmp(locale_lr, locale_lr_next)))
925                {
926                   /* Matched lang/region part, now if CS matches */
927                   if ((!locale_parts->codeset) && (!locale_parts_next->codeset))
928                     {
929                        /* Lang/Region parts match and no charsets,
930                         * we have a match
931                         */
932                        found = 1;
933                     }
934                   else if (locale_parts->codeset && locale_parts_next->codeset)
935                     {
936                        if (!strcmp(locale_parts->codeset, locale_parts_next->codeset))
937                          {
938                             /* Lang/Region and charsets match */
939                             found = 1;
940                          }
941                        else if (locale_cs_canonic)
942                          {
943                             char *locale_cs_canonic_next;
944                             /* try to match charsets in canonic form */
945
946                             locale_cs_canonic_next =
947                               e_intl_locale_charset_canonic_get(locale_parts_next->codeset);
948
949                             if (locale_cs_canonic_next)
950                               {
951                                  if (!strcmp(locale_cs_canonic, locale_cs_canonic_next))
952                                    {
953                                       /* Lang/Resion and charsets in canonic
954                                        * form match
955                                        */
956                                       found = 1;
957                                    }
958                                  free(locale_cs_canonic_next);
959                               }
960                             else
961                               {
962                                  if (!strcmp(locale_cs_canonic, locale_parts_next->codeset))
963                                    {
964                                       /* Lang/Resion and charsets in canonic
965                                        * form match
966                                        */
967                                       found = 1;
968                                    }
969                               }
970                          }
971                     }
972                }
973              else
974                {
975                   /* Its an alias */
976                   if (!strcmp(locale_lr, locale_next)) found = 1;
977                }
978              e_intl_locale_parts_free(locale_parts_next);
979              E_FREE(locale_lr_next);
980           }
981         free(locale_next);
982      }
983
984    e_intl_locale_parts_free(locale_parts);
985    free(locale_lr);
986    E_FREE(locale_cs_canonic);
987    return found;
988 }
989
990 /*
991  *  arg local must be an already validated and unaliased locale
992  *  returns the locale search order e.g.
993  *  en_US.UTF-8 ->
994  *   Mask (b) Locale (en_US.UTF-8)
995  *   Mask (a) Locale (en_US)
996  *   Mask (9) Locale (en.UTF-8)
997  *   Mask (8) Locale (en)
998  */
999 static Eina_List *
1000 _e_intl_locale_search_order_get(const char *locale)
1001 {
1002    Eina_List *search_list;
1003    E_Locale_Parts *locale_parts;
1004    char *masked_locale;
1005    int mask;
1006
1007    locale_parts = e_intl_locale_parts_get(locale);
1008    if (!locale_parts) return NULL;
1009
1010    search_list = NULL;
1011    for (mask = E_INTL_LOC_ALL; mask >= E_INTL_LOC_LANG; mask--)
1012      {
1013         if ((mask & locale_parts->mask) == mask)
1014           {
1015              /* Only append if the mask we need is available */
1016              masked_locale = e_intl_locale_parts_combine(locale_parts, mask);
1017              search_list = eina_list_append(search_list, masked_locale);
1018           }
1019      }
1020    e_intl_locale_parts_free(locale_parts);
1021    return search_list;
1022 }
1023
1024 static Eina_List *
1025 _e_intl_imc_dir_scan(const char *dir)
1026 {
1027    Eina_List *imcs = NULL;
1028    Eina_List *files;
1029    char *file;
1030
1031    files = ecore_file_ls(dir);
1032
1033    EINA_LIST_FREE(files, file)
1034      {
1035         if (strstr(file, ".imc"))
1036           {
1037              char buf[PATH_MAX];
1038
1039              snprintf(buf, sizeof(buf), "%s/%s", dir, file);
1040              imcs = eina_list_append(imcs, strdup(buf));
1041           }
1042         free(file);
1043      }
1044    return imcs;
1045 }