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 size_t32 MSGCATALOG_MAGIC
= 0x950412de;
50 const size_t32 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 size_t32 nLen
; // length of the string
108 size_t32 ofsString
; // pointer to the string
111 // header of a .mo file
112 struct wxMsgCatalogHeader
114 size_t32 magic
, // offset +00: magic id
115 revision
, // +04: revision
116 numStrings
; // +08: number of strings in the file
117 size_t32 ofsOrigTable
, // +0C: start of original string table
118 ofsTransTable
; // +10: start of translated string table
119 size_t32 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 size_t32 m_numStrings
, // number of strings in this domain
128 m_nHashSize
; // number of entries in hash table
129 size_t32
*m_pHashTable
; // pointer to hash table
130 wxMsgTableEntry
*m_pOrigTable
, // pointer to original strings
131 *m_pTransTable
; // translated
133 const char *StringAtOfs(wxMsgTableEntry
*pTable
, size_t32 index
) const
134 { return (const char *)(m_pData
+ Swap(pTable
[index
].ofsString
)); }
137 // calculate the hash value of given string
138 static inline size_t32
GetHash(const char *sz
);
139 // big<->little endian
140 inline size_t32
Swap(size_t32 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 size_t32
wxMsgCatalog::GetHash(const char *sz
)
163 #define HASHWORDBITS 32 // the length of size_t32
167 while ( *sz
!= '\0' ) {
169 hval
+= (size_t32
)*sz
++;
170 g
= hval
& ((size_t32
)0xf << (HASHWORDBITS
- 4));
172 hval
^= g
>> (HASHWORDBITS
- 8);
180 // swap the 2 halves of 32 bit integer if needed
181 size_t32
wxMsgCatalog::Swap(size_t32 ui
) const
183 return m_bSwapped
? (ui
<< 24) | ((ui
& 0xff00) << 8) |
184 ((ui
>> 8) & 0xff00) | (ui
>> 24)
188 wxMsgCatalog::wxMsgCatalog()
194 wxMsgCatalog::~wxMsgCatalog()
197 wxDELETEA(m_pszName
);
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
== wxInvalidOffset
)
268 // read the whole file in memory
269 m_pData
= new size_t8
[nSize
];
270 if ( fileMsg
.Read(m_pData
, nSize
) != nSize
) {
276 bool bValid
= (size_t)nSize
> sizeof(wxMsgCatalogHeader
);
278 wxMsgCatalogHeader
*pHeader
= (wxMsgCatalogHeader
*)m_pData
;
280 // we'll have to swap all the integers if it's true
281 m_bSwapped
= pHeader
->magic
== MSGCATALOG_MAGIC_SW
;
283 // check the magic number
284 bValid
= m_bSwapped
|| pHeader
->magic
== MSGCATALOG_MAGIC
;
288 // it's either too short or has incorrect magic number
289 wxLogWarning(_("'%s' is not a valid message catalog."), strFullName
.c_str());
296 m_numStrings
= Swap(pHeader
->numStrings
);
297 m_pOrigTable
= (wxMsgTableEntry
*)(m_pData
+
298 Swap(pHeader
->ofsOrigTable
));
299 m_pTransTable
= (wxMsgTableEntry
*)(m_pData
+
300 Swap(pHeader
->ofsTransTable
));
302 m_nHashSize
= Swap(pHeader
->nHashSize
);
303 m_pHashTable
= (size_t32
*)(m_pData
+ Swap(pHeader
->ofsHashTable
));
305 m_pszName
= new char[strlen(szName
) + 1];
306 strcpy(m_pszName
, szName
);
308 // everything is fine
312 // search for a string
313 const char *wxMsgCatalog::GetString(const char *szOrig
) const
315 if ( szOrig
== NULL
)
318 if ( HasHashTable() ) { // use hash table for lookup if possible
319 size_t32 nHashVal
= GetHash(szOrig
);
320 size_t32 nIndex
= nHashVal
% m_nHashSize
;
322 size_t32 nIncr
= 1 + (nHashVal
% (m_nHashSize
- 2));
325 size_t32 nStr
= Swap(m_pHashTable
[nIndex
]);
329 if ( strcmp(szOrig
, StringAtOfs(m_pOrigTable
, nStr
- 1)) == 0 )
330 return StringAtOfs(m_pTransTable
, nStr
- 1);
332 if ( nIndex
>= m_nHashSize
- nIncr
)
333 nIndex
-= m_nHashSize
- nIncr
;
338 else { // no hash table: use default binary search
342 while ( bottom
< top
) {
343 current
= (bottom
+ top
) / 2;
344 int res
= strcmp(szOrig
, StringAtOfs(m_pOrigTable
, current
));
348 bottom
= current
+ 1;
350 return StringAtOfs(m_pTransTable
, current
);
358 // ----------------------------------------------------------------------------
360 // ----------------------------------------------------------------------------
364 m_pszOldLocale
= NULL
;
368 // NB: this function has (desired) side effect of changing current locale
369 bool wxLocale::Init(const char *szName
,
371 const char *szLocale
,
374 m_strLocale
= szName
;
375 m_strShort
= szShort
;
377 // change current locale (default: same as long name)
378 if ( szLocale
== NULL
)
380 m_pszOldLocale
= setlocale(LC_ALL
, szLocale
);
381 if ( m_pszOldLocale
== NULL
)
382 wxLogError(_("locale '%s' can not be set."), szLocale
);
384 // the short name will be used to look for catalog files as well,
385 // so we need something here
386 if ( m_strShort
.IsEmpty() ) {
387 // #### I don't know how these 2 letter abbreviations are formed,
388 // this wild guess is almost surely wrong
389 m_strShort
= wxToLower(szLocale
[0]) + wxToLower(szLocale
[1]);
392 // save the old locale to be able to restore it later
393 m_pOldLocale
= wxSetLocale(this);
395 // load the default catalog with wxWindows standard messages
399 bOk
= AddCatalog("wxstd");
405 wxLocale::~wxLocale()
408 wxMsgCatalog
*pTmpCat
;
409 while ( m_pMsgCat
!= NULL
) {
411 m_pMsgCat
= m_pMsgCat
->m_pNext
;
415 // restore old locale
416 wxSetLocale(m_pOldLocale
);
417 setlocale(LC_ALL
, m_pszOldLocale
);
420 // get the translation of given string in current locale
421 const char *wxLocale::GetString(const char *szOrigString
,
422 const char *szDomain
) const
424 wxASSERT( szOrigString
!= NULL
); // would be pretty silly
426 const char *pszTrans
= NULL
;
428 wxMsgCatalog
*pMsgCat
;
429 if ( szDomain
!= NULL
) {
430 pMsgCat
= FindCatalog(szDomain
);
432 // does the catalog exist?
433 if ( pMsgCat
!= NULL
)
434 pszTrans
= pMsgCat
->GetString(szOrigString
);
437 // search in all domains
438 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
) {
439 pszTrans
= pMsgCat
->GetString(szOrigString
);
440 if ( pszTrans
!= NULL
) // take the first found
445 if ( pszTrans
== NULL
) {
446 if ( wxIsLoggingTransErrors() ) {
447 // suppress further error messages
448 // (do it before LogWarning to prevent infinite recursion!)
449 wxSuppressTransErrors();
451 if ( szDomain
!= NULL
)
452 wxLogWarning(_("string '%s' not found in domain '%s' for locale '%s'."),
453 szOrigString
, szDomain
, m_strLocale
.c_str());
455 wxLogWarning(_("string '%s' not found in locale '%s'."),
456 szOrigString
, m_strLocale
.c_str());
465 // find catalog by name in a linked list, return NULL if !found
466 wxMsgCatalog
*wxLocale::FindCatalog(const char *szDomain
) const
468 // linear search in the linked list
469 wxMsgCatalog
*pMsgCat
;
470 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
) {
471 if ( Stricmp(pMsgCat
->GetName(), szDomain
) == 0 )
478 // check if the given catalog is loaded
479 bool wxLocale::IsLoaded(const char *szDomain
) const
481 return FindCatalog(szDomain
) != NULL
;
484 // add a catalog to our linked list
485 bool wxLocale::AddCatalog(const char *szDomain
)
487 wxMsgCatalog
*pMsgCat
= new wxMsgCatalog
;
489 if ( pMsgCat
->Load(m_strShort
, szDomain
) ) {
490 // add it to the head of the list so that in GetString it will
491 // be searched before the catalogs added earlier
492 pMsgCat
->m_pNext
= m_pMsgCat
;
498 // don't add it because it couldn't be loaded anyway
505 // ----------------------------------------------------------------------------
506 // global functions and variables
507 // ----------------------------------------------------------------------------
509 // translation errors logging
510 // --------------------------
512 static bool gs_bGiveTransErrors
= TRUE
;
514 void wxSuppressTransErrors()
516 gs_bGiveTransErrors
= FALSE
;
519 void wxRestoreTransErrors()
521 gs_bGiveTransErrors
= TRUE
;
524 bool wxIsLoggingTransErrors()
526 return gs_bGiveTransErrors
;
529 // retrieve/change current locale
530 // ------------------------------
532 // the current locale object
533 wxLocale
*g_pLocale
= NULL
;
535 wxLocale
*wxGetLocale()
540 wxLocale
*wxSetLocale(wxLocale
*pLocale
)
542 wxLocale
*pOld
= g_pLocale
;