vdr  2.4.7
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 4.2 2020/06/15 15:57:32 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  "alb,sqi",
41  "ara",
42  "bos",
43  "bul",
44  "cat,cln",
45  "chi,zho",
46  "cze,ces",
47  "dan",
48  "dut,nla,nld",
49  "ell,gre",
50  "esl,spa",
51  "est",
52  "eus,baq",
53  "fin,suo",
54  "fra,fre",
55  "hrv",
56  "hun",
57  "iri,gle", // 'NorDig'
58  "ita",
59  "jpn",
60  "lav",
61  "lit",
62  "ltz",
63  "mac,mkd",
64  "mlt",
65  "nor",
66  "pol",
67  "por",
68  "rom,rum",
69  "rus",
70  "slk,slo",
71  "slv",
72  "smi", // 'NorDig' Sami language (Norway, Sweden, Finnland, Russia)
73  "srb,srp,scr,scc",
74  "sve,swe",
75  "tur",
76  "ukr",
77  NULL
78  };
79 
80 struct tSpecialLc { const char *Code; const char *Name; };
81 const struct tSpecialLc SpecialLanguageCodeList[] = {
82  { "qaa", trNOOP("LanguageName$original language (qaa)") },
83  { "mis", trNOOP("LanguageName$uncoded languages (mis)") },
84  { "mul", trNOOP("LanguageName$multiple languages (mul)") },
85  { "nar", trNOOP("LanguageName$narrative (nar)") },
86  { "und", trNOOP("LanguageName$undetermined (und)") },
87  { "zxx", trNOOP("LanguageName$no linguistic content (zxx)") },
88  { NULL, NULL }
89  };
90 
92 
96 
97 static int NumLocales = 1;
98 static int NumLanguages = 1;
99 static int CurrentLanguage = 0;
100 
101 static bool ContainsCode(const char *Codes, const char *Code)
102 {
103  while (*Codes) {
104  int l = 0;
105  for ( ; l < 3 && Code[l]; l++) {
106  if (Codes[l] != tolower(Code[l]))
107  break;
108  }
109  if (l == 3)
110  return true;
111  Codes++;
112  }
113  return false;
114 }
115 
116 static const char *SkipContext(const char *s)
117 {
118  const char *p = strchr(s, '$');
119  return p ? p + 1 : s;
120 }
121 
122 static void SetEnvLanguage(const char *Locale)
123 {
124  setenv("LANGUAGE", Locale, 1);
125  extern int _nl_msg_cat_cntr;
126  ++_nl_msg_cat_cntr;
127 }
128 
129 static void SetLanguageNames(void)
130 {
131  // Update the translation for special language codes:
132  int i = NumLanguages;
133  for (const struct tSpecialLc *slc = SpecialLanguageCodeList; slc->Code; slc++) {
134  const char *TranslatedName = gettext(slc->Name);
135  free(LanguageNames[i]);
136  LanguageNames[i++] = strdup(TranslatedName != slc->Name ? TranslatedName : SkipContext(slc->Name));
137  }
138 }
139 
140 void I18nInitialize(const char *LocaleDir)
141 {
142  I18nLocaleDir = LocaleDir;
146  textdomain("vdr");
147  bindtextdomain("vdr", I18nLocaleDir);
148  cFileNameList Locales(I18nLocaleDir, true);
149  if (Locales.Size() > 0) {
150  char *OldLocale = strdup(setlocale(LC_MESSAGES, NULL));
151  for (int i = 0; i < Locales.Size(); i++) {
152  cString FileName = cString::sprintf("%s/%s/LC_MESSAGES/vdr.mo", *I18nLocaleDir, Locales[i]);
153  if (access(FileName, F_OK) == 0) { // found a locale with VDR texts
154  if (NumLocales < I18N_MAX_LANGUAGES - 1) {
155  SetEnvLanguage(Locales[i]);
156  const char *TranslatedLanguageName = gettext(LanguageName);
157  if (TranslatedLanguageName != LanguageName) {
158  NumLocales++;
159  if (strstr(OldLocale, Locales[i]) == OldLocale)
161  LanguageLocales.Append(strdup(Locales[i]));
162  LanguageNames.Append(strdup(TranslatedLanguageName));
163  const char *Code = gettext(LanguageCode);
164  for (const char **lc = LanguageCodeList; *lc; lc++) {
165  if (ContainsCode(*lc, Code)) {
166  Code = *lc;
167  break;
168  }
169  }
170  LanguageCodes.Append(strdup(Code));
171  }
172  }
173  else {
174  esyslog("ERROR: too many locales - increase I18N_MAX_LANGUAGES!");
175  break;
176  }
177  }
178  }
180  free(OldLocale);
181  dsyslog("found %d locales in %s", NumLocales - 1, *I18nLocaleDir);
182  }
183  // Prepare any known language codes for which there was no locale:
185  for (const char **lc = LanguageCodeList; *lc; lc++) {
186  bool Found = false;
187  for (int i = 0; i < LanguageCodes.Size(); i++) {
188  if (strcmp(*lc, LanguageCodes[i]) == 0) {
189  Found = true;
190  break;
191  }
192  }
193  if (!Found) {
194  dsyslog("no locale for language code '%s'", *lc);
195  NumLanguages++;
197  LanguageNames.Append(strdup(*lc));
198  LanguageCodes.Append(strdup(*lc));
199  }
200  }
201  // Add special language codes and names:
202  for (const struct tSpecialLc *slc = SpecialLanguageCodeList; slc->Code; slc++) {
203  const char *TranslatedName = gettext(slc->Name);
204  LanguageNames.Append(strdup( TranslatedName != slc->Name ? TranslatedName : SkipContext(slc->Name)));
205  LanguageCodes.Append(strdup(slc->Code));
206  }
207 }
208 
209 void I18nRegister(const char *Plugin)
210 {
211  cString Domain = cString::sprintf("vdr-%s", Plugin);
212  bindtextdomain(Domain, I18nLocaleDir);
213 }
214 
215 void I18nSetLocale(const char *Locale)
216 {
217  if (Locale && *Locale) {
218  int i = LanguageLocales.Find(Locale);
219  if (i >= 0) {
220  CurrentLanguage = i;
221  SetEnvLanguage(Locale);
223  }
224  else
225  dsyslog("unknown locale: '%s'", Locale);
226  }
227 }
228 
230 {
231  return CurrentLanguage;
232 }
233 
234 void I18nSetLanguage(int Language)
235 {
236  if (Language < NumLanguages) {
237  CurrentLanguage = Language;
239  }
240 }
241 
243 {
244  return NumLocales;
245 }
246 
248 {
249  return &LanguageNames;
250 }
251 
252 const char *I18nTranslate(const char *s, const char *Plugin)
253 {
254  if (!s)
255  return s;
256  if (CurrentLanguage) {
257  const char *t = Plugin ? dgettext(Plugin, s) : gettext(s);
258  if (t != s)
259  return t;
260  }
261  return SkipContext(s);
262 }
263 
264 const char *I18nLocale(int Language)
265 {
266  return 0 <= Language && Language < LanguageLocales.Size() ? LanguageLocales[Language] : NULL;
267 }
268 
269 const char *I18nLanguageCode(int Language)
270 {
271  return 0 <= Language && Language < LanguageCodes.Size() ? LanguageCodes[Language] : NULL;
272 }
273 
274 int I18nLanguageIndex(const char *Code)
275 {
276  for (int i = 0; i < LanguageCodes.Size(); i++) {
278  return i;
279  }
280  //dsyslog("unknown language code: '%s'", Code);
281  return -1;
282 }
283 
284 const char *I18nNormalizeLanguageCode(const char *Code)
285 {
286  for (int i = 0; i < 3; i++) {
287  if (Code[i]) {
288  // ETSI EN 300 468 defines language codes as consisting of three letters
289  // according to ISO 639-2. This means that they are supposed to always consist
290  // of exactly three letters in the range a-z - no digits, UTF-8 or other
291  // funny characters. However, some broadcasters apparently don't have a
292  // copy of the DVB standard (or they do, but are perhaps unable to read it),
293  // so they put all sorts of non-standard stuff into the language codes,
294  // like nonsense as "2ch" or "A 1" (yes, they even go as far as using
295  // blanks!). Such things should go into the description of the EPG event's
296  // ComponentDescriptor.
297  // So, as a workaround for this broadcaster stupidity, let's ignore
298  // language codes with unprintable characters...
299  if (!isprint(Code[i])) {
300  //dsyslog("invalid language code: '%s'", Code);
301  return "???";
302  }
303  // ...and replace blanks with underlines (ok, this breaks the 'const'
304  // of the Code parameter - but hey, it's them who started this):
305  if (Code[i] == ' ')
306  *((char *)&Code[i]) = '_';
307  }
308  else
309  break;
310  }
311  int n = I18nLanguageIndex(Code);
312  return n >= 0 ? I18nLanguageCode(n) : Code;
313 }
314 
315 bool I18nIsPreferredLanguage(int *PreferredLanguages, const char *LanguageCode, int &OldPreference, int *Position)
316 {
317  int pos = 1;
318  bool found = false;
319  while (LanguageCode) {
320  int LanguageIndex = I18nLanguageIndex(LanguageCode);
321  for (int i = 0; i < LanguageCodes.Size(); i++) {
322  if (PreferredLanguages[i] < 0)
323  break; // the language is not a preferred one
324  if (PreferredLanguages[i] == LanguageIndex) {
325  if (OldPreference < 0 || i < OldPreference) {
326  OldPreference = i;
327  if (Position)
328  *Position = pos;
329  found = true;
330  break;
331  }
332  }
333  }
334  if ((LanguageCode = strchr(LanguageCode, '+')) != NULL) {
335  LanguageCode++;
336  pos++;
337  }
338  else if (pos == 1 && Position)
339  *Position = 0;
340  }
341  if (OldPreference < 0) {
342  OldPreference = LanguageCodes.Size(); // higher than the maximum possible value
343  return true; // if we don't find a preferred one, we take the first one
344  }
345  return found;
346 }
int Find(const char *s) const
Definition: tools.c:1568
Definition: tools.h:174
static cString sprintf(const char *fmt,...) __attribute__((format(printf
Definition: tools.c:1133
int Size(void) const
Definition: tools.h:721
virtual void Append(T Data)
Definition: tools.h:741
void I18nInitialize(const char *LocaleDir)
Detects all available locales and loads the language names and codes.
Definition: i18n.c:140
static void SetLanguageNames(void)
Definition: i18n.c:129
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:269
static const char * SkipContext(const char *s)
Definition: i18n.c:116
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:315
static void SetEnvLanguage(const char *Locale)
Definition: i18n.c:122
static cStringList LanguageCodes
Definition: i18n.c:95
int I18nLanguageIndex(const char *Code)
Returns the index of the language with the given three letter language Code.
Definition: i18n.c:274
static int NumLocales
Definition: i18n.c:97
const cStringList * I18nLanguages(void)
Returns the list of available languages.
Definition: i18n.c:247
const char * LanguageCode
Definition: i18n.c:30
int I18nNumLanguagesWithLocale(void)
Returns the number of entries in the list returned by I18nLanguages() that actually have a locale.
Definition: i18n.c:242
static cStringList LanguageLocales
Definition: i18n.c:93
int I18nCurrentLanguage(void)
Returns the index of the current language.
Definition: i18n.c:229
const char * I18nLocale(int Language)
Returns the locale code of the given Language (which is an index as returned by I18nCurrentLanguage()...
Definition: i18n.c:264
const char * LanguageName
Definition: i18n.c:28
const char * I18nTranslate(const char *s, const char *Plugin)
Translates the given string (with optional Plugin context) into the current language.
Definition: i18n.c:252
const struct tSpecialLc SpecialLanguageCodeList[]
Definition: i18n.c:81
static cString I18nLocaleDir
Definition: i18n.c:91
static int NumLanguages
Definition: i18n.c:98
static cStringList LanguageNames
Definition: i18n.c:94
static bool ContainsCode(const char *Codes, const char *Code)
Definition: i18n.c:101
void I18nSetLocale(const char *Locale)
Sets the current locale to Locale.
Definition: i18n.c:215
void I18nRegister(const char *Plugin)
Registers the named plugin, so that it can use internationalized texts.
Definition: i18n.c:209
const char * I18nNormalizeLanguageCode(const char *Code)
Returns a 3 letter language code that may not be zero terminated.
Definition: i18n.c:284
const char * LanguageCodeList[]
Definition: i18n.c:37
void I18nSetLanguage(int Language)
Sets the current language index to Language.
Definition: i18n.c:234
static int CurrentLanguage
Definition: i18n.c:99
#define I18N_MAX_LANGUAGES
Definition: i18n.h:18
#define I18N_DEFAULT_LOCALE
Definition: i18n.h:16
#define trNOOP(s)
Definition: i18n.h:88
const char * Code
Definition: i18n.c:80
const char * Name
Definition: i18n.c:80
#define dsyslog(a...)
Definition: tools.h:37
#define esyslog(a...)
Definition: tools.h:35