/[winpt]/trunk/Src/wptNLS.cpp
ViewVC logotype

Annotation of /trunk/Src/wptNLS.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 428 - (hide annotations)
Sat Apr 7 11:55:16 2012 UTC (12 years, 10 months ago) by twoaday
File size: 12248 byte(s)


1 twoaday 273 /* wptNLS.cpp - W32 Native Language Support
2 twoaday 398 * Copyright (C) 2001-2003, 2006, 2008, 2009, 2012 Timo Schulz
3 twoaday 273 * Copyright (C) 1995-1999 Free Software Foundation, Inc.
4     *
5 twoaday 278 * This code is a stripped down version of simple-gettext.c
6     * written by by Ulrich Drepper from the GPG project.
7 twoaday 273 *
8     * WinPT is free software; you can redistribute it and/or
9     * modify it under the terms of the GNU General Public License
10     * as published by the Free Software Foundation; either version 2
11     * of the License, or (at your option) any later version.
12     *
13     * WinPT is distributed in the hope that it will be useful,
14     * but WITHOUT ANY WARRANTY; without even the implied warranty of
15     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16     * General Public License for more details.
17     */
18     #ifdef HAVE_CONFIG_H
19     #include <config.h>
20     #endif
21    
22     #include <stdio.h>
23     #include <string.h>
24     #include <sys/stat.h>
25 twoaday 328 #include <time.h>
26 twoaday 273 #include <windows.h>
27    
28     #include "wptNLS.h"
29 twoaday 278 #include "wptTypes.h"
30 twoaday 328 #include "wptUtil.h"
31     #include "wptW32API.h"
32     #include "wptVersion.h"
33     #include "StringBuffer.h"
34 twoaday 273
35     /* The magic number of the GNU message catalog format. */
36     #define MAGIC 0x950412de
37     #define MAGIC_SWAPPED 0xde120495
38    
39 twoaday 398 /* Revision number of the currently used .mo (binary) file format. */
40 twoaday 273 #define MO_REVISION_NUMBER 0
41    
42     #define SWAPIT(flag, data) ((flag) ? do_swap_u32(data) : (data) )
43    
44    
45 twoaday 398 /* We assume to have `unsigned long int' value with at least 32 bits. */
46 twoaday 273 #define HASHWORDBITS 32
47    
48     /* Header for binary .mo file format. */
49     struct mo_file_header {
50     DWORD magic; /* The magic number. */
51     DWORD revision; /* The revision number of the file format. */
52     DWORD nstrings; /* The number of strings pairs. */
53     DWORD orig_tab_offset; /* Offset of table with start offsets of original
54     strings. */
55     DWORD trans_tab_offset; /* Offset of table with start offsets of translation
56     strings. */
57     DWORD hash_tab_size; /* Size of hashing table. */
58     DWORD hash_tab_offset; /* Offset of first hashing entry. */
59     };
60    
61     struct string_desc {
62     DWORD length; /* Length of addressed string. */
63     DWORD offset; /* Offset of string in file. */
64     };
65    
66 twoaday 328 struct loaded_domain_s {
67 twoaday 372 /* Full file name to the MO file */
68 twoaday 328 char *file_name;
69 twoaday 372 /* Last modification date of the MO file */
70 twoaday 328 time_t modify_time;
71 twoaday 372
72 twoaday 328 char *data;
73     int must_swap;
74     DWORD nstrings;
75     char *mapped;
76     struct string_desc *orig_tab;
77     struct string_desc *trans_tab;
78     DWORD hash_size;
79     DWORD *hash_tab;
80 twoaday 273 };
81 twoaday 328 typedef struct loaded_domain_s *gettext_domain_t;
82 twoaday 273
83     /* List of all available languages. */
84     struct lang_table_s lang_list[] = {
85     {"en", "English", LANG_ENGLISH},
86     {"de", "German", LANG_GERMAN},
87 twoaday 328 {"ru", "Russian", LANG_RUSSIAN},
88 twoaday 398 {"nl", "Dutch", LANG_DUTCH},
89 twoaday 428 {"pl", "Polish", LANG_POLISH},
90 twoaday 328 {NULL, NULL, 0}
91 twoaday 273 };
92    
93 twoaday 278
94 twoaday 328 /* The current gettext domain. */
95     static gettext_domain_t the_domain;
96 twoaday 273
97    
98 twoaday 368 /* Return TRUE if the native user language is not English
99     and thus, gettext support is available */
100     static bool
101     gettext_is_required (void)
102     {
103     LANGID lang = GetUserDefaultLangID ();
104     if (PRIMARYLANGID (lang) == LANG_ENGLISH)
105     return false;
106     return true;
107     }
108    
109    
110 twoaday 273 static DWORD
111     do_swap_u32 (DWORD i)
112     {
113     return (i << 24) | ((i & 0xff00) << 8) | ((i >> 8) & 0xff00) | (i >> 24);
114     }
115    
116    
117     /* The so called `hashpjw' function by P.J. Weinberger
118     [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools,
119     1986, 1987 Bell Telephone Laboratories, Inc.] */
120     static DWORD
121     hash_string (const char *str_param)
122     {
123     DWORD hval, g;
124     const char *str = str_param;
125    
126     hval = 0;
127     while (*str != '\0') {
128     hval <<= 4;
129     hval += (DWORD) *str++;
130     g = hval & ((DWORD) 0xf << (HASHWORDBITS - 4));
131     if (g != 0) {
132     hval ^= g >> (HASHWORDBITS - 8);
133     hval ^= g;
134     }
135     }
136     return hval;
137     }
138    
139 twoaday 278
140 twoaday 328 static gettext_domain_t
141 twoaday 278 load_domain (const char *filename)
142 twoaday 273 {
143     FILE *fp;
144     size_t size;
145     struct stat st;
146     struct mo_file_header *data = NULL;
147 twoaday 328 gettext_domain_t domain = NULL;
148 twoaday 273 size_t to_read;
149     char *read_ptr;
150    
151 twoaday 278 fp = fopen (filename, "rb");
152     if (!fp)
153 twoaday 407 return NULL;
154 twoaday 273 /* we must know about the size of the file */
155 twoaday 328 if (fstat (fileno(fp), &st) ||
156     (size = (size_t)st.st_size) != (size_t)st.st_size ||
157     size < sizeof (struct mo_file_header)) {
158     fclose (fp);
159 twoaday 273 return NULL;
160     }
161    
162 twoaday 278 data = (struct mo_file_header *) malloc (size);
163     if (!data)
164     BUG (0);
165 twoaday 273
166     to_read = size;
167     read_ptr = (char *) data;
168     do {
169 twoaday 328 size_t nb = fread (read_ptr, 1, to_read, fp);
170     if (nb < to_read) {
171 twoaday 273 fclose (fp);
172 twoaday 368 free (data);
173 twoaday 328 return NULL; /* read error */
174 twoaday 273 }
175     read_ptr += nb;
176     to_read -= nb;
177 twoaday 328 } while (to_read > 0);
178 twoaday 273 fclose (fp);
179    
180 twoaday 372 /* Using the magic number we can test whether it really is a message catalog file. */
181 twoaday 328 if (data->magic != MAGIC && data->magic != MAGIC_SWAPPED) {
182 twoaday 273 /* The magic number is wrong: not a message catalog file. */
183 twoaday 328 free (data);
184 twoaday 273 return NULL;
185     }
186    
187 twoaday 328 domain = (struct loaded_domain_s *)calloc (1, sizeof *domain);
188 twoaday 278 if (!domain)
189     BUG (0);
190 twoaday 273 domain->data = (char *) data;
191     domain->must_swap = data->magic != MAGIC;
192    
193     /* Fill in the information about the available tables. */
194 twoaday 328 switch (SWAPIT(domain->must_swap, data->revision)) {
195 twoaday 273 case 0:
196 twoaday 328 domain->nstrings = SWAPIT (domain->must_swap, data->nstrings);
197 twoaday 273 domain->orig_tab = (struct string_desc *)
198 twoaday 328 ((char *) data + SWAPIT (domain->must_swap, data->orig_tab_offset));
199 twoaday 273 domain->trans_tab = (struct string_desc *)
200 twoaday 328 ((char *) data + SWAPIT (domain->must_swap, data->trans_tab_offset));
201     domain->hash_size = SWAPIT (domain->must_swap, data->hash_tab_size);
202 twoaday 273 domain->hash_tab = (DWORD *)
203 twoaday 328 ((char *) data + SWAPIT (domain->must_swap, data->hash_tab_offset));
204 twoaday 273 break;
205    
206     default: /* This is an invalid revision. */
207 twoaday 328 free (data);
208     free (domain);
209 twoaday 273 return NULL;
210     }
211    
212     /* allocate an array to keep track of code page mappings */
213 twoaday 368 domain->mapped = (char *)calloc (1, domain->nstrings);
214 twoaday 278 if (!domain->mapped)
215     BUG (0);
216 twoaday 328
217     domain->file_name = strdup (filename);
218     domain->modify_time = st.st_mtime;
219 twoaday 273 return domain;
220 twoaday 278 }
221 twoaday 273
222    
223 twoaday 328 /* To avoid problems that the current directory is not the
224     * same folder the WinPT.exe is located in, we extract the
225     * module directory.
226     */
227     char*
228     get_module_dir (char *buf, DWORD buflen)
229     {
230     if (buflen < 1 || !GetModuleFileName (glob_hinst, buf, buflen-1))
231     return NULL;
232    
233 twoaday 368 size_t pos = strlen (buf);
234 twoaday 328 while (pos > 0 && buf[--pos] != '\\')
235     ;
236 twoaday 372 buf[pos + 1] = '\0';
237 twoaday 328 return buf;
238     }
239    
240    
241     /* Returns a localisation name for a specific resource (file).
242 twoaday 368 * For instance, lang=DE and file=winpt.chm will become winpt.DE.chm
243 twoaday 328 */
244     char*
245     get_locale_name (const char *file)
246     {
247 twoaday 368 const char *lang = gettext_get_langid ();
248 twoaday 372 char buf[MAX_PATH+1];
249 twoaday 328
250 twoaday 372 char *p = strrchr (file, '.');
251 twoaday 328 if (p == NULL) {
252 twoaday 407 p = new char[strlen (file) + strlen (lang) + 8 + 1];
253 twoaday 328 sprintf (p, "%s.%s", file, lang);
254     return p;
255     }
256 twoaday 372 char *name = substr (file, 0, p - file);
257 twoaday 407 char *ext = substr (file, p - file + 1, strlen (file));
258 twoaday 328 p = get_module_dir (buf, MAX_PATH);
259     if (!p)
260     BUG (NULL);
261     StringBuffer s = p;
262     s = s + name + "." + lang + "." + ext;
263     p = s.getBufferCopy();
264     free_if_alloc (name);
265     free_if_alloc (ext);
266     return p;
267     }
268    
269    
270 twoaday 278 /* Deallocate static resources. */
271     void
272     gettext_free_current_domain (void)
273     {
274 twoaday 328 if (the_domain == NULL)
275 twoaday 278 return;
276 twoaday 407 safe_free(the_domain->data);
277     safe_free(the_domain->mapped);
278     safe_free(the_domain->file_name);
279     safe_free(the_domain);
280 twoaday 278 the_domain = NULL;
281     }
282    
283    
284 twoaday 368 /* Replace and release the old domain and assign a new domain then */
285     static void
286     gettext_update_domain (gettext_domain_t new_dom)
287     {
288     gettext_free_current_domain ();
289     the_domain = new_dom;
290     }
291    
292    
293 twoaday 328 char* get_reg_entry_mo (void);
294    
295 twoaday 368 /* Fallback code which is used when no new NLS file were found.
296     Then we read the MODir registry key and check if a file named
297     'winpt.mo' exists in this directory. */
298 twoaday 328 static gettext_domain_t
299     load_modir_domain (void)
300 twoaday 368 {
301 twoaday 366 char *modir = get_reg_entry_mo ();
302 twoaday 328 if (!modir)
303     return NULL;
304     if (dir_exist_check (modir)) {
305     free_if_alloc (modir);
306     return NULL;
307     }
308 twoaday 366 StringBuffer s = modir;
309 twoaday 328 if (modir[strlen (modir)-1] != '\\')
310     s += "\\";
311     s += "winpt.mo";
312 twoaday 366
313 twoaday 368 gettext_domain_t domain = NULL;
314 twoaday 328 const char *fname = s.getBuffer();
315     if (!file_exist_check (fname))
316     domain = load_domain (fname);
317     free_if_alloc (modir);
318     return domain;
319     }
320    
321    
322 twoaday 368 /* Try to load the user domain, either from
323     the WinPT.exe directory or from the registry */
324 twoaday 372 void
325 twoaday 328 gettext_set_user_domain (void)
326     {
327 twoaday 368 if (!gettext_is_required ())
328 twoaday 372 return;
329 twoaday 368
330 twoaday 328 gettext_domain_t domain = NULL;
331     char *file = get_locale_name ("winpt.mo");
332     if (file && !file_exist_check (file))
333     domain = load_domain (file);
334     else
335     domain = load_modir_domain ();
336     free_if_alloc (file);
337 twoaday 368 gettext_update_domain (domain);
338 twoaday 328 }
339    
340    
341     int
342     gettext_domain_needs_refresh (void)
343     {
344     struct stat st;
345    
346     if (the_domain == NULL || stat (the_domain->file_name, &st))
347     return 0;
348     /* '1' means that the MO file were modified in the mean time
349     and the application should reload the domain. */
350     if (st.st_mtime != the_domain->modify_time)
351     return 1;
352    
353     return 0;
354     }
355    
356 twoaday 273 static const char*
357 twoaday 328 get_string (gettext_domain_t domain, DWORD idx)
358 twoaday 273 {
359     char *p = domain->data + SWAPIT(domain->must_swap,
360     domain->trans_tab[idx].offset);
361 twoaday 328 if (!domain->mapped[idx])
362     domain->mapped[idx] = 1;
363 twoaday 368 /* FIXME: all text is in UTF-8 and need to be converted to
364     the locale charset. But the caller does not free the data and
365     thus we will get a leak */
366 twoaday 366 return (const char *)p;
367 twoaday 273 }
368    
369    
370     const char *
371 twoaday 328 gettext (const char *msgid)
372 twoaday 273 {
373     size_t act = 0;
374     size_t top, bottom;
375 twoaday 328
376 twoaday 366 gettext_domain_t domain = the_domain;
377 twoaday 328 if (domain == NULL)
378 twoaday 273 goto not_found;
379    
380     /* Locate the MSGID and its translation. */
381 twoaday 328 if (domain->hash_size > 2 && domain->hash_tab) {
382 twoaday 273 /* Use the hashing table. */
383     DWORD len = strlen (msgid);
384     DWORD hash_val = hash_string (msgid);
385     DWORD idx = hash_val % domain->hash_size;
386     DWORD incr = 1 + (hash_val % (domain->hash_size - 2));
387     DWORD nstr = SWAPIT (domain->must_swap, domain->hash_tab[idx]);
388    
389 twoaday 328 if (!nstr) /* Hash table entry is empty. */
390 twoaday 273 goto not_found;
391 twoaday 328
392     if (SWAPIT(domain->must_swap,
393 twoaday 273 domain->orig_tab[nstr - 1].length) == len
394 twoaday 328 && !strcmp (msgid,
395     domain->data + SWAPIT (domain->must_swap,
396     domain->orig_tab[nstr - 1].offset)))
397     return get_string (domain, nstr - 1);
398 twoaday 273 for(;;) {
399     if (idx >= domain->hash_size - incr)
400     idx -= domain->hash_size - incr;
401     else
402     idx += incr;
403 twoaday 366 nstr = SWAPIT (domain->must_swap, domain->hash_tab[idx]);
404 twoaday 328 if (!nstr)
405 twoaday 273 goto not_found; /* Hash table entry is empty. */
406    
407 twoaday 328 if (SWAPIT (domain->must_swap,
408     domain->orig_tab[nstr - 1].length) == len
409     && !strcmp (msgid,
410     domain->data + SWAPIT (domain->must_swap,
411     domain->orig_tab[nstr - 1].offset)))
412    
413     return get_string (domain, nstr-1);
414 twoaday 273 } /* NOTREACHED */
415     }
416    
417     /* Now we try the default method: binary search in the sorted
418     array of messages. */
419     bottom = 0;
420     top = domain->nstrings;
421 twoaday 366 while (bottom < top) {
422 twoaday 273 act = (bottom + top) / 2;
423 twoaday 366 int cmp_val = strcmp (msgid, domain->data + SWAPIT(domain->must_swap,
424     domain->orig_tab[act].offset));
425 twoaday 273 if (cmp_val < 0)
426     top = act;
427     else if (cmp_val > 0)
428     bottom = act + 1;
429 twoaday 328 else
430     return get_string (domain, act);
431 twoaday 273 }
432    
433     not_found:
434     return msgid;
435     }
436    
437    
438 twoaday 368
439 twoaday 273 /* Map the user specific language ID to a gettext conform language string.
440 twoaday 368 English "en" is defined as the fallback.
441 twoaday 273 Example: LANG_GERMAN -> "de" */
442     const char*
443 twoaday 278 gettext_get_langid (void)
444 twoaday 273 {
445 twoaday 366 LANGID lang = GetUserDefaultLangID ();
446 twoaday 368
447 twoaday 328 for (int i=0; lang_list[i].id; i++) {
448 twoaday 273 if (PRIMARYLANGID (lang) == lang_list[i].langid)
449     return lang_list[i].id;
450     }
451 twoaday 368
452     return "en";
453 twoaday 273 }
454    
455    
456     /* Take a table with control item IDs and their translation
457     and set each text to the translated value. */
458     void
459 twoaday 366 gettext_localize_dialog (HWND dlg, struct gettext_tab_s *tab, const char *title)
460 twoaday 273 {
461 twoaday 328 for (int i=0; tab[i].trans != NULL; i++)
462 twoaday 273 SetDlgItemText (dlg, tab[i].ctlid, tab[i].trans);
463 twoaday 328 if (title != NULL)
464 twoaday 273 SetWindowText (dlg, title);
465     }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26