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