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