[W32] Make use of the LANGUAGE envvar.
[gnupg.git] / util / simple-gettext.c
1 /* simple-gettext.c  - a simplified version of gettext.
2  * Copyright (C) 1995, 1996, 1997, 1999,
3  *               2005 Free Software Foundation, Inc.
4  *
5  * This file is part of GnuPG.
6  *
7  * GnuPG is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * GnuPG is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
20  * USA.
21  */
22
23 /* This is a simplified version of gettext written by Ulrich Drepper.
24  * It is used for the Win32 version of GnuPG beucase all the overhead
25  * of gettext is not needed and we have to do some special Win32 stuff.
26  * I decided that this is far easier than to tweak gettext for the special
27  * cases (I tried it but it is a lot of code).  wk 15.09.99
28  */
29
30 #include <config.h>
31 #ifdef USE_SIMPLE_GETTEXT
32 #if !defined (_WIN32) && !defined (__CYGWIN32__)
33 #error This file can only be used under Windows or Cygwin32
34 #endif
35
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <ctype.h>
40 #include <errno.h>
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include "types.h"
44 #include "util.h"
45
46 #include "windows.h" /* For GetModuleFileName.  */
47
48 /* The magic number of the GNU message catalog format.  */
49 #define MAGIC         0x950412de
50 #define MAGIC_SWAPPED 0xde120495
51
52 /* Revision number of the currently used .mo (binary) file format.  */
53 #define MO_REVISION_NUMBER 0
54
55
56 /* Header for binary .mo file format.  */
57 struct mo_file_header
58 {
59   /* The magic number.  */
60   u32 magic;
61   /* The revision number of the file format.  */
62   u32 revision;
63   /* The number of strings pairs.  */
64   u32 nstrings;
65   /* Offset of table with start offsets of original strings.  */
66   u32 orig_tab_offset;
67   /* Offset of table with start offsets of translation strings.  */
68   u32 trans_tab_offset;
69   /* Size of hashing table.  */
70   u32 hash_tab_size;
71   /* Offset of first hashing entry.  */
72   u32 hash_tab_offset;
73 };
74
75 struct string_desc
76 {
77   /* Length of addressed string.  */
78   u32 length;
79   /* Offset of string in file.  */
80   u32 offset;
81 };
82
83
84 struct overflow_space_s
85 {
86   struct overflow_space_s *next;
87   u32 idx;
88   char d[1];
89 };
90
91 struct loaded_domain
92 {
93   char *data;
94   int must_swap;
95   u32 nstrings;
96   char *mapped;  /* 0 = not yet mapped, 1 = mapped,
97                     2 = mapped to
98                     overflow space */
99   struct overflow_space_s *overflow_space;
100   struct string_desc *orig_tab;
101   struct string_desc *trans_tab;
102   u32 hash_size;
103   u32 *hash_tab;
104 };
105
106
107 static struct loaded_domain *the_domain;
108
109 static __inline__ u32
110 do_swap_u32( u32 i )
111 {
112   return (i << 24) | ((i & 0xff00) << 8) | ((i >> 8) & 0xff00) | (i >> 24);
113 }
114
115 #define SWAPIT(flag, data) ((flag) ? do_swap_u32(data) : (data) )
116
117
118 /* We assume to have `unsigned long int' value with at least 32 bits.  */
119 #define HASHWORDBITS 32
120
121 /* The so called `hashpjw' function by P.J. Weinberger
122    [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools,
123    1986, 1987 Bell Telephone Laboratories, Inc.]  */
124
125 static __inline__ ulong
126 hash_string( const char *str_param )
127 {
128     unsigned long int hval, g;
129     const char *str = str_param;
130
131     hval = 0;
132     while (*str != '\0')
133     {
134         hval <<= 4;
135         hval += (unsigned long int) *str++;
136         g = hval & ((unsigned long int) 0xf << (HASHWORDBITS - 4));
137         if (g != 0)
138         {
139           hval ^= g >> (HASHWORDBITS - 8);
140           hval ^= g;
141         }
142     }
143     return hval;
144 }
145
146
147 static struct loaded_domain *
148 load_domain( const char *filename )
149 {
150     FILE *fp;
151     size_t size;
152     struct stat st;
153     struct mo_file_header *data = NULL;
154     struct loaded_domain *domain = NULL;
155     size_t to_read;
156     char *read_ptr;
157
158     fp = fopen( filename, "rb" );
159     if( !fp )
160        return NULL; /* can't open the file */
161     /* we must know about the size of the file */
162     if( fstat( fileno(fp ), &st )
163         || (size = (size_t)st.st_size) != st.st_size
164         || size < sizeof (struct mo_file_header) ) {
165         fclose( fp );
166         return NULL;
167     }
168
169     data = malloc( size );
170     if( !data ) {
171         fclose( fp );
172         return NULL; /* out of memory */
173     }
174
175     to_read = size;
176     read_ptr = (char *) data;
177     do {
178         long int nb = fread( read_ptr, 1, to_read, fp );
179         if( nb < to_read ) {
180             fclose (fp);
181             free(data);
182             return NULL; /* read error */
183         }
184         read_ptr += nb;
185         to_read -= nb;
186     } while( to_read > 0 );
187     fclose (fp);
188
189     /* Using the magic number we can test whether it really is a message
190      * catalog file.  */
191     if( data->magic != MAGIC && data->magic != MAGIC_SWAPPED ) {
192         /* The magic number is wrong: not a message catalog file.  */
193         free( data );
194         return NULL;
195     }
196
197     domain = calloc( 1, sizeof *domain );
198     if( !domain )  {
199         free( data );
200         return NULL;
201     }
202     domain->data = (char *) data;
203     domain->must_swap = data->magic != MAGIC;
204
205     /* Fill in the information about the available tables.  */
206     switch( SWAPIT(domain->must_swap, data->revision) ) {
207       case 0:
208         domain->nstrings = SWAPIT(domain->must_swap, data->nstrings);
209         domain->orig_tab = (struct string_desc *)
210           ((char *) data + SWAPIT(domain->must_swap, data->orig_tab_offset));
211         domain->trans_tab = (struct string_desc *)
212           ((char *) data + SWAPIT(domain->must_swap, data->trans_tab_offset));
213         domain->hash_size = SWAPIT(domain->must_swap, data->hash_tab_size);
214         domain->hash_tab = (u32 *)
215           ((char *) data + SWAPIT(domain->must_swap, data->hash_tab_offset));
216       break;
217
218       default: /* This is an invalid revision.  */
219         free( data );
220         free( domain );
221         return NULL;
222     }
223
224     /* Allocate an array to keep track of code page mappings. */
225     domain->mapped = calloc( 1, domain->nstrings );
226     if( !domain->mapped ) {
227         free( data );
228         free( domain );
229         return NULL;
230     }
231
232     return domain;
233 }
234
235
236 /* Set the file used for translations.  Pass a NULL to disable
237    translation.  A new filename may be set at anytime.  WARNING: After
238    changing the filename you should not access any data retrieved by
239    gettext().
240
241    If REGKEY is not NULL, the function tries to selected the language
242    the registry key "Lang" below that key.  If in addition the
243    environment variable LANGUAGE has been set, that value will
244    override a value set by the registry key.
245  */
246 int
247 set_gettext_file ( const char *filename, const char *regkey )
248 {
249   struct loaded_domain *domain = NULL;
250
251   if ( filename && *filename )
252     {
253       if ( filename[0] == '/'
254 #ifdef HAVE_DRIVE_LETTERS
255            || ( isalpha(filename[0])
256                 && filename[1] == ':'
257                 && (filename[2] == '/' || filename[2] == '\\') )
258 #endif
259            )
260         {
261           /* absolute path - use it as is */
262           domain = load_domain( filename );
263         }
264       else if (regkey)  /* Standard.  */
265         {
266           char *instdir, *langid, *fname;
267           char *p;
268           int envvar_mode = 0;
269           
270         again:
271           if (!envvar_mode && (p = getenv ("LANGUAGE")) && *p)
272             {
273               envvar_mode = 1;
274               langid = malloc (strlen (p)+1);
275               if (!langid)
276                 return -1;
277               strcpy (langid, p);
278               /* We only make use of the first language given.  Strip
279                  the rest.  */
280               p = strchr (langid, ':');
281               if (p)
282                 *p = 0;
283               
284               /* In the $LANGUAGE case we do not use the registered
285                  installation directory but the one where the gpg
286                  binary has been found.  */
287               instdir = malloc (MAX_PATH+5);
288               if ( !instdir || !GetModuleFileName (NULL, instdir, MAX_PATH) )
289                 {
290                   free (langid);
291                   free (instdir);
292                   return -1; /* Error getting the process' file name.  */
293                 }
294               p = strrchr (instdir, DIRSEP_C);
295               if (!p)
296                 {
297                   free (langid);
298                   free (instdir);
299                   return -1; /* Invalid file name returned.  */
300                 }
301               *p = 0;
302             }
303           else
304             {
305               instdir = read_w32_registry_string ("HKEY_LOCAL_MACHINE",
306                                                   regkey,
307                                                   "Install Directory");
308               if (!instdir)
309                 return -1;
310               langid = read_w32_registry_string (NULL, /* HKCU then HKLM */
311                                                  regkey,
312                                                  "Lang");
313               if (!langid)
314                 {
315                   free (instdir);
316                   return -1;
317                 }
318             }
319           
320           /* Strip stuff after a dot in case the user tried to enter
321              the entire locale syntacs as usual for POSIX.  */
322           p = strchr (langid, '.');
323           if (p)
324             *p = 0;
325           
326           /* Build the key: "<instdir>/<domain>.nls/<langid>.mo" We
327              use a directory below the installation directory with the
328              domain included in case the software has been insalled
329              with other software altogether at the same place.  */
330           fname = malloc (strlen (instdir) + 1 + strlen (filename) + 5
331                           + strlen (langid) + 3 + 1);
332           if (!fname)
333             {
334               free (instdir);
335               free (langid);
336               return -1;
337             }
338           strcpy (stpcpy (stpcpy (stpcpy (stpcpy ( stpcpy (fname,
339                   instdir),"\\"), filename), ".nls\\"), langid), ".mo");
340           free (instdir);
341           free (langid);
342
343           /* Better make sure that we don't mix forward and backward
344              slashes.  It seems that some Windoze versions don't
345              accept this. */
346           for (p=fname; *p; p++) 
347             {
348               if (*p == '/')
349                 *p = '\\';
350             }
351           domain = load_domain (fname);
352           free(fname);
353
354           if (!domain && envvar_mode == 1)
355             {
356               /* In case it failed, we try again using the registry
357                  method. */
358               envvar_mode++;
359               goto again;
360             }
361         }
362       
363
364       if (!domain)
365         return -1;
366     }
367
368   if ( the_domain )
369     {
370       struct overflow_space_s *os, *os2;
371
372       free ( the_domain->data );
373       free ( the_domain->mapped );
374       for (os=the_domain->overflow_space; os; os = os2)
375         {
376           os2 = os->next;
377           free (os);
378         }
379       free ( the_domain );
380       the_domain = NULL;
381     }
382   the_domain = domain;
383   return 0;
384 }
385
386
387 static const char*
388 get_string( struct loaded_domain *domain, u32 idx )
389 {
390   struct overflow_space_s *os;
391   char *p;
392
393   p = domain->data + SWAPIT(domain->must_swap, domain->trans_tab[idx].offset);
394   if (!domain->mapped[idx]) 
395     {
396       size_t plen, buflen;
397       char *buf;
398
399       domain->mapped[idx] = 1;
400
401       plen = strlen (p);
402       buf = utf8_to_native (p, plen, -1);
403       buflen = strlen (buf);
404       if (buflen <= plen)
405         strcpy (p, buf);
406       else
407         {
408           /* There is not enough space for the translation - store it
409              in the overflow_space else and mark that in the mapped
410              array.  Because we expect that this won't happen too
411              often, we use a simple linked list.  */
412           os = malloc (sizeof *os + buflen);
413           if (os)
414             {
415               os->idx = idx;
416               strcpy (os->d, buf);
417               os->next = domain->overflow_space;
418               domain->overflow_space = os;
419               p = os->d;
420             }
421           else
422             p = "ERROR in GETTEXT MALLOC";
423         }
424       xfree (buf);
425     }
426   else if (domain->mapped[idx] == 2) 
427     { /* We need to get the string from the overflow_space. */
428       for (os=domain->overflow_space; os; os = os->next)
429         if (os->idx == idx)
430           return (const char*)os->d;
431       p = "ERROR in GETTEXT\n";
432     }
433   return (const char*)p;
434 }
435
436
437
438 const char *
439 gettext( const char *msgid )
440 {
441     struct loaded_domain *domain;
442     size_t act = 0;
443     size_t top, bottom;
444
445     if( !(domain = the_domain) )
446         goto not_found;
447
448     /* Locate the MSGID and its translation.  */
449     if( domain->hash_size > 2 && domain->hash_tab ) {
450         /* Use the hashing table.  */
451         u32 len = strlen (msgid);
452         u32 hash_val = hash_string (msgid);
453         u32 idx = hash_val % domain->hash_size;
454         u32 incr = 1 + (hash_val % (domain->hash_size - 2));
455         u32 nstr = SWAPIT (domain->must_swap, domain->hash_tab[idx]);
456
457         if ( !nstr ) /* Hash table entry is empty.  */
458             goto not_found;
459
460         if( SWAPIT(domain->must_swap,
461                     domain->orig_tab[nstr - 1].length) == len
462             && !strcmp( msgid,
463                        domain->data + SWAPIT(domain->must_swap,
464                                     domain->orig_tab[nstr - 1].offset)) )
465             return get_string( domain, nstr - 1 );
466
467         for(;;) {
468             if (idx >= domain->hash_size - incr)
469                 idx -= domain->hash_size - incr;
470             else
471                 idx += incr;
472
473             nstr = SWAPIT(domain->must_swap, domain->hash_tab[idx]);
474             if( !nstr )
475                 goto not_found; /* Hash table entry is empty.  */
476
477             if ( SWAPIT(domain->must_swap,
478                                 domain->orig_tab[nstr - 1].length) == len
479                  && !strcmp (msgid,
480                          domain->data + SWAPIT(domain->must_swap,
481                                            domain->orig_tab[nstr - 1].offset)))
482                 return get_string( domain, nstr-1 );
483         }
484         /* NOTREACHED */
485     }
486
487     /* Now we try the default method:  binary search in the sorted
488        array of messages.  */
489     bottom = 0;
490     top = domain->nstrings;
491     while( bottom < top ) {
492         int cmp_val;
493
494         act = (bottom + top) / 2;
495         cmp_val = strcmp(msgid, domain->data
496                                + SWAPIT(domain->must_swap,
497                                         domain->orig_tab[act].offset));
498         if (cmp_val < 0)
499             top = act;
500         else if (cmp_val > 0)
501             bottom = act + 1;
502         else
503             return get_string( domain, act );
504     }
505
506   not_found:
507     return msgid;
508 }
509
510 #if 0
511        unsigned int cp1, cp2;
512
513        cp1 = GetConsoleCP();
514        cp2 = GetConsoleOutputCP();
515
516        log_info("InputCP=%u  OutputCP=%u\n", cp1, cp2 );
517
518        if( !SetConsoleOutputCP( 1252 ) )
519             log_info("SetConsoleOutputCP failed: %s\n", w32_strerror (0));
520
521        cp1 = GetConsoleCP();
522        cp2 = GetConsoleOutputCP();
523        log_info("InputCP=%u  OutputCP=%u after switch1\n", cp1, cp2 );
524 #endif
525
526 #endif /* USE_SIMPLE_GETTEXT */