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
== wxInvalidOffset
)
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 // ----------------------------------------------------------------------------
368 m_pszOldLocale
= NULL
;
372 // NB: this function has (desired) side effect of changing current locale
373 bool wxLocale::Init(const char *szName
,
375 const char *szLocale
,
378 m_strLocale
= szName
;
379 m_strShort
= szShort
;
381 // change current locale (default: same as long name)
382 if ( szLocale
== NULL
)
384 m_pszOldLocale
= setlocale(LC_ALL
, szLocale
);
385 if ( m_pszOldLocale
== NULL
)
386 wxLogError("locale '%s' can not be set.", szLocale
);
388 // the short name will be used to look for catalog files as well,
389 // so we need something here
390 if ( m_strShort
.IsEmpty() ) {
391 // #### I don't know how these 2 letter abbreviations are formed,
392 // this wild guess is almost surely wrong
393 m_strShort
= wxToLower(szLocale
[0]) + wxToLower(szLocale
[1]);
396 // save the old locale to be able to restore it later
397 m_pOldLocale
= wxSetLocale(this);
399 // load the default catalog with wxWindows standard messages
403 bOk
= AddCatalog("wxstd");
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' for locale '%s'.",
457 szOrigString
, szDomain
, m_strLocale
.c_str());
459 wxLogWarning("string '%s' not found in locale '%s'.",
460 szOrigString
, m_strLocale
.c_str());
469 // find catalog by name in a linked list, return NULL if !found
470 wxMsgCatalog
*wxLocale::FindCatalog(const char *szDomain
) const
472 // linear search in the linked list
473 wxMsgCatalog
*pMsgCat
;
474 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
) {
475 if ( Stricmp(pMsgCat
->GetName(), szDomain
) == 0 )
482 // check if the given catalog is loaded
483 bool wxLocale::IsLoaded(const char *szDomain
) const
485 return FindCatalog(szDomain
) != NULL
;
488 // add a catalog to our linked list
489 bool wxLocale::AddCatalog(const char *szDomain
)
491 wxMsgCatalog
*pMsgCat
= new wxMsgCatalog
;
493 if ( pMsgCat
->Load(m_strShort
, szDomain
) ) {
494 // add it to the head of the list so that in GetString it will
495 // be searched before the catalogs added earlier
496 pMsgCat
->m_pNext
= m_pMsgCat
;
502 // don't add it because it couldn't be loaded anyway
509 // ----------------------------------------------------------------------------
510 // global functions and variables
511 // ----------------------------------------------------------------------------
513 // translation errors logging
514 // --------------------------
516 static bool gs_bGiveTransErrors
= TRUE
;
518 void wxSuppressTransErrors()
520 gs_bGiveTransErrors
= FALSE
;
523 void wxRestoreTransErrors()
525 gs_bGiveTransErrors
= TRUE
;
528 bool wxIsLoggingTransErrors()
530 return gs_bGiveTransErrors
;
533 // retrieve/change current locale
534 // ------------------------------
536 // the current locale object
537 wxLocale
*g_pLocale
= NULL
;
539 wxLocale
*wxGetLocale()
544 wxLocale
*wxSetLocale(wxLocale
*pLocale
)
546 wxLocale
*pOld
= g_pLocale
;