1 /*
2 i18n.c
3
4 For Tux Paint
5 Language-related functions
6
7 Copyright (c) 2002-2020 by Bill Kendrick and others
8 bill@newbreedsoftware.com
9 http://www.tuxpaint.org/
10
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
15
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 (See COPYING.txt)
25
26 $Id$
27
28 June 14, 2002 - July 26, 2020
29 */
30
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <libintl.h>
35 #include <locale.h>
36 #include "i18n.h"
37 #include "debug.h"
38
39 #ifdef WIN32
40 #include <sys/types.h>
41 #endif
42
43 #ifdef __BEOS__
44 #include <wchar.h>
45 #else
46 #include <wchar.h>
47 #include <wctype.h>
48 #endif
49
50
51 /* Globals: */
52
53 static int langint = LANG_EN;
54
55 /* Strings representing each language's ISO 639 (-1 or -2) codes.
56 * Should map to the 'enum' of possible languages ("LANG_xxx")
57 * found in "i18n.h" (where "NUM_LANGS" is found, as the final
58 * entry in the 'enum' list). */
59 const char *lang_prefixes[NUM_LANGS] = {
60 "ach",
61 "af",
62 "ak",
63 "am",
64 "an",
65 "ar",
66 "as",
67 "ast",
68 "az",
69 "be",
70 "bg",
71 "bm",
72 "bn",
73 "bo",
74 "br",
75 "brx",
76 "bs",
77 "ca@valencia",
78 "ca",
79 "cgg",
80 "cs",
81 "cy",
82 "da",
83 "de",
84 "doi",
85 "el",
86 "en",
87 "en_AU",
88 "en_CA",
89 "en_GB",
90 "en_ZA",
91 "eo",
92 "es_MX",
93 "es",
94 "et",
95 "eu",
96 "fa",
97 "ff",
98 "fi",
99 "fo",
100 "fr",
101 "ga",
102 "gd",
103 "gl",
104 "gos",
105 "gu",
106 "he",
107 "hi",
108 "hr",
109 "hu",
110 "hy",
111 "tlh",
112 "id",
113 "is",
114 "it",
115 "iu",
116 "ja",
117 "ka",
118 "kab",
119 "kn",
120 "km",
121 "kok@roman",
122 "kok",
123 "ko",
124 "ks@devanagari",
125 "ks",
126 "ku",
127 "lb",
128 "lg",
129 "lt",
130 "lv",
131 "mai",
132 "ml",
133 "mk",
134 "mn",
135 "mni",
136 "mni@meiteimayek",
137 "mr",
138 "ms",
139 "nb",
140 "ne",
141 "nl",
142 "nn",
143 "nr",
144 "nso",
145 "oc",
146 "oj",
147 "or",
148 "pa",
149 "pl",
150 "pt_BR",
151 "pt",
152 "ro",
153 "ru",
154 "rw",
155 "sat@olchiki",
156 "sat",
157 "sa",
158 "sc",
159 "sd",
160 "sd@devanagari",
161 "shs",
162 "si",
163 "sk",
164 "sl",
165 "son",
166 "sq",
167 "sr@latin",
168 "sr",
169 "su",
170 "sv",
171 "sw",
172 "ta",
173 "te",
174 "th",
175 "tl",
176 "tr",
177 "tw",
178 "uk",
179 "ur",
180 "vec",
181 "ve",
182 "vi",
183 "wa",
184 "wo",
185 "xh",
186 "zam",
187 "zh_CN",
188 "zh_TW",
189 "zu",
190 };
191
192
193 /* Languages which don't use the default font */
194 static int lang_use_own_font[] = {
195 LANG_AR,
196 LANG_BO,
197 LANG_GU,
198 LANG_HI,
199 LANG_JA,
200 LANG_KA,
201 LANG_KO,
202 LANG_ML,
203 LANG_TA,
204 LANG_TE,
205 LANG_TH,
206 LANG_ZH_CN,
207 LANG_ZH_TW,
208 -1
209 };
210
211 /* Languages which are written right-to-left */
212 static int lang_use_right_to_left[] = {
213 LANG_AR,
214 LANG_FA,
215 LANG_HE,
216 LANG_KS,
217 LANG_SD,
218 LANG_UR,
219 -1
220 };
221
222 /* FIXME: */
223 static int lang_use_right_to_left_word[] = {
224 #ifdef NO_SDLPANGO
225 LANG_HE,
226 #endif
227 -1
228 };
229
230 /* Languages which require a vertical 'nudge' in
231 * text rendering, and by how much? */
232 static int lang_y_nudge[][2] = {
233 {LANG_KM, 4},
234 {-1, -1}
235 };
236
237
238 int need_own_font;
239 int need_right_to_left;
240 int need_right_to_left_word;
241 const char *lang_prefix, *short_lang_prefix;
242
243 w_langs wished_langs[255];
244
245 /* Mappings from human-readable language names (found in
246 * config files, or command-line arguments) to the precise
247 * local code to use. Some locales appear multiple times,
248 * (e.g. "de_DE.UTF-8" is represented by both "german"
249 * (the English name of the language) and "deutsch"
250 * (the German name of the language)).
251 */
252 static const language_to_locale_struct language_to_locale_array[] = {
253 {"english", "C"},
254 {"american-english", "C"},
255 {"acholi", "ach_UG.UTF-8"},
256 {"acoli", "ach_UG.UTF-8"},
257 {"akan", "ak_GH.UTF-8"},
258 {"twi-fante", "ak_GH.UTF-8"},
259 {"amharic", "am_ET.UTF-8"},
260 {"arabic", "ar_SA.UTF-8"},
261 {"aragones", "an_ES.UTF-8"},
262 {"armenian", "hy_AM.UTF-8"},
263 {"hayeren", "hy_AM.UTF-8"},
264 {"assamese", "as_IN.UTF-8"},
265 {"asturian", "ast_ES.UTF-8"},
266 {"azerbaijani", "az_AZ.UTF-8"},
267 {"bambara", "bm_ML.UTF-8"},
268 {"bengali", "bn_IN.UTF-8"},
269 {"bodo", "brx_IN.UTF-8"},
270 {"bosnian", "bs_BA.UTF-8"},
271 {"croatian", "hr_HR.UTF-8"},
272 {"hrvatski", "hr_HR.UTF-8"},
273 {"catalan", "ca_ES.UTF-8"},
274 {"catala", "ca_ES.UTF-8"},
275 {"valencian", "ca_ES.UTF-8@valencia"},
276 {"valencia", "ca_ES.UTF-8@valencia"},
277 {"kiga", "cgg_UG.UTF-8"},
278 {"chiga", "cgg_UG.UTF-8"},
279 {"belarusian", "be_BY.UTF-8"},
280 {"bielaruskaja", "be_BY.UTF-8"},
281 {"czech", "cs_CZ.UTF-8"},
282 {"cesky", "cs_CZ.UTF-8"},
283 {"danish", "da_DK.UTF-8"},
284 {"dansk", "da_DK.UTF-8"},
285 {"dogri", "doi_IN.UTF-8"},
286 {"german", "de_DE.UTF-8"},
287 {"deutsch", "de_DE.UTF-8"},
288 {"estonian", "et_EE.UTF-8"},
289 {"greek", "el_GR.UTF-8"},
290 {"gronings", "gos_NL.UTF-8"},
291 {"zudelk-veenkelonioals", "gos_NL.UTF-8"},
292 {"gujarati", "gu_IN.UTF-8"},
293 {"british-english", "en_GB.UTF-8"},
294 {"british", "en_GB.UTF-8"},
295 {"australian-english", "en_AU.UTF-8"},
296 {"canadian-english", "en_CA.UTF-8"},
297 {"southafrican-english", "en_ZA.UTF-8"},
298 {"esperanto", "eo.UTF-8"},
299 {"spanish", "es_ES.UTF-8"},
300 {"mexican", "es_MX.UTF-8"},
301 {"mexican-spanish", "es_MX.UTF-8"},
302 {"espanol-mejicano", "es_MX.UTF-8"},
303 {"espanol", "es_ES.UTF-8"},
304 {"persian", "fa_IR.UTF-8"},
305 {"fula", "ff_SN.UTF-8"},
306 {"fulah", "ff_SN.UTF-8"},
307 {"pulaar-fulfulde", "ff_SN.UTF-8"},
308 {"finnish", "fi_FI.UTF-8"},
309 {"suomi", "fi_FI.UTF-8"},
310 {"faroese", "fo_FO.UTF-8"},
311 {"french", "fr_FR.UTF-8"},
312 {"francais", "fr_FR.UTF-8"},
313 {"gaelic", "ga_IE.UTF-8"},
314 {"irish-gaelic", "ga_IE.UTF-8"},
315 {"gaidhlig", "ga_IE.UTF-8"},
316 {"scottish", "gd_GB.UTF-8"},
317 {"ghaidhlig", "gd_GB.UTF-8"},
318 {"scottish-gaelic", "gd_GB.UTF-8"},
319 {"galician", "gl_ES.UTF-8"},
320 {"galego", "gl_ES.UTF-8"},
321 {"hebrew", "he_IL.UTF-8"},
322 {"hindi", "hi_IN.UTF-8"},
323 {"hungarian", "hu_HU.UTF-8"},
324 {"magyar", "hu_HU.UTF-8"},
325 {"indonesian", "id_ID.UTF-8"},
326 {"bahasa-indonesia", "id_ID.UTF-8"},
327 {"icelandic", "is_IS.UTF-8"},
328 {"islenska", "is_IS.UTF-8"},
329 {"italian", "it_IT.UTF-8"},
330 {"italiano", "it_IT.UTF-8"},
331 {"inuktitut", "iu_CA.UTF-8"},
332 {"japanese", "ja_JP.UTF-8"},
333 {"venda", "ve_ZA.UTF-8"},
334 {"venetian", "vec.UTF-8"},
335 {"veneto", "vec.UTF-8"},
336 {"vietnamese", "vi_VN.UTF-8"},
337 {"afrikaans", "af_ZA.UTF-8"},
338 {"albanian", "sq_AL.UTF-8"},
339 {"breton", "br_FR.UTF-8"},
340 {"brezhoneg", "br_FR.UTF-8"},
341 {"bulgarian", "bg_BG.UTF-8"},
342 {"welsh", "cy_GB.UTF-8"},
343 {"cymraeg", "cy_GB.UTF-8"},
344 {"bokmal", "nb_NO.UTF-8"},
345 {"basque", "eu_ES.UTF-8"},
346 {"euskara", "eu_ES.UTF-8"},
347 {"georgian", "ka_GE"},
348 {"kabyle", "kab"},
349 {"kabylian", "kab"},
350 {"kinyarwanda", "rw_RW.UTF-8"},
351 {"klingon", "tlh.UTF-8"},
352 {"tlhIngan", "tlh.UTF-8"},
353 {"kannada", "kn_IN.UTF-8"},
354 {"korean", "ko_KR.UTF-8"},
355 {"kashmiri-devanagari", "ks_IN.UTF-8@devanagari"},
356 {"kashmiri-perso-arabic", "ks_IN.UTF-8"},
357 {"kurdish", "ku_TR.UTF-8"},
358 {"tamil", "ta_IN.UTF-8"},
359 {"telugu", "te_IN.UTF-8"},
360 {"lithuanian", "lt_LT.UTF-8"},
361 {"lietuviu", "lt_LT.UTF-8"},
362 {"latvian", "lv_LV.UTF-8"},
363 {"luganda", "lg_UG.UTF-8"},
364 {"luxembourgish", "lb_LU.UTF-8"},
365 {"letzebuergesch", "lb_LU.UTF-8"},
366 {"konkani-devanagari", "kok_IN.UTF-8"},
367 {"konkani-roman", "kok@roman"},
368 {"maithili", "mai_IN.UTF-8"},
369 {"macedonian", "mk_MK.UTF-8"},
370 {"mongolian", "mn_MN.UTF-8"},
371 {"manipuri-bengali", "mni_IN.UTF-8"},
372 {"manipuri-meitei-mayek", "mni@meiteimayek"},
373 {"marathi", "mr_IN.UTF-8"},
374 {"malay", "ms_MY.UTF-8"},
375 {"nepali", "ne_NP.UTF-8"},
376 {"dutch", "nl_NL.UTF-8"},
377 {"nederlands", "nl_NL.UTF-8"},
378 {"norwegian", "nn_NO.UTF-8"},
379 {"nynorsk", "nn_NO.UTF-8"},
380 {"norsk", "nn_NO.UTF-8"},
381 {"ndebele", "nr_ZA.UTF-8"},
382 {"northern-sotho", "nso_ZA.UTF-8"},
383 {"sesotho-sa-leboa", "nso_ZA.UTF-8"},
384 {"occitan", "oc_FR.UTF-8"},
385 {"odia", "or_IN.UTF-8"}, // Proper spelling
386 {"oriya", "or_IN.UTF-8"}, // Alternative
387 {"ojibwe", "oj_CA.UTF-8"}, // Proper spelling
388 {"ojibway", "oj_CA.UTF-8"}, // For compatibility
389 {"punjabi", "pa_IN.UTF-8"},
390 {"panjabi", "pa_IN.UTF-8"},
391 {"polish", "pl_PL.UTF-8"},
392 {"polski", "pl_PL.UTF-8"},
393 {"brazilian-portuguese", "pt_BR.UTF-8"},
394 {"portugues-brazilian", "pt_BR.UTF-8"},
395 {"brazilian", "pt_BR.UTF-8"},
396 {"portuguese", "pt_PT.UTF-8"},
397 {"portugues", "pt_PT.UTF-8"},
398 {"romanian", "ro_RO.UTF-8"},
399 {"russian", "ru_RU.UTF-8"},
400 {"russkiy", "ru_RU.UTF-8"},
401 {"sanskrit", "sa_IN.UTF-8"},
402 {"santali-devanagari", "sat_IN.UTF-8"},
403 {"santali-ol-chiki", "sat@olchiki"},
404 {"sardinian", "sc_IT"},
405 {"sardu", "sc_IT"},
406 {"serbian", "sr_RS.UTF-8"}, /* Was sr_YU, but that's not in /usr/share/i18n/SUPPORTED, and sr_RS is -bjk 2014.08.04 */
407 {"serbian-latin", "sr_RS@latin"},
408 {"shuswap", "shs_CA.UTF-8"},
409 {"secwepemctin", "shs_CA.UTF-8"},
410 {"sindhi-perso-arabic", "sd_IN.UTF-8"},
411 {"sindhi-devanagari", "sd_IN.UTF-8@devanagari"},
412 {"sinhala", "si_LK.UTF-8"},
413 {"slovak", "sk_SK.UTF-8"},
414 {"slovenian", "sl_SI.UTF-8"},
415 {"slovensko", "sl_SI.UTF-8"},
416 {"songhay", "son.UTF-8"},
417 {"sundanese", "su_ID.UTF-8"},
418 {"swedish", "sv_SE.UTF-8"},
419 {"svenska", "sv_SE.UTF-8"},
420 {"swahili", "sw_TZ.UTF-8"},
421 {"tagalog", "tl_PH.UTF-8"},
422 {"thai", "th_TH.UTF-8"},
423 {"tibetan", "bo_CN.UTF-8"}, /* Based on: http://texinfo.org/pipermail/texinfo-pretest/2005-April/000334.html */
424 {"turkish", "tr_TR.UTF-8"},
425 {"twi", "tw_GH.UTF-8"},
426 {"ukrainian", "uk_UA.UTF-8"},
427 {"urdu", "ur_IN.UTF-8"},
428 {"walloon", "wa_BE.UTF-8"},
429 {"walon", "wa_BE.UTF-8"},
430 {"wolof", "wo_SN.UTF-8"},
431 {"xhosa", "xh_ZA.UTF-8"},
432 {"chinese", "zh_CN.UTF-8"},
433 {"simplified-chinese", "zh_CN.UTF-8"},
434 {"traditional-chinese", "zh_TW.UTF-8"},
435 {"zapotec", "zam.UTF-8"},
436 {"miahuatlan-zapotec", "zam.UTF-8"},
437 {"khmer", "km_KH.UTF-8"},
438 {"malayalam", "ml_IN.UTF-8"},
439 {"zulu", "zu_ZA.UTF-8"}
440 };
441
442
443 /**
444 * Show available languages
445 *
446 * @param exitcode Exit code; also determines whether STDERR or STDOUT used.
447 * (e.g., is this output of "--lang help" (STDOUT & exit 0),
448 * or complaint of an inappropriate "--lang" argument (STDERR & exit 1)?)
449 */
show_lang_usage(int exitcode)450 static void show_lang_usage(int exitcode)
451 {
452 FILE *f = exitcode ? stderr : stdout;
453 const char *const prg = "tuxpaint";
454
455 /* FIXME: All this should REALLY be array-based!!! */
456 fprintf(f, "\n" "Usage: %s [--lang LANGUAGE]\n" "\n" "LANGUAGE may be one of:\n"
457 /* C */ " english american-english\n"
458 /* ach */ " acholi acoli\n"
459 /* af */ " afrikaans\n"
460 /* ak */ " akan twi-fante\n"
461 /* sq */ " albanian\n"
462 /* am */ " amharic\n"
463 /* ar */ " arabic\n"
464 /* an */ " aragones\n"
465 /* hy */ " armenian hayeren\n"
466 /* as */ " assamese\n"
467 /* ast */ " asturian\n"
468 /* en_AU */ " australian-english\n"
469 /* az */ " azerbaijani\n"
470 /* bm */ " bambara\n"
471 /* eu */ " basque euskara\n"
472 /* be */ " belarusian bielaruskaja\n"
473 /* bn */ " bengali\n"
474 /* brx */ " bodo\n"
475 /* nb */ " bokmal\n"
476 /* bs */ " bosnian\n"
477 /* pt_BR */ " brazilian brazilian-portuguese portugues-brazilian\n"
478 /* br */ " breton brezhoneg\n"
479 /* en_GB */ " british british-english\n"
480 /* bg_BG */ " bulgarian\n"
481 /* en_CA */ " canadian-english\n"
482 /* ca */ " catalan catala\n"
483 /* zh_CN */ " chinese simplified-chinese\n"
484 /* zh_TW */ " traditional-chinese\n"
485 /* hr */ " croatian hrvatski\n"
486 /* cs */ " czech cesky\n"
487 /* da */ " danish dansk\n"
488 /* doi */ " dogri\n"
489 /* nl */ " dutch nederlands\n"
490 /* eo */ " esperanto\n"
491 /* et */ " estonian\n"
492 /* fo */ " faroese\n"
493 /* fi */ " finnish suomi\n"
494 /* fr */ " french francais\n"
495 /* ff */ " fula fulah pulaar-fulfulde\n"
496 /* ga */ " gaelic irish-gaelic gaidhlig\n"
497 /* gl */ " galician galego\n"
498 /* ka */ " georgian\n"
499 /* de */ " german deutsch\n"
500 /* el */ " greek\n"
501 /* gos */ " gronings zudelk-veenkelonioals\n"
502 /* gu */ " gujarati\n"
503 /* he */ " hebrew\n"
504 /* hi */ " hindi\n"
505 /* hu */ " hungarian magyar\n"
506 /* is */ " icelandic islenska\n"
507 /* id */ " indonesian bahasa-indonesia\n"
508 /* iu */ " inuktitut\n"
509 /* it */ " italian italiano\n"
510 /* ja */ " japanese\n"
511 /* kab */ " kabyle kabylian\n"
512 /* kn */ " kannada\n"
513 /* ks@devanagari */ " kashmiri-devanagari\n"
514 /* ks */ " kashmiri-perso-arabic\n"
515 /* km */ " khmer\n"
516 /* cgg */ " kiga chiga\n"
517 /* rw */ " kinyarwanda\n"
518 /* tlh */ " klingon tlhIngan\n"
519 /* kok */ " konkani-devanagari\n"
520 /* kok@roman */ " konkani-roman\n"
521 /* ko */ " korean\n"
522 /* ku */ " kurdish\n"
523 /* lv */ " latvian\n"
524 /* lt */ " lithuanian lietuviu\n"
525 /* lg */ " luganda\n"
526 /* lb */ " luxembourgish letzebuergesch\n"
527 /* mai */ " maithili\n"
528 /* mk */ " macedonian\n"
529 /* ms */ " malay\n"
530 /* ml */ " malayalam\n"
531 /* mni */ " manipuri-bengali\n"
532 /* mni@meiteimayek */ " manipuri-meitei-mayek\n"
533 /* nr */ " marathi\n"
534 /* es_MX */ " mexican mexican-spanish espanol-mejicano\n"
535 /* mn */ " mongolian\n"
536 /* nr */ " ndebele\n"
537 /* ne */ " nepali\n"
538 /* nso */ " northern-sotho sesotho-sa-leboa\n"
539 /* nn */ " norwegian nynorsk norsk\n"
540 /* oc */ " occitan\n"
541 /* or */ " odia oriya\n"
542 /* oj */ " ojibwe ojibway\n"
543 /* fa */ " persian\n"
544 /* pl */ " polish polski\n"
545 /* pt */ " portuguese portugues\n"
546 /* pa */ " punjabi panjabi\n"
547 /* ro */ " romanian\n"
548 /* ru */ " russian russkiy\n"
549 /* sa */ " sanskrit\n"
550 /* sat */ " santali-devanagari\n"
551 /* sat@olchiki */ " santali-ol-chiki\n"
552 /* sc */ " sardinian sardu\n"
553 /* gd */ " scottish scottish-gaelic ghaidhlig\n"
554 /* sr */ " serbian\n"
555 /* sr@latin */ " serbian-latin\n"
556 /* shs*/ " shuswap secwepemctin\n"
557 /* sd@devanagari */ " sindhi-devanagari\n"
558 /* sd */ " sindhi-perso-arabic\n"
559 /* si */ " sinhala\n"
560 /* sk */ " slovak\n"
561 /* sl */ " slovenian slovensko\n"
562 /* en_ZA */ " southafrican-english\n"
563 /* son */ " songhay\n"
564 /* es */ " spanish espanol\n"
565 /* su */ " sundanese\n"
566 /* sw */ " swahili\n"
567 /* sv */ " swedish svenska\n"
568 /* tl */ " tagalog\n"
569 /* ta */ " tamil\n"
570 /* te */ " telugu\n"
571 /* th */ " thai\n"
572 /* twi */ " twi\n"
573 /* bo */ " tibetan\n"
574 /* tr */ " turkish\n"
575 /* uk */ " ukrainian\n"
576 /* ur */ " urdu\n"
577 /* ca@valencia */ " valencian valencia\n"
578 /* ve */ " venda\n"
579 /* vec */ " venetian veneto\n"
580 /* vi */ " vietnamese\n"
581 /* wa */ " walloon walon\n"
582 /* wo */ " wolof\n"
583 /* cy */ " welsh cymraeg\n"
584 /* xh */ " xhosa\n"
585 /* zam */ " zapotec miahuatlan-zapotec\n"
586 /* zu */ " zulu\n"
587 "\n", prg);
588 exit(exitcode);
589 }
590
591
592 /**
593 * Show available locales as a "usage" output
594 *
595 * @param f File descriptor to write to (e.g., STDOUT or STDERR)
596 * @param prg Program name (e.g., "tuxpaint" or "tuxpaint.exe")
597 */
show_locale_usage(FILE * f,const char * const prg)598 static void show_locale_usage(FILE * f, const char *const prg)
599 {
600 /* FIXME: Add accented characters to the descriptions */
601 fprintf(f,
602 "\n"
603 "Usage: %s [--locale LOCALE]\n"
604 "\n"
605 "LOCALE may be one of:\n"
606 " C (English American English)\n"
607 " ach_UG (Acholi Acoli)\n"
608 " af_ZA (Afrikaans)\n"
609 " ak_GH (Akan Twi-Fante)\n"
610 " am_ET (Amharic)\n"
611 " ar_SA (Arabic)\n"
612 " an_ES (Aragones)\n"
613 " hy_AM (Armenian)\n"
614 " as_IN (Assamese)\n"
615 " ast_ES (Asturian)\n"
616 " az_AZ (Azerbaijani)\n"
617 " bm_ML (Bambara)\n"
618 " eu_ES (Basque Euskara)\n"
619 " be_BY (Belarusian Bielaruskaja)\n"
620 " bn_IN (Bengali)\n"
621 " brx_IN (Bodo)\n"
622 " bs_BA (Bosnian)\n"
623 " nb_NO (Bokmal)\n"
624 " pt_BR (Brazilian Brazilian Portuguese Portugues Brazilian)\n"
625 " br_FR (Breton Brezhoneg)\n"
626 " en_AU (Australian English)\n"
627 " en_CA (Canadian English)\n"
628 " en_GB (British British English)\n"
629 " en_ZA (South African English)\n"
630 " bg_BG (Bulgarian)\n"
631 " ca_ES (Catalan Catala)\n"
632 " ca_ES@valencia (Valencian Valencia)n"
633 " zh_CN (Chinese-Simplified)\n"
634 " zh_TW (Chinese-Traditional)\n"
635 " cs_CZ (Czech Cesky)\n"
636 " da_DK (Danish Dansk)\n"
637 " doi_IN (Dogri)\n"
638 " nl_NL (Dutch)\n"
639 " fa_IR (Persian)\n"
640 " ff_SN (Fulah)\n"
641 " fi_FI (Finnish Suomi)\n"
642 " fo_FO (Faroese)\n"
643 " fr_FR (French Francais)\n"
644 " ga_IE (Irish Gaelic Gaidhlig)\n"
645 " gd_GB (Scottish Gaelic Ghaidhlig)\n"
646 " gl_ES (Galician Galego)\n"
647 " gos_NL (Gronings Zudelk Veenkelonioals)\n"
648 " gu_IN (Gujarati)\n"
649 " de_DE (German Deutsch)\n"
650 " eo (Esperanto)\n"
651 " et_EE (Estonian)\n"
652 " el_GR (Greek)\n"
653 " he_IL (Hebrew)\n"
654 " hi_IN (Hindi)\n"
655 " hr_HR (Croatian Hrvatski)\n"
656 " hu_HU (Hungarian Magyar)\n"
657 " cgg_UG (Kiga Chiga)\n"
658 " tlh (Klingon tlhIngan)\n"
659 " is_IS (Icelandic Islenska)\n"
660 " id_ID (Indonesian Bahasa Indonesia)\n"
661 " it_IT (Italian Italiano)\n"
662 " iu_CA (Inuktitut)\n"
663 " ja_JP (Japanese)\n"
664 " ka_GE (Georgian)\n"
665 " kn_IN (Kannada)\n"
666 " km_KH (Khmer)\n"
667 " ko_KR (Korean)\n"
668 " ks_IN@devanagari (Kashmiri (Devanagari))\n"
669 " ks_IN (Kashmiri (Perso-Arabic))\n"
670 " ku_TR (Kurdish)\n"
671 " ms_MY (Malay)\n"
672 " ml_IN (Malayalam)\n"
673 " lg_UG (Luganda)\n"
674 " lb_LU (Luxembourgish Letzebuergesch)\n"
675 " lv_LV (Latvian)\n"
676 " lt_LT (Lithuanian Lietuviu)\n"
677 " kok_IN (Konkani (Devanagari))\n"
678 " kok@roman (Konkani (Roman))\n"
679 " mai_IN (Maithili)\n"
680 " mk_MK (Macedonian)\n"
681 " mni_IN (Manipuri (Bengali))\n"
682 " mni@meiteimayek (Manipuri(Meitei Mayek))\n"
683 " mn_MN (Mongolian)\n"
684 " mr_IN (Marathi)\n"
685 " nr_ZA (Ndebele)\n"
686 " ne_NP (Nepali)\n"
687 " nso_ZA (Northern Sotho Sotho sa Leboa)\n"
688 " nn_NO (Norwegian Nynorsk Norsk)\n"
689 " oc_FR (Occitan)\n"
690 " oj_CA (Ojibway)\n"
691 " or_IN (Odia Oriya)\n"
692 " pa_IN (Punjabi Panjabi)\n"
693 " pl_PL (Polish Polski)\n"
694 " pt_PT (Portuguese Portugues)\n"
695 " ro_RO (Romanian)\n"
696 " ru_RU (Russian Russkiy)\n"
697 " rw_RW (Kinyarwanda)\n"
698 " sa_IN (Sanskrit)\n"
699 " sat_IN (Santali)\n"
700 " sat@olchiki (Santali (Ol-Chiki))\n"
701 " sc_IT (Sardinian)\n"
702 " sd_IN@devanagari (Sindhi (Devanagari))\n"
703 " sd_IN (Sindhii (Perso-Arabic))\n"
704 " shs_CA (Shuswap Secwepemctin)\n"
705 " si_LK (Sinhala)\n"
706 " sk_SK (Slovak)\n"
707 " sl_SI (Slovenian)\n"
708 " son (Songhay)\n"
709 " sq_AL (Albanian)\n"
710 " sr_YU (Serbian (cyrillic))\n"
711 " sr_RS@latin (Serbian (latin))\n"
712 " es_ES (Spanish Espanol)\n"
713 " su_ID (Sundanese)\n"
714 " es_MX (Mexican Mexican Spanish Espanol Mejicano)\n"
715 " sw_TZ (Swahili)\n"
716 " sv_SE (Swedish Svenska)\n"
717 " ta_IN (Tamil)\n"
718 " te_IN (Telugu)\n"
719 " tl_PH (Tagalog)\n"
720 " bo_CN (Tibetan)\n"
721 " th_TH (Thai)\n"
722 " tr_TR (Turkish)\n"
723 " tw_GH (Twi)\n"
724 " uk_UA (Ukrainian)\n"
725 " ur_IN (Urdu)\n"
726 " ve_ZA (Venda)\n"
727 " vec (Venetian)\n"
728 " vi_VN (Vietnamese)\n"
729 " wa_BE (Walloon)\n"
730 " wo_SN (Wolof)\n"
731 " cy_GB (Welsh Cymraeg)\n"
732 " xh_ZA (Xhosa)\n" " zam (Zapoteco-Miahuatlan)\n" " zu_ZA (Zulu)\n" "\n", prg);
733 }
734
735 /**
736 * Return the current language
737 *
738 * @return The current language (one of the LANG_xxx enums)
739 */
get_current_language(void)740 int get_current_language(void)
741 {
742 return langint;
743 }
744
745 /**
746 * Search an array of ints for a given int
747 *
748 * @param l The int to search for
749 * @param array The array of ints to search, terminated by -1
750 * @return 1 if "l" is found in "array", 0 otherwise
751 */
search_int_array(int l,int * array)752 static int search_int_array(int l, int *array)
753 {
754 int i;
755
756 for (i = 0; array[i] != -1; i++)
757 {
758 if (array[i] == l)
759 return 1;
760 }
761
762 return 0;
763 }
764
765 /**
766 * Ensures that iswprint() works beyond ASCII,
767 * even if the locale wouldn't normally support that.
768 * Tries fallback locales until one works.
769 * Emits an error message to STDERR if none work.
770 */
ctype_utf8(void)771 static void ctype_utf8(void)
772 {
773 #ifndef _WIN32
774 /* FIXME: should this iterate over more locales?
775 A zapotec speaker may have es_MX.UTF-8 available but not have en_US.UTF-8 for example */
776 const char *names[] = { "en_US.UTF8", "en_US.UTF-8", "UTF8", "UTF-8", "C.UTF-8" };
777 int i = sizeof(names) / sizeof(names[0]);
778
779 for (;;)
780 {
781 if (iswprint((wchar_t) 0xf7)) // division symbol -- which is in Latin-1 :-/
782 return;
783 if (--i < 0)
784 break;
785 setlocale(LC_CTYPE, names[i]);
786 setlocale(LC_MESSAGES, names[i]);
787 }
788 fprintf(stderr, "Failed to find a locale with iswprint() working!\n");
789 #endif
790 }
791
792 /**
793 * For a given language, return its local, or exit with a usage error.
794 *
795 * @param langstr Name of language (e.g., "german")
796 * @return Locale (e.g., "de_DE.UTF-8")
797 */
language_to_locale(const char * langstr)798 static const char *language_to_locale(const char *langstr)
799 {
800 int i = sizeof language_to_locale_array / sizeof language_to_locale_array[0];
801
802 while (i--)
803 {
804 if (!strcmp(langstr, language_to_locale_array[i].language))
805 return language_to_locale_array[i].locale;
806 }
807 if (strcmp(langstr, "help") == 0 || strcmp(langstr, "list") == 0)
808 show_lang_usage(0);
809 fprintf(stderr, "%s is an invalid language\n", langstr);
810 show_lang_usage(59);
811 return NULL;
812 }
813
814 /**
815 * Set language ("langint" global) based on a given locale;
816 * will try a few ways of checking, is case-insensitive, etc.
817 *
818 * @param loc Locale (e.g., "pt_BR.UTF-8", "pt_BR", "pt_br", etc.)
819 */
set_langint_from_locale_string(const char * restrict loc)820 static void set_langint_from_locale_string(const char *restrict loc)
821 {
822 char *baseloc = strdup(loc);
823 char *dot = strchr(baseloc, '.');
824 char *at = strchr(baseloc, '@');
825 char *cntrycode = strchr(baseloc, '_');
826 char straux[255];
827 char *ataux = NULL;
828 char *ccodeaux = NULL;
829 size_t len_baseloc;
830 int found = 0;
831 int i;
832
833 // printf("langint %i\n", langint);
834
835 if (!loc)
836 return;
837
838 /* Remove the .UTF-8 extension, then
839 try to find the full locale including country code and variant,
840 if it fails, then try to find the language code plus the variant,
841 if it still fails, try to find language and country code without the variant,
842 finally scan just the lang part.
843 as a last resource reverse the scanning
844 */
845
846 if (dot)
847 *dot = '\0';
848
849 if (cntrycode)
850 {
851 ccodeaux = strdup(cntrycode);
852 *cntrycode = '\0';
853 }
854
855 if (at)
856 {
857 ataux = strdup(at);
858 *at = '\0';
859
860 if (cntrycode)
861 {
862 /* ll_CC@variant */
863 //if (found == 0) printf("ll_CC@variant check\n");
864 snprintf(straux, 255, "%s%s%s", baseloc, ccodeaux, ataux);
865 len_baseloc = strlen(straux);
866 for (i = 0; i < NUM_LANGS && found == 0; i++)
867 {
868 // Case-insensitive (both "pt_BR" and "pt_br" work, etc.)
869 if (len_baseloc == strlen(lang_prefixes[i]) && !strncasecmp(straux, lang_prefixes[i], len_baseloc))
870 {
871 langint = i;
872 found = 1;
873 }
874 }
875 }
876
877 /* ll@variant */
878 //if (found == 0) printf("ll@variant check\n");
879 snprintf(straux, 255, "%s%s", baseloc, ataux);
880 len_baseloc = strlen(straux);
881 for (i = 0; i < NUM_LANGS && found == 0; i++)
882 {
883 // Case-insensitive (both "pt_BR" and "pt_br" work, etc.)
884 if (len_baseloc == strlen(lang_prefixes[i]) && !strncasecmp(straux, lang_prefixes[i], len_baseloc))
885 {
886 langint = i;
887 found = 1;
888 }
889 }
890 }
891
892 if (cntrycode)
893 {
894 /* ll_CC */
895 //if (found == 0) printf("ll_CC check\n");
896 snprintf(straux, 255, "%s%s", baseloc, ccodeaux);
897 len_baseloc = strlen(straux);
898
899 /* Which, if any, of the locales is it? */
900
901 for (i = 0; i < NUM_LANGS && found == 0; i++)
902 {
903 // Case-insensitive (both "pt_BR" and "pt_br" work, etc.)
904 if (len_baseloc == strlen(lang_prefixes[i]) &&
905 !strncasecmp(straux, lang_prefixes[i], strlen(lang_prefixes[i])))
906 {
907 langint = i;
908 found = 1;
909 }
910 }
911 }
912
913 /* ll */
914 // if (found == 0) printf("ll check\n");
915 len_baseloc = strlen(baseloc);
916 /* Which, if any, of the locales is it? */
917
918 for (i = 0; i < NUM_LANGS && found == 0; i++)
919 {
920 // Case-insensitive (both "pt_BR" and "pt_br" work, etc.)
921 if (len_baseloc == strlen(lang_prefixes[i]) && !strncasecmp(baseloc, lang_prefixes[i], strlen(lang_prefixes[i])))
922 {
923 langint = i;
924 found = 1;
925 }
926 }
927
928 /* Last resort, we should never arrive here, this check depends
929 on the right order in lang_prefixes[]
930 Languages sharing the same starting letters must be ordered
931 from longest to shortest, like currently are pt_BR and pt */
932 // if (found == 0)
933 // printf("Language still not found: loc= %s Trying reverse check as last resource...\n", loc);
934
935 for (i = 0; i < NUM_LANGS && found == 0; i++)
936 {
937 // Case-insensitive (both "pt_BR" and "pt_br" work, etc.)
938 if (!strncasecmp(loc, lang_prefixes[i], strlen(lang_prefixes[i])))
939 {
940 langint = i;
941 found = 1;
942 }
943 }
944 // printf("langint %i, lang_ext %s\n", langint, lang_prefixes[langint]);
945
946 free(baseloc);
947 if (ataux)
948 free(ataux);
949 if (ccodeaux)
950 free(ccodeaux);
951 }
952
953 #define HAVE_SETENV
954 #ifdef WIN32
955 #undef HAVE_SETENV
956 #endif
957
958
959 /**
960 * Set an environment variable.
961 * (Wrapper for setenv() or putenv(), depending on OS)
962 *
963 * @param name Variable to set
964 * @param value Value to set the variable to
965 */
mysetenv(const char * name,const char * value)966 static void mysetenv(const char *name, const char *value)
967 {
968 #ifdef HAVE_SETENV
969 setenv(name, value, 1);
970 #else
971 int len = strlen(name) + 1 + strlen(value) + 1;
972 char *str = malloc(len);
973
974 sprintf(str, "%s=%s", name, value);
975 putenv(str);
976 #endif
977 }
978
979
980 /**
981 * Attempt to set Tux Paint's UI language.
982 *
983 * @param loc Locale
984 * @return The Y-nudge value for font rendering in the language.
985 */
986
set_current_language(const char * restrict loc,int * ptr_num_wished_langs)987 static int set_current_language(const char *restrict loc, int * ptr_num_wished_langs)
988 {
989 int i;
990 int j = 0;
991 char *oldloc;
992 char *env_language;
993 char *env_language_lang;
994 char *env;
995 int num_wished_langs = 0;
996
997 *ptr_num_wished_langs = 0;
998
999 if (strlen(loc) > 0)
1000 {
1001 /* Got command line or config file language */
1002 DEBUG_PRINTF("Language via config: %s\n", loc);
1003 mysetenv("LANGUAGE", loc);
1004 }
1005 else
1006 {
1007 DEBUG_PRINTF("Language NOT set via config\n");
1008
1009 /* Find what language to use from env vars */
1010 env = getenv("LANGUAGE");
1011 if (env == NULL || env[0] == '\0')
1012 {
1013 env = getenv("LC_ALL");
1014 if (env != NULL && env[0] != '\0')
1015 {
1016 DEBUG_PRINTF("Language via LC_ALL: %s\n", getenv("LC_ALL"));
1017 mysetenv("LANGUAGE", getenv("LC_ALL"));
1018 }
1019 else
1020 {
1021 env = getenv("LC_MESSAGES");
1022 if (env != NULL && env[0] != '\0')
1023 {
1024 DEBUG_PRINTF("Language via LC_MESSAGES: %s\n", getenv("LC_MESSAGES"));
1025 mysetenv("LANGUAGE", getenv("LC_MESSAGES"));
1026 }
1027 else
1028 {
1029 env = getenv("LANG");
1030 if (env != NULL && env[0] != '\0')
1031 {
1032 DEBUG_PRINTF("Language via LANG: %s\n", getenv("LANG"));
1033 mysetenv("LANGUAGE", getenv("LANG"));
1034 }
1035 else
1036 {
1037 DEBUG_PRINTF("No language set!\n");
1038 }
1039 }
1040 }
1041 }
1042 else
1043 {
1044 DEBUG_PRINTF("Language was set to '%s'\n", getenv("LANGUAGE"));
1045 }
1046 }
1047
1048 oldloc = strdup(loc);
1049
1050 /* First set the locale according to the environment, then try to overwrite with loc,
1051 after that, ctype_utf8() call will test the compatibility with utf8 and try to load
1052 a different locale if the resulting one is not compatible. */
1053 DEBUG_PRINTF("Locale BEFORE is: %s\n", setlocale(LC_ALL, NULL)); //EP
1054
1055 setlocale(LC_ALL, "");
1056 setlocale(LC_ALL, loc);
1057 ctype_utf8();
1058
1059 DEBUG_PRINTF("Locale AFTER is: %s\n", setlocale(LC_ALL, NULL)); //EP
1060
1061 bindtextdomain("tuxpaint", LOCALEDIR);
1062 /* Old version of glibc does not have bind_textdomain_codeset() */
1063 #if defined(_WIN32) || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 2) || __GLIBC__ > 2 || defined(__NetBSD__) || __APPLE__
1064 bind_textdomain_codeset("tuxpaint", "UTF-8");
1065 #endif
1066 textdomain("tuxpaint");
1067
1068 #ifdef _WIN32
1069 if (!*loc)
1070 loc = _nl_locale_name(LC_MESSAGES, "");
1071 #else
1072 // NULL: Used to direct setlocale() to query the current
1073 // internationalised environment and return the name of the locale().
1074 loc = setlocale(LC_MESSAGES, NULL);
1075 #endif
1076
1077 if (strcmp(oldloc, "") != 0 && strcmp(loc, oldloc) != 0)
1078 {
1079 /* System doesn't recognize that locale! Hack, per Albert C., is to set LC_ALL to a valid UTF-8 locale, then set LANGUAGE to the locale we want to force -bjk 2010.10.05 */
1080
1081 /* Albert's comments from December 2009:
1082 gettext() won't even bother to look up messages unless it
1083 is totally satisfied that you are using one of the locales that
1084 it ships with! Make gettext() unhappy, and it'll switch to the
1085 lobotomized 7-bit Linux "C" locale just to spite you.
1086
1087 http://sources.redhat.com/cgi-bin/cvsweb.cgi/libc/localedata/SUPPORTED?content-type=text/x-cvsweb-markup&cvsroot=glibc
1088
1089 You can confuse gettext() into mostly behaving. For example, a
1090 user could pick a random UTF-8 locale and change the messages.
1091 In that case, Tux Paint thinks it's in the other locale but the
1092 messages come out right. Like so: LANGUAGE=zam LC_ALL=fr_FR.UTF-8
1093 It doesn't work to leave LC_ALL unset, set it to "zam", set it to "C",
1094 or set it to random nonsense. Yeah, Tux Paint will think it's in
1095 a French locale, but the messages will be Zapotec nonetheless.
1096
1097 Maybe it's time to give up on gettext().
1098
1099 [see also: https://sourceforge.net/mailarchive/message.php?msg_name=787b0d920912222352i5ab22834x92686283b565016b%40mail.gmail.com ]
1100 */
1101
1102 /* Unneeded here, this has yet been done as part of ctype_utf8() call before, iterating over a list of locales */
1103 // setlocale(LC_ALL, "en_US.UTF-8"); /* Is it dumb to assume "en_US" is pretty close to "C" locale? */
1104
1105 mysetenv("LANGUAGE", oldloc);
1106 set_langint_from_locale_string(oldloc);
1107 }
1108 else
1109 {
1110 #ifdef _WIN32
1111 if (getenv("LANGUAGE") == NULL)
1112 mysetenv("LANGUAGE", loc);
1113 #endif
1114
1115 if (getenv("LANGUAGE") == NULL)
1116 mysetenv("LANGUAGE", "C");
1117 }
1118 env_language = strdup(getenv("LANGUAGE"));
1119
1120 if (*env_language)
1121 {
1122 env_language_lang = strtok(env_language, ":");
1123 while (env_language_lang != NULL)
1124 {
1125 num_wished_langs++;
1126 set_langint_from_locale_string(env_language_lang);
1127 wished_langs[j].langint = langint;
1128 wished_langs[j].lang_prefix = lang_prefixes[langint];
1129 wished_langs[j].need_own_font = search_int_array(langint, lang_use_own_font);
1130 wished_langs[j].need_right_to_left = search_int_array(langint, lang_use_right_to_left);
1131 wished_langs[j].need_right_to_left_word = search_int_array(langint, lang_use_right_to_left_word);
1132 for (i = 0; lang_y_nudge[i][0] != -1; i++)
1133 {
1134 // printf("lang_y_nudge[%d][0] = %d\n", i, lang_y_nudge[i][0]);
1135 if (lang_y_nudge[i][0] == langint)
1136 {
1137 wished_langs[j].lang_y_nudge = lang_y_nudge[i][1];
1138 break;
1139 }
1140 }
1141
1142
1143 j++;
1144 env_language_lang = strtok(NULL, ":");
1145 }
1146 if (*env_language)
1147 free(env_language);
1148 }
1149 // set_langint_from_locale_string(loc);
1150
1151
1152 lang_prefix = lang_prefixes[wished_langs[0].langint];
1153
1154 short_lang_prefix = strdup(lang_prefix);
1155 /* When in doubt, cut off country code */
1156 if (strchr(short_lang_prefix, '_'))
1157 *strchr(short_lang_prefix, '_') = '\0';
1158
1159 need_own_font = wished_langs[0].need_own_font;
1160 need_right_to_left = wished_langs[0].need_right_to_left;
1161 need_right_to_left_word = wished_langs[0].need_right_to_left_word;
1162
1163 #ifdef DEBUG
1164 fprintf(stderr, "DEBUG: Language is %s (%d) %s/%s\n",
1165 lang_prefix, langint, need_right_to_left ? "(RTL)" : "", need_right_to_left_word ? "(RTL words)" : "");
1166 fflush(stderr);
1167 #endif
1168
1169 free(oldloc);
1170
1171 DEBUG_PRINTF("lang_prefixes[%d] is \"%s\"\n", get_current_language(), lang_prefixes[get_current_language()]);
1172
1173 *ptr_num_wished_langs = num_wished_langs;
1174
1175 return wished_langs[0].lang_y_nudge;
1176 }
1177
1178
1179 /**
1180 * Given a locale (e.g., "de_DE.UTF-8" or a language name (e.g., "german"),
1181 * attempt to set Tux Paint's UI language. Show help, and exit,
1182 * if asked (either 'locale' or 'lang' are "help"), or if the
1183 * given input is not recognized.
1184 *
1185 * @param char * lang Language name (or NULL)
1186 * @param char * locale Locale (or NULL)
1187 * @param int * a place to return the number of languages we want to use, when scanning stamp descriptions
1188 * @return Y-nudge
1189 */
setup_i18n(const char * restrict lang,const char * restrict locale,int * num_wished_langs)1190 int setup_i18n(const char *restrict lang, const char *restrict locale, int * num_wished_langs)
1191 {
1192 DEBUG_PRINTF("lang %p, locale %p\n", lang, locale);
1193 DEBUG_PRINTF("lang \"%s\", locale \"%s\"\n", lang, locale);
1194
1195 if (locale)
1196 {
1197 if (!strcmp(locale, "help"))
1198 {
1199 show_locale_usage(stdout, "tuxpaint");
1200 exit(0);
1201 }
1202 }
1203 else
1204 locale = "";
1205
1206 if (lang)
1207 locale = language_to_locale(lang);
1208 return set_current_language(locale, num_wished_langs);
1209 }
1210
1211 #ifdef NO_SDLPANGO
1212 /**
1213 * FIXME
1214 */
smash_i18n(void)1215 int smash_i18n(void)
1216 {
1217 int tmp;
1218 return set_current_language("C", &tmp);
1219 }
1220 #endif
1221