]> git.saurik.com Git - wxWidgets.git/blob - src/common/intl.cpp
...
[wxWidgets.git] / src / common / intl.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: intl.cpp
3 // Purpose: Internationalization and localisation for wxWindows
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 29/01/98
7 // RCS-ID: $Id$
8 // Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence: wxWindows license
10 /////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declaration
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #ifdef __GNUG__
21 #pragma implementation "intl.h"
22 #endif
23
24 // For compilers that support precompilation, includes "wx.h".
25 #include "wx/wxprec.h"
26
27 #ifdef __BORLANDC__
28 #pragma hdrstop
29 #endif
30
31 // standard headers
32 #include <locale.h>
33 #include <ctype.h>
34
35 // wxWindows
36 #include "wx/defs.h"
37 #include "wx/string.h"
38 #include "wx/intl.h"
39 #include "wx/file.h"
40 #include "wx/log.h"
41 #include "wx/utils.h"
42
43 #include <stdlib.h>
44
45 // ----------------------------------------------------------------------------
46 // simple types
47 // ----------------------------------------------------------------------------
48
49 // FIXME adjust if necessary
50 typedef unsigned char size_t8;
51 typedef unsigned long size_t32;
52
53 // ----------------------------------------------------------------------------
54 // constants
55 // ----------------------------------------------------------------------------
56
57 // magic number identifying the .mo format file
58 const size_t32 MSGCATALOG_MAGIC = 0x950412de;
59 const size_t32 MSGCATALOG_MAGIC_SW = 0xde120495;
60
61 // extension of ".mo" files
62 #define MSGCATALOG_EXTENSION ".mo"
63
64 // ----------------------------------------------------------------------------
65 // global functions
66 // ----------------------------------------------------------------------------
67
68 // suppress further error messages about missing translations
69 // (if you don't have one catalog file, you wouldn't like to see the
70 // error message for each string in it, so normally it's given only
71 // once)
72 void wxSuppressTransErrors();
73
74 // restore the logging
75 void wxRestoreTransErrors();
76
77 // get the current state
78 bool wxIsLoggingTransErrors();
79
80 static wxLocale *wxSetLocale(wxLocale *pLocale);
81
82 // ----------------------------------------------------------------------------
83 // wxMsgCatalog corresponds to one disk-file message catalog.
84 //
85 // This is a "low-level" class and is used only by wxLocale (that's why
86 // it's designed to be stored in a linked list)
87 // ----------------------------------------------------------------------------
88
89 class wxMsgCatalog
90 {
91 public:
92 // ctor & dtor
93 wxMsgCatalog();
94 ~wxMsgCatalog();
95
96 // load the catalog from disk (szDirPrefix corresponds to language)
97 bool Load(const char *szDirPrefix, const char *szName);
98 bool IsLoaded() const { return m_pData != NULL; }
99
100 // get name of the catalog
101 const char *GetName() const { return m_pszName; }
102
103 // get the translated string: returns NULL if not found
104 const char *GetString(const char *sz) const;
105
106 // public variable pointing to the next element in a linked list (or NULL)
107 wxMsgCatalog *m_pNext;
108
109 private:
110 // this implementation is binary compatible with GNU gettext() version 0.10
111
112 // an entry in the string table
113 struct wxMsgTableEntry
114 {
115 size_t32 nLen; // length of the string
116 size_t32 ofsString; // pointer to the string
117 };
118
119 // header of a .mo file
120 struct wxMsgCatalogHeader
121 {
122 size_t32 magic, // offset +00: magic id
123 revision, // +04: revision
124 numStrings; // +08: number of strings in the file
125 size_t32 ofsOrigTable, // +0C: start of original string table
126 ofsTransTable; // +10: start of translated string table
127 size_t32 nHashSize, // +14: hash table size
128 ofsHashTable; // +18: offset of hash table start
129 };
130
131 // all data is stored here, NULL if no data loaded
132 size_t8 *m_pData;
133
134 // data description
135 size_t32 m_numStrings, // number of strings in this domain
136 m_nHashSize; // number of entries in hash table
137 size_t32 *m_pHashTable; // pointer to hash table
138 wxMsgTableEntry *m_pOrigTable, // pointer to original strings
139 *m_pTransTable; // translated
140
141 const char *StringAtOfs(wxMsgTableEntry *pTable, size_t32 index) const
142 { return (const char *)(m_pData + Swap(pTable[index].ofsString)); }
143
144 // utility functions
145 // calculate the hash value of given string
146 static inline size_t32 GetHash(const char *sz);
147 // big<->little endian
148 inline size_t32 Swap(size_t32 ui) const;
149
150 // internal state
151 bool HasHashTable() const // true if hash table is present
152 { return m_nHashSize > 2 && m_pHashTable != NULL; }
153
154 bool m_bSwapped; // wrong endianness?
155
156 char *m_pszName; // name of the domain
157 };
158
159 // ----------------------------------------------------------------------------
160 // global variables
161 // ----------------------------------------------------------------------------
162
163 // the list of the directories to search for message catalog files
164 static wxArrayString s_searchPrefixes;
165
166 // ============================================================================
167 // implementation
168 // ============================================================================
169
170 // ----------------------------------------------------------------------------
171 // wxMsgCatalog class
172 // ----------------------------------------------------------------------------
173
174 // calculate hash value using the so called hashpjw function by P.J. Weinberger
175 // [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools]
176 size_t32 wxMsgCatalog::GetHash(const char *sz)
177 {
178 #define HASHWORDBITS 32 // the length of size_t32
179
180 size_t32 hval = 0;
181 size_t32 g;
182 while ( *sz != '\0' ) {
183 hval <<= 4;
184 hval += (size_t32)*sz++;
185 g = hval & ((size_t32)0xf << (HASHWORDBITS - 4));
186 if ( g != 0 ) {
187 hval ^= g >> (HASHWORDBITS - 8);
188 hval ^= g;
189 }
190 }
191
192 return hval;
193 }
194
195 // swap the 2 halves of 32 bit integer if needed
196 size_t32 wxMsgCatalog::Swap(size_t32 ui) const
197 {
198 return m_bSwapped ? (ui << 24) | ((ui & 0xff00) << 8) |
199 ((ui >> 8) & 0xff00) | (ui >> 24)
200 : ui;
201 }
202
203 wxMsgCatalog::wxMsgCatalog()
204 {
205 m_pData = NULL;
206 m_pszName = NULL;
207 }
208
209 wxMsgCatalog::~wxMsgCatalog()
210 {
211 wxDELETEA(m_pData);
212 wxDELETEA(m_pszName);
213 }
214
215 // small class to suppress the translation erros until exit from current scope
216 class NoTransErr
217 {
218 public:
219 NoTransErr() { wxSuppressTransErrors(); }
220 ~NoTransErr() { wxRestoreTransErrors(); }
221 };
222
223 // return all directories to search for given prefix
224 static wxString GetAllMsgCatalogSubdirs(const char *prefix,
225 const char *lang)
226 {
227 wxString searchPath;
228
229 // search first in prefix/fr/LC_MESSAGES, then in prefix/fr and finally in
230 // prefix (assuming the language is 'fr')
231 searchPath << prefix << wxFILE_SEP_PATH << lang << wxFILE_SEP_PATH
232 << "LC_MESSAGES" << wxPATH_SEP
233 << prefix << wxFILE_SEP_PATH << lang << wxPATH_SEP
234 << prefix << wxPATH_SEP;
235
236 return searchPath;
237 }
238
239 // construct the search path for the given language
240 static wxString GetFullSearchPath(const char *lang)
241 {
242 wxString searchPath;
243
244 // first take the entries explicitly added by the program
245 size_t count = s_searchPrefixes.Count();
246 for ( size_t n = 0; n < count; n++ )
247 {
248 searchPath << GetAllMsgCatalogSubdirs(s_searchPrefixes[n], lang)
249 << wxPATH_SEP;
250 }
251
252 // then take the current directory
253 // FIXME it should be the directory of the executable
254 searchPath << GetAllMsgCatalogSubdirs(".", lang) << wxPATH_SEP;
255
256 // and finally add some standard ones
257 searchPath
258 << GetAllMsgCatalogSubdirs("/usr/share/locale", lang) << wxPATH_SEP
259 << GetAllMsgCatalogSubdirs("/usr/lib/locale", lang) << wxPATH_SEP
260 << GetAllMsgCatalogSubdirs("/usr/local/share/locale", lang);
261
262 return searchPath;
263 }
264
265 // open disk file and read in it's contents
266 bool wxMsgCatalog::Load(const char *szDirPrefix, const char *szName)
267 {
268 // FIXME VZ: I forgot the exact meaning of LC_PATH - anyone to remind me?
269 #if 0
270 const char *pszLcPath = getenv("LC_PATH");
271 if ( pszLcPath != NULL )
272 strPath += pszLcPath + wxString(szDirPrefix) + MSG_PATH;
273 #endif // 0
274
275 wxString searchPath = GetFullSearchPath(szDirPrefix);
276 const char *sublocale = strchr(szDirPrefix, '_');
277 if ( sublocale )
278 {
279 // also add just base locale name: for things like "fr_BE" (belgium
280 // french) we should use "fr" if no belgium specific message catalogs
281 // exist
282 searchPath << GetFullSearchPath(wxString(szDirPrefix).
283 Left((size_t)(sublocale - szDirPrefix)))
284 << wxPATH_SEP;
285 }
286
287 wxString strFile = szName;
288 strFile += MSGCATALOG_EXTENSION;
289
290 // don't give translation errors here because the wxstd catalog might
291 // not yet be loaded (and it's normal)
292 //
293 // (we're using an object because we have several return paths)
294 NoTransErr noTransErr;
295
296 wxLogVerbose(_("looking for catalog '%s' in path '%s'."),
297 szName, searchPath.c_str());
298
299 wxString strFullName;
300 if ( !wxFindFileInPath(&strFullName, searchPath, strFile) ) {
301 wxLogWarning(_("catalog file for domain '%s' not found."), szName);
302 return FALSE;
303 }
304
305 // open file
306 wxLogVerbose(_("using catalog '%s' from '%s'."),
307 szName, strFullName.c_str());
308
309 wxFile fileMsg(strFullName);
310 if ( !fileMsg.IsOpened() )
311 return FALSE;
312
313 // get the file size
314 off_t nSize = fileMsg.Length();
315 if ( nSize == wxInvalidOffset )
316 return FALSE;
317
318 // read the whole file in memory
319 m_pData = new size_t8[nSize];
320 if ( fileMsg.Read(m_pData, nSize) != nSize ) {
321 wxDELETEA(m_pData);
322 return FALSE;
323 }
324
325 // examine header
326 bool bValid = (size_t)nSize > sizeof(wxMsgCatalogHeader);
327
328 wxMsgCatalogHeader *pHeader = (wxMsgCatalogHeader *)m_pData;
329 if ( bValid ) {
330 // we'll have to swap all the integers if it's true
331 m_bSwapped = pHeader->magic == MSGCATALOG_MAGIC_SW;
332
333 // check the magic number
334 bValid = m_bSwapped || pHeader->magic == MSGCATALOG_MAGIC;
335 }
336
337 if ( !bValid ) {
338 // it's either too short or has incorrect magic number
339 wxLogWarning(_("'%s' is not a valid message catalog."), strFullName.c_str());
340
341 wxDELETEA(m_pData);
342 return FALSE;
343 }
344
345 // initialize
346 m_numStrings = Swap(pHeader->numStrings);
347 m_pOrigTable = (wxMsgTableEntry *)(m_pData +
348 Swap(pHeader->ofsOrigTable));
349 m_pTransTable = (wxMsgTableEntry *)(m_pData +
350 Swap(pHeader->ofsTransTable));
351
352 m_nHashSize = Swap(pHeader->nHashSize);
353 m_pHashTable = (size_t32 *)(m_pData + Swap(pHeader->ofsHashTable));
354
355 m_pszName = new char[strlen(szName) + 1];
356 strcpy(m_pszName, szName);
357
358 // everything is fine
359 return TRUE;
360 }
361
362 // search for a string
363 const char *wxMsgCatalog::GetString(const char *szOrig) const
364 {
365 if ( szOrig == NULL )
366 return NULL;
367
368 if ( HasHashTable() ) { // use hash table for lookup if possible
369 size_t32 nHashVal = GetHash(szOrig);
370 size_t32 nIndex = nHashVal % m_nHashSize;
371
372 size_t32 nIncr = 1 + (nHashVal % (m_nHashSize - 2));
373
374 while ( TRUE ) {
375 size_t32 nStr = Swap(m_pHashTable[nIndex]);
376 if ( nStr == 0 )
377 return NULL;
378
379 if ( strcmp(szOrig, StringAtOfs(m_pOrigTable, nStr - 1)) == 0 )
380 return StringAtOfs(m_pTransTable, nStr - 1);
381
382 if ( nIndex >= m_nHashSize - nIncr)
383 nIndex -= m_nHashSize - nIncr;
384 else
385 nIndex += nIncr;
386 }
387 }
388 else { // no hash table: use default binary search
389 size_t32 bottom = 0,
390 top = m_numStrings,
391 current;
392 while ( bottom < top ) {
393 current = (bottom + top) / 2;
394 int res = strcmp(szOrig, StringAtOfs(m_pOrigTable, current));
395 if ( res < 0 )
396 top = current;
397 else if ( res > 0 )
398 bottom = current + 1;
399 else // found!
400 return StringAtOfs(m_pTransTable, current);
401 }
402 }
403
404 // not found
405 return NULL;
406 }
407
408 // ----------------------------------------------------------------------------
409 // wxLocale
410 // ----------------------------------------------------------------------------
411
412 wxLocale::wxLocale()
413 {
414 m_pszOldLocale = NULL;
415 m_pMsgCat = NULL;
416 }
417
418 // NB: this function has (desired) side effect of changing current locale
419 bool wxLocale::Init(const char *szName,
420 const char *szShort,
421 const char *szLocale,
422 bool bLoadDefault)
423 {
424 m_strLocale = szName;
425 m_strShort = szShort;
426
427 // change current locale (default: same as long name)
428 if ( szLocale == NULL )
429 szLocale = szName;
430 m_pszOldLocale = setlocale(LC_ALL, szLocale);
431 if ( m_pszOldLocale == NULL )
432 wxLogError(_("locale '%s' can not be set."), szLocale);
433
434 // the short name will be used to look for catalog files as well,
435 // so we need something here
436 if ( m_strShort.IsEmpty() ) {
437 // FIXME I don't know how these 2 letter abbreviations are formed,
438 // this wild guess is surely wrong
439 m_strShort = tolower(szLocale[0]) + tolower(szLocale[1]);
440 }
441
442 // save the old locale to be able to restore it later
443 m_pOldLocale = wxSetLocale(this);
444
445 // load the default catalog with wxWindows standard messages
446 m_pMsgCat = NULL;
447 bool bOk = TRUE;
448 if ( bLoadDefault )
449 bOk = AddCatalog("wxstd");
450
451 return bOk;
452 }
453
454 void wxLocale::AddCatalogLookupPathPrefix(const wxString& prefix)
455 {
456 if ( s_searchPrefixes.Index(prefix) == wxNOT_FOUND )
457 {
458 s_searchPrefixes.Add(prefix);
459 }
460 //else: already have it
461 }
462
463 // clean up
464 wxLocale::~wxLocale()
465 {
466 // free memory
467 wxMsgCatalog *pTmpCat;
468 while ( m_pMsgCat != NULL ) {
469 pTmpCat = m_pMsgCat;
470 m_pMsgCat = m_pMsgCat->m_pNext;
471 delete pTmpCat;
472 }
473
474 // restore old locale
475 wxSetLocale(m_pOldLocale);
476 setlocale(LC_ALL, m_pszOldLocale);
477 }
478
479 // get the translation of given string in current locale
480 const char *wxLocale::GetString(const char *szOrigString,
481 const char *szDomain) const
482 {
483 if ( IsEmpty(szOrigString) )
484 return szDomain;
485
486 const char *pszTrans = NULL;
487
488 wxMsgCatalog *pMsgCat;
489 if ( szDomain != NULL ) {
490 pMsgCat = FindCatalog(szDomain);
491
492 // does the catalog exist?
493 if ( pMsgCat != NULL )
494 pszTrans = pMsgCat->GetString(szOrigString);
495 }
496 else {
497 // search in all domains
498 for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext ) {
499 pszTrans = pMsgCat->GetString(szOrigString);
500 if ( pszTrans != NULL ) // take the first found
501 break;
502 }
503 }
504
505 if ( pszTrans == NULL ) {
506 if ( wxIsLoggingTransErrors() ) {
507 // suppress further error messages if we're not debugging: this avoids
508 // flooding the user with messages about each and every missing string if,
509 // for example, a whole catalog file is missing.
510
511 // do it before calling LogWarning to prevent infinite recursion!
512 #ifdef __WXDEBUG__
513 NoTransErr noTransErr;
514 #else // !debug
515 wxSuppressTransErrors();
516 #endif // debug/!debug
517
518 if ( szDomain != NULL )
519 {
520 wxLogWarning(_("string '%s' not found in domain '%s' for locale '%s'."),
521 szOrigString, szDomain, m_strLocale.c_str());
522 }
523 else
524 {
525 wxLogWarning(_("string '%s' not found in locale '%s'."),
526 szOrigString, m_strLocale.c_str());
527 }
528 }
529
530 return szOrigString;
531 }
532 else
533 return pszTrans;
534 }
535
536 // find catalog by name in a linked list, return NULL if !found
537 wxMsgCatalog *wxLocale::FindCatalog(const char *szDomain) const
538 {
539 // linear search in the linked list
540 wxMsgCatalog *pMsgCat;
541 for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext ) {
542 if ( Stricmp(pMsgCat->GetName(), szDomain) == 0 )
543 return pMsgCat;
544 }
545
546 return NULL;
547 }
548
549 // check if the given catalog is loaded
550 bool wxLocale::IsLoaded(const char *szDomain) const
551 {
552 return FindCatalog(szDomain) != NULL;
553 }
554
555 // add a catalog to our linked list
556 bool wxLocale::AddCatalog(const char *szDomain)
557 {
558 wxMsgCatalog *pMsgCat = new wxMsgCatalog;
559
560 if ( pMsgCat->Load(m_strShort, szDomain) ) {
561 // add it to the head of the list so that in GetString it will
562 // be searched before the catalogs added earlier
563 pMsgCat->m_pNext = m_pMsgCat;
564 m_pMsgCat = pMsgCat;
565
566 return TRUE;
567 }
568 else {
569 // don't add it because it couldn't be loaded anyway
570 delete pMsgCat;
571
572 return FALSE;
573 }
574 }
575
576 // ----------------------------------------------------------------------------
577 // global functions and variables
578 // ----------------------------------------------------------------------------
579
580 // translation errors logging
581 // --------------------------
582
583 static bool gs_bGiveTransErrors = TRUE;
584
585 void wxSuppressTransErrors()
586 {
587 gs_bGiveTransErrors = FALSE;
588 }
589
590 void wxRestoreTransErrors()
591 {
592 gs_bGiveTransErrors = TRUE;
593 }
594
595 bool wxIsLoggingTransErrors()
596 {
597 return gs_bGiveTransErrors;
598 }
599
600 // retrieve/change current locale
601 // ------------------------------
602
603 // the current locale object
604 static wxLocale *g_pLocale = NULL;
605
606 wxLocale *wxGetLocale()
607 {
608 return g_pLocale;
609 }
610
611 wxLocale *wxSetLocale(wxLocale *pLocale)
612 {
613 wxLocale *pOld = g_pLocale;
614 g_pLocale = pLocale;
615 return pOld;
616 }