1 /* Choice of user interface language, and internationalization. */
2 
3 /* -------------------------- Specification ---------------------------- */
4 
5 #ifdef GNU_GETTEXT
6 
7 /* Initializes the current interface language, according to the given
8    arguments, getting the defaults from environment variables. */
9 global void init_language (const char* argv_language, const char* argv_localedir, bool lisp_error_p);
10 
11 /* Returns the translation of msgid according to the current interface
12    language. */
13 global const char * clgettext (const char * msgid);
14 
15 #endif
16 
17 /* Returns the translation of string according to the current interface
18  language. A string is returned.
19  can trigger GC */
20 global object CLSTEXT (const char*);
21 
22 /* Returns the translated value of obj. obj is translated,
23  then READ-FROM-STRING is applied to the result.
24  can trigger GC */
25 global object CLOTEXT (const char*);
26 
27 
28 /* -------------------------- Implementation --------------------------- */
29 
30 #ifdef GNU_GETTEXT
31 
32 /* language, that is used for communication with the user: */
33 static enum {
34   language_english,
35   language_german,
36   language_french,
37   language_spanish,
38   language_dutch,
39   language_russian,
40   language_danish,
41   language_swedish
42 } language;
43 
44 /* Initializes the language, given the language name. */
45 local bool init_language_from (const char* langname);
46 
current_language_o(void)47 global object current_language_o (void) {
48   switch (language) {
49     case language_english:  { return S(english); }
50     case language_german:   { return S(german); }
51     case language_french:   { return S(french); }
52     case language_spanish:  { return S(spanish); }
53     case language_dutch:    { return S(dutch); }
54     case language_russian:  { return S(russian); }
55     case language_danish:   { return S(danish); }
56     case language_swedish:  { return S(swedish); }
57     default: NOTREACHED;
58   }
59 }
60 
init_language_from(const char * langname)61 local bool init_language_from (const char* langname) {
62   if (NULL == langname) return false;
63   if (asciz_equal(langname,"ENGLISH") || asciz_equal(langname,"english")) {
64     language = language_english; return true;
65   }
66   if (asciz_equal(langname,"DEUTSCH") || asciz_equal(langname,"deutsch")
67       || asciz_equal(langname,"GERMAN") || asciz_equal(langname,"german")) {
68     language = language_german; return true;
69   }
70   if (asciz_equal(langname,"FRANCAIS") || asciz_equal(langname,"francais")
71      #ifndef ASCII_CHS
72       || asciz_equal(langname,"FRAN\307AIS") || asciz_equal(langname,"FRAN\303\207AIS") /* FRENCH */
73       || asciz_equal(langname,"fran\347ais") || asciz_equal(langname,"fran\303\247ais") /* french */
74      #endif
75       || asciz_equal(langname,"FRENCH") || asciz_equal(langname,"french")) {
76     language = language_french; return true;
77   }
78   if (asciz_equal(langname,"ESPANOL") || asciz_equal(langname,"espanol")
79      #ifndef ASCII_CHS
80       || asciz_equal(langname,"ESPA\321OL") || asciz_equal(langname,"ESPA\303\221OL") /* SPANISH */
81       || asciz_equal(langname,"espa\361ol") || asciz_equal(langname,"espa\303\261ol") /* spanish */
82      #endif
83       || asciz_equal(langname,"SPANISH") || asciz_equal(langname,"spanish")) {
84     language = language_spanish; return true;
85   }
86   if (asciz_equal(langname,"russian") || asciz_equal(langname,"RUSSIAN")
87      #ifndef ASCII_CHS
88       || asciz_equal(langname,"\320\240\320\243\320\241\320\241\320\232\320\230\320\231")
89       || asciz_equal(langname,"\321\200\321\203\321\201\321\201\320\272\320\270\320\271")
90       || asciz_equal(langname,"\240\243\241\241\232\230\231")
91       || asciz_equal(langname,"\200\203\201\201\272\270\271")
92      #endif
93       ) {
94     language = language_russian; return true;
95   }
96   if (asciz_equal(langname,"NEDERLANDS") || asciz_equal(langname,"nederlands")
97       || asciz_equal(langname,"DUTCH") || asciz_equal(langname,"dutch")) {
98     language = language_dutch; return true;
99   }
100   if (asciz_equal(langname,"DANSK") || asciz_equal(langname,"dansk")
101       || asciz_equal(langname,"DANISH") || asciz_equal(langname,"danish")) {
102     language = language_danish; return true;
103   }
104   if (asciz_equal(langname,"SVENSKA") || asciz_equal(langname,"svenska")
105       || asciz_equal(langname,"SWEDISH") || asciz_equal(langname,"swedish")) {
106     language = language_swedish; return true;
107   }
108   return false;
109 }
110 
111 /* Initializes the language. */
init_language(const char * argv_language,const char * argv_localedir,bool lisp_error_p)112 global void init_language
113 (const char* argv_language, const char* argv_localedir, bool lisp_error_p) {
114 #define ANSIC_ERROR(f,a)   if (lisp_error_p) ANSIC_error(); else {      \
115   fprintf(stderr,GETTEXT("WARNING: %s/%s: %s.\n"),f,a,strerror(errno)); \
116   goto init_language_failure;                                           \
117  }
118 #define MY_NOTREACHED if (lisp_error_p) NOTREACHED; else abort()
119   /* language is set with priorities in this order:
120      1. -L command line argument
121      2. environment-variable CLISP_LANGUAGE
122      3. environment-variable LANG
123      4. default: English */
124   if (argv_language == NULL) {
125     /* noop */
126   } else if (init_language_from(argv_language)
127              || init_language_from(getenv("CLISP_LANGUAGE"))) {
128   /* At this point we have chosen the language based upon the
129    command-line option or the clisp-specific environment variables. */
130   /* GNU gettext chooses the message catalog based upon:
131    1. environment variable LANGUAGE [only if dcgettext.c, not with
132       cat-compat.c],
133    2. environment variable LC_ALL,
134    3. environment variable LC_MESSAGES,
135    4. environment variable LANG.
136    We clobber LC_MESSAGES and unset the earlier two variables. */
137     var const char *locale1, *locale2;
138     switch (language) {
139       case language_english: locale1 = "en_US"; locale2 = "en_US.utf8"; break;
140       case language_german:  locale1 = "de_DE"; locale2 = "de_DE.utf8"; break;
141       case language_french:  locale1 = "fr_FR"; locale2 = "fr_FR.utf8"; break;
142       case language_spanish: locale1 = "es_ES"; locale2 = "es_ES.utf8"; break;
143       case language_dutch:   locale1 = "nl_NL"; locale2 = "nl_NL.utf8"; break;
144       case language_russian: locale1 = "ru_RU"; locale2 = "ru_RU.utf8"; break;
145       case language_danish:  locale1 = "da_DK"; locale2 = "da_DK.utf8"; break;
146       case language_swedish: locale1 = "sv_SE"; locale2 = "sv_SE.utf8"; break;
147       default:               MY_NOTREACHED;
148     }
149     if (getenv("LANGUAGE") && unsetenv("LANGUAGE")) {
150       ANSIC_ERROR("unsetenv","LANGUAGE");
151     }
152     if (getenv("LC_ALL") && unsetenv("LC_ALL")) {
153       ANSIC_ERROR("unsetenv","LC_ALL");
154     }
155     if (NULL == setlocale(LC_MESSAGES,locale2)) {
156       if (NULL == setlocale(LC_MESSAGES,locale1)) {
157         if (lisp_error_p) {
158           pushSTACK(ascii_to_string(locale1));
159           pushSTACK(ascii_to_string(locale2));
160           pushSTACK(TheSubr(subr_self)->name);
161           error(error_condition,GETTEXT("~S: locales ~S and ~S are not installed on this system"));
162         } else {
163           fprintf(stderr,GETTEXT("locales %s and %s are not installed on this system\n"),locale1,locale2);
164           goto init_language_failure;
165         }
166       } else if (setenv("LC_MESSAGES",locale1,1)) {
167         ANSIC_ERROR("setenv/LC_MESSAGES",locale1);
168       }
169     } else if (setenv("LC_MESSAGES",locale2,1)) {
170       ANSIC_ERROR("setenv/LC_MESSAGES",locale2);
171     }
172     { /* Invalidate the gettext internal caches. */
173       char *td = textdomain(NULL);
174       if (NULL == td) {
175         ANSIC_ERROR("textdomain",NULL);
176       }
177       if (NULL == textdomain(td)) {
178         ANSIC_ERROR("textdomain",td);
179       }
180     }
181   } else if (lisp_error_p) {
182     pushSTACK(ascii_to_string(argv_language));
183     pushSTACK(TheSubr(subr_self)->name);
184     error(error_condition,GETTEXT("~S: invalid language ~S"));
185   } else {
186     fprintf(stderr,"invalid language %s.\n",argv_language);
187     goto init_language_failure;
188   }
189   /* At this point we have chosen the language based upon an
190      environment variable GNU gettext knows about.
191      argv_localedir=NULL usually means (setq *current-language* ...)
192      in which case reusing text domain from the original (or previous)
193      call to bindtextdomain() is a wise choice */
194   if (argv_localedir) { /* make sure that it exists and is a directory */
195     char truename[MAXPATHLEN];
196     switch (classify_namestring(argv_localedir,truename,NULL,NULL)) {
197       case FILE_KIND_FILE:
198         if (lisp_error_p) {
199           pushSTACK(ascii_to_string(truename));
200           pushSTACK(ascii_to_string(argv_localedir));
201           pushSTACK(TheSubr(subr_self)->name);
202           error(error_condition,GETTEXT("~S: ~S resolves to ~S which is a file, not a directory"));
203         } else {
204           fprintf(stderr,GETTEXT("%s resolves o %s which is a file, not a directory\n"),argv_localedir,truename);
205           goto init_language_failure;
206         }
207         MY_NOTREACHED;
208       case FILE_KIND_NONE: case FILE_KIND_BAD:
209         if (lisp_error_p) {
210           pushSTACK(ascii_to_string(argv_localedir));
211           pushSTACK(TheSubr(subr_self)->name);
212           error(error_condition,GETTEXT("~S: ~S does not exist"));
213         } else {
214           fprintf(stderr,GETTEXT("%s does not exist\n"),argv_localedir);
215           goto init_language_failure;
216         }
217         MY_NOTREACHED;
218       case FILE_KIND_DIR:
219         if (NULL == bindtextdomain("clisp",truename)) {
220           ANSIC_ERROR("bindtextdomain/clisp",truename);
221         }
222         if (NULL == bindtextdomain("clisplow",truename)) {
223           ANSIC_ERROR("bindtextdomain/clisplow",truename);
224         }
225     }
226   }
227  #ifdef ENABLE_UNICODE
228   if (NULL == bind_textdomain_codeset("clisp","UTF-8")) {
229     ANSIC_ERROR("bind_textdomain_codeset","UTF-8");
230   }
231  #endif
232   return;
233  init_language_failure:
234   fprintf(stderr,GETTEXT("WARNING: setting language to %s failed.\n"),argv_language);
235 #undef ANSIC_ERROR
236 #undef MY_NOTREACHED
237 }
238 
clisp_gettext(const char * domain,const char * msgid)239 local const char * clisp_gettext (const char * domain, const char * msgid) {
240   var const char * translated_msg;
241   if (msgid[0] == '\0') {
242     /* If you ask gettext to translate the empty string, it returns
243        the catalog's header (containing meta information)! */
244     translated_msg = msgid;
245   } else {
246     begin_system_call();
247     translated_msg = dgettext(domain,msgid);
248     end_system_call();
249   }
250   return translated_msg;
251 }
252 
253 /* High-level messages, which are converted to Lisp strings, are
254    stored in a separate catalog and returned in the UTF-8 encoding. */
clgettext(const char * msgid)255 modexp const char * clgettext (const char * msgid)
256 { return clisp_gettext("clisp", msgid); }
257 
258 /* Low-level messages, which are output through fprintf(3), are
259    stored in a separate catalog and returned in locale encoding. */
clgettextl(const char * msgid)260 global const char * clgettextl (const char * msgid)
261 { return clisp_gettext("clisplow", msgid); }
262 
263 #else
264   #define clgettext(m)  m       /* for CLSTEXT below */
265 #endif
266 
267 /* FIXME: Don't hardwire ISO-8859-1. The catalog's character set is
268  given by the "Content-Type:" line in the meta information.
269  in anticipation of this fix, CLSTEXT is a function, not a macro */
CLSTEXT(const char * asciz)270 modexp maygc object CLSTEXT (const char* asciz) {
271   return asciz_to_string(clgettext(asciz),Symbol_value(S(utf_8)));
272 }
273 
CLOTEXT(const char * asciz)274 global maygc object CLOTEXT (const char* asciz) {
275   dynamic_bind(S(packagestar),O(default_package)); /* bind *PACKAGE* */
276   pushSTACK(CLSTEXT(asciz)); funcall(L(read_from_string),1);
277   dynamic_unbind(S(packagestar));
278   return value1;
279 }
280