vdr 2.7.5
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
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 { "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
99static int NumLocales = 1;
100static int NumLanguages = 1;
101static int CurrentLanguage = 0;
102
103static 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
118static const char *SkipContext(const char *s)
119{
120 const char *p = strchr(s, '$');
121 return p ? p + 1 : s;
122}
123
124static 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
131static 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
142void I18nInitialize(const char *LocaleDir)
143{
144 I18nLocaleDir = LocaleDir;
145 LanguageLocales.Append(strdup(I18N_DEFAULT_LOCALE));
146 LanguageNames.Append(strdup(SkipContext(LanguageName)));
147 LanguageCodes.Append(strdup(LanguageCodeList[0]));
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++;
198 LanguageLocales.Append(strdup(I18N_DEFAULT_LOCALE));
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
211void I18nRegister(const char *Plugin)
212{
213 cString Domain = cString::sprintf("vdr-%s", Plugin);
214 bindtextdomain(Domain, I18nLocaleDir);
215}
216
217void 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
236void 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
254const 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
266const char *I18nLocale(int Language)
267{
268 return 0 <= Language && Language < LanguageLocales.Size() ? LanguageLocales[Language] : NULL;
269}
270
271const char *I18nLanguageCode(int Language)
272{
273 return 0 <= Language && Language < LanguageCodes.Size() ? LanguageCodes[Language] : NULL;
274}
275
276int 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
286const 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
317bool 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}
static cString sprintf(const char *fmt,...) __attribute__((format(printf
Definition tools.c:1195
int Size(void) const
Definition tools.h:754
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 * I18nLocale(int Language)
Returns the locale code of the given Language (which is an index as returned by I18nCurrentLanguage()...
Definition i18n.c:266
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
const cStringList * I18nLanguages(void)
Returns the list of available languages.
Definition i18n.c:249
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 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 * 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 char * I18nNormalizeLanguageCode(const char *Code)
Returns a 3 letter language code that may not be zero terminated.
Definition i18n.c:286
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
static const char * SkipContext(const char *s)
Definition i18n.c:118
void I18nRegister(const char *Plugin)
Registers the named plugin, so that it can use internationalized texts.
Definition i18n.c:211
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
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
#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