]> git.saurik.com Git - wxWidgets.git/blame - src/common/intl.cpp
Maybe negative wxWindowId are better than just -1.
[wxWidgets.git] / src / common / intl.cpp
CommitLineData
c801d85f
KB
1/////////////////////////////////////////////////////////////////////////////
2// Name: intl.cpp
3// Purpose: Internationalization and localisation for wxWindows
4// Author: Vadim Zeitlin
5// Modified by:
6// Created: 29/01/98
7// RCS-ID: $Id$
8// Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
7af89395 9// Licence: wxWindows license
c801d85f
KB
10/////////////////////////////////////////////////////////////////////////////
11
12// ============================================================================
1678ad78 13// declaration
c801d85f
KB
14// ============================================================================
15
16// ----------------------------------------------------------------------------
17// headers
18// ----------------------------------------------------------------------------
19
20#ifdef __GNUG__
84c18814 21 #pragma implementation "intl.h"
c801d85f
KB
22#endif
23
24// For compilers that support precompilation, includes "wx.h".
25#include "wx/wxprec.h"
26
27#ifdef __BORLANDC__
84c18814 28 #pragma hdrstop
c801d85f
KB
29#endif
30
1678ad78
GL
31// standard headers
32#include <locale.h>
0fb67cd1 33#include <ctype.h>
7502ba29 34
1678ad78
GL
35// wxWindows
36#include "wx/defs.h"
37#include "wx/string.h"
c801d85f
KB
38#include "wx/intl.h"
39#include "wx/file.h"
40#include "wx/log.h"
41#include "wx/utils.h"
42
43#include <stdlib.h>
44
84c18814
VZ
45// ----------------------------------------------------------------------------
46// simple types
47// ----------------------------------------------------------------------------
48
49// FIXME adjust if necessary
50typedef unsigned char size_t8;
51typedef unsigned long size_t32;
52
c801d85f
KB
53// ----------------------------------------------------------------------------
54// constants
55// ----------------------------------------------------------------------------
56
57// magic number identifying the .mo format file
c86f1403
VZ
58const size_t32 MSGCATALOG_MAGIC = 0x950412de;
59const size_t32 MSGCATALOG_MAGIC_SW = 0xde120495;
c801d85f
KB
60
61// extension of ".mo" files
62#define MSGCATALOG_EXTENSION ".mo"
63
64// ----------------------------------------------------------------------------
1678ad78 65// global functions
c801d85f
KB
66// ----------------------------------------------------------------------------
67
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
71// once)
1678ad78 72void wxSuppressTransErrors();
c801d85f
KB
73
74// restore the logging
1678ad78 75void wxRestoreTransErrors();
c801d85f
KB
76
77// get the current state
1678ad78 78bool wxIsLoggingTransErrors();
c801d85f 79
84c18814 80static wxLocale *wxSetLocale(wxLocale *pLocale);
c801d85f
KB
81
82// ----------------------------------------------------------------------------
83// wxMsgCatalog corresponds to one disk-file message catalog.
84//
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// ----------------------------------------------------------------------------
88
89class wxMsgCatalog
90{
91public:
92 // ctor & dtor
93 wxMsgCatalog();
94 ~wxMsgCatalog();
95
96 // load the catalog from disk (szDirPrefix corresponds to language)
e36e6f95 97 bool Load(const wxChar *szDirPrefix, const wxChar *szName);
c801d85f
KB
98 bool IsLoaded() const { return m_pData != NULL; }
99
100 // get name of the catalog
e36e6f95 101 const wxChar *GetName() const { return m_pszName; }
c801d85f
KB
102
103 // get the translated string: returns NULL if not found
104 const char *GetString(const char *sz) const;
105
106 // public variable pointing to the next element in a linked list (or NULL)
107 wxMsgCatalog *m_pNext;
7af89395 108
c801d85f
KB
109private:
110 // this implementation is binary compatible with GNU gettext() version 0.10
111
112 // an entry in the string table
113 struct wxMsgTableEntry
114 {
c86f1403
VZ
115 size_t32 nLen; // length of the string
116 size_t32 ofsString; // pointer to the string
c801d85f
KB
117 };
118
119 // header of a .mo file
120 struct wxMsgCatalogHeader
121 {
c86f1403 122 size_t32 magic, // offset +00: magic id
84c18814
VZ
123 revision, // +04: revision
124 numStrings; // +08: number of strings in the file
c86f1403 125 size_t32 ofsOrigTable, // +0C: start of original string table
84c18814 126 ofsTransTable; // +10: start of translated string table
c86f1403 127 size_t32 nHashSize, // +14: hash table size
84c18814 128 ofsHashTable; // +18: offset of hash table start
7af89395
VZ
129 };
130
c801d85f 131 // all data is stored here, NULL if no data loaded
c86f1403 132 size_t8 *m_pData;
7af89395 133
c801d85f 134 // data description
84c18814 135 size_t32 m_numStrings, // number of strings in this domain
c801d85f 136 m_nHashSize; // number of entries in hash table
84c18814 137 size_t32 *m_pHashTable; // pointer to hash table
c801d85f
KB
138 wxMsgTableEntry *m_pOrigTable, // pointer to original strings
139 *m_pTransTable; // translated
140
c86f1403 141 const char *StringAtOfs(wxMsgTableEntry *pTable, size_t32 index) const
c801d85f
KB
142 { return (const char *)(m_pData + Swap(pTable[index].ofsString)); }
143
144 // utility functions
145 // calculate the hash value of given string
c86f1403 146 static inline size_t32 GetHash(const char *sz);
c801d85f 147 // big<->little endian
c86f1403 148 inline size_t32 Swap(size_t32 ui) const;
c801d85f
KB
149
150 // internal state
151 bool HasHashTable() const // true if hash table is present
152 { return m_nHashSize > 2 && m_pHashTable != NULL; }
153
154 bool m_bSwapped; // wrong endianness?
155
e36e6f95 156 wxChar *m_pszName; // name of the domain
c801d85f
KB
157};
158
fd323a5e
VZ
159// ----------------------------------------------------------------------------
160// global variables
161// ----------------------------------------------------------------------------
162
163// the list of the directories to search for message catalog files
164static wxArrayString s_searchPrefixes;
165
c801d85f
KB
166// ============================================================================
167// implementation
168// ============================================================================
169
170// ----------------------------------------------------------------------------
171// wxMsgCatalog class
172// ----------------------------------------------------------------------------
173
174// calculate hash value using the so called hashpjw function by P.J. Weinberger
175// [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools]
c86f1403 176size_t32 wxMsgCatalog::GetHash(const char *sz)
c801d85f 177{
c86f1403 178 #define HASHWORDBITS 32 // the length of size_t32
c801d85f 179
c86f1403
VZ
180 size_t32 hval = 0;
181 size_t32 g;
c801d85f
KB
182 while ( *sz != '\0' ) {
183 hval <<= 4;
c86f1403
VZ
184 hval += (size_t32)*sz++;
185 g = hval & ((size_t32)0xf << (HASHWORDBITS - 4));
c801d85f
KB
186 if ( g != 0 ) {
187 hval ^= g >> (HASHWORDBITS - 8);
188 hval ^= g;
189 }
190 }
191
192 return hval;
193}
194
195// swap the 2 halves of 32 bit integer if needed
c86f1403 196size_t32 wxMsgCatalog::Swap(size_t32 ui) const
c801d85f 197{
7af89395 198 return m_bSwapped ? (ui << 24) | ((ui & 0xff00) << 8) |
c801d85f
KB
199 ((ui >> 8) & 0xff00) | (ui >> 24)
200 : ui;
201}
202
7af89395
VZ
203wxMsgCatalog::wxMsgCatalog()
204{
c801d85f
KB
205 m_pData = NULL;
206 m_pszName = NULL;
207}
208
7af89395
VZ
209wxMsgCatalog::~wxMsgCatalog()
210{
211 wxDELETEA(m_pData);
212 wxDELETEA(m_pszName);
c801d85f
KB
213}
214
fd323a5e 215// small class to suppress the translation erros until exit from current scope
c801d85f
KB
216class NoTransErr
217{
fd323a5e 218public:
c801d85f
KB
219 NoTransErr() { wxSuppressTransErrors(); }
220 ~NoTransErr() { wxRestoreTransErrors(); }
221};
7af89395 222
fd323a5e 223// return all directories to search for given prefix
e36e6f95
OK
224static wxString GetAllMsgCatalogSubdirs(const wxChar *prefix,
225 const wxChar *lang)
fd323a5e
VZ
226{
227 wxString searchPath;
228
229 // search first in prefix/fr/LC_MESSAGES, then in prefix/fr and finally in
230 // prefix (assuming the language is 'fr')
7af89395 231 searchPath << prefix << wxFILE_SEP_PATH << lang << wxFILE_SEP_PATH
e36e6f95 232 << _T("LC_MESSAGES") << wxPATH_SEP
7af89395
VZ
233 << prefix << wxFILE_SEP_PATH << lang << wxPATH_SEP
234 << prefix << wxPATH_SEP;
fd323a5e
VZ
235
236 return searchPath;
237}
238
239// construct the search path for the given language
e36e6f95 240static wxString GetFullSearchPath(const wxChar *lang)
fd323a5e
VZ
241{
242 wxString searchPath;
243
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++ )
247 {
248 searchPath << GetAllMsgCatalogSubdirs(s_searchPrefixes[n], lang)
7af89395 249 << wxPATH_SEP;
fd323a5e
VZ
250 }
251
252 // then take the current directory
253 // FIXME it should be the directory of the executable
e36e6f95 254 searchPath << GetAllMsgCatalogSubdirs(_T("."), lang) << wxPATH_SEP;
fd323a5e
VZ
255
256 // and finally add some standard ones
257 searchPath
e36e6f95
OK
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);
fd323a5e
VZ
261
262 return searchPath;
263}
264
c801d85f 265// open disk file and read in it's contents
e36e6f95 266bool wxMsgCatalog::Load(const wxChar *szDirPrefix, const wxChar *szName)
c801d85f 267{
fd323a5e
VZ
268 // FIXME VZ: I forgot the exact meaning of LC_PATH - anyone to remind me?
269#if 0
e36e6f95 270 const wxChar *pszLcPath = wxGetenv("LC_PATH");
c801d85f 271 if ( pszLcPath != NULL )
fd323a5e
VZ
272 strPath += pszLcPath + wxString(szDirPrefix) + MSG_PATH;
273#endif // 0
7af89395 274
fd323a5e 275 wxString searchPath = GetFullSearchPath(szDirPrefix);
e36e6f95 276 const wxChar *sublocale = wxStrchr(szDirPrefix, _T('_'));
fd323a5e
VZ
277 if ( sublocale )
278 {
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
281 // exist
282 searchPath << GetFullSearchPath(wxString(szDirPrefix).
283 Left((size_t)(sublocale - szDirPrefix)))
7af89395 284 << wxPATH_SEP;
fd323a5e 285 }
7af89395 286
c801d85f
KB
287 wxString strFile = szName;
288 strFile += MSGCATALOG_EXTENSION;
289
290 // don't give translation errors here because the wxstd catalog might
291 // not yet be loaded (and it's normal)
292 //
293 // (we're using an object because we have several return paths)
294 NoTransErr noTransErr;
295
1a5a8367 296 wxLogVerbose(_("looking for catalog '%s' in path '%s'."),
fd323a5e 297 szName, searchPath.c_str());
c801d85f
KB
298
299 wxString strFullName;
fd323a5e 300 if ( !wxFindFileInPath(&strFullName, searchPath, strFile) ) {
1a5a8367 301 wxLogWarning(_("catalog file for domain '%s' not found."), szName);
c801d85f
KB
302 return FALSE;
303 }
304
305 // open file
1a5a8367 306 wxLogVerbose(_("using catalog '%s' from '%s'."),
1678ad78 307 szName, strFullName.c_str());
7af89395 308
c801d85f
KB
309 wxFile fileMsg(strFullName);
310 if ( !fileMsg.IsOpened() )
1678ad78 311 return FALSE;
c801d85f
KB
312
313 // get the file size
1678ad78
GL
314 off_t nSize = fileMsg.Length();
315 if ( nSize == wxInvalidOffset )
316 return FALSE;
c801d85f
KB
317
318 // read the whole file in memory
c86f1403 319 m_pData = new size_t8[nSize];
c801d85f 320 if ( fileMsg.Read(m_pData, nSize) != nSize ) {
a3622daa 321 wxDELETEA(m_pData);
1678ad78 322 return FALSE;
c801d85f 323 }
7af89395 324
c801d85f 325 // examine header
1678ad78 326 bool bValid = (size_t)nSize > sizeof(wxMsgCatalogHeader);
7af89395 327
fd3f686c 328 wxMsgCatalogHeader *pHeader = (wxMsgCatalogHeader *)m_pData;
c801d85f 329 if ( bValid ) {
c801d85f
KB
330 // we'll have to swap all the integers if it's true
331 m_bSwapped = pHeader->magic == MSGCATALOG_MAGIC_SW;
332
333 // check the magic number
334 bValid = m_bSwapped || pHeader->magic == MSGCATALOG_MAGIC;
335 }
7af89395 336
c801d85f
KB
337 if ( !bValid ) {
338 // it's either too short or has incorrect magic number
1a5a8367 339 wxLogWarning(_("'%s' is not a valid message catalog."), strFullName.c_str());
7af89395 340
a3622daa 341 wxDELETEA(m_pData);
c801d85f
KB
342 return FALSE;
343 }
7af89395 344
c801d85f
KB
345 // initialize
346 m_numStrings = Swap(pHeader->numStrings);
7af89395 347 m_pOrigTable = (wxMsgTableEntry *)(m_pData +
1678ad78 348 Swap(pHeader->ofsOrigTable));
7af89395 349 m_pTransTable = (wxMsgTableEntry *)(m_pData +
1678ad78 350 Swap(pHeader->ofsTransTable));
c801d85f
KB
351
352 m_nHashSize = Swap(pHeader->nHashSize);
c86f1403 353 m_pHashTable = (size_t32 *)(m_pData + Swap(pHeader->ofsHashTable));
c801d85f 354
e36e6f95
OK
355 m_pszName = new wxChar[wxStrlen(szName) + 1];
356 wxStrcpy(m_pszName, szName);
c801d85f
KB
357
358 // everything is fine
359 return TRUE;
360}
361
362// search for a string
363const char *wxMsgCatalog::GetString(const char *szOrig) const
364{
365 if ( szOrig == NULL )
366 return NULL;
367
368 if ( HasHashTable() ) { // use hash table for lookup if possible
7af89395 369 size_t32 nHashVal = GetHash(szOrig);
c86f1403 370 size_t32 nIndex = nHashVal % m_nHashSize;
c801d85f 371
c86f1403 372 size_t32 nIncr = 1 + (nHashVal % (m_nHashSize - 2));
7af89395 373
c801d85f 374 while ( TRUE ) {
c86f1403 375 size_t32 nStr = Swap(m_pHashTable[nIndex]);
c801d85f
KB
376 if ( nStr == 0 )
377 return NULL;
7af89395 378
c801d85f
KB
379 if ( strcmp(szOrig, StringAtOfs(m_pOrigTable, nStr - 1)) == 0 )
380 return StringAtOfs(m_pTransTable, nStr - 1);
381
382 if ( nIndex >= m_nHashSize - nIncr)
383 nIndex -= m_nHashSize - nIncr;
384 else
385 nIndex += nIncr;
386 }
387 }
388 else { // no hash table: use default binary search
c86f1403 389 size_t32 bottom = 0,
c801d85f
KB
390 top = m_numStrings,
391 current;
392 while ( bottom < top ) {
393 current = (bottom + top) / 2;
394 int res = strcmp(szOrig, StringAtOfs(m_pOrigTable, current));
395 if ( res < 0 )
396 top = current;
397 else if ( res > 0 )
398 bottom = current + 1;
399 else // found!
400 return StringAtOfs(m_pTransTable, current);
401 }
402 }
403
404 // not found
405 return NULL;
406}
407
408// ----------------------------------------------------------------------------
409// wxLocale
410// ----------------------------------------------------------------------------
411
23fcecf7 412wxLocale::wxLocale()
c801d85f 413{
23fcecf7
VZ
414 m_pszOldLocale = NULL;
415 m_pMsgCat = NULL;
416}
417
418// NB: this function has (desired) side effect of changing current locale
e36e6f95
OK
419bool wxLocale::Init(const wxChar *szName,
420 const wxChar *szShort,
421 const wxChar *szLocale,
23fcecf7
VZ
422 bool bLoadDefault)
423{
424 m_strLocale = szName;
425 m_strShort = szShort;
426
c801d85f
KB
427 // change current locale (default: same as long name)
428 if ( szLocale == NULL )
429 szLocale = szName;
e36e6f95 430 m_pszOldLocale = wxSetlocale(LC_ALL, szLocale);
c801d85f 431 if ( m_pszOldLocale == NULL )
1a5a8367 432 wxLogError(_("locale '%s' can not be set."), szLocale);
c801d85f
KB
433
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() ) {
fd323a5e
VZ
437 // FIXME I don't know how these 2 letter abbreviations are formed,
438 // this wild guess is surely wrong
0fb67cd1 439 m_strShort = tolower(szLocale[0]) + tolower(szLocale[1]);
c801d85f 440 }
7af89395 441
c801d85f 442 // save the old locale to be able to restore it later
7af89395
VZ
443 m_pOldLocale = wxSetLocale(this);
444
c801d85f
KB
445 // load the default catalog with wxWindows standard messages
446 m_pMsgCat = NULL;
23fcecf7 447 bool bOk = TRUE;
c801d85f 448 if ( bLoadDefault )
e36e6f95 449 bOk = AddCatalog(_T("wxstd"));
23fcecf7
VZ
450
451 return bOk;
c801d85f
KB
452}
453
fd323a5e
VZ
454void wxLocale::AddCatalogLookupPathPrefix(const wxString& prefix)
455{
3c67202d 456 if ( s_searchPrefixes.Index(prefix) == wxNOT_FOUND )
fd323a5e
VZ
457 {
458 s_searchPrefixes.Add(prefix);
459 }
460 //else: already have it
461}
462
c801d85f
KB
463// clean up
464wxLocale::~wxLocale()
465{
fd323a5e
VZ
466 // free memory
467 wxMsgCatalog *pTmpCat;
468 while ( m_pMsgCat != NULL ) {
469 pTmpCat = m_pMsgCat;
470 m_pMsgCat = m_pMsgCat->m_pNext;
471 delete pTmpCat;
472 }
473
474 // restore old locale
475 wxSetLocale(m_pOldLocale);
e36e6f95 476 wxSetlocale(LC_ALL, m_pszOldLocale);
c801d85f
KB
477}
478
479// get the translation of given string in current locale
e36e6f95
OK
480const wxMB2WXbuf wxLocale::GetString(const wxChar *szOrigString,
481 const wxChar *szDomain) const
c801d85f 482{
e36e6f95 483 if ( wxIsEmpty(szOrigString) )
dd0e574a 484 return szDomain;
c801d85f
KB
485
486 const char *pszTrans = NULL;
e36e6f95 487 const wxWX2MBbuf szOrgString = wxConv_libc.cWX2MB(szOrigString);
c801d85f
KB
488
489 wxMsgCatalog *pMsgCat;
490 if ( szDomain != NULL ) {
491 pMsgCat = FindCatalog(szDomain);
7af89395 492
c801d85f
KB
493 // does the catalog exist?
494 if ( pMsgCat != NULL )
e36e6f95 495 pszTrans = pMsgCat->GetString(szOrgString);
c801d85f
KB
496 }
497 else {
498 // search in all domains
499 for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext ) {
e36e6f95 500 pszTrans = pMsgCat->GetString(szOrgString);
c801d85f
KB
501 if ( pszTrans != NULL ) // take the first found
502 break;
503 }
504 }
505
506 if ( pszTrans == NULL ) {
507 if ( wxIsLoggingTransErrors() ) {
fd323a5e
VZ
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.
511
512 // do it before calling LogWarning to prevent infinite recursion!
513#ifdef __WXDEBUG__
514 NoTransErr noTransErr;
515#else // !debug
c801d85f 516 wxSuppressTransErrors();
fd323a5e 517#endif // debug/!debug
7af89395 518
c801d85f 519 if ( szDomain != NULL )
fd323a5e 520 {
1a5a8367 521 wxLogWarning(_("string '%s' not found in domain '%s' for locale '%s'."),
fd323a5e
VZ
522 szOrigString, szDomain, m_strLocale.c_str());
523 }
c801d85f 524 else
fd323a5e 525 {
1a5a8367 526 wxLogWarning(_("string '%s' not found in locale '%s'."),
fd323a5e
VZ
527 szOrigString, m_strLocale.c_str());
528 }
c801d85f
KB
529 }
530
e36e6f95 531 return (wxMB2WXbuf)(szOrigString);
c801d85f
KB
532 }
533 else
e36e6f95 534 return (wxMB2WXbuf)(wxConv_libc.cMB2WX(pszTrans));
c801d85f
KB
535}
536
537// find catalog by name in a linked list, return NULL if !found
e36e6f95 538wxMsgCatalog *wxLocale::FindCatalog(const wxChar *szDomain) const
c801d85f
KB
539{
540// linear search in the linked list
541 wxMsgCatalog *pMsgCat;
542 for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext ) {
e36e6f95 543 if ( wxStricmp(pMsgCat->GetName(), szDomain) == 0 )
c801d85f
KB
544 return pMsgCat;
545 }
7af89395 546
c801d85f
KB
547 return NULL;
548}
549
550// check if the given catalog is loaded
e36e6f95 551bool wxLocale::IsLoaded(const wxChar *szDomain) const
c801d85f
KB
552{
553 return FindCatalog(szDomain) != NULL;
554}
555
556// add a catalog to our linked list
e36e6f95 557bool wxLocale::AddCatalog(const wxChar *szDomain)
c801d85f
KB
558{
559 wxMsgCatalog *pMsgCat = new wxMsgCatalog;
7af89395 560
c801d85f
KB
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;
565 m_pMsgCat = pMsgCat;
7af89395 566
c801d85f
KB
567 return TRUE;
568 }
569 else {
570 // don't add it because it couldn't be loaded anyway
571 delete pMsgCat;
7af89395 572
c801d85f
KB
573 return FALSE;
574 }
575}
576
577// ----------------------------------------------------------------------------
578// global functions and variables
579// ----------------------------------------------------------------------------
580
581// translation errors logging
582// --------------------------
583
584static bool gs_bGiveTransErrors = TRUE;
585
586void wxSuppressTransErrors()
587{
588 gs_bGiveTransErrors = FALSE;
589}
590
591void wxRestoreTransErrors()
592{
593 gs_bGiveTransErrors = TRUE;
594}
595
596bool wxIsLoggingTransErrors()
597{
598 return gs_bGiveTransErrors;
599}
600
601// retrieve/change current locale
602// ------------------------------
603
604// the current locale object
84c18814 605static wxLocale *g_pLocale = NULL;
c801d85f 606
1678ad78
GL
607wxLocale *wxGetLocale()
608{
7af89395 609 return g_pLocale;
1678ad78
GL
610}
611
c801d85f
KB
612wxLocale *wxSetLocale(wxLocale *pLocale)
613{
7af89395
VZ
614 wxLocale *pOld = g_pLocale;
615 g_pLocale = pLocale;
616 return pOld;
c801d85f 617}