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