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 // FIXME adjust if necessary
49 typedef unsigned char size_t8
;
50 typedef unsigned long size_t32
;
52 // ----------------------------------------------------------------------------
54 // ----------------------------------------------------------------------------
56 // magic number identifying the .mo format file
57 const size_t32 MSGCATALOG_MAGIC
= 0x950412de;
58 const size_t32 MSGCATALOG_MAGIC_SW
= 0xde120495;
60 // extension of ".mo" files
61 #define MSGCATALOG_EXTENSION ".mo"
63 // ----------------------------------------------------------------------------
65 // ----------------------------------------------------------------------------
67 // suppress further error messages about missing translations
68 // (if you don't have one catalog file, you wouldn't like to see the
69 // error message for each string in it, so normally it's given only
71 void wxSuppressTransErrors();
73 // restore the logging
74 void wxRestoreTransErrors();
76 // get the current state
77 bool wxIsLoggingTransErrors();
79 static wxLocale
*wxSetLocale(wxLocale
*pLocale
);
81 // ----------------------------------------------------------------------------
82 // wxMsgCatalog corresponds to one disk-file message catalog.
84 // This is a "low-level" class and is used only by wxLocale (that's why
85 // it's designed to be stored in a linked list)
86 // ----------------------------------------------------------------------------
95 // load the catalog from disk (szDirPrefix corresponds to language)
96 bool Load(const char *szDirPrefix
, const char *szName
);
97 bool IsLoaded() const { return m_pData
!= NULL
; }
99 // get name of the catalog
100 const char *GetName() const { return m_pszName
; }
102 // get the translated string: returns NULL if not found
103 const char *GetString(const char *sz
) const;
105 // public variable pointing to the next element in a linked list (or NULL)
106 wxMsgCatalog
*m_pNext
;
109 // this implementation is binary compatible with GNU gettext() version 0.10
111 // an entry in the string table
112 struct wxMsgTableEntry
114 size_t32 nLen
; // length of the string
115 size_t32 ofsString
; // pointer to the string
118 // header of a .mo file
119 struct wxMsgCatalogHeader
121 size_t32 magic
, // offset +00: magic id
122 revision
, // +04: revision
123 numStrings
; // +08: number of strings in the file
124 size_t32 ofsOrigTable
, // +0C: start of original string table
125 ofsTransTable
; // +10: start of translated string table
126 size_t32 nHashSize
, // +14: hash table size
127 ofsHashTable
; // +18: offset of hash table start
130 // all data is stored here, NULL if no data loaded
134 size_t32 m_numStrings
, // number of strings in this domain
135 m_nHashSize
; // number of entries in hash table
136 size_t32
*m_pHashTable
; // pointer to hash table
137 wxMsgTableEntry
*m_pOrigTable
, // pointer to original strings
138 *m_pTransTable
; // translated
140 const char *StringAtOfs(wxMsgTableEntry
*pTable
, size_t32 index
) const
141 { return (const char *)(m_pData
+ Swap(pTable
[index
].ofsString
)); }
144 // calculate the hash value of given string
145 static inline size_t32
GetHash(const char *sz
);
146 // big<->little endian
147 inline size_t32
Swap(size_t32 ui
) const;
150 bool HasHashTable() const // true if hash table is present
151 { return m_nHashSize
> 2 && m_pHashTable
!= NULL
; }
153 bool m_bSwapped
; // wrong endianness?
155 char *m_pszName
; // name of the domain
158 // ----------------------------------------------------------------------------
160 // ----------------------------------------------------------------------------
162 // the list of the directories to search for message catalog files
163 static wxArrayString s_searchPrefixes
;
165 // ============================================================================
167 // ============================================================================
169 // ----------------------------------------------------------------------------
170 // wxMsgCatalog class
171 // ----------------------------------------------------------------------------
173 // calculate hash value using the so called hashpjw function by P.J. Weinberger
174 // [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools]
175 size_t32
wxMsgCatalog::GetHash(const char *sz
)
177 #define HASHWORDBITS 32 // the length of size_t32
181 while ( *sz
!= '\0' ) {
183 hval
+= (size_t32
)*sz
++;
184 g
= hval
& ((size_t32
)0xf << (HASHWORDBITS
- 4));
186 hval
^= g
>> (HASHWORDBITS
- 8);
194 // swap the 2 halves of 32 bit integer if needed
195 size_t32
wxMsgCatalog::Swap(size_t32 ui
) const
197 return m_bSwapped
? (ui
<< 24) | ((ui
& 0xff00) << 8) |
198 ((ui
>> 8) & 0xff00) | (ui
>> 24)
202 wxMsgCatalog::wxMsgCatalog()
208 wxMsgCatalog::~wxMsgCatalog()
211 wxDELETEA(m_pszName
);
214 // small class to suppress the translation erros until exit from current scope
218 NoTransErr() { wxSuppressTransErrors(); }
219 ~NoTransErr() { wxRestoreTransErrors(); }
222 // return all directories to search for given prefix
223 static wxString
GetAllMsgCatalogSubdirs(const char *prefix
,
228 // search first in prefix/fr/LC_MESSAGES, then in prefix/fr and finally in
229 // prefix (assuming the language is 'fr')
230 searchPath
<< prefix
<< wxFILE_SEP_PATH
<< lang
<< wxFILE_SEP_PATH
231 << "LC_MESSAGES" << wxPATH_SEP
232 << prefix
<< wxFILE_SEP_PATH
<< lang
<< wxPATH_SEP
233 << prefix
<< wxPATH_SEP
;
238 // construct the search path for the given language
239 static wxString
GetFullSearchPath(const char *lang
)
243 // first take the entries explicitly added by the program
244 size_t count
= s_searchPrefixes
.Count();
245 for ( size_t n
= 0; n
< count
; n
++ )
247 searchPath
<< GetAllMsgCatalogSubdirs(s_searchPrefixes
[n
], lang
)
251 // then take the current directory
252 // FIXME it should be the directory of the executable
253 searchPath
<< GetAllMsgCatalogSubdirs(".", lang
) << wxPATH_SEP
;
255 // and finally add some standard ones
257 << GetAllMsgCatalogSubdirs("/usr/share/locale", lang
) << wxPATH_SEP
258 << GetAllMsgCatalogSubdirs("/usr/lib/locale", lang
) << wxPATH_SEP
259 << GetAllMsgCatalogSubdirs("/usr/local/share/locale", lang
);
264 // open disk file and read in it's contents
265 bool wxMsgCatalog::Load(const char *szDirPrefix
, const char *szName
)
267 // FIXME VZ: I forgot the exact meaning of LC_PATH - anyone to remind me?
269 const char *pszLcPath
= getenv("LC_PATH");
270 if ( pszLcPath
!= NULL
)
271 strPath
+= pszLcPath
+ wxString(szDirPrefix
) + MSG_PATH
;
274 wxString searchPath
= GetFullSearchPath(szDirPrefix
);
275 const char *sublocale
= strchr(szDirPrefix
, '_');
278 // also add just base locale name: for things like "fr_BE" (belgium
279 // french) we should use "fr" if no belgium specific message catalogs
281 searchPath
<< GetFullSearchPath(wxString(szDirPrefix
).
282 Left((size_t)(sublocale
- szDirPrefix
)))
286 wxString strFile
= szName
;
287 strFile
+= MSGCATALOG_EXTENSION
;
289 // don't give translation errors here because the wxstd catalog might
290 // not yet be loaded (and it's normal)
292 // (we're using an object because we have several return paths)
293 NoTransErr noTransErr
;
295 wxLogVerbose(_("looking for catalog '%s' in path '%s'."),
296 szName
, searchPath
.c_str());
298 wxString strFullName
;
299 if ( !wxFindFileInPath(&strFullName
, searchPath
, strFile
) ) {
300 wxLogWarning(_("catalog file for domain '%s' not found."), szName
);
305 wxLogVerbose(_("using catalog '%s' from '%s'."),
306 szName
, strFullName
.c_str());
308 wxFile
fileMsg(strFullName
);
309 if ( !fileMsg
.IsOpened() )
313 off_t nSize
= fileMsg
.Length();
314 if ( nSize
== wxInvalidOffset
)
317 // read the whole file in memory
318 m_pData
= new size_t8
[nSize
];
319 if ( fileMsg
.Read(m_pData
, nSize
) != nSize
) {
325 bool bValid
= (size_t)nSize
> sizeof(wxMsgCatalogHeader
);
327 wxMsgCatalogHeader
*pHeader
= (wxMsgCatalogHeader
*)m_pData
;
329 // we'll have to swap all the integers if it's true
330 m_bSwapped
= pHeader
->magic
== MSGCATALOG_MAGIC_SW
;
332 // check the magic number
333 bValid
= m_bSwapped
|| pHeader
->magic
== MSGCATALOG_MAGIC
;
337 // it's either too short or has incorrect magic number
338 wxLogWarning(_("'%s' is not a valid message catalog."), strFullName
.c_str());
345 m_numStrings
= Swap(pHeader
->numStrings
);
346 m_pOrigTable
= (wxMsgTableEntry
*)(m_pData
+
347 Swap(pHeader
->ofsOrigTable
));
348 m_pTransTable
= (wxMsgTableEntry
*)(m_pData
+
349 Swap(pHeader
->ofsTransTable
));
351 m_nHashSize
= Swap(pHeader
->nHashSize
);
352 m_pHashTable
= (size_t32
*)(m_pData
+ Swap(pHeader
->ofsHashTable
));
354 m_pszName
= new char[strlen(szName
) + 1];
355 strcpy(m_pszName
, szName
);
357 // everything is fine
361 // search for a string
362 const char *wxMsgCatalog::GetString(const char *szOrig
) const
364 if ( szOrig
== NULL
)
367 if ( HasHashTable() ) { // use hash table for lookup if possible
368 size_t32 nHashVal
= GetHash(szOrig
);
369 size_t32 nIndex
= nHashVal
% m_nHashSize
;
371 size_t32 nIncr
= 1 + (nHashVal
% (m_nHashSize
- 2));
374 size_t32 nStr
= Swap(m_pHashTable
[nIndex
]);
378 if ( strcmp(szOrig
, StringAtOfs(m_pOrigTable
, nStr
- 1)) == 0 )
379 return StringAtOfs(m_pTransTable
, nStr
- 1);
381 if ( nIndex
>= m_nHashSize
- nIncr
)
382 nIndex
-= m_nHashSize
- nIncr
;
387 else { // no hash table: use default binary search
391 while ( bottom
< top
) {
392 current
= (bottom
+ top
) / 2;
393 int res
= strcmp(szOrig
, StringAtOfs(m_pOrigTable
, current
));
397 bottom
= current
+ 1;
399 return StringAtOfs(m_pTransTable
, current
);
407 // ----------------------------------------------------------------------------
409 // ----------------------------------------------------------------------------
413 m_pszOldLocale
= NULL
;
417 // NB: this function has (desired) side effect of changing current locale
418 bool wxLocale::Init(const char *szName
,
420 const char *szLocale
,
423 m_strLocale
= szName
;
424 m_strShort
= szShort
;
426 // change current locale (default: same as long name)
427 if ( szLocale
== NULL
)
429 m_pszOldLocale
= setlocale(LC_ALL
, szLocale
);
430 if ( m_pszOldLocale
== NULL
)
431 wxLogError(_("locale '%s' can not be set."), szLocale
);
433 // the short name will be used to look for catalog files as well,
434 // so we need something here
435 if ( m_strShort
.IsEmpty() ) {
436 // FIXME I don't know how these 2 letter abbreviations are formed,
437 // this wild guess is surely wrong
438 m_strShort
= wxToLower(szLocale
[0]) + wxToLower(szLocale
[1]);
441 // save the old locale to be able to restore it later
442 m_pOldLocale
= wxSetLocale(this);
444 // load the default catalog with wxWindows standard messages
448 bOk
= AddCatalog("wxstd");
453 void wxLocale::AddCatalogLookupPathPrefix(const wxString
& prefix
)
455 if ( s_searchPrefixes
.Index(prefix
) == wxNOT_FOUND
)
457 s_searchPrefixes
.Add(prefix
);
459 //else: already have it
463 wxLocale::~wxLocale()
466 wxMsgCatalog
*pTmpCat
;
467 while ( m_pMsgCat
!= NULL
) {
469 m_pMsgCat
= m_pMsgCat
->m_pNext
;
473 // restore old locale
474 wxSetLocale(m_pOldLocale
);
475 setlocale(LC_ALL
, m_pszOldLocale
);
478 // get the translation of given string in current locale
479 const char *wxLocale::GetString(const char *szOrigString
,
480 const char *szDomain
) const
482 wxASSERT( szOrigString
!= NULL
); // would be pretty silly
484 const char *pszTrans
= NULL
;
486 wxMsgCatalog
*pMsgCat
;
487 if ( szDomain
!= NULL
) {
488 pMsgCat
= FindCatalog(szDomain
);
490 // does the catalog exist?
491 if ( pMsgCat
!= NULL
)
492 pszTrans
= pMsgCat
->GetString(szOrigString
);
495 // search in all domains
496 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
) {
497 pszTrans
= pMsgCat
->GetString(szOrigString
);
498 if ( pszTrans
!= NULL
) // take the first found
503 if ( pszTrans
== NULL
) {
504 if ( wxIsLoggingTransErrors() ) {
505 // suppress further error messages if we're not debugging: this avoids
506 // flooding the user with messages about each and every missing string if,
507 // for example, a whole catalog file is missing.
509 // do it before calling LogWarning to prevent infinite recursion!
511 NoTransErr noTransErr
;
513 wxSuppressTransErrors();
514 #endif // debug/!debug
516 if ( szDomain
!= NULL
)
518 wxLogWarning(_("string '%s' not found in domain '%s' for locale '%s'."),
519 szOrigString
, szDomain
, m_strLocale
.c_str());
523 wxLogWarning(_("string '%s' not found in locale '%s'."),
524 szOrigString
, m_strLocale
.c_str());
534 // find catalog by name in a linked list, return NULL if !found
535 wxMsgCatalog
*wxLocale::FindCatalog(const char *szDomain
) const
537 // linear search in the linked list
538 wxMsgCatalog
*pMsgCat
;
539 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
) {
540 if ( Stricmp(pMsgCat
->GetName(), szDomain
) == 0 )
547 // check if the given catalog is loaded
548 bool wxLocale::IsLoaded(const char *szDomain
) const
550 return FindCatalog(szDomain
) != NULL
;
553 // add a catalog to our linked list
554 bool wxLocale::AddCatalog(const char *szDomain
)
556 wxMsgCatalog
*pMsgCat
= new wxMsgCatalog
;
558 if ( pMsgCat
->Load(m_strShort
, szDomain
) ) {
559 // add it to the head of the list so that in GetString it will
560 // be searched before the catalogs added earlier
561 pMsgCat
->m_pNext
= m_pMsgCat
;
567 // don't add it because it couldn't be loaded anyway
574 // ----------------------------------------------------------------------------
575 // global functions and variables
576 // ----------------------------------------------------------------------------
578 // translation errors logging
579 // --------------------------
581 static bool gs_bGiveTransErrors
= TRUE
;
583 void wxSuppressTransErrors()
585 gs_bGiveTransErrors
= FALSE
;
588 void wxRestoreTransErrors()
590 gs_bGiveTransErrors
= TRUE
;
593 bool wxIsLoggingTransErrors()
595 return gs_bGiveTransErrors
;
598 // retrieve/change current locale
599 // ------------------------------
601 // the current locale object
602 static wxLocale
*g_pLocale
= NULL
;
604 wxLocale
*wxGetLocale()
609 wxLocale
*wxSetLocale(wxLocale
*pLocale
)
611 wxLocale
*pOld
= g_pLocale
;