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