]> git.saurik.com Git - wxWidgets.git/blob - src/common/intl.cpp
Remaining Makefile.ins,
[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 #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!
52 typedef 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
91 const size_t32 MSGCATALOG_MAGIC = 0x950412de;
92 const 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)
105 void wxSuppressTransErrors();
106
107 // restore the logging
108 void wxRestoreTransErrors();
109
110 // get the current state
111 bool wxIsLoggingTransErrors();
112
113 static 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
122 class wxMsgCatalog
123 {
124 public:
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
142 private:
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
197 static 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]
209 size_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
229 size_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
236 wxMsgCatalog::wxMsgCatalog()
237 {
238 m_pData = NULL;
239 m_pszName = NULL;
240 }
241
242 wxMsgCatalog::~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
249 class NoTransErr
250 {
251 public:
252 NoTransErr() { wxSuppressTransErrors(); }
253 ~NoTransErr() { wxRestoreTransErrors(); }
254 };
255
256 // return all directories to search for given prefix
257 static 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
273 static 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
299 bool 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
396 const 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
445 wxLocale::wxLocale()
446 {
447 m_pszOldLocale = NULL;
448 m_pMsgCat = NULL;
449 }
450
451 // NB: this function has (desired) side effect of changing current locale
452 bool 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
487 void 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
497 wxLocale::~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
513 const 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
571 wxMsgCatalog *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
584 bool wxLocale::IsLoaded(const wxChar *szDomain) const
585 {
586 return FindCatalog(szDomain) != NULL;
587 }
588
589 // add a catalog to our linked list
590 bool 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
617 static bool gs_bGiveTransErrors = TRUE;
618
619 void wxSuppressTransErrors()
620 {
621 gs_bGiveTransErrors = FALSE;
622 }
623
624 void wxRestoreTransErrors()
625 {
626 gs_bGiveTransErrors = TRUE;
627 }
628
629 bool wxIsLoggingTransErrors()
630 {
631 return gs_bGiveTransErrors;
632 }
633
634 // retrieve/change current locale
635 // ------------------------------
636
637 // the current locale object
638 static wxLocale *g_pLocale = NULL;
639
640 wxLocale *wxGetLocale()
641 {
642 return g_pLocale;
643 }
644
645 wxLocale *wxSetLocale(wxLocale *pLocale)
646 {
647 wxLocale *pOld = g_pLocale;
648 g_pLocale = pLocale;
649 return pOld;
650 }
651
652 #endif // wxUSE_INTL
653