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
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
37const 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
80struct tSpecialLc { const char *Code; const char *Name; };
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
98static int NumLocales = 1;
99static int NumLanguages = 1;
100static int CurrentLanguage = 0;
101
102static 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
117static const char *SkipContext(const char *s)
118{
119 const char *p = strchr(s, '$');
120 return p ? p + 1 : s;
121}
122
123static 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
130static 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
141void 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
210void I18nRegister(const char *Plugin)
211{
212 cString Domain = cString::sprintf("vdr-%s", Plugin);
213 bindtextdomain(Domain, I18nLocaleDir);
214}
215
216void 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
235void 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
253const 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
265const char *I18nLocale(int Language)
266{
267 return 0 <= Language && Language < LanguageLocales.Size() ? LanguageLocales[Language] : NULL;
268}
269
270const char *I18nLanguageCode(int Language)
271{
272 return 0 <= Language && Language < LanguageCodes.Size() ? LanguageCodes[Language] : NULL;
273}
274
275int 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
285const 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
316bool 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
static cString sprintf(const char *fmt,...) __attribute__((format(printf
Definition: tools.c:1149
int Size(void) const
virtual void Append(T Data)
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 * I18nLocale(int Language)
Returns the locale code of the given Language (which is an index as returned by I18nCurrentLanguage()...
Definition: i18n.c:265
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
const cStringList * I18nLanguages(void)
Returns the list of available languages.
Definition: i18n.c:248
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 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 * 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 char * I18nNormalizeLanguageCode(const char *Code)
Returns a 3 letter language code that may not be zero terminated.
Definition: i18n.c:285
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
static const char * SkipContext(const char *s)
Definition: i18n.c:117
void I18nRegister(const char *Plugin)
Registers the named plugin, so that it can use internationalized texts.
Definition: i18n.c:210
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
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
#define I18N_MAX_LANGUAGES
Definition: i18n.h:18
#define I18N_DEFAULT_LOCALE
Definition: i18n.h:16
#define trNOOP(s)
Definition: i18n.h:88
#define dsyslog(a...)
#define esyslog(a...)
const char * Code
Definition: i18n.c:80
const char * Name
Definition: i18n.c:80