doxygen: add newline after \file
[blender.git] / source / blender / blentranslation / intern / blt_lang.c
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2008 Blender Foundation.
17  * All rights reserved.
18  */
19
20 /** \file
21  * \ingroup blt
22  *
23  * Main internationalization functions to set the locale and query available languages.
24  */
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29
30 #ifndef _WIN32
31 #  include <locale.h>
32 #endif
33
34 #include "RNA_types.h"
35
36 #include "BLT_translation.h"
37 #include "BLT_lang.h"  /* own include */
38
39 #include "BLI_path_util.h"
40 #include "BLI_string.h"
41 #include "BLI_utildefines.h"
42
43 #include "BKE_appdir.h"
44
45 #include "DNA_userdef_types.h"
46
47 #include "MEM_guardedalloc.h"
48
49 /* Cached IME support flags */
50 static bool ime_is_lang_supported = false;
51 static void blt_lang_check_ime_supported(void);
52
53 #ifdef WITH_INTERNATIONAL
54
55 #include "BLI_fileops.h"
56 #include "BLI_linklist.h"
57
58 #include "boost_locale_wrapper.h"
59
60 /* Locale options. */
61 static const char **locales = NULL;
62 static int num_locales = 0;
63 static EnumPropertyItem *locales_menu = NULL;
64 static int num_locales_menu = 0;
65
66 static void free_locales(void)
67 {
68         if (locales) {
69                 int idx = num_locales_menu - 1; /* Last item does not need to be freed! */
70                 while (idx--) {
71                         MEM_freeN((void *)locales_menu[idx].identifier);
72                         MEM_freeN((void *)locales_menu[idx].name);
73                         MEM_freeN((void *)locales_menu[idx].description); /* Also frees locales's relevant value! */
74                 }
75
76                 MEM_freeN((void *)locales);
77                 locales = NULL;
78         }
79         if (locales_menu) {
80                 MEM_freeN(locales_menu);
81                 locales_menu = NULL;
82         }
83         num_locales = num_locales_menu = 0;
84 }
85
86 static void fill_locales(void)
87 {
88         const char * const languages_path = BKE_appdir_folder_id(BLENDER_DATAFILES, "locale");
89         char languages[FILE_MAX];
90         LinkNode *lines = NULL, *line;
91         char *str;
92         int idx = 0;
93
94         free_locales();
95
96         BLI_join_dirfile(languages, FILE_MAX, languages_path, "languages");
97         line = lines = BLI_file_read_as_lines(languages);
98
99         /* This whole "parsing" code is a bit weak, in that it expects strictly formatted input file...
100          * Should not be a problem, though, as this file is script-generated! */
101
102         /* First loop to find highest locale ID */
103         while (line) {
104                 int t;
105                 str = (char *)line->link;
106                 if (str[0] == '#' || str[0] == '\0') {
107                         line = line->next;
108                         continue; /* Comment or void... */
109                 }
110                 t = atoi(str);
111                 if (t >= num_locales)
112                         num_locales = t + 1;
113                 num_locales_menu++;
114                 line = line->next;
115         }
116         num_locales_menu++; /* The "closing" void item... */
117
118         /* And now, build locales and locale_menu! */
119         locales_menu = MEM_callocN(num_locales_menu * sizeof(EnumPropertyItem), __func__);
120         line = lines;
121         /* Do not allocate locales with zero-sized mem, as LOCALE macro uses NULL locales as invalid marker! */
122         if (num_locales > 0) {
123                 locales = MEM_callocN(num_locales * sizeof(char *), __func__);
124                 while (line) {
125                         int id;
126                         char *loc, *sep1, *sep2, *sep3;
127
128                         str = (char *)line->link;
129                         if (str[0] == '#' || str[0] == '\0') {
130                                 line = line->next;
131                                 continue;
132                         }
133
134                         id = atoi(str);
135                         sep1 = strchr(str, ':');
136                         if (sep1) {
137                                 sep1++;
138                                 sep2 = strchr(sep1, ':');
139                                 if (sep2) {
140                                         locales_menu[idx].value = id;
141                                         locales_menu[idx].icon = 0;
142                                         locales_menu[idx].name = BLI_strdupn(sep1, sep2 - sep1);
143
144                                         sep2++;
145                                         sep3 = strchr(sep2, ':');
146
147                                         if (sep3) {
148                                                 locales_menu[idx].identifier = loc = BLI_strdupn(sep2, sep3 - sep2);
149                                         }
150                                         else {
151                                                 locales_menu[idx].identifier = loc = BLI_strdup(sep2);
152                                         }
153
154                                         if (id == 0) {
155                                                 /* The DEFAULT/Automatic item... */
156                                                 if (BLI_strnlen(loc, 2)) {
157                                                         locales[id] = "";
158                                                         /* Keep this tip in sync with the one in rna_userdef (rna_enum_language_default_items). */
159                                                         locales_menu[idx].description = BLI_strdup("Automatically choose system's defined language "
160                                                                                                    "if available, or fall-back to English");
161                                                 }
162                                                 /* Menu "label", not to be stored in locales! */
163                                                 else {
164                                                         locales_menu[idx].description = BLI_strdup("");
165                                                 }
166                                         }
167                                         else {
168                                                 locales[id] = locales_menu[idx].description = BLI_strdup(loc);
169                                         }
170                                         idx++;
171                                 }
172                         }
173
174                         line = line->next;
175                 }
176         }
177
178         /* Add closing item to menu! */
179         locales_menu[idx].identifier = NULL;
180         locales_menu[idx].value = locales_menu[idx].icon = 0;
181         locales_menu[idx].name = locales_menu[idx].description = "";
182
183         BLI_file_free_lines(lines);
184 }
185 #endif  /* WITH_INTERNATIONAL */
186
187 EnumPropertyItem *BLT_lang_RNA_enum_properties(void)
188 {
189 #ifdef WITH_INTERNATIONAL
190         return locales_menu;
191 #else
192         return NULL;
193 #endif
194 }
195
196 void BLT_lang_init(void)
197 {
198 #ifdef WITH_INTERNATIONAL
199         const char * const messagepath = BKE_appdir_folder_id(BLENDER_DATAFILES, "locale");
200 #endif
201
202         /* Make sure LANG is correct and wouldn't cause std::rumtime_error. */
203 #ifndef _WIN32
204         /* TODO(sergey): This code only ensures LANG is set properly, so later when
205          * Cycles will try to use file system API from boost there'll be no runtime
206          * exception generated by std::locale() which _requires_ having proper LANG
207          * set in the environment.
208          *
209          * Ideally we also need to ensure LC_ALL, LC_MESSAGES and others are also
210          * set to a proper value, but currently it's not a huge deal and doesn't
211          * cause any headache.
212          *
213          * Would also be good to find nicer way to check if LANG is correct.
214          */
215         const char *lang = BLI_getenv("LANG");
216         if (lang != NULL) {
217                 char *old_locale = setlocale(LC_ALL, NULL);
218                 /* Make a copy so subsequenct setlocale() doesn't interfere. */
219                 old_locale = BLI_strdup(old_locale);
220                 if (setlocale(LC_ALL, lang) == NULL) {
221                         setenv("LANG", "C", 1);
222                         printf("Warning: Falling back to the standard locale (\"C\")\n");
223                 }
224                 setlocale(LC_ALL, old_locale);
225                 MEM_freeN(old_locale);
226         }
227 #endif
228
229 #ifdef WITH_INTERNATIONAL
230         if (messagepath) {
231                 bl_locale_init(messagepath, TEXT_DOMAIN_NAME);
232                 fill_locales();
233         }
234         else {
235                 printf("%s: 'locale' data path for translations not found, continuing\n", __func__);
236         }
237 #else
238 #endif
239 }
240
241 void BLT_lang_free(void)
242 {
243 #ifdef WITH_INTERNATIONAL
244         free_locales();
245 #else
246 #endif
247 }
248
249 #ifdef WITH_INTERNATIONAL
250 #  define ULANGUAGE ((U.language >= 0 && U.language < num_locales) ? U.language : 0)
251 #  define LOCALE(_id) (locales ? locales[(_id)] : "")
252 #endif
253
254 void BLT_lang_set(const char *str)
255 {
256 #ifdef WITH_INTERNATIONAL
257         int ulang = ULANGUAGE;
258         const char *short_locale = str ? str : LOCALE(ulang);
259         const char *short_locale_utf8 = NULL;
260
261         if ((U.transopts & USER_DOTRANSLATE) == 0)
262                 return;
263
264         /* We want to avoid locales like '.UTF-8'! */
265         if (short_locale[0]) {
266                 /* Hurrey! encoding needs to be placed *before* variant! */
267                 char *variant = strchr(short_locale, '@');
268                 if (variant) {
269                         char *locale = BLI_strdupn(short_locale, variant - short_locale);
270                         short_locale_utf8 = BLI_sprintfN("%s.UTF-8%s", locale, variant);
271                         MEM_freeN(locale);
272                 }
273                 else {
274                         short_locale_utf8 = BLI_sprintfN("%s.UTF-8", short_locale);
275                 }
276                 bl_locale_set(short_locale_utf8);
277                 MEM_freeN((void *)short_locale_utf8);
278         }
279         else {
280                 bl_locale_set(short_locale);
281         }
282 #else
283         (void)str;
284 #endif
285         blt_lang_check_ime_supported();
286 }
287
288 /* Get the current locale (short code, e.g. es_ES). */
289 const char *BLT_lang_get(void)
290 {
291 #ifdef WITH_INTERNATIONAL
292         if (BLT_translate()) {
293                 const char *locale = LOCALE(ULANGUAGE);
294                 if (locale[0] == '\0') {
295                         /* Default locale, we have to find which one we are actually using! */
296                         locale = bl_locale_get();
297                 }
298                 return locale;
299         }
300         return "en_US";  /* Kind of default locale in Blender when no translation enabled. */
301 #else
302         return "";
303 #endif
304 }
305
306 #undef LOCALE
307 #undef ULANGUAGE
308
309 /* Get locale's elements (if relevant pointer is not NULL and element actually exists, e.g. if there is no variant,
310  * *variant and *language_variant will always be NULL).
311  * Non-null elements are always MEM_mallocN'ed, it's the caller's responsibility to free them.
312  * NOTE: Keep that one always available, you never know, may become useful even in no-WITH_INTERNATIONAL context...
313  */
314 void BLT_lang_locale_explode(
315         const char *locale, char **language, char **country, char **variant,
316         char **language_country, char **language_variant)
317 {
318         char *m1, *m2, *_t = NULL;
319
320         m1 = strchr(locale, '_');
321         m2 = strchr(locale, '@');
322
323         if (language || language_variant) {
324                 if (m1 || m2) {
325                         _t = m1 ? BLI_strdupn(locale, m1 - locale) : BLI_strdupn(locale, m2 - locale);
326                         if (language)
327                                 *language = _t;
328                 }
329                 else if (language) {
330                         *language = BLI_strdup(locale);
331                 }
332         }
333         if (country) {
334                 if (m1)
335                         *country = m2 ? BLI_strdupn(m1 + 1, m2 - (m1 + 1)) : BLI_strdup(m1 + 1);
336                 else
337                         *country = NULL;
338         }
339         if (variant) {
340                 if (m2)
341                         *variant = BLI_strdup(m2 + 1);
342                 else
343                         *variant = NULL;
344         }
345         if (language_country) {
346                 if (m1)
347                         *language_country = m2 ? BLI_strdupn(locale, m2 - locale) : BLI_strdup(locale);
348                 else
349                         *language_country = NULL;
350         }
351         if (language_variant) {
352                 if (m2)
353                         *language_variant = m1 ? BLI_strdupcat(_t, m2) : BLI_strdup(locale);
354                 else
355                         *language_variant = NULL;
356         }
357         if (_t && !language) {
358                 MEM_freeN(_t);
359         }
360 }
361
362 /* Test if the translation context allows IME input - used to
363  * avoid weird character drawing if IME inputs non-ascii chars.
364  */
365 static void blt_lang_check_ime_supported(void)
366 {
367 #ifdef WITH_INPUT_IME
368         const char *uilng = BLT_lang_get();
369         if (U.transopts & USER_DOTRANSLATE) {
370                 ime_is_lang_supported = STREQ(uilng, "zh_CN") ||
371                                         STREQ(uilng, "zh_TW") ||
372                                         STREQ(uilng, "ja_JP");
373         }
374         else {
375                 ime_is_lang_supported = false;
376         }
377 #else
378         ime_is_lang_supported = false;
379 #endif
380 }
381
382 bool BLT_lang_is_ime_supported(void)
383 {
384 #ifdef WITH_INPUT_IME
385         return ime_is_lang_supported;
386 #else
387         return false;
388 #endif
389 }