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