]> git.saurik.com Git - wxWidgets.git/blame - src/common/intl.cpp
more backwards compatibility
[wxWidgets.git] / src / common / intl.cpp
CommitLineData
c801d85f
KB
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>
7af89395 9// Licence: wxWindows license
c801d85f
KB
10/////////////////////////////////////////////////////////////////////////////
11
12// ============================================================================
1678ad78 13// declaration
c801d85f
KB
14// ============================================================================
15
16// ----------------------------------------------------------------------------
17// headers
18// ----------------------------------------------------------------------------
19
20#ifdef __GNUG__
84c18814 21 #pragma implementation "intl.h"
c801d85f
KB
22#endif
23
24// For compilers that support precompilation, includes "wx.h".
25#include "wx/wxprec.h"
26
27#ifdef __BORLANDC__
84c18814 28 #pragma hdrstop
c801d85f
KB
29#endif
30
1678ad78
GL
31// standard headers
32#include <locale.h>
7502ba29 33
1678ad78
GL
34// wxWindows
35#include "wx/defs.h"
36#include "wx/string.h"
c801d85f
KB
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
84c18814
VZ
44// ----------------------------------------------------------------------------
45// simple types
46// ----------------------------------------------------------------------------
47
48// FIXME adjust if necessary
49typedef unsigned char size_t8;
50typedef unsigned long size_t32;
51
c801d85f
KB
52// ----------------------------------------------------------------------------
53// constants
54// ----------------------------------------------------------------------------
55
56// magic number identifying the .mo format file
c86f1403
VZ
57const size_t32 MSGCATALOG_MAGIC = 0x950412de;
58const size_t32 MSGCATALOG_MAGIC_SW = 0xde120495;
c801d85f
KB
59
60// extension of ".mo" files
61#define MSGCATALOG_EXTENSION ".mo"
62
63// ----------------------------------------------------------------------------
1678ad78 64// global functions
c801d85f
KB
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)
1678ad78 71void wxSuppressTransErrors();
c801d85f
KB
72
73// restore the logging
1678ad78 74void wxRestoreTransErrors();
c801d85f
KB
75
76// get the current state
1678ad78 77bool wxIsLoggingTransErrors();
c801d85f 78
84c18814 79static wxLocale *wxSetLocale(wxLocale *pLocale);
c801d85f
KB
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
88class wxMsgCatalog
89{
90public:
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;
7af89395 107
c801d85f
KB
108private:
109 // this implementation is binary compatible with GNU gettext() version 0.10
110
111 // an entry in the string table
112 struct wxMsgTableEntry
113 {
c86f1403
VZ
114 size_t32 nLen; // length of the string
115 size_t32 ofsString; // pointer to the string
c801d85f
KB
116 };
117
118 // header of a .mo file
119 struct wxMsgCatalogHeader
120 {
c86f1403 121 size_t32 magic, // offset +00: magic id
84c18814
VZ
122 revision, // +04: revision
123 numStrings; // +08: number of strings in the file
c86f1403 124 size_t32 ofsOrigTable, // +0C: start of original string table
84c18814 125 ofsTransTable; // +10: start of translated string table
c86f1403 126 size_t32 nHashSize, // +14: hash table size
84c18814 127 ofsHashTable; // +18: offset of hash table start
7af89395
VZ
128 };
129
c801d85f 130 // all data is stored here, NULL if no data loaded
c86f1403 131 size_t8 *m_pData;
7af89395 132
c801d85f 133 // data description
84c18814 134 size_t32 m_numStrings, // number of strings in this domain
c801d85f 135 m_nHashSize; // number of entries in hash table
84c18814 136 size_t32 *m_pHashTable; // pointer to hash table
c801d85f
KB
137 wxMsgTableEntry *m_pOrigTable, // pointer to original strings
138 *m_pTransTable; // translated
139
c86f1403 140 const char *StringAtOfs(wxMsgTableEntry *pTable, size_t32 index) const
c801d85f
KB
141 { return (const char *)(m_pData + Swap(pTable[index].ofsString)); }
142
143 // utility functions
144 // calculate the hash value of given string
c86f1403 145 static inline size_t32 GetHash(const char *sz);
c801d85f 146 // big<->little endian
c86f1403 147 inline size_t32 Swap(size_t32 ui) const;
c801d85f
KB
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
fd323a5e
VZ
158// ----------------------------------------------------------------------------
159// global variables
160// ----------------------------------------------------------------------------
161
162// the list of the directories to search for message catalog files
163static wxArrayString s_searchPrefixes;
164
c801d85f
KB
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]
c86f1403 175size_t32 wxMsgCatalog::GetHash(const char *sz)
c801d85f 176{
c86f1403 177 #define HASHWORDBITS 32 // the length of size_t32
c801d85f 178
c86f1403
VZ
179 size_t32 hval = 0;
180 size_t32 g;
c801d85f
KB
181 while ( *sz != '\0' ) {
182 hval <<= 4;
c86f1403
VZ
183 hval += (size_t32)*sz++;
184 g = hval & ((size_t32)0xf << (HASHWORDBITS - 4));
c801d85f
KB
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
c86f1403 195size_t32 wxMsgCatalog::Swap(size_t32 ui) const
c801d85f 196{
7af89395 197 return m_bSwapped ? (ui << 24) | ((ui & 0xff00) << 8) |
c801d85f
KB
198 ((ui >> 8) & 0xff00) | (ui >> 24)
199 : ui;
200}
201
7af89395
VZ
202wxMsgCatalog::wxMsgCatalog()
203{
c801d85f
KB
204 m_pData = NULL;
205 m_pszName = NULL;
206}
207
7af89395
VZ
208wxMsgCatalog::~wxMsgCatalog()
209{
210 wxDELETEA(m_pData);
211 wxDELETEA(m_pszName);
c801d85f
KB
212}
213
fd323a5e 214// small class to suppress the translation erros until exit from current scope
c801d85f
KB
215class NoTransErr
216{
fd323a5e 217public:
c801d85f
KB
218 NoTransErr() { wxSuppressTransErrors(); }
219 ~NoTransErr() { wxRestoreTransErrors(); }
220};
7af89395 221
fd323a5e
VZ
222// return all directories to search for given prefix
223static 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')
7af89395
VZ
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;
fd323a5e
VZ
234
235 return searchPath;
236}
237
238// construct the search path for the given language
239static 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)
7af89395 248 << wxPATH_SEP;
fd323a5e
VZ
249 }
250
251 // then take the current directory
252 // FIXME it should be the directory of the executable
7af89395 253 searchPath << GetAllMsgCatalogSubdirs(".", lang) << wxPATH_SEP;
fd323a5e
VZ
254
255 // and finally add some standard ones
256 searchPath
7af89395
VZ
257 << GetAllMsgCatalogSubdirs("/usr/share/locale", lang) << wxPATH_SEP
258 << GetAllMsgCatalogSubdirs("/usr/lib/locale", lang) << wxPATH_SEP
fd323a5e
VZ
259 << GetAllMsgCatalogSubdirs("/usr/local/share/locale", lang);
260
261 return searchPath;
262}
263
c801d85f
KB
264// open disk file and read in it's contents
265bool wxMsgCatalog::Load(const char *szDirPrefix, const char *szName)
266{
fd323a5e
VZ
267 // FIXME VZ: I forgot the exact meaning of LC_PATH - anyone to remind me?
268#if 0
c801d85f
KB
269 const char *pszLcPath = getenv("LC_PATH");
270 if ( pszLcPath != NULL )
fd323a5e
VZ
271 strPath += pszLcPath + wxString(szDirPrefix) + MSG_PATH;
272#endif // 0
7af89395 273
fd323a5e
VZ
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)))
7af89395 283 << wxPATH_SEP;
fd323a5e 284 }
7af89395 285
c801d85f
KB
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
1a5a8367 295 wxLogVerbose(_("looking for catalog '%s' in path '%s'."),
fd323a5e 296 szName, searchPath.c_str());
c801d85f
KB
297
298 wxString strFullName;
fd323a5e 299 if ( !wxFindFileInPath(&strFullName, searchPath, strFile) ) {
1a5a8367 300 wxLogWarning(_("catalog file for domain '%s' not found."), szName);
c801d85f
KB
301 return FALSE;
302 }
303
304 // open file
1a5a8367 305 wxLogVerbose(_("using catalog '%s' from '%s'."),
1678ad78 306 szName, strFullName.c_str());
7af89395 307
c801d85f
KB
308 wxFile fileMsg(strFullName);
309 if ( !fileMsg.IsOpened() )
1678ad78 310 return FALSE;
c801d85f
KB
311
312 // get the file size
1678ad78
GL
313 off_t nSize = fileMsg.Length();
314 if ( nSize == wxInvalidOffset )
315 return FALSE;
c801d85f
KB
316
317 // read the whole file in memory
c86f1403 318 m_pData = new size_t8[nSize];
c801d85f 319 if ( fileMsg.Read(m_pData, nSize) != nSize ) {
a3622daa 320 wxDELETEA(m_pData);
1678ad78 321 return FALSE;
c801d85f 322 }
7af89395 323
c801d85f 324 // examine header
1678ad78 325 bool bValid = (size_t)nSize > sizeof(wxMsgCatalogHeader);
7af89395 326
fd3f686c 327 wxMsgCatalogHeader *pHeader = (wxMsgCatalogHeader *)m_pData;
c801d85f 328 if ( bValid ) {
c801d85f
KB
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 }
7af89395 335
c801d85f
KB
336 if ( !bValid ) {
337 // it's either too short or has incorrect magic number
1a5a8367 338 wxLogWarning(_("'%s' is not a valid message catalog."), strFullName.c_str());
7af89395 339
a3622daa 340 wxDELETEA(m_pData);
c801d85f
KB
341 return FALSE;
342 }
7af89395 343
c801d85f
KB
344 // initialize
345 m_numStrings = Swap(pHeader->numStrings);
7af89395 346 m_pOrigTable = (wxMsgTableEntry *)(m_pData +
1678ad78 347 Swap(pHeader->ofsOrigTable));
7af89395 348 m_pTransTable = (wxMsgTableEntry *)(m_pData +
1678ad78 349 Swap(pHeader->ofsTransTable));
c801d85f
KB
350
351 m_nHashSize = Swap(pHeader->nHashSize);
c86f1403 352 m_pHashTable = (size_t32 *)(m_pData + Swap(pHeader->ofsHashTable));
c801d85f
KB
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
362const 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
7af89395 368 size_t32 nHashVal = GetHash(szOrig);
c86f1403 369 size_t32 nIndex = nHashVal % m_nHashSize;
c801d85f 370
c86f1403 371 size_t32 nIncr = 1 + (nHashVal % (m_nHashSize - 2));
7af89395 372
c801d85f 373 while ( TRUE ) {
c86f1403 374 size_t32 nStr = Swap(m_pHashTable[nIndex]);
c801d85f
KB
375 if ( nStr == 0 )
376 return NULL;
7af89395 377
c801d85f
KB
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
c86f1403 388 size_t32 bottom = 0,
c801d85f
KB
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
23fcecf7 411wxLocale::wxLocale()
c801d85f 412{
23fcecf7
VZ
413 m_pszOldLocale = NULL;
414 m_pMsgCat = NULL;
415}
416
417// NB: this function has (desired) side effect of changing current locale
7af89395
VZ
418bool wxLocale::Init(const char *szName,
419 const char *szShort,
23fcecf7
VZ
420 const char *szLocale,
421 bool bLoadDefault)
422{
423 m_strLocale = szName;
424 m_strShort = szShort;
425
c801d85f
KB
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 )
1a5a8367 431 wxLogError(_("locale '%s' can not be set."), szLocale);
c801d85f
KB
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() ) {
fd323a5e
VZ
436 // FIXME I don't know how these 2 letter abbreviations are formed,
437 // this wild guess is surely wrong
c801d85f
KB
438 m_strShort = wxToLower(szLocale[0]) + wxToLower(szLocale[1]);
439 }
7af89395 440
c801d85f 441 // save the old locale to be able to restore it later
7af89395
VZ
442 m_pOldLocale = wxSetLocale(this);
443
c801d85f
KB
444 // load the default catalog with wxWindows standard messages
445 m_pMsgCat = NULL;
23fcecf7 446 bool bOk = TRUE;
c801d85f 447 if ( bLoadDefault )
23fcecf7
VZ
448 bOk = AddCatalog("wxstd");
449
450 return bOk;
c801d85f
KB
451}
452
fd323a5e
VZ
453void wxLocale::AddCatalogLookupPathPrefix(const wxString& prefix)
454{
3c67202d 455 if ( s_searchPrefixes.Index(prefix) == wxNOT_FOUND )
fd323a5e
VZ
456 {
457 s_searchPrefixes.Add(prefix);
458 }
459 //else: already have it
460}
461
c801d85f
KB
462// clean up
463wxLocale::~wxLocale()
464{
fd323a5e
VZ
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);
c801d85f
KB
476}
477
478// get the translation of given string in current locale
7af89395 479const char *wxLocale::GetString(const char *szOrigString,
c801d85f
KB
480 const char *szDomain) const
481{
dd0e574a
VZ
482 if ( IsEmpty(szOrigString) )
483 return szDomain;
c801d85f
KB
484
485 const char *pszTrans = NULL;
486
487 wxMsgCatalog *pMsgCat;
488 if ( szDomain != NULL ) {
489 pMsgCat = FindCatalog(szDomain);
7af89395 490
c801d85f
KB
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() ) {
fd323a5e
VZ
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
c801d85f 514 wxSuppressTransErrors();
fd323a5e 515#endif // debug/!debug
7af89395 516
c801d85f 517 if ( szDomain != NULL )
fd323a5e 518 {
1a5a8367 519 wxLogWarning(_("string '%s' not found in domain '%s' for locale '%s'."),
fd323a5e
VZ
520 szOrigString, szDomain, m_strLocale.c_str());
521 }
c801d85f 522 else
fd323a5e 523 {
1a5a8367 524 wxLogWarning(_("string '%s' not found in locale '%s'."),
fd323a5e
VZ
525 szOrigString, m_strLocale.c_str());
526 }
c801d85f
KB
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
536wxMsgCatalog *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 }
7af89395 544
c801d85f
KB
545 return NULL;
546}
547
548// check if the given catalog is loaded
549bool wxLocale::IsLoaded(const char *szDomain) const
550{
551 return FindCatalog(szDomain) != NULL;
552}
553
554// add a catalog to our linked list
555bool wxLocale::AddCatalog(const char *szDomain)
556{
557 wxMsgCatalog *pMsgCat = new wxMsgCatalog;
7af89395 558
c801d85f
KB
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;
7af89395 564
c801d85f
KB
565 return TRUE;
566 }
567 else {
568 // don't add it because it couldn't be loaded anyway
569 delete pMsgCat;
7af89395 570
c801d85f
KB
571 return FALSE;
572 }
573}
574
575// ----------------------------------------------------------------------------
576// global functions and variables
577// ----------------------------------------------------------------------------
578
579// translation errors logging
580// --------------------------
581
582static bool gs_bGiveTransErrors = TRUE;
583
584void wxSuppressTransErrors()
585{
586 gs_bGiveTransErrors = FALSE;
587}
588
589void wxRestoreTransErrors()
590{
591 gs_bGiveTransErrors = TRUE;
592}
593
594bool wxIsLoggingTransErrors()
595{
596 return gs_bGiveTransErrors;
597}
598
599// retrieve/change current locale
600// ------------------------------
601
602// the current locale object
84c18814 603static wxLocale *g_pLocale = NULL;
c801d85f 604
1678ad78
GL
605wxLocale *wxGetLocale()
606{
7af89395 607 return g_pLocale;
1678ad78
GL
608}
609
c801d85f
KB
610wxLocale *wxSetLocale(wxLocale *pLocale)
611{
7af89395
VZ
612 wxLocale *pOld = g_pLocale;
613 g_pLocale = pLocale;
614 return pOld;
c801d85f 615}