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