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