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