X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/c67d6888d421207045dc6ce9c9762727118c92e2..64dcc269136ad1ad47c6df4d707c9a42961c5694:/src/common/intl.cpp?ds=sidebyside diff --git a/src/common/intl.cpp b/src/common/intl.cpp index f41c6ba9c5..36c76c398b 100644 --- a/src/common/intl.cpp +++ b/src/common/intl.cpp @@ -2,11 +2,12 @@ // Name: src/common/intl.cpp // Purpose: Internationalization and localisation for wxWindows // 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,7 +18,7 @@ // headers // ---------------------------------------------------------------------------- -#ifdef __GNUG__ +#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) #pragma implementation "intl.h" #endif @@ -31,7 +32,11 @@ #if wxUSE_INTL // standard headers + +#ifndef __WXWINCE__ #include +#endif + #include #include #ifdef HAVE_LANGINFO_H @@ -48,16 +53,22 @@ #include "wx/dynarray.h" #endif // WX_PRECOMP +#ifdef __WIN32__ + #include "wx/msw/private.h" +#elif defined(__UNIX_LIKE__) + #include "wx/fontmap.h" // for CharsetToEncoding() +#endif + #include "wx/file.h" #include "wx/tokenzr.h" #include "wx/module.h" #include "wx/fontmap.h" #include "wx/encconv.h" +#include "wx/hashmap.h" +#include "wx/ptr_scpd.h" -#ifdef __WIN32__ - #include "wx/msw/private.h" -#elif defined(__UNIX_LIKE__) - #include "wx/fontmap.h" // for CharsetToEncoding() +#if defined(__WXMAC__) + #include "wx/mac/private.h" // includes mac headers #endif // ---------------------------------------------------------------------------- @@ -65,41 +76,8 @@ // ---------------------------------------------------------------------------- // 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 - wxASSERT_MSG( wxAssertIsEqual(sizeof(int), 4), - "size_t32 is incorrectly defined!" ); - } - } intsizechecker; - #endif -#endif // Win/!Win +typedef wxUint8 size_t8; +typedef wxUint32 size_t32; // ---------------------------------------------------------------------------- // constants @@ -168,140 +146,852 @@ static inline wxString ExtractNotLang(const wxString& langFull) #endif // __UNIX__ + // ---------------------------------------------------------------------------- -// 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) +// Plural forms parser // ---------------------------------------------------------------------------- -class wxMsgCatalog +/* + Simplified Grammar + +Expression: + LogicalOrExpression '?' Expression ':' Expression + LogicalOrExpression + +LogicalOrExpression: + LogicalAndExpression "||" LogicalOrExpression // to (a || b) || c + LogicalAndExpression + +LogicalAndExpression: + EqualityExpression "&&" LogicalAndExpression // to (a && b) && c + EqualityExpression + +EqualityExpression: + RelationalExpression "==" RelationalExperession + RelationalExpression "!=" RelationalExperession + RelationalExpression + +RelationalExpression: + MultiplicativeExpression '>' MultiplicativeExpression + MultiplicativeExpression '<' MultiplicativeExpression + MultiplicativeExpression ">=" MultiplicativeExpression + MultiplicativeExpression "<=" MultiplicativeExpression + MultiplicativeExpression + +MultiplicativeExpression: + PmExpression '%' PmExpression + PmExpression + +PmExpression: + N + Number + '(' Expression ')' +*/ + +class wxPluralFormsToken { public: - // ctor & dtor - wxMsgCatalog(); - ~wxMsgCatalog(); + enum Type + { + T_ERROR, T_EOF, T_NUMBER, T_N, T_PLURAL, T_NPLURALS, T_EQUAL, T_ASSIGN, + T_GREATER, T_GREATER_OR_EQUAL, T_LESS, T_LESS_OR_EQUAL, + T_REMINDER, T_NOT_EQUAL, + T_LOGICAL_AND, T_LOGICAL_OR, T_QUESTION, T_COLON, T_SEMICOLON, + T_LEFT_BRACKET, T_RIGHT_BRACKET + }; + Type type() const { return m_type; } + void setType(Type type) { m_type = type; } + // for T_NUMBER only + typedef int Number; + Number number() const { return m_number; } + void setNumber(Number num) { m_number = num; } +private: + Type m_type; + Number m_number; +}; - // load the catalog from disk (szDirPrefix corresponds to language) - bool Load(const wxChar *szDirPrefix, const wxChar *szName, bool bConvertEncoding = FALSE); - bool IsLoaded() const { return m_pData != NULL; } - // get name of the catalog - const wxChar *GetName() const { return m_pszName; } +class wxPluralFormsScanner +{ +public: + wxPluralFormsScanner(const char* s); + const wxPluralFormsToken& token() const { return m_token; } + bool nextToken(); // returns false if error +private: + const char* m_s; + wxPluralFormsToken m_token; +}; - // get the translated string: returns NULL if not found - const char *GetString(const char *sz) const; +wxPluralFormsScanner::wxPluralFormsScanner(const char* s) : m_s(s) +{ + nextToken(); +} - // public variable pointing to the next element in a linked list (or NULL) - wxMsgCatalog *m_pNext; +bool wxPluralFormsScanner::nextToken() +{ + wxPluralFormsToken::Type type = wxPluralFormsToken::T_ERROR; + while (isspace(*m_s)) + { + ++m_s; + } + if (*m_s == 0) + { + type = wxPluralFormsToken::T_EOF; + } + else if (isdigit(*m_s)) + { + wxPluralFormsToken::Number number = *m_s++ - '0'; + while (isdigit(*m_s)) + { + number = number * 10 + (*m_s++ - '0'); + } + m_token.setNumber(number); + type = wxPluralFormsToken::T_NUMBER; + } + else if (isalpha(*m_s)) + { + const char* begin = m_s++; + while (isalnum(*m_s)) + { + ++m_s; + } + size_t size = m_s - begin; + if (size == 1 && memcmp(begin, "n", size) == 0) + { + type = wxPluralFormsToken::T_N; + } + else if (size == 6 && memcmp(begin, "plural", size) == 0) + { + type = wxPluralFormsToken::T_PLURAL; + } + else if (size == 8 && memcmp(begin, "nplurals", size) == 0) + { + type = wxPluralFormsToken::T_NPLURALS; + } + } + else if (*m_s == '=') + { + ++m_s; + if (*m_s == '=') + { + ++m_s; + type = wxPluralFormsToken::T_EQUAL; + } + else + { + type = wxPluralFormsToken::T_ASSIGN; + } + } + else if (*m_s == '>') + { + ++m_s; + if (*m_s == '=') + { + ++m_s; + type = wxPluralFormsToken::T_GREATER_OR_EQUAL; + } + else + { + type = wxPluralFormsToken::T_GREATER; + } + } + else if (*m_s == '<') + { + ++m_s; + if (*m_s == '=') + { + ++m_s; + type = wxPluralFormsToken::T_LESS_OR_EQUAL; + } + else + { + type = wxPluralFormsToken::T_LESS; + } + } + else if (*m_s == '%') + { + ++m_s; + type = wxPluralFormsToken::T_REMINDER; + } + else if (*m_s == '!' && m_s[1] == '=') + { + m_s += 2; + type = wxPluralFormsToken::T_NOT_EQUAL; + } + else if (*m_s == '&' && m_s[1] == '&') + { + m_s += 2; + type = wxPluralFormsToken::T_LOGICAL_AND; + } + else if (*m_s == '|' && m_s[1] == '|') + { + m_s += 2; + type = wxPluralFormsToken::T_LOGICAL_OR; + } + else if (*m_s == '?') + { + ++m_s; + type = wxPluralFormsToken::T_QUESTION; + } + else if (*m_s == ':') + { + ++m_s; + type = wxPluralFormsToken::T_COLON; + } else if (*m_s == ';') { + ++m_s; + type = wxPluralFormsToken::T_SEMICOLON; + } + else if (*m_s == '(') + { + ++m_s; + type = wxPluralFormsToken::T_LEFT_BRACKET; + } + else if (*m_s == ')') + { + ++m_s; + type = wxPluralFormsToken::T_RIGHT_BRACKET; + } + m_token.setType(type); + return type != wxPluralFormsToken::T_ERROR; +} + +class wxPluralFormsNode; + +// NB: Can't use wxDEFINE_SCOPED_PTR_TYPE because wxPluralFormsNode is not +// fully defined yet: +class wxPluralFormsNodePtr +{ +public: + wxPluralFormsNodePtr(wxPluralFormsNode *p = NULL) : m_p(p) {} + ~wxPluralFormsNodePtr(); + wxPluralFormsNode& operator*() const { return *m_p; } + wxPluralFormsNode* operator->() const { return m_p; } + wxPluralFormsNode* get() const { return m_p; } + wxPluralFormsNode* release(); + void reset(wxPluralFormsNode *p); private: - // this implementation is binary compatible with GNU gettext() version 0.10 + wxPluralFormsNode *m_p; +}; - // an entry in the string table - struct wxMsgTableEntry - { - size_t32 nLen; // length of the string - size_t32 ofsString; // pointer to the string - }; +class wxPluralFormsNode +{ +public: + wxPluralFormsNode(const wxPluralFormsToken& token) : m_token(token) {} + const wxPluralFormsToken& token() const { return m_token; } + const wxPluralFormsNode* node(size_t i) const + { return m_nodes[i].get(); } + void setNode(size_t i, wxPluralFormsNode* n); + wxPluralFormsNode* releaseNode(size_t i); + wxPluralFormsToken::Number evaluate(wxPluralFormsToken::Number n) const; - // 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)); } - - // convert encoding to platform native one, if neccessary - void ConvertEncoding(); - - // utility functions - // calculate the hash value of given string - static 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 +private: + wxPluralFormsToken m_token; + wxPluralFormsNodePtr m_nodes[3]; }; + +wxPluralFormsNodePtr::~wxPluralFormsNodePtr() +{ + delete m_p; +} +wxPluralFormsNode* wxPluralFormsNodePtr::release() +{ + wxPluralFormsNode *p = m_p; + m_p = NULL; + return p; +} +void wxPluralFormsNodePtr::reset(wxPluralFormsNode *p) +{ + if (p != m_p) + { + delete m_p; + m_p = p; + } +} -// ---------------------------------------------------------------------------- -// global variables -// ---------------------------------------------------------------------------- -// the list of the directories to search for message catalog files -static wxArrayString s_searchPrefixes; +void wxPluralFormsNode::setNode(size_t i, wxPluralFormsNode* n) +{ + m_nodes[i].reset(n); +} -// ============================================================================ -// implementation -// ============================================================================ +wxPluralFormsNode* wxPluralFormsNode::releaseNode(size_t i) +{ + return m_nodes[i].release(); +} -// ---------------------------------------------------------------------------- -// wxMsgCatalog class -// ---------------------------------------------------------------------------- +wxPluralFormsToken::Number +wxPluralFormsNode::evaluate(wxPluralFormsToken::Number n) const +{ + switch (token().type()) + { + // leaf + case wxPluralFormsToken::T_NUMBER: + return token().number(); + case wxPluralFormsToken::T_N: + return n; + // 2 args + case wxPluralFormsToken::T_EQUAL: + return node(0)->evaluate(n) == node(1)->evaluate(n); + case wxPluralFormsToken::T_NOT_EQUAL: + return node(0)->evaluate(n) != node(1)->evaluate(n); + case wxPluralFormsToken::T_GREATER: + return node(0)->evaluate(n) > node(1)->evaluate(n); + case wxPluralFormsToken::T_GREATER_OR_EQUAL: + return node(0)->evaluate(n) >= node(1)->evaluate(n); + case wxPluralFormsToken::T_LESS: + return node(0)->evaluate(n) < node(1)->evaluate(n); + case wxPluralFormsToken::T_LESS_OR_EQUAL: + return node(0)->evaluate(n) <= node(1)->evaluate(n); + case wxPluralFormsToken::T_REMINDER: + { + wxPluralFormsToken::Number number = node(1)->evaluate(n); + if (number != 0) + { + return node(0)->evaluate(n) % number; + } + else + { + return 0; + } + } + case wxPluralFormsToken::T_LOGICAL_AND: + return node(0)->evaluate(n) && node(1)->evaluate(n); + case wxPluralFormsToken::T_LOGICAL_OR: + return node(0)->evaluate(n) || node(1)->evaluate(n); + // 3 args + case wxPluralFormsToken::T_QUESTION: + return node(0)->evaluate(n) + ? node(1)->evaluate(n) + : node(2)->evaluate(n); + default: + return 0; + } +} + + +class wxPluralFormsCalculator +{ +public: + wxPluralFormsCalculator() : m_nplurals(0), m_plural(0) {} + + // input: number, returns msgstr index + int evaluate(int n) const; + + // input: text after "Plural-Forms:" (e.g. "nplurals=2; plural=(n != 1);"), + // if s == 0, creates default handler + // returns 0 if error + static wxPluralFormsCalculator* make(const char* s = 0); -// 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) + ~wxPluralFormsCalculator() {} + + void init(wxPluralFormsToken::Number nplurals, wxPluralFormsNode* plural); + wxString getString() const; + +private: + wxPluralFormsToken::Number m_nplurals; + wxPluralFormsNodePtr m_plural; +}; + +wxDEFINE_SCOPED_PTR_TYPE(wxPluralFormsCalculator); + +void wxPluralFormsCalculator::init(wxPluralFormsToken::Number nplurals, + wxPluralFormsNode* plural) { - #define HASHWORDBITS 32 // the length of size_t32 + m_nplurals = nplurals; + m_plural.reset(plural); +} - 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; +int wxPluralFormsCalculator::evaluate(int n) const +{ + if (m_plural.get() == 0) + { + return 0; } - } + wxPluralFormsToken::Number number = m_plural->evaluate(n); + if (number < 0 || number > m_nplurals) + { + return 0; + } + return number; +} + + +class wxPluralFormsParser +{ +public: + wxPluralFormsParser(wxPluralFormsScanner& scanner) : m_scanner(scanner) {} + bool parse(wxPluralFormsCalculator& rCalculator); + +private: + wxPluralFormsNode* parsePlural(); + // stops at T_SEMICOLON, returns 0 if error + wxPluralFormsScanner& m_scanner; + const wxPluralFormsToken& token() const; + bool nextToken(); + + wxPluralFormsNode* expression(); + wxPluralFormsNode* logicalOrExpression(); + wxPluralFormsNode* logicalAndExpression(); + wxPluralFormsNode* equalityExpression(); + wxPluralFormsNode* multiplicativeExpression(); + wxPluralFormsNode* relationalExpression(); + wxPluralFormsNode* pmExpression(); +}; - return hval; +bool wxPluralFormsParser::parse(wxPluralFormsCalculator& rCalculator) +{ + if (token().type() != wxPluralFormsToken::T_NPLURALS) + return false; + if (!nextToken()) + return false; + if (token().type() != wxPluralFormsToken::T_ASSIGN) + return false; + if (!nextToken()) + return false; + if (token().type() != wxPluralFormsToken::T_NUMBER) + return false; + wxPluralFormsToken::Number nplurals = token().number(); + if (!nextToken()) + return false; + if (token().type() != wxPluralFormsToken::T_SEMICOLON) + return false; + if (!nextToken()) + return false; + if (token().type() != wxPluralFormsToken::T_PLURAL) + return false; + if (!nextToken()) + return false; + if (token().type() != wxPluralFormsToken::T_ASSIGN) + return false; + if (!nextToken()) + return false; + wxPluralFormsNode* plural = parsePlural(); + if (plural == 0) + return false; + if (token().type() != wxPluralFormsToken::T_SEMICOLON) + return false; + if (!nextToken()) + return false; + if (token().type() != wxPluralFormsToken::T_EOF) + return false; + rCalculator.init(nplurals, plural); + return true; +} + +wxPluralFormsNode* wxPluralFormsParser::parsePlural() +{ + wxPluralFormsNode* p = expression(); + if (p == NULL) + { + return NULL; + } + wxPluralFormsNodePtr n(p); + if (token().type() != wxPluralFormsToken::T_SEMICOLON) + { + return NULL; + } + return n.release(); +} + +const wxPluralFormsToken& wxPluralFormsParser::token() const +{ + return m_scanner.token(); +} + +bool wxPluralFormsParser::nextToken() +{ + if (!m_scanner.nextToken()) + return false; + return true; +} + +wxPluralFormsNode* wxPluralFormsParser::expression() +{ + wxPluralFormsNode* p = logicalOrExpression(); + if (p == NULL) + return NULL; + wxPluralFormsNodePtr n(p); + if (token().type() == wxPluralFormsToken::T_QUESTION) + { + wxPluralFormsNodePtr qn(new wxPluralFormsNode(token())); + if (!nextToken()) + { + return 0; + } + p = expression(); + if (p == 0) + { + return 0; + } + qn->setNode(1, p); + if (token().type() != wxPluralFormsToken::T_COLON) + { + return 0; + } + if (!nextToken()) + { + return 0; + } + p = expression(); + if (p == 0) + { + return 0; + } + qn->setNode(2, p); + qn->setNode(0, n.release()); + return qn.release(); + } + return n.release(); +} + +wxPluralFormsNode*wxPluralFormsParser::logicalOrExpression() +{ + wxPluralFormsNode* p = logicalAndExpression(); + if (p == NULL) + return NULL; + wxPluralFormsNodePtr ln(p); + if (token().type() == wxPluralFormsToken::T_LOGICAL_OR) + { + wxPluralFormsNodePtr un(new wxPluralFormsNode(token())); + if (!nextToken()) + { + return 0; + } + p = logicalOrExpression(); + if (p == 0) + { + return 0; + } + wxPluralFormsNodePtr rn(p); // right + if (rn->token().type() == wxPluralFormsToken::T_LOGICAL_OR) + { + // see logicalAndExpression comment + un->setNode(0, ln.release()); + un->setNode(1, rn->releaseNode(0)); + rn->setNode(0, un.release()); + return rn.release(); + } + + + un->setNode(0, ln.release()); + un->setNode(1, rn.release()); + return un.release(); + } + return ln.release(); +} + +wxPluralFormsNode* wxPluralFormsParser::logicalAndExpression() +{ + wxPluralFormsNode* p = equalityExpression(); + if (p == NULL) + return NULL; + wxPluralFormsNodePtr ln(p); // left + if (token().type() == wxPluralFormsToken::T_LOGICAL_AND) + { + wxPluralFormsNodePtr un(new wxPluralFormsNode(token())); // up + if (!nextToken()) + { + return NULL; + } + p = logicalAndExpression(); + if (p == 0) + { + return NULL; + } + wxPluralFormsNodePtr rn(p); // right + if (rn->token().type() == wxPluralFormsToken::T_LOGICAL_AND) + { +// transform 1 && (2 && 3) -> (1 && 2) && 3 +// u r +// l r -> u 3 +// 2 3 l 2 + un->setNode(0, ln.release()); + un->setNode(1, rn->releaseNode(0)); + rn->setNode(0, un.release()); + return rn.release(); + } + + un->setNode(0, ln.release()); + un->setNode(1, rn.release()); + return un.release(); + } + return ln.release(); } -// swap the 2 halves of 32 bit integer if needed -size_t32 wxMsgCatalog::Swap(size_t32 ui) const +wxPluralFormsNode* wxPluralFormsParser::equalityExpression() { - return m_bSwapped ? (ui << 24) | ((ui & 0xff00) << 8) | - ((ui >> 8) & 0xff00) | (ui >> 24) - : ui; + wxPluralFormsNode* p = relationalExpression(); + if (p == NULL) + return NULL; + wxPluralFormsNodePtr n(p); + if (token().type() == wxPluralFormsToken::T_EQUAL + || token().type() == wxPluralFormsToken::T_NOT_EQUAL) + { + wxPluralFormsNodePtr qn(new wxPluralFormsNode(token())); + if (!nextToken()) + { + return NULL; + } + p = relationalExpression(); + if (p == NULL) + { + return NULL; + } + qn->setNode(1, p); + qn->setNode(0, n.release()); + return qn.release(); + } + return n.release(); +} + +wxPluralFormsNode* wxPluralFormsParser::relationalExpression() +{ + wxPluralFormsNode* p = multiplicativeExpression(); + if (p == NULL) + return NULL; + wxPluralFormsNodePtr n(p); + if (token().type() == wxPluralFormsToken::T_GREATER + || token().type() == wxPluralFormsToken::T_LESS + || token().type() == wxPluralFormsToken::T_GREATER_OR_EQUAL + || token().type() == wxPluralFormsToken::T_LESS_OR_EQUAL) + { + wxPluralFormsNodePtr qn(new wxPluralFormsNode(token())); + if (!nextToken()) + { + return NULL; + } + p = multiplicativeExpression(); + if (p == NULL) + { + return NULL; + } + qn->setNode(1, p); + qn->setNode(0, n.release()); + return qn.release(); + } + return n.release(); +} + +wxPluralFormsNode* wxPluralFormsParser::multiplicativeExpression() +{ + 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(); } -wxMsgCatalog::wxMsgCatalog() +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 +// ---------------------------------------------------------------------------- + +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 wxChar *szDirPrefix, const wxChar *szName, + wxPluralFormsCalculatorPtr& rPluralFormsCalculator); + + // fills the hash with string-translation pairs + void FillHash(wxMessagesHash& hash, bool convertEncoding) const; + +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; + + // amount of memory pointed to by m_pData. + size_t32 m_nSize; + + // 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; + + // 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; + } + + 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_nSize) + { + return NULL; + } + + return (const char *)(m_pData + 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: + // load the catalog from disk (szDirPrefix corresponds to language) + bool Load(const wxChar *szDirPrefix, const wxChar *szName, bool bConvertEncoding = FALSE); + + // get name of the catalog + wxString GetName() const { return m_name; } + + // get the translated string: returns NULL if not found + const wxChar *GetString(const wxChar *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 + wxPluralFormsCalculatorPtr m_pluralFormsCalculator; +}; + +// ---------------------------------------------------------------------------- +// global variables +// ---------------------------------------------------------------------------- + +// the list of the directories to search for message catalog files +static wxArrayString s_searchPrefixes; + +// ============================================================================ +// implementation +// ============================================================================ + +// ---------------------------------------------------------------------------- +// wxMsgCatalogFile class +// ---------------------------------------------------------------------------- + +wxMsgCatalogFile::wxMsgCatalogFile() { - m_pData = NULL; - m_pszName = NULL; + m_pData = NULL; + m_nSize = 0; } -wxMsgCatalog::~wxMsgCatalog() +wxMsgCatalogFile::~wxMsgCatalogFile() { - wxDELETEA(m_pData); - wxDELETEA(m_pszName); + wxDELETEA(m_pData); } // return all directories to search for given prefix @@ -335,9 +1025,20 @@ static wxString GetFullSearchPath(const wxChar *lang) // LC_PATH is a standard env var containing the search path for the .mo // files +#ifndef __WXWINCE__ const wxChar *pszLcPath = wxGetenv(wxT("LC_PATH")); if ( pszLcPath != NULL ) searchPath << GetAllMsgCatalogSubdirs(pszLcPath, lang); +#endif + +#ifdef __UNIX__ + // add some standard ones and the one in the tree where wxWin was installed: + searchPath + << GetAllMsgCatalogSubdirs(wxString(wxGetInstallPrefix()) + wxT("/share/locale"), lang) + << GetAllMsgCatalogSubdirs(wxT("/usr/share/locale"), lang) + << GetAllMsgCatalogSubdirs(wxT("/usr/lib/locale"), lang) + << GetAllMsgCatalogSubdirs(wxT("/usr/local/share/locale"), lang); +#endif // __UNIX__ // then take the current directory // FIXME it should be the directory of the executable @@ -346,20 +1047,17 @@ static wxString GetFullSearchPath(const wxChar *lang) wxGetWorkingDirectory( cwd , sizeof( cwd ) ) ; searchPath << GetAllMsgCatalogSubdirs(cwd, lang); // generic search paths could be somewhere in the system folder preferences -#else +#else // !Mac searchPath << GetAllMsgCatalogSubdirs(wxT("."), lang); - // and finally add some standard ones - searchPath - << GetAllMsgCatalogSubdirs(wxT("/usr/share/locale"), lang) - << GetAllMsgCatalogSubdirs(wxT("/usr/lib/locale"), lang) - << GetAllMsgCatalogSubdirs(wxT("/usr/local/share/locale"), lang); -#endif +#endif // platform + return searchPath; } // open disk file and read in it's contents -bool wxMsgCatalog::Load(const wxChar *szDirPrefix, const wxChar *szName0, bool bConvertEncoding) +bool wxMsgCatalogFile::Load(const wxChar *szDirPrefix, const wxChar *szName0, + wxPluralFormsCalculatorPtr& rPluralFormsCalculator) { /* We need to handle locales like de_AT.iso-8859-1 For this we first chop off the .CHARSET specifier and ignore it. @@ -445,115 +1143,200 @@ bool wxMsgCatalog::Load(const wxChar *szDirPrefix, const wxChar *szName0, bool b Swap(pHeader->ofsOrigTable)); m_pTransTable = (wxMsgTableEntry *)(m_pData + Swap(pHeader->ofsTransTable)); + m_nSize = nSize; + + // now parse catalog's header and try to extract catalog charset and + // plural forms formula from it: - 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); - - if (bConvertEncoding) - ConvertEncoding(); + const char* headerData = StringAtOfs(m_pOrigTable, 0); + if (headerData && headerData[0] == 0) + { + // Extract the charset: + wxString header = wxString::FromAscii(StringAtOfs(m_pTransTable, 0)); + int begin = header.Find(wxT("Content-Type: text/plain; charset=")); + if (begin != wxNOT_FOUND) + { + begin += 34; //strlen("Content-Type: text/plain; charset=") + size_t end = header.find('\n', begin); + if (end != size_t(-1)) + { + m_charset.assign(header, begin, end - begin); + if (m_charset == wxT("CHARSET")) + { + // "CHARSET" is not valid charset, but lazy translator + m_charset.Clear(); + } + } + } + // else: incorrectly filled Content-Type header + + // Extract plural forms: + begin = header.Find(wxT("Plural-Forms:")); + if (begin != wxNOT_FOUND) + { + begin += 13; + size_t end = header.find('\n', begin); + if (end != size_t(-1)) + { + wxString pfs(header, begin, end - begin); + wxPluralFormsCalculator* pCalculator = wxPluralFormsCalculator + ::make(pfs.ToAscii()); + if (pCalculator != 0) + { + rPluralFormsCalculator.reset(pCalculator); + } + else + { + wxLogVerbose(_("Cannot parse Plural-Forms:'%s'"), + pfs.c_str()); + } + } + } + if (rPluralFormsCalculator.get() == NULL) + { + rPluralFormsCalculator.reset(wxPluralFormsCalculator::make()); + } + } // everything is fine - return TRUE; + return true; } -// search for a string -const char *wxMsgCatalog::GetString(const char *szOrig) const +void wxMsgCatalogFile::FillHash(wxMessagesHash& hash, bool convertEncoding) const { - if ( szOrig == NULL ) - return NULL; +#if wxUSE_WCHAR_T + wxCSConv *csConv = NULL; + if ( !!m_charset ) + csConv = new wxCSConv(m_charset); - if ( HasHashTable() ) { // use hash table for lookup if possible - size_t32 nHashVal = GetHash(szOrig); - size_t32 nIndex = nHashVal % m_nHashSize; + wxMBConv& inputConv = csConv ? *((wxMBConv*)csConv) : *wxConvCurrent; +#elif wxUSE_FONTMAP + wxEncodingConverter converter; + if ( convertEncoding ) + { + wxFontEncoding targetEnc = wxFONTENCODING_SYSTEM; + wxFontEncoding enc = wxFontMapper::Get()->CharsetToEncoding(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]; + } + } - size_t32 nIncr = 1 + (nHashVal % (m_nHashSize - 2)); + 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 nStr = Swap(m_pHashTable[nIndex]); - if ( nStr == 0 ) - return NULL; + for (size_t i = 0; i < m_numStrings; i++) + { + const char *data = StringAtOfs(m_pOrigTable, i); +#if wxUSE_WCHAR_T + wxString msgid(data, inputConv); +#else + wxString msgid(data); +#endif - if ( strcmp(szOrig, StringAtOfs(m_pOrigTable, nStr - 1)) == 0 ) { - // work around for BC++ 5.5 bug: without a temp var, the optimizer - // breaks the code and the return value is incorrect - const char *tmp = StringAtOfs(m_pTransTable, nStr - 1); - return tmp; - } + data = StringAtOfs(m_pTransTable, i); + size_t length = Swap(m_pTransTable[i].nLen); + size_t offset = 0; + size_t index = 0; + while (offset < length) + { + wxString msgstr; +#if wxUSE_WCHAR_T + #if wxUSE_UNICODE + msgstr = wxString(data + offset, inputConv); + #else + if ( convertEncoding ) + msgstr = wxString(inputConv.cMB2WC(data + offset), wxConvLocal); + else + msgstr = wxString(data + offset); + #endif +#else // !wxUSE_WCHAR_T + #if wxUSE_FONTMAP + if ( convertEncoding ) + msgstr = wxString(converter.Convert(data + offset)); + else + #endif + msgstr = wxString(data + offset); +#endif // wxUSE_WCHAR_T/!wxUSE_WCHAR_T - if ( nIndex >= m_nHashSize - nIncr) - nIndex -= m_nHashSize - nIncr; - else - nIndex += nIncr; - } - } - 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! - // work around the same BC++ 5.5 bug as above - const char *tmp = StringAtOfs(m_pTransTable, current); - return tmp; - } + if ( !msgstr.empty() ) + { + hash[index == 0 ? msgid : msgid + wxChar(index)] = msgstr; + } + offset += strlen(data + offset) + 1; + ++index; + } } - } - // not found - return NULL; +#if wxUSE_WCHAR_T + delete csConv; +#endif } -void wxMsgCatalog::ConvertEncoding() -{ - // first, find encoding header: - const char *hdr = StringAtOfs(m_pOrigTable, 0); - if ( hdr == NULL || hdr[0] != 0 ) { - // not supported by this catalog, does not have correct header - return; - } - wxString header(StringAtOfs(m_pTransTable, 0)); - wxString charset; - int pos = header.Find(wxT("Content-Type: text/plain; charset=")); - if (pos == wxNOT_FOUND) - return; // incorrectly filled Content-Type header - size_t n = pos + 34; /*strlen("Content-Type: text/plain; charset=")*/ - while (header[n] != wxT('\n')) - charset << header[n++]; +// ---------------------------------------------------------------------------- +// wxMsgCatalog class +// ---------------------------------------------------------------------------- + +bool wxMsgCatalog::Load(const wxChar *szDirPrefix, const wxChar *szName, + bool bConvertEncoding) +{ + wxMsgCatalogFile file; -#if wxUSE_FONTMAP - wxFontEncoding enc = wxTheFontMapper->CharsetToEncoding(charset, FALSE); - if ( enc == wxFONTENCODING_SYSTEM ) - return; // unknown encoding + m_name = szName; - wxFontEncoding targetEnc = wxLocale::GetSystemEncoding(); - if (targetEnc == wxFONTENCODING_SYSTEM) + if ( file.Load(szDirPrefix, szName, m_pluralFormsCalculator) ) { - wxFontEncodingArray a = wxEncodingConverter::GetPlatformEquivalents(enc); - if (a[0] == enc) - return; // no conversion needed, locale uses native encoding - if (a.GetCount() == 0) - return; // we don't know common equiv. under this platform - targetEnc = a[0]; + file.FillHash(m_messages, bConvertEncoding); + return TRUE; } - wxEncodingConverter converter; - converter.Init(enc, targetEnc); - - for (size_t i = 0; i < m_numStrings; i++) - converter.Convert((char*)StringAtOfs(m_pTransTable, i)); -#endif // wxUSE_FONTMAP + return FALSE; } +const wxChar *wxMsgCatalog::GetString(const wxChar *sz, 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(sz) + wxChar(index)); // plural + } + else + { + i = m_messages.find(sz); + } + + if ( i != m_messages.end() ) + { + return i->second.c_str(); + } + else + return NULL; +} // ---------------------------------------------------------------------------- // wxLocale @@ -605,8 +1388,30 @@ bool wxLocale::Init(const wxChar *szName, { // the argument to setlocale() szLocale = szShort; + + wxCHECK_MSG( szLocale, FALSE, _T("no locale to set in wxLocale::Init()") ); + } + +#ifdef __WXWINCE__ + // FIXME: I'm guessing here + wxChar localeName[256]; + int ret = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SLANGUAGE, localeName, + 256); + if (ret != 0) + { + m_pszOldLocale = wxStrdup(localeName); } + else + m_pszOldLocale = NULL; + + // TODO: how to find languageId + // SetLocaleInfo(languageId, SORT_DEFAULT, localeName); +#else m_pszOldLocale = wxSetlocale(LC_ALL, szLocale); + if ( m_pszOldLocale ) + m_pszOldLocale = wxStrdup(m_pszOldLocale); +#endif + if ( m_pszOldLocale == NULL ) wxLogError(_("locale '%s' can not be set."), szLocale); @@ -635,13 +1440,42 @@ bool wxLocale::Init(const wxChar *szName, return bOk; } + +#if defined(__UNIX__) && wxUSE_UNICODE +static wxWCharBuffer wxSetlocaleTryUTF(int c, const wxChar *lc) +{ + wxMB2WXbuf l = wxSetlocale(c, lc); + if ( !l && lc && lc[0] != 0 ) + { + wxString buf(lc); + wxString buf2; + buf2 = buf + wxT(".UTF-8"); + l = wxSetlocale(c, buf2.c_str()); + if ( !l ) + { + buf2 = buf + wxT(".utf-8"); + l = wxSetlocale(c, buf2.c_str()); + } + if ( !l ) + { + buf2 = buf + wxT(".UTF8"); + l = wxSetlocale(c, buf2.c_str()); + } + if ( !l ) + { + buf2 = buf + wxT(".utf8"); + l = wxSetlocale(c, buf2.c_str()); + } + } + return l; +} +#else +#define wxSetlocaleTryUTF(c, lc) wxSetlocale(c, lc) +#endif + bool wxLocale::Init(int language, int flags) { - wxLanguageInfo *info = NULL; int lang = language; - - CreateLanguagesDB(); - if (lang == wxLANGUAGE_DEFAULT) { // auto detect the language @@ -654,17 +1488,7 @@ bool wxLocale::Init(int language, int flags) return FALSE; } - if (lang != wxLANGUAGE_DEFAULT) - { - for (size_t i = 0; i < ms_languagesDB->GetCount(); i++) - { - if (ms_languagesDB->Item(i).Language == lang) - { - info = &ms_languagesDB->Item(i); - break; - } - } - } + const wxLanguageInfo *info = GetLanguageInfo(lang); // Unknown language: if (info == NULL) @@ -676,99 +1500,161 @@ bool wxLocale::Init(int language, int flags) wxString name = info->Description; wxString canonical = info->CanonicalName; wxString locale; - const wxChar *retloc; // Set the locale: -#ifdef __UNIX__ +#if defined(__UNIX__) && !defined(__WXMAC__) if (language == wxLANGUAGE_DEFAULT) locale = wxEmptyString; else locale = info->CanonicalName; - retloc = wxSetlocale(LC_ALL, locale); + wxMB2WXbuf retloc = wxSetlocaleTryUTF(LC_ALL, locale); - if (retloc == NULL) + if ( !retloc ) { // Some C libraries don't like xx_YY form and require xx only - retloc = wxSetlocale(LC_ALL, locale.Mid(0,2)); + retloc = wxSetlocaleTryUTF(LC_ALL, locale.Mid(0,2)); } - if (retloc == NULL) + if ( !retloc ) { // Some C libraries (namely glibc) still use old ISO 639, // so will translate the abbrev for them wxString mid = locale.Mid(0,2); - if (mid == wxT("he")) locale = wxT("iw") + locale.Mid(3); - else if (mid == wxT("id")) locale = wxT("in") + locale.Mid(3); - else if (mid == wxT("yi")) locale = wxT("ji") + locale.Mid(3); - retloc = wxSetlocale(LC_ALL, locale); + if (mid == wxT("he")) + locale = wxT("iw") + locale.Mid(3); + else if (mid == wxT("id")) + locale = wxT("in") + locale.Mid(3); + else if (mid == wxT("yi")) + locale = wxT("ji") + locale.Mid(3); + else if (mid == wxT("nb")) + locale = wxT("no_NO"); + else if (mid == wxT("nn")) + locale = wxT("no_NY"); + + retloc = wxSetlocaleTryUTF(LC_ALL, locale); } - if (retloc == NULL) + if ( !retloc ) { // (This time, we changed locale in previous if-branch, so try again.) // Some C libraries don't like xx_YY form and require xx only - retloc = wxSetlocale(LC_ALL, locale.Mid(0,2)); + retloc = wxSetlocaleTryUTF(LC_ALL, locale.Mid(0,2)); } - if (retloc == NULL) + if ( !retloc ) { wxLogError(wxT("Cannot set locale to '%s'."), locale.c_str()); return FALSE; } #elif defined(__WIN32__) + + #if wxUSE_UNICODE && (defined(__VISUALC__) || defined(__MINGW32__)) + // NB: setlocale() from msvcrt.dll (used by VC++ and Mingw) + // can't set locale to language that can only be written using + // Unicode. Therefore wxSetlocale call failed, but we don't want + // to report it as an error -- so that at least message catalogs + // can be used. Watch for code marked with + // #ifdef SETLOCALE_FAILS_ON_UNICODE_LANGS bellow. + #define SETLOCALE_FAILS_ON_UNICODE_LANGS + #endif + + wxMB2WXbuf retloc = wxT("C"); if (language != wxLANGUAGE_DEFAULT) { if (info->WinLang == 0) { wxLogWarning(wxT("Locale '%s' not supported by OS."), name.c_str()); - retloc = wxT("C"); + // retloc already set to "C" } else { + int codepage = -1; wxUint32 lcid = MAKELCID(MAKELANGID(info->WinLang, info->WinSublang), SORT_DEFAULT); - if (SetThreadLocale(lcid)) - retloc = wxSetlocale(LC_ALL, wxEmptyString); + // FIXME +#ifndef __WXWINCE__ + SetThreadLocale(lcid); +#endif + // NB: we must translate LCID to CRT's setlocale string ourselves, + // because SetThreadLocale does not modify change the + // interpretation of setlocale(LC_ALL, "") call: + wxChar buffer[256]; + buffer[0] = wxT('\0'); + GetLocaleInfo(lcid, LOCALE_SENGLANGUAGE, buffer, 256); + locale << buffer; + if (GetLocaleInfo(lcid, LOCALE_SENGCOUNTRY, buffer, 256) > 0) + locale << wxT("_") << buffer; + if (GetLocaleInfo(lcid, LOCALE_IDEFAULTANSICODEPAGE, buffer, 256) > 0) + { + codepage = wxAtoi(buffer); + if (codepage != 0) + locale << wxT(".") << buffer; + } + if (locale.IsEmpty()) + { + wxLogLastError(wxT("SetThreadLocale")); + wxLogError(wxT("Cannot set locale to language %s."), name.c_str()); + return FALSE; + } else { - // Windows9X doesn't support SetThreadLocale, so we must - // translate LCID to CRT's setlocale string ourselves - locale.Empty(); - if (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) - { - wxChar buffer[256]; - buffer[0] = wxT('\0'); - GetLocaleInfo(lcid, LOCALE_SENGLANGUAGE, buffer, 256); - locale << buffer; - if (GetLocaleInfo(lcid, LOCALE_SENGCOUNTRY, buffer, 256) > 0) - locale << wxT("_") << buffer; - } - if (locale.IsEmpty()) + // FIXME +#ifndef __WXWINCE__ + retloc = wxSetlocale(LC_ALL, locale); +#endif +#ifdef SETLOCALE_FAILS_ON_UNICODE_LANGS + if (codepage == 0 && (const wxChar*)retloc == NULL) { - wxLogLastError(wxT("SetThreadLocale")); - wxLogError(wxT("Cannot set locale to language %s."), name.c_str()); - return FALSE; + retloc = wxT("C"); } - else - retloc = wxSetlocale(LC_ALL, locale); +#endif } } } else + { + // FIXME +#ifndef __WXWINCE__ retloc = wxSetlocale(LC_ALL, wxEmptyString); +#else + retloc = NULL; +#endif +#ifdef SETLOCALE_FAILS_ON_UNICODE_LANGS + if ((const wxChar*)retloc == NULL) + { + wxChar buffer[16]; + if (GetLocaleInfo(LOCALE_USER_DEFAULT, + LOCALE_IDEFAULTANSICODEPAGE, buffer, 16) > 0 && + wxStrcmp(buffer, wxT("0")) == 0) + { + retloc = wxT("C"); + } + } +#endif + } - if (retloc == NULL) + if ( !retloc ) { wxLogError(wxT("Cannot set locale to language %s."), name.c_str()); return FALSE; } -#elif defined(__WXMAC__) - retloc = wxSetlocale(LC_ALL , wxEmptyString); +#elif defined(__WXMAC__) || defined(__WXPM__) + wxMB2WXbuf retloc = wxSetlocale(LC_ALL , wxEmptyString); #else return FALSE; + #define WX_NO_LOCALE_SUPPORT #endif - return Init(name, canonical, wxString(retloc), - (flags & wxLOCALE_LOAD_DEFAULT) != 0, - (flags & wxLOCALE_CONV_ENCODING) != 0); +#ifndef WX_NO_LOCALE_SUPPORT + wxChar *szLocale = retloc ? wxStrdup(retloc) : NULL; + bool ret = Init(name, canonical, retloc, + (flags & wxLOCALE_LOAD_DEFAULT) != 0, + (flags & wxLOCALE_CONV_ENCODING) != 0); + free(szLocale); + + if ( ret ) + m_language = lang; + + return ret; +#endif } @@ -790,7 +1676,7 @@ void wxLocale::AddCatalogLookupPathPrefix(const wxString& prefix) size_t i = 0, count = ms_languagesDB->GetCount(); -#if defined(__UNIX__) +#if defined(__UNIX__) && !defined(__WXMAC__) // first get the string identifying the language from the environment wxString langFull; if (!wxGetEnv(wxT("LC_ALL"), &langFull) && @@ -854,10 +1740,16 @@ void wxLocale::AddCatalogLookupPathPrefix(const wxString& prefix) wxString lang; if ( langOrig == wxT("iw")) lang = _T("he"); - else if ( langOrig == wxT("in") ) + else if (langOrig == wxT("in")) lang = wxT("id"); - else if ( langOrig == wxT("ji") ) + else if (langOrig == wxT("ji")) lang = wxT("yi"); + else if (langOrig == wxT("no_NO")) + lang = wxT("nb_NO"); + else if (langOrig == wxT("no_NY")) + lang = wxT("nn_NO"); + else if (langOrig == wxT("no")) + lang = wxT("nb_NO"); else lang = langOrig; @@ -913,279 +1805,279 @@ void wxLocale::AddCatalogLookupPathPrefix(const wxString& prefix) } } #elif defined(__WXMAC__) - char* lc = NULL ; + const wxChar * lc = NULL ; long lang = GetScriptVariable( smSystemScript, smScriptLang) ; switch( GetScriptManagerVariable( smRegionCode ) ) { case verUS : - lc = "en_US" ; + lc = wxT("en_US") ; break ; case verFrance : - lc = "fr_FR" ; + lc = wxT("fr_FR") ; break ; case verBritain : - lc = "en_GB" ; + lc = wxT("en_GB") ; break ; case verGermany : - lc = "de_DE" ; + lc = wxT("de_DE") ; break ; case verItaly : - lc = "it_IT" ; + lc = wxT("it_IT") ; break ; case verNetherlands : - lc = "nl_NL" ; + lc = wxT("nl_NL") ; break ; case verFlemish : - lc = "nl_BE" ; + lc = wxT("nl_BE") ; break ; case verSweden : - lc = "sv_SE" ; + lc = wxT("sv_SE" ); break ; case verSpain : - lc = "es_ES" ; + lc = wxT("es_ES" ); break ; case verDenmark : - lc = "da_DK" ; + lc = wxT("da_DK") ; break ; case verPortugal : - lc = "pt_PT" ; + lc = wxT("pt_PT") ; break ; case verFrCanada: - lc = "fr_CA" ; + lc = wxT("fr_CA") ; break ; case verNorway: - lc = "no_NO" ; + lc = wxT("nb_NO") ; break ; case verIsrael: - lc = "iw_IL" ; + lc = wxT("iw_IL") ; break ; case verJapan: - lc = "ja_JP" ; + lc = wxT("ja_JP") ; break ; case verAustralia: - lc = "en_AU" ; + lc = wxT("en_AU") ; break ; case verArabic: - lc = "ar" ; + lc = wxT("ar") ; break ; case verFinland: - lc = "fi_FI" ; + lc = wxT("fi_FI") ; break ; case verFrSwiss: - lc = "fr_CH" ; + lc = wxT("fr_CH") ; break ; case verGrSwiss: - lc = "de_CH" ; + lc = wxT("de_CH") ; break ; case verGreece: - lc = "el_GR" ; + lc = wxT("el_GR") ; break ; case verIceland: - lc = "is_IS" ; + lc = wxT("is_IS") ; break ; case verMalta: - lc = "mt_MT" ; + lc = wxT("mt_MT") ; break ; case verCyprus: // _CY is not part of wx, so we have to translate according to the system language if ( lang == langGreek ) { - lc = "el_GR" ; + lc = wxT("el_GR") ; } else if ( lang == langTurkish ) { - lc = "tr_TR" ; + lc = wxT("tr_TR") ; } break ; case verTurkey: - lc = "tr_TR" ; + lc = wxT("tr_TR") ; break ; case verYugoCroatian: - lc = "hr_HR" ; + lc = wxT("hr_HR") ; break ; case verIndiaHindi: - lc = "hi_IN" ; + lc = wxT("hi_IN") ; break ; case verPakistanUrdu: - lc = "ur_PK" ; + lc = wxT("ur_PK") ; break ; case verTurkishModified: - lc = "tr_TR" ; + lc = wxT("tr_TR") ; break ; case verItalianSwiss: - lc = "it_CH" ; + lc = wxT("it_CH") ; break ; case verInternational: - lc = "en" ; + lc = wxT("en") ; break ; case verRomania: - lc = "ro_RO" ; + lc = wxT("ro_RO") ; break ; case verGreecePoly: - lc = "el_GR" ; + lc = wxT("el_GR") ; break ; case verLithuania: - lc = "lt_LT" ; + lc = wxT("lt_LT") ; break ; case verPoland: - lc = "pl_PL" ; + lc = wxT("pl_PL") ; break ; case verMagyar : case verHungary: - lc = "hu_HU" ; + lc = wxT("hu_HU") ; break ; case verEstonia: - lc = "et_EE" ; + lc = wxT("et_EE") ; break ; case verLatvia: - lc = "lv_LV" ; + lc = wxT("lv_LV") ; break ; case verSami: // not known break ; case verFaroeIsl: - lc = "fo_FO" ; + lc = wxT("fo_FO") ; break ; case verIran: - lc = "fa_IR" ; + lc = wxT("fa_IR") ; break ; case verRussia: - lc = "ru_RU" ; + lc = wxT("ru_RU") ; break ; case verIreland: - lc = "ga_IE" ; + lc = wxT("ga_IE") ; break ; case verKorea: - lc = "ko_KR" ; + lc = wxT("ko_KR") ; break ; case verChina: - lc = "zh_CN" ; + lc = wxT("zh_CN") ; break ; case verTaiwan: - lc = "zh_TW" ; + lc = wxT("zh_TW") ; break ; case verThailand: - lc = "th_TH" ; + lc = wxT("th_TH") ; break ; case verCzech: - lc = "cs_CZ" ; + lc = wxT("cs_CZ") ; break ; case verSlovak: - lc = "sk_SK" ; + lc = wxT("sk_SK") ; break ; case verBengali: - lc = "bn" ; + lc = wxT("bn") ; break ; case verByeloRussian: - lc = "be_BY" ; + lc = wxT("be_BY") ; break ; case verUkraine: - lc = "uk_UA" ; + lc = wxT("uk_UA") ; break ; case verGreeceAlt: - lc = "el_GR" ; + lc = wxT("el_GR") ; break ; case verSerbian: - lc = "sr_YU" ; + lc = wxT("sr_YU") ; break ; case verSlovenian: - lc = "sl_SI" ; + lc = wxT("sl_SI") ; break ; case verMacedonian: - lc = "mk_MK" ; + lc = wxT("mk_MK") ; break ; case verCroatia: - lc = "hr_HR" ; + lc = wxT("hr_HR") ; break ; case verBrazil: - lc = "pt_BR " ; + lc = wxT("pt_BR ") ; break ; case verBulgaria: - lc = "bg_BG" ; + lc = wxT("bg_BG") ; break ; case verCatalonia: - lc = "ca_ES" ; + lc = wxT("ca_ES") ; break ; case verScottishGaelic: - lc = "gd" ; + lc = wxT("gd") ; break ; case verManxGaelic: - lc = "gv" ; + lc = wxT("gv") ; break ; case verBreton: - lc = "br" ; + lc = wxT("br") ; break ; case verNunavut: - lc = "iu_CA" ; + lc = wxT("iu_CA") ; break ; case verWelsh: - lc = "cy" ; + lc = wxT("cy") ; break ; case verIrishGaelicScript: - lc = "ga_IE" ; + lc = wxT("ga_IE") ; break ; case verEngCanada: - lc = "en_CA" ; + lc = wxT("en_CA") ; break ; case verBhutan: - lc = "dz_BT" ; + lc = wxT("dz_BT") ; break ; case verArmenian: - lc = "hy_AM" ; + lc = wxT("hy_AM") ; break ; case verGeorgian: - lc = "ka_GE" ; + lc = wxT("ka_GE") ; break ; case verSpLatinAmerica: - lc = "es_AR" ; + lc = wxT("es_AR") ; break ; case verTonga: - lc = "to_TO" ; + lc = wxT("to_TO" ); break ; case verFrenchUniversal: - lc = "fr_FR" ; + lc = wxT("fr_FR") ; break ; case verAustria: - lc = "de_AT" ; + lc = wxT("de_AT") ; break ; case verGujarati: - lc = "gu_IN" ; + lc = wxT("gu_IN") ; break ; case verPunjabi: - lc = "pa" ; + lc = wxT("pa") ; break ; case verIndiaUrdu: - lc = "ur_IN" ; + lc = wxT("ur_IN") ; break ; case verVietnam: - lc = "vi_VN" ; + lc = wxT("vi_VN") ; break ; case verFrBelgium: - lc = "fr_BE" ; + lc = wxT("fr_BE") ; break ; case verUzbek: - lc = "uz_UZ" ; + lc = wxT("uz_UZ") ; break ; case verSingapore: - lc = "zh_SG" ; + lc = wxT("zh_SG") ; break ; case verNynorsk: - lc = "nn_NO" ; + lc = wxT("nn_NO") ; break ; case verAfrikaans: - lc = "af_ZA" ; + lc = wxT("af_ZA") ; break ; case verEsperanto: - lc = "eo" ; + lc = wxT("eo") ; break ; case verMarathi: - lc = "mr_IN" ; + lc = wxT("mr_IN") ; break ; case verTibetan: - lc = "bo" ; + lc = wxT("bo") ; break ; case verNepal: - lc = "ne_NP" ; + lc = wxT("ne_NP") ; break ; case verGreenland: - lc = "kl_GL" ; + lc = wxT("kl_GL") ; break ; default : break ; @@ -1197,7 +2089,7 @@ void wxLocale::AddCatalogLookupPathPrefix(const wxString& prefix) break; } } - + #elif defined(__WIN32__) LCID lcid = GetUserDefaultLCID(); if ( lcid != 0 ) @@ -1251,12 +2143,29 @@ wxString wxLocale::GetSystemEncodingName() // to Unix98) char *oldLocale = strdup(setlocale(LC_CTYPE, NULL)); setlocale(LC_CTYPE, ""); - char *alang = nl_langinfo(CODESET); + const char *alang = nl_langinfo(CODESET); setlocale(LC_CTYPE, oldLocale); free(oldLocale); - if (alang) + + if ( alang ) { - encname = wxConvLibc.cMB2WX(alang); + // 7 bit ASCII encoding has several alternative names which we should + // recognize to avoid warnings about unrecognized encoding on each + // program startup + + // nl_langinfo() under Solaris returns 646 by default which stands for + // ISO-646, i.e. 7 bit ASCII + // + // and recent glibc call it ANSI_X3.4-1968... + if ( strcmp(alang, "646") == 0 || + strcmp(alang, "ANSI_X3.4-1968") == 0 ) + { + encname = _T("US-ASCII"); + } + else + { + encname = wxString::FromAscii( alang ); + } } else #endif // HAVE_LANGINFO_H @@ -1264,24 +2173,24 @@ wxString wxLocale::GetSystemEncodingName() // 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) - wxChar *lang = wxGetenv(wxT("LC_ALL")); - wxChar *dot = lang ? wxStrchr(lang, wxT('.')) : (wxChar *)NULL; + char *lang = getenv( "LC_ALL"); + char *dot = lang ? strchr(lang, '.') : (char *)NULL; if (!dot) { - lang = wxGetenv(wxT("LC_CTYPE")); + lang = getenv( "LC_CTYPE" ); if ( lang ) - dot = wxStrchr(lang, wxT('.')); + dot = strchr(lang, '.' ); } if (!dot) { - lang = wxGetenv(wxT("LANG")); + lang = getenv( "LANG"); if ( lang ) - dot = wxStrchr(lang, wxT('.')); + dot = strchr(lang, '.'); } if ( dot ) { - encname = dot+1; + encname = wxString::FromAscii( dot+1 ); } } #endif // Win32/Unix @@ -1324,23 +2233,115 @@ wxFontEncoding wxLocale::GetSystemEncoding() wxString encname = GetSystemEncodingName(); if ( !encname.empty() ) { - return wxTheFontMapper-> + wxFontEncoding enc = wxFontMapper::Get()-> CharsetToEncoding(encname, FALSE /* not interactive */); + + // on some modern Linux systems (RedHat 8) the default system locale + // is UTF8 -- but it isn't supported by wxGTK in ANSI build at all so + // don't even try to use it in this case +#if !wxUSE_UNICODE + if ( enc == wxFONTENCODING_UTF8 ) + { + // the most similar supported encoding... + enc = wxFONTENCODING_ISO8859_1; + } +#endif // !wxUSE_UNICODE + + // this should probably be considered as a bug in CharsetToEncoding(): + // it shouldn't return wxFONTENCODING_DEFAULT at all - but it does it + // for US-ASCII charset + // + // we, OTOH, definitely shouldn't return it as it doesn't make sense at + // all (which encoding is it?) + if ( enc != wxFONTENCODING_DEFAULT ) + { + return enc; + } + //else: return wxFONTENCODING_SYSTEM below } #endif // Win32/Unix return wxFONTENCODING_SYSTEM; } -/*static*/ void wxLocale::AddLanguage(const wxLanguageInfo& info) +/* static */ +void wxLocale::AddLanguage(const wxLanguageInfo& info) { CreateLanguagesDB(); ms_languagesDB->Add(info); } +/* static */ +const wxLanguageInfo *wxLocale::GetLanguageInfo(int lang) +{ + CreateLanguagesDB(); + + const size_t count = ms_languagesDB->GetCount(); + for ( size_t i = 0; i < count; i++ ) + { + if ( ms_languagesDB->Item(i).Language == lang ) + { + return &ms_languagesDB->Item(i); + } + } + + 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(_T('_'))) == 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 becauce the entry for the default + // country always appears first in ms_languagesDB + if ( !infoRet ) + infoRet = info; + } + } + + return infoRet; +} + wxString wxLocale::GetSysName() const { + // FIXME +#ifndef __WXWINCE__ return wxSetlocale(LC_ALL, NULL); +#else + return wxEmptyString; +#endif } // clean up @@ -1356,81 +2357,94 @@ wxLocale::~wxLocale() // restore old locale wxSetLocale(m_pOldLocale); + // FIXME +#ifndef __WXWINCE__ wxSetlocale(LC_ALL, m_pszOldLocale); +#endif + free((wxChar *)m_pszOldLocale); // const_cast } // get the translation of given string in current locale -const wxMB2WXbuf wxLocale::GetString(const wxChar *szOrigString, - const wxChar *szDomain) const -{ - if ( wxIsEmpty(szOrigString) ) - return szDomain; - - const char *pszTrans = NULL; -#if wxUSE_UNICODE - const wxWX2MBbuf szOrgString = wxConvCurrent->cWX2MB(szOrigString); -#else // ANSI - #define szOrgString szOrigString -#endif // Unicode/ANSI - - wxMsgCatalog *pMsgCat; - if ( szDomain != NULL ) { - pMsgCat = FindCatalog(szDomain); - - // 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; - } - } +const wxChar *wxLocale::GetString(const wxChar *szOrigString, + const wxChar *szDomain) const +{ + return GetString(szOrigString, szOrigString, size_t(-1), szDomain); +} - if ( pszTrans == NULL ) { -#ifdef __WXDEBUG__ - if ( !NoTransErr::Suppress() ) { - NoTransErr noTransErr; +const wxChar *wxLocale::GetString(const wxChar *szOrigString, + const wxChar *szOrigString2, + size_t n, + const wxChar *szDomain) const +{ + if ( wxIsEmpty(szOrigString) ) + return wxEmptyString; - if ( szDomain != NULL ) - { - wxLogDebug(_T("string '%s' not found in domain '%s' for locale '%s'."), - szOrigString, szDomain, m_strLocale.c_str()); - } - else - { - wxLogDebug(_T("string '%s' not found in locale '%s'."), - szOrigString, m_strLocale.c_str()); - } + const wxChar *pszTrans = NULL; + wxMsgCatalog *pMsgCat; + + if ( szDomain != NULL ) + { + pMsgCat = FindCatalog(szDomain); + + // does the catalog exist? + if ( pMsgCat != NULL ) + pszTrans = pMsgCat->GetString(szOrigString, n); + } + else + { + // search in all domains + for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext ) + { + pszTrans = pMsgCat->GetString(szOrigString, n); + if ( pszTrans != NULL ) // take the first found + break; + } } -#endif // __WXDEBUG__ - return (wxMB2WXbuf)(szOrigString); - } + if ( pszTrans == NULL ) + { +#ifdef __WXDEBUG__ + if ( !NoTransErr::Suppress() ) + { + NoTransErr noTransErr; + + if ( szDomain != NULL ) + { + wxLogTrace(_T("i18n"), + _T("string '%s'[%d] not found in domain '%s' for locale '%s'."), + szOrigString, n, szDomain, m_strLocale.c_str()); - // or preferably wxCSConv(charset).cMB2WX(pszTrans) or something, a macro - // similar to wxConvertMB2WX could be written for that + } + else + { + wxLogTrace(_T("i18n"), + _T("string '%s'[%d] not found in locale '%s'."), + szOrigString, n, m_strLocale.c_str()); + } + } +#endif // __WXDEBUG__ - return wxConvertMB2WX(pszTrans); + if (n == size_t(-1)) + return szOrigString; + else + return n == 1 ? szOrigString : szOrigString2; + } - // undo the hack from the beginning of this function - #undef szOrgString + return pszTrans; } // 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; - } + // 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; + return NULL; } // check if the given catalog is loaded @@ -1460,6 +2474,62 @@ bool wxLocale::AddCatalog(const wxChar *szDomain) } } +// ---------------------------------------------------------------------------- +// accessors for locale-dependent data +// ---------------------------------------------------------------------------- + +#if 0 + +#ifdef __WXMSW__ + +/* static */ +wxString wxLocale::GetInfo(wxLocaleInfo index) +{ + wxString str; + wxChar buffer[256]; + size_t count; + buffer[0] = wxT('\0'); + switch (index) + { + case wxSYS_DECIMAL_SEPARATOR: + count = ::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, buffer, 256); + if (!count) + str << "."; + else + str << buffer; + break; + case wxSYS_LIST_SEPARATOR: + count = ::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SLIST, buffer, 256); + if (!count) + str << ","; + else + str << buffer; + break; + case wxSYS_LEADING_ZERO: // 0 means no leading zero, 1 means leading zero + count = ::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ILZERO, buffer, 256); + if (!count) + str << "0"; + else + str << buffer; + break; + default: + wxFAIL_MSG("Unknown System String !"); + } + return str; +} + +#else // !__WXMSW__ + +/* static */ +wxString wxLocale::GetInfo(wxLocaleInfo index, wxLocaleCategory) +{ + return wxEmptyString; +} + +#endif // __WXMSW__/!__WXMSW__ + +#endif // 0 + // ---------------------------------------------------------------------------- // global functions and variables // ---------------------------------------------------------------------------- @@ -2005,7 +3075,7 @@ IMPLEMENT_DYNAMIC_CLASS(wxLocaleModule, wxModule) #define LNG(wxlang, canonical, winlang, winsublang, desc) \ info.Language = wxlang; \ info.CanonicalName = wxT(canonical); \ - info.Description = desc; \ + info.Description = wxT(desc); \ SETWINLANG(info, winlang, winsublang) \ AddLanguage(info); @@ -2014,7 +3084,7 @@ void wxLocale::InitLanguagesDB() wxLanguageInfo info; wxStringTokenizer tkn; - LNG(wxLANGUAGE_ABKHAZIAN, "ab" , 0 , 0 , "Abkhazian") + LNG(wxLANGUAGE_ABKHAZIAN, "ab" , 0 , 0 , "Abkhazian") LNG(wxLANGUAGE_AFAR, "aa" , 0 , 0 , "Afar") LNG(wxLANGUAGE_AFRIKAANS, "af_ZA", LANG_AFRIKAANS , SUBLANG_DEFAULT , "Afrikaans") LNG(wxLANGUAGE_ALBANIAN, "sq_AL", LANG_ALBANIAN , SUBLANG_DEFAULT , "Albanian") @@ -2057,11 +3127,11 @@ void wxLocale::InitLanguagesDB() LNG(wxLANGUAGE_CATALAN, "ca_ES", LANG_CATALAN , SUBLANG_DEFAULT , "Catalan") LNG(wxLANGUAGE_CHINESE, "zh_CN", LANG_CHINESE , SUBLANG_DEFAULT , "Chinese") LNG(wxLANGUAGE_CHINESE_SIMPLIFIED, "zh_CN", LANG_CHINESE , SUBLANG_CHINESE_SIMPLIFIED , "Chinese (Simplified)") - LNG(wxLANGUAGE_CHINESE_TRADITIONAL, "zh_CN", LANG_CHINESE , SUBLANG_CHINESE_TRADITIONAL , "Chinese (Traditional)") + LNG(wxLANGUAGE_CHINESE_TRADITIONAL, "zh_TW", LANG_CHINESE , SUBLANG_CHINESE_TRADITIONAL , "Chinese (Traditional)") LNG(wxLANGUAGE_CHINESE_HONGKONG, "zh_HK", LANG_CHINESE , SUBLANG_CHINESE_HONGKONG , "Chinese (Hongkong)") LNG(wxLANGUAGE_CHINESE_MACAU, "zh_MO", LANG_CHINESE , SUBLANG_CHINESE_MACAU , "Chinese (Macau)") LNG(wxLANGUAGE_CHINESE_SINGAPORE, "zh_SG", LANG_CHINESE , SUBLANG_CHINESE_SINGAPORE , "Chinese (Singapore)") - LNG(wxLANGUAGE_CHINESE_TAIWAN, "zh_TW", 0 , 0 , "Chinese (Taiwan)") + LNG(wxLANGUAGE_CHINESE_TAIWAN, "zh_TW", LANG_CHINESE , SUBLANG_CHINESE_TRADITIONAL , "Chinese (Taiwan)") LNG(wxLANGUAGE_CORSICAN, "co" , 0 , 0 , "Corsican") LNG(wxLANGUAGE_CROATIAN, "hr_HR", LANG_CROATIAN , SUBLANG_DEFAULT , "Croatian") LNG(wxLANGUAGE_CZECH, "cs_CZ", LANG_CZECH , SUBLANG_DEFAULT , "Czech") @@ -2155,7 +3225,7 @@ void wxLocale::InitLanguagesDB() LNG(wxLANGUAGE_NAURU, "na" , 0 , 0 , "Nauru") LNG(wxLANGUAGE_NEPALI, "ne" , LANG_NEPALI , SUBLANG_DEFAULT , "Nepali") LNG(wxLANGUAGE_NEPALI_INDIA, "ne_IN", LANG_NEPALI , SUBLANG_NEPALI_INDIA , "Nepali (India)") - LNG(wxLANGUAGE_NORWEGIAN_BOKMAL, "no_NO", LANG_NORWEGIAN , SUBLANG_NORWEGIAN_BOKMAL , "Norwegian (Bokmal)") + LNG(wxLANGUAGE_NORWEGIAN_BOKMAL, "nb_NO", LANG_NORWEGIAN , SUBLANG_NORWEGIAN_BOKMAL , "Norwegian (Bokmal)") LNG(wxLANGUAGE_NORWEGIAN_NYNORSK, "nn_NO", LANG_NORWEGIAN , SUBLANG_NORWEGIAN_NYNORSK , "Norwegian (Nynorsk)") LNG(wxLANGUAGE_OCCITAN, "oc" , 0 , 0 , "Occitan") LNG(wxLANGUAGE_ORIYA, "or" , LANG_ORIYA , SUBLANG_DEFAULT , "Oriya") @@ -2242,7 +3312,7 @@ void wxLocale::InitLanguagesDB() LNG(wxLANGUAGE_YORUBA, "yo" , 0 , 0 , "Yoruba") LNG(wxLANGUAGE_ZHUANG, "za" , 0 , 0 , "Zhuang") LNG(wxLANGUAGE_ZULU, "zu" , 0 , 0 , "Zulu") - + }; #undef LNG