1 /*
2 * tvheadend, internationalization (locale)
3 * Copyright (C) 2015 Jaroslav Kysela
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <pthread.h>
23 #include "pthread.h"
24 #include "tvh_locale.h"
25 #include "tvh_string.h"
26 #include "redblack.h"
27
28 struct tvh_locale {
29 const char *lang;
30 const char **strings;
31 };
32
33 #include "tvh_locale_inc.c"
34
35 #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
36
37 pthread_mutex_t tvh_gettext_mutex = PTHREAD_MUTEX_INITIALIZER;
38
39 /*
40 *
41 */
42
43 struct msg {
44 RB_ENTRY(msg) link;
45 const char *src;
46 const char *dst;
47 };
48
49 struct lng {
50 RB_ENTRY(lng) link;
51 const char *tvh_lang;
52 const char *locale_lang;
53 int msgs_initialized;
54 RB_HEAD(, msg) msgs;
55 };
56
57 static RB_HEAD(, lng) lngs;
58
59 static struct lng *lng_default = NULL;
60 static struct lng *lng_last = NULL;
61
62 /*
63 * Message RB tree
64 */
65
msg_cmp(const struct msg * a,const struct msg * b)66 static inline int msg_cmp(const struct msg *a, const struct msg *b)
67 {
68 return strcmp(a->src, b->src);
69 }
70
msg_add_strings(struct lng * lng,const char ** strings)71 static void msg_add_strings(struct lng *lng, const char **strings)
72 {
73 struct msg *m;
74 const char **p;
75
76 for (p = strings; *p; p += 2) {
77 m = calloc(1, sizeof(*m));
78 m->src = p[0];
79 m->dst = p[1];
80 if (RB_INSERT_SORTED(&lng->msgs, m, link, msg_cmp))
81 abort();
82 }
83 }
84
msg_find(struct lng * lng,const char * msg)85 static inline const char *msg_find(struct lng *lng, const char *msg)
86 {
87 struct msg *m, ms;
88
89 ms.src = msg;
90 m = RB_FIND(&lng->msgs, &ms, link, msg_cmp);
91 if (m)
92 return m->dst;
93 return msg;
94 }
95
96 /*
97 * Language RB tree
98 */
99
lng_cmp(const struct lng * a,const struct lng * b)100 static inline int lng_cmp(const struct lng *a, const struct lng *b)
101 {
102 return strcmp(a->tvh_lang, b->tvh_lang);
103 }
104
lng_add(const char * tvh_lang,const char * locale_lang)105 static struct lng *lng_add(const char *tvh_lang, const char *locale_lang)
106 {
107 struct lng *l = calloc(1, sizeof(*l));
108 l->tvh_lang = tvh_lang;
109 l->locale_lang = locale_lang;
110 if (RB_INSERT_SORTED(&lngs, l, link, lng_cmp))
111 abort();
112 return l;
113 }
114
lng_init(struct lng * l)115 static void lng_init(struct lng *l)
116 {
117 struct tvh_locale *tl;
118 int i;
119
120 l->msgs_initialized = 1;
121 for (i = 0, tl = tvh_locales; i < ARRAY_SIZE(tvh_locales); i++, tl++)
122 if (strcmp(tl->lang, l->locale_lang) == 0) {
123 msg_add_strings(l, tl->strings);
124 break;
125 }
126 }
127
lng_get(const char * tvh_lang)128 static struct lng *lng_get(const char *tvh_lang)
129 {
130 struct lng *l, ls;
131 char *s;
132
133 if (tvh_lang != NULL && tvh_lang[0] != '\0') {
134 s = alloca(strlen(tvh_lang) + 1);
135 ls.tvh_lang = s;
136 for ( ; *tvh_lang && *tvh_lang != ','; s++, tvh_lang++)
137 *s = *tvh_lang;
138 *s = '\0';
139 l = RB_FIND(&lngs, &ls, link, lng_cmp);
140 if (l) {
141 if (!l->msgs_initialized)
142 lng_init(l);
143 return l;
144 }
145 }
146 return lng_get("eng");
147 }
148
lng_get_locale(char * locale_lang)149 static struct lng *lng_get_locale(char *locale_lang)
150 {
151 struct lng *l;
152
153 if (locale_lang != NULL && locale_lang[0] != '\0') {
154 RB_FOREACH(l, &lngs, link)
155 if (!strcmp(l->locale_lang, locale_lang)) {
156 if (!l->msgs_initialized)
157 lng_init(l);
158 return l;
159 }
160 }
161 return lng_get("eng");
162 }
163
164 /*
165 *
166 */
tvh_gettext_langcode_valid(const char * code)167 int tvh_gettext_langcode_valid(const char *code)
168 {
169 struct lng ls;
170 int ret;
171
172 pthread_mutex_lock(&tvh_gettext_mutex);
173 ls.tvh_lang = code;
174 ret = RB_FIND(&lngs, &ls, link, lng_cmp) != NULL;
175 pthread_mutex_unlock(&tvh_gettext_mutex);
176 return ret;
177 }
178
179 /*
180 *
181 */
182
tvh_gettext_get_lang(const char * lang)183 const char *tvh_gettext_get_lang(const char *lang)
184 {
185 struct lng *l = lng_get(lang);
186 return l->locale_lang;
187 }
188
tvh_gettext_default_init(void)189 static void tvh_gettext_default_init(void)
190 {
191 static char dflt[16];
192 char *p;
193
194 p = getenv("LC_ALL");
195 if (p == NULL)
196 p = getenv("LANG");
197 if (p == NULL)
198 p = getenv("LANGUAGE");
199 if (p == NULL)
200 p = (char *)"en_US";
201
202 strlcpy(dflt, p, sizeof(dflt));
203 for (p = dflt; *p && *p != '.'; p++);
204 if (*p == '.') *p = '\0';
205
206 if ((lng_default = lng_get_locale(dflt)) != NULL)
207 return;
208
209 for (p = dflt; *p && *p != '_'; p++);
210 if (*p == '_') *p = '\0';
211
212 if ((lng_default = lng_get_locale(dflt)) != NULL)
213 return;
214
215 lng_default = lng_add(dflt, dflt);
216 if (!lng_default)
217 return;
218 }
219
tvh_gettext_lang(const char * lang,const char * s)220 const char *tvh_gettext_lang(const char *lang, const char *s)
221 {
222 pthread_mutex_lock(&tvh_gettext_mutex);
223 if (lang == NULL) {
224 s = msg_find(lng_default, s);
225 } else {
226 if (!strcmp(lng_last->locale_lang, lang)) {
227 s = msg_find(lng_last, s);
228 } else {
229 if ((lng_last = lng_get(lang)) == NULL)
230 s = msg_find(lng_default, s);
231 else
232 s = msg_find(lng_last, s);
233 }
234 }
235 pthread_mutex_unlock(&tvh_gettext_mutex);
236 return s;
237 }
238
239 /*
240 *
241 */
242
tvh_gettext_init(void)243 void tvh_gettext_init(void)
244 {
245 static const char *tbl[] = {
246 "ach", "ach",
247 "ady", "ady",
248 "ara", "ar",
249 "bul", "bg",
250 "cze", "cs",
251 "dan", "da",
252 "ger", "de",
253 "eng", "en_US",
254 "eng_GB", "en_GB",
255 "eng_US", "en_US",
256 "spa", "es",
257 "est", "et",
258 "per", "fa",
259 "fin", "fi",
260 "fre", "fr",
261 "heb", "he",
262 "hrv", "hr",
263 "hun", "hu",
264 "ita", "it",
265 "kor", "ko",
266 "lav", "lv",
267 "lit", "lt",
268 "dut", "nl",
269 "nor", "no",
270 "pol", "pl",
271 "por", "pt",
272 "rum", "ro",
273 "rus", "ru",
274 "slv", "sl",
275 "slo", "sk",
276 "srp", "sr",
277 "alb", "sq",
278 "swe", "sv",
279 "tur", "tr",
280 "ukr", "uk",
281 "chi", "zh",
282 "chi_CN", "zh-Hans",
283 NULL, NULL
284 };
285 const char **p;
286 for (p = tbl; *p; p += 2)
287 lng_add(p[0], p[1]);
288 tvh_gettext_default_init();
289 lng_last = lng_default;
290 }
291
tvh_gettext_done(void)292 void tvh_gettext_done(void)
293 {
294 struct lng *l;
295 struct msg *m;
296
297 while ((l = RB_FIRST(&lngs)) != NULL) {
298 while ((m = RB_FIRST(&l->msgs)) != NULL) {
299 RB_REMOVE(&l->msgs, m, link);
300 free(m);
301 }
302 RB_REMOVE(&lngs, l, link);
303 free(l);
304 }
305 }
306