1 /*
2    Copyright (c) 2001-2002 Perry Rapp
3    "The MIT license"
4    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
7 */
8 /*=============================================================
9  * locales.c -- functions dealing with locales
10  * TODO: this is a mess of ifdefs -- please clean up at some point
11  *==============================================================*/
12 
13 #include "llstdlib.h"
14 #ifdef HAVE_LOCALE_H
15 # include <locale.h>
16 #if defined(_WIN32) && !defined(__CYGWIN__)
17 # include "isolangs.h"
18 #endif
19 #endif
20 #ifdef HAVE_LANGINFO_CODESET
21 # include <langinfo.h>
22 #else
23 # include "langinfz.h"
24 #endif
25 #include "translat.h"
26 #include "liflines.h"
27 #include "feedback.h"
28 #include "icvt.h"
29 #include "lloptions.h"
30 #include "date.h"
31 #include "gedcomi.h"
32 
33 /*********************************************
34  * local function prototypes
35  *********************************************/
36 
37 /* alphabetical */
38 static void customlocale(STRING prefix);
39 static STRING get_current_locale(INT category);
40 #ifdef ENABLE_NLS
41 static BOOLEAN is_msgcategory(int category);
42 #if ! ( defined(HAVE_SETLOCALE) && defined(HAVE_LC_MESSAGES) )
43 static STRING llsetenv(STRING name, STRING value);
44 #endif /* ! (defined(HAVE_SETLOCALE) && defined(HAVE_LC_MESSAGES) ) */
45 #endif /* ENABLE_NLS */
46 static void notify_gettext_language_changed(void);
47 static void send_uilang_callbacks(void);
48 #ifdef ENABLE_NLS
49 static STRING setmsgs(STRING localename);
50 #endif /* ENABLE_NLS */
51 static char * win32_setlocale(int category, char * locale);
52 
53 /*********************************************
54  * local variables
55  *********************************************/
56 
57 static STRING  deflocale_coll = NULL;
58 static STRING  deflocale_msgs = NULL;
59 static BOOLEAN customized_loc = FALSE;
60 #ifdef ENABLE_NLS
61 static BOOLEAN customized_msgs = FALSE;
62 #endif /* ENABLE_NLS */
63 static STRING current_coll = NULL; /* most recent */
64 static STRING current_msgs = NULL; /* most recent */
65 static STRING rptlocalestr = NULL; /* if set by report program */
66 static LIST f_uicodeset_callbacks = NULL; /* collection of callbacks for UI codeset changes */
67 static LIST f_uilang_callbacks = NULL; /* collection of callbacks for UI language changes */
68 
69 
70 
71 /*********************************************
72  * local & exported function definitions
73  * body of module
74  *********************************************/
75 
76 /*==========================================
77  * get_current_locale -- return current locale
78  *  returns "C" in case setlocale(x, 0) returns 0
79  * Created: 2002/02/24 (Perry Rapp)
80  *========================================*/
81 #ifdef HAVE_SETLOCALE
82 static STRING
get_current_locale(INT category)83 get_current_locale (INT category)
84 {
85 	STRING str = 0;
86 	str = setlocale(category, NULL);
87 	return str ? str : "C";
88 }
89 #endif /* HAVE_SETLOCALE */
90 /*==========================================
91  * save_original_locales -- grab current locales for later default
92  *  We need these for an obscure problem. If user sets only
93  *  locales for report, then when we switch back to GUI mode,
94  *  we shouldn't stay in the customized report locale.
95  * Created: 2002/02/24 (Perry Rapp)
96  *========================================*/
97 void
save_original_locales(void)98 save_original_locales (void)
99 {
100 	/* get collation locale, if available */
101 #ifdef HAVE_SETLOCALE
102 	deflocale_coll = strsave(get_current_locale(LC_COLLATE));
103 	current_coll = strsave(deflocale_coll);
104 #endif /* HAVE_SETLOCALE */
105 
106 	/* get messages locale (via locale or via environ.) */
107 #ifdef HAVE_SETLOCALE
108 #ifdef LC_MESSAGES
109 	if (LC_MESSAGES >= 0 && LC_MESSAGES != 1729) {
110 		/* 1729 is the gettext code when there wasn't any LC_MESSAGES */
111 		deflocale_msgs = strsave(get_current_locale(LC_MESSAGES));
112 		current_msgs = strsave(deflocale_msgs);
113 	}
114 #endif /* LC_MESSAGES */
115 #endif /* HAVE_SETLOCALE */
116 	/* fallback to the environment (see setmsgs) */
117 	if (!deflocale_msgs) {
118 		STRING msgs = getenv("LC_MESSAGES");
119 		deflocale_msgs = strsave(msgs ? msgs : "");
120 		current_msgs = strsave(deflocale_msgs);
121 	}
122 
123 }
124 /*==========================================
125  * get_original_locale_collate -- Get collation locale captured at startup
126  * caller may not alter string
127  * Created: 2002/06/15 (Perry Rapp)
128  *========================================*/
129 STRING
get_original_locale_collate(void)130 get_original_locale_collate (void)
131 {
132 #ifdef HAVE_SETLOCALE
133 	return deflocale_coll;
134 #endif /* HAVE_SETLOCALE */
135 	return "";
136 }
137 /*==========================================
138  * get_current_locale_collate -- Get collation locale (as last set)
139  * caller may not alter string
140  * Created: 2002/06/15 (Perry Rapp)
141  *========================================*/
142 STRING
get_current_locale_collate(void)143 get_current_locale_collate (void)
144 {
145 	return current_coll;
146 }
147 /*==========================================
148  * get_original_locale_msgs -- Get LC_MESSAGES locale captured at startup
149  * caller may not alter string
150  * Created: 2002/06/15 (Perry Rapp)
151  *========================================*/
152 STRING
get_original_locale_msgs(void)153 get_original_locale_msgs (void)
154 {
155 	return deflocale_msgs;
156 }
157 /*==========================================
158  * get_current_locale_msgs -- Get LC_MESSAGES locale (as last set)
159  * caller may not alter string
160  * Created: 2002/06/15 (Perry Rapp)
161  *========================================*/
162 STRING
get_current_locale_msgs(void)163 get_current_locale_msgs (void)
164 {
165 	return current_msgs;
166 }
167 /*==========================================
168  * are_locales_supported -- locale support compiled in ?
169  * Created: 2002/06/15 (Perry Rapp)
170  *========================================*/
171 BOOLEAN
are_locales_supported(void)172 are_locales_supported (void)
173 {
174 #ifdef HAVE_SETLOCALE
175 	return TRUE;
176 #endif /* HAVE_SETLOCALE */
177 	return FALSE;
178 }
179 /*==========================================
180  * is_nls_supported -- is NLS (National Language Support) compiled in ?
181  * Created: 2002/06/15 (Perry Rapp)
182  *========================================*/
183 BOOLEAN
is_nls_supported(void)184 is_nls_supported (void)
185 {
186 #ifdef ENABLE_NLS
187 	return TRUE;
188 #endif /* ENABLE_NLS */
189 	return FALSE;
190 }
191 /*==========================================
192  * is_iconv_supported -- is iconv (codeset conversion library) compiled in ?
193  * Created: 2002/06/15 (Perry Rapp)
194  *========================================*/
195 BOOLEAN
is_iconv_supported(void)196 is_iconv_supported (void)
197 {
198 #ifdef HAVE_ICONV
199 	return TRUE;
200 #endif /* HAVE_ICONV */
201 	return FALSE;
202 }
203 /*==========================================
204  * ll_langinfo -- wrapper for nl_langinfo
205  *  in case not provided (eg, MS-Windows)
206  *========================================*/
207 STRING
ll_langinfo(void)208 ll_langinfo (void)
209 {
210 	STRING str = nl_langinfo(CODESET);
211 	/* TODO: Should we apply norm_charmap.c ?
212 	http://www.cl.cam.ac.uk/~mgk25/ucs/norm_charmap.c
213 	*/
214 	/* TODO: In any case tho, Markus' nice replacement nl_langinfo gives the
215 	wrong default codepages for MS-Windows I think -- eg, should be 1252 for
216 	generic default instead of 8859-1 */
217 
218 	/* TODO: Check out libcharset (in the libiconv distribution)
219 	It probably has the Win32 code in it */
220 
221 	return str ? str : ""; /* I don't know if nl_langinfo ever returns NULL */
222 }
223 /*==========================================
224  * termlocale -- free locale related variables
225  * Created: 2002/02/24 (Perry Rapp)
226  *========================================*/
227 void
termlocale(void)228 termlocale (void)
229 {
230 	/* free & zero out globals */
231 	strfree(&deflocale_coll);
232 	strfree(&current_coll);
233 	strfree(&deflocale_msgs);
234 	strfree(&current_msgs);
235 }
236 /*==========================================
237  * uilocale -- set locale to GUI locale
238  *  (eg, for displaying a sorted list of people)
239  * Created: 2001/08/02 (Perry Rapp)
240  *========================================*/
241 void
uilocale(void)242 uilocale (void)
243 {
244 	update_textdomain_localedir(PACKAGE, "Ui");
245 
246 	customlocale("UiLocale");
247 }
248 /*==========================================
249  * rptlocale -- set locale to report locale
250  *  (eg, for _namesort)
251  * Created: 2001/08/02 (Perry Rapp)
252  *========================================*/
253 void
rptlocale(void)254 rptlocale (void)
255 {
256 	/* 2007-04-19, Perry:
257 	 I'm not sure what textdomain to use here
258 	 rptinfo has a textdomain (see llrpt_gettext)
259 	 but that is per-rptinfo
260 	*/
261 
262 	customlocale("RptLocale");
263 	if (rptlocalestr) /* report has specified locale */
264 		llsetlocale(LC_ALL, rptlocalestr);
265 }
266 /*==========================================
267  * rpt_setlocale -- set report locale to custom locale
268  *  used by report language
269  * Created: 2002/06/27 (Perry Rapp)
270  *========================================*/
271 STRING
rpt_setlocale(STRING str)272 rpt_setlocale (STRING str)
273 {
274 	strfree(&rptlocalestr);
275 	rptlocalestr = llsetlocale(LC_ALL, str);
276 	if (rptlocalestr)
277 		rptlocalestr = strsave(rptlocalestr);
278 	return rptlocalestr;
279 }
280 /*==========================================
281  * setmsgs -- set locale for LC_MESSAGES
282  * Returns non-null string if succeeds
283  *========================================*/
284 #ifdef ENABLE_NLS
285 static STRING
setmsgs(STRING localename)286 setmsgs (STRING localename)
287 {
288 	STRING str;
289 	if (eqstr_ex(current_msgs, localename))
290 		return localename; /* skip it if already current */
291 #if defined(HAVE_SETLOCALE) && defined(HAVE_LC_MESSAGES)
292 	str = setlocale(LC_MESSAGES, localename);
293 	if (str) {
294 		strfree(&current_msgs);
295 		current_msgs = strsave(str);
296 	}
297 #else
298 	str = llsetenv("LC_MESSAGES", localename);
299 	if (str) {
300 		strfree(&current_msgs);
301 		current_msgs = strsave(str);
302 	}
303 #endif
304 	if (str) {
305 		notify_gettext_language_changed();
306 		send_uilang_callbacks();
307 		date_update_lang();
308 	}
309 	return str;
310 }
311 #endif /* ENABLE_NLS */
312 #ifdef ENABLE_NLS
313 #if ! ( defined(HAVE_SETLOCALE) && defined(HAVE_LC_MESSAGES) )
314 /*==========================================
315  * llsetenv -- assign a value to an environment variable
316  * Workaround for systems without HAVE_SETLOCALE && HAVE_LC_MESSAGES
317  * Returns value if it succeeded
318  *========================================*/
319 static STRING
llsetenv(STRING name,STRING value)320 llsetenv (STRING name, STRING value)
321 {
322 	char buffer[128];
323 	STRING str = 0;
324 
325 	buffer[0] = 0;
326 	llstrappf(buffer, sizeof(buffer), uu8, "%s=%s", name, value);
327 
328 #ifdef HAVE_SETENV
329 	if (setenv(name, value, 1) != -1)
330 		str = value;
331 #else
332 #ifdef HAVE_PUTENV
333 	if (putenv(buffer) != -1)
334 		str = value;
335 #else
336 #ifdef HAVE__PUTENV
337 	if (_putenv(buffer) != -1)
338 		str = value;
339 #endif /* HAVE__PUTENV */
340 #endif /* HAVE_PUTENV */
341 #endif /* HAVE_SETENV */
342 	return str;
343 }
344 #endif /* !defined(HAVE_SETLOCALE) && !defined(HAVE_LC_MESSAGES) */
345 #endif /* ENABLE_NLS */
346 /*==========================================
347  * customlocale -- set locale to custom setting
348  *  depending on user options
349  *  prefix:  [IN]  option prefix (eg, "UiLocale")
350  * Created: 2002/02/24 (Perry Rapp)
351  *========================================*/
352 static void
customlocale(STRING prefix)353 customlocale (STRING prefix)
354 {
355 	char option[64];
356 	STRING str;
357 	INT prefixlen = strlen(prefix);
358 
359 	if (prefixlen > 30) return;
360 
361 	strcpy(option, prefix);
362 
363 #ifdef HAVE_SETLOCALE
364 	/* did user set, eg, UiLocaleCollate option ? */
365 	strcpy(option+prefixlen, "Collate");
366 	str = getlloptstr(option, 0);
367 	if (str) {
368 		customized_loc = TRUE;
369 		str = llsetlocale(LC_COLLATE, str);
370 	}
371 	if (!str) {
372 		/* did user set, eg, UiLocale option ? */
373 		option[prefixlen] = 0;
374 		str = getlloptstr(option, 0);
375 		if (str) {
376 			customized_loc = TRUE;
377 			str = llsetlocale(LC_COLLATE, str);
378 		}
379 		/* nothing set, so try to revert to startup value */
380 		if (!str && customized_loc)
381 			llsetlocale(LC_COLLATE, deflocale_coll);
382 	}
383 #endif /* HAVE_SETLOCALE */
384 
385 #ifdef ENABLE_NLS
386 	/* did user set, eg, UiLocaleMessages option ? */
387 	strcpy(option+prefixlen, "Messages");
388 	str = getlloptstr(option, 0);
389 	if (str) {
390 		customized_msgs = TRUE;
391 		str = setmsgs(str);
392 	} else {
393 		/* did user set, eg, UiLocale option ? */
394 		option[prefixlen] = 0;
395 		str = getlloptstr(option, 0);
396 		if (str) {
397 			customized_msgs = TRUE;
398 			str = setmsgs(str);
399 		}
400 		if (!str && customized_msgs)
401 			setmsgs(deflocale_msgs ? deflocale_msgs : "");
402 	}
403 #endif /* ENABLE_NLS */
404 }
405 /*==========================================
406  * notify_gettext_language_changed --
407  *  signal gettext that desired language has changed
408  * Created: 2002/06/15 (Perry Rapp)
409  *========================================*/
410 static void
notify_gettext_language_changed(void)411 notify_gettext_language_changed (void)
412 {
413 #if ENABLE_NLS
414 #if  WIN32_INTL_SHIM
415 	gt_notify_language_change();
416 #else
417 	extern int _nl_msg_cat_cntr;
418 	++_nl_msg_cat_cntr;
419 #endif
420 #endif
421 }
422 /*==========================================
423  * llsetlocale -- wrapper for setlocale
424  * Handle MS-Windows annoying lack of LC_MESSAGES
425  * TODO: clean up other translat.c functions by calling this
426  *========================================*/
427 char *
llsetlocale(int category,char * locale)428 llsetlocale (int category, char * locale)
429 {
430 	char * rtn = "C";
431 #ifdef HAVE_SETLOCALE
432 	rtn = setlocale(category, locale);
433 	if (!rtn && locale)
434 		rtn = win32_setlocale(category, locale);
435 #endif /* HAVE_SETLOCALE */
436 #ifdef ENABLE_NLS
437 	if (rtn && is_msgcategory(category)) {
438 		setmsgs(locale);
439 	}
440 #endif /* ENABLE_NLS */
441 	return rtn;
442 }
443 /*==========================================
444  * is_msgcategory -- check for LC_ALL or LC_MESSAGES
445  *========================================*/
446 #ifdef ENABLE_NLS
447 static BOOLEAN
is_msgcategory(int category)448 is_msgcategory (int category)
449 {
450 #ifdef LC_MESSAGES
451 	return category==LC_ALL || category==LC_MESSAGES;
452 #else
453 	return category==LC_ALL;
454 #endif
455 }
456 #endif /* ENABLE_NLS */
457 /*==========================================
458  * win32_setlocale -- handle MS-Windows goofed up locale names
459  *========================================*/
460 static char *
win32_setlocale(int category,char * locale)461 win32_setlocale (int category, char * locale)
462 {
463 	char * rtn = 0;
464 #if defined(_WIN32) && !defined(__CYGWIN__)
465 	/* TODO: Obviously this needs work -- and move to win32 subdir ? */
466 	if (locale) {
467 		char w32loc[30]="";
468 		char * ptr;
469 		int i;
470 		for (i=0; langs[i]; i += 2) {
471 			if (eqstrn(locale, langs[i], 2)) {
472 				llstrapps(w32loc, sizeof(w32loc), uu8, langs[i+1]);
473 				break;
474 			}
475 		}
476 		if (!langs[i])
477 			return 0;
478 		ptr = locale+strlen(langs[i]);
479 		if (ptr[0]=='_') {
480 			llstrappc(w32loc, sizeof(w32loc), ptr[0]);
481 			for (i=0; countries[i]; i += 2) {
482 				if (eqstrn(ptr+1, countries[i], 2)) {
483 					llstrapps(w32loc, sizeof(w32loc), uu8, countries[i+1]);
484 					break;
485 				}
486 			}
487 		}
488 		/* TODO: strip off codeset, because we don't want user's codeset anyway,
489 		at least unless int_codeset == 0 */
490 		rtn = setlocale(category, w32loc);
491 	}
492 #else
493 	category=category; /* unused */
494 	locale=locale; /* unused */
495 #endif /* _WIN32 */
496 	return rtn;
497 }
498 /*==========================================
499  * register_uilang_callback --
500  *========================================*/
501 void
register_uilang_callback(CALLBACK_FNC fncptr,VPTR uparm)502 register_uilang_callback (CALLBACK_FNC fncptr, VPTR uparm)
503 {
504 	add_listener(&f_uilang_callbacks, fncptr, uparm);
505 }
506 /*==========================================
507  * unregister_uilang_callback --
508  *========================================*/
509 void
unregister_uilang_callback(CALLBACK_FNC fncptr,VPTR uparm)510 unregister_uilang_callback (CALLBACK_FNC fncptr, VPTR uparm)
511 {
512 	delete_listener(&f_uilang_callbacks, fncptr, uparm);
513 }
514 /*==========================================
515  * send_uilang_callbacks --
516  *========================================*/
517 #ifdef ENABLE_NLS
518 static void
send_uilang_callbacks(void)519 send_uilang_callbacks (void)
520 {
521 	notify_listeners(&f_uilang_callbacks);
522 }
523 #endif /* ENABLE_NLS */
524 /*==========================================
525  * register_uicodeset_callback --
526  *========================================*/
527 void
register_uicodeset_callback(CALLBACK_FNC fncptr,VPTR uparm)528 register_uicodeset_callback (CALLBACK_FNC fncptr, VPTR uparm)
529 {
530 	add_listener(&f_uicodeset_callbacks, fncptr, uparm);
531 }
532 /*==========================================
533  * unregister_uicodeset_callback --
534  *========================================*/
535 void
unregister_uicodeset_callback(CALLBACK_FNC fncptr,VPTR uparm)536 unregister_uicodeset_callback (CALLBACK_FNC fncptr, VPTR uparm)
537 {
538 	delete_listener(&f_uicodeset_callbacks, fncptr, uparm);
539 }
540 /*==========================================
541  * locales_notify_uicodeset_changes --
542  *========================================*/
543 void
locales_notify_uicodeset_changes(void)544 locales_notify_uicodeset_changes (void)
545 {
546 	notify_listeners(&f_uicodeset_callbacks);
547 }
548 /*==========================================
549  * locales_notify_language_change -- notify gettext of change
550  *========================================*/
551 void
locales_notify_language_change(void)552 locales_notify_language_change (void)
553 {
554 	notify_gettext_language_changed();
555 }
556 
557