tuxpaint-pencil-sharpener/src/i18n.c
Bill Kendrick cef773a694 Now REQUIRING SDL2_Pango; no NO_SDLPANGO option
Motivation - Without SDL2_Pango, languages like Japanese,
Arabic, and Thai do not render properly.  Currently, Debian
(and hence Ubuntu) do not have SDL2_Pango, so Tux Paint 0.9.28
is adversely affected.  Fedora DOES have SDL2_Pango, so works well.
This also allows us to delete a lot of ancient cruft code.

Closes https://sourceforge.net/p/tuxpaint/bugs/268/
h/t Pere

(INSTALL docs to be updated momentarily)
2023-04-30 16:10:29 -07:00

1331 lines
37 KiB
C

/*
i18n.c
For Tux Paint
Language-related functions
Copyright (c) 2002-2023 by Bill Kendrick and others
bill@newbreedsoftware.com
https://tuxpaint.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
(See COPYING.txt)
June 14, 2002 - April 30, 2023
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libintl.h>
#include <locale.h>
#include "platform.h"
#include "i18n.h"
#include "debug.h"
#ifdef WIN32
#include <sys/types.h>
#endif
#ifdef BDIST_WIN32
#include <unistd.h>
#endif
#ifdef __BEOS__
#include <wchar.h>
#else
#include <wchar.h>
#include <wctype.h>
#endif
#ifdef __ANDROID__
#include "SDL2/SDL.h"
#include "jni.h"
// since setlocale on the Android is not supported well,
// setlocale cannot get current default locale of the device.
// Here, JNI and Java Locale class can get the default locale
// if user has not set locale and lang in the config file yet.
static char *android_locale()
{
static char android_locale_buf[32];
JNIEnv *mEnv = Android_JNI_GetEnv();
jclass mLocaleClass = (*mEnv)->FindClass(mEnv, "java/util/Locale");
jmethodID mGetDefaultMethod = (*mEnv)->GetStaticMethodID(mEnv, mLocaleClass, "getDefault",
"()Ljava/util/Locale;");
jobject mLocaleObject = (*mEnv)->CallStaticObjectMethod(mEnv, mLocaleClass, mGetDefaultMethod);
jmethodID mToStringMethod = (*mEnv)->GetMethodID(mEnv, mLocaleClass, "toString",
"()Ljava/lang/String;");
jstring mLocaleString = (*mEnv)->CallObjectMethod(mEnv, mLocaleObject, mToStringMethod);
const char *locale = (*mEnv)->GetStringUTFChars(mEnv, mLocaleString, 0);
strcpy(android_locale_buf, locale);
(*mEnv)->ReleaseStringUTFChars(mEnv, mLocaleString, locale);
printf("android locale %s\n", android_locale_buf);
return android_locale_buf;
}
#endif
#if defined(__MACOS__)
#include "macos.h"
#elif defined(__IOS__)
#include "ios.h"
#endif
/* Globals: */
static int langint = LANG_EN;
/* Strings representing each language's ISO 639 (-1 or -2) codes.
* Should map to the 'enum' of possible languages ("LANG_xxx")
* found in "i18n.h" (where "NUM_LANGS" is found, as the final
* entry in the 'enum' list). */
const char *lang_prefixes[NUM_LANGS] = {
"ach",
"af",
"ak",
"am",
"an",
"ar",
"as",
"ast",
"az",
"be",
"bg",
"bm",
"bn",
"bo",
"br",
"brx",
"bs",
"ca@valencia",
"ca",
"cgg",
"cs",
"cy",
"da",
"de",
"doi",
"el",
"en",
"en_AU",
"en_CA",
"en_GB",
"en_ZA",
"eo",
"es_MX",
"es",
"et",
"eu",
"fa",
"ff",
"fi",
"fo",
"fr",
"ga",
"gd",
"gl",
"gos",
"gu",
"he",
"hi",
"hr",
"hu",
"hy",
"tlh",
"id",
"is",
"it",
"iu",
"ja",
"ka",
"kab",
"kn",
"km",
"kok@roman",
"kok",
"ko",
"ks@devanagari",
"ks",
"ku",
"lb",
"lg",
"lt",
"lv",
"mai",
"ml",
"mk",
"mn",
"mni",
"mni@meiteimayek",
"mr",
"ms",
"nb",
"ne",
"nl",
"nn",
"nr",
"nso",
"oc",
"oj",
"or",
"pa",
"pl",
"pt_BR",
"pt",
"ro",
"ru",
"rw",
"sat@olchiki",
"sat",
"sa",
"sc",
"sd",
"sd@devanagari",
"shs",
"si",
"sk",
"sl",
"son",
"sq",
"sr@latin",
"sr",
"su",
"sv",
"sw",
"ta",
"te",
"th",
"tl",
"tr",
"tw",
"uk",
"ur",
"vec",
"ve",
"vi",
"wa",
"wo",
"xh",
"zam",
"zh_CN",
"zh_TW",
"zu",
};
/* Languages which don't use the default font */
static int lang_use_own_font[] = {
LANG_AR,
LANG_BO,
LANG_GU,
LANG_HI,
LANG_JA,
LANG_KA,
LANG_KO,
LANG_ML,
LANG_TA,
LANG_TE,
LANG_TH,
LANG_ZH_CN,
LANG_ZH_TW,
-1
};
/* Languages which are written right-to-left */
static int lang_use_right_to_left[] = {
LANG_AR,
LANG_FA,
LANG_HE,
LANG_KS,
LANG_SD,
LANG_UR,
-1
};
/* FIXME: Remove! (We now require SDL_Pango all the time, so this is unnecessary -bjk 2023.04.30) */
static int lang_use_right_to_left_word[] = {
-1
};
/* Languages which require a vertical 'nudge' in
* text rendering, and by how much? */
static int lang_y_nudge[][2] = {
{LANG_KM, 4},
{-1, -1}
};
int need_own_font;
int need_right_to_left;
int need_right_to_left_word;
const char *lang_prefix, *short_lang_prefix;
w_langs wished_langs[255];
/* Mappings from human-readable language names (found in
* config files, or command-line arguments) to the precise
* local code to use. Some locales appear multiple times,
* (e.g. "de_DE.UTF-8" is represented by both "german"
* (the English name of the language) and "deutsch"
* (the German name of the language)).
*/
static const language_to_locale_struct language_to_locale_array[] = {
{"english", "C"},
{"american-english", "C"},
{"acholi", "ach_UG.UTF-8"},
{"acoli", "ach_UG.UTF-8"},
{"akan", "ak_GH.UTF-8"},
{"twi-fante", "ak_GH.UTF-8"},
{"amharic", "am_ET.UTF-8"},
{"arabic", "ar_SA.UTF-8"},
{"aragones", "an_ES.UTF-8"},
{"armenian", "hy_AM.UTF-8"},
{"hayeren", "hy_AM.UTF-8"},
{"assamese", "as_IN.UTF-8"},
{"asturian", "ast_ES.UTF-8"},
{"azerbaijani", "az_AZ.UTF-8"},
{"bambara", "bm_ML.UTF-8"},
{"bengali", "bn_IN.UTF-8"},
{"bodo", "brx_IN.UTF-8"},
{"bosnian", "bs_BA.UTF-8"},
{"croatian", "hr_HR.UTF-8"},
{"hrvatski", "hr_HR.UTF-8"},
{"catalan", "ca_ES.UTF-8"},
{"catala", "ca_ES.UTF-8"},
{"valencian", "ca_ES.UTF-8@valencia"},
{"valencia", "ca_ES.UTF-8@valencia"},
{"kiga", "cgg_UG.UTF-8"},
{"chiga", "cgg_UG.UTF-8"},
{"belarusian", "be_BY.UTF-8"},
{"bielaruskaja", "be_BY.UTF-8"},
{"czech", "cs_CZ.UTF-8"},
{"cesky", "cs_CZ.UTF-8"},
{"danish", "da_DK.UTF-8"},
{"dansk", "da_DK.UTF-8"},
{"dogri", "doi_IN.UTF-8"},
{"german", "de_DE.UTF-8"},
{"deutsch", "de_DE.UTF-8"},
{"estonian", "et_EE.UTF-8"},
{"greek", "el_GR.UTF-8"},
{"gronings", "gos_NL.UTF-8"},
{"zudelk-veenkelonioals", "gos_NL.UTF-8"},
{"gujarati", "gu_IN.UTF-8"},
{"british-english", "en_GB.UTF-8"},
{"british", "en_GB.UTF-8"},
{"australian-english", "en_AU.UTF-8"},
{"canadian-english", "en_CA.UTF-8"},
{"southafrican-english", "en_ZA.UTF-8"},
{"esperanto", "eo.UTF-8"},
{"spanish", "es_ES.UTF-8"},
{"mexican", "es_MX.UTF-8"},
{"mexican-spanish", "es_MX.UTF-8"},
{"espanol-mejicano", "es_MX.UTF-8"},
{"espanol", "es_ES.UTF-8"},
{"persian", "fa_IR.UTF-8"},
{"fula", "ff_SN.UTF-8"},
{"fulah", "ff_SN.UTF-8"},
{"pulaar-fulfulde", "ff_SN.UTF-8"},
{"finnish", "fi_FI.UTF-8"},
{"suomi", "fi_FI.UTF-8"},
{"faroese", "fo_FO.UTF-8"},
{"french", "fr_FR.UTF-8"},
{"francais", "fr_FR.UTF-8"},
{"gaelic", "ga_IE.UTF-8"},
{"irish-gaelic", "ga_IE.UTF-8"},
{"gaidhlig", "ga_IE.UTF-8"},
{"scottish", "gd_GB.UTF-8"},
{"ghaidhlig", "gd_GB.UTF-8"},
{"scottish-gaelic", "gd_GB.UTF-8"},
{"galician", "gl_ES.UTF-8"},
{"galego", "gl_ES.UTF-8"},
{"hebrew", "he_IL.UTF-8"},
{"hindi", "hi_IN.UTF-8"},
{"hungarian", "hu_HU.UTF-8"},
{"magyar", "hu_HU.UTF-8"},
{"indonesian", "id_ID.UTF-8"},
{"bahasa-indonesia", "id_ID.UTF-8"},
{"icelandic", "is_IS.UTF-8"},
{"islenska", "is_IS.UTF-8"},
{"italian", "it_IT.UTF-8"},
{"italiano", "it_IT.UTF-8"},
{"inuktitut", "iu_CA.UTF-8"},
{"japanese", "ja_JP.UTF-8"},
{"venda", "ve_ZA.UTF-8"},
{"venetian", "vec.UTF-8"},
{"veneto", "vec.UTF-8"},
{"vietnamese", "vi_VN.UTF-8"},
{"afrikaans", "af_ZA.UTF-8"},
{"albanian", "sq_AL.UTF-8"},
{"breton", "br_FR.UTF-8"},
{"brezhoneg", "br_FR.UTF-8"},
{"bulgarian", "bg_BG.UTF-8"},
{"welsh", "cy_GB.UTF-8"},
{"cymraeg", "cy_GB.UTF-8"},
{"bokmal", "nb_NO.UTF-8"},
{"basque", "eu_ES.UTF-8"},
{"euskara", "eu_ES.UTF-8"},
{"georgian", "ka_GE"},
{"kabyle", "kab"},
{"kabylian", "kab"},
{"kinyarwanda", "rw_RW.UTF-8"},
{"klingon", "tlh.UTF-8"},
{"tlhIngan", "tlh.UTF-8"},
{"kannada", "kn_IN.UTF-8"},
{"korean", "ko_KR.UTF-8"},
{"kashmiri-devanagari", "ks_IN.UTF-8@devanagari"},
{"kashmiri-perso-arabic", "ks_IN.UTF-8"},
{"kurdish", "ku_TR.UTF-8"},
{"tamil", "ta_IN.UTF-8"},
{"telugu", "te_IN.UTF-8"},
{"lithuanian", "lt_LT.UTF-8"},
{"lietuviu", "lt_LT.UTF-8"},
{"latvian", "lv_LV.UTF-8"},
{"luganda", "lg_UG.UTF-8"},
{"luxembourgish", "lb_LU.UTF-8"},
{"letzebuergesch", "lb_LU.UTF-8"},
{"konkani-devanagari", "kok_IN.UTF-8"},
{"konkani-roman", "kok@roman"},
{"maithili", "mai_IN.UTF-8"},
{"macedonian", "mk_MK.UTF-8"},
{"mongolian", "mn_MN.UTF-8"},
{"manipuri-bengali", "mni_IN.UTF-8"},
{"manipuri-meitei-mayek", "mni@meiteimayek"},
{"marathi", "mr_IN.UTF-8"},
{"malay", "ms_MY.UTF-8"},
{"nepali", "ne_NP.UTF-8"},
{"dutch", "nl_NL.UTF-8"},
{"nederlands", "nl_NL.UTF-8"},
{"norwegian", "nn_NO.UTF-8"},
{"nynorsk", "nn_NO.UTF-8"},
{"norsk", "nn_NO.UTF-8"},
{"ndebele", "nr_ZA.UTF-8"},
{"northern-sotho", "nso_ZA.UTF-8"},
{"sesotho-sa-leboa", "nso_ZA.UTF-8"},
{"occitan", "oc_FR.UTF-8"},
{"odia", "or_IN.UTF-8"}, // Proper spelling
{"oriya", "or_IN.UTF-8"}, // Alternative
{"ojibwe", "oj_CA.UTF-8"}, // Proper spelling
{"ojibway", "oj_CA.UTF-8"}, // For compatibility
{"punjabi", "pa_IN.UTF-8"},
{"panjabi", "pa_IN.UTF-8"},
{"polish", "pl_PL.UTF-8"},
{"polski", "pl_PL.UTF-8"},
{"brazilian-portuguese", "pt_BR.UTF-8"},
{"portugues-brazilian", "pt_BR.UTF-8"},
{"brazilian", "pt_BR.UTF-8"},
{"portuguese", "pt_PT.UTF-8"},
{"portugues", "pt_PT.UTF-8"},
{"romanian", "ro_RO.UTF-8"},
{"russian", "ru_RU.UTF-8"},
{"russkiy", "ru_RU.UTF-8"},
{"sanskrit", "sa_IN.UTF-8"},
{"santali-devanagari", "sat_IN.UTF-8"},
{"santali-ol-chiki", "sat@olchiki"},
{"sardinian", "sc_IT"},
{"sardu", "sc_IT"},
{"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 */
{"serbian-latin", "sr_RS@latin"},
{"shuswap", "shs_CA.UTF-8"},
{"secwepemctin", "shs_CA.UTF-8"},
{"sindhi-perso-arabic", "sd_IN.UTF-8"},
{"sindhi-devanagari", "sd_IN.UTF-8@devanagari"},
{"sinhala", "si_LK.UTF-8"},
{"slovak", "sk_SK.UTF-8"},
{"slovenian", "sl_SI.UTF-8"},
{"slovensko", "sl_SI.UTF-8"},
{"songhay", "son.UTF-8"},
{"sundanese", "su_ID.UTF-8"},
{"swedish", "sv_SE.UTF-8"},
{"svenska", "sv_SE.UTF-8"},
{"swahili", "sw_TZ.UTF-8"},
{"tagalog", "tl_PH.UTF-8"},
{"thai", "th_TH.UTF-8"},
{"tibetan", "bo_CN.UTF-8"}, /* Based on: http://texinfo.org/pipermail/texinfo-pretest/2005-April/000334.html */
{"turkish", "tr_TR.UTF-8"},
{"twi", "tw_GH.UTF-8"},
{"ukrainian", "uk_UA.UTF-8"},
{"urdu", "ur_IN.UTF-8"},
{"walloon", "wa_BE.UTF-8"},
{"walon", "wa_BE.UTF-8"},
{"wolof", "wo_SN.UTF-8"},
{"xhosa", "xh_ZA.UTF-8"},
{"chinese", "zh_CN.UTF-8"},
{"simplified-chinese", "zh_CN.UTF-8"},
{"traditional-chinese", "zh_TW.UTF-8"},
{"zapotec", "zam.UTF-8"},
{"miahuatlan-zapotec", "zam.UTF-8"},
{"khmer", "km_KH.UTF-8"},
{"malayalam", "ml_IN.UTF-8"},
{"zulu", "zu_ZA.UTF-8"}
};
/**
* Show available languages
*
* @param exitcode Exit code; also determines whether STDERR or STDOUT used.
* (e.g., is this output of "--lang help" (STDOUT & exit 0),
* or complaint of an inappropriate "--lang" argument (STDERR & exit 1)?)
*/
static void show_lang_usage(int exitcode)
{
FILE *f = exitcode ? stderr : stdout;
const char *const prg = "tuxpaint";
/* FIXME: All this should REALLY be array-based!!! */
fprintf(f, "\n" "Usage: %s [--lang LANGUAGE]\n" "\n" "LANGUAGE may be one of:\n"
/* C */ " english american-english\n"
/* ach */ " acholi acoli\n"
/* af */ " afrikaans\n"
/* ak */ " akan twi-fante\n"
/* sq */ " albanian\n"
/* am */ " amharic\n"
/* ar */ " arabic\n"
/* an */ " aragones\n"
/* hy */ " armenian hayeren\n"
/* as */ " assamese\n"
/* ast */ " asturian\n"
/* en_AU */ " australian-english\n"
/* az */ " azerbaijani\n"
/* bm */ " bambara\n"
/* eu */ " basque euskara\n"
/* be */ " belarusian bielaruskaja\n"
/* bn */ " bengali\n"
/* brx */ " bodo\n"
/* nb */ " bokmal\n"
/* bs */ " bosnian\n"
/* pt_BR */
" brazilian brazilian-portuguese portugues-brazilian\n"
/* br */ " breton brezhoneg\n"
/* en_GB */ " british british-english\n"
/* bg_BG */ " bulgarian\n"
/* en_CA */ " canadian-english\n"
/* ca */ " catalan catala\n"
/* zh_CN */ " chinese simplified-chinese\n"
/* zh_TW */ " traditional-chinese\n"
/* hr */ " croatian hrvatski\n"
/* cs */ " czech cesky\n"
/* da */ " danish dansk\n"
/* doi */ " dogri\n"
/* nl */ " dutch nederlands\n"
/* eo */ " esperanto\n"
/* et */ " estonian\n"
/* fo */ " faroese\n"
/* fi */ " finnish suomi\n"
/* fr */ " french francais\n"
/* ff */ " fula fulah pulaar-fulfulde\n"
/* ga */ " gaelic irish-gaelic gaidhlig\n"
/* gl */ " galician galego\n"
/* ka */ " georgian\n"
/* de */ " german deutsch\n"
/* el */ " greek\n"
/* gos */ " gronings zudelk-veenkelonioals\n"
/* gu */ " gujarati\n"
/* he */ " hebrew\n"
/* hi */ " hindi\n"
/* hu */ " hungarian magyar\n"
/* is */ " icelandic islenska\n"
/* id */ " indonesian bahasa-indonesia\n"
/* iu */ " inuktitut\n"
/* it */ " italian italiano\n"
/* ja */ " japanese\n"
/* kab */ " kabyle kabylian\n"
/* kn */ " kannada\n"
/* ks@devanagari */ " kashmiri-devanagari\n"
/* ks */ " kashmiri-perso-arabic\n"
/* km */ " khmer\n"
/* cgg */ " kiga chiga\n"
/* rw */ " kinyarwanda\n"
/* tlh */ " klingon tlhIngan\n"
/* kok */ " konkani-devanagari\n"
/* kok@roman */ " konkani-roman\n"
/* ko */ " korean\n"
/* ku */ " kurdish\n"
/* lv */ " latvian\n"
/* lt */ " lithuanian lietuviu\n"
/* lg */ " luganda\n"
/* lb */ " luxembourgish letzebuergesch\n"
/* mai */ " maithili\n"
/* mk */ " macedonian\n"
/* ms */ " malay\n"
/* ml */ " malayalam\n"
/* mni */ " manipuri-bengali\n"
/* mni@meiteimayek */ " manipuri-meitei-mayek\n"
/* nr */ " marathi\n"
/* es_MX */
" mexican mexican-spanish espanol-mejicano\n"
/* mn */ " mongolian\n"
/* nr */ " ndebele\n"
/* ne */ " nepali\n"
/* nso */ " northern-sotho sesotho-sa-leboa\n"
/* nn */ " norwegian nynorsk norsk\n"
/* oc */ " occitan\n"
/* or */ " odia oriya\n"
/* oj */ " ojibwe ojibway\n"
/* fa */ " persian\n"
/* pl */ " polish polski\n"
/* pt */ " portuguese portugues\n"
/* pa */ " punjabi panjabi\n"
/* ro */ " romanian\n"
/* ru */ " russian russkiy\n"
/* sa */ " sanskrit\n"
/* sat */ " santali-devanagari\n"
/* sat@olchiki */ " santali-ol-chiki\n"
/* sc */ " sardinian sardu\n"
/* gd */ " scottish scottish-gaelic ghaidhlig\n"
/* sr */ " serbian\n"
/* sr@latin */ " serbian-latin\n"
/* shs*/ " shuswap secwepemctin\n"
/* sd@devanagari */ " sindhi-devanagari\n"
/* sd */ " sindhi-perso-arabic\n"
/* si */ " sinhala\n"
/* sk */ " slovak\n"
/* sl */ " slovenian slovensko\n"
/* en_ZA */ " southafrican-english\n"
/* son */ " songhay\n"
/* es */ " spanish espanol\n"
/* su */ " sundanese\n"
/* sw */ " swahili\n"
/* sv */ " swedish svenska\n"
/* tl */ " tagalog\n"
/* ta */ " tamil\n"
/* te */ " telugu\n"
/* th */ " thai\n"
/* twi */ " twi\n"
/* bo */ " tibetan\n"
/* tr */ " turkish\n"
/* uk */ " ukrainian\n"
/* ur */ " urdu\n"
/* ca@valencia */ " valencian valencia\n"
/* ve */ " venda\n"
/* vec */ " venetian veneto\n"
/* vi */ " vietnamese\n"
/* wa */ " walloon walon\n"
/* wo */ " wolof\n"
/* cy */ " welsh cymraeg\n"
/* xh */ " xhosa\n"
/* zam */ " zapotec miahuatlan-zapotec\n"
/* zu */ " zulu\n"
"\n", prg);
exit(exitcode);
}
/**
* Show available locales as a "usage" output
*
* @param f File descriptor to write to (e.g., STDOUT or STDERR)
* @param prg Program name (e.g., "tuxpaint" or "tuxpaint.exe")
*/
static void show_locale_usage(FILE * f, const char *const prg)
{
/* FIXME: Add accented characters to the descriptions */
fprintf(f,
"\n"
"Usage: %s [--locale LOCALE]\n"
"\n"
"LOCALE may be one of:\n"
" C (English American English)\n"
" ach_UG (Acholi Acoli)\n"
" af_ZA (Afrikaans)\n"
" ak_GH (Akan Twi-Fante)\n"
" am_ET (Amharic)\n"
" ar_SA (Arabic)\n"
" an_ES (Aragones)\n"
" hy_AM (Armenian)\n"
" as_IN (Assamese)\n"
" ast_ES (Asturian)\n"
" az_AZ (Azerbaijani)\n"
" bm_ML (Bambara)\n"
" eu_ES (Basque Euskara)\n"
" be_BY (Belarusian Bielaruskaja)\n"
" bn_IN (Bengali)\n"
" brx_IN (Bodo)\n"
" bs_BA (Bosnian)\n"
" nb_NO (Bokmal)\n"
" pt_BR (Brazilian Brazilian Portuguese Portugues Brazilian)\n"
" br_FR (Breton Brezhoneg)\n"
" en_AU (Australian English)\n"
" en_CA (Canadian English)\n"
" en_GB (British British English)\n"
" en_ZA (South African English)\n"
" bg_BG (Bulgarian)\n"
" ca_ES (Catalan Catala)\n"
" ca_ES@valencia (Valencian Valencia)n"
" zh_CN (Chinese-Simplified)\n"
" zh_TW (Chinese-Traditional)\n"
" cs_CZ (Czech Cesky)\n"
" da_DK (Danish Dansk)\n"
" doi_IN (Dogri)\n"
" nl_NL (Dutch)\n"
" fa_IR (Persian)\n"
" ff_SN (Fulah)\n"
" fi_FI (Finnish Suomi)\n"
" fo_FO (Faroese)\n"
" fr_FR (French Francais)\n"
" ga_IE (Irish Gaelic Gaidhlig)\n"
" gd_GB (Scottish Gaelic Ghaidhlig)\n"
" gl_ES (Galician Galego)\n"
" gos_NL (Gronings Zudelk Veenkelonioals)\n"
" gu_IN (Gujarati)\n"
" de_DE (German Deutsch)\n"
" eo (Esperanto)\n"
" et_EE (Estonian)\n"
" el_GR (Greek)\n"
" he_IL (Hebrew)\n"
" hi_IN (Hindi)\n"
" hr_HR (Croatian Hrvatski)\n"
" hu_HU (Hungarian Magyar)\n"
" cgg_UG (Kiga Chiga)\n"
" tlh (Klingon tlhIngan)\n"
" is_IS (Icelandic Islenska)\n"
" id_ID (Indonesian Bahasa Indonesia)\n"
" it_IT (Italian Italiano)\n"
" iu_CA (Inuktitut)\n"
" ja_JP (Japanese)\n"
" ka_GE (Georgian)\n"
" kn_IN (Kannada)\n"
" km_KH (Khmer)\n"
" ko_KR (Korean)\n"
" ks_IN@devanagari (Kashmiri (Devanagari))\n"
" ks_IN (Kashmiri (Perso-Arabic))\n"
" ku_TR (Kurdish)\n"
" ms_MY (Malay)\n"
" ml_IN (Malayalam)\n"
" lg_UG (Luganda)\n"
" lb_LU (Luxembourgish Letzebuergesch)\n"
" lv_LV (Latvian)\n"
" lt_LT (Lithuanian Lietuviu)\n"
" kok_IN (Konkani (Devanagari))\n"
" kok@roman (Konkani (Roman))\n"
" mai_IN (Maithili)\n"
" mk_MK (Macedonian)\n"
" mni_IN (Manipuri (Bengali))\n"
" mni@meiteimayek (Manipuri(Meitei Mayek))\n"
" mn_MN (Mongolian)\n"
" mr_IN (Marathi)\n"
" nr_ZA (Ndebele)\n"
" ne_NP (Nepali)\n"
" nso_ZA (Northern Sotho Sotho sa Leboa)\n"
" nn_NO (Norwegian Nynorsk Norsk)\n"
" oc_FR (Occitan)\n"
" oj_CA (Ojibway)\n"
" or_IN (Odia Oriya)\n"
" pa_IN (Punjabi Panjabi)\n"
" pl_PL (Polish Polski)\n"
" pt_PT (Portuguese Portugues)\n"
" ro_RO (Romanian)\n"
" ru_RU (Russian Russkiy)\n"
" rw_RW (Kinyarwanda)\n"
" sa_IN (Sanskrit)\n"
" sat_IN (Santali)\n"
" sat@olchiki (Santali (Ol-Chiki))\n"
" sc_IT (Sardinian)\n"
" sd_IN@devanagari (Sindhi (Devanagari))\n"
" sd_IN (Sindhii (Perso-Arabic))\n"
" shs_CA (Shuswap Secwepemctin)\n"
" si_LK (Sinhala)\n"
" sk_SK (Slovak)\n"
" sl_SI (Slovenian)\n"
" son (Songhay)\n"
" sq_AL (Albanian)\n"
" sr_YU (Serbian (cyrillic))\n"
" sr_RS@latin (Serbian (latin))\n"
" es_ES (Spanish Espanol)\n"
" su_ID (Sundanese)\n"
" es_MX (Mexican Mexican Spanish Espanol Mejicano)\n"
" sw_TZ (Swahili)\n"
" sv_SE (Swedish Svenska)\n"
" ta_IN (Tamil)\n"
" te_IN (Telugu)\n"
" tl_PH (Tagalog)\n"
" bo_CN (Tibetan)\n"
" th_TH (Thai)\n"
" tr_TR (Turkish)\n"
" tw_GH (Twi)\n"
" uk_UA (Ukrainian)\n"
" ur_IN (Urdu)\n"
" ve_ZA (Venda)\n"
" vec (Venetian)\n"
" vi_VN (Vietnamese)\n"
" wa_BE (Walloon)\n"
" wo_SN (Wolof)\n"
" cy_GB (Welsh Cymraeg)\n"
" xh_ZA (Xhosa)\n" " zam (Zapoteco-Miahuatlan)\n" " zu_ZA (Zulu)\n" "\n", prg);
}
/**
* Return the current language
*
* @return The current language (one of the LANG_xxx enums)
*/
int get_current_language(void)
{
return langint;
}
/**
* Search an array of ints for a given int
*
* @param l The int to search for
* @param array The array of ints to search, terminated by -1
* @return 1 if "l" is found in "array", 0 otherwise
*/
static int search_int_array(int l, int *array)
{
int i;
for (i = 0; array[i] != -1; i++)
{
if (array[i] == l)
return 1;
}
return 0;
}
/**
* Ensures that iswprint() works beyond ASCII,
* even if the locale wouldn't normally support that.
* Tries fallback locales until one works.
* Emits an error message to STDERR if none work.
*/
static void ctype_utf8(void)
{
#ifndef _WIN32
/* FIXME: should this iterate over more locales?
A zapotec speaker may have es_MX.UTF-8 available but not have en_US.UTF-8 for example */
const char *names[] = { "en_US.UTF8", "en_US.UTF-8", "UTF8", "UTF-8", "C.UTF-8" };
int i = sizeof(names) / sizeof(names[0]);
for (;;)
{
if (iswprint((wchar_t)0xf7)) // division symbol -- which is in Latin-1 :-/
return;
if (--i < 0)
break;
setlocale(LC_CTYPE, names[i]);
setlocale(LC_MESSAGES, names[i]);
}
fprintf(stderr, "Failed to find a locale with iswprint() working!\n");
#endif
}
/**
* For a given language, return its locale, or exit with a usage error.
*
* @param langstr Name of language (e.g., "german")
* @return Locale (e.g., "de_DE.UTF-8")
*/
static const char *language_to_locale(const char *langstr)
{
int i = sizeof language_to_locale_array / sizeof language_to_locale_array[0];
while (i--)
{
if (!strcmp(langstr, language_to_locale_array[i].language))
return language_to_locale_array[i].locale;
}
if (strcmp(langstr, "help") == 0 || strcmp(langstr, "list") == 0)
show_lang_usage(0);
fprintf(stderr, "%s is an invalid language\n", langstr);
show_lang_usage(59);
return NULL;
}
#if defined(__APPLE__)
/**
* For a given locale, return the known locale that matches it closest, or exit
* with a usage error.
*
* @param inlocale Name of some locale (e.g., "ko_US")
* @return Known locale. (e.g., "ko_KR.UTF-8")
*/
static const char *locale_to_closest_locale(const char *inlocale)
{
const int numlocale = sizeof(language_to_locale_array) / sizeof(language_to_locale_array[0]);
const char *outlocale = NULL;
int outlocale_score = 0;
int i = 0;
int j = 0;
/* find the locale with the longest string match */
for (i = 0; i < numlocale; i++)
{
const char *candidate = language_to_locale_array[i].locale;
for (j = 0; j < (int)strlen(inlocale) && j < (int)strlen(candidate); j++)
{
if (inlocale[j] != candidate[j])
break;
}
if (j > outlocale_score)
{
outlocale = candidate;
outlocale_score = j;
}
}
/* locale must match at least two characters */
if (outlocale_score < 2)
{
outlocale = "";
}
return outlocale;
}
#endif
/**
* Set language ("langint" global) based on a given locale;
* will try a few ways of checking, is case-insensitive, etc.
*
* @param loc Locale (e.g., "pt_BR.UTF-8", "pt_BR", "pt_br", etc.)
*/
static void set_langint_from_locale_string(const char *restrict loc)
{
char *baseloc = strdup(loc);
char *dot = strchr(baseloc, '.');
char *at = strchr(baseloc, '@');
char *cntrycode = strchr(baseloc, '_');
char straux[255];
char *ataux = NULL;
char *ccodeaux = NULL;
size_t len_baseloc;
int found = 0;
int i;
// printf("langint %i\n", langint);
if (!loc)
return;
/* Remove the .UTF-8 extension, then
try to find the full locale including country code and variant,
if it fails, then try to find the language code plus the variant,
if it still fails, try to find language and country code without the variant,
finally scan just the lang part.
as a last resource reverse the scanning
*/
if (dot)
*dot = '\0';
if (cntrycode)
{
ccodeaux = strdup(cntrycode);
*cntrycode = '\0';
}
if (at)
{
ataux = strdup(at);
*at = '\0';
if (cntrycode)
{
/* ll_CC@variant */
//if (found == 0) printf("ll_CC@variant check\n");
snprintf(straux, 255, "%s%s%s", baseloc, ccodeaux, ataux);
len_baseloc = strlen(straux);
for (i = 0; i < NUM_LANGS && found == 0; i++)
{
// Case-insensitive (both "pt_BR" and "pt_br" work, etc.)
if (len_baseloc == strlen(lang_prefixes[i]) && !strncasecmp(straux, lang_prefixes[i], len_baseloc))
{
langint = i;
found = 1;
}
}
}
/* ll@variant */
//if (found == 0) printf("ll@variant check\n");
snprintf(straux, 255, "%s%s", baseloc, ataux);
len_baseloc = strlen(straux);
for (i = 0; i < NUM_LANGS && found == 0; i++)
{
// Case-insensitive (both "pt_BR" and "pt_br" work, etc.)
if (len_baseloc == strlen(lang_prefixes[i]) && !strncasecmp(straux, lang_prefixes[i], len_baseloc))
{
langint = i;
found = 1;
}
}
}
if (cntrycode)
{
/* ll_CC */
//if (found == 0) printf("ll_CC check\n");
snprintf(straux, 255, "%s%s", baseloc, ccodeaux);
len_baseloc = strlen(straux);
/* Which, if any, of the locales is it? */
for (i = 0; i < NUM_LANGS && found == 0; i++)
{
// Case-insensitive (both "pt_BR" and "pt_br" work, etc.)
if (len_baseloc == strlen(lang_prefixes[i]) && !strncasecmp(straux, lang_prefixes[i], strlen(lang_prefixes[i])))
{
langint = i;
found = 1;
}
}
}
/* ll */
// if (found == 0) printf("ll check\n");
len_baseloc = strlen(baseloc);
/* Which, if any, of the locales is it? */
for (i = 0; i < NUM_LANGS && found == 0; i++)
{
// Case-insensitive (both "pt_BR" and "pt_br" work, etc.)
if (len_baseloc == strlen(lang_prefixes[i]) && !strncasecmp(baseloc, lang_prefixes[i], strlen(lang_prefixes[i])))
{
langint = i;
found = 1;
}
}
/* Last resort, we should never arrive here, this check depends
on the right order in lang_prefixes[]
Languages sharing the same starting letters must be ordered
from longest to shortest, like currently are pt_BR and pt */
// if (found == 0)
// printf("Language still not found: loc= %s Trying reverse check as last resource...\n", loc);
for (i = 0; i < NUM_LANGS && found == 0; i++)
{
// Case-insensitive (both "pt_BR" and "pt_br" work, etc.)
if (!strncasecmp(loc, lang_prefixes[i], strlen(lang_prefixes[i])))
{
langint = i;
found = 1;
}
}
// printf("langint %i, lang_ext %s\n", langint, lang_prefixes[langint]);
free(baseloc);
if (ataux)
free(ataux);
if (ccodeaux)
free(ccodeaux);
}
#define HAVE_SETENV
#ifdef WIN32
#undef HAVE_SETENV
#endif
/**
* Set an environment variable.
* (Wrapper for setenv() or putenv(), depending on OS)
*
* @param name Variable to set
* @param value Value to set the variable to
*/
void mysetenv(const char *name, const char *value)
{
#ifndef HAVE_SETENV
int len;
char *str;
#endif
if (name != NULL && value != NULL)
{
#ifdef HAVE_SETENV
setenv(name, value, 1);
#else
len = strlen(name) + 1 + strlen(value) + 1;
str = malloc(len);
sprintf(str, "%s=%s", name, value);
putenv(str);
#endif
}
else
{
fprintf(stderr,
"WARNING: mysetenv() received a null pointer. name=%s, value=%s\n",
(name == NULL ? "NULL" : name), (value == NULL ? "NULL" : value));
}
}
/**
* Attempt to set Tux Paint's UI language.
*
* @param loc Locale
* @return The Y-nudge value for font rendering in the language.
*/
static int set_current_language(const char *restrict loc, int *ptr_num_wished_langs)
{
int i;
int j = 0;
char *oldloc;
char *env_language;
char *env_language_lang;
char *env;
int num_wished_langs = 0;
*ptr_num_wished_langs = 0;
if (strlen(loc) > 0)
{
/* Got command line or config file language */
DEBUG_PRINTF("Language via config: %s\n", loc);
mysetenv("LANGUAGE", loc);
}
else
{
DEBUG_PRINTF("Language NOT set via config\n");
/* Find what language to use from env vars */
env = getenv("LANGUAGE");
if (env == NULL || env[0] == '\0')
{
env = getenv("LC_ALL");
if (env != NULL && env[0] != '\0')
{
DEBUG_PRINTF("Language via LC_ALL: %s\n", getenv("LC_ALL"));
mysetenv("LANGUAGE", getenv("LC_ALL"));
}
else
{
env = getenv("LC_MESSAGES");
if (env != NULL && env[0] != '\0')
{
DEBUG_PRINTF("Language via LC_MESSAGES: %s\n", getenv("LC_MESSAGES"));
mysetenv("LANGUAGE", getenv("LC_MESSAGES"));
}
else
{
env = getenv("LANG");
if (env != NULL && env[0] != '\0')
{
DEBUG_PRINTF("Language via LANG: %s\n", getenv("LANG"));
mysetenv("LANGUAGE", getenv("LANG"));
}
else
{
DEBUG_PRINTF("No language set!\n");
}
}
}
}
else
{
DEBUG_PRINTF("Language was set to '%s'\n", getenv("LANGUAGE"));
}
}
oldloc = strdup(loc);
/* First set the locale according to the environment, then try to overwrite with loc,
after that, ctype_utf8() call will test the compatibility with utf8 and try to load
a different locale if the resulting one is not compatible. */
DEBUG_PRINTF("Locale BEFORE is: %s\n", setlocale(LC_ALL, NULL)); //EP
setlocale(LC_ALL, "");
setlocale(LC_ALL, loc);
ctype_utf8();
DEBUG_PRINTF("Locale AFTER is: %s\n", setlocale(LC_ALL, NULL)); //EP
#ifdef BDIST_WIN32
// FIXME: After the update of MinGW/MSYS2 in January 2022, gettext() no longer find
// translation (.mo) files unless dirname is specified by full path.
//
// -- 2022/02/02: Shin-ichi TOYAMA & Pere Pujal i Carabantes
char curdir[256];
char f[512];
getcwd(curdir, sizeof(curdir));
snprintf(f, sizeof(f), "%s%s", curdir, "\\locale");
#ifdef DEBUG
printf("Current directory at launchtime: %s\n", curdir);
printf("Localedir is set to: %s\n", f);
#endif
bindtextdomain("tuxpaint", f);
#else
bindtextdomain("tuxpaint", LOCALEDIR);
#endif
/* Old version of glibc does not have bind_textdomain_codeset() */
#if defined(_WIN32) || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 2) || __GLIBC__ > 2 || defined(__NetBSD__) || __APPLE__
bind_textdomain_codeset("tuxpaint", "UTF-8");
#endif
textdomain("tuxpaint");
// NULL: Used to direct setlocale() to query the current
// internationalised environment and return the name of the locale().
loc = setlocale(LC_MESSAGES, NULL);
if (oldloc && loc && strcmp(oldloc, "") != 0 && strcmp(loc, oldloc) != 0)
{
/* 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 */
/* Albert's comments from December 2009:
gettext() won't even bother to look up messages unless it
is totally satisfied that you are using one of the locales that
it ships with! Make gettext() unhappy, and it'll switch to the
lobotomized 7-bit Linux "C" locale just to spite you.
http://sources.redhat.com/cgi-bin/cvsweb.cgi/libc/localedata/SUPPORTED?content-type=text/x-cvsweb-markup&cvsroot=glibc
You can confuse gettext() into mostly behaving. For example, a
user could pick a random UTF-8 locale and change the messages.
In that case, Tux Paint thinks it's in the other locale but the
messages come out right. Like so: LANGUAGE=zam LC_ALL=fr_FR.UTF-8
It doesn't work to leave LC_ALL unset, set it to "zam", set it to "C",
or set it to random nonsense. Yeah, Tux Paint will think it's in
a French locale, but the messages will be Zapotec nonetheless.
Maybe it's time to give up on gettext().
[see also: https://sourceforge.net/mailarchive/message.php?msg_name=787b0d920912222352i5ab22834x92686283b565016b%40mail.gmail.com ]
*/
/* Unneeded here, this has yet been done as part of ctype_utf8() call before, iterating over a list of locales */
// setlocale(LC_ALL, "en_US.UTF-8"); /* Is it dumb to assume "en_US" is pretty close to "C" locale? */
mysetenv("LANGUAGE", oldloc);
set_langint_from_locale_string(oldloc);
}
else
{
#ifdef _WIN32
if (getenv("LANGUAGE") == NULL)
mysetenv("LANGUAGE", loc);
#endif
if (getenv("LANGUAGE") == NULL)
mysetenv("LANGUAGE", "C");
}
env_language = strdup(getenv("LANGUAGE"));
if (*env_language)
{
env_language_lang = strtok(env_language, ":");
while (env_language_lang != NULL)
{
num_wished_langs++;
set_langint_from_locale_string(env_language_lang);
wished_langs[j].langint = langint;
wished_langs[j].lang_prefix = lang_prefixes[langint];
wished_langs[j].need_own_font = search_int_array(langint, lang_use_own_font);
wished_langs[j].need_right_to_left = search_int_array(langint, lang_use_right_to_left);
wished_langs[j].need_right_to_left_word = search_int_array(langint, lang_use_right_to_left_word);
for (i = 0; lang_y_nudge[i][0] != -1; i++)
{
// printf("lang_y_nudge[%d][0] = %d\n", i, lang_y_nudge[i][0]);
if (lang_y_nudge[i][0] == langint)
{
wished_langs[j].lang_y_nudge = lang_y_nudge[i][1];
break;
}
}
j++;
env_language_lang = strtok(NULL, ":");
}
if (*env_language)
free(env_language);
}
// set_langint_from_locale_string(loc);
lang_prefix = lang_prefixes[wished_langs[0].langint];
short_lang_prefix = strdup(lang_prefix);
/* When in doubt, cut off country code */
if (strchr(short_lang_prefix, '_'))
*strchr(short_lang_prefix, '_') = '\0';
need_own_font = wished_langs[0].need_own_font;
need_right_to_left = wished_langs[0].need_right_to_left;
need_right_to_left_word = wished_langs[0].need_right_to_left_word;
#ifdef DEBUG
fprintf(stderr, "DEBUG: Language is %s (%d) %s/%s\n",
lang_prefix, langint, need_right_to_left ? "(RTL)" : "", need_right_to_left_word ? "(RTL words)" : "");
fflush(stderr);
#endif
free(oldloc);
DEBUG_PRINTF("lang_prefixes[%d] is \"%s\"\n", get_current_language(), lang_prefixes[get_current_language()]);
*ptr_num_wished_langs = num_wished_langs;
return wished_langs[0].lang_y_nudge;
}
/**
* Given a locale (e.g., "de_DE.UTF-8" or a language name (e.g., "german"),
* attempt to set Tux Paint's UI language. Show help, and exit,
* if asked (either 'locale' or 'lang' are "help"), or if the
* given input is not recognized.
*
* @param char * lang Language name (or NULL)
* @param char * locale Locale (or NULL)
* @param int * a place to return the number of languages we want to use, when scanning stamp descriptions
* @return Y-nudge
*/
int setup_i18n(const char *restrict lang, const char *restrict locale, int *num_wished_langs)
{
DEBUG_PRINTF("lang %p, locale %p\n", lang, locale);
DEBUG_PRINTF("lang \"%s\", locale \"%s\"\n", lang, locale);
if (locale)
{
if (!strcmp(locale, "help"))
{
show_locale_usage(stdout, "tuxpaint");
exit(0);
}
}
else
{
#if defined(__APPLE__)
locale = locale_to_closest_locale(apple_locale());
#else
locale = "";
#endif
}
if (lang)
locale = language_to_locale(lang);
#ifdef __ANDROID__
if (locale == NULL)
locale = android_locale();
#endif
if (locale == NULL)
locale = "";
return set_current_language(locale, num_wished_langs);
}