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