vdr  1.7.27
i18n.c
Go to the documentation of this file.
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 }