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" 
  37 #include "wx/string.h" 
  45 // ---------------------------------------------------------------------------- 
  47 // ---------------------------------------------------------------------------- 
  49 // FIXME adjust if necessary 
  50 typedef unsigned char size_t8
; 
  51 typedef unsigned long size_t32
; 
  53 // ---------------------------------------------------------------------------- 
  55 // ---------------------------------------------------------------------------- 
  57 // magic number identifying the .mo format file 
  58 const size_t32 MSGCATALOG_MAGIC    
= 0x950412de; 
  59 const size_t32 MSGCATALOG_MAGIC_SW 
= 0xde120495; 
  61 // extension of ".mo" files 
  62 #define MSGCATALOG_EXTENSION  ".mo" 
  64 // ---------------------------------------------------------------------------- 
  66 // ---------------------------------------------------------------------------- 
  68 // suppress further error messages about missing translations 
  69 // (if you don't have one catalog file, you wouldn't like to see the 
  70 //  error message for each string in it, so normally it's given only 
  72 void wxSuppressTransErrors(); 
  74 // restore the logging 
  75 void wxRestoreTransErrors(); 
  77 // get the current state 
  78 bool wxIsLoggingTransErrors(); 
  80 static wxLocale 
*wxSetLocale(wxLocale 
*pLocale
); 
  82 // ---------------------------------------------------------------------------- 
  83 // wxMsgCatalog corresponds to one disk-file message catalog. 
  85 // This is a "low-level" class and is used only by wxLocale (that's why 
  86 // it's designed to be stored in a linked list) 
  87 // ---------------------------------------------------------------------------- 
  96   // load the catalog from disk (szDirPrefix corresponds to language) 
  97   bool Load(const wxChar 
*szDirPrefix
, const wxChar 
*szName
); 
  98   bool IsLoaded() const { return m_pData 
!= NULL
; } 
 100   // get name of the catalog 
 101   const wxChar 
*GetName() const { return m_pszName
; } 
 103   // get the translated string: returns NULL if not found 
 104   const char *GetString(const char *sz
) const; 
 106   // public variable pointing to the next element in a linked list (or NULL) 
 107   wxMsgCatalog 
*m_pNext
; 
 110   // this implementation is binary compatible with GNU gettext() version 0.10 
 112   // an entry in the string table 
 113   struct wxMsgTableEntry
 
 115     size_t32   nLen
;           // length of the string 
 116     size_t32   ofsString
;      // pointer to the string 
 119   // header of a .mo file 
 120   struct wxMsgCatalogHeader
 
 122     size_t32  magic
,          // offset +00:  magic id 
 123               revision
,       //        +04:  revision 
 124               numStrings
;     //        +08:  number of strings in the file 
 125     size_t32  ofsOrigTable
,   //        +0C:  start of original string table 
 126               ofsTransTable
;  //        +10:  start of translated string table 
 127     size_t32  nHashSize
,      //        +14:  hash table size 
 128               ofsHashTable
;   //        +18:  offset of hash table start 
 131   // all data is stored here, NULL if no data loaded 
 135   size_t32          m_numStrings
,   // number of strings in this domain 
 136                     m_nHashSize
;    // number of entries in hash table 
 137   size_t32         
*m_pHashTable
;   // pointer to hash table 
 138   wxMsgTableEntry  
*m_pOrigTable
,   // pointer to original   strings 
 139                    *m_pTransTable
;  //            translated 
 141   const char *StringAtOfs(wxMsgTableEntry 
*pTable
, size_t32 index
) const 
 142     { return (const char *)(m_pData 
+ Swap(pTable
[index
].ofsString
)); } 
 145     // calculate the hash value of given string 
 146   static inline size_t32 
GetHash(const char *sz
); 
 147     // big<->little endian 
 148   inline size_t32 
Swap(size_t32 ui
) const; 
 151   bool HasHashTable() const // true if hash table is present 
 152     { return m_nHashSize 
> 2 && m_pHashTable 
!= NULL
; } 
 154   bool          m_bSwapped
;   // wrong endianness? 
 156   wxChar       
*m_pszName
;    // name of the domain 
 159 // ---------------------------------------------------------------------------- 
 161 // ---------------------------------------------------------------------------- 
 163 // the list of the directories to search for message catalog files 
 164 static wxArrayString s_searchPrefixes
; 
 166 // ============================================================================ 
 168 // ============================================================================ 
 170 // ---------------------------------------------------------------------------- 
 171 // wxMsgCatalog class 
 172 // ---------------------------------------------------------------------------- 
 174 // calculate hash value using the so called hashpjw function by P.J. Weinberger 
 175 // [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools] 
 176 size_t32 
wxMsgCatalog::GetHash(const char *sz
) 
 178   #define HASHWORDBITS 32     // the length of size_t32 
 182   while ( *sz 
!= '\0' ) { 
 184     hval 
+= (size_t32
)*sz
++; 
 185     g 
= hval 
& ((size_t32
)0xf << (HASHWORDBITS 
- 4)); 
 187       hval 
^= g 
>> (HASHWORDBITS 
- 8); 
 195 // swap the 2 halves of 32 bit integer if needed 
 196 size_t32 
wxMsgCatalog::Swap(size_t32 ui
) const 
 198   return m_bSwapped 
? (ui 
<< 24) | ((ui 
& 0xff00) << 8) | 
 199                       ((ui 
>> 8) & 0xff00) | (ui 
>> 24) 
 203 wxMsgCatalog::wxMsgCatalog() 
 209 wxMsgCatalog::~wxMsgCatalog() 
 212   wxDELETEA(m_pszName
); 
 215 // small class to suppress the translation erros until exit from current scope 
 219     NoTransErr() { wxSuppressTransErrors(); } 
 220    ~NoTransErr() { wxRestoreTransErrors();  } 
 223 // return all directories to search for given prefix 
 224 static wxString 
GetAllMsgCatalogSubdirs(const wxChar 
*prefix
, 
 229     // search first in prefix/fr/LC_MESSAGES, then in prefix/fr and finally in 
 230     // prefix (assuming the language is 'fr') 
 231     searchPath 
<< prefix 
<< wxFILE_SEP_PATH 
<< lang 
<< wxFILE_SEP_PATH
 
 232                          << _T("LC_MESSAGES") << wxPATH_SEP
 
 233                << prefix 
<< wxFILE_SEP_PATH 
<< lang 
<< wxPATH_SEP
 
 234                << prefix 
<< wxPATH_SEP
; 
 239 // construct the search path for the given language 
 240 static wxString 
GetFullSearchPath(const wxChar 
*lang
) 
 244     // first take the entries explicitly added by the program 
 245     size_t count 
= s_searchPrefixes
.Count(); 
 246     for ( size_t n 
= 0; n 
< count
; n
++ ) 
 248         searchPath 
<< GetAllMsgCatalogSubdirs(s_searchPrefixes
[n
], lang
) 
 252     // then take the current directory 
 253     // FIXME it should be the directory of the executable 
 254     searchPath 
<< GetAllMsgCatalogSubdirs(_T("."), lang
) << wxPATH_SEP
; 
 256     // and finally add some standard ones 
 258         << GetAllMsgCatalogSubdirs(_T("/usr/share/locale"), lang
) << wxPATH_SEP
 
 259         << GetAllMsgCatalogSubdirs(_T("/usr/lib/locale"), lang
) << wxPATH_SEP
 
 260         << GetAllMsgCatalogSubdirs(_T("/usr/local/share/locale"), lang
); 
 265 // open disk file and read in it's contents 
 266 bool wxMsgCatalog::Load(const wxChar 
*szDirPrefix
, const wxChar 
*szName
) 
 268   // FIXME VZ: I forgot the exact meaning of LC_PATH - anyone to remind me? 
 270   const wxChar 
*pszLcPath 
= wxGetenv("LC_PATH"); 
 271   if ( pszLcPath 
!= NULL 
) 
 272       strPath 
+= pszLcPath 
+ wxString(szDirPrefix
) + MSG_PATH
; 
 275   wxString searchPath 
= GetFullSearchPath(szDirPrefix
); 
 276   const wxChar 
*sublocale 
= wxStrchr(szDirPrefix
, _T('_')); 
 279       // also add just base locale name: for things like "fr_BE" (belgium 
 280       // french) we should use "fr" if no belgium specific message catalogs 
 282       searchPath 
<< GetFullSearchPath(wxString(szDirPrefix
). 
 283                                       Left((size_t)(sublocale 
- szDirPrefix
))) 
 287   wxString strFile 
= szName
; 
 288   strFile 
+= MSGCATALOG_EXTENSION
; 
 290   // don't give translation errors here because the wxstd catalog might 
 291   // not yet be loaded (and it's normal) 
 293   // (we're using an object because we have several return paths) 
 294   NoTransErr noTransErr
; 
 296   wxLogVerbose(_("looking for catalog '%s' in path '%s'."), 
 297                szName
, searchPath
.c_str()); 
 299   wxString strFullName
; 
 300   if ( !wxFindFileInPath(&strFullName
, searchPath
, strFile
) ) { 
 301     wxLogWarning(_("catalog file for domain '%s' not found."), szName
); 
 306   wxLogVerbose(_("using catalog '%s' from '%s'."), 
 307              szName
, strFullName
.c_str()); 
 309   wxFile 
fileMsg(strFullName
); 
 310   if ( !fileMsg
.IsOpened() ) 
 314   off_t nSize 
= fileMsg
.Length(); 
 315   if ( nSize 
== wxInvalidOffset 
) 
 318   // read the whole file in memory 
 319   m_pData 
= new size_t8
[nSize
]; 
 320   if ( fileMsg
.Read(m_pData
, nSize
) != nSize 
) { 
 326   bool bValid 
= (size_t)nSize 
> sizeof(wxMsgCatalogHeader
); 
 328   wxMsgCatalogHeader 
*pHeader 
= (wxMsgCatalogHeader 
*)m_pData
; 
 330     // we'll have to swap all the integers if it's true 
 331     m_bSwapped 
= pHeader
->magic 
== MSGCATALOG_MAGIC_SW
; 
 333     // check the magic number 
 334     bValid 
= m_bSwapped 
|| pHeader
->magic 
== MSGCATALOG_MAGIC
; 
 338     // it's either too short or has incorrect magic number 
 339     wxLogWarning(_("'%s' is not a valid message catalog."), strFullName
.c_str()); 
 346   m_numStrings  
= Swap(pHeader
->numStrings
); 
 347   m_pOrigTable  
= (wxMsgTableEntry 
*)(m_pData 
+ 
 348                    Swap(pHeader
->ofsOrigTable
)); 
 349   m_pTransTable 
= (wxMsgTableEntry 
*)(m_pData 
+ 
 350                    Swap(pHeader
->ofsTransTable
)); 
 352   m_nHashSize   
= Swap(pHeader
->nHashSize
); 
 353   m_pHashTable  
= (size_t32 
*)(m_pData 
+ Swap(pHeader
->ofsHashTable
)); 
 355   m_pszName 
= new wxChar
[wxStrlen(szName
) + 1]; 
 356   wxStrcpy(m_pszName
, szName
); 
 358   // everything is fine 
 362 // search for a string 
 363 const char *wxMsgCatalog::GetString(const char *szOrig
) const 
 365   if ( szOrig 
== NULL 
) 
 368   if ( HasHashTable() ) {   // use hash table for lookup if possible 
 369     size_t32 nHashVal 
= GetHash(szOrig
); 
 370     size_t32 nIndex   
= nHashVal 
% m_nHashSize
; 
 372     size_t32 nIncr 
= 1 + (nHashVal 
% (m_nHashSize 
- 2)); 
 375       size_t32 nStr 
= Swap(m_pHashTable
[nIndex
]); 
 379       if ( strcmp(szOrig
, StringAtOfs(m_pOrigTable
, nStr 
- 1)) == 0 ) 
 380         return StringAtOfs(m_pTransTable
, nStr 
- 1); 
 382       if ( nIndex 
>= m_nHashSize 
- nIncr
) 
 383         nIndex 
-= m_nHashSize 
- nIncr
; 
 388   else {                    // no hash table: use default binary search 
 392     while ( bottom 
< top 
) { 
 393       current 
= (bottom 
+ top
) / 2; 
 394       int res 
= strcmp(szOrig
, StringAtOfs(m_pOrigTable
, current
)); 
 398         bottom 
= current 
+ 1; 
 400         return StringAtOfs(m_pTransTable
, current
); 
 408 // ---------------------------------------------------------------------------- 
 410 // ---------------------------------------------------------------------------- 
 414   m_pszOldLocale 
= NULL
; 
 418 // NB: this function has (desired) side effect of changing current locale 
 419 bool wxLocale::Init(const wxChar 
*szName
, 
 420                     const wxChar 
*szShort
, 
 421                     const wxChar 
*szLocale
, 
 424   m_strLocale 
= szName
; 
 425   m_strShort 
= szShort
; 
 427   // change current locale (default: same as long name) 
 428   if ( szLocale 
== NULL 
) 
 430   m_pszOldLocale 
= wxSetlocale(LC_ALL
, szLocale
); 
 431   if ( m_pszOldLocale 
== NULL 
) 
 432     wxLogError(_("locale '%s' can not be set."), szLocale
); 
 434   // the short name will be used to look for catalog files as well, 
 435   // so we need something here 
 436   if ( m_strShort
.IsEmpty() ) { 
 437     // FIXME I don't know how these 2 letter abbreviations are formed, 
 438     //       this wild guess is surely wrong 
 439     m_strShort 
= tolower(szLocale
[0]) + tolower(szLocale
[1]); 
 442   // save the old locale to be able to restore it later 
 443   m_pOldLocale 
= wxSetLocale(this); 
 445   // load the default catalog with wxWindows standard messages 
 449     bOk 
= AddCatalog(_T("wxstd")); 
 454 void wxLocale::AddCatalogLookupPathPrefix(const wxString
& prefix
) 
 456     if ( s_searchPrefixes
.Index(prefix
) == wxNOT_FOUND 
) 
 458         s_searchPrefixes
.Add(prefix
); 
 460     //else: already have it 
 464 wxLocale::~wxLocale() 
 467     wxMsgCatalog 
*pTmpCat
; 
 468     while ( m_pMsgCat 
!= NULL 
) { 
 470         m_pMsgCat 
= m_pMsgCat
->m_pNext
; 
 474     // restore old locale 
 475     wxSetLocale(m_pOldLocale
); 
 476     wxSetlocale(LC_ALL
, m_pszOldLocale
); 
 479 // get the translation of given string in current locale 
 480 const wxMB2WXbuf 
wxLocale::GetString(const wxChar 
*szOrigString
, 
 481                                      const wxChar 
*szDomain
) const 
 483   if ( wxIsEmpty(szOrigString
) ) 
 486   const char *pszTrans 
= NULL
; 
 487   const wxWX2MBbuf szOrgString 
= wxConv_libc
.cWX2MB(szOrigString
); 
 489   wxMsgCatalog 
*pMsgCat
; 
 490   if ( szDomain 
!= NULL 
) { 
 491     pMsgCat 
= FindCatalog(szDomain
); 
 493     // does the catalog exist? 
 494     if ( pMsgCat 
!= NULL 
) 
 495       pszTrans 
= pMsgCat
->GetString(szOrgString
); 
 498     // search in all domains 
 499     for ( pMsgCat 
= m_pMsgCat
; pMsgCat 
!= NULL
; pMsgCat 
= pMsgCat
->m_pNext 
) { 
 500       pszTrans 
= pMsgCat
->GetString(szOrgString
); 
 501       if ( pszTrans 
!= NULL 
)   // take the first found 
 506   if ( pszTrans 
== NULL 
) { 
 507     if ( wxIsLoggingTransErrors() ) { 
 508       // suppress further error messages if we're not debugging: this avoids 
 509       // flooding the user with messages about each and every missing string if, 
 510       // for example, a whole catalog file is missing. 
 512       // do it before calling LogWarning to prevent infinite recursion! 
 514       NoTransErr noTransErr
; 
 516       wxSuppressTransErrors(); 
 517 #endif // debug/!debug 
 519       if ( szDomain 
!= NULL 
) 
 521         wxLogWarning(_("string '%s' not found in domain '%s' for locale '%s'."), 
 522                      szOrigString
, szDomain
, m_strLocale
.c_str()); 
 526         wxLogWarning(_("string '%s' not found in locale '%s'."), 
 527                      szOrigString
, m_strLocale
.c_str()); 
 531     return (wxMB2WXbuf
)(szOrigString
); 
 534     return (wxMB2WXbuf
)(wxConv_libc
.cMB2WX(pszTrans
)); 
 537 // find catalog by name in a linked list, return NULL if !found 
 538 wxMsgCatalog 
*wxLocale::FindCatalog(const wxChar 
*szDomain
) const 
 540 // linear search in the linked list 
 541   wxMsgCatalog 
*pMsgCat
; 
 542   for ( pMsgCat 
= m_pMsgCat
; pMsgCat 
!= NULL
; pMsgCat 
= pMsgCat
->m_pNext 
) { 
 543     if ( wxStricmp(pMsgCat
->GetName(), szDomain
) == 0 ) 
 550 // check if the given catalog is loaded 
 551 bool wxLocale::IsLoaded(const wxChar 
*szDomain
) const 
 553   return FindCatalog(szDomain
) != NULL
; 
 556 // add a catalog to our linked list 
 557 bool wxLocale::AddCatalog(const wxChar 
*szDomain
) 
 559   wxMsgCatalog 
*pMsgCat 
= new wxMsgCatalog
; 
 561   if ( pMsgCat
->Load(m_strShort
, szDomain
) ) { 
 562     // add it to the head of the list so that in GetString it will 
 563     // be searched before the catalogs added earlier 
 564     pMsgCat
->m_pNext 
= m_pMsgCat
; 
 570     // don't add it because it couldn't be loaded anyway 
 577 // ---------------------------------------------------------------------------- 
 578 // global functions and variables 
 579 // ---------------------------------------------------------------------------- 
 581 // translation errors logging 
 582 // -------------------------- 
 584 static bool gs_bGiveTransErrors 
= TRUE
; 
 586 void wxSuppressTransErrors() 
 588   gs_bGiveTransErrors 
= FALSE
; 
 591 void wxRestoreTransErrors() 
 593   gs_bGiveTransErrors 
= TRUE
; 
 596 bool wxIsLoggingTransErrors() 
 598   return gs_bGiveTransErrors
; 
 601 // retrieve/change current locale 
 602 // ------------------------------ 
 604 // the current locale object 
 605 static wxLocale 
*g_pLocale 
= NULL
; 
 607 wxLocale 
*wxGetLocale() 
 612 wxLocale 
*wxSetLocale(wxLocale 
*pLocale
) 
 614   wxLocale 
*pOld 
= g_pLocale
;