X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/ed58dbeab64d18a0f869ea8e1610a0a88ad7b22c..7198c3368055d88249a338eb33b21f051f674806:/src/common/intl.cpp diff --git a/src/common/intl.cpp b/src/common/intl.cpp index 3dd7a175ad..9b38ffbf14 100644 --- a/src/common/intl.cpp +++ b/src/common/intl.cpp @@ -1,12 +1,13 @@ ///////////////////////////////////////////////////////////////////////////// -// Name: intl.cpp -// Purpose: Internationalization and localisation for wxWindows +// Name: src/common/intl.cpp +// Purpose: Internationalization and localisation for wxWidgets // Author: Vadim Zeitlin -// Modified by: +// Modified by: Michael N. Filippov +// (2003/09/30 - PluralForms support) // Created: 29/01/98 // RCS-ID: $Id$ // Copyright: (c) 1998 Vadim Zeitlin -// Licence: wxWindows license +// Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// // ============================================================================ @@ -17,10 +18,6 @@ // headers // ---------------------------------------------------------------------------- -#ifdef __GNUG__ - #pragma implementation "intl.h" -#endif - // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" @@ -28,625 +25,1679 @@ #pragma hdrstop #endif -#if wxUSE_INTL +#ifdef __EMX__ +// The following define is needed by Innotek's libc to +// make the definition of struct localeconv available. +#define __INTERNAL_DEFS +#endif -// standard headers -#include -#include +#if wxUSE_INTL -// wxWindows -#include "wx/defs.h" -#include "wx/string.h" -#include "wx/intl.h" -#include "wx/file.h" -#include "wx/log.h" -#include "wx/debug.h" -#include "wx/utils.h" +#ifndef WX_PRECOMP + #include "wx/dynarray.h" + #include "wx/string.h" + #include "wx/intl.h" + #include "wx/log.h" + #include "wx/utils.h" + #include "wx/app.h" + #include "wx/hashmap.h" + #include "wx/module.h" +#endif // WX_PRECOMP + +#ifndef __WXWINCE__ + #include +#endif +// standard headers +#include #include +#ifdef HAVE_LANGINFO_H + #include +#endif -// ---------------------------------------------------------------------------- -// simple types -// ---------------------------------------------------------------------------- - -// this should *not* be wxChar, this type must have exactly 8 bits! -typedef unsigned char size_t8; - -#ifdef __WXMSW__ - #if defined(__WIN16__) - typedef unsigned long size_t32; - #elif defined(__WIN32__) - typedef unsigned int size_t32; - #else - // Win64 will have different type sizes - #error "Please define a 32 bit type" - #endif -#else // !Windows - // SIZEOF_XXX are defined by configure - #if defined(SIZEOF_INT) && (SIZEOF_INT == 4) - typedef unsigned int size_t32; - #elif defined(SIZEOF_LONG) && (SIZEOF_LONG == 4) - typedef unsigned long size_t32; - #else - // assume sizeof(int) == 4 - what else can we do - typedef unsigned int size_t32; - - // ... but at least check it during run time - static class IntSizeChecker - { - public: - IntSizeChecker() - { - // Asserting a sizeof directly causes some compilers to - // issue a "using constant in a conditional expression" warning - size_t intsize = sizeof(int); +#ifdef __WIN32__ + #include "wx/msw/private.h" +#endif - wxASSERT_MSG( intsize == 4, - "size_t32 is incorrectly defined!" ); - } - } intsizechecker; - #endif -#endif // Win/!Win +#include "wx/file.h" +#include "wx/filename.h" +#include "wx/tokenzr.h" +#include "wx/fontmap.h" +#include "wx/scopedptr.h" +#include "wx/apptrait.h" +#include "wx/stdpaths.h" +#include "wx/hashset.h" + +#if defined(__WXOSX__) + #include "wx/osx/core/cfref.h" + #include + #include + #include "wx/osx/core/cfstring.h" +#endif // ---------------------------------------------------------------------------- // constants // ---------------------------------------------------------------------------- -// magic number identifying the .mo format file -const size_t32 MSGCATALOG_MAGIC = 0x950412de; -const size_t32 MSGCATALOG_MAGIC_SW = 0xde120495; +#define TRACE_I18N wxS("i18n") -// extension of ".mo" files -#define MSGCATALOG_EXTENSION ".mo" +// ============================================================================ +// implementation +// ============================================================================ // ---------------------------------------------------------------------------- // global functions // ---------------------------------------------------------------------------- -// suppress further error messages about missing translations -// (if you don't have one catalog file, you wouldn't like to see the -// error message for each string in it, so normally it's given only -// once) -void wxSuppressTransErrors(); +static wxLocale *wxSetLocale(wxLocale *pLocale); + +namespace +{ + +// get just the language part ("en" in "en_GB") +inline wxString ExtractLang(const wxString& langFull) +{ + return langFull.BeforeFirst('_'); +} -// restore the logging -void wxRestoreTransErrors(); +// helper functions of GetSystemLanguage() +#ifdef __UNIX__ -// get the current state -bool wxIsLoggingTransErrors(); +// get everything else (including the leading '_') +inline wxString ExtractNotLang(const wxString& langFull) +{ + size_t pos = langFull.find('_'); + if ( pos != wxString::npos ) + return langFull.substr(pos); + else + return wxString(); +} -static wxLocale *wxSetLocale(wxLocale *pLocale); +#endif // __UNIX__ + +} // anonymous namespace // ---------------------------------------------------------------------------- -// wxMsgCatalog corresponds to one disk-file message catalog. -// -// This is a "low-level" class and is used only by wxLocale (that's why -// it's designed to be stored in a linked list) +// wxLanguageInfo // ---------------------------------------------------------------------------- -class wxMsgCatalog -{ -public: - // ctor & dtor - wxMsgCatalog(); - ~wxMsgCatalog(); - - // load the catalog from disk (szDirPrefix corresponds to language) - bool Load(const wxChar *szDirPrefix, const wxChar *szName); - bool IsLoaded() const { return m_pData != NULL; } - - // get name of the catalog - const wxChar *GetName() const { return m_pszName; } - - // get the translated string: returns NULL if not found - const char *GetString(const char *sz) const; - - // public variable pointing to the next element in a linked list (or NULL) - wxMsgCatalog *m_pNext; - -private: - // this implementation is binary compatible with GNU gettext() version 0.10 - - // an entry in the string table - struct wxMsgTableEntry - { - size_t32 nLen; // length of the string - size_t32 ofsString; // pointer to the string - }; - - // header of a .mo file - struct wxMsgCatalogHeader - { - size_t32 magic, // offset +00: magic id - revision, // +04: revision - numStrings; // +08: number of strings in the file - size_t32 ofsOrigTable, // +0C: start of original string table - ofsTransTable; // +10: start of translated string table - size_t32 nHashSize, // +14: hash table size - ofsHashTable; // +18: offset of hash table start - }; - - // all data is stored here, NULL if no data loaded - size_t8 *m_pData; - - // data description - size_t32 m_numStrings, // number of strings in this domain - m_nHashSize; // number of entries in hash table - size_t32 *m_pHashTable; // pointer to hash table - wxMsgTableEntry *m_pOrigTable, // pointer to original strings - *m_pTransTable; // translated - - const char *StringAtOfs(wxMsgTableEntry *pTable, size_t32 index) const - { return (const char *)(m_pData + Swap(pTable[index].ofsString)); } - - // utility functions - // calculate the hash value of given string - static inline size_t32 GetHash(const char *sz); - // big<->little endian - inline size_t32 Swap(size_t32 ui) const; - - // internal state - bool HasHashTable() const // true if hash table is present - { return m_nHashSize > 2 && m_pHashTable != NULL; } - - bool m_bSwapped; // wrong endianness? - - wxChar *m_pszName; // name of the domain -}; +#ifdef __WXMSW__ -// ---------------------------------------------------------------------------- -// global variables -// ---------------------------------------------------------------------------- +// helper used by wxLanguageInfo::GetLocaleName() and elsewhere to determine +// whether the locale is Unicode-only (it is if this function returns empty +// string) +static wxString wxGetANSICodePageForLocale(LCID lcid) +{ + wxString cp; -// the list of the directories to search for message catalog files -static wxArrayString s_searchPrefixes; + wxChar buffer[16]; + if ( ::GetLocaleInfo(lcid, LOCALE_IDEFAULTANSICODEPAGE, + buffer, WXSIZEOF(buffer)) > 0 ) + { + if ( buffer[0] != wxT('0') || buffer[1] != wxT('\0') ) + cp = buffer; + //else: this locale doesn't use ANSI code page + } -// ============================================================================ -// implementation -// ============================================================================ + return cp; +} + +wxUint32 wxLanguageInfo::GetLCID() const +{ + return MAKELCID(MAKELANGID(WinLang, WinSublang), SORT_DEFAULT); +} + +wxString wxLanguageInfo::GetLocaleName() const +{ + wxString locale; + + const LCID lcid = GetLCID(); + + wxChar buffer[256]; + buffer[0] = wxT('\0'); + if ( !::GetLocaleInfo(lcid, LOCALE_SENGLANGUAGE, buffer, WXSIZEOF(buffer)) ) + { + wxLogLastError(wxT("GetLocaleInfo(LOCALE_SENGLANGUAGE)")); + return locale; + } + + locale << buffer; + if ( ::GetLocaleInfo(lcid, LOCALE_SENGCOUNTRY, + buffer, WXSIZEOF(buffer)) > 0 ) + { + locale << wxT('_') << buffer; + } + + const wxString cp = wxGetANSICodePageForLocale(lcid); + if ( !cp.empty() ) + { + locale << wxT('.') << cp; + } + + return locale; +} + +#endif // __WXMSW__ // ---------------------------------------------------------------------------- -// wxMsgCatalog class +// wxLocale // ---------------------------------------------------------------------------- -// calculate hash value using the so called hashpjw function by P.J. Weinberger -// [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools] -size_t32 wxMsgCatalog::GetHash(const char *sz) +#include "wx/arrimpl.cpp" +WX_DECLARE_EXPORTED_OBJARRAY(wxLanguageInfo, wxLanguageInfoArray); +WX_DEFINE_OBJARRAY(wxLanguageInfoArray) + +wxLanguageInfoArray *wxLocale::ms_languagesDB = NULL; + +/*static*/ void wxLocale::CreateLanguagesDB() { - #define HASHWORDBITS 32 // the length of size_t32 + if (ms_languagesDB == NULL) + { + ms_languagesDB = new wxLanguageInfoArray; + InitLanguagesDB(); + } +} + +/*static*/ void wxLocale::DestroyLanguagesDB() +{ + wxDELETE(ms_languagesDB); +} - size_t32 hval = 0; - size_t32 g; - while ( *sz != '\0' ) { - hval <<= 4; - hval += (size_t32)*sz++; - g = hval & ((size_t32)0xf << (HASHWORDBITS - 4)); - if ( g != 0 ) { - hval ^= g >> (HASHWORDBITS - 8); - hval ^= g; + +void wxLocale::DoCommonInit() +{ + m_pszOldLocale = NULL; + + m_pOldLocale = wxSetLocale(this); + + // Set translations object, but only if the user didn't do so yet. + // This is to preserve compatibility with wx-2.8 where wxLocale was + // the only API for translations. wxLocale works as a stack, with + // latest-created one being the active one: + // wxLocale loc_fr(wxLANGUAGE_FRENCH); + // // _() returns French + // { + // wxLocale loc_cs(wxLANGUAGE_CZECH); + // // _() returns Czech + // } + // // _() returns French again + wxTranslations *oldTrans = wxTranslations::Get(); + if ( !oldTrans || + (m_pOldLocale && oldTrans == &m_pOldLocale->m_translations) ) + { + wxTranslations::SetNonOwned(&m_translations); } - } - return hval; + m_language = wxLANGUAGE_UNKNOWN; + m_initialized = false; } -// swap the 2 halves of 32 bit integer if needed -size_t32 wxMsgCatalog::Swap(size_t32 ui) const +// NB: this function has (desired) side effect of changing current locale +bool wxLocale::Init(const wxString& name, + const wxString& shortName, + const wxString& locale, + bool bLoadDefault +#if WXWIN_COMPATIBILITY_2_8 + ,bool WXUNUSED_UNLESS_DEBUG(bConvertEncoding) +#endif + ) { - return m_bSwapped ? (ui << 24) | ((ui & 0xff00) << 8) | - ((ui >> 8) & 0xff00) | (ui >> 24) - : ui; +#if WXWIN_COMPATIBILITY_2_8 + wxASSERT_MSG( bConvertEncoding, + wxS("wxLocale::Init with bConvertEncoding=false is no longer supported, add charset to your catalogs") ); +#endif + + bool ret = DoInit(name, shortName, locale); + + // NB: don't use 'lang' here, 'language' may be wxLANGUAGE_DEFAULT + wxTranslations *t = wxTranslations::Get(); + if ( t ) + { + t->SetLanguage(shortName); + + if ( bLoadDefault ) + t->AddStdCatalog(); + } + + return ret; } -wxMsgCatalog::wxMsgCatalog() +bool wxLocale::DoInit(const wxString& name, + const wxString& shortName, + const wxString& locale) { - m_pData = NULL; - m_pszName = NULL; + wxASSERT_MSG( !m_initialized, + wxS("you can't call wxLocale::Init more than once") ); + + m_initialized = true; + m_strLocale = name; + m_strShort = shortName; + m_language = wxLANGUAGE_UNKNOWN; + + // change current locale (default: same as long name) + wxString szLocale(locale); + if ( szLocale.empty() ) + { + // the argument to setlocale() + szLocale = shortName; + + wxCHECK_MSG( !szLocale.empty(), false, + wxS("no locale to set in wxLocale::Init()") ); + } + + const char *oldLocale = wxSetlocale(LC_ALL, szLocale); + if ( oldLocale ) + m_pszOldLocale = wxStrdup(oldLocale); + else + m_pszOldLocale = NULL; + + if ( m_pszOldLocale == NULL ) + { + wxLogError(_("locale '%s' cannot be set."), szLocale); + } + + // the short name will be used to look for catalog files as well, + // so we need something here + if ( m_strShort.empty() ) { + // FIXME I don't know how these 2 letter abbreviations are formed, + // this wild guess is surely wrong + if ( !szLocale.empty() ) + { + m_strShort += (wxChar)wxTolower(szLocale[0]); + if ( szLocale.length() > 1 ) + m_strShort += (wxChar)wxTolower(szLocale[1]); + } + } + + return true; } -wxMsgCatalog::~wxMsgCatalog() + +#if defined(__UNIX__) && wxUSE_UNICODE && !defined(__WXMAC__) +static const char *wxSetlocaleTryUTF8(int c, const wxString& lc) { - wxDELETEA(m_pData); - wxDELETEA(m_pszName); + const char *l = NULL; + + // NB: We prefer to set UTF-8 locale if it's possible and only fall back to + // non-UTF-8 locale if it fails + + if ( !lc.empty() ) + { + wxString buf(lc); + wxString buf2; + buf2 = buf + wxS(".UTF-8"); + l = wxSetlocale(c, buf2); + if ( !l ) + { + buf2 = buf + wxS(".utf-8"); + l = wxSetlocale(c, buf2); + } + if ( !l ) + { + buf2 = buf + wxS(".UTF8"); + l = wxSetlocale(c, buf2); + } + if ( !l ) + { + buf2 = buf + wxS(".utf8"); + l = wxSetlocale(c, buf2); + } + } + + // if we can't set UTF-8 locale, try non-UTF-8 one: + if ( !l ) + l = wxSetlocale(c, lc); + + return l; } +#else +#define wxSetlocaleTryUTF8(c, lc) wxSetlocale(c, lc) +#endif -// small class to suppress the translation erros until exit from current scope -class NoTransErr +bool wxLocale::Init(int language, int flags) { -public: - NoTransErr() { wxSuppressTransErrors(); } - ~NoTransErr() { wxRestoreTransErrors(); } -}; +#if WXWIN_COMPATIBILITY_2_8 + wxASSERT_MSG( !(flags & wxLOCALE_CONV_ENCODING), + wxS("wxLOCALE_CONV_ENCODING is no longer supported, add charset to your catalogs") ); +#endif -// return all directories to search for given prefix -static wxString GetAllMsgCatalogSubdirs(const wxChar *prefix, - const wxChar *lang) -{ - wxString searchPath; + bool ret = true; - // search first in prefix/fr/LC_MESSAGES, then in prefix/fr and finally in - // prefix (assuming the language is 'fr') - searchPath << prefix << wxFILE_SEP_PATH << lang << wxFILE_SEP_PATH - << _T("LC_MESSAGES") << wxPATH_SEP - << prefix << wxFILE_SEP_PATH << lang << wxPATH_SEP - << prefix << wxPATH_SEP; - - return searchPath; -} - -// construct the search path for the given language -static wxString GetFullSearchPath(const wxChar *lang) -{ - wxString searchPath; - - // first take the entries explicitly added by the program - size_t count = s_searchPrefixes.Count(); - for ( size_t n = 0; n < count; n++ ) - { - searchPath << GetAllMsgCatalogSubdirs(s_searchPrefixes[n], lang) - << wxPATH_SEP; - } - - // then take the current directory - // FIXME it should be the directory of the executable - searchPath << GetAllMsgCatalogSubdirs(_T("."), lang) << wxPATH_SEP; - - // and finally add some standard ones - searchPath - << GetAllMsgCatalogSubdirs(_T("/usr/share/locale"), lang) << wxPATH_SEP - << GetAllMsgCatalogSubdirs(_T("/usr/lib/locale"), lang) << wxPATH_SEP - << GetAllMsgCatalogSubdirs(_T("/usr/local/share/locale"), lang); - - return searchPath; -} - -// open disk file and read in it's contents -bool wxMsgCatalog::Load(const wxChar *szDirPrefix, const wxChar *szName0) -{ - /* We need to handle locales like de_AT.iso-8859-1 - For this we first chop off the .CHARSET specifier and ignore it. - FIXME: UNICODE SUPPORT: must use CHARSET specifier! - */ - wxString szName = szName0; - if(szName.Find('.') != -1) // contains a dot - szName = szName.Left(szName.Find('.')); - - // FIXME VZ: I forgot the exact meaning of LC_PATH - anyone to remind me? - // KB: search path where to find the mo files, probably : delimited -#if 0 - const wxChar *pszLcPath = wxGetenv("LC_PATH"); - if ( pszLcPath != NULL ) - strPath += pszLcPath + wxString(szDirPrefix) + MSG_PATH; -#endif // 0 + int lang = language; + if (lang == wxLANGUAGE_DEFAULT) + { + // auto detect the language + lang = GetSystemLanguage(); + } + + // We failed to detect system language, so we will use English: + if (lang == wxLANGUAGE_UNKNOWN) + { + return false; + } + + const wxLanguageInfo *info = GetLanguageInfo(lang); - wxString searchPath = GetFullSearchPath(szDirPrefix); - const wxChar *sublocale = wxStrchr(szDirPrefix, _T('_')); - if ( sublocale ) - { - // also add just base locale name: for things like "fr_BE" (belgium - // french) we should use "fr" if no belgium specific message catalogs - // exist - searchPath << GetFullSearchPath(wxString(szDirPrefix). - Left((size_t)(sublocale - szDirPrefix))) - << wxPATH_SEP; - } + // Unknown language: + if (info == NULL) + { + wxLogError(wxS("Unknown language %i."), lang); + return false; + } - wxString strFile = szName; - strFile += MSGCATALOG_EXTENSION; + wxString name = info->Description; + wxString canonical = info->CanonicalName; + wxString locale; - // don't give translation errors here because the wxstd catalog might - // not yet be loaded (and it's normal) - // - // (we're using an object because we have several return paths) + // Set the locale: +#if defined(__OS2__) + const char *retloc = wxSetlocale(LC_ALL , wxEmptyString); +#elif defined(__UNIX__) && !defined(__WXMAC__) + if (language != wxLANGUAGE_DEFAULT) + locale = info->CanonicalName; - NoTransErr noTransErr; -// Then why do you translate at all? Just use _T() and not _(). RR. + const char *retloc = wxSetlocaleTryUTF8(LC_ALL, locale); - wxLogVerbose(_("looking for catalog '%s' in path '%s'."), - szName.c_str, searchPath.c_str()); + const wxString langOnly = ExtractLang(locale); + if ( !retloc ) + { + // Some C libraries don't like xx_YY form and require xx only + retloc = wxSetlocaleTryUTF8(LC_ALL, langOnly); + } - wxString strFullName; - if ( !wxFindFileInPath(&strFullName, searchPath, strFile) ) { - wxLogWarning(_("catalog file for domain '%s' not found."), szName.c_str()); - return FALSE; - } +#if wxUSE_FONTMAP + // some systems (e.g. FreeBSD and HP-UX) don't have xx_YY aliases but + // require the full xx_YY.encoding form, so try using UTF-8 because this is + // the only thing we can do generically + // + // TODO: add encodings applicable to each language to the lang DB and try + // them all in turn here + if ( !retloc ) + { + const wxChar **names = + wxFontMapperBase::GetAllEncodingNames(wxFONTENCODING_UTF8); + while ( *names ) + { + retloc = wxSetlocale(LC_ALL, locale + wxS('.') + *names++); + if ( retloc ) + break; + } + } +#endif // wxUSE_FONTMAP - // open file - wxLogVerbose(_("using catalog '%s' from '%s'."), - szName.c_str(), strFullName.c_str()); + if ( !retloc ) + { + // Some C libraries (namely glibc) still use old ISO 639, + // so will translate the abbrev for them + wxString localeAlt; + if ( langOnly == wxS("he") ) + localeAlt = wxS("iw") + ExtractNotLang(locale); + else if ( langOnly == wxS("id") ) + localeAlt = wxS("in") + ExtractNotLang(locale); + else if ( langOnly == wxS("yi") ) + localeAlt = wxS("ji") + ExtractNotLang(locale); + else if ( langOnly == wxS("nb") ) + localeAlt = wxS("no_NO"); + else if ( langOnly == wxS("nn") ) + localeAlt = wxS("no_NY"); + + if ( !localeAlt.empty() ) + { + retloc = wxSetlocaleTryUTF8(LC_ALL, localeAlt); + if ( !retloc ) + retloc = wxSetlocaleTryUTF8(LC_ALL, ExtractLang(localeAlt)); + } + } - wxFile fileMsg(strFullName); - if ( !fileMsg.IsOpened() ) - return FALSE; - - // get the file size - off_t nSize = fileMsg.Length(); - if ( nSize == wxInvalidOffset ) - return FALSE; - - // read the whole file in memory - m_pData = new size_t8[nSize]; - if ( fileMsg.Read(m_pData, nSize) != nSize ) { - wxDELETEA(m_pData); - return FALSE; - } + if ( !retloc ) + ret = false; + +#ifdef __AIX__ + // at least in AIX 5.2 libc is buggy and the string returned from + // setlocale(LC_ALL) can't be passed back to it because it returns 6 + // strings (one for each locale category), i.e. for C locale we get back + // "C C C C C C" + // + // this contradicts IBM own docs but this is not of much help, so just work + // around it in the crudest possible manner + char* p = const_cast(wxStrchr(retloc, ' ')); + if ( p ) + *p = '\0'; +#endif // __AIX__ + +#elif defined(__WIN32__) + const char *retloc = "C"; + if ( language != wxLANGUAGE_DEFAULT ) + { + if ( info->WinLang == 0 ) + { + wxLogWarning(wxS("Locale '%s' not supported by OS."), name.c_str()); + // retloc already set to "C" + } + else // language supported by Windows + { + // Windows CE doesn't have SetThreadLocale() and there doesn't seem + // to be any equivalent +#ifndef __WXWINCE__ + const wxUint32 lcid = info->GetLCID(); - // examine header - bool bValid = (size_t)nSize > sizeof(wxMsgCatalogHeader); - - wxMsgCatalogHeader *pHeader = (wxMsgCatalogHeader *)m_pData; - if ( bValid ) { - // we'll have to swap all the integers if it's true - m_bSwapped = pHeader->magic == MSGCATALOG_MAGIC_SW; - - // check the magic number - bValid = m_bSwapped || pHeader->magic == MSGCATALOG_MAGIC; - } - - if ( !bValid ) { - // it's either too short or has incorrect magic number - wxLogWarning(_("'%s' is not a valid message catalog."), strFullName.c_str()); - - wxDELETEA(m_pData); - return FALSE; - } - - // initialize - m_numStrings = Swap(pHeader->numStrings); - m_pOrigTable = (wxMsgTableEntry *)(m_pData + - Swap(pHeader->ofsOrigTable)); - m_pTransTable = (wxMsgTableEntry *)(m_pData + - Swap(pHeader->ofsTransTable)); - - m_nHashSize = Swap(pHeader->nHashSize); - m_pHashTable = (size_t32 *)(m_pData + Swap(pHeader->ofsHashTable)); - - m_pszName = new wxChar[wxStrlen(szName) + 1]; - wxStrcpy(m_pszName, szName); - - // everything is fine - return TRUE; -} - -// search for a string -const char *wxMsgCatalog::GetString(const char *szOrig) const -{ - if ( szOrig == NULL ) - return NULL; + // change locale used by Windows functions + ::SetThreadLocale(lcid); +#endif + + // and also call setlocale() to change locale used by the CRT + locale = info->GetLocaleName(); + if ( locale.empty() ) + { + ret = false; + } + else // have a valid locale + { + retloc = wxSetlocale(LC_ALL, locale); + } + } + } + else // language == wxLANGUAGE_DEFAULT + { + retloc = wxSetlocale(LC_ALL, wxEmptyString); + } + +#if wxUSE_UNICODE && (defined(__VISUALC__) || defined(__MINGW32__)) + // VC++ setlocale() (also used by Mingw) can't set locale to languages that + // can only be written using Unicode, therefore wxSetlocale() call fails + // for such languages but we don't want to report it as an error -- so that + // at least message catalogs can be used. + if ( !retloc ) + { + if ( wxGetANSICodePageForLocale(LOCALE_USER_DEFAULT).empty() ) + { + // we set the locale to a Unicode-only language, don't treat the + // inability of CRT to use it as an error + retloc = "C"; + } + } +#endif // CRT not handling Unicode-only languages - if ( HasHashTable() ) { // use hash table for lookup if possible - size_t32 nHashVal = GetHash(szOrig); - size_t32 nIndex = nHashVal % m_nHashSize; + if ( !retloc ) + ret = false; +#elif defined(__WXMAC__) + if (lang == wxLANGUAGE_DEFAULT) + locale = wxEmptyString; + else + locale = info->CanonicalName; - size_t32 nIncr = 1 + (nHashVal % (m_nHashSize - 2)); + const char *retloc = wxSetlocale(LC_ALL, locale); - while ( TRUE ) { - size_t32 nStr = Swap(m_pHashTable[nIndex]); - if ( nStr == 0 ) - return NULL; + if ( !retloc ) + { + // Some C libraries don't like xx_YY form and require xx only + retloc = wxSetlocale(LC_ALL, ExtractLang(locale)); + } +#else + wxUnusedVar(flags); + return false; + #define WX_NO_LOCALE_SUPPORT +#endif - if ( strcmp(szOrig, StringAtOfs(m_pOrigTable, nStr - 1)) == 0 ) - return StringAtOfs(m_pTransTable, nStr - 1); +#ifndef WX_NO_LOCALE_SUPPORT + if ( !ret ) + { + wxLogWarning(_("Cannot set locale to language \"%s\"."), name.c_str()); - if ( nIndex >= m_nHashSize - nIncr) - nIndex -= m_nHashSize - nIncr; - else - nIndex += nIncr; + // continue nevertheless and try to load at least the translations for + // this language } - } - else { // no hash table: use default binary search - size_t32 bottom = 0, - top = m_numStrings, - current; - while ( bottom < top ) { - current = (bottom + top) / 2; - int res = strcmp(szOrig, StringAtOfs(m_pOrigTable, current)); - if ( res < 0 ) - top = current; - else if ( res > 0 ) - bottom = current + 1; - else // found! - return StringAtOfs(m_pTransTable, current); + + if ( !DoInit(name, canonical, retloc) ) + { + ret = false; } - } - // not found - return NULL; + if (IsOk()) // setlocale() succeeded + m_language = lang; + + // NB: don't use 'lang' here, 'language' + wxTranslations *t = wxTranslations::Get(); + if ( t ) + { + t->SetLanguage(static_cast(language)); + + if ( flags & wxLOCALE_LOAD_DEFAULT ) + t->AddStdCatalog(); + } + + return ret; +#endif // !WX_NO_LOCALE_SUPPORT +} + +/*static*/ int wxLocale::GetSystemLanguage() +{ + CreateLanguagesDB(); + + // init i to avoid compiler warning + size_t i = 0, + count = ms_languagesDB->GetCount(); + +#if defined(__UNIX__) + // first get the string identifying the language from the environment + wxString langFull; +#ifdef __WXMAC__ + wxCFRef userLocaleRef(CFLocaleCopyCurrent()); + + // because the locale identifier (kCFLocaleIdentifier) is formatted a little bit differently, eg + // az_Cyrl_AZ@calendar=buddhist;currency=JPY we just recreate the base info as expected by wx here + + wxCFStringRef str(wxCFRetain((CFStringRef)CFLocaleGetValue(userLocaleRef, kCFLocaleLanguageCode))); + langFull = str.AsString()+"_"; + str.reset(wxCFRetain((CFStringRef)CFLocaleGetValue(userLocaleRef, kCFLocaleCountryCode))); + langFull += str.AsString(); +#else + if (!wxGetEnv(wxS("LC_ALL"), &langFull) && + !wxGetEnv(wxS("LC_MESSAGES"), &langFull) && + !wxGetEnv(wxS("LANG"), &langFull)) + { + // no language specified, treat it as English + return wxLANGUAGE_ENGLISH_US; + } + + if ( langFull == wxS("C") || langFull == wxS("POSIX") ) + { + // default C locale is English too + return wxLANGUAGE_ENGLISH_US; + } +#endif + + // the language string has the following form + // + // lang[_LANG][.encoding][@modifier] + // + // (see environ(5) in the Open Unix specification) + // + // where lang is the primary language, LANG is a sublang/territory, + // encoding is the charset to use and modifier "allows the user to select + // a specific instance of localization data within a single category" + // + // for example, the following strings are valid: + // fr + // fr_FR + // de_DE.iso88591 + // de_DE@euro + // de_DE.iso88591@euro + + // for now we don't use the encoding, although we probably should (doing + // translations of the msg catalogs on the fly as required) (TODO) + // + // we need the modified for languages like Valencian: ca_ES@valencia + // though, remember it + wxString modifier; + size_t posModifier = langFull.find_first_of(wxS("@")); + if ( posModifier != wxString::npos ) + modifier = langFull.Mid(posModifier); + + size_t posEndLang = langFull.find_first_of(wxS("@.")); + if ( posEndLang != wxString::npos ) + { + langFull.Truncate(posEndLang); + } + + // do we have just the language (or sublang too)? + const bool justLang = langFull.find('_') == wxString::npos; + + // 0. Make sure the lang is according to latest ISO 639 + // (this is necessary because glibc uses iw and in instead + // of he and id respectively). + + // the language itself (second part is the dialect/sublang) + wxString langOrig = ExtractLang(langFull); + + wxString lang; + if ( langOrig == wxS("iw")) + lang = wxS("he"); + else if (langOrig == wxS("in")) + lang = wxS("id"); + else if (langOrig == wxS("ji")) + lang = wxS("yi"); + else if (langOrig == wxS("no_NO")) + lang = wxS("nb_NO"); + else if (langOrig == wxS("no_NY")) + lang = wxS("nn_NO"); + else if (langOrig == wxS("no")) + lang = wxS("nb_NO"); + else + lang = langOrig; + + // did we change it? + if ( lang != langOrig ) + { + langFull = lang + ExtractNotLang(langFull); + } + + // 1. Try to find the language either as is: + // a) With modifier if set + if ( !modifier.empty() ) + { + wxString langFullWithModifier = langFull + modifier; + for ( i = 0; i < count; i++ ) + { + if ( ms_languagesDB->Item(i).CanonicalName == langFullWithModifier ) + break; + } + } + + // b) Without modifier + if ( modifier.empty() || i == count ) + { + for ( i = 0; i < count; i++ ) + { + if ( ms_languagesDB->Item(i).CanonicalName == langFull ) + break; + } + } + + // 2. If langFull is of the form xx_YY, try to find xx: + if ( i == count && !justLang ) + { + for ( i = 0; i < count; i++ ) + { + if ( ms_languagesDB->Item(i).CanonicalName == lang ) + { + break; + } + } + } + + // 3. If langFull is of the form xx, try to find any xx_YY record: + if ( i == count && justLang ) + { + for ( i = 0; i < count; i++ ) + { + if ( ExtractLang(ms_languagesDB->Item(i).CanonicalName) + == langFull ) + { + break; + } + } + } + + + if ( i == count ) + { + // In addition to the format above, we also can have full language + // names in LANG env var - for example, SuSE is known to use + // LANG="german" - so check for use of non-standard format and try to + // find the name in verbose description. + for ( i = 0; i < count; i++ ) + { + if (ms_languagesDB->Item(i).Description.CmpNoCase(langFull) == 0) + { + break; + } + } + } +#elif defined(__WIN32__) + LCID lcid = GetUserDefaultLCID(); + if ( lcid != 0 ) + { + wxUint32 lang = PRIMARYLANGID(LANGIDFROMLCID(lcid)); + wxUint32 sublang = SUBLANGID(LANGIDFROMLCID(lcid)); + + for ( i = 0; i < count; i++ ) + { + if (ms_languagesDB->Item(i).WinLang == lang && + ms_languagesDB->Item(i).WinSublang == sublang) + { + break; + } + } + } + //else: leave wxlang == wxLANGUAGE_UNKNOWN +#endif // Unix/Win32 + + if ( i < count ) + { + // we did find a matching entry, use it + return ms_languagesDB->Item(i).Language; + } + + // no info about this language in the database + return wxLANGUAGE_UNKNOWN; } // ---------------------------------------------------------------------------- -// wxLocale +// encoding stuff // ---------------------------------------------------------------------------- -wxLocale::wxLocale() +// this is a bit strange as under Windows we get the encoding name using its +// numeric value and under Unix we do it the other way round, but this just +// reflects the way different systems provide the encoding info + +/* static */ +wxString wxLocale::GetSystemEncodingName() { - m_pszOldLocale = NULL; - m_pMsgCat = NULL; + wxString encname; + +#if defined(__WIN32__) && !defined(__WXMICROWIN__) + // FIXME: what is the error return value for GetACP()? + UINT codepage = ::GetACP(); + encname.Printf(wxS("windows-%u"), codepage); +#elif defined(__WXMAC__) + // default is just empty string, this resolves to the default system + // encoding later +#elif defined(__UNIX_LIKE__) + +#if defined(HAVE_LANGINFO_H) && defined(CODESET) + // GNU libc provides current character set this way (this conforms + // to Unix98) + char *oldLocale = strdup(setlocale(LC_CTYPE, NULL)); + setlocale(LC_CTYPE, ""); + const char *alang = nl_langinfo(CODESET); + setlocale(LC_CTYPE, oldLocale); + free(oldLocale); + + if ( alang ) + { + encname = wxString::FromAscii( alang ); + } + else // nl_langinfo() failed +#endif // HAVE_LANGINFO_H + { + // if we can't get at the character set directly, try to see if it's in + // the environment variables (in most cases this won't work, but I was + // out of ideas) + char *lang = getenv( "LC_ALL"); + char *dot = lang ? strchr(lang, '.') : NULL; + if (!dot) + { + lang = getenv( "LC_CTYPE" ); + if ( lang ) + dot = strchr(lang, '.' ); + } + if (!dot) + { + lang = getenv( "LANG"); + if ( lang ) + dot = strchr(lang, '.'); + } + + if ( dot ) + { + encname = wxString::FromAscii( dot+1 ); + } + } +#endif // Win32/Unix + + return encname; } -// NB: this function has (desired) side effect of changing current locale -bool wxLocale::Init(const wxChar *szName, - const wxChar *szShort, - const wxChar *szLocale, - bool bLoadDefault) +/* static */ +wxFontEncoding wxLocale::GetSystemEncoding() { - m_strLocale = szName; - m_strShort = szShort; +#if defined(__WIN32__) && !defined(__WXMICROWIN__) + UINT codepage = ::GetACP(); + + // wxWidgets only knows about CP1250-1257, 874, 932, 936, 949, 950 + if ( codepage >= 1250 && codepage <= 1257 ) + { + return (wxFontEncoding)(wxFONTENCODING_CP1250 + codepage - 1250); + } - // change current locale (default: same as long name) - if ( szLocale == NULL ) - szLocale = szName; - m_pszOldLocale = wxSetlocale(LC_ALL, szLocale); - if ( m_pszOldLocale == NULL ) - wxLogError(_("locale '%s' can not be set."), szLocale); + if ( codepage == 874 ) + { + return wxFONTENCODING_CP874; + } - // the short name will be used to look for catalog files as well, - // so we need something here - if ( m_strShort.IsEmpty() ) { - // FIXME I don't know how these 2 letter abbreviations are formed, - // this wild guess is surely wrong - m_strShort = tolower(szLocale[0]) + tolower(szLocale[1]); - } + if ( codepage == 932 ) + { + return wxFONTENCODING_CP932; + } + + if ( codepage == 936 ) + { + return wxFONTENCODING_CP936; + } + + if ( codepage == 949 ) + { + return wxFONTENCODING_CP949; + } + + if ( codepage == 950 ) + { + return wxFONTENCODING_CP950; + } +#elif defined(__WXMAC__) + CFStringEncoding encoding = 0 ; + encoding = CFStringGetSystemEncoding() ; + return wxMacGetFontEncFromSystemEnc( encoding ) ; +#elif defined(__UNIX_LIKE__) && wxUSE_FONTMAP + const wxString encname = GetSystemEncodingName(); + if ( !encname.empty() ) + { + wxFontEncoding enc = wxFontMapperBase::GetEncodingFromName(encname); + + // on some modern Linux systems (RedHat 8) the default system locale + // is UTF8 -- but it isn't supported by wxGTK1 in ANSI build at all so + // don't even try to use it in this case +#if !wxUSE_UNICODE && \ + ((defined(__WXGTK__) && !defined(__WXGTK20__)) || defined(__WXMOTIF__)) + if ( enc == wxFONTENCODING_UTF8 ) + { + // the most similar supported encoding... + enc = wxFONTENCODING_ISO8859_1; + } +#endif // !wxUSE_UNICODE + + // GetEncodingFromName() returns wxFONTENCODING_DEFAULT for C locale + // (a.k.a. US-ASCII) which is arguably a bug but keep it like this for + // backwards compatibility and just take care to not return + // wxFONTENCODING_DEFAULT from here as this surely doesn't make sense + if ( enc == wxFONTENCODING_DEFAULT ) + { + // we don't have wxFONTENCODING_ASCII, so use the closest one + return wxFONTENCODING_ISO8859_1; + } - // save the old locale to be able to restore it later - m_pOldLocale = wxSetLocale(this); + if ( enc != wxFONTENCODING_MAX ) + { + return enc; + } + //else: return wxFONTENCODING_SYSTEM below + } +#endif // Win32/Unix - // load the default catalog with wxWindows standard messages - m_pMsgCat = NULL; - bool bOk = TRUE; - if ( bLoadDefault ) - bOk = AddCatalog(_T("wxstd")); + return wxFONTENCODING_SYSTEM; +} - return bOk; +/* static */ +void wxLocale::AddLanguage(const wxLanguageInfo& info) +{ + CreateLanguagesDB(); + ms_languagesDB->Add(info); } -void wxLocale::AddCatalogLookupPathPrefix(const wxString& prefix) +/* static */ +const wxLanguageInfo *wxLocale::GetLanguageInfo(int lang) { - if ( s_searchPrefixes.Index(prefix) == wxNOT_FOUND ) + CreateLanguagesDB(); + + // calling GetLanguageInfo(wxLANGUAGE_DEFAULT) is a natural thing to do, so + // make it work + if ( lang == wxLANGUAGE_DEFAULT ) + lang = GetSystemLanguage(); + + const size_t count = ms_languagesDB->GetCount(); + for ( size_t i = 0; i < count; i++ ) { - s_searchPrefixes.Add(prefix); + if ( ms_languagesDB->Item(i).Language == lang ) + { + // We need to create a temporary here in order to make this work with BCC in final build mode + wxLanguageInfo *ptr = &ms_languagesDB->Item(i); + return ptr; + } } - //else: already have it + + return NULL; +} + +/* static */ +wxString wxLocale::GetLanguageName(int lang) +{ + if ( lang == wxLANGUAGE_DEFAULT || lang == wxLANGUAGE_UNKNOWN ) + return wxEmptyString; + + const wxLanguageInfo *info = GetLanguageInfo(lang); + if ( !info ) + return wxEmptyString; + else + return info->Description; +} + +/* static */ +wxString wxLocale::GetLanguageCanonicalName(int lang) +{ + if ( lang == wxLANGUAGE_DEFAULT || lang == wxLANGUAGE_UNKNOWN ) + return wxEmptyString; + + const wxLanguageInfo *info = GetLanguageInfo(lang); + if ( !info ) + return wxEmptyString; + else + return info->CanonicalName; +} + +/* static */ +const wxLanguageInfo *wxLocale::FindLanguageInfo(const wxString& locale) +{ + CreateLanguagesDB(); + + const wxLanguageInfo *infoRet = NULL; + + const size_t count = ms_languagesDB->GetCount(); + for ( size_t i = 0; i < count; i++ ) + { + const wxLanguageInfo *info = &ms_languagesDB->Item(i); + + if ( wxStricmp(locale, info->CanonicalName) == 0 || + wxStricmp(locale, info->Description) == 0 ) + { + // exact match, stop searching + infoRet = info; + break; + } + + if ( wxStricmp(locale, info->CanonicalName.BeforeFirst(wxS('_'))) == 0 ) + { + // a match -- but maybe we'll find an exact one later, so continue + // looking + // + // OTOH, maybe we had already found a language match and in this + // case don't overwrite it because the entry for the default + // country always appears first in ms_languagesDB + if ( !infoRet ) + infoRet = info; + } + } + + return infoRet; +} + +wxString wxLocale::GetSysName() const +{ + return wxSetlocale(LC_ALL, NULL); } // clean up wxLocale::~wxLocale() { - // free memory - wxMsgCatalog *pTmpCat; - while ( m_pMsgCat != NULL ) { - pTmpCat = m_pMsgCat; - m_pMsgCat = m_pMsgCat->m_pNext; - delete pTmpCat; + // Restore old translations object. + // See DoCommonInit() for explanation of why this is needed for backward + // compatibility. + if ( wxTranslations::Get() == &m_translations ) + { + if ( m_pOldLocale ) + wxTranslations::SetNonOwned(&m_pOldLocale->m_translations); + else + wxTranslations::Set(NULL); } - // restore old locale + // restore old locale pointer wxSetLocale(m_pOldLocale); + wxSetlocale(LC_ALL, m_pszOldLocale); + free(const_cast(m_pszOldLocale)); } -// get the translation of given string in current locale -const wxMB2WXbuf wxLocale::GetString(const wxChar *szOrigString, - const wxChar *szDomain) const + +// check if the given locale is provided by OS and C run time +/* static */ +bool wxLocale::IsAvailable(int lang) { - if ( wxIsEmpty(szOrigString) ) - return szDomain; + const wxLanguageInfo *info = wxLocale::GetLanguageInfo(lang); + wxCHECK_MSG( info, false, wxS("invalid language") ); - const char *pszTrans = NULL; - const wxWX2MBbuf szOrgString = wxConvCurrent->cWX2MB(szOrigString); +#if defined(__WIN32__) + if ( !info->WinLang ) + return false; - wxMsgCatalog *pMsgCat; - if ( szDomain != NULL ) { - pMsgCat = FindCatalog(szDomain); + if ( !::IsValidLocale(info->GetLCID(), LCID_INSTALLED) ) + return false; - // does the catalog exist? - if ( pMsgCat != NULL ) - pszTrans = pMsgCat->GetString(szOrgString); - } - else { - // search in all domains - for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext ) { - pszTrans = pMsgCat->GetString(szOrgString); - if ( pszTrans != NULL ) // take the first found - break; - } - } +#elif defined(__UNIX__) - if ( pszTrans == NULL ) { - if ( wxIsLoggingTransErrors() ) { - // suppress further error messages if we're not debugging: this avoids - // flooding the user with messages about each and every missing string if, - // for example, a whole catalog file is missing. + // Test if setting the locale works, then set it back. + char * const oldLocale = wxStrdupA(setlocale(LC_ALL, NULL)); - // do it before calling LogWarning to prevent infinite recursion! -#ifdef __WXDEBUG__ - NoTransErr noTransErr; -#else // !debug - wxSuppressTransErrors(); -#endif // debug/!debug + // Some platforms don't like xx_YY form and require xx only so test for + // it too. + const bool + available = wxSetlocaleTryUTF8(LC_ALL, info->CanonicalName) || + wxSetlocaleTryUTF8(LC_ALL, ExtractLang(info->CanonicalName)); - if ( szDomain != NULL ) - { - wxLogWarning(_("string '%s' not found in domain '%s' for locale '%s'."), - szOrigString, szDomain, m_strLocale.c_str()); - } - else - { - wxLogWarning(_("string '%s' not found in locale '%s'."), - szOrigString, m_strLocale.c_str()); - } - } + // restore the original locale + wxSetlocale(LC_ALL, oldLocale); - return (wxMB2WXbuf)(szOrigString); - } - else - return (wxMB2WXbuf)(wxConvCurrent->cMB2WX(pszTrans)); + free(oldLocale); + + if ( !available ) + return false; +#endif + + return true; } -// find catalog by name in a linked list, return NULL if !found -wxMsgCatalog *wxLocale::FindCatalog(const wxChar *szDomain) const -{ -// linear search in the linked list - wxMsgCatalog *pMsgCat; - for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext ) { - if ( wxStricmp(pMsgCat->GetName(), szDomain) == 0 ) - return pMsgCat; - } - return NULL; +bool wxLocale::AddCatalog(const wxString& domain) +{ + wxTranslations *t = wxTranslations::Get(); + if ( !t ) + return false; + return t->AddCatalog(domain); } -// check if the given catalog is loaded -bool wxLocale::IsLoaded(const wxChar *szDomain) const +bool wxLocale::AddCatalog(const wxString& domain, wxLanguage msgIdLanguage) { - return FindCatalog(szDomain) != NULL; + wxTranslations *t = wxTranslations::Get(); + if ( !t ) + return false; + return t->AddCatalog(domain, msgIdLanguage); } // add a catalog to our linked list -bool wxLocale::AddCatalog(const wxChar *szDomain) +bool wxLocale::AddCatalog(const wxString& szDomain, + wxLanguage msgIdLanguage, + const wxString& msgIdCharset) { - wxMsgCatalog *pMsgCat = new wxMsgCatalog; - - if ( pMsgCat->Load(m_strShort, szDomain) ) { - // add it to the head of the list so that in GetString it will - // be searched before the catalogs added earlier - pMsgCat->m_pNext = m_pMsgCat; - m_pMsgCat = pMsgCat; + wxTranslations *t = wxTranslations::Get(); + if ( !t ) + return false; +#if wxUSE_UNICODE + wxUnusedVar(msgIdCharset); + return t->AddCatalog(szDomain, msgIdLanguage); +#else + return t->AddCatalog(szDomain, msgIdLanguage, msgIdCharset); +#endif +} - return TRUE; - } - else { - // don't add it because it couldn't be loaded anyway - delete pMsgCat; +bool wxLocale::IsLoaded(const wxString& domain) const +{ + wxTranslations *t = wxTranslations::Get(); + if ( !t ) + return false; + return t->IsLoaded(domain); +} - return FALSE; - } +wxString wxLocale::GetHeaderValue(const wxString& header, + const wxString& domain) const +{ + wxTranslations *t = wxTranslations::Get(); + if ( !t ) + return wxEmptyString; + return t->GetHeaderValue(header, domain); } // ---------------------------------------------------------------------------- -// global functions and variables +// accessors for locale-dependent data // ---------------------------------------------------------------------------- -// translation errors logging -// -------------------------- +#if defined(__WXMSW__) || defined(__WXOSX__) + +namespace +{ + +// This function translates from Unicode date formats described at +// +// http://unicode.org/reports/tr35/tr35-6.html#Date_Format_Patterns +// +// to strftime()-like syntax. This translation is not lossless but we try to do +// our best. + +static wxString TranslateFromUnicodeFormat(const wxString& fmt) +{ + wxString fmtWX; + fmtWX.reserve(fmt.length()); + + char chLast = '\0'; + size_t lastCount = 0; + + const char* formatchars = + "dghHmMsSy" +#ifdef __WXMSW__ + "t" +#else + "EawD" +#endif + ; + for ( wxString::const_iterator p = fmt.begin(); /* end handled inside */; ++p ) + { + if ( p != fmt.end() ) + { + if ( *p == chLast ) + { + lastCount++; + continue; + } + + const wxUniChar ch = (*p).GetValue(); + if ( ch.IsAscii() && strchr(formatchars, ch) ) + { + // these characters come in groups, start counting them + chLast = ch; + lastCount = 1; + continue; + } + } + + // interpret any special characters we collected so far + if ( lastCount ) + { + switch ( chLast ) + { + case 'd': + switch ( lastCount ) + { + case 1: // d + case 2: // dd + // these two are the same as we don't distinguish + // between 1 and 2 digits for days + fmtWX += "%d"; + break; +#ifdef __WXMSW__ + case 3: // ddd + fmtWX += "%a"; + break; + + case 4: // dddd + fmtWX += "%A"; + break; +#endif + default: + wxFAIL_MSG( "too many 'd's" ); + } + break; +#ifndef __WXMSW__ + case 'D': + switch ( lastCount ) + { + case 1: // D + case 2: // DD + case 3: // DDD + fmtWX += "%j"; + break; + + default: + wxFAIL_MSG( "wrong number of 'D's" ); + } + break; + case 'w': + switch ( lastCount ) + { + case 1: // w + case 2: // ww + fmtWX += "%W"; + break; + + default: + wxFAIL_MSG( "wrong number of 'w's" ); + } + break; + case 'E': + switch ( lastCount ) + { + case 1: // E + case 2: // EE + case 3: // EEE + fmtWX += "%a"; + break; + case 4: // EEEE + fmtWX += "%A"; + break; + case 5: // EEEEE + fmtWX += "%a"; + break; + + default: + wxFAIL_MSG( "wrong number of 'E's" ); + } + break; +#endif + case 'M': + switch ( lastCount ) + { + case 1: // M + case 2: // MM + // as for 'd' and 'dd' above + fmtWX += "%m"; + break; + + case 3: + fmtWX += "%b"; + break; + + case 4: + fmtWX += "%B"; + break; + + default: + wxFAIL_MSG( "too many 'M's" ); + } + break; + + case 'y': + switch ( lastCount ) + { + case 1: // y + case 2: // yy + fmtWX += "%y"; + break; + + case 4: // yyyy + fmtWX += "%Y"; + break; + + default: + wxFAIL_MSG( "wrong number of 'y's" ); + } + break; + + case 'H': + switch ( lastCount ) + { + case 1: // H + case 2: // HH + fmtWX += "%H"; + break; + + default: + wxFAIL_MSG( "wrong number of 'H's" ); + } + break; + + case 'h': + switch ( lastCount ) + { + case 1: // h + case 2: // hh + fmtWX += "%I"; + break; + + default: + wxFAIL_MSG( "wrong number of 'h's" ); + } + break; + + case 'm': + switch ( lastCount ) + { + case 1: // m + case 2: // mm + fmtWX += "%M"; + break; + + default: + wxFAIL_MSG( "wrong number of 'm's" ); + } + break; + + case 's': + switch ( lastCount ) + { + case 1: // s + case 2: // ss + fmtWX += "%S"; + break; + + default: + wxFAIL_MSG( "wrong number of 's's" ); + } + break; + + case 'g': + // strftime() doesn't have era string, + // ignore this format + wxASSERT_MSG( lastCount <= 2, "too many 'g's" ); + + break; +#ifndef __WXMSW__ + case 'a': + fmtWX += "%p"; + break; +#endif +#ifdef __WXMSW__ + case 't': + switch ( lastCount ) + { + case 1: // t + case 2: // tt + fmtWX += "%p"; + break; + + default: + wxFAIL_MSG( "too many 't's" ); + } + break; +#endif + default: + wxFAIL_MSG( "unreachable" ); + } + + chLast = '\0'; + lastCount = 0; + } + + if ( p == fmt.end() ) + break; + + // not a special character so must be just a separator, treat as is + if ( *p == wxT('%') ) + { + // this one needs to be escaped + fmtWX += wxT('%'); + } + + fmtWX += *p; + } + + return fmtWX; +} + +} // anonymous namespace + +#endif // __WXMSW__ || __WXOSX__ -static bool gs_bGiveTransErrors = TRUE; +#if defined(__WXMSW__) -void wxSuppressTransErrors() +namespace { - gs_bGiveTransErrors = FALSE; + +LCTYPE GetLCTYPEFormatFromLocalInfo(wxLocaleInfo index) +{ + switch ( index ) + { + case wxLOCALE_SHORT_DATE_FMT: + return LOCALE_SSHORTDATE; + + case wxLOCALE_LONG_DATE_FMT: + return LOCALE_SLONGDATE; + + case wxLOCALE_TIME_FMT: + return LOCALE_STIMEFORMAT; + + default: + wxFAIL_MSG( "no matching LCTYPE" ); + } + + return 0; } -void wxRestoreTransErrors() +} // anonymous namespace + +/* static */ +wxString wxLocale::GetInfo(wxLocaleInfo index, wxLocaleCategory cat) { - gs_bGiveTransErrors = TRUE; + wxUint32 lcid = LOCALE_USER_DEFAULT; + if ( wxGetLocale() ) + { + const wxLanguageInfo * const + info = GetLanguageInfo(wxGetLocale()->GetLanguage()); + if ( info ) + lcid = info->GetLCID(); + } + + wxString str; + + wxChar buf[256]; + buf[0] = wxT('\0'); + + switch ( index ) + { + case wxLOCALE_THOUSANDS_SEP: + if ( ::GetLocaleInfo(lcid, LOCALE_STHOUSAND, buf, WXSIZEOF(buf)) ) + str = buf; + break; + + case wxLOCALE_DECIMAL_POINT: + if ( ::GetLocaleInfo(lcid, + cat == wxLOCALE_CAT_MONEY + ? LOCALE_SMONDECIMALSEP + : LOCALE_SDECIMAL, + buf, + WXSIZEOF(buf)) ) + str = buf; + break; + + case wxLOCALE_SHORT_DATE_FMT: + case wxLOCALE_LONG_DATE_FMT: + case wxLOCALE_TIME_FMT: + if ( ::GetLocaleInfo(lcid, GetLCTYPEFormatFromLocalInfo(index), + buf, WXSIZEOF(buf)) ) + { + return TranslateFromUnicodeFormat(buf); + } + break; + + case wxLOCALE_DATE_TIME_FMT: + // there doesn't seem to be any specific setting for this, so just + // combine date and time ones + // + // we use the short date because this is what "%c" uses by default + // ("%#c" uses long date but we have no way to specify the + // alternate representation here) + { + const wxString datefmt = GetInfo(wxLOCALE_SHORT_DATE_FMT); + if ( datefmt.empty() ) + break; + + const wxString timefmt = GetInfo(wxLOCALE_TIME_FMT); + if ( timefmt.empty() ) + break; + + str << datefmt << ' ' << timefmt; + } + break; + + default: + wxFAIL_MSG( "unknown wxLocaleInfo" ); + } + + return str; } -bool wxIsLoggingTransErrors() +#elif defined(__WXOSX__) + +/* static */ +wxString wxLocale::GetInfo(wxLocaleInfo index, wxLocaleCategory WXUNUSED(cat)) { - return gs_bGiveTransErrors; + CFLocaleRef userLocaleRefRaw; + if ( wxGetLocale() ) + { + userLocaleRefRaw = CFLocaleCreate + ( + kCFAllocatorDefault, + wxCFStringRef(wxGetLocale()->GetCanonicalName()) + ); + } + else // no current locale, use the default one + { + userLocaleRefRaw = CFLocaleCopyCurrent(); + } + + wxCFRef userLocaleRef(userLocaleRefRaw); + + CFStringRef cfstr = 0; + switch ( index ) + { + case wxLOCALE_THOUSANDS_SEP: + cfstr = (CFStringRef) CFLocaleGetValue(userLocaleRef, kCFLocaleGroupingSeparator); + break; + + case wxLOCALE_DECIMAL_POINT: + cfstr = (CFStringRef) CFLocaleGetValue(userLocaleRef, kCFLocaleDecimalSeparator); + break; + + case wxLOCALE_SHORT_DATE_FMT: + case wxLOCALE_LONG_DATE_FMT: + case wxLOCALE_DATE_TIME_FMT: + case wxLOCALE_TIME_FMT: + { + CFDateFormatterStyle dateStyle = kCFDateFormatterNoStyle; + CFDateFormatterStyle timeStyle = kCFDateFormatterNoStyle; + switch (index ) + { + case wxLOCALE_SHORT_DATE_FMT: + dateStyle = kCFDateFormatterShortStyle; + break; + case wxLOCALE_LONG_DATE_FMT: + dateStyle = kCFDateFormatterFullStyle; + break; + case wxLOCALE_DATE_TIME_FMT: + dateStyle = kCFDateFormatterFullStyle; + timeStyle = kCFDateFormatterMediumStyle; + break; + case wxLOCALE_TIME_FMT: + timeStyle = kCFDateFormatterMediumStyle; + break; + default: + wxFAIL_MSG( "unexpected time locale" ); + return wxString(); + } + wxCFRef dateFormatter( CFDateFormatterCreate + (NULL, userLocaleRef, dateStyle, timeStyle)); + wxCFStringRef cfs = wxCFRetain( CFDateFormatterGetFormat(dateFormatter )); + wxString format = TranslateFromUnicodeFormat(cfs.AsString()); + // we always want full years + format.Replace("%y","%Y"); + return format; + } + break; + + default: + wxFAIL_MSG( "Unknown locale info" ); + return wxString(); + } + + wxCFStringRef str(wxCFRetain(cfstr)); + return str.AsString(); } +#else // !__WXMSW__ && !__WXOSX__, assume generic POSIX + +namespace +{ + +wxString GetDateFormatFromLangInfo(wxLocaleInfo index) +{ +#ifdef HAVE_LANGINFO_H + // array containing parameters for nl_langinfo() indexes by offset of index + // from wxLOCALE_SHORT_DATE_FMT + static const nl_item items[] = + { + D_FMT, D_T_FMT, D_T_FMT, T_FMT, + }; + + const int nlidx = index - wxLOCALE_SHORT_DATE_FMT; + if ( nlidx < 0 || nlidx >= (int)WXSIZEOF(items) ) + { + wxFAIL_MSG( "logic error in GetInfo() code" ); + return wxString(); + } + + const wxString fmt(nl_langinfo(items[nlidx])); + + // just return the format returned by nl_langinfo() except for long date + // format which we need to recover from date/time format ourselves (but not + // if we failed completely) + if ( fmt.empty() || index != wxLOCALE_LONG_DATE_FMT ) + return fmt; + + // this is not 100% precise but the idea is that a typical date/time format + // under POSIX systems is a combination of a long date format with time one + // so we should be able to get just the long date format by removing all + // time-specific format specifiers + static const char *timeFmtSpecs = "HIklMpPrRsSTXzZ"; + static const char *timeSep = " :./-"; + + wxString fmtDateOnly; + const wxString::const_iterator end = fmt.end(); + wxString::const_iterator lastSep = end; + for ( wxString::const_iterator p = fmt.begin(); p != end; ++p ) + { + if ( strchr(timeSep, *p) ) + { + if ( lastSep == end ) + lastSep = p; + + // skip it for now, we'll discard it if it's followed by a time + // specifier later or add it to fmtDateOnly if it is not + continue; + } + + if ( *p == '%' && + (p + 1 != end) && strchr(timeFmtSpecs, p[1]) ) + { + // time specified found: skip it and any preceding separators + ++p; + lastSep = end; + continue; + } + + if ( lastSep != end ) + { + fmtDateOnly += wxString(lastSep, p); + lastSep = end; + } + + fmtDateOnly += *p; + } + + return fmtDateOnly; +#else // !HAVE_LANGINFO_H + wxUnusedVar(index); + + // no fallback, let the application deal with unavailability of + // nl_langinfo() itself as there is no good way for us to do it (well, we + // could try to reverse engineer the format from strftime() output but this + // looks like too much trouble considering the relatively small number of + // systems without nl_langinfo() still in use) + return wxString(); +#endif // HAVE_LANGINFO_H/!HAVE_LANGINFO_H +} + +} // anonymous namespace + +/* static */ +wxString wxLocale::GetInfo(wxLocaleInfo index, wxLocaleCategory cat) +{ + lconv * const lc = localeconv(); + if ( !lc ) + return wxString(); + + switch ( index ) + { + case wxLOCALE_THOUSANDS_SEP: + if ( cat == wxLOCALE_CAT_NUMBER ) + return lc->thousands_sep; + else if ( cat == wxLOCALE_CAT_MONEY ) + return lc->mon_thousands_sep; + + wxFAIL_MSG( "invalid wxLocaleCategory" ); + break; + + + case wxLOCALE_DECIMAL_POINT: + if ( cat == wxLOCALE_CAT_NUMBER ) + return lc->decimal_point; + else if ( cat == wxLOCALE_CAT_MONEY ) + return lc->mon_decimal_point; + + wxFAIL_MSG( "invalid wxLocaleCategory" ); + break; + + case wxLOCALE_SHORT_DATE_FMT: + case wxLOCALE_LONG_DATE_FMT: + case wxLOCALE_DATE_TIME_FMT: + case wxLOCALE_TIME_FMT: + if ( cat != wxLOCALE_CAT_DATE && cat != wxLOCALE_CAT_DEFAULT ) + { + wxFAIL_MSG( "invalid wxLocaleCategory" ); + break; + } + + return GetDateFormatFromLangInfo(index); + + + default: + wxFAIL_MSG( "unknown wxLocaleInfo value" ); + } + + return wxString(); +} + +#endif // platform + +// ---------------------------------------------------------------------------- +// global functions and variables +// ---------------------------------------------------------------------------- + // retrieve/change current locale // ------------------------------ @@ -655,15 +1706,39 @@ static wxLocale *g_pLocale = NULL; wxLocale *wxGetLocale() { - return g_pLocale; + return g_pLocale; } wxLocale *wxSetLocale(wxLocale *pLocale) { - wxLocale *pOld = g_pLocale; - g_pLocale = pLocale; - return pOld; + wxLocale *pOld = g_pLocale; + g_pLocale = pLocale; + return pOld; } -#endif // wxUSE_INTL + +// ---------------------------------------------------------------------------- +// wxLocale module (for lazy destruction of languagesDB) +// ---------------------------------------------------------------------------- + +class wxLocaleModule: public wxModule +{ + DECLARE_DYNAMIC_CLASS(wxLocaleModule) + public: + wxLocaleModule() {} + + bool OnInit() + { + return true; + } + + void OnExit() + { + wxLocale::DestroyLanguagesDB(); + } +}; + +IMPLEMENT_DYNAMIC_CLASS(wxLocaleModule, wxModule) + +#endif // wxUSE_INTL