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