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
;
280 pHeader
= (wxMsgCatalogHeader
*)m_pData
;
282 // we'll have to swap all the integers if it's true
283 m_bSwapped
= pHeader
->magic
== MSGCATALOG_MAGIC_SW
;
285 // check the magic number
286 bValid
= m_bSwapped
|| pHeader
->magic
== MSGCATALOG_MAGIC
;
290 // it's either too short or has incorrect magic number
291 wxLogWarning(_("'%s' is not a valid message catalog."), strFullName
.c_str());
298 m_numStrings
= Swap(pHeader
->numStrings
);
299 m_pOrigTable
= (wxMsgTableEntry
*)(m_pData
+
300 Swap(pHeader
->ofsOrigTable
));
301 m_pTransTable
= (wxMsgTableEntry
*)(m_pData
+
302 Swap(pHeader
->ofsTransTable
));
304 m_nHashSize
= Swap(pHeader
->nHashSize
);
305 m_pHashTable
= (size_t32
*)(m_pData
+ Swap(pHeader
->ofsHashTable
));
307 m_pszName
= new char[strlen(szName
) + 1];
308 strcpy(m_pszName
, szName
);
310 // everything is fine
314 // search for a string
315 const char *wxMsgCatalog::GetString(const char *szOrig
) const
317 if ( szOrig
== NULL
)
320 if ( HasHashTable() ) { // use hash table for lookup if possible
321 size_t32 nHashVal
= GetHash(szOrig
);
322 size_t32 nIndex
= nHashVal
% m_nHashSize
;
324 size_t32 nIncr
= 1 + (nHashVal
% (m_nHashSize
- 2));
327 size_t32 nStr
= Swap(m_pHashTable
[nIndex
]);
331 if ( strcmp(szOrig
, StringAtOfs(m_pOrigTable
, nStr
- 1)) == 0 )
332 return StringAtOfs(m_pTransTable
, nStr
- 1);
334 if ( nIndex
>= m_nHashSize
- nIncr
)
335 nIndex
-= m_nHashSize
- nIncr
;
340 else { // no hash table: use default binary search
344 while ( bottom
< top
) {
345 current
= (bottom
+ top
) / 2;
346 int res
= strcmp(szOrig
, StringAtOfs(m_pOrigTable
, current
));
350 bottom
= current
+ 1;
352 return StringAtOfs(m_pTransTable
, current
);
360 // ----------------------------------------------------------------------------
362 // ----------------------------------------------------------------------------
366 m_pszOldLocale
= NULL
;
370 // NB: this function has (desired) side effect of changing current locale
371 bool wxLocale::Init(const char *szName
,
373 const char *szLocale
,
376 m_strLocale
= szName
;
377 m_strShort
= szShort
;
379 // change current locale (default: same as long name)
380 if ( szLocale
== NULL
)
382 m_pszOldLocale
= setlocale(LC_ALL
, szLocale
);
383 if ( m_pszOldLocale
== NULL
)
384 wxLogError(_("locale '%s' can not be set."), szLocale
);
386 // the short name will be used to look for catalog files as well,
387 // so we need something here
388 if ( m_strShort
.IsEmpty() ) {
389 // #### I don't know how these 2 letter abbreviations are formed,
390 // this wild guess is almost surely wrong
391 m_strShort
= wxToLower(szLocale
[0]) + wxToLower(szLocale
[1]);
394 // save the old locale to be able to restore it later
395 m_pOldLocale
= wxSetLocale(this);
397 // load the default catalog with wxWindows standard messages
401 bOk
= AddCatalog("wxstd");
407 wxLocale::~wxLocale()
410 wxMsgCatalog
*pTmpCat
;
411 while ( m_pMsgCat
!= NULL
) {
413 m_pMsgCat
= m_pMsgCat
->m_pNext
;
417 // restore old locale
418 wxSetLocale(m_pOldLocale
);
419 setlocale(LC_ALL
, m_pszOldLocale
);
422 // get the translation of given string in current locale
423 const char *wxLocale::GetString(const char *szOrigString
,
424 const char *szDomain
) const
426 wxASSERT( szOrigString
!= NULL
); // would be pretty silly
428 const char *pszTrans
= NULL
;
430 wxMsgCatalog
*pMsgCat
;
431 if ( szDomain
!= NULL
) {
432 pMsgCat
= FindCatalog(szDomain
);
434 // does the catalog exist?
435 if ( pMsgCat
!= NULL
)
436 pszTrans
= pMsgCat
->GetString(szOrigString
);
439 // search in all domains
440 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
) {
441 pszTrans
= pMsgCat
->GetString(szOrigString
);
442 if ( pszTrans
!= NULL
) // take the first found
447 if ( pszTrans
== NULL
) {
448 if ( wxIsLoggingTransErrors() ) {
449 // suppress further error messages
450 // (do it before LogWarning to prevent infinite recursion!)
451 wxSuppressTransErrors();
453 if ( szDomain
!= NULL
)
454 wxLogWarning(_("string '%s' not found in domain '%s' for locale '%s'."),
455 szOrigString
, szDomain
, m_strLocale
.c_str());
457 wxLogWarning(_("string '%s' not found in locale '%s'."),
458 szOrigString
, m_strLocale
.c_str());
467 // find catalog by name in a linked list, return NULL if !found
468 wxMsgCatalog
*wxLocale::FindCatalog(const char *szDomain
) const
470 // linear search in the linked list
471 wxMsgCatalog
*pMsgCat
;
472 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
) {
473 if ( Stricmp(pMsgCat
->GetName(), szDomain
) == 0 )
480 // check if the given catalog is loaded
481 bool wxLocale::IsLoaded(const char *szDomain
) const
483 return FindCatalog(szDomain
) != NULL
;
486 // add a catalog to our linked list
487 bool wxLocale::AddCatalog(const char *szDomain
)
489 wxMsgCatalog
*pMsgCat
= new wxMsgCatalog
;
491 if ( pMsgCat
->Load(m_strShort
, szDomain
) ) {
492 // add it to the head of the list so that in GetString it will
493 // be searched before the catalogs added earlier
494 pMsgCat
->m_pNext
= m_pMsgCat
;
500 // don't add it because it couldn't be loaded anyway
507 // ----------------------------------------------------------------------------
508 // global functions and variables
509 // ----------------------------------------------------------------------------
511 // translation errors logging
512 // --------------------------
514 static bool gs_bGiveTransErrors
= TRUE
;
516 void wxSuppressTransErrors()
518 gs_bGiveTransErrors
= FALSE
;
521 void wxRestoreTransErrors()
523 gs_bGiveTransErrors
= TRUE
;
526 bool wxIsLoggingTransErrors()
528 return gs_bGiveTransErrors
;
531 // retrieve/change current locale
532 // ------------------------------
534 // the current locale object
535 wxLocale
*g_pLocale
= NULL
;
537 wxLocale
*wxGetLocale()
542 wxLocale
*wxSetLocale(wxLocale
*pLocale
)
544 wxLocale
*pOld
= g_pLocale
;