vdr  2.2.0
i18n.c
Go to the documentation of this file.
1 /*
2  * i18n.c: Internationalization
3  *
4  * See the main source file 'vdr.c' for copyright information and
5  * how to reach the author.
6  *
7  * $Id: i18n.c 3.0 2012/09/01 10:53:43 kls Exp $
8  */
9 
10 /*
11  * In case an English phrase is used in more than one context (and might need
12  * different translations in other languages) it can be preceded with an
13  * arbitrary string to describe its context, separated from the actual phrase
14  * by a '$' character (see for instance "Button$Stop" vs. "Stop").
15  * Of course this means that no English phrase may contain the '$' character!
16  * If this should ever become necessary, the existing '$' would have to be
17  * replaced with something different...
18  */
19 
20 #include "i18n.h"
21 #include <ctype.h>
22 #include <libintl.h>
23 #include <locale.h>
24 #include <unistd.h>
25 #include "tools.h"
26 
27 // TRANSLATORS: The name of the language, as written natively
28 const char *LanguageName = trNOOP("LanguageName$English");
29 // TRANSLATORS: The 3-letter code of the language
30 const char *LanguageCode = trNOOP("LanguageCode$eng");
31 
32 // List of known language codes with aliases.
33 // Actually we could list all codes from http://www.loc.gov/standards/iso639-2
34 // here, but that would be several hundreds - and for most of them it's unlikely
35 // they're ever going to be used...
36 
37 const char *LanguageCodeList[] = {
38  "eng,dos",
39  "deu,ger",
40  "slv,slo",
41  "ita",
42  "dut,nla,nld",
43  "prt",
44  "fra,fre",
45  "nor",
46  "fin,suo",
47  "pol",
48  "esl,spa",
49  "ell,gre",
50  "sve,swe",
51  "rom,rum",
52  "hun",
53  "cat,cln",
54  "rus",
55  "srb,srp,scr,scc",
56  "hrv",
57  "est",
58  "dan",
59  "cze,ces",
60  "tur",
61  "ukr",
62  "ara",
63  NULL
64  };
65 
67 
71 
72 static int NumLocales = 1;
73 static int CurrentLanguage = 0;
74 
75 static bool ContainsCode(const char *Codes, const char *Code)
76 {
77  while (*Codes) {
78  int l = 0;
79  for ( ; l < 3 && Code[l]; l++) {
80  if (Codes[l] != tolower(Code[l]))
81  break;
82  }
83  if (l == 3)
84  return true;
85  Codes++;
86  }
87  return false;
88 }
89 
90 static const char *SkipContext(const char *s)
91 {
92  const char *p = strchr(s, '$');
93  return p ? p + 1 : s;
94 }
95 
96 static void SetEnvLanguage(const char *Locale)
97 {
98  setenv("LANGUAGE", Locale, 1);
99  extern int _nl_msg_cat_cntr;
100  ++_nl_msg_cat_cntr;
101 }
102 
103 void I18nInitialize(const char *LocaleDir)
104 {
105  I18nLocaleDir = LocaleDir;
106  LanguageLocales.Append(strdup(I18N_DEFAULT_LOCALE));
107  LanguageNames.Append(strdup(SkipContext(LanguageName)));
108  LanguageCodes.Append(strdup(LanguageCodeList[0]));
109  textdomain("vdr");
110  bindtextdomain("vdr", I18nLocaleDir);
111  cFileNameList Locales(I18nLocaleDir, true);
112  if (Locales.Size() > 0) {
113  char *OldLocale = strdup(setlocale(LC_MESSAGES, NULL));
114  for (int i = 0; i < Locales.Size(); i++) {
115  cString FileName = cString::sprintf("%s/%s/LC_MESSAGES/vdr.mo", *I18nLocaleDir, Locales[i]);
116  if (access(FileName, F_OK) == 0) { // found a locale with VDR texts
117  if (NumLocales < I18N_MAX_LANGUAGES - 1) {
118  SetEnvLanguage(Locales[i]);
119  const char *TranslatedLanguageName = gettext(LanguageName);
120  if (TranslatedLanguageName != LanguageName) {
121  NumLocales++;
122  if (strstr(OldLocale, Locales[i]) == OldLocale)
123  CurrentLanguage = LanguageLocales.Size();
124  LanguageLocales.Append(strdup(Locales[i]));
125  LanguageNames.Append(strdup(TranslatedLanguageName));
126  const char *Code = gettext(LanguageCode);
127  for (const char **lc = LanguageCodeList; *lc; lc++) {
128  if (ContainsCode(*lc, Code)) {
129  Code = *lc;
130  break;
131  }
132  }
133  LanguageCodes.Append(strdup(Code));
134  }
135  }
136  else {
137  esyslog("ERROR: too many locales - increase I18N_MAX_LANGUAGES!");
138  break;
139  }
140  }
141  }
142  SetEnvLanguage(LanguageLocales[CurrentLanguage]);
143  free(OldLocale);
144  dsyslog("found %d locales in %s", NumLocales - 1, *I18nLocaleDir);
145  }
146  // Prepare any known language codes for which there was no locale:
147  for (const char **lc = LanguageCodeList; *lc; lc++) {
148  bool Found = false;
149  for (int i = 0; i < LanguageCodes.Size(); i++) {
150  if (strcmp(*lc, LanguageCodes[i]) == 0) {
151  Found = true;
152  break;
153  }
154  }
155  if (!Found) {
156  dsyslog("no locale for language code '%s'", *lc);
157  LanguageLocales.Append(strdup(I18N_DEFAULT_LOCALE));
158  LanguageNames.Append(strdup(*lc));
159  LanguageCodes.Append(strdup(*lc));
160  }
161  }
162 }
163 
164 void I18nRegister(const char *Plugin)
165 {
166  cString Domain = cString::sprintf("vdr-%s", Plugin);
167  bindtextdomain(Domain, I18nLocaleDir);
168 }
169 
170 void I18nSetLocale(const char *Locale)
171 {
172  if (Locale && *Locale) {
173  int i = LanguageLocales.Find(Locale);
174  if (i >= 0) {
175  CurrentLanguage = i;
176  SetEnvLanguage(Locale);
177  }
178  else
179  dsyslog("unknown locale: '%s'", Locale);
180  }
181 }
182 
184 {
185  return CurrentLanguage;
186 }
187 
188 void I18nSetLanguage(int Language)
189 {
190  if (Language < LanguageNames.Size()) {
191  CurrentLanguage = Language;
193  }
194 }
195 
197 {
198  return NumLocales;
199 }
200 
202 {
203  return &LanguageNames;
204 }
205 
206 const char *I18nTranslate(const char *s, const char *Plugin)
207 {
208  if (!s)
209  return s;
210  if (CurrentLanguage) {
211  const char *t = Plugin ? dgettext(Plugin, s) : gettext(s);
212  if (t != s)
213  return t;
214  }
215  return SkipContext(s);
216 }
217 
218 const char *I18nLocale(int Language)
219 {
220  return 0 <= Language && Language < LanguageLocales.Size() ? LanguageLocales[Language] : NULL;
221 }
222 
223 const char *I18nLanguageCode(int Language)
224 {
225  return 0 <= Language && Language < LanguageCodes.Size() ? LanguageCodes[Language] : NULL;
226 }
227 
228 int I18nLanguageIndex(const char *Code)
229 {
230  for (int i = 0; i < LanguageCodes.Size(); i++) {
231  if (ContainsCode(LanguageCodes[i], Code))
232  return i;
233  }
234  //dsyslog("unknown language code: '%s'", Code);
235  return -1;
236 }
237 
238 const char *I18nNormalizeLanguageCode(const char *Code)
239 {
240  for (int i = 0; i < 3; i++) {
241  if (Code[i]) {
242  // ETSI EN 300 468 defines language codes as consisting of three letters
243  // according to ISO 639-2. This means that they are supposed to always consist
244  // of exactly three letters in the range a-z - no digits, UTF-8 or other
245  // funny characters. However, some broadcasters apparently don't have a
246  // copy of the DVB standard (or they do, but are perhaps unable to read it),
247  // so they put all sorts of non-standard stuff into the language codes,
248  // like nonsense as "2ch" or "A 1" (yes, they even go as far as using
249  // blanks!). Such things should go into the description of the EPG event's
250  // ComponentDescriptor.
251  // So, as a workaround for this broadcaster stupidity, let's ignore
252  // language codes with unprintable characters...
253  if (!isprint(Code[i])) {
254  //dsyslog("invalid language code: '%s'", Code);
255  return "???";
256  }
257  // ...and replace blanks with underlines (ok, this breaks the 'const'
258  // of the Code parameter - but hey, it's them who started this):
259  if (Code[i] == ' ')
260  *((char *)&Code[i]) = '_';
261  }
262  else
263  break;
264  }
265  int n = I18nLanguageIndex(Code);
266  return n >= 0 ? I18nLanguageCode(n) : Code;
267 }
268 
269 bool I18nIsPreferredLanguage(int *PreferredLanguages, const char *LanguageCode, int &OldPreference, int *Position)
270 {
271  int pos = 1;
272  bool found = false;
273  while (LanguageCode) {
274  int LanguageIndex = I18nLanguageIndex(LanguageCode);
275  for (int i = 0; i < LanguageCodes.Size(); i++) {
276  if (PreferredLanguages[i] < 0)
277  break; // the language is not a preferred one
278  if (PreferredLanguages[i] == LanguageIndex) {
279  if (OldPreference < 0 || i < OldPreference) {
280  OldPreference = i;
281  if (Position)
282  *Position = pos;
283  found = true;
284  break;
285  }
286  }
287  }
288  if ((LanguageCode = strchr(LanguageCode, '+')) != NULL) {
289  LanguageCode++;
290  pos++;
291  }
292  else if (pos == 1 && Position)
293  *Position = 0;
294  }
295  if (OldPreference < 0) {
296  OldPreference = LanguageCodes.Size(); // higher than the maximum possible value
297  return true; // if we don't find a preferred one, we take the first one
298  }
299  return found;
300 }
int Find(const char *s) const
Definition: tools.c:1484
#define dsyslog(a...)
Definition: tools.h:36
int I18nCurrentLanguage(void)
Returns the index of the current language.
Definition: i18n.c:183
#define I18N_MAX_LANGUAGES
Definition: i18n.h:18
static const char * SkipContext(const char *s)
Definition: i18n.c:90
const char * LanguageCodeList[]
Definition: i18n.c:37
static cString sprintf(const char *fmt,...) __attribute__((format(printf
Definition: tools.c:1080
static int NumLocales
Definition: i18n.c:72
virtual void Append(T Data)
Definition: tools.h:571
#define esyslog(a...)
Definition: tools.h:34
static cStringList LanguageCodes
Definition: i18n.c:70
void I18nSetLocale(const char *Locale)
Sets the current locale to Locale.
Definition: i18n.c:170
static int CurrentLanguage
Definition: i18n.c:73
const char * I18nLocale(int Language)
Returns the locale code of the given Language (which is an index as returned by I18nCurrentLanguage()...
Definition: i18n.c:218
#define trNOOP(s)
Definition: i18n.h:88
const char * LanguageCode
Definition: i18n.c:30
#define I18N_DEFAULT_LOCALE
Definition: i18n.h:16
static void SetEnvLanguage(const char *Locale)
Definition: i18n.c:96
const char * I18nTranslate(const char *s, const char *Plugin)
Translates the given string (with optional Plugin context) into the current language.
Definition: i18n.c:206
void I18nRegister(const char *Plugin)
Registers the named plugin, so that it can use internationalized texts.
Definition: i18n.c:164
int Size(void) const
Definition: tools.h:551
const char * I18nLanguageCode(int Language)
Returns the three letter language code of the given Language (which is an index as returned by I18nCu...
Definition: i18n.c:223
bool I18nIsPreferredLanguage(int *PreferredLanguages, const char *LanguageCode, int &OldPreference, int *Position)
Checks the given LanguageCode (which may be something like "eng" or "eng+deu") against the PreferredL...
Definition: i18n.c:269
static cString I18nLocaleDir
Definition: i18n.c:66
const cStringList * I18nLanguages(void)
Returns the list of available languages.
Definition: i18n.c:201
static cStringList LanguageLocales
Definition: i18n.c:68
int I18nNumLanguagesWithLocale(void)
Returns the number of entries in the list returned by I18nLanguages() that actually have a locale...
Definition: i18n.c:196
void I18nSetLanguage(int Language)
Sets the current language index to Language.
Definition: i18n.c:188
int I18nLanguageIndex(const char *Code)
Returns the index of the language with the given three letter language Code.
Definition: i18n.c:228
static bool ContainsCode(const char *Codes, const char *Code)
Definition: i18n.c:75
static cStringList LanguageNames
Definition: i18n.c:69
const char * I18nNormalizeLanguageCode(const char *Code)
Returns a 3 letter language code that may not be zero terminated.
Definition: i18n.c:238
void I18nInitialize(const char *LocaleDir)
Detects all available locales and loads the language names and codes.
Definition: i18n.c:103
const char * LanguageName
Definition: i18n.c:28
Definition: tools.h:168