Scan for language files during bootstrap instead of using a hard-coded list.
[citadel.git] / webcit / gettext.c
1 /*
2  * Copyright (c) 1996-2012 by the citadel.org team
3  *
4  * This program is open source software.  You can redistribute it and/or
5  * modify it under the terms of the GNU General Public License version 3.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  */
12
13 #include "webcit.h"
14 #include "webserver.h"
15 #define SEARCH_LANG 20          /* how many langs should we parse? */
16
17 #ifdef ENABLE_NLS
18 #include "language_list.h"
19
20 const char **AvailLangLoaded;
21 long nLocalesLoaded = 0;
22
23 #ifdef HAVE_USELOCALE
24 locale_t *wc_locales; /* here we keep the parsed stuff */
25 #endif
26
27 /* Keep information about one locale */
28 typedef struct _lang_pref{
29         char lang[16];          /* the language locale string */
30         char region[16];        /* the region locale string */
31         long priority;          /* which priority does it have */
32         int availability;       /* do we know it? */
33         int selectedlang;       /* is this the selected language? */
34 } LangStruct;
35
36 /* parse browser locale header 
37  *
38  * seems as most browsers just do a one after comma value even if more than 10 locales are available. Sample strings:
39  * opera: 
40  * Accept-Language: sq;q=1.0,de;q=0.9,as;q=0.8,ar;q=0.7,bn;q=0.6,zh-cn;q=0.5,kn;q=0.4,ch;q=0.3,fo;q=0.2,gn;q=0.1,ce;q=0.1,ie;q=0.1 
41  * Firefox 
42  * Accept-Language: 'de-de,en-us;q=0.7,en;q=0.3' 
43  * Accept-Language: de,en-ph;q=0.8,en-us;q=0.5,de-at;q=0.3 
44  * Accept-Language: de,en-us;q=0.9,it;q=0.9,de-de;q=0.8,en-ph;q=0.7,de-at;q=0.7,zh-cn;q=0.6,cy;q=0.5,ar-om;q=0.5,en-tt;q=0.4,xh;q=0.3,nl-be;q=0.3,cs;q=0.2,sv;q=0.1,tk;q=0.1 
45  */
46
47 void httplang_to_locale(StrBuf *LocaleString, wcsession *sess)
48 {
49         LangStruct wanted_locales[SEARCH_LANG];
50         LangStruct *ls;
51
52         int i = 0;
53         int j = 0;
54         /* size_t len = strlen(LocaleString); */
55         long prio;
56         int av;
57         int nBest;
58         int nParts;
59         StrBuf *Buf = NULL;
60         StrBuf *SBuf = NULL;
61
62         nParts = StrBufNum_tokens(LocaleString, ',');
63         for (i=0; ((i<nParts) && (i < SEARCH_LANG)); i++)
64         {
65                 char lbuf[32];
66                 int blen;
67                         
68                 if (Buf == NULL) {
69                         Buf = NewStrBuf();
70                         SBuf = NewStrBuf();
71                 }
72                 else {
73                         FlushStrBuf(Buf);
74                         FlushStrBuf(SBuf);
75                 }
76
77                 ls = &wanted_locales[i];
78
79                 StrBufExtract_token(Buf, LocaleString, i, ',');
80                 /* we are searching, if this list item has something like ;q=n*/
81                 if (StrBufNum_tokens(Buf, '=') > 1) {
82                         int sbuflen, k;
83                         StrBufExtract_token(SBuf, Buf, 1, '=');
84                         sbuflen = StrLength(SBuf);
85                         for (k = 0; k < sbuflen; k++) {
86                                 if (ChrPtr(SBuf)[k] == '.') {
87                                         StrBufPeek(SBuf, NULL, k, '0');
88                                 }
89                         }
90                         ls->priority = StrTol(SBuf);
91                 }
92                 else {
93                         ls->priority = 1000;
94                 }
95
96                 /* get the locale part */
97                 StrBufExtract_token(SBuf, Buf, 0, ';');
98
99                 /* get the lang part, which should be allways there */
100                 extract_token(&ls->lang[0], 
101                               ChrPtr(SBuf), 
102                               0, '-', 
103                               sizeof(ls->lang));
104
105                 /* get the area code if any. */
106                 if (StrBufNum_tokens(SBuf, '-') > 1) {
107                         extract_token(&ls->region[0], 
108                                       ChrPtr(SBuf), 
109                                       1, '-', 
110                                       sizeof(ls->region)
111                         );
112                 }
113                 else { /* no ara code? use lang code */
114                         blen=strlen(&ls->lang[0]);
115                         memcpy(&ls->region[0], ls->lang, blen);
116                         ls->region[blen] = '\0';
117                 }
118
119                 /* area codes are uppercase */
120                 blen = strlen(&ls->region[0]);
121                 for (j = 0; j < blen; j++)
122                 {
123                         int chars;
124                         chars = toupper(ls->region[j]);
125                         ls->region[j] = (char)chars; /* todo ? */
126                 }
127                 snprintf(&lbuf[0], 
128                          sizeof(lbuf), 
129                          "%s_%s", 
130                          &ls->lang[0], 
131                          &ls->region[0]);
132                         
133                 /* check if we have this lang */
134                 ls->availability = 1;
135                 ls->selectedlang = -1;
136                 for (j = 0; j < nLocalesLoaded; j++) {
137                         int result;
138                         /* match against the LANG part */
139                         result = strcasecmp(&ls->lang[0], AvailLangLoaded[j]);
140                         if ((result < 0) && (result < ls->availability)){
141                                 ls->availability = result;
142                                 ls->selectedlang = j;
143                         }
144                         /* match against lang and locale */
145                         if (0 == strcasecmp(&lbuf[0], AvailLangLoaded[j])){
146                                 ls->availability = 0;
147                                 ls->selectedlang = j;
148                                 j = nLocalesLoaded;
149                         }
150                 }
151         }
152         
153         prio = 0;
154         av = -1000;
155         nBest = -1;
156         for (i = 0; ((i < nParts) && (i<SEARCH_LANG)); i++) {
157                 ls = &wanted_locales[i];
158                 if (    (ls->availability <= 0)
159                         && (av < ls->availability)
160                         && (prio < ls->priority)
161                         && (ls->selectedlang != -1)
162                 ) {
163                         nBest = ls->selectedlang;
164                         av = ls->availability;
165                         prio = ls->priority;
166                 }
167         }
168         if (nBest == -1) {
169                 /* fall back to C */
170                 nBest=0;
171         }
172         sess->selected_language = nBest;
173         syslog(9, "language found: %s", AvailLangLoaded[WC->selected_language]);
174         FreeStrBuf(&Buf);
175         FreeStrBuf(&SBuf);
176 }
177
178
179 /*
180  * show the language chooser on the login dialog
181  * depending on the browser locale change the sequence of the 
182  * language chooser.
183  */
184 void tmplput_offer_languages(StrBuf *Target, WCTemplputParams *TP)
185 {
186 #ifdef HAVE_USELOCALE
187         int i;
188
189         wc_printf("<select name=\"language\" id=\"lname\" size=\"1\" onChange=\"switch_to_lang($('lname').value);\">\n");
190
191         for (i=0; i < nLocalesLoaded; ++i) {
192                 wc_printf("<option %s value=%s>%s</option>\n",
193                         ((WC->selected_language == i) ? "selected" : ""),
194                         AvailLangLoaded[i],
195                         AvailLangLoaded[i]
196                 );
197         }
198
199         wc_printf("</select>\n");
200 #else
201         wc_printf("%s", (getenv("LANG") ? getenv("LANG") : "C"));
202 #endif
203 }
204
205 /*
206  * Set the selected language for this session.
207  */
208 void set_selected_language(const char *lang) {
209 #ifdef HAVE_USELOCALE
210         int i;
211         for (i = 0; i<nLocalesLoaded; ++i) {
212                 if (!strcasecmp(lang, AvailLangLoaded[i])) {
213                         WC->selected_language = i;
214                         break;
215                 }
216         }
217 #endif
218 }
219
220 /*
221  * Activate the selected language for this session.
222  */
223 void go_selected_language(void) {
224 #ifdef HAVE_USELOCALE
225         wcsession *WCC = WC;
226         if (WCC->selected_language < 0) return;
227         uselocale(wc_locales[WCC->selected_language]);  /* switch locales */
228         textdomain(textdomain(NULL));                   /* clear the cache */
229 #else
230         char *language;
231         
232         language = getenv("LANG");
233         setlocale(LC_MESSAGES, language);
234 #endif
235 }
236
237 /*
238  * Deactivate the selected language for this session.
239  */
240 void stop_selected_language(void) {
241 #ifdef HAVE_USELOCALE
242         uselocale(LC_GLOBAL_LOCALE);                    /* switch locales */
243         textdomain(textdomain(NULL));                   /* clear the cache */
244 #endif
245 }
246
247
248 /*
249  * Create a locale_t for each available language
250  */
251 void initialize_locales(void) {
252         int nLocales;
253         int i;
254         char *language = NULL;
255
256 #ifdef ENABLE_NLS
257         setlocale(LC_ALL, "");
258         syslog(9, "Text domain: %s", textdomain("webcit"));
259         syslog(9, "Message catalog directory: %s", bindtextdomain(textdomain(NULL), LOCALEDIR"/locale"));
260         syslog(9, "Text domain Charset: %s", bind_textdomain_codeset("webcit","UTF8"));
261 #endif
262
263         nLocales = 0; 
264         while (!IsEmptyStr(AvailLang[nLocales]))
265                 nLocales++;
266
267         language = getenv("WEBCIT_LANG");
268         if ((language) && (!IsEmptyStr(language)) && (strcmp(language, "UNLIMITED") != 0)) {
269                 syslog(9, "Nailing locale to %s", language);
270         }
271         else language = NULL;
272
273         AvailLangLoaded = malloc (sizeof(char*) * nLocales);
274         memset(AvailLangLoaded, 0, sizeof(char*) * nLocales);
275 #ifdef HAVE_USELOCALE
276         wc_locales = malloc (sizeof(locale_t) * nLocales);
277         memset(wc_locales,0, sizeof(locale_t) * nLocales);
278         wc_locales[0] = newlocale(LC_ALL_MASK, NULL, NULL);
279 #endif
280
281         for (i = 1; i < nLocales; ++i) {
282 #ifdef HAVE_USELOCALE
283                 wc_locales[nLocalesLoaded] = newlocale(
284                         (LC_MESSAGES_MASK|LC_TIME_MASK),
285                         AvailLang[i],
286                         wc_locales[0]
287                 );
288                 if (wc_locales[nLocalesLoaded] == NULL) {
289                         syslog(1, "locale for %s disabled: %s", AvailLang[i], strerror(errno));
290                 }
291                 else {
292                         syslog(3, "Found locale: %s", AvailLang[i]);
293                         AvailLangLoaded[nLocalesLoaded] = AvailLang[i];
294                         nLocalesLoaded++;
295                 }
296 #else
297                 if ((language != NULL) && (strcmp(language, AvailLang[i]) == 0)) {
298                         setenv("LANG", AvailLang[i], 1);
299                         AvailLangLoaded[nLocalesLoaded] = AvailLang[i];
300                         setlocale(LC_MESSAGES, AvailLang[i]);
301                         nLocalesLoaded++;
302                 }
303                 else if (nLocalesLoaded == 0) {
304                         setenv("LANG", AvailLang[i], 1);
305                         AvailLangLoaded[nLocalesLoaded] = AvailLang[i];
306                         nLocalesLoaded++;
307                 }
308 #endif
309         }
310         if ((language != NULL) && (nLocalesLoaded == 0)) {
311                 syslog(1, "Your selected locale [%s] isn't available on your system. falling back to C", language);
312 #ifndef HAVE_USELOCALE
313                 setlocale(LC_MESSAGES, AvailLang[0]);
314                 setenv("LANG", AvailLang[0], 1);
315 #endif
316                 AvailLangLoaded[0] = AvailLang[0];
317                 nLocalesLoaded = 1;
318         }
319 }
320
321
322 void 
323 ServerShutdownModule_GETTEXT
324 (void)
325 {
326 #ifdef HAVE_USELOCALE
327         int i;
328         for (i = 0; i < nLocalesLoaded; ++i) {
329                 freelocale(wc_locales[i]);
330         }
331         free(wc_locales);
332 #endif
333         free(AvailLangLoaded);
334 }
335
336 #else   /* ENABLE_NLS */
337 const char *AvailLang[] = {
338         "C",
339         ""
340 };
341
342 /* dummy for non NLS enabled systems */
343 void tmplput_offer_languages(StrBuf *Target, WCTemplputParams *TP)
344 {
345         wc_printf("English (US)");
346 }
347
348 /* dummy for non NLS enabled systems */
349 void set_selected_language(const char *lang) {
350 }
351
352 /* dummy for non NLS enabled systems */
353 void go_selected_language(void) {
354 }
355
356 /* dummy for non NLS enabled systems */
357 void stop_selected_language(void) {
358 }
359
360 void initialize_locales(void) {
361 }
362
363 #endif  /* ENABLE_NLS */
364
365
366 void TmplGettext(StrBuf *Target, WCTemplputParams *TP)
367 {
368         StrBufAppendBufPlain(Target, _(TP->Tokens->Params[0]->Start), -1, 0);
369 }
370
371
372 /*
373  * Returns the language currently in use.
374  * This function returns a static string, so don't do anything stupid please.
375  */
376 const char *get_selected_language(void) {
377 #ifdef ENABLE_NLS
378 #ifdef HAVE_USELOCALE
379         return AvailLang[WC->selected_language];
380 #else
381         return "en";
382 #endif
383 #else
384         return "en";
385 #endif
386 }
387
388
389 void Header_HandleAcceptLanguage(StrBuf *Line, ParsedHttpHdrs *hdr)
390 {
391         hdr->HR.browser_language = Line;
392 }
393
394
395 void 
396 InitModule_GETTEXT
397 (void)
398 {
399         initialize_locales();
400         
401         RegisterHeaderHandler(HKEY("ACCEPT-LANGUAGE"), 
402                               Header_HandleAcceptLanguage);
403                               
404         RegisterNamespace("LANG:SELECT", 0, 0, 
405                           tmplput_offer_languages, NULL, CTX_NONE);
406 }
407
408
409 void
410 SessionNewModule_GETTEXT
411 (wcsession *sess)
412 {
413 #ifdef ENABLE_NLS
414         if (    (sess != NULL)
415                 && (!sess->Hdr->HR.Static)
416                 && (sess->Hdr->HR.browser_language != NULL)
417         ) {
418                 httplang_to_locale(sess->Hdr->HR.browser_language, sess);
419         }
420 #endif
421 }
422
423 void
424 SessionAttachModule_GETTEXT
425 (wcsession *sess)
426 {
427 #ifdef ENABLE_NLS
428         go_selected_language();                                 /* set locale */
429 #endif
430 }
431
432 void 
433 SessionDestroyModule_GETTEXT
434 (wcsession *sess)
435 {
436 #ifdef ENABLE_NLS
437         stop_selected_language();                               /* unset locale */
438 #endif
439 }