vdr
1.7.27
|
00001 /* 00002 * i18n.c: Internationalization 00003 * 00004 * See the main source file 'vdr.c' for copyright information and 00005 * how to reach the author. 00006 * 00007 * $Id: i18n.c 2.4 2011/08/15 10:01:45 kls Exp $ 00008 */ 00009 00010 /* 00011 * In case an English phrase is used in more than one context (and might need 00012 * different translations in other languages) it can be preceded with an 00013 * arbitrary string to describe its context, separated from the actual phrase 00014 * by a '$' character (see for instance "Button$Stop" vs. "Stop"). 00015 * Of course this means that no English phrase may contain the '$' character! 00016 * If this should ever become necessary, the existing '$' would have to be 00017 * replaced with something different... 00018 */ 00019 00020 #include "i18n.h" 00021 #include <ctype.h> 00022 #include <libintl.h> 00023 #include <locale.h> 00024 #include <unistd.h> 00025 #include "tools.h" 00026 00027 // TRANSLATORS: The name of the language, as written natively 00028 const char *LanguageName = trNOOP("LanguageName$English"); 00029 // TRANSLATORS: The 3-letter code of the language 00030 const char *LanguageCode = trNOOP("LanguageCode$eng"); 00031 00032 // List of known language codes with aliases. 00033 // Actually we could list all codes from http://www.loc.gov/standards/iso639-2 00034 // here, but that would be several hundreds - and for most of them it's unlikely 00035 // they're ever going to be used... 00036 00037 const char *LanguageCodeList[] = { 00038 "eng,dos", 00039 "deu,ger", 00040 "slv,slo", 00041 "ita", 00042 "dut,nla,nld", 00043 "prt", 00044 "fra,fre", 00045 "nor", 00046 "fin,suo", 00047 "pol", 00048 "esl,spa", 00049 "ell,gre", 00050 "sve,swe", 00051 "rom,rum", 00052 "hun", 00053 "cat,cln", 00054 "rus", 00055 "srb,srp,scr,scc", 00056 "hrv", 00057 "est", 00058 "dan", 00059 "cze,ces", 00060 "tur", 00061 "ukr", 00062 "ara", 00063 NULL 00064 }; 00065 00066 static const char *I18nLocaleDir = LOCDIR; 00067 00068 static cStringList LanguageLocales; 00069 static cStringList LanguageNames; 00070 static cStringList LanguageCodes; 00071 00072 static int NumLocales = 1; 00073 static int CurrentLanguage = 0; 00074 00075 static bool ContainsCode(const char *Codes, const char *Code) 00076 { 00077 while (*Codes) { 00078 int l = 0; 00079 for ( ; l < 3 && Code[l]; l++) { 00080 if (Codes[l] != tolower(Code[l])) 00081 break; 00082 } 00083 if (l == 3) 00084 return true; 00085 Codes++; 00086 } 00087 return false; 00088 } 00089 00090 static const char *SkipContext(const char *s) 00091 { 00092 const char *p = strchr(s, '$'); 00093 return p ? p + 1 : s; 00094 } 00095 00096 static void SetEnvLanguage(const char *Locale) 00097 { 00098 setenv("LANGUAGE", Locale, 1); 00099 extern int _nl_msg_cat_cntr; 00100 ++_nl_msg_cat_cntr; 00101 } 00102 00103 void I18nInitialize(const char *LocaleDir) 00104 { 00105 if (LocaleDir) 00106 I18nLocaleDir = LocaleDir; 00107 LanguageLocales.Append(strdup(I18N_DEFAULT_LOCALE)); 00108 LanguageNames.Append(strdup(SkipContext(LanguageName))); 00109 LanguageCodes.Append(strdup(LanguageCodeList[0])); 00110 textdomain("vdr"); 00111 bindtextdomain("vdr", I18nLocaleDir); 00112 cFileNameList Locales(I18nLocaleDir, true); 00113 if (Locales.Size() > 0) { 00114 char *OldLocale = strdup(setlocale(LC_MESSAGES, NULL)); 00115 for (int i = 0; i < Locales.Size(); i++) { 00116 cString FileName = cString::sprintf("%s/%s/LC_MESSAGES/vdr.mo", I18nLocaleDir, Locales[i]); 00117 if (access(FileName, F_OK) == 0) { // found a locale with VDR texts 00118 if (NumLocales < I18N_MAX_LANGUAGES - 1) { 00119 SetEnvLanguage(Locales[i]); 00120 const char *TranslatedLanguageName = gettext(LanguageName); 00121 if (TranslatedLanguageName != LanguageName) { 00122 NumLocales++; 00123 if (strstr(OldLocale, Locales[i]) == OldLocale) 00124 CurrentLanguage = LanguageLocales.Size(); 00125 LanguageLocales.Append(strdup(Locales[i])); 00126 LanguageNames.Append(strdup(TranslatedLanguageName)); 00127 const char *Code = gettext(LanguageCode); 00128 for (const char **lc = LanguageCodeList; *lc; lc++) { 00129 if (ContainsCode(*lc, Code)) { 00130 Code = *lc; 00131 break; 00132 } 00133 } 00134 LanguageCodes.Append(strdup(Code)); 00135 } 00136 } 00137 else { 00138 esyslog("ERROR: too many locales - increase I18N_MAX_LANGUAGES!"); 00139 break; 00140 } 00141 } 00142 } 00143 SetEnvLanguage(LanguageLocales[CurrentLanguage]); 00144 free(OldLocale); 00145 dsyslog("found %d locales in %s", NumLocales - 1, I18nLocaleDir); 00146 } 00147 // Prepare any known language codes for which there was no locale: 00148 for (const char **lc = LanguageCodeList; *lc; lc++) { 00149 bool Found = false; 00150 for (int i = 0; i < LanguageCodes.Size(); i++) { 00151 if (strcmp(*lc, LanguageCodes[i]) == 0) { 00152 Found = true; 00153 break; 00154 } 00155 } 00156 if (!Found) { 00157 dsyslog("no locale for language code '%s'", *lc); 00158 LanguageLocales.Append(strdup(I18N_DEFAULT_LOCALE)); 00159 LanguageNames.Append(strdup(*lc)); 00160 LanguageCodes.Append(strdup(*lc)); 00161 } 00162 } 00163 } 00164 00165 void I18nRegister(const char *Plugin) 00166 { 00167 cString Domain = cString::sprintf("vdr-%s", Plugin); 00168 bindtextdomain(Domain, I18nLocaleDir); 00169 } 00170 00171 void I18nSetLocale(const char *Locale) 00172 { 00173 if (Locale && *Locale) { 00174 int i = LanguageLocales.Find(Locale); 00175 if (i >= 0) { 00176 CurrentLanguage = i; 00177 SetEnvLanguage(Locale); 00178 } 00179 else 00180 dsyslog("unknown locale: '%s'", Locale); 00181 } 00182 } 00183 00184 int I18nCurrentLanguage(void) 00185 { 00186 return CurrentLanguage; 00187 } 00188 00189 void I18nSetLanguage(int Language) 00190 { 00191 if (Language < LanguageNames.Size()) { 00192 CurrentLanguage = Language; 00193 I18nSetLocale(I18nLocale(CurrentLanguage)); 00194 } 00195 } 00196 00197 int I18nNumLanguagesWithLocale(void) 00198 { 00199 return NumLocales; 00200 } 00201 00202 const cStringList *I18nLanguages(void) 00203 { 00204 return &LanguageNames; 00205 } 00206 00207 const char *I18nTranslate(const char *s, const char *Plugin) 00208 { 00209 if (!s) 00210 return s; 00211 if (CurrentLanguage) { 00212 const char *t = Plugin ? dgettext(Plugin, s) : gettext(s); 00213 if (t != s) 00214 return t; 00215 } 00216 return SkipContext(s); 00217 } 00218 00219 const char *I18nLocale(int Language) 00220 { 00221 return 0 <= Language && Language < LanguageLocales.Size() ? LanguageLocales[Language] : NULL; 00222 } 00223 00224 const char *I18nLanguageCode(int Language) 00225 { 00226 return 0 <= Language && Language < LanguageCodes.Size() ? LanguageCodes[Language] : NULL; 00227 } 00228 00229 int I18nLanguageIndex(const char *Code) 00230 { 00231 for (int i = 0; i < LanguageCodes.Size(); i++) { 00232 if (ContainsCode(LanguageCodes[i], Code)) 00233 return i; 00234 } 00235 //dsyslog("unknown language code: '%s'", Code); 00236 return -1; 00237 } 00238 00239 const char *I18nNormalizeLanguageCode(const char *Code) 00240 { 00241 for (int i = 0; i < 3; i++) { 00242 if (Code[i]) { 00243 // ETSI EN 300 468 defines language codes as consisting of three letters 00244 // according to ISO 639-2. This means that they are supposed to always consist 00245 // of exactly three letters in the range a-z - no digits, UTF-8 or other 00246 // funny characters. However, some broadcasters apparently don't have a 00247 // copy of the DVB standard (or they do, but are perhaps unable to read it), 00248 // so they put all sorts of non-standard stuff into the language codes, 00249 // like nonsense as "2ch" or "A 1" (yes, they even go as far as using 00250 // blanks!). Such things should go into the description of the EPG event's 00251 // ComponentDescriptor. 00252 // So, as a workaround for this broadcaster stupidity, let's ignore 00253 // language codes with unprintable characters... 00254 if (!isprint(Code[i])) { 00255 //dsyslog("invalid language code: '%s'", Code); 00256 return "???"; 00257 } 00258 // ...and replace blanks with underlines (ok, this breaks the 'const' 00259 // of the Code parameter - but hey, it's them who started this): 00260 if (Code[i] == ' ') 00261 *((char *)&Code[i]) = '_'; 00262 } 00263 else 00264 break; 00265 } 00266 int n = I18nLanguageIndex(Code); 00267 return n >= 0 ? I18nLanguageCode(n) : Code; 00268 } 00269 00270 bool I18nIsPreferredLanguage(int *PreferredLanguages, const char *LanguageCode, int &OldPreference, int *Position) 00271 { 00272 int pos = 1; 00273 bool found = false; 00274 while (LanguageCode) { 00275 int LanguageIndex = I18nLanguageIndex(LanguageCode); 00276 for (int i = 0; i < LanguageCodes.Size(); i++) { 00277 if (PreferredLanguages[i] < 0) 00278 break; // the language is not a preferred one 00279 if (PreferredLanguages[i] == LanguageIndex) { 00280 if (OldPreference < 0 || i < OldPreference) { 00281 OldPreference = i; 00282 if (Position) 00283 *Position = pos; 00284 found = true; 00285 break; 00286 } 00287 } 00288 } 00289 if ((LanguageCode = strchr(LanguageCode, '+')) != NULL) { 00290 LanguageCode++; 00291 pos++; 00292 } 00293 else if (pos == 1 && Position) 00294 *Position = 0; 00295 } 00296 if (OldPreference < 0) { 00297 OldPreference = LanguageCodes.Size(); // higher than the maximum possible value 00298 return true; // if we don't find a preferred one, we take the first one 00299 } 00300 return found; 00301 }