+ wxPluralFormsNode* p = pmExpression();
+ if (p == NULL)
+ return NULL;
+ wxPluralFormsNodePtr n(p);
+ if (token().type() == wxPluralFormsToken::T_REMINDER)
+ {
+ wxPluralFormsNodePtr qn(new wxPluralFormsNode(token()));
+ if (!nextToken())
+ {
+ return NULL;
+ }
+ p = pmExpression();
+ if (p == NULL)
+ {
+ return NULL;
+ }
+ qn->setNode(1, p);
+ qn->setNode(0, n.release());
+ return qn.release();
+ }
+ return n.release();
+}
+
+wxPluralFormsNode* wxPluralFormsParser::pmExpression()
+{
+ wxPluralFormsNodePtr n;
+ if (token().type() == wxPluralFormsToken::T_N
+ || token().type() == wxPluralFormsToken::T_NUMBER)
+ {
+ n.reset(new wxPluralFormsNode(token()));
+ if (!nextToken())
+ {
+ return NULL;
+ }
+ }
+ else if (token().type() == wxPluralFormsToken::T_LEFT_BRACKET) {
+ if (!nextToken())
+ {
+ return NULL;
+ }
+ wxPluralFormsNode* p = expression();
+ if (p == NULL)
+ {
+ return NULL;
+ }
+ n.reset(p);
+ if (token().type() != wxPluralFormsToken::T_RIGHT_BRACKET)
+ {
+ return NULL;
+ }
+ if (!nextToken())
+ {
+ return NULL;
+ }
+ }
+ else
+ {
+ return NULL;
+ }
+ return n.release();
+}
+
+wxPluralFormsCalculator* wxPluralFormsCalculator::make(const char* s)
+{
+ wxPluralFormsCalculatorPtr calculator(new wxPluralFormsCalculator);
+ if (s != NULL)
+ {
+ wxPluralFormsScanner scanner(s);
+ wxPluralFormsParser p(scanner);
+ if (!p.parse(*calculator))
+ {
+ return NULL;
+ }
+ }
+ return calculator.release();
+}
+
+
+
+
+// ----------------------------------------------------------------------------
+// wxMsgCatalogFile corresponds to one disk-file message catalog.
+//
+// This is a "low-level" class and is used only by wxMsgCatalog
+// NOTE: for the documentation of the binary catalog (.MO) files refer to
+// the GNU gettext manual:
+// http://www.gnu.org/software/autoconf/manual/gettext/MO-Files.html
+// ----------------------------------------------------------------------------
+
+WX_DECLARE_EXPORTED_STRING_HASH_MAP(wxString, wxMessagesHash);
+
+class wxMsgCatalogFile
+{
+public:
+ // ctor & dtor
+ wxMsgCatalogFile();
+ ~wxMsgCatalogFile();
+
+ // load the catalog from disk (szDirPrefix corresponds to language)
+ bool Load(const wxString& szDirPrefix, const wxString& szName,
+ wxPluralFormsCalculatorPtr& rPluralFormsCalculator);
+
+ // fills the hash with string-translation pairs
+ bool FillHash(wxMessagesHash& hash,
+ const wxString& msgIdCharset,
+ bool convertEncoding) const;
+
+ // return the charset of the strings in this catalog or empty string if
+ // none/unknown
+ wxString GetCharset() const { return m_charset; }
+
+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
+ wxMemoryBuffer m_data;
+
+ // data description
+ size_t32 m_numStrings; // number of strings in this domain
+ wxMsgTableEntry *m_pOrigTable, // pointer to original strings
+ *m_pTransTable; // translated
+
+ wxString m_charset; // from the message catalog header
+
+
+ // swap the 2 halves of 32 bit integer if needed
+ size_t32 Swap(size_t32 ui) const
+ {
+ return m_bSwapped ? (ui << 24) | ((ui & 0xff00) << 8) |
+ ((ui >> 8) & 0xff00) | (ui >> 24)
+ : ui;
+ }
+
+ // just return the pointer to the start of the data as "char *" to
+ // facilitate doing pointer arithmetic with it
+ char *StringData() const
+ {
+ return static_cast<char *>(m_data.GetData());
+ }
+
+ const char *StringAtOfs(wxMsgTableEntry *pTable, size_t32 n) const
+ {
+ const wxMsgTableEntry * const ent = pTable + n;
+
+ // this check could fail for a corrupt message catalog
+ size_t32 ofsString = Swap(ent->ofsString);
+ if ( ofsString + Swap(ent->nLen) > m_data.GetDataLen())
+ {
+ return NULL;
+ }
+
+ return StringData() + ofsString;
+ }
+
+ bool m_bSwapped; // wrong endianness?
+
+ DECLARE_NO_COPY_CLASS(wxMsgCatalogFile)
+};
+
+
+// ----------------------------------------------------------------------------
+// wxMsgCatalog corresponds to one loaded 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)
+// ----------------------------------------------------------------------------
+
+class wxMsgCatalog
+{
+public:
+#if !wxUSE_UNICODE
+ wxMsgCatalog() { m_conv = NULL; }
+ ~wxMsgCatalog();
+#endif
+
+ // load the catalog from disk (szDirPrefix corresponds to language)
+ bool Load(const wxString& dirPrefix, const wxString& name,
+ const wxString& msgIdCharset, bool bConvertEncoding = false);
+
+ // get name of the catalog
+ wxString GetName() const { return m_name; }
+
+ // get the translated string: returns NULL if not found
+ const wxString *GetString(const wxString& sz, size_t n = size_t(-1)) const;
+
+ // public variable pointing to the next element in a linked list (or NULL)
+ wxMsgCatalog *m_pNext;
+
+private:
+ wxMessagesHash m_messages; // all messages in the catalog
+ wxString m_name; // name of the domain
+
+#if !wxUSE_UNICODE
+ // the conversion corresponding to this catalog charset if we installed it
+ // as the global one
+ wxCSConv *m_conv;
+#endif
+
+ wxPluralFormsCalculatorPtr m_pluralFormsCalculator;
+};
+
+// ----------------------------------------------------------------------------
+// global variables
+// ----------------------------------------------------------------------------
+
+// the list of the directories to search for message catalog files
+static wxArrayString gs_searchPrefixes;
+
+// ============================================================================
+// implementation
+// ============================================================================
+
+// ----------------------------------------------------------------------------
+// wxLanguageInfo
+// ----------------------------------------------------------------------------
+
+#ifdef __WXMSW__
+
+// 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;
+
+ wxChar buffer[16];
+ if ( ::GetLocaleInfo(lcid, LOCALE_IDEFAULTANSICODEPAGE,
+ buffer, WXSIZEOF(buffer)) > 0 )
+ {
+ if ( buffer[0] != _T('0') || buffer[1] != _T('\0') )
+ cp = buffer;
+ //else: this locale doesn't use ANSI code page
+ }
+
+ 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] = _T('\0');
+ if ( !::GetLocaleInfo(lcid, LOCALE_SENGLANGUAGE, buffer, WXSIZEOF(buffer)) )
+ {
+ wxLogLastError(_T("GetLocaleInfo(LOCALE_SENGLANGUAGE)"));
+ return locale;
+ }
+
+ locale << buffer;
+ if ( ::GetLocaleInfo(lcid, LOCALE_SENGCOUNTRY,
+ buffer, WXSIZEOF(buffer)) > 0 )
+ {
+ locale << _T('_') << buffer;
+ }
+
+ const wxString cp = wxGetANSICodePageForLocale(lcid);
+ if ( !cp.empty() )
+ {
+ locale << _T('.') << cp;
+ }
+
+ return locale;
+}
+
+#endif // __WXMSW__
+
+// ----------------------------------------------------------------------------
+// wxMsgCatalogFile class
+// ----------------------------------------------------------------------------
+
+wxMsgCatalogFile::wxMsgCatalogFile()
+{
+}
+
+wxMsgCatalogFile::~wxMsgCatalogFile()
+{
+}
+
+// return the directories to search for message catalogs under the given
+// prefix, separated by wxPATH_SEP
+static
+wxString GetMsgCatalogSubdirs(const wxString& prefix, const wxString& lang)
+{
+ // Search first in Unix-standard prefix/lang/LC_MESSAGES, then in
+ // prefix/lang and finally in just prefix.
+ //
+ // Note that we use LC_MESSAGES on all platforms and not just Unix, because
+ // it doesn't cost much to look into one more directory and doing it this
+ // way has two important benefits:
+ // a) we don't break compatibility with wx-2.6 and older by stopping to
+ // look in a directory where the catalogs used to be and thus silently
+ // breaking apps after they are recompiled against the latest wx
+ // b) it makes it possible to package app's support files in the same
+ // way on all target platforms
+ const wxString pathPrefix = wxFileName(prefix, lang).GetFullPath();
+
+ wxString searchPath;
+ searchPath.reserve(4*pathPrefix.length());
+ searchPath << pathPrefix << wxFILE_SEP_PATH << "LC_MESSAGES" << wxPATH_SEP
+ << prefix << wxFILE_SEP_PATH << wxPATH_SEP
+ << pathPrefix;
+
+ return searchPath;
+}
+
+// construct the search path for the given language
+static wxString GetFullSearchPath(const wxString& lang)
+{
+ // first take the entries explicitly added by the program
+ wxArrayString paths;
+ paths.reserve(gs_searchPrefixes.size() + 1);
+ size_t n,
+ count = gs_searchPrefixes.size();
+ for ( n = 0; n < count; n++ )
+ {
+ paths.Add(GetMsgCatalogSubdirs(gs_searchPrefixes[n], lang));
+ }
+
+
+#if wxUSE_STDPATHS
+ // then look in the standard location
+ const wxString stdp = wxStandardPaths::Get().
+ GetLocalizedResourcesDir(lang, wxStandardPaths::ResourceCat_Messages);
+
+ if ( paths.Index(stdp) == wxNOT_FOUND )
+ paths.Add(stdp);
+#endif // wxUSE_STDPATHS
+
+ // last look in default locations
+#ifdef __UNIX__
+ // LC_PATH is a standard env var containing the search path for the .mo
+ // files
+ const char *pszLcPath = wxGetenv("LC_PATH");
+ if ( pszLcPath )
+ {
+ const wxString lcp = GetMsgCatalogSubdirs(pszLcPath, lang);
+ if ( paths.Index(lcp) == wxNOT_FOUND )
+ paths.Add(lcp);
+ }
+
+ // also add the one from where wxWin was installed:
+ wxString wxp = wxGetInstallPrefix();
+ if ( !wxp.empty() )
+ {
+ wxp = GetMsgCatalogSubdirs(wxp + wxS("/share/locale"), lang);
+ if ( paths.Index(wxp) == wxNOT_FOUND )
+ paths.Add(wxp);
+ }
+#endif // __UNIX__
+
+
+ // finally construct the full search path
+ wxString searchPath;
+ searchPath.reserve(500);
+ count = paths.size();
+ for ( n = 0; n < count; n++ )
+ {
+ searchPath += paths[n];
+ if ( n != count - 1 )
+ searchPath += wxPATH_SEP;
+ }
+
+ return searchPath;
+}
+
+// open disk file and read in it's contents
+bool wxMsgCatalogFile::Load(const wxString& szDirPrefix, const wxString& szName,
+ wxPluralFormsCalculatorPtr& rPluralFormsCalculator)
+{
+ wxCHECK_MSG( szDirPrefix.length() >= LEN_LANG, false,
+ "invalid language specification" );
+
+ wxString searchPath;
+
+#if wxUSE_FONTMAP
+ // first look for the catalog for this language and the current locale:
+ // notice that we don't use the system name for the locale as this would
+ // force us to install catalogs in different locations depending on the
+ // system but always use the canonical name
+ wxFontEncoding encSys = wxLocale::GetSystemEncoding();
+ if ( encSys != wxFONTENCODING_SYSTEM )
+ {
+ wxString fullname(szDirPrefix);
+ fullname << wxS('.') << wxFontMapperBase::GetEncodingName(encSys);
+ searchPath << GetFullSearchPath(fullname) << wxPATH_SEP;
+ }
+#endif // wxUSE_FONTMAP
+
+
+ searchPath += GetFullSearchPath(szDirPrefix);
+ if ( szDirPrefix.length() > LEN_LANG && szDirPrefix[LEN_LANG] == wxS('_') )
+ {
+ // also add just base locale name: for things like "fr_BE" (Belgium
+ // French) we should use fall back on plain "fr" if no Belgium-specific
+ // message catalogs exist
+ searchPath << wxPATH_SEP
+ << GetFullSearchPath(ExtractLang(szDirPrefix));
+ }
+
+ // 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)
+
+ NoTransErr noTransErr;
+ wxLogVerbose(_("looking for catalog '%s' in path '%s'."),
+ szName, searchPath.c_str());
+ wxLogTrace(TRACE_I18N, wxS("Looking for \"%s.mo\" in \"%s\""),
+ szName, searchPath.c_str());
+
+ wxFileName fn(szName);
+ fn.SetExt(wxS("mo"));
+
+ wxString strFullName;
+#if wxUSE_FILESYSTEM
+ wxFileSystem fileSys;
+ if ( !fileSys.FindFileInPath(&strFullName, searchPath, fn.GetFullPath()) )
+#else // !wxUSE_FILESYSTEM
+ if ( !wxFindFileInPath(&strFullName, searchPath, fn.GetFullPath()) )
+#endif // wxUSE_FILESYSTEM/!wxUSE_FILESYSTEM
+ {
+ wxLogVerbose(_("catalog file for domain '%s' not found."), szName);
+ wxLogTrace(TRACE_I18N, wxS("Catalog \"%s.mo\" not found"), szName);
+ return false;
+ }
+
+ // open file and read its data
+ wxLogVerbose(_("using catalog '%s' from '%s'."), szName, strFullName.c_str());
+ wxLogTrace(TRACE_I18N, wxS("Using catalog \"%s\"."), strFullName.c_str());
+
+#if wxUSE_FILESYSTEM
+ wxFSFile * const fileMsg = fileSys.OpenFile(strFullName);
+ if ( !fileMsg )
+ return false;
+
+ wxInputStream *fileStream = fileMsg->GetStream();
+ m_data.SetDataLen(0);
+
+ static const size_t chunkSize = 4096;
+ while ( !fileStream->Eof() ) {
+ fileStream->Read(m_data.GetAppendBuf(chunkSize), chunkSize);
+ m_data.UngetAppendBuf(fileStream->LastRead());
+ }
+
+ delete fileMsg;
+#else // !wxUSE_FILESYSTEM
+ wxFile fileMsg(strFullName);
+ if ( !fileMsg.IsOpened() )
+ return false;
+
+ // get the file size (assume it is less than 4Gb...)
+ wxFileOffset lenFile = fileMsg.Length();
+ if ( lenFile == wxInvalidOffset )
+ return false;
+
+ size_t nSize = wx_truncate_cast(size_t, lenFile);
+ wxASSERT_MSG( nSize == lenFile + size_t(0), wxS("message catalog bigger than 4GB?") );
+
+ // read the whole file in memory
+ if ( fileMsg.Read(m_data.GetWriteBuf(nSize), nSize) != lenFile )
+ return false;
+
+ m_data.UngetWriteBuf(nSize);
+#endif // wxUSE_FILESYSTEM/!wxUSE_FILESYSTEM
+
+
+ // examine header
+ bool bValid = m_data.GetDataLen() > sizeof(wxMsgCatalogHeader);
+
+ const wxMsgCatalogHeader *pHeader = (wxMsgCatalogHeader *)m_data.GetData();
+ 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());
+
+ return false;
+ }
+
+ // initialize
+ m_numStrings = Swap(pHeader->numStrings);
+ m_pOrigTable = (wxMsgTableEntry *)(StringData() +
+ Swap(pHeader->ofsOrigTable));
+ m_pTransTable = (wxMsgTableEntry *)(StringData() +
+ Swap(pHeader->ofsTransTable));
+
+ // now parse catalog's header and try to extract catalog charset and
+ // plural forms formula from it:
+
+ const char* headerData = StringAtOfs(m_pOrigTable, 0);
+ if ( headerData && headerData[0] == '\0' )
+ {
+ // Extract the charset:
+ const char * const header = StringAtOfs(m_pTransTable, 0);
+ const char *
+ cset = strstr(header, "Content-Type: text/plain; charset=");
+ if ( cset )
+ {
+ cset += 34; // strlen("Content-Type: text/plain; charset=")
+
+ const char * const csetEnd = strchr(cset, '\n');
+ if ( csetEnd )
+ {
+ m_charset = wxString(cset, csetEnd - cset);
+ if ( m_charset == wxS("CHARSET") )
+ {
+ // "CHARSET" is not valid charset, but lazy translator
+ m_charset.empty();
+ }
+ }
+ }
+ // else: incorrectly filled Content-Type header
+
+ // Extract plural forms:
+ const char * plurals = strstr(header, "Plural-Forms:");
+ if ( plurals )
+ {
+ plurals += 13; // strlen("Plural-Forms:")
+ const char * const pluralsEnd = strchr(plurals, '\n');
+ if ( pluralsEnd )
+ {
+ const size_t pluralsLen = pluralsEnd - plurals;
+ wxCharBuffer buf(pluralsLen);
+ strncpy(buf.data(), plurals, pluralsLen);
+ wxPluralFormsCalculator * const
+ pCalculator = wxPluralFormsCalculator::make(buf);
+ if ( pCalculator )
+ {
+ rPluralFormsCalculator.reset(pCalculator);
+ }
+ else
+ {
+ wxLogVerbose(_("Failed to parse Plural-Forms: '%s'"),
+ buf.data());
+ }
+ }
+ }
+
+ if ( !rPluralFormsCalculator.get() )
+ rPluralFormsCalculator.reset(wxPluralFormsCalculator::make());
+ }
+
+ // everything is fine
+ return true;
+}
+
+bool wxMsgCatalogFile::FillHash(wxMessagesHash& hash,
+ const wxString& msgIdCharset,
+ bool convertEncoding) const
+{
+#if wxUSE_UNICODE
+ // this parameter doesn't make sense, we always must convert encoding in
+ // Unicode build
+ convertEncoding = true;
+#elif wxUSE_FONTMAP
+ if ( convertEncoding )
+ {
+ // determine if we need any conversion at all
+ wxFontEncoding encCat = wxFontMapperBase::GetEncodingFromName(m_charset);
+ if ( encCat == wxLocale::GetSystemEncoding() )
+ {
+ // no need to convert
+ convertEncoding = false;
+ }
+ }
+#endif // wxUSE_UNICODE/wxUSE_FONTMAP
+
+#if wxUSE_WCHAR_T
+ // conversion to use to convert catalog strings to the GUI encoding
+ wxMBConv *inputConv,
+ *inputConvPtr = NULL; // same as inputConv but safely deleteable
+ if ( convertEncoding && !m_charset.empty() )
+ {
+ inputConvPtr =
+ inputConv = new wxCSConv(m_charset);
+ }
+ else // no need or not possible to convert the encoding
+ {
+#if wxUSE_UNICODE
+ // we must somehow convert the narrow strings in the message catalog to
+ // wide strings, so use the default conversion if we have no charset
+ inputConv = wxConvCurrent;
+#else // !wxUSE_UNICODE
+ inputConv = NULL;
+#endif // wxUSE_UNICODE/!wxUSE_UNICODE
+ }
+
+ // conversion to apply to msgid strings before looking them up: we only
+ // need it if the msgids are neither in 7 bit ASCII nor in the same
+ // encoding as the catalog
+ wxCSConv *sourceConv = msgIdCharset.empty() || (msgIdCharset == m_charset)
+ ? NULL
+ : new wxCSConv(msgIdCharset);
+
+#elif wxUSE_FONTMAP
+ wxASSERT_MSG( msgIdCharset.empty(),
+ wxS("non-ASCII msgid languages only supported if wxUSE_WCHAR_T=1") );
+
+ wxEncodingConverter converter;
+ if ( convertEncoding )
+ {
+ wxFontEncoding targetEnc = wxFONTENCODING_SYSTEM;
+ wxFontEncoding enc = wxFontMapperBase::Get()->CharsetToEncoding(m_charset, false);
+ if ( enc == wxFONTENCODING_SYSTEM )
+ {
+ convertEncoding = false; // unknown encoding
+ }
+ else
+ {
+ targetEnc = wxLocale::GetSystemEncoding();
+ if (targetEnc == wxFONTENCODING_SYSTEM)
+ {
+ wxFontEncodingArray a = wxEncodingConverter::GetPlatformEquivalents(enc);
+ if (a[0] == enc)
+ // no conversion needed, locale uses native encoding
+ convertEncoding = false;
+ if (a.GetCount() == 0)
+ // we don't know common equiv. under this platform
+ convertEncoding = false;
+ targetEnc = a[0];
+ }
+ }
+
+ if ( convertEncoding )
+ {
+ converter.Init(enc, targetEnc);
+ }
+ }
+#endif // wxUSE_WCHAR_T/!wxUSE_WCHAR_T
+ (void)convertEncoding; // get rid of warnings about unused parameter
+
+ for (size_t32 i = 0; i < m_numStrings; i++)
+ {
+ const char *data = StringAtOfs(m_pOrigTable, i);
+ if (!data)
+ return false; // may happen for invalid MO files
+
+ wxString msgid;
+#if wxUSE_UNICODE
+ msgid = wxString(data, *inputConv);
+#else // ASCII
+ #if wxUSE_WCHAR_T
+ if ( inputConv && sourceConv )
+ msgid = wxString(inputConv->cMB2WC(data), *sourceConv);
+ else
+ #endif
+ msgid = data;
+#endif // wxUSE_UNICODE
+
+ data = StringAtOfs(m_pTransTable, i);
+ if (!data)
+ return false; // may happen for invalid MO files
+
+ size_t length = Swap(m_pTransTable[i].nLen);
+ size_t offset = 0;
+ size_t index = 0;
+ while (offset < length)
+ {
+ const char * const str = data + offset;
+
+ wxString msgstr;
+#if wxUSE_UNICODE
+ msgstr = wxString(str, *inputConv);
+#elif wxUSE_WCHAR_T
+ if ( inputConv )
+ msgstr = wxString(inputConv->cMB2WC(str), *wxConvUI);
+ else
+ msgstr = str;
+#else // !wxUSE_WCHAR_T
+ #if wxUSE_FONTMAP
+ if ( bConvertEncoding )
+ msgstr = wxString(converter.Convert(str));
+ else
+ #endif
+ msgstr = str;
+#endif // wxUSE_WCHAR_T/!wxUSE_WCHAR_T
+
+ if ( !msgstr.empty() )
+ {
+ hash[index == 0 ? msgid : msgid + wxChar(index)] = msgstr;
+ }
+
+ // skip this string
+ // IMPORTANT: accesses to the 'data' pointer are valid only for
+ // the first 'length+1' bytes (GNU specs says that the
+ // final NUL is not counted in length); using wxStrnlen()
+ // we make sure we don't access memory beyond the valid range
+ // (which otherwise may happen for invalid MO files):
+ offset += wxStrnlen(str, length - offset) + 1;
+ ++index;
+ }
+ }
+
+#if wxUSE_WCHAR_T
+ delete sourceConv;
+ delete inputConvPtr;
+#endif // wxUSE_WCHAR_T
+
+ return true;
+}
+
+
+// ----------------------------------------------------------------------------
+// wxMsgCatalog class
+// ----------------------------------------------------------------------------
+
+#if !wxUSE_UNICODE
+wxMsgCatalog::~wxMsgCatalog()
+{
+ if ( m_conv )
+ {
+ if ( wxConvUI == m_conv )
+ {
+ // we only change wxConvUI if it points to wxConvLocal so we reset
+ // it back to it too
+ wxConvUI = &wxConvLocal;
+ }
+
+ delete m_conv;
+ }
+}
+#endif // !wxUSE_UNICODE
+
+bool wxMsgCatalog::Load(const wxString& dirPrefix, const wxString& name,
+ const wxString& msgIdCharset, bool bConvertEncoding)
+{
+ wxMsgCatalogFile file;
+
+ m_name = name;
+
+ if ( !file.Load(dirPrefix, name, m_pluralFormsCalculator) )
+ return false;
+
+ if ( !file.FillHash(m_messages, msgIdCharset, bConvertEncoding) )
+ return false;
+
+#if !wxUSE_UNICODE
+ // we should use a conversion compatible with the message catalog encoding
+ // in the GUI if we don't convert the strings to the current conversion but
+ // as the encoding is global, only change it once, otherwise we could get
+ // into trouble if we use several message catalogs with different encodings
+ //
+ // this is, of course, a hack but it at least allows the program to use
+ // message catalogs in any encodings without asking the user to change his
+ // locale
+ if ( !bConvertEncoding &&
+ !file.GetCharset().empty() &&
+ wxConvUI == &wxConvLocal )
+ {
+ wxConvUI =
+ m_conv = new wxCSConv(file.GetCharset());
+ }
+#endif // !wxUSE_UNICODE
+
+ return true;
+}
+
+const wxString *wxMsgCatalog::GetString(const wxString& str, size_t n) const
+{
+ int index = 0;
+ if (n != size_t(-1))
+ {
+ index = m_pluralFormsCalculator->evaluate(n);
+ }
+ wxMessagesHash::const_iterator i;
+ if (index != 0)
+ {
+ i = m_messages.find(wxString(str) + wxChar(index)); // plural
+ }
+ else
+ {
+ i = m_messages.find(str);
+ }
+
+ if ( i != m_messages.end() )
+ {
+ return &i->second;
+ }
+ else
+ return NULL;
+}
+
+// ----------------------------------------------------------------------------
+// wxLocale
+// ----------------------------------------------------------------------------
+
+#include "wx/arrimpl.cpp"
+WX_DECLARE_EXPORTED_OBJARRAY(wxLanguageInfo, wxLanguageInfoArray);
+WX_DEFINE_OBJARRAY(wxLanguageInfoArray)
+
+wxLanguageInfoArray *wxLocale::ms_languagesDB = NULL;
+
+/*static*/ void wxLocale::CreateLanguagesDB()
+{
+ if (ms_languagesDB == NULL)
+ {
+ ms_languagesDB = new wxLanguageInfoArray;
+ InitLanguagesDB();
+ }
+}
+
+/*static*/ void wxLocale::DestroyLanguagesDB()
+{
+ delete ms_languagesDB;
+ ms_languagesDB = NULL;
+}
+
+
+void wxLocale::DoCommonInit()
+{
+ m_pszOldLocale = NULL;
+
+ m_pOldLocale = wxSetLocale(this);
+
+ m_pMsgCat = NULL;
+ m_language = wxLANGUAGE_UNKNOWN;
+ m_initialized = false;
+}
+
+// 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,
+ bool bConvertEncoding)
+{
+ wxASSERT_MSG( !m_initialized,
+ wxS("you can't call wxLocale::Init more than once") );
+
+ m_initialized = true;
+ m_strLocale = name;
+ m_strShort = shortName;
+ m_bConvertEncoding = bConvertEncoding;
+ 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' can not 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]);
+ }
+ }
+
+ // load the default catalog with wxWidgets standard messages
+ m_pMsgCat = NULL;
+ bool bOk = true;
+ if ( bLoadDefault )
+ {
+ bOk = AddCatalog(wxS("wxstd"));
+
+ // there may be a catalog with toolkit specific overrides, it is not
+ // an error if this does not exist
+ if ( bOk )
+ {
+ wxString port(wxPlatformInfo::Get().GetPortIdName());
+ if ( !port.empty() )
+ {
+ AddCatalog(port.BeforeFirst(wxS('/')).MakeLower());
+ }
+ }
+ }
+
+ return bOk;
+}
+
+
+#if defined(__UNIX__) && wxUSE_UNICODE && !defined(__WXMAC__)
+static const char *wxSetlocaleTryUTF8(int c, const wxString& lc)
+{
+ 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
+
+bool wxLocale::Init(int language, int flags)
+{
+ bool ret = true;
+
+ 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);
+
+ // Unknown language:
+ if (info == NULL)
+ {
+ wxLogError(wxS("Unknown language %i."), lang);
+ return false;
+ }
+
+ wxString name = info->Description;
+ wxString canonical = info->CanonicalName;
+ wxString locale;
+
+ // 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;
+
+ const char *retloc = wxSetlocaleTryUTF8(LC_ALL, locale);
+
+ 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);
+ }
+
+#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
+
+ 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));
+ }
+ }
+
+ 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<char*>(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();
+
+ // 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 ( !retloc )
+ ret = false;
+#elif defined(__WXMAC__)
+ if (lang == wxLANGUAGE_DEFAULT)
+ locale = wxEmptyString;
+ else
+ locale = info->CanonicalName;
+
+ const char *retloc = wxSetlocale(LC_ALL, locale);
+
+ 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
+
+#ifndef WX_NO_LOCALE_SUPPORT
+ if ( !ret )
+ {
+ wxLogWarning(_("Cannot set locale to language \"%s\"."), name.c_str());
+
+ // continue nevertheless and try to load at least the translations for
+ // this language
+ }
+
+ if ( !Init(name, canonical, retloc,
+ (flags & wxLOCALE_LOAD_DEFAULT) != 0,
+ (flags & wxLOCALE_CONV_ENCODING) != 0) )
+ {
+ ret = false;
+ }
+
+ if (IsOk()) // setlocale() succeeded
+ m_language = lang;
+
+ return ret;
+#endif // !WX_NO_LOCALE_SUPPORT
+}
+
+
+
+void wxLocale::AddCatalogLookupPathPrefix(const wxString& prefix)
+{
+ if ( gs_searchPrefixes.Index(prefix) == wxNOT_FOUND )
+ {
+ gs_searchPrefixes.Add(prefix);
+ }
+ //else: already have it
+}
+
+/*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<CFLocaleRef> 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);
+ }
+
+ // 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 this
+
+ // do we have just the language (or sublang too)?
+ bool justLang = langFull.length() == LEN_LANG;
+ if ( justLang ||
+ (langFull.length() == LEN_FULL && langFull[LEN_LANG] == wxS('_')) )
+ {
+ // 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;
+ }
+ }
+ }
+ }
+ else // not standard format
+ {
+ // 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;
+}
+
+// ----------------------------------------------------------------------------
+// encoding stuff
+// ----------------------------------------------------------------------------
+
+// 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()
+{
+ 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;
+}
+
+/* static */
+wxFontEncoding wxLocale::GetSystemEncoding()
+{
+#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);
+ }
+
+ if ( codepage == 874 )
+ {
+ return wxFONTENCODING_CP874;
+ }
+
+ 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;
+ }
+
+ if ( enc != wxFONTENCODING_MAX )
+ {
+ return enc;
+ }
+ //else: return wxFONTENCODING_SYSTEM below
+ }
+#endif // Win32/Unix
+
+ return wxFONTENCODING_SYSTEM;
+}
+
+/* static */
+void wxLocale::AddLanguage(const wxLanguageInfo& info)
+{
+ CreateLanguagesDB();
+ ms_languagesDB->Add(info);
+}
+
+/* static */
+const wxLanguageInfo *wxLocale::GetLanguageInfo(int lang)
+{
+ 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++ )
+ {
+ 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;
+ }
+ }
+
+ return NULL;
+}
+
+/* static */
+wxString wxLocale::GetLanguageName(int lang)
+{
+ const wxLanguageInfo *info = GetLanguageInfo(lang);
+ if ( !info )
+ return wxEmptyString;
+ else
+ return info->Description;
+}
+
+/* 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 locale pointer
+ wxSetLocale(m_pOldLocale);
+
+ wxSetlocale(LC_ALL, m_pszOldLocale);
+ free((wxChar *)m_pszOldLocale); // const_cast
+}
+
+// get the translation of given string in current locale
+const wxString& wxLocale::GetString(const wxString& origString,
+ const wxString& domain) const
+{
+ return GetString(origString, origString, size_t(-1), domain);
+}
+
+const wxString& wxLocale::GetString(const wxString& origString,
+ const wxString& origString2,
+ size_t n,
+ const wxString& domain) const
+{
+ if ( origString.empty() )
+ return GetUntranslatedString(origString);
+
+ const wxString *trans = NULL;
+ wxMsgCatalog *pMsgCat;
+
+ if ( !domain.empty() )
+ {
+ pMsgCat = FindCatalog(domain);
+
+ // does the catalog exist?
+ if ( pMsgCat != NULL )
+ trans = pMsgCat->GetString(origString, n);
+ }
+ else
+ {
+ // search in all domains
+ for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext )
+ {
+ trans = pMsgCat->GetString(origString, n);
+ if ( trans != NULL ) // take the first found
+ break;
+ }
+ }
+
+ if ( trans == NULL )
+ {
+#ifdef __WXDEBUG__
+ if ( !NoTransErr::Suppress() )
+ {
+ NoTransErr noTransErr;
+
+ wxLogTrace(TRACE_I18N,
+ wxS("string \"%s\"[%ld] not found in %slocale '%s'."),
+ origString, (long)n,
+ wxString::Format(wxS("domain '%s' "), domain).c_str(),
+ m_strLocale.c_str());
+ }
+#endif // __WXDEBUG__
+
+ if (n == size_t(-1))
+ return GetUntranslatedString(origString);
+ else
+ return GetUntranslatedString(n == 1 ? origString : origString2);
+ }
+
+ return *trans;
+}
+
+WX_DECLARE_HASH_SET(wxString, wxStringHash, wxStringEqual,
+ wxLocaleUntranslatedStrings);
+
+/* static */
+const wxString& wxLocale::GetUntranslatedString(const wxString& str)
+{
+ static wxLocaleUntranslatedStrings s_strings;
+
+ wxLocaleUntranslatedStrings::iterator i = s_strings.find(str);
+ if ( i == s_strings.end() )
+ return *s_strings.insert(str).first;
+
+ return *i;
+}
+
+wxString wxLocale::GetHeaderValue(const wxString& header,
+ const wxString& domain) const
+{
+ if ( header.empty() )
+ return wxEmptyString;
+
+ const wxString *trans = NULL;
+ wxMsgCatalog *pMsgCat;
+
+ if ( !domain.empty() )
+ {
+ pMsgCat = FindCatalog(domain);
+
+ // does the catalog exist?
+ if ( pMsgCat == NULL )
+ return wxEmptyString;
+
+ trans = pMsgCat->GetString(wxEmptyString, (size_t)-1);
+ }
+ else
+ {
+ // search in all domains
+ for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext )
+ {
+ trans = pMsgCat->GetString(wxEmptyString, (size_t)-1);
+ if ( trans != NULL ) // take the first found
+ break;
+ }
+ }
+
+ if ( !trans || trans->empty() )
+ return wxEmptyString;
+
+ size_t found = trans->find(header);
+ if ( found == wxString::npos )
+ return wxEmptyString;
+
+ found += header.length() + 2 /* ': ' */;
+
+ // Every header is separated by \n
+
+ size_t endLine = trans->find(wxS('\n'), found);
+ size_t len = (endLine == wxString::npos) ?
+ wxString::npos : (endLine - found);
+
+ return trans->substr(found, len);
+}
+
+
+// find catalog by name in a linked list, return NULL if !found
+wxMsgCatalog *wxLocale::FindCatalog(const wxString& domain) const
+{
+ // linear search in the linked list
+ wxMsgCatalog *pMsgCat;
+ for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext )
+ {
+ if ( pMsgCat->GetName() == domain )
+ return pMsgCat;
+ }
+
+ return NULL;
+}
+
+// check if the given locale is provided by OS and C run time
+/* static */
+bool wxLocale::IsAvailable(int lang)
+{
+ const wxLanguageInfo *info = wxLocale::GetLanguageInfo(lang);
+ wxCHECK_MSG( info, false, wxS("invalid language") );
+
+#if defined(__WIN32__)
+ if ( !info->WinLang )
+ return false;
+
+ if ( !::IsValidLocale(info->GetLCID(), LCID_INSTALLED) )
+ return false;
+
+#elif defined(__UNIX__)
+
+ // Test if setting the locale works, then set it back.
+ const char *oldLocale = wxSetlocale(LC_ALL, "");
+ const char *tmp = wxSetlocaleTryUTF8(LC_ALL, info->CanonicalName);
+ if ( !tmp )
+ {
+ // Some C libraries don't like xx_YY form and require xx only
+ tmp = wxSetlocaleTryUTF8(LC_ALL, ExtractLang(info->CanonicalName));
+ if ( !tmp )
+ return false;
+ }
+ // restore the original locale
+ wxSetlocale(LC_ALL, oldLocale);
+#endif
+
+ return true;
+}
+
+// check if the given catalog is loaded
+bool wxLocale::IsLoaded(const wxString& szDomain) const
+{
+ return FindCatalog(szDomain) != NULL;
+}
+
+// add a catalog to our linked list
+bool wxLocale::AddCatalog(const wxString& szDomain)
+{
+ return AddCatalog(szDomain, wxLANGUAGE_ENGLISH_US, wxEmptyString);
+}
+
+// add a catalog to our linked list
+bool wxLocale::AddCatalog(const wxString& szDomain,
+ wxLanguage msgIdLanguage,
+ const wxString& msgIdCharset)
+
+{
+ wxCHECK_MSG( !m_strShort.empty(), false, "must initialize catalog first" );
+
+
+ // It is OK to not load catalog if the msgid language and m_language match,
+ // in which case we can directly display the texts embedded in program's
+ // source code:
+ if ( msgIdLanguage == m_language )
+ return true;
+
+
+ wxMsgCatalog *pMsgCat = new wxMsgCatalog;
+
+ if ( pMsgCat->Load(m_strShort, szDomain, msgIdCharset, m_bConvertEncoding) )
+ {
+ // 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;
+
+ return true;
+ }
+
+ // don't add it because it couldn't be loaded anyway
+ delete pMsgCat;
+
+
+ // If there's no exact match, we may still get partial match where the
+ // (basic) language is same, but the country differs. For example, it's
+ // permitted to use en_US strings from sources even if m_language is en_GB:
+ const wxLanguageInfo *msgIdLangInfo = GetLanguageInfo(msgIdLanguage);
+ if ( msgIdLangInfo &&
+ ExtractLang(msgIdLangInfo->CanonicalName) == ExtractLang(m_strShort) )
+ {
+ return true;
+ }
+
+ return false;