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"
36 #include "wx/string.h"
44 // ----------------------------------------------------------------------------
46 // ----------------------------------------------------------------------------
48 // magic number identifying the .mo format file
49 const uint32 MSGCATALOG_MAGIC
= 0x950412de;
50 const uint32 MSGCATALOG_MAGIC_SW
= 0xde120495;
52 // extension of ".mo" files
53 #define MSGCATALOG_EXTENSION ".mo"
55 // ----------------------------------------------------------------------------
57 // ----------------------------------------------------------------------------
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
63 void wxSuppressTransErrors();
65 // restore the logging
66 void wxRestoreTransErrors();
68 // get the current state
69 bool wxIsLoggingTransErrors();
71 // get the current locale object (## may be NULL!)
72 extern wxLocale
*wxSetLocale(wxLocale
*pLocale
);
74 // ----------------------------------------------------------------------------
75 // wxMsgCatalog corresponds to one disk-file message catalog.
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 // ----------------------------------------------------------------------------
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
; }
92 // get name of the catalog
93 const char *GetName() const { return m_pszName
; }
95 // get the translated string: returns NULL if not found
96 const char *GetString(const char *sz
) const;
98 // public variable pointing to the next element in a linked list (or NULL)
99 wxMsgCatalog
*m_pNext
;
102 // this implementation is binary compatible with GNU gettext() version 0.10
104 // an entry in the string table
105 struct wxMsgTableEntry
107 uint32 nLen
; // length of the string
108 uint32 ofsString
; // pointer to the string
111 // header of a .mo file
112 struct wxMsgCatalogHeader
114 uint32 magic
, // offset +00: magic id
115 revision
, // +04: revision
116 numStrings
; // +08: number of strings in the file
117 uint32 ofsOrigTable
, // +0C: start of original string table
118 ofsTransTable
; // +10: start of translated string table
119 uint32 nHashSize
, // +14: hash table size
120 ofsHashTable
; // +18: offset of hash table start
123 // all data is stored here, NULL if no data loaded
127 uint32 m_numStrings
, // number of strings in this domain
128 m_nHashSize
; // number of entries in hash table
129 uint32
*m_pHashTable
; // pointer to hash table
130 wxMsgTableEntry
*m_pOrigTable
, // pointer to original strings
131 *m_pTransTable
; // translated
133 const char *StringAtOfs(wxMsgTableEntry
*pTable
, uint32 index
) const
134 { return (const char *)(m_pData
+ Swap(pTable
[index
].ofsString
)); }
137 // calculate the hash value of given string
138 static inline uint32
GetHash(const char *sz
);
139 // big<->little endian
140 inline uint32
Swap(uint32 ui
) const;
143 bool HasHashTable() const // true if hash table is present
144 { return m_nHashSize
> 2 && m_pHashTable
!= NULL
; }
146 bool m_bSwapped
; // wrong endianness?
148 char *m_pszName
; // name of the domain
151 // ============================================================================
153 // ============================================================================
155 // ----------------------------------------------------------------------------
156 // wxMsgCatalog class
157 // ----------------------------------------------------------------------------
159 // calculate hash value using the so called hashpjw function by P.J. Weinberger
160 // [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools]
161 uint32
wxMsgCatalog::GetHash(const char *sz
)
163 #define HASHWORDBITS 32 // the length of uint32
167 while ( *sz
!= '\0' ) {
169 hval
+= (uint32
)*sz
++;
170 g
= hval
& ((uint32
)0xf << (HASHWORDBITS
- 4));
172 hval
^= g
>> (HASHWORDBITS
- 8);
180 // swap the 2 halves of 32 bit integer if needed
181 uint32
wxMsgCatalog::Swap(uint32 ui
) const
183 return m_bSwapped
? (ui
<< 24) | ((ui
& 0xff00) << 8) |
184 ((ui
>> 8) & 0xff00) | (ui
>> 24)
188 wxMsgCatalog::wxMsgCatalog()
194 wxMsgCatalog::~wxMsgCatalog()
203 NoTransErr() { wxSuppressTransErrors(); }
204 ~NoTransErr() { wxRestoreTransErrors(); }
207 // open disk file and read in it's contents
208 bool wxMsgCatalog::Load(const char *szDirPrefix
, const char *szName
)
210 // search order (assume language 'foo') is
211 // 1) $LC_PATH/foo/LC_MESSAGES (if LC_PATH set)
212 // 2) ./foo/LC_MESSAGES
214 // 4) . (Added by JACS)
216 // under UNIX we search also in:
217 // 5) /usr/share/locale/foo/LC_MESSAGES (Linux)
218 // 6) /usr/lib/locale/foo/LC_MESSAGES (Solaris)
219 #define MSG_PATH FILE_SEP_PATH + "LC_MESSAGES" PATH_SEP
221 wxString
strPath("");
222 const char *pszLcPath
= getenv("LC_PATH");
223 if ( pszLcPath
!= NULL
)
224 strPath
+= pszLcPath
+ wxString(szDirPrefix
) + MSG_PATH
; // (1)
226 // NB: '<<' is unneeded between too literal strings:
227 // they are concatenated at compile time
228 strPath
+= "./" + wxString(szDirPrefix
) + MSG_PATH
// (2)
229 + "./" + szDirPrefix
+ FILE_SEP_PATH
+ PATH_SEP
// (3)
232 "/usr/share/locale/" + szDirPrefix
+ MSG_PATH
// (5)
233 "/usr/lib/locale/" + szDirPrefix
+ MSG_PATH
// (6)
237 wxString strFile
= szName
;
238 strFile
+= MSGCATALOG_EXTENSION
;
240 // don't give translation errors here because the wxstd catalog might
241 // not yet be loaded (and it's normal)
243 // (we're using an object because we have several return paths)
244 NoTransErr noTransErr
;
246 wxLogVerbose("looking for catalog '%s' in path '%s'.",
247 szName
, strPath
.c_str());
249 wxString strFullName
;
250 if ( !wxFindFileInPath(&strFullName
, strPath
, strFile
) ) {
251 wxLogWarning("catalog file for domain '%s' not found.", szName
);
256 wxLogVerbose("using catalog '%s' from '%s'.",
257 szName
, strFullName
.c_str());
259 wxFile
fileMsg(strFullName
);
260 if ( !fileMsg
.IsOpened() )
264 off_t nSize
= fileMsg
.Length();
265 if ( nSize
== ofsInvalid
)
268 // read the whole file in memory
269 m_pData
= new uint8
[nSize
];
270 if ( fileMsg
.Read(m_pData
, nSize
) != nSize
) {
277 bool bValid
= (size_t)nSize
> sizeof(wxMsgCatalogHeader
);
279 wxMsgCatalogHeader
*pHeader
;
281 pHeader
= (wxMsgCatalogHeader
*)m_pData
;
283 // we'll have to swap all the integers if it's true
284 m_bSwapped
= pHeader
->magic
== MSGCATALOG_MAGIC_SW
;
286 // check the magic number
287 bValid
= m_bSwapped
|| pHeader
->magic
== MSGCATALOG_MAGIC
;
291 // it's either too short or has incorrect magic number
292 wxLogWarning("'%s' is not a valid message catalog.", strFullName
.c_str());
300 m_numStrings
= Swap(pHeader
->numStrings
);
301 m_pOrigTable
= (wxMsgTableEntry
*)(m_pData
+
302 Swap(pHeader
->ofsOrigTable
));
303 m_pTransTable
= (wxMsgTableEntry
*)(m_pData
+
304 Swap(pHeader
->ofsTransTable
));
306 m_nHashSize
= Swap(pHeader
->nHashSize
);
307 m_pHashTable
= (uint32
*)(m_pData
+ Swap(pHeader
->ofsHashTable
));
309 m_pszName
= new char[strlen(szName
) + 1];
310 strcpy(m_pszName
, szName
);
312 // everything is fine
316 // search for a string
317 const char *wxMsgCatalog::GetString(const char *szOrig
) const
319 if ( szOrig
== NULL
)
322 if ( HasHashTable() ) { // use hash table for lookup if possible
323 uint32 nHashVal
= GetHash(szOrig
);
324 uint32 nIndex
= nHashVal
% m_nHashSize
;
326 uint32 nIncr
= 1 + (nHashVal
% (m_nHashSize
- 2));
329 uint32 nStr
= Swap(m_pHashTable
[nIndex
]);
333 if ( strcmp(szOrig
, StringAtOfs(m_pOrigTable
, nStr
- 1)) == 0 )
334 return StringAtOfs(m_pTransTable
, nStr
- 1);
336 if ( nIndex
>= m_nHashSize
- nIncr
)
337 nIndex
-= m_nHashSize
- nIncr
;
342 else { // no hash table: use default binary search
346 while ( bottom
< top
) {
347 current
= (bottom
+ top
) / 2;
348 int res
= strcmp(szOrig
, StringAtOfs(m_pOrigTable
, current
));
352 bottom
= current
+ 1;
354 return StringAtOfs(m_pTransTable
, current
);
362 // ----------------------------------------------------------------------------
364 // ----------------------------------------------------------------------------
366 // NB: ctor has (desired) side effect of changing current locale
367 wxLocale::wxLocale(const char *szName
,
369 const char *szLocale
,
371 : m_strLocale(szName
), m_strShort(szShort
)
373 // change current locale (default: same as long name)
374 if ( szLocale
== NULL
)
376 m_pszOldLocale
= setlocale(LC_ALL
, szLocale
);
377 if ( m_pszOldLocale
== NULL
)
378 wxLogError("locale '%s' can not be set.", szLocale
);
380 // the short name will be used to look for catalog files as well,
381 // so we need something here
382 if ( m_strShort
.IsEmpty() ) {
383 // #### I don't know how these 2 letter abbreviations are formed,
384 // this wild guess is almost surely wrong
385 m_strShort
= wxToLower(szLocale
[0]) + wxToLower(szLocale
[1]);
388 // save the old locale to be able to restore it later
389 m_pOldLocale
= wxSetLocale(this);
391 // load the default catalog with wxWindows standard messages
398 wxLocale::~wxLocale()
401 wxMsgCatalog
*pTmpCat
;
402 while ( m_pMsgCat
!= NULL
) {
404 m_pMsgCat
= m_pMsgCat
->m_pNext
;
408 // restore old locale
409 wxSetLocale(m_pOldLocale
);
410 setlocale(LC_ALL
, m_pszOldLocale
);
413 // get the translation of given string in current locale
414 const char *wxLocale::GetString(const char *szOrigString
,
415 const char *szDomain
) const
417 wxASSERT( szOrigString
!= NULL
); // would be pretty silly
419 const char *pszTrans
= NULL
;
421 wxMsgCatalog
*pMsgCat
;
422 if ( szDomain
!= NULL
) {
423 pMsgCat
= FindCatalog(szDomain
);
425 // does the catalog exist?
426 if ( pMsgCat
!= NULL
)
427 pszTrans
= pMsgCat
->GetString(szOrigString
);
430 // search in all domains
431 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
) {
432 pszTrans
= pMsgCat
->GetString(szOrigString
);
433 if ( pszTrans
!= NULL
) // take the first found
438 if ( pszTrans
== NULL
) {
439 if ( wxIsLoggingTransErrors() ) {
440 // suppress further error messages
441 // (do it before LogWarning to prevent infinite recursion!)
442 wxSuppressTransErrors();
444 if ( szDomain
!= NULL
)
445 wxLogWarning("string '%s' not found in domain '%s' for locale '%s'.",
446 szOrigString
, szDomain
, m_strLocale
.c_str());
448 wxLogWarning("string '%s' not found in locale '%s'.",
449 szOrigString
, m_strLocale
.c_str());
458 // find catalog by name in a linked list, return NULL if !found
459 wxMsgCatalog
*wxLocale::FindCatalog(const char *szDomain
) const
461 // linear search in the linked list
462 wxMsgCatalog
*pMsgCat
;
463 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
) {
464 if ( Stricmp(pMsgCat
->GetName(), szDomain
) == 0 )
471 // check if the given catalog is loaded
472 bool wxLocale::IsLoaded(const char *szDomain
) const
474 return FindCatalog(szDomain
) != NULL
;
477 // add a catalog to our linked list
478 bool wxLocale::AddCatalog(const char *szDomain
)
480 wxMsgCatalog
*pMsgCat
= new wxMsgCatalog
;
482 if ( pMsgCat
->Load(m_strShort
, szDomain
) ) {
483 // add it to the head of the list so that in GetString it will
484 // be searched before the catalogs added earlier
485 pMsgCat
->m_pNext
= m_pMsgCat
;
491 // don't add it because it couldn't be loaded anyway
498 // ----------------------------------------------------------------------------
499 // global functions and variables
500 // ----------------------------------------------------------------------------
502 // translation errors logging
503 // --------------------------
505 static bool gs_bGiveTransErrors
= TRUE
;
507 void wxSuppressTransErrors()
509 gs_bGiveTransErrors
= FALSE
;
512 void wxRestoreTransErrors()
514 gs_bGiveTransErrors
= TRUE
;
517 bool wxIsLoggingTransErrors()
519 return gs_bGiveTransErrors
;
522 // retrieve/change current locale
523 // ------------------------------
525 // the current locale object
526 wxLocale
*g_pLocale
= NULL
;
528 wxLocale
*wxGetLocale()
533 wxLocale
*wxSetLocale(wxLocale
*pLocale
)
535 wxLocale
*pOld
= g_pLocale
;