vdr  2.6.1
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 5.1 2021/05/21 09:50:57 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  { "qad", trNOOP("LanguageName$audio description (qad)") },
84  { "mis", trNOOP("LanguageName$uncoded languages (mis)") },
85  { "mul", trNOOP("LanguageName$multiple languages (mul)") },
86  { "nar", trNOOP("LanguageName$narrative (nar)") },
87  { "und", trNOOP("LanguageName$undetermined (und)") },
88  { "zxx", trNOOP("LanguageName$no linguistic content (zxx)") },
89  { NULL, NULL }
90  };
91 
93 
97 
98 static int NumLocales = 1;
99 static int NumLanguages = 1;
100 static int CurrentLanguage = 0;
101 
102 static bool ContainsCode(const char *Codes, const char *Code)
103 {
104  while (*Codes) {
105  int l = 0;
106  for ( ; l < 3 && Code[l]; l++) {
107  if (Codes[l] != tolower(Code[l]))
108  break;
109  }
110  if (l == 3)
111  return true;
112  Codes++;
113  }
114  return false;
115 }
116 
117 static const char *SkipContext(const char *s)
118 {
119  const char *p = strchr(s, '$');
120  return p ? p + 1 : s;
121 }
122 
123 static void SetEnvLanguage(const char *Locale)
124 {
125  setenv("LANGUAGE", Locale, 1);
126  extern int _nl_msg_cat_cntr;
127  ++_nl_msg_cat_cntr;
128 }
129 
130 static void SetLanguageNames(void)
131 {
132  // Update the translation for special language codes:
133  int i = NumLanguages;
134  for (const struct tSpecialLc *slc = SpecialLanguageCodeList; slc->Code; slc++) {
135  const char *TranslatedName = gettext(slc->Name);
136  free(LanguageNames[i]);
137  LanguageNames[i++] = strdup(TranslatedName != slc->Name ? TranslatedName : SkipContext(slc->Name));
138  }
139 }
140 
141 void I18nInitialize(const char *LocaleDir)
142 {
143  I18nLocaleDir = LocaleDir;
147  textdomain("vdr");
148  bindtextdomain("vdr", I18nLocaleDir);
149  cFileNameList Locales(I18nLocaleDir, true);
150  if (Locales.Size() > 0) {
151  char *OldLocale = strdup(setlocale(LC_MESSAGES, NULL));
152  for (int i = 0; i < Locales.Size(); i++) {
153  cString FileName = cString::sprintf("%s/%s/LC_MESSAGES/vdr.mo", *I18nLocaleDir, Locales[i]);
154  if (access(FileName, F_OK) == 0) { // found a locale with VDR texts
155  if (NumLocales < I18N_MAX_LANGUAGES - 1) {
156  SetEnvLanguage(Locales[i]);
157  const char *TranslatedLanguageName = gettext(LanguageName);
158  if (TranslatedLanguageName != LanguageName) {
159  NumLocales++;
160  if (strstr(OldLocale, Locales[i]) == OldLocale)
162  LanguageLocales.Append(strdup(Locales[i]));
163  LanguageNames.Append(strdup(TranslatedLanguageName));
164  const char *Code = gettext(LanguageCode);
165  for (const char **lc = LanguageCodeList; *lc; lc++) {
166  if (ContainsCode(*lc, Code)) {
167  Code = *lc;
168  break;
169  }
170  }
171  LanguageCodes.Append(strdup(Code));
172  }
173  }
174  else {
175  esyslog("ERROR: too many locales - increase I18N_MAX_LANGUAGES!");
176  break;
177  }
178  }
179  }
181  free(OldLocale);
182  dsyslog("found %d locales in %s", NumLocales - 1, *I18nLocaleDir);
183  }
184  // Prepare any known language codes for which there was no locale:
186  for (const char **lc = LanguageCodeList; *lc; lc++) {
187  bool Found = false;
188  for (int i = 0; i < LanguageCodes.Size(); i++) {
189  if (strcmp(*lc, LanguageCodes[i]) == 0) {
190  Found = true;
191  break;
192  }
193  }
194  if (!Found) {
195  dsyslog("no locale for language code '%s'", *lc);
196  NumLanguages++;
198  LanguageNames.Append(strdup(*lc));
199  LanguageCodes.Append(strdup(*lc));
200  }
201  }
202  // Add special language codes and names:
203  for (const struct tSpecialLc *slc = SpecialLanguageCodeList; slc->Code; slc++) {
204  const char *TranslatedName = gettext(slc->Name);
205  LanguageNames.Append(strdup( TranslatedName != slc->Name ? TranslatedName : SkipContext(slc->Name)));
206  LanguageCodes.Append(strdup(slc->Code));
207  }
208 }
209 
210 void I18nRegister(const char *Plugin)
211 {
212  cString Domain = cString::sprintf("vdr-%s", Plugin);
213  bindtextdomain(Domain, I18nLocaleDir);
214 }
215 
216 void I18nSetLocale(const char *Locale)
217 {
218  if (Locale && *Locale) {
219  int i = LanguageLocales.Find(Locale);
220  if (i >= 0) {
221  CurrentLanguage = i;
222  SetEnvLanguage(Locale);
224  }
225  else
226  dsyslog("unknown locale: '%s'", Locale);
227  }
228 }
229 
231 {
232  return CurrentLanguage;
233 }
234 
235 void I18nSetLanguage(int Language)
236 {
237  if (Language < NumLanguages) {
238  CurrentLanguage = Language;
240  }
241 }
242 
244 {
245  return NumLocales;
246 }
247 
249 {
250  return &LanguageNames;
251 }
252 
253 const char *I18nTranslate(const char *s, const char *Plugin)
254 {
255  if (!s)
256  return s;
257  if (CurrentLanguage) {
258  const char *t = Plugin ? dgettext(Plugin, s) : gettext(s);
259  if (t != s)
260  return t;
261  }
262  return SkipContext(s);
263 }
264 
265 const char *I18nLocale(int Language)
266 {
267  return 0 <= Language && Language < LanguageLocales.Size() ? LanguageLocales[Language] : NULL;
268 }
269 
270 const char *I18nLanguageCode(int Language)
271 {
272  return 0 <= Language && Language < LanguageCodes.Size() ? LanguageCodes[Language] : NULL;
273 }
274 
275 int I18nLanguageIndex(const char *Code)
276 {
277  for (int i = 0; i < LanguageCodes.Size(); i++) {
279  return i;
280  }
281  //dsyslog("unknown language code: '%s'", Code);
282  return -1;
283 }
284 
285 const char *I18nNormalizeLanguageCode(const char *Code)
286 {
287  for (int i = 0; i < 3; i++) {
288  if (Code[i]) {
289  // ETSI EN 300 468 defines language codes as consisting of three letters
290  // according to ISO 639-2. This means that they are supposed to always consist
291  // of exactly three letters in the range a-z - no digits, UTF-8 or other
292  // funny characters. However, some broadcasters apparently don't have a
293  // copy of the DVB standard (or they do, but are perhaps unable to read it),
294  // so they put all sorts of non-standard stuff into the language codes,
295  // like nonsense as "2ch" or "A 1" (yes, they even go as far as using
296  // blanks!). Such things should go into the description of the EPG event's
297  // ComponentDescriptor.
298  // So, as a workaround for this broadcaster stupidity, let's ignore
299  // language codes with unprintable characters...
300  if (!isprint(Code[i])) {
301  //dsyslog("invalid language code: '%s'", Code);
302  return "???";
303  }
304  // ...and replace blanks with underlines (ok, this breaks the 'const'
305  // of the Code parameter - but hey, it's them who started this):
306  if (Code[i] == ' ')
307  *((char *)&Code[i]) = '_';
308  }
309  else
310  break;
311  }
312  int n = I18nLanguageIndex(Code);
313  return n >= 0 ? I18nLanguageCode(n) : Code;
314 }
315 
316 bool I18nIsPreferredLanguage(int *PreferredLanguages, const char *LanguageCode, int &OldPreference, int *Position)
317 {
318  int pos = 1;
319  bool found = false;
320  while (LanguageCode) {
321  int LanguageIndex = I18nLanguageIndex(LanguageCode);
322  for (int i = 0; i < LanguageCodes.Size(); i++) {
323  if (PreferredLanguages[i] < 0)
324  break; // the language is not a preferred one
325  if (PreferredLanguages[i] == LanguageIndex) {
326  if (OldPreference < 0 || i < OldPreference) {
327  OldPreference = i;
328  if (Position)
329  *Position = pos;
330  found = true;
331  break;
332  }
333  }
334  }
335  if ((LanguageCode = strchr(LanguageCode, '+')) != NULL) {
336  LanguageCode++;
337  pos++;
338  }
339  else if (pos == 1 && Position)
340  *Position = 0;
341  }
342  if (OldPreference < 0) {
343  OldPreference = LanguageCodes.Size(); // higher than the maximum possible value
344  return true; // if we don't find a preferred one, we take the first one
345  }
346  return found;
347 }
int Find(const char *s) const
Definition: tools.c:1584
Definition: tools.h:178
static cString sprintf(const char *fmt,...) __attribute__((format(printf
Definition: tools.c:1149
int Size(void) const
Definition: tools.h:764
virtual void Append(T Data)
Definition: tools.h:784
void I18nInitialize(const char *LocaleDir)
Detects all available locales and loads the language names and codes.
Definition: i18n.c:141
static void SetLanguageNames(void)
Definition: i18n.c:130
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:270
static const char * SkipContext(const char *s)
Definition: i18n.c:117
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:316
static void SetEnvLanguage(const char *Locale)
Definition: i18n.c:123
static cStringList LanguageCodes
Definition: i18n.c:96
int I18nLanguageIndex(const char *Code)
Returns the index of the language with the given three letter language Code.
Definition: i18n.c:275
static int NumLocales
Definition: i18n.c:98
const cStringList * I18nLanguages(void)
Returns the list of available languages.
Definition: i18n.c:248
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:243
static cStringList LanguageLocales
Definition: i18n.c:94
int I18nCurrentLanguage(void)
Returns the index of the current language.
Definition: i18n.c:230
const char * I18nLocale(int Language)
Returns the locale code of the given Language (which is an index as returned by I18nCurrentLanguage()...
Definition: i18n.c:265
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:253
const struct tSpecialLc SpecialLanguageCodeList[]
Definition: i18n.c:81
static cString I18nLocaleDir
Definition: i18n.c:92
static int NumLanguages
Definition: i18n.c:99
static cStringList LanguageNames
Definition: i18n.c:95
static bool ContainsCode(const char *Codes, const char *Code)
Definition: i18n.c:102
void I18nSetLocale(const char *Locale)
Sets the current locale to Locale.
Definition: i18n.c:216
void I18nRegister(const char *Plugin)
Registers the named plugin, so that it can use internationalized texts.
Definition: i18n.c:210
const char * I18nNormalizeLanguageCode(const char *Code)
Returns a 3 letter language code that may not be zero terminated.
Definition: i18n.c:285
const char * LanguageCodeList[]
Definition: i18n.c:37
void I18nSetLanguage(int Language)
Sets the current language index to Language.
Definition: i18n.c:235
static int CurrentLanguage
Definition: i18n.c:100
#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