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