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