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