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