1 /////////////////////////////////////////////////////////////////////////////
3 // Purpose: Internationalization and localisation for wxWindows
4 // Author: Vadim Zeitlin
8 // Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence: wxWindows license
10 /////////////////////////////////////////////////////////////////////////////
12 // ============================================================================
14 // ============================================================================
16 // ----------------------------------------------------------------------------
18 // ----------------------------------------------------------------------------
21 #pragma implementation "intl.h"
24 // For compilers that support precompilation, includes "wx.h".
25 #include "wx/wxprec.h"
34 #include "wx/string.h"
46 // ----------------------------------------------------------------------------
48 // ----------------------------------------------------------------------------
50 // magic number identifying the .mo format file
51 const uint32 MSGCATALOG_MAGIC
= 0x950412de;
52 const uint32 MSGCATALOG_MAGIC_SW
= 0xde120495;
54 // extension of ".mo" files
55 #define MSGCATALOG_EXTENSION ".mo"
57 // ----------------------------------------------------------------------------
58 // global functions (private to this module)
59 // ----------------------------------------------------------------------------
61 // suppress further error messages about missing translations
62 // (if you don't have one catalog file, you wouldn't like to see the
63 // error message for each string in it, so normally it's given only
65 static void wxSuppressTransErrors();
67 // restore the logging
68 static void wxRestoreTransErrors();
70 // get the current state
71 static bool wxIsLoggingTransErrors();
73 // get the current locale object (@@ may be NULL!)
74 static wxLocale
*wxSetLocale(wxLocale
*pLocale
);
76 // ----------------------------------------------------------------------------
77 // wxMsgCatalog corresponds to one disk-file message catalog.
79 // This is a "low-level" class and is used only by wxLocale (that's why
80 // it's designed to be stored in a linked list)
81 // ----------------------------------------------------------------------------
90 // load the catalog from disk (szDirPrefix corresponds to language)
91 bool Load(const char *szDirPrefix
, const char *szName
);
92 bool IsLoaded() const { return m_pData
!= NULL
; }
94 // get name of the catalog
95 const char *GetName() const { return m_pszName
; }
97 // get the translated string: returns NULL if not found
98 const char *GetString(const char *sz
) const;
100 // public variable pointing to the next element in a linked list (or NULL)
101 wxMsgCatalog
*m_pNext
;
104 // this implementation is binary compatible with GNU gettext() version 0.10
106 // an entry in the string table
107 struct wxMsgTableEntry
109 uint32 nLen
; // length of the string
110 uint32 ofsString
; // pointer to the string
113 // header of a .mo file
114 struct wxMsgCatalogHeader
116 uint32 magic
, // offset +00: magic id
117 revision
, // +04: revision
118 numStrings
; // +08: number of strings in the file
119 uint32 ofsOrigTable
, // +0C: start of original string table
120 ofsTransTable
; // +10: start of translated string table
121 uint32 nHashSize
, // +14: hash table size
122 ofsHashTable
; // +18: offset of hash table start
125 // all data is stored here, NULL if no data loaded
129 uint32 m_numStrings
, // number of strings in this domain
130 m_nHashSize
; // number of entries in hash table
131 uint32
*m_pHashTable
; // pointer to hash table
132 wxMsgTableEntry
*m_pOrigTable
, // pointer to original strings
133 *m_pTransTable
; // translated
135 const char *StringAtOfs(wxMsgTableEntry
*pTable
, uint32 index
) const
136 { return (const char *)(m_pData
+ Swap(pTable
[index
].ofsString
)); }
139 // calculate the hash value of given string
140 static inline uint32
GetHash(const char *sz
);
141 // big<->little endian
142 inline uint32
Swap(uint32 ui
) const;
145 bool HasHashTable() const // true if hash table is present
146 { return m_nHashSize
> 2 && m_pHashTable
!= NULL
; }
148 bool m_bSwapped
; // wrong endianness?
150 char *m_pszName
; // name of the domain
153 // ============================================================================
155 // ============================================================================
157 // ----------------------------------------------------------------------------
158 // wxMsgCatalog class
159 // ----------------------------------------------------------------------------
161 // calculate hash value using the so called hashpjw function by P.J. Weinberger
162 // [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools]
163 uint32
wxMsgCatalog::GetHash(const char *sz
)
165 #define HASHWORDBITS 32 // the length of uint32
169 while ( *sz
!= '\0' ) {
171 hval
+= (uint32
)*sz
++;
172 g
= hval
& ((uint32
)0xf << (HASHWORDBITS
- 4));
174 hval
^= g
>> (HASHWORDBITS
- 8);
182 // swap the 2 halves of 32 bit integer if needed
183 uint32
wxMsgCatalog::Swap(uint32 ui
) const
185 return m_bSwapped
? (ui
<< 24) | ((ui
& 0xff00) << 8) |
186 ((ui
>> 8) & 0xff00) | (ui
>> 24)
190 wxMsgCatalog::wxMsgCatalog()
196 wxMsgCatalog::~wxMsgCatalog()
202 // a helper class which suppresses all translation error messages
203 // from the moment of it's creation until it's destruction
207 NoTransErr() { wxSuppressTransErrors(); }
208 ~NoTransErr() { wxRestoreTransErrors(); }
211 // open disk file and read in it's contents
212 bool wxMsgCatalog::Load(const char *szDirPrefix
, const char *szName
)
214 // search order (assume language 'lang') is
215 // 1) $LC_PATH/lang/LC_MESSAGES (if LC_PATH set)
216 // 2) ./lang/LC_MESSAGES
218 // 4) . (Added by JACS)
220 // under UNIX we search also in:
221 // 5) /usr/share/locale/lang/LC_MESSAGES (Linux)
222 // 6) /usr/lib/locale/lang/LC_MESSAGES (Solaris)
223 #define MSG_PATH FILE_SEP_PATH + "LC_MESSAGES" PATH_SEP
225 wxString
strPath("");
226 const char *pszLcPath
= getenv("LC_PATH");
227 if ( pszLcPath
!= NULL
)
228 strPath
+= pszLcPath
+ wxString(szDirPrefix
) + MSG_PATH
; // (1)
230 // NB: '<<' is unneeded between too literal strings:
231 // they are concatenated at compile time
232 strPath
<< "./" << wxString(szDirPrefix
) + MSG_PATH
// (2)
233 << "./" << szDirPrefix
<< FILE_SEP_PATH
<< PATH_SEP
// (3)
234 << "." << PATH_SEP
// (4)
236 "/usr/share/locale/" << szDirPrefix
<< MSG_PATH
// (5)
237 "/usr/lib/locale/" << szDirPrefix
<< MSG_PATH
// (6)
241 wxString strFile
= szName
;
242 strFile
+= MSGCATALOG_EXTENSION
;
244 // don't give translation errors here because the wxstd catalog might
245 // not yet be loaded (and it's normal)
247 // (we're using an object because we have several return paths)
248 NoTransErr noTransErr
;
250 wxLogVerbose(_("looking for catalog '%s' in path '%s'..."),
251 szName
, strPath
.c_str());
253 wxString strFullName
;
254 if ( !wxFindFileInPath(&strFullName
, strPath
, strFile
) ) {
255 wxLogWarning(_("catalog file for domain '%s' not found."), szName
);
260 wxLogVerbose(_("catalog '%s' found in '%s'."), szName
, strFullName
.c_str());
262 // declare these vars here because we're using goto further down
266 wxFile
fileMsg(strFullName
);
267 if ( !fileMsg
.IsOpened() )
271 nSize
= fileMsg
.Length();
272 if ( nSize
== ofsInvalid
)
275 // read the whole file in memory
276 m_pData
= new uint8
[nSize
];
277 if ( fileMsg
.Read(m_pData
, nSize
) != nSize
) {
284 bValid
= (size_t)nSize
> sizeof(wxMsgCatalogHeader
);
286 wxMsgCatalogHeader
*pHeader
;
288 pHeader
= (wxMsgCatalogHeader
*)m_pData
;
290 // we'll have to swap all the integers if it's true
291 m_bSwapped
= pHeader
->magic
== MSGCATALOG_MAGIC_SW
;
293 // check the magic number
294 bValid
= m_bSwapped
|| pHeader
->magic
== MSGCATALOG_MAGIC
;
298 // it's either too short or has incorrect magic number
299 wxLogWarning(_("'%s' is not a valid message catalog."),
300 strFullName
.c_str());
308 m_numStrings
= Swap(pHeader
->numStrings
);
309 m_pOrigTable
= (wxMsgTableEntry
*)(m_pData
+ Swap(pHeader
->ofsOrigTable
));
310 m_pTransTable
= (wxMsgTableEntry
*)(m_pData
+ Swap(pHeader
->ofsTransTable
));
312 m_nHashSize
= Swap(pHeader
->nHashSize
);
313 m_pHashTable
= (uint32
*)(m_pData
+ Swap(pHeader
->ofsHashTable
));
315 m_pszName
= new char[strlen(szName
) + 1];
316 strcpy(m_pszName
, szName
);
318 // everything is fine
322 wxLogError(_("error opening message catalog '%s', not loaded."),
323 strFullName
.c_str());
327 // search for a string
328 const char *wxMsgCatalog::GetString(const char *szOrig
) const
330 if ( szOrig
== NULL
)
333 if ( HasHashTable() ) { // use hash table for lookup if possible
334 uint32 nHashVal
= GetHash(szOrig
);
335 uint32 nIndex
= nHashVal
% m_nHashSize
;
337 uint32 nIncr
= 1 + (nHashVal
% (m_nHashSize
- 2));
340 uint32 nStr
= Swap(m_pHashTable
[nIndex
]);
344 if ( strcmp(szOrig
, StringAtOfs(m_pOrigTable
, nStr
- 1)) == 0 )
345 return StringAtOfs(m_pTransTable
, nStr
- 1);
347 if ( nIndex
>= m_nHashSize
- nIncr
)
348 nIndex
-= m_nHashSize
- nIncr
;
353 else { // no hash table: use default binary search
357 while ( bottom
< top
) {
358 current
= (bottom
+ top
) / 2;
359 int res
= strcmp(szOrig
, StringAtOfs(m_pOrigTable
, current
));
363 bottom
= current
+ 1;
365 return StringAtOfs(m_pTransTable
, current
);
373 // ----------------------------------------------------------------------------
375 // ----------------------------------------------------------------------------
377 // NB: ctor has (desired) side effect of changing current locale
378 wxLocale::wxLocale(const char *szName
,
380 const char *szLocale
,
382 : m_strLocale(szName
), m_strShort(szShort
)
384 // change current locale (default: same as long name)
385 if ( szLocale
== NULL
)
387 m_pszOldLocale
= setlocale(LC_ALL
, szLocale
);
388 if ( m_pszOldLocale
== NULL
)
389 wxLogError(_("locale '%s' can not be set."), szLocale
);
391 // the short name will be used to look for catalog files as well,
392 // so we need something here
393 if ( m_strShort
.IsEmpty() ) {
394 // @@@@ I don't know how these 2 letter abbreviations are formed,
395 // this wild guess is almost surely wrong
396 m_strShort
= wxToLower(szLocale
[0]) + wxToLower(szLocale
[1]);
399 // save the old locale to be able to restore it later
400 m_pOldLocale
= wxSetLocale(this);
402 // load the default catalog with wxWindows standard messages
409 wxLocale::~wxLocale()
412 wxMsgCatalog
*pTmpCat
;
413 while ( m_pMsgCat
!= NULL
) {
415 m_pMsgCat
= m_pMsgCat
->m_pNext
;
419 // restore old locale
420 wxSetLocale(m_pOldLocale
);
421 setlocale(LC_ALL
, m_pszOldLocale
);
424 // get the translation of given string in current locale
425 const char *wxLocale::GetString(const char *szOrigString
,
426 const char *szDomain
) const
428 wxASSERT( szOrigString
!= NULL
); // would be pretty silly
430 const char *pszTrans
= NULL
;
432 wxMsgCatalog
*pMsgCat
;
433 if ( szDomain
!= NULL
) {
434 pMsgCat
= FindCatalog(szDomain
);
436 // does the catalog exist?
437 if ( pMsgCat
!= NULL
)
438 pszTrans
= pMsgCat
->GetString(szOrigString
);
441 // search in all domains
442 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
) {
443 pszTrans
= pMsgCat
->GetString(szOrigString
);
444 if ( pszTrans
!= NULL
) // take the first found
449 if ( pszTrans
== NULL
) {
450 if ( wxIsLoggingTransErrors() ) {
451 // suppress further error messages
452 // (do it before LogWarning to prevent infinite recursion!)
453 wxSuppressTransErrors();
455 if ( szDomain
!= NULL
)
456 wxLogWarning(_("string '%s' not found in domain '%s'"
457 " for locale '%s'."),
458 szOrigString
, szDomain
, m_strLocale
.c_str());
460 wxLogWarning(_("string '%s' not found in locale '%s'."),
461 szOrigString
, m_strLocale
.c_str());
470 // find catalog by name in a linked list, return NULL if !found
471 wxMsgCatalog
*wxLocale::FindCatalog(const char *szDomain
) const
473 // linear search in the linked list
474 wxMsgCatalog
*pMsgCat
;
475 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
) {
476 if ( Stricmp(pMsgCat
->GetName(), szDomain
) == 0 )
483 // check if the given catalog is loaded
484 bool wxLocale::IsLoaded(const char *szDomain
) const
486 return FindCatalog(szDomain
) != NULL
;
489 // add a catalog to our linked list
490 bool wxLocale::AddCatalog(const char *szDomain
)
492 wxMsgCatalog
*pMsgCat
= new wxMsgCatalog
;
494 if ( pMsgCat
->Load(m_strShort
, szDomain
) ) {
495 // add it to the head of the list so that in GetString it will
496 // be searched before the catalogs added earlier
497 pMsgCat
->m_pNext
= m_pMsgCat
;
503 // don't add it because it couldn't be loaded anyway
510 // ----------------------------------------------------------------------------
511 // global functions and variables
512 // ----------------------------------------------------------------------------
514 // translation errors logging
515 // --------------------------
517 static bool gs_bGiveTransErrors
= TRUE
;
519 void wxSuppressTransErrors()
521 gs_bGiveTransErrors
= FALSE
;
524 void wxRestoreTransErrors()
526 gs_bGiveTransErrors
= TRUE
;
529 bool wxIsLoggingTransErrors()
531 return gs_bGiveTransErrors
;
534 // retrieve/change current locale
535 // ------------------------------
537 // the current locale object
538 wxLocale
*g_pLocale
= NULL
;
540 wxLocale
*wxSetLocale(wxLocale
*pLocale
)
542 wxLocale
*pOld
= g_pLocale
;