1 /****************************************************************************
2 Copyright (C) 1987-2015 by Jeffery P. Hansen
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License along
15 with this program; if not, write to the Free Software Foundation, Inc.,
16 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18 Last edit by hansen on Fri Feb 13 22:29:46 2009
19 ****************************************************************************/
20
21 #ifdef __cplusplus
22 #include <cstdlib>
23 #include <cassert>
24 #else
25 #include <stdlib.h>
26 #include <assert.h>
27 #endif
28
29 #include <dirent.h>
30
31 #include "tkgate.h"
32
33 extern int is_verbose;
34
35 static int gat_msgLookup(ClientData d,Tcl_Interp *tcl,int argc,const char *argv[]);
36
readLongMsg(FILE * f,char * msg,int n,int doFill,Encoder * encoder)37 static void readLongMsg(FILE *f,char *msg,int n,int doFill,Encoder *encoder)
38 {
39 char tmp[8*STRMAX];
40 char *p;
41 int l;
42
43 p = tmp;
44 while (fgets(p,n,f)) {
45 if (doFill) {
46 char *q = strchr(p,'\n');
47 if (q) *q = ' ';
48 }
49
50 l = strlen(p);
51 if (strncmp(p,"-end-",5) == 0) {
52 if (p != tmp)
53 p[-1] = 0;
54 else
55 *p = 0;
56 break;
57 }
58 p += l;
59 n -= l;
60 if (n <= 0) {
61 printf("Fatal Error: Message too long.\n");
62 exit(1);
63 break;
64 }
65 }
66
67 recodeText(encoder,msg,8*STRMAX,tmp);
68 }
69
getLocaleDescriptor(const char * locale)70 static Locale *getLocaleDescriptor(const char *locale)
71 {
72 Locale *l;
73 char fileName[STRMAX];
74 char buf[STRMAX],tag[STRMAX];
75 FILE *f;
76 unsigned loadMask = 0;
77
78 sprintf(fileName,"%s/locale/%s/messages",TkGate.homedir,locale);
79 f = fopen(fileName,"r");
80 if (!f) {
81 logError(ERL_ERROR,"Missing messages file for locale <%s>.",locale);
82 return 0;
83 }
84
85 /*
86 * Set default values
87 */
88 l = (Locale*)malloc(sizeof(Locale));
89 l->l_code = strdup(locale);
90 l->l_name = l->l_code;
91 l->l_messages = strdup(fileName);
92 l->l_encFont = CE_ISO8859_1;
93 l->l_encDisplay = CE_ISO8859_1;
94 l->l_encMessages = CE_ISO8859_1;
95 l->l_encVerilog = CE_ISO8859_1;
96 l->l_encPostscript = CE_ISO8859_1;
97
98 /*
99 * Read messages file for declarations
100 */
101 while (fgets(buf,STRMAX,f)) {
102 if (sscanf(buf,"\\language %[^\n\r]",tag) == 1) {
103 Encoder *e = getEncoder(CE_UTF8,l->l_encMessages);
104 recodeText(e,buf,STRMAX,tag);
105 l->l_name = strdup(buf);
106 loadMask |= 0x1;
107 } else if (sscanf(buf,"\\messages-encoding %s",tag) == 1) {
108 l->l_encMessages = strdup(tag);
109 loadMask |= 0x2;
110 } else if (sscanf(buf,"\\verilog-encoding %s",tag) == 1) {
111 l->l_encVerilog = strdup(tag);
112 loadMask |= 0x4;
113 } else if (sscanf(buf,"\\display-encoding %s",tag) == 1) {
114 l->l_encDisplay = strdup(tag);
115 loadMask |= 0x8;
116 } else if (sscanf(buf,"\\font-encoding %s",tag) == 1) {
117 l->l_encFont = strdup(tag);
118 loadMask |= 0x10;
119 } else if (sscanf(buf,"\\postscript-encoding %s",tag) == 1) {
120 l->l_encPostscript = strdup(tag);
121 loadMask |= 0x20;
122 }
123 if (loadMask == 0x3f) break; /* everything loaded */
124 }
125
126 #if LOCALE_DEBUG
127 printf("loaded locale %s (%s)\n",l->l_code,l->l_name);
128 #endif
129
130 return l;
131 }
132
133 /*
134 * Find the set of locales that are available
135 */
init_localeSet(void)136 void init_localeSet(void)
137 {
138 char dirName[STRMAX];
139 DIR *d;
140 struct dirent *de;
141
142 TkGate.localeTable = new_SHash_noob();
143 TkGate.localeNameTable = new_SHash_noob();
144
145 sprintf(dirName,"%s/locale",TkGate.homedir);
146 d = opendir(dirName);
147 if (!d) {
148 logError(ERL_FATAL,"Can not find locale directory %s/locale.",TkGate.homedir);
149 exit(1);
150 }
151 while ((de = readdir(d))) {
152 Locale *l;
153
154 if (*de->d_name == '.') continue; /* not a locale */
155 l = getLocaleDescriptor(de->d_name);
156 if (l) {
157 Locale *x;
158 x = SHash_find(TkGate.localeNameTable, l->l_name);
159 if (x) {
160 logError(ERL_ERROR,"Locale name '%s' for '%s' conflicts with locale '%s'.",l->l_name,l->l_code,x->l_code);
161 } else {
162 SHash_insert(TkGate.localeTable, l->l_code,l);
163 SHash_insert(TkGate.localeNameTable, l->l_name,l);
164 }
165 }
166 }
167 closedir(d);
168
169 /*
170 * Set default locale.
171 */
172 TkGate.locale = (Locale*) SHash_find(TkGate.localeTable, "en");
173 if (!TkGate.locale) {
174 logError(ERL_FATAL,"tkgate: can not find required file locale/en/messages in %s.\n",TkGate.homedir);
175 exit(1);
176 }
177 }
178
179 /*****************************************************************************
180 *
181 * Determine our locale and territory.
182 *
183 *****************************************************************************/
getTkGateLocale(Tcl_Interp * tcl,char * lang,char * territory)184 static void getTkGateLocale(Tcl_Interp *tcl,char *lang,char *territory)
185 {
186 char *p = 0;
187
188 /*
189 * If Tcl/Tk is running, then look at the environment variables LC_ALL,
190 * LC_MESSAGES, and LANG (in that order) to determine the locale.
191 */
192 if (tcl) {
193 p = getenv("TKGATE_LANG");
194 if (!p || !*p) p = getenv("LC_ALL");
195 if (!p || !*p) p = getenv("LC_MESSAGES");
196 if (!p || !*p) p = getenv("LANG");
197 if (!p || !*p) {
198 /*
199 * If we have Japanese support and kinput is running, an invalid locale
200 * will cause a crash in the tcl/tk initialization step, even we are running
201 * in English mode. Force LANG it a valid locale to avoid this problem.
202 */
203 putenv("LANG=ASCII");
204 p = "en_US";
205 }
206 strncpy(lang,p,1024);
207 } else
208 strcpy(lang,"en_US");
209
210 /* If locale is any of the following, set the locale to "en" */
211 if (strcmp(lang,"ASCII") == 0 ||
212 strcmp(lang,"US-ASCII") == 0 ||
213 strcmp(lang,"C") == 0 ||
214 strcmp(lang,"POSIX") == 0)
215 strcpy(lang,"en_US");
216
217 /*
218 * Check for a territory, and partition lang into language and territory.
219 */
220 if (strlen(lang) >= 5) {
221 strcpy(territory,lang + 3);
222 territory[2] = 0;
223 }
224 lang[2] = 0;
225
226 if (!*territory)
227 strcpy(territory, "US");
228 }
229
readMessagesFile(Locale * locale)230 static SHash *readMessagesFile(Locale *locale)
231 {
232 Encoder *encoder = getEncoder(CE_UTF8, locale->l_encMessages);
233 char buf[STRMAX],buf2[STRMAX],tag[STRMAX],msg[8*STRMAX];
234 SHash *H = new_SHash_noob();
235 FILE *f;
236
237 #if LOCALE_DEBUG
238 printf("[loading locale %s in <%s> format]\n",locale->l_code, locale->l_encMessages);
239 #endif
240
241 f = fopen(locale->l_messages,"r");
242 assert(f); /* We've already verified we can open this file. */
243
244
245 // TkGate.fontCode = strdup(tag);
246 // TkGate.saveFileEncoding = strdup(tag);
247
248 /*
249 * Read the messages file. We ignore the directives because we have already read them
250 * and stored the values in the locale.
251 */
252 while (fgets(buf2 ,1024,f)) {
253 recodeText(encoder,buf,STRMAX,buf2);
254
255 if (sscanf(buf,"\\font-encoding %s",tag) == 1) {
256 } else if (sscanf(buf,"\\messages-encoding %s",tag) == 1) {
257 } else if (sscanf(buf,"\\verilog-encoding %s",tag) == 1) {
258 } else if (sscanf(buf,"\\display-encoding %s",tag) == 1) {
259 } else if (sscanf(buf,"\\postscript-encoding %s",tag) == 1) {
260 } else if (sscanf(buf,"\\language %s",tag) == 1) {
261 } else if (sscanf(buf,"\\%s",tag) == 1) {
262 logError(ERL_ERROR,"Unknown command '%s' in messages file '%s'.",tag,locale->l_messages);
263 } else if (sscanf(buf,"%s %[^\n]",tag,msg) == 2 && *tag != '#') {
264 if (strcmp(msg,"-begin-") == 0)
265 readLongMsg(f,msg,8096,0,encoder);
266 else if (strcmp(msg,"-fillbegin-") == 0)
267 readLongMsg(f,msg,8096,1,encoder);
268 else if (strcmp(msg,"-empty-") == 0)
269 *msg = 0;
270
271 if (SHash_find(H,tag)) {
272 logError(ERL_ERROR,"Duplicate add of message '%s'.",tag);
273 }
274
275 SHash_insert(H,tag,ob_strdup(msg));
276 }
277 }
278 fclose(f);
279
280 return H;
281 }
282
283 /*
284 * If the locale is not English we make sure that we have messages for everything
285 * that is in the English file, and we have no tags that aren't in the English
286 * file. We issue warnings for any discrepancies.
287 */
verifyMessagesFile(SHash * H,Locale * englishLocale)288 static void verifyMessagesFile(SHash *H,Locale *englishLocale)
289 {
290 char buf[STRMAX],buf2[STRMAX],tag[STRMAX],msg[8*STRMAX];
291 int no_msg_count = 0;
292 FILE *f;
293 /** @TODO to remove */
294 /* char *p = 0; */
295 Encoder *encoder = 0;
296
297 f = fopen(englishLocale->l_messages,"r");
298 assert(f);
299
300
301 while (fgets(buf2,1024,f)) {
302 recodeText(encoder,buf,STRMAX,buf2);
303
304 if (sscanf(buf,"\\messages-encoding %s",tag) == 1) {
305 encoder = getEncoder(CE_UTF8, tag);
306 } else if (sscanf(buf,"\\%s",tag) == 1) {
307 /* Ignore */
308 } else if (sscanf(buf,"%s %[^\n]",tag,msg) == 2 && *tag != '#') {
309 if (strcmp(msg,"-begin-") == 0)
310 readLongMsg(f,msg,8096,0,encoder);
311 else if (strcmp(msg,"-fillbegin-") == 0)
312 readLongMsg(f,msg,8096,1,encoder);
313
314 if (!SHash_find(H,tag)) {
315 SHash_insert(H,tag,ob_strdup(msg));
316
317 /*
318 * Warn if there was a missing tag (but is ok not to redefine the @ tags.
319 */
320 if (*tag != '@') {
321 if (is_verbose)
322 logError(ERL_ERROR,"No localized string for symbol '%s'. Using English value.",tag);
323 else
324 no_msg_count++;
325 }
326 }
327 }
328 }
329 fclose(f);
330
331 if (!is_verbose && no_msg_count > 0) {
332 logError(ERL_ERROR,"No localized strings for %d messages. Use 'tkgate -v' for details.",no_msg_count);
333 }
334 }
335
336 /*
337 * Determine which locale we are going to run under and read the appropriate
338 * message files. If the locale is not "en" (English), then the both the
339 * locale specific message file and the English message file is read. If there
340 * are any messages in the English message file that are not in the locale
341 * specific message file, a warning is printed and the English message
342 * is used in place of the missing locale-specific message.
343 */
localization_Setup(Tcl_Interp * tcl)344 void localization_Setup(Tcl_Interp *tcl)
345 {
346 char lang[STRMAX],territory[STRMAX];
347 Locale *englishLocale;
348
349 /*
350 * Make sure we have at least the English locale.
351 */
352 englishLocale = (Locale*) SHash_find(TkGate.localeTable, "en");
353 if (!englishLocale) {
354 logError(ERL_FATAL,"Can not find required locale/en/messages in %s.\n",TkGate.homedir);
355 exit(1);
356 }
357
358 /*
359 * Get the locale code
360 */
361 getTkGateLocale(tcl, lang, territory);
362
363 /*
364 * Make sure the requested locale is available. If not, use English
365 */
366 TkGate.locale = (Locale*) SHash_find(TkGate.localeTable, lang);
367 if (!TkGate.locale) {
368 printf("[No support for locale '%s'. Reverting to English.]\n",lang);
369 strcpy(lang,"en");
370 TkGate.locale = englishLocale;
371 }
372
373 /*
374 * Set flag if we have a Japanese locale
375 */
376 TkGate.japaneseMode = (strcmp(lang,"ja") == 0);
377
378 /*
379 * Read the messages for the selected local.
380 */
381 message_table = readMessagesFile(TkGate.locale);
382
383 /*
384 * Test the loaded locale against the English locale to look for missing tags, etc.
385 */
386 if (strcmp(lang,"en") != 0) {
387 verifyMessagesFile(message_table,englishLocale);
388 }
389
390 if (tcl) {
391 Tcl_SetVar(tcl,"lang",lang,TCL_GLOBAL_ONLY);
392 Tcl_SetVar(tcl,"territory",territory,TCL_GLOBAL_ONLY);
393 Tcl_CreateCommand(tcl,"m",gat_msgLookup,(ClientData)0,0);
394 }
395 }
396
397 /*
398 * tcl handler code for message lookup function 'm'.
399 */
gat_msgLookup(ClientData d,Tcl_Interp * tcl,int argc,const char * argv[])400 static int gat_msgLookup(ClientData d,Tcl_Interp *tcl,int argc,const char *argv[])
401 {
402 char *msg;
403
404 if (argc < 2) return TCL_OK;
405
406 msg = msgLookup(argv[1]);
407 Tcl_SetResult(tcl, msg, TCL_VOLATILE);
408
409 return TCL_OK;
410 }
411