1 ///////////////////////////////////////////////////////////////////////////////
2 // Original Name:        src/common/intl.cpp
3 // Original Purpose:     Internationalization and localisation for wxWidgets
4 // Original Author:      Vadim Zeitlin
5 // Modified by: Charlie Fenton for BOINC  13 June, 2013
6 // Created:     29/01/98
7 // Copyright:   (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
8 // Licence:     wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10 //
11 // This file is part of BOINC.
12 // http://boinc.berkeley.edu
13 //
14 // Simplified string localization code for use in BOINC adapted from
15 // wxWidgets src/common/intl.cpp.
16 //
17 // For sample code to get preferred system language, see
18 // wxLocale::GetSystemLanguage() in wxWidgets src/common/intl.cpp
19 //
20 // Those portions of this code which are taken from wxWidgets are
21 // Copyright:   (c) 1998 Vadim Zeitlin
22 // and are covered by the wxWidgets license which can be found here:
23 // <http://www.wxwidgets.org/about/licence3.txt>
24 //
25 // This code assumes all catalogs are encoded UTF-8
26 // (Note: wxstd.mo files are encoded ISO 8859-1 not UTF-8.)
27 //
28 // We recommended that you first call BOINCTranslationAddCatalog()
29 // for each desired catalog (*.mo) file for the user's preferred
30 // language, then for each desired catalog (*.mo) file for the
31 // user's second preferred language and (optionally) then for each
32 // desired catalog (*.mo) file for the user's third preferred
33 // language.  This will make it more likley that a translation
34 // will be found in some language useful to the user.
35 //
36 
37 #include <stdint.h>
38 #include <cstring>
39 #include <cstdio>
40 #include <cstdlib>
41 #include <sys/param.h>  // for MAXPATHLEN
42 #include <sys/stat.h>   // For stat()
43 #include <filesys.h>
44 
45 #include "translate.h"
46 
47 static const uint32_t MSGCATALOG_MAGIC    = 0x950412de;
48 static const uint32_t MSGCATALOG_MAGIC_SW = 0xde120495;
49 
50 #define MAXCATALOGS 20
51 #define VERBOSE true
52 
53 // an entry in the string table
54 struct MsgTableEntry {
55     uint32_t    nLen;           // length of the string
56     uint32_t    ofsString;      // pointer to the string
57 };
58 
59 // header of a .mo file
60 struct MsgCatalogHeader {
61     uint32_t    magic,          // offset +00:  magic id
62                 revision,       //        +04:  revision
63                 numStrings;     //        +08:  number of strings in the file
64     uint32_t    ofsOrigTable,   //        +0C:  start of original string table
65                 ofsTransTable;  //        +10:  start of translated string table
66     uint32_t    nHashSize,      //        +14:  hash table size
67                 ofsHashTable;   //        +18:  offset of hash table start
68 };
69 
70 
71 struct MsgCatalogData {
72     uint8_t         *pData;             // Pointer to catalog data
73     uint32_t        nSize;              // Amount of memory pointed to by pData.
74     uint32_t        NumStrings;         // number of strings in this domain
75     bool            bSwapped;           // wrong endianness?
76     MsgTableEntry   *pOrigTable;        // pointer to original strings
77     MsgTableEntry   *pTransTable;       // pointer to translated strings
78     char            languageCode[32];   // language code (e.g., "it_IT")
79     char            catalogName[128];   // catalog name (e.g., "BOINC-Setup")
80 };
81 
82 static struct MsgCatalogData theCatalogData[MAXCATALOGS];
83 
84 static uint32_t     numLoadedCatalogs = 0;
85 
86 // swap the 2 halves of 32 bit integer if needed
Swap(uint32_t ui,bool bSwapped)87 static uint32_t Swap(uint32_t ui, bool bSwapped) {
88       return bSwapped ? (ui << 24) | ((ui & 0xff00) << 8) |
89                           ((ui >> 8) & 0xff00) | (ui >> 24)
90                         : ui;
91 }
92 
93 
94 // load a catalog from disk
95 // open disk file and read in it's contents
96 //
97 // catalogsDir is the path to the locale directory in
98 // the BOINC Data directory (ending in "locale/").
99 //
100 // language code is the standad language code such
101 // as "fr" or "zh_CN".
102 //
103 // catalogName is the domain (the file name of the
104 // *.mo file without the .mo such as "BOINC-Manager").
105 //
106 // Returns true if successful.
107 //
LoadCatalog(const char * catalogsDir,const char * languageCode,const char * catalogName)108 static bool LoadCatalog(const char * catalogsDir,
109                 const char *languageCode,
110                 const char *catalogName
111                 ) {
112     unsigned int j;
113     char searchPath[MAXPATHLEN];
114     struct stat sbuf;
115     char temp[32];
116     char *underscore;
117     FILE * f;
118     uint8_t *pData;
119     uint32_t nSize;
120     uint32_t NumStrings;
121     bool  bSwapped;
122     MsgTableEntry *pOrigTable;
123     MsgTableEntry *pTransTable;
124     MsgCatalogData *pCatalog;
125 
126 #if VERBOSE
127     fprintf(stderr, "Attempting to load catalog %s for language code %s\n",
128             catalogName, languageCode);
129 #endif
130     for (j=0; j<numLoadedCatalogs; ++j) {
131         pCatalog = &(theCatalogData[j]);
132         if (!strcmp(pCatalog->catalogName, catalogName)) {
133             if (!strcmp(pCatalog->languageCode, languageCode)) {
134                 // Don't load a catalog twice for the same language
135 #if VERBOSE
136                 fprintf(stderr, "Ignoring Catalog %s for language code %s; it was already loaded\n",
137                         catalogName, languageCode);
138 #endif
139                 return true;    // Already loaded
140             }
141             // Don't load more languages for this catalog
142             // if we've already "loaded" it for English
143             if (!strcmp(pCatalog->languageCode, "en")) {
144 #if VERBOSE
145                 fprintf(stderr, "Ignoring Catalog %s for language code %s; after English for same catalog\n",
146                         catalogName, languageCode);
147 #endif
148                 return true;    // Already loaded
149             }
150         }
151     }
152 
153     // Since the original strings are English, no
154     // translation is needed for language en.
155     if (!strcmp("en", languageCode)) {
156         pCatalog = &(theCatalogData[numLoadedCatalogs++]);
157         strlcpy(pCatalog->languageCode, languageCode, sizeof(pCatalog->languageCode));
158         strlcpy(pCatalog->catalogName, catalogName, sizeof(pCatalog->catalogName));
159         return true;
160     }
161 
162     strlcpy(searchPath, catalogsDir, sizeof(searchPath));
163     strlcat(searchPath, languageCode, sizeof(searchPath));
164     strlcat(searchPath, "/", sizeof(searchPath));
165     strlcat(searchPath, catalogName, sizeof(searchPath));
166     strlcat(searchPath, ".mo", sizeof(searchPath));
167     if (stat(searchPath, &sbuf) != 0) {
168         // Try just base locale name: for things like "fr_BE" (belgium
169         // french) we should use "fr" if no belgium specific message
170         // catalogs exist
171         strlcpy(temp, languageCode, sizeof(temp));
172         underscore = strchr(temp, (int)'_');
173         if (underscore == NULL) return false;
174         *underscore = '\0';
175         strlcpy(searchPath, catalogsDir, sizeof(searchPath));
176         strlcat(searchPath, temp, sizeof(searchPath));
177         strlcat(searchPath, "/", sizeof(searchPath));
178         strlcat(searchPath, catalogName, sizeof(searchPath));
179         strlcat(searchPath, ".mo", sizeof(searchPath));
180         if (stat(searchPath, &sbuf) != 0) return false;
181     }
182 
183 
184     pData = (uint8_t*)malloc(sbuf.st_size);
185     if (pData == NULL) return false;
186     f = fopen(searchPath, "r");
187     if (f == NULL) {
188         free(pData);
189         pData = NULL;
190         return false;
191     }
192     nSize = fread(pData, 1, sbuf.st_size, f);
193     fclose(f);
194     if (nSize != sbuf.st_size) {
195         free(pData);
196         pData = NULL;
197         return false;
198     }
199 
200     // examine header
201     bool bValid = nSize + (size_t)0 > sizeof(MsgCatalogHeader);
202 
203     MsgCatalogHeader *pHeader = (MsgCatalogHeader *)pData;
204     if ( bValid ) {
205     // we'll have to swap all the integers if it's true
206     bSwapped = pHeader->magic == MSGCATALOG_MAGIC_SW;
207 
208     // check the magic number
209     bValid = bSwapped || pHeader->magic == MSGCATALOG_MAGIC;
210     }
211 
212     if ( !bValid ) {
213         // it's either too short or has incorrect magic number
214         free(pData);
215         pData = NULL;
216         return false;
217     }
218 
219     // initialize
220     NumStrings  = Swap(pHeader->numStrings, bSwapped);
221     if (NumStrings <= 1) {
222         // This file has no translations (is effectively
223         // empty) so don't load it for better efficiency.
224 #if VERBOSE
225         fprintf(stderr, "File %s contains no translated strings!\n", searchPath);
226 #endif
227         free(pData);
228         pData = NULL;
229         return true;    // Not an error
230     }
231     pOrigTable  = (MsgTableEntry *)(pData +
232                    Swap(pHeader->ofsOrigTable, bSwapped));
233     pTransTable = (MsgTableEntry *)(pData +
234                    Swap(pHeader->ofsTransTable, bSwapped));
235 
236 
237     // now parse catalog's header and try to extract catalog charset
238     uint32_t ofsString = Swap(pOrigTable->ofsString, bSwapped);
239     // this check could fail for a corrupt message catalog
240     if ( ofsString + Swap(pOrigTable->nLen, bSwapped) <= nSize) {
241         char *begin = strstr((char *)(pData + Swap(pTransTable->ofsString, bSwapped)),
242                             "Content-Type: text/plain; charset="
243                             );
244         if (begin != NULL) {
245             begin += 34; //strlen("Content-Type: text/plain; charset=")
246             if (strncasecmp(begin, "utf-8", 5)) {
247 #if VERBOSE
248                 fprintf(stderr, "File %s is not utf-8!\n", searchPath);
249 #endif
250                 free(pData);
251                 pData = NULL;
252                 return false;
253             }
254         }
255     }
256 
257     pCatalog = &(theCatalogData[numLoadedCatalogs++]);
258     pCatalog->pData = pData;
259     pCatalog->nSize = nSize;
260     pCatalog->NumStrings = NumStrings;
261     pCatalog->bSwapped = bSwapped;
262     pCatalog->pOrigTable = pOrigTable;
263     pCatalog->pTransTable = pTransTable;
264     strlcpy(pCatalog->languageCode, languageCode, sizeof(pCatalog->languageCode));
265     strlcpy(pCatalog->catalogName, catalogName, sizeof(pCatalog->catalogName));
266 #if VERBOSE
267     fprintf(stderr, "Successfully loaded catalog %s for language code %s\n",
268             catalogName, languageCode);
269 #endif
270     return true;
271 }
272 
273 
274 // Searches through the catalogs in the order they were added
275 // until it finds a translation for the src string, and
276 // returns a ponter to the UTF-8 encoded localized string.
277 // Returns a pointer to the original string if no translation
278 // was found.
279 //
_(char * src)280 uint8_t * _(char *src) {
281     unsigned int i, j;
282     MsgCatalogData *pCatalog;
283 
284     for (j=0; j<numLoadedCatalogs; ++j) {
285         pCatalog = &(theCatalogData[j]);
286 
287         // Since the original strings are English, no
288         // translation is needed for language en.
289         if (!strcmp("en", pCatalog->languageCode)) {
290             continue;   // Try next catalog
291         }
292 
293         if (pCatalog->pData == NULL) continue;    // Should never happen
294         for (i=0; i<pCatalog->NumStrings; ++i) {
295             if (!strcmp((char *)pCatalog->pData + pCatalog->pOrigTable[i].ofsString, src)) {
296                 return (pCatalog->pData + pCatalog->pTransTable[i].ofsString);
297             }
298         }
299     }
300 
301     return (uint8_t *)src;
302 }
303 
304 
305 // catalogsDir is the path to the locale directory in
306 // the BOINC Data directory (ending in "locale/").
307 //
308 // language code is the standad language code such
309 // as "fr" or "zh_CN".
310 //
311 // catalogName is the domain (the file name of the
312 // *.mo file without the .mo such as "BOINC-Manager").
313 //
314 // Returns true if successful.
315 //
BOINCTranslationAddCatalog(const char * catalogsDir,const char * languageCode,const char * catalogName)316 bool BOINCTranslationAddCatalog(const char * catalogsDir,
317                                 const char *languageCode,
318                                 const char * catalogName
319                                 ) {
320     bool success = false;
321     DIRREF dirp;
322     char filename[64];
323     int retval;
324 
325     if (numLoadedCatalogs >= (MAXCATALOGS)) {
326 #if VERBOSE
327         fprintf(stderr, "Trying to load too many catalogs\n");
328 #endif
329         return false;
330     }
331 
332     // Add a catalog for this exact language and region code
333     success = LoadCatalog(catalogsDir, languageCode, catalogName);
334 
335     // Add catalogs for the same language code but different region codes
336     dirp = dir_open(catalogsDir);
337     if (dirp) {
338         while (true) {
339             retval = dir_scan(filename, dirp, sizeof(filename));
340             if (retval) break;
341             if (!strcmp(languageCode, filename)) continue;  // Already added above
342             if (!strncmp(languageCode, filename, 2)) {  // First 2 characters match
343                 if (LoadCatalog(catalogsDir, filename, catalogName)) {
344                     success = true;
345                 }
346             }
347         }
348 
349         dir_close(dirp);
350     }
351 
352     return success;
353 }
354 
355 
BOINCTranslationInit()356 void BOINCTranslationInit() {
357     int i;
358     MsgCatalogData *pCatalog;
359 
360     numLoadedCatalogs = 0;
361 
362     for (i=0; i<MAXCATALOGS; ++i) {
363         pCatalog = &(theCatalogData[i]);
364         pCatalog->pData = NULL;
365         pCatalog->nSize = 0;
366         pCatalog->NumStrings = 0;
367         pCatalog->bSwapped = false;
368         pCatalog->pOrigTable = NULL;
369         pCatalog->pTransTable = NULL;
370         pCatalog->languageCode[0] = '\0';
371         pCatalog->catalogName[0] = '\0';
372 
373     }
374 }
375 
376 
BOINCTranslationCleanup()377 void BOINCTranslationCleanup() {
378     int i;
379     MsgCatalogData *pCatalog;
380 
381     for (i=0; i<MAXCATALOGS; ++i) {
382         pCatalog = &(theCatalogData[i]);
383         if (pCatalog->pData) free(pCatalog->pData);
384         pCatalog->pData = NULL;
385         pCatalog->nSize = 0;
386         pCatalog->NumStrings = 0;
387         pCatalog->bSwapped = false;
388         pCatalog->pOrigTable = NULL;
389         pCatalog->pTransTable = NULL;
390         pCatalog->languageCode[0] = '\0';
391         pCatalog->catalogName[0] = '\0';
392     }
393 
394     numLoadedCatalogs = 0;
395 }
396