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
e_intl_init(void)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
e_intl_shutdown(void)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
e_intl_post_init(void)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
e_intl_post_shutdown(void)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 E_API void
e_intl_language_set(const char * lang)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 resolved.\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 E_API const char *
e_intl_language_get(void)257 e_intl_language_get(void)
258 {
259    return _e_intl_language;
260 }
261 
262 E_API const char *
e_intl_language_alias_get(void)263 e_intl_language_alias_get(void)
264 {
265    return _e_intl_language_alias;
266 }
267 
268 E_API Eina_List *
e_intl_language_list(void)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
_e_intl_language_list_find(Eina_List * language_list,char * language)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 E_API void
e_intl_input_method_set(const char * imc_path)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                        // if you see valgrind complain about memory
349                        // definitely lost here... it's wrong.
350                        _e_intl_input_method_exec = e_util_exe_safe_run
351                          (imc->e_im_exec, NULL);
352                        ecore_exe_tag_set(_e_intl_input_method_exec, "E/im_exec");
353 
354                        if ((!_e_intl_input_method_exec) ||
355                            (!ecore_exe_pid_get(_e_intl_input_method_exec)))
356                          e_util_dialog_show(_("Input Method Error"),
357                                             _("Error starting the input method executable<ps/><ps/>"
358                                               "please make sure that your input<ps/>"
359                                               "method configuration is correct and<ps/>"
360                                               "that your configuration's<ps/>"
361                                               "executable is in your PATH<ps/>"));
362                     }
363                   e_intl_input_method_config_free(imc);
364                }
365           }
366      }
367 }
368 
369 E_API Eina_List *
e_intl_input_method_list(void)370 e_intl_input_method_list(void)
371 {
372    Eina_List *input_methods;
373    Eina_List *im_list;
374 
375    im_list = NULL;
376 
377    /* Personal Path */
378    im_list = _e_intl_imc_dir_scan(e_intl_imc_personal_path_get());
379 
380    /* System Path */
381    input_methods = _e_intl_imc_dir_scan(e_intl_imc_system_path_get());
382    im_list = eina_list_merge(im_list, input_methods);
383 
384    return im_list;
385 }
386 
387 const char *
e_intl_imc_personal_path_get(void)388 e_intl_imc_personal_path_get(void)
389 {
390    if (!_e_intl_imc_personal_path)
391      {
392         char buf[4096];
393 
394         e_user_dir_concat_static(buf, "input_methods");
395         _e_intl_imc_personal_path = eina_stringshare_add(buf);
396      }
397    return _e_intl_imc_personal_path;
398 }
399 
400 const char *
e_intl_imc_system_path_get(void)401 e_intl_imc_system_path_get(void)
402 {
403    if (!_e_intl_imc_system_path)
404      {
405         char buf[4096];
406 
407         e_prefix_data_concat_static(buf, "data/input_methods");
408         _e_intl_imc_system_path = eina_stringshare_add(buf);
409      }
410    return _e_intl_imc_system_path;
411 }
412 
413 static Eina_Bool
_e_intl_cb_exit(void * data EINA_UNUSED,int type EINA_UNUSED,void * event)414 _e_intl_cb_exit(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
415 {
416    Ecore_Exe_Event_Del *ev;
417 
418    ev = event;
419    if (!ev->exe) return ECORE_CALLBACK_PASS_ON;
420    if (ev->exe == _e_intl_input_method_exec)
421      _e_intl_input_method_exec = NULL;
422    return ECORE_CALLBACK_PASS_ON;
423 }
424 
425 static void
_e_intl_locale_hash_free(Eina_Hash * locale_hash)426 _e_intl_locale_hash_free(Eina_Hash *locale_hash)
427 {
428    if (!locale_hash) return;
429    eina_hash_foreach(locale_hash, _e_intl_locale_hash_free_cb, NULL);
430    eina_hash_free(locale_hash);
431 }
432 
433 static Eina_Bool
_e_intl_locale_hash_free_cb(const Eina_Hash * hash EINA_UNUSED,const void * key EINA_UNUSED,void * data,void * fdata EINA_UNUSED)434 _e_intl_locale_hash_free_cb(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata EINA_UNUSED)
435 {
436    free(data);
437    return 1;
438 }
439 
440 /*
441  * get the directory associated with the language. Language Must be valid alias
442  * i.e. Already validated and already de-aliased.
443  *
444  * NULL means:
445  *  1) The user does not have an enlightenment translation for this locale
446  *  2) The user does not have their locale.aliases configured correctly
447  *
448  * @return NULL if not found.
449  */
450 static char *
_e_intl_language_path_find(char * language)451 _e_intl_language_path_find(char *language)
452 {
453    char *directory;
454    char *data;
455    Eina_List *dir_list;
456    Eina_List *search_list;
457    Eina_List *next_dir;
458    Eina_List *next_search;
459    E_Path_Dir *epd;
460    char *search_locale;
461    int found;
462 
463    search_list = _e_intl_locale_search_order_get(language);
464 
465    if (!search_list) return NULL;
466 
467    directory = NULL;
468    found = 0;
469    dir_list = e_path_dir_list_get(path_messages);
470 
471    /* For each directory in the path */
472    EINA_LIST_FOREACH(dir_list, next_dir, epd)
473      {
474         if (found) break;
475 
476         /* Match canonicalized locale against each possible search */
477         EINA_LIST_FOREACH(search_list, next_search, search_locale)
478           {
479              char message_path[PATH_MAX];
480 
481              snprintf(message_path, sizeof(message_path), "%s/%s/LC_MESSAGES/%s.mo", epd->dir, search_locale, PACKAGE);
482 
483              if ((ecore_file_exists(message_path)) && (!ecore_file_is_dir(message_path)))
484                {
485                   directory = strdup(epd->dir);
486                   found = 1;
487                   break;
488                }
489           }
490      }
491 
492    e_path_dir_list_free(dir_list);
493 
494    EINA_LIST_FREE(search_list, data)
495      free(data);
496 
497    return directory;
498 }
499 
500 static Eina_List *
_e_intl_language_dir_scan(const char * dir)501 _e_intl_language_dir_scan(const char *dir)
502 {
503    Eina_List *languages;
504    Eina_List *files;
505    char *file;
506 
507    languages = NULL;
508 
509    files = ecore_file_ls(dir);
510    if (!files) return NULL;
511 
512    if (files)
513      {
514         EINA_LIST_FREE(files, file)
515           {
516              char file_path[PATH_MAX];
517 
518              snprintf(file_path, sizeof(file_path), "%s/%s/LC_MESSAGES/%s.mo",
519                       dir, file, PACKAGE);
520              if (ecore_file_exists(file_path) && !ecore_file_is_dir(file_path))
521                languages = eina_list_append(languages, file);
522              else
523                free(file);
524           }
525      }
526    return languages;
527 }
528 
529 /* get the alias for a locale
530  *
531  * return pointer to allocated alias string. never returns NULL
532  * String will be the same if its a valid locale already or there
533  * is no alias.
534  */
535 static char *
_e_intl_locale_alias_get(const char * language)536 _e_intl_locale_alias_get(const char *language)
537 {
538    Eina_Hash *alias_hash;
539    char *alias;
540    char lbuf[256];
541    char *lower_language = lbuf;
542 
543    if ((!language) || (!strncmp(language, "POSIX", strlen("POSIX"))))
544      return strdup("C");
545 
546    alias_hash = _e_intl_locale_alias_hash_get();
547    if (!alias_hash) /* No alias file available */
548      return strdup(language);
549 
550    strncpy(lbuf, language, sizeof(lbuf) - 1);
551    lbuf[sizeof(lbuf) - 1] = '\0';
552    eina_str_tolower(&lower_language);
553    alias = eina_hash_find(alias_hash, lower_language);
554 
555    if (alias)
556      alias = strdup(alias);
557    else
558      alias = strdup(language);
559 
560    _e_intl_locale_hash_free(alias_hash);
561 
562    return alias;
563 }
564 
565 static Eina_Hash *
_e_intl_locale_alias_hash_get(void)566 _e_intl_locale_alias_hash_get(void)
567 {
568    Eina_List *next;
569    Eina_List *dir_list;
570    E_Path_Dir *epd;
571    Eina_Hash *alias_hash;
572 
573    dir_list = e_path_dir_list_get(path_messages);
574    alias_hash = NULL;
575 
576    EINA_LIST_FOREACH(dir_list, next, epd)
577      {
578         char buf[4096];
579         FILE *f;
580 
581         snprintf(buf, sizeof(buf), "%s/locale.alias", epd->dir);
582         f = fopen(buf, "r");
583         if (f)
584           {
585              char alias[4096], locale[4096];
586 
587              /* read locale alias lines */
588              while (fscanf(f, "%4095s %4095[^\n]\n", alias, locale) == 2)
589                {
590                   /* skip comments */
591                   if ((alias[0] == '!') || (alias[0] == '#'))
592                     continue;
593 
594                   /* skip dupes */
595                   if (eina_hash_find(alias_hash, alias))
596                     continue;
597 
598                   if (!alias_hash) alias_hash = eina_hash_string_superfast_new(NULL);
599                   eina_hash_add(alias_hash, alias, strdup(locale));
600                }
601              fclose(f);
602           }
603      }
604    e_path_dir_list_free(dir_list);
605 
606    return alias_hash;
607 }
608 
609 /* return parts of the locale that are requested in the mask
610  * return null if the locale looks to be invalid (Does not have
611  * ll_DD)
612  *
613  * the returned string needs to be freed
614  */
615 /*
616  * Not canonic, just gets the parts
617  */
618 E_API E_Locale_Parts *
e_intl_locale_parts_get(const char * locale)619 e_intl_locale_parts_get(const char *locale)
620 {
621    /* Parse Results */
622    E_Locale_Parts *locale_parts;
623    char language[4] = {0};
624    char territory[4] = {0};
625    char codeset[32] = {0};
626    char modifier[32] = {0};
627 
628    /* Parse State */
629    int state = 0;   /* start out looking for the language */
630    unsigned int locale_idx;
631    int tmp_idx = 0;
632 
633    /* Parse Loop - Separators are _ . @ */
634    for (locale_idx = 0; locale_idx < strlen(locale); locale_idx++)
635      {
636         char locale_char;
637         locale_char = locale[locale_idx];
638 
639         /* we have finished scanning the locale string */
640         if (!locale_char)
641           break;
642 
643         /* scan this character based on the current start */
644         switch (state)
645           {
646            case 0: /* Gathering Language */
647              if (tmp_idx == 2 && locale_char == '_')
648                {
649                   state++;
650                   language[tmp_idx] = 0;
651                   tmp_idx = 0;
652                }
653              else if ((tmp_idx < 2) && (islower(locale_char)))
654                language[tmp_idx++] = locale_char;
655              else if (locale_char == '.') // no territory
656                {
657                   state = 2;
658                   language[tmp_idx] = 0;
659                   tmp_idx = 0;
660                }
661              else
662                return NULL;
663              break;
664 
665            case 1: /* Gathering Territory */
666              if (tmp_idx == 2 && locale_char == '.')
667                {
668                   state++;
669                   territory[tmp_idx] = 0;
670                   tmp_idx = 0;
671                }
672              else if ((tmp_idx == 2) && (locale_char == '@'))
673                {
674                   state += 2;
675                   territory[tmp_idx] = 0;
676                   codeset[0] = 0;
677                   tmp_idx = 0;
678                }
679              else if ((tmp_idx < 2) && isupper(locale_char))
680                territory[tmp_idx++] = locale_char;
681              else
682                return NULL;
683              break;
684 
685            case 2: /* Gathering Codeset */
686              if (locale_char == '@')
687                {
688                   state++;
689                   codeset[tmp_idx] = 0;
690                   tmp_idx = 0;
691                }
692              else if (tmp_idx < 31)
693                codeset[tmp_idx++] = locale_char;
694              else
695                return NULL;
696              break;
697 
698            case 3: /* Gathering modifier */
699              if (tmp_idx < 31)
700                modifier[tmp_idx++] = locale_char;
701              else
702                return NULL;
703              break;
704 
705            default:
706              break;
707           }
708      }
709 
710    /* set end-of-string \0 */
711    /* There are no breaks here on purpose */
712    switch (state)
713      {
714       case 0:
715         language[tmp_idx] = 0;
716         tmp_idx = 0;
717         EINA_FALLTHROUGH;
718         /* no break */
719 
720       case 1:
721         territory[tmp_idx] = 0;
722         tmp_idx = 0;
723         EINA_FALLTHROUGH;
724         /* no break */
725 
726       case 2:
727         codeset[tmp_idx] = 0;
728         tmp_idx = 0;
729         EINA_FALLTHROUGH;
730         /* no break */
731 
732       case 3:
733         modifier[tmp_idx] = 0;
734         EINA_FALLTHROUGH;
735         /* no break */
736 
737       default:
738         break;
739      }
740 
741    if ((!language[0]) && (!territory[0]) && (!codeset[0]) && (!modifier[0])) return NULL;
742    locale_parts = E_NEW(E_Locale_Parts, 1);
743 
744    /* Put the parts of the string together */
745    if (language[0] != 0)
746      {
747         locale_parts->mask |= E_INTL_LOC_LANG;
748         locale_parts->lang = eina_stringshare_add(language);
749      }
750    if (territory[0] != 0)
751      {
752         locale_parts->mask |= E_INTL_LOC_REGION;
753         locale_parts->region = eina_stringshare_add(territory);
754      }
755    if (codeset[0] != 0)
756      {
757         locale_parts->mask |= E_INTL_LOC_CODESET;
758         locale_parts->codeset = eina_stringshare_add(codeset);
759      }
760    if (modifier[0] != 0)
761      {
762         locale_parts->mask |= E_INTL_LOC_MODIFIER;
763         locale_parts->modifier = eina_stringshare_add(modifier);
764      }
765 
766    return locale_parts;
767 }
768 
769 E_API void
e_intl_locale_parts_free(E_Locale_Parts * locale_parts)770 e_intl_locale_parts_free(E_Locale_Parts *locale_parts)
771 {
772    if (locale_parts)
773      {
774         if (locale_parts->lang) eina_stringshare_del(locale_parts->lang);
775         if (locale_parts->region) eina_stringshare_del(locale_parts->region);
776         if (locale_parts->codeset) eina_stringshare_del(locale_parts->codeset);
777         if (locale_parts->modifier) eina_stringshare_del(locale_parts->modifier);
778         E_FREE(locale_parts);
779      }
780 }
781 
782 E_API char *
e_intl_locale_parts_combine(E_Locale_Parts * locale_parts,int mask)783 e_intl_locale_parts_combine(E_Locale_Parts *locale_parts, int mask)
784 {
785    int locale_size;
786    char *locale;
787 
788    if (!locale_parts) return NULL;
789 
790    if ((mask & locale_parts->mask) != mask) return NULL;
791 
792    /* Construct the clean locale string */
793    locale_size = 0;
794 
795    /* determine the size */
796    if (mask & E_INTL_LOC_LANG)
797      locale_size = strlen(locale_parts->lang) + 1;
798 
799    if (mask & E_INTL_LOC_REGION)
800      locale_size += strlen(locale_parts->region) + 1;
801 
802    if (mask & E_INTL_LOC_CODESET)
803      locale_size += strlen(locale_parts->codeset) + 1;
804 
805    if (mask & E_INTL_LOC_MODIFIER)
806      locale_size += strlen(locale_parts->modifier) + 1;
807 
808    if (!locale_size) return NULL;
809 
810    /* Allocate memory */
811    locale = (char *)malloc(locale_size);
812    locale[0] = 0;
813 
814    if (mask & E_INTL_LOC_LANG)
815      strcat(locale, locale_parts->lang);
816 
817    if (mask & E_INTL_LOC_REGION)
818      {
819         if (locale[0] != 0) strcat(locale, "_");
820         strcat(locale, locale_parts->region);
821      }
822 
823    if (mask & E_INTL_LOC_CODESET)
824      {
825         if (locale[0] != 0) strcat(locale, ".");
826         strcat(locale, locale_parts->codeset);
827      }
828 
829    if (mask & E_INTL_LOC_MODIFIER)
830      {
831         if (locale[0] != 0) strcat(locale, "@");
832         strcat(locale, locale_parts->modifier);
833      }
834 
835    return locale;
836 }
837 
838 E_API char *
e_intl_locale_charset_canonic_get(const char * charset)839 e_intl_locale_charset_canonic_get(const char *charset)
840 {
841    char charset_canonic[32];
842    char c;
843    int i, i_tmp;
844 
845    i = 0;
846    i_tmp = 0;
847    while ((c = charset[i++]) != 0)
848      {
849         if (isalnum(c))
850           charset_canonic[i_tmp++] = tolower(c);
851      }
852    charset_canonic[i_tmp] = 0;
853 
854    if (!strcmp(charset, charset_canonic))
855      return NULL;
856 
857    return strdup(charset_canonic);
858 }
859 
860 static Eina_List *
_e_intl_locale_system_locales_get(void)861 _e_intl_locale_system_locales_get(void)
862 {
863    Eina_List *locales;
864    FILE *output;
865 
866    locales = NULL;
867    /* FIXME: Maybe needed for other BSD OS, or even Solaris */
868 #ifdef __OpenBSD__
869    output = popen("ls /usr/share/locale", "r");
870 #else
871    output = popen("locale -a", "r");
872 #endif
873    if (output)
874      {
875         char line[32];
876         while (fscanf(output, "%31[^\n]\n", line) == 1)
877           locales = eina_list_append(locales, strdup(line));
878 
879         pclose(output);
880      }
881    return locales;
882 }
883 
884 /*
885  * must be an un aliased locale;
886  */
887 static int
_e_intl_locale_validate(const char * locale)888 _e_intl_locale_validate(const char *locale)
889 {
890    Eina_List *all_locales;
891    E_Locale_Parts *locale_parts;
892    char *locale_next;
893    char *locale_lr = NULL;
894    char *locale_cs_canonic;
895    int found;
896 
897    found = 0;
898 
899    locale_parts = e_intl_locale_parts_get(locale);
900 
901    /* Gather the search information */
902    if (locale_parts)
903      {
904         if (locale_parts->mask & E_INTL_LOC_REGION)
905           locale_lr = e_intl_locale_parts_combine(locale_parts, E_INTL_LOC_LANG | E_INTL_LOC_REGION);
906         else if (locale_parts->lang)
907           locale_lr = strdup(locale_parts->lang);
908      }
909    if (!locale_lr)
910      {
911         /* Not valid locale, maybe its an alias */
912         locale_lr = strdup(locale);
913         locale_cs_canonic = NULL;
914      }
915    else
916      {
917         if (locale_parts && locale_parts->codeset)
918           locale_cs_canonic = e_intl_locale_charset_canonic_get(locale_parts->codeset);
919         else
920           locale_cs_canonic = NULL;
921      }
922 
923    /* Get list of all available locales and aliases */
924    all_locales = _e_intl_locale_system_locales_get();
925 
926    /* Match locale with one from the list */
927    EINA_LIST_FREE(all_locales, locale_next)
928      {
929         if (found == 0)
930           {
931              E_Locale_Parts *locale_parts_next;
932              char *locale_lr_next = NULL;
933 
934              locale_parts_next = e_intl_locale_parts_get(locale_next);
935              if (locale_parts_next)
936                {
937                   if (locale_parts_next->mask & E_INTL_LOC_REGION)
938                     locale_lr_next = e_intl_locale_parts_combine(locale_parts_next,
939                                                                  E_INTL_LOC_LANG | E_INTL_LOC_REGION);
940                   else if (locale_parts_next->lang)
941                     locale_lr_next = strdup(locale_parts_next->lang);
942                }
943              if ((locale_parts) && (locale_lr_next) &&
944                  (!strcmp(locale_lr, locale_lr_next)))
945                {
946                   /* Matched lang/region part, now if CS matches */
947                   if ((!locale_parts->codeset) && (!locale_parts_next->codeset))
948                     {
949                        /* Lang/Region parts match and no charsets,
950                         * we have a match
951                         */
952                        found = 1;
953                     }
954                   else if (locale_parts->codeset && locale_parts_next->codeset)
955                     {
956                        if (!strcmp(locale_parts->codeset, locale_parts_next->codeset))
957                          {
958                             /* Lang/Region and charsets match */
959                             found = 1;
960                          }
961                        else if (locale_cs_canonic)
962                          {
963                             char *locale_cs_canonic_next;
964                             /* try to match charsets in canonic form */
965 
966                             locale_cs_canonic_next =
967                               e_intl_locale_charset_canonic_get(locale_parts_next->codeset);
968 
969                             if (locale_cs_canonic_next)
970                               {
971                                  if (!strcmp(locale_cs_canonic, locale_cs_canonic_next))
972                                    {
973                                       /* Lang/Resion and charsets in canonic
974                                        * form match
975                                        */
976                                       found = 1;
977                                    }
978                                  free(locale_cs_canonic_next);
979                               }
980                             else
981                               {
982                                  if (!strcmp(locale_cs_canonic, locale_parts_next->codeset))
983                                    {
984                                       /* Lang/Resion and charsets in canonic
985                                        * form match
986                                        */
987                                       found = 1;
988                                    }
989                               }
990                          }
991                     }
992                }
993              else
994                {
995                   /* Its an alias */
996                   if (!strcmp(locale_lr, locale_next)) found = 1;
997                }
998              e_intl_locale_parts_free(locale_parts_next);
999              E_FREE(locale_lr_next);
1000           }
1001         free(locale_next);
1002      }
1003 
1004    e_intl_locale_parts_free(locale_parts);
1005    free(locale_lr);
1006    E_FREE(locale_cs_canonic);
1007    return found;
1008 }
1009 
1010 /*
1011  *  arg local must be an already validated and unaliased locale
1012  *  returns the locale search order e.g.
1013  *  en_US.UTF-8 ->
1014  *   Mask (b) Locale (en_US.UTF-8)
1015  *   Mask (a) Locale (en_US)
1016  *   Mask (9) Locale (en.UTF-8)
1017  *   Mask (8) Locale (en)
1018  */
1019 static Eina_List *
_e_intl_locale_search_order_get(const char * locale)1020 _e_intl_locale_search_order_get(const char *locale)
1021 {
1022    Eina_List *search_list;
1023    E_Locale_Parts *locale_parts;
1024    char *masked_locale;
1025    int mask;
1026 
1027    locale_parts = e_intl_locale_parts_get(locale);
1028    if (!locale_parts) return NULL;
1029 
1030    search_list = NULL;
1031    for (mask = E_INTL_LOC_ALL; mask >= E_INTL_LOC_LANG; mask--)
1032      {
1033         if ((mask & locale_parts->mask) == mask)
1034           {
1035              /* Only append if the mask we need is available */
1036              masked_locale = e_intl_locale_parts_combine(locale_parts, mask);
1037              search_list = eina_list_append(search_list, masked_locale);
1038           }
1039      }
1040    e_intl_locale_parts_free(locale_parts);
1041    return search_list;
1042 }
1043 
1044 static Eina_List *
_e_intl_imc_dir_scan(const char * dir)1045 _e_intl_imc_dir_scan(const char *dir)
1046 {
1047    Eina_List *imcs = NULL;
1048    Eina_List *files;
1049    char *file;
1050 
1051    files = ecore_file_ls(dir);
1052 
1053    EINA_LIST_FREE(files, file)
1054      {
1055         if (strstr(file, ".imc"))
1056           {
1057              char buf[PATH_MAX];
1058 
1059              snprintf(buf, sizeof(buf), "%s/%s", dir, file);
1060              imcs = eina_list_append(imcs, strdup(buf));
1061           }
1062         free(file);
1063      }
1064    return imcs;
1065 }
1066 
1067