X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/1c193821a999730fbf6a9bea83763f37daae68f1..924fe7dabe68c324b6e5005bbe1c4119447867f3:/src/common/intl.cpp diff --git a/src/common/intl.cpp b/src/common/intl.cpp index aef903c92b..317cc06e04 100644 --- a/src/common/intl.cpp +++ b/src/common/intl.cpp @@ -1,8 +1,9 @@ ///////////////////////////////////////////////////////////////////////////// // Name: src/common/intl.cpp -// Purpose: Internationalization and localisation for wxWindows +// Purpose: Internationalization and localisation for wxWidgets // Author: Vadim Zeitlin -// Modified by: +// Modified by: Michael N. Filippov +// (2003/09/30 - PluralForms support) // Created: 29/01/98 // RCS-ID: $Id$ // Copyright: (c) 1998 Vadim Zeitlin @@ -17,10 +18,22 @@ // headers // ---------------------------------------------------------------------------- -#ifdef __GNUG__ +#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) #pragma implementation "intl.h" #endif +#if defined(__BORLAND__) && !defined(__WXDEBUG__) + // There's a bug in Borland's compiler that breaks wxLocale with -O2, + // so make sure that flag is not used for this file: + #pragma option -O1 +#endif + +#ifdef __EMX__ +// The following define is needed by Innotek's libc to +// make the definition of struct localeconv available. +#define __INTERNAL_DEFS +#endif + // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" @@ -42,7 +55,7 @@ #include #endif -// wxWindows +// wxWidgets #ifndef WX_PRECOMP #include "wx/string.h" #include "wx/intl.h" @@ -64,6 +77,7 @@ #include "wx/fontmap.h" #include "wx/encconv.h" #include "wx/hashmap.h" +#include "wx/ptr_scpd.h" #if defined(__WXMAC__) #include "wx/mac/private.h" // includes mac headers @@ -145,6 +159,713 @@ static inline wxString ExtractNotLang(const wxString& langFull) #endif // __UNIX__ +// ---------------------------------------------------------------------------- +// Plural forms parser +// ---------------------------------------------------------------------------- + +/* + 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: + 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; +}; + + +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; +}; + +wxPluralFormsScanner::wxPluralFormsScanner(const char* s) : m_s(s) +{ + nextToken(); +} + +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: + wxPluralFormsNode *m_p; +}; + +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; + +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; + } +} + + +void wxPluralFormsNode::setNode(size_t i, wxPluralFormsNode* n) +{ + m_nodes[i].reset(n); +} + +wxPluralFormsNode* wxPluralFormsNode::releaseNode(size_t i) +{ + return m_nodes[i].release(); +} + +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); + + ~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) +{ + m_nplurals = nplurals; + m_plural.reset(plural); +} + +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(); +}; + +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(); +} + +wxPluralFormsNode* wxPluralFormsParser::equalityExpression() +{ + 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(); +} + +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. @@ -162,10 +883,12 @@ public: ~wxMsgCatalogFile(); // load the catalog from disk (szDirPrefix corresponds to language) - bool Load(const wxChar *szDirPrefix, const wxChar *szName); + bool Load(const wxChar *szDirPrefix, const wxChar *szName, + wxPluralFormsCalculatorPtr& rPluralFormsCalculator); // fills the hash with string-translation pairs - void FillHash(wxMessagesHash& hash, bool convertEncoding) const; + void FillHash(wxMessagesHash& hash, const wxString& msgIdCharset, + bool convertEncoding) const; private: // this implementation is binary compatible with GNU gettext() version 0.10 @@ -200,6 +923,8 @@ private: 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 { @@ -215,13 +940,13 @@ private: // 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); } - wxString GetCharset() const; - bool m_bSwapped; // wrong endianness? DECLARE_NO_COPY_CLASS(wxMsgCatalogFile) @@ -239,13 +964,14 @@ class wxMsgCatalog { public: // load the catalog from disk (szDirPrefix corresponds to language) - bool Load(const wxChar *szDirPrefix, const wxChar *szName, bool bConvertEncoding = FALSE); + bool Load(const wxChar *szDirPrefix, const wxChar *szName, + const wxChar *msgIdCharset = NULL, 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) const; + 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; @@ -253,6 +979,7 @@ public: private: wxMessagesHash m_messages; // all messages in the catalog wxString m_name; // name of the domain + wxPluralFormsCalculatorPtr m_pluralFormsCalculator; }; // ---------------------------------------------------------------------------- @@ -343,7 +1070,8 @@ static wxString GetFullSearchPath(const wxChar *lang) } // open disk file and read in it's contents -bool wxMsgCatalogFile::Load(const wxChar *szDirPrefix, const wxChar *szName0) +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. @@ -431,44 +1159,87 @@ bool wxMsgCatalogFile::Load(const wxChar *szDirPrefix, const wxChar *szName0) Swap(pHeader->ofsTransTable)); m_nSize = nSize; + // 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: + 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; } -void wxMsgCatalogFile::FillHash(wxMessagesHash& hash, bool convertEncoding) const +void wxMsgCatalogFile::FillHash(wxMessagesHash& hash, + const wxString& msgIdCharset, + bool convertEncoding) const { - wxString charset = GetCharset(); - #if wxUSE_WCHAR_T wxCSConv *csConv = NULL; - if ( !!charset ) - csConv = new wxCSConv(charset); + if ( !!m_charset ) + csConv = new wxCSConv(m_charset); wxMBConv& inputConv = csConv ? *((wxMBConv*)csConv) : *wxConvCurrent; - for (size_t i = 0; i < m_numStrings; i++) - { - wxString key(StringAtOfs(m_pOrigTable, i), inputConv); - - #if wxUSE_UNICODE - hash[key] = wxString(StringAtOfs(m_pTransTable, i), inputConv); - #else - if ( convertEncoding ) - hash[key] = - wxString(inputConv.cMB2WC(StringAtOfs(m_pTransTable, i)), - wxConvLocal); - else - hash[key] = StringAtOfs(m_pTransTable, i); - #endif - } + wxCSConv *sourceConv = NULL; + if ( !msgIdCharset.empty() && (m_charset != msgIdCharset) ) + sourceConv = new wxCSConv(msgIdCharset); - delete csConv; -#else // !wxUSE_WCHAR_T - #if wxUSE_FONTMAP +#elif wxUSE_FONTMAP + wxASSERT_MSG( msgIdCharset == NULL, + _T("non-ASCII msgid languages only supported if wxUSE_WCHAR_T=1") ); + + wxEncodingConverter converter; if ( convertEncoding ) { wxFontEncoding targetEnc = wxFONTENCODING_SYSTEM; - wxFontEncoding enc = wxFontMapper::Get()->CharsetToEncoding(charset, FALSE); + wxFontEncoding enc = wxFontMapper::Get()->CharsetToEncoding(m_charset, FALSE); if ( enc == wxFONTENCODING_SYSTEM ) { convertEncoding = FALSE; // unknown encoding @@ -491,85 +1262,105 @@ void wxMsgCatalogFile::FillHash(wxMessagesHash& hash, bool convertEncoding) cons if ( convertEncoding ) { - wxEncodingConverter converter; converter.Init(enc, targetEnc); - - for (size_t i = 0; i < m_numStrings; i++) - { - wxString key(StringAtOfs(m_pOrigTable, i)); - hash[key] = - converter.Convert(wxString(StringAtOfs(m_pTransTable, i))); - } - } - } - - if ( !convertEncoding ) - #endif // wxUSE_FONTMAP/!wxUSE_FONTMAP - { - for (size_t i = 0; i < m_numStrings; i++) - { - wxString key(StringAtOfs(m_pOrigTable, i)); - hash[key] = StringAtOfs(m_pTransTable, i); } } #endif // wxUSE_WCHAR_T/!wxUSE_WCHAR_T -} + (void)convertEncoding; // get rid of warnings about unused parameter -wxString wxMsgCatalogFile::GetCharset() const -{ - // 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 wxEmptyString; - } - - wxString header = wxString::FromAscii( StringAtOfs(m_pTransTable, 0)); - wxString charset; - int pos = header.Find(wxT("Content-Type: text/plain; charset=")); - if ( pos == wxNOT_FOUND ) + for (size_t i = 0; i < m_numStrings; i++) { - // incorrectly filled Content-Type header - return wxEmptyString; - } + const char *data = StringAtOfs(m_pOrigTable, i); +#if wxUSE_UNICODE + wxString msgid(data, inputConv); +#else + wxString msgid; +#if wxUSE_WCHAR_T + if ( convertEncoding && sourceConv ) + msgid = wxString(inputConv.cMB2WC(data), *sourceConv); + else +#endif + msgid = data; +#endif // wxUSE_UNICODE - size_t n = pos + 34; /*strlen("Content-Type: text/plain; charset=")*/ - while ( header[n] != wxT('\n') ) - charset << header[n++]; + 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 ( charset == wxT("CHARSET") ) - { - // "CHARSET" is not valid charset, but lazy translator - return wxEmptyString; + if ( !msgstr.empty() ) + { + hash[index == 0 ? msgid : msgid + wxChar(index)] = msgstr; + } + offset += strlen(data + offset) + 1; + ++index; + } } - return charset; +#if wxUSE_WCHAR_T + delete sourceConv; + delete csConv; +#endif } + // ---------------------------------------------------------------------------- // wxMsgCatalog class // ---------------------------------------------------------------------------- bool wxMsgCatalog::Load(const wxChar *szDirPrefix, const wxChar *szName, - bool bConvertEncoding) + const wxChar *msgIdCharset, bool bConvertEncoding) { wxMsgCatalogFile file; m_name = szName; - if ( file.Load(szDirPrefix, szName) ) + if ( file.Load(szDirPrefix, szName, m_pluralFormsCalculator) ) { - file.FillHash(m_messages, bConvertEncoding); + file.FillHash(m_messages, msgIdCharset, bConvertEncoding); return TRUE; } return FALSE; } -const wxChar *wxMsgCatalog::GetString(const wxChar *sz) const +const wxChar *wxMsgCatalog::GetString(const wxChar *sz, size_t n) const { - wxMessagesHash::const_iterator i = m_messages.find(sz); + 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(); @@ -604,11 +1395,12 @@ wxLanguageInfoArray *wxLocale::ms_languagesDB = NULL; } -wxLocale::wxLocale() +void wxLocale::DoCommonInit() { m_pszOldLocale = NULL; m_pMsgCat = NULL; m_language = wxLANGUAGE_UNKNOWN; + m_initialized = false; } // NB: this function has (desired) side effect of changing current locale @@ -618,6 +1410,10 @@ bool wxLocale::Init(const wxChar *szName, bool bLoadDefault, bool bConvertEncoding) { + wxASSERT_MSG( !m_initialized, + _T("you can't call wxLocale::Init more than once") ); + + m_initialized = true; m_strLocale = szName; m_strShort = szShort; m_bConvertEncoding = bConvertEncoding; @@ -639,7 +1435,7 @@ bool wxLocale::Init(const wxChar *szName, 256); if (ret != 0) { - m_pszOldLocale = wxStrdup(localeName); + m_pszOldLocale = wxStrdup(localeName); } else m_pszOldLocale = NULL; @@ -647,7 +1443,11 @@ bool wxLocale::Init(const wxChar *szName, // TODO: how to find languageId // SetLocaleInfo(languageId, SORT_DEFAULT, localeName); #else - m_pszOldLocale = wxStrdup(wxSetlocale(LC_ALL, szLocale)); + wxMB2WXbuf oldLocale = wxSetlocale(LC_ALL, szLocale); + if ( oldLocale ) + m_pszOldLocale = wxStrdup(oldLocale); + else + m_pszOldLocale = NULL; #endif if ( m_pszOldLocale == NULL ) @@ -658,7 +1458,7 @@ bool wxLocale::Init(const wxChar *szName, if ( m_strShort.IsEmpty() ) { // FIXME I don't know how these 2 letter abbreviations are formed, // this wild guess is surely wrong - if ( szLocale[0] ) + if ( szLocale && szLocale[0] ) { m_strShort += (wxChar)wxTolower(szLocale[0]); if ( szLocale[1] ) @@ -669,7 +1469,7 @@ bool wxLocale::Init(const wxChar *szName, // save the old locale to be able to restore it later m_pOldLocale = wxSetLocale(this); - // load the default catalog with wxWindows standard messages + // load the default catalog with wxWidgets standard messages m_pMsgCat = NULL; bool bOk = TRUE; if ( bLoadDefault ) @@ -683,11 +1483,27 @@ bool wxLocale::Init(const wxChar *szName, static wxWCharBuffer wxSetlocaleTryUTF(int c, const wxChar *lc) { wxMB2WXbuf l = wxSetlocale(c, lc); - if ( lc && lc[0] != 0 && !l ) + if ( !l && lc && lc[0] != 0 ) { wxString buf(lc); - buf += wxT(".utf8"); - l = wxSetlocale(c, buf.c_str()); + 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; } @@ -777,7 +1593,10 @@ bool wxLocale::Init(int language, int flags) // #ifdef SETLOCALE_FAILS_ON_UNICODE_LANGS bellow. #define SETLOCALE_FAILS_ON_UNICODE_LANGS #endif - + +#if !wxUSE_UNICODE + const +#endif wxMB2WXbuf retloc = wxT("C"); if (language != wxLANGUAGE_DEFAULT) { @@ -788,7 +1607,11 @@ bool wxLocale::Init(int language, int flags) } else { - int codepage = -1; + int codepage + #ifdef SETLOCALE_FAILS_ON_UNICODE_LANGS + = -1 + #endif + ; wxUint32 lcid = MAKELCID(MAKELANGID(info->WinLang, info->WinSublang), SORT_DEFAULT); // FIXME @@ -872,7 +1695,7 @@ bool wxLocale::Init(int language, int flags) (flags & wxLOCALE_CONV_ENCODING) != 0); free(szLocale); - if ( ret ) + if (IsOk()) // setlocale() succeeded m_language = lang; return ret; @@ -1347,7 +2170,7 @@ void wxLocale::AddCatalogLookupPathPrefix(const wxString& prefix) // 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 he encoding info +// reflects the way different systems provide the encoding info /* static */ wxString wxLocale::GetSystemEncodingName() @@ -1365,7 +2188,7 @@ 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); @@ -1379,8 +2202,18 @@ wxString wxLocale::GetSystemEncodingName() // 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 ) + // + // HP-UX uses HP-Roman8 cset which is not the same as ASCII (see RFC + // 1345 for its definition) but must be recognized as otherwise HP + // users get a warning about it on each program startup, so handle it + // here -- but it would be obviously better to add real supprot to it, + // of course! + if ( strcmp(alang, "646") == 0 + || strcmp(alang, "ANSI_X3.4-1968") == 0 +#ifdef __HPUX__ + || strcmp(alang, "roman8") == 0 +#endif // __HPUX__ + ) { encname = _T("US-ASCII"); } @@ -1426,7 +2259,7 @@ wxFontEncoding wxLocale::GetSystemEncoding() #if defined(__WIN32__) && !defined(__WXMICROWIN__) UINT codepage = ::GetACP(); - // wxWindows only knows about CP1250-1257, 932, 936, 949, 950 + // wxWidgets only knows about CP1250-1257, 932, 936, 949, 950 if ( codepage >= 1250 && codepage <= 1257 ) { return (wxFontEncoding)(wxFONTENCODING_CP1250 + codepage - 1250); @@ -1451,6 +2284,14 @@ wxFontEncoding wxLocale::GetSystemEncoding() { return wxFONTENCODING_CP950; } +#elif defined(__WXMAC__) + TextEncoding encoding = 0 ; +#if TARGET_CARBON + encoding = CFStringGetSystemEncoding() ; +#else + UpgradeScriptInfoToTextEncoding ( smSystemScript , kTextLanguageDontCare , kTextRegionDontCare , NULL , &encoding ) ; +#endif + return wxMacGetFontEncFromSystemEnc( encoding ) ; #elif defined(__UNIX_LIKE__) && wxUSE_FONTMAP wxString encname = GetSystemEncodingName(); if ( !encname.empty() ) @@ -1498,6 +2339,11 @@ 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++ ) { @@ -1589,9 +2435,17 @@ wxLocale::~wxLocale() // get the translation of given string in current locale const wxChar *wxLocale::GetString(const wxChar *szOrigString, const wxChar *szDomain) const +{ + return GetString(szOrigString, szOrigString, size_t(-1), szDomain); +} + +const wxChar *wxLocale::GetString(const wxChar *szOrigString, + const wxChar *szOrigString2, + size_t n, + const wxChar *szDomain) const { if ( wxIsEmpty(szOrigString) ) - return _T(""); + return wxEmptyString; const wxChar *pszTrans = NULL; wxMsgCatalog *pMsgCat; @@ -1602,14 +2456,14 @@ const wxChar *wxLocale::GetString(const wxChar *szOrigString, // does the catalog exist? if ( pMsgCat != NULL ) - pszTrans = pMsgCat->GetString(szOrigString); + pszTrans = pMsgCat->GetString(szOrigString, n); } else { // search in all domains for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext ) { - pszTrans = pMsgCat->GetString(szOrigString); + pszTrans = pMsgCat->GetString(szOrigString, n); if ( pszTrans != NULL ) // take the first found break; } @@ -1625,19 +2479,24 @@ const wxChar *wxLocale::GetString(const wxChar *szOrigString, if ( szDomain != NULL ) { wxLogTrace(_T("i18n"), - _T("string '%s' not found in domain '%s' for locale '%s'."), - szOrigString, szDomain, m_strLocale.c_str()); + _T("string '%s'[%lu] not found in domain '%s' for locale '%s'."), + szOrigString, (unsigned long)n, + szDomain, m_strLocale.c_str()); + } else { wxLogTrace(_T("i18n"), - _T("string '%s' not found in locale '%s'."), - szOrigString, m_strLocale.c_str()); + _T("string '%s'[%lu] not found in locale '%s'."), + szOrigString, (unsigned long)n, m_strLocale.c_str()); } } #endif // __WXDEBUG__ - return szOrigString; + if (n == size_t(-1)) + return szOrigString; + else + return n == 1 ? szOrigString : szOrigString2; } return pszTrans; @@ -1665,22 +2524,47 @@ bool wxLocale::IsLoaded(const wxChar *szDomain) const // add a catalog to our linked list bool wxLocale::AddCatalog(const wxChar *szDomain) +{ + return AddCatalog(szDomain, wxLANGUAGE_ENGLISH, NULL); +} + +// add a catalog to our linked list +bool wxLocale::AddCatalog(const wxChar *szDomain, + wxLanguage msgIdLanguage, + const wxChar *msgIdCharset) + { wxMsgCatalog *pMsgCat = new wxMsgCatalog; - if ( pMsgCat->Load(m_strShort, szDomain, m_bConvertEncoding) ) { + 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; + return true; } else { // don't add it because it couldn't be loaded anyway delete pMsgCat; - return FALSE; + // 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 (m_language == msgIdLanguage) + return true; + + // 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 && + msgIdLangInfo->CanonicalName.Mid(0, 2) == m_strShort.Mid(0, 2) ) + { + return true; + } + + return false; } } @@ -1688,12 +2572,10 @@ bool wxLocale::AddCatalog(const wxChar *szDomain) // accessors for locale-dependent data // ---------------------------------------------------------------------------- -#if 0 - #ifdef __WXMSW__ /* static */ -wxString wxLocale::GetInfo(wxLocaleInfo index) +wxString wxLocale::GetInfo(wxLocaleInfo index, wxLocaleCategory WXUNUSED(cat)) { wxString str; wxChar buffer[256]; @@ -1701,29 +2583,31 @@ wxString wxLocale::GetInfo(wxLocaleInfo index) buffer[0] = wxT('\0'); switch (index) { - case wxSYS_DECIMAL_SEPARATOR: + case wxLOCALE_DECIMAL_POINT: count = ::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, buffer, 256); if (!count) - str << "."; + str << wxT("."); else str << buffer; break; +#if 0 case wxSYS_LIST_SEPARATOR: count = ::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SLIST, buffer, 256); if (!count) - str << ","; + str << wxT(","); 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"; + str << wxT("0"); else str << buffer; break; +#endif default: - wxFAIL_MSG("Unknown System String !"); + wxFAIL_MSG(wxT("Unknown System String !")); } return str; } @@ -1731,15 +2615,42 @@ wxString wxLocale::GetInfo(wxLocaleInfo index) #else // !__WXMSW__ /* static */ -wxString wxLocale::GetInfo(wxLocaleInfo index, wxLocaleCategory) +wxString wxLocale::GetInfo(wxLocaleInfo index, wxLocaleCategory cat) { - return wxEmptyString; + struct lconv *locale_info = localeconv(); + switch (cat) + { + case wxLOCALE_CAT_NUMBER: + switch (index) + { + case wxLOCALE_THOUSANDS_SEP: + return wxString(locale_info->thousands_sep, + *wxConvCurrent); + case wxLOCALE_DECIMAL_POINT: + return wxString(locale_info->decimal_point, + *wxConvCurrent); + default: + return wxEmptyString; + } + case wxLOCALE_CAT_MONEY: + switch (index) + { + case wxLOCALE_THOUSANDS_SEP: + return wxString(locale_info->mon_thousands_sep, + *wxConvCurrent); + case wxLOCALE_DECIMAL_POINT: + return wxString(locale_info->mon_decimal_point, + *wxConvCurrent); + default: + return wxEmptyString; + } + default: + return wxEmptyString; + } } #endif // __WXMSW__/!__WXMSW__ -#endif // 0 - // ---------------------------------------------------------------------------- // global functions and variables // ---------------------------------------------------------------------------- @@ -2335,7 +3246,7 @@ void wxLocale::InitLanguagesDB() LNG(wxLANGUAGE_BURMESE, "my" , 0 , 0 , "Burmese") LNG(wxLANGUAGE_CAMBODIAN, "km" , 0 , 0 , "Cambodian") LNG(wxLANGUAGE_CATALAN, "ca_ES", LANG_CATALAN , SUBLANG_DEFAULT , "Catalan") - LNG(wxLANGUAGE_CHINESE, "zh_CN", LANG_CHINESE , SUBLANG_DEFAULT , "Chinese") + LNG(wxLANGUAGE_CHINESE, "zh_TW", LANG_CHINESE , SUBLANG_DEFAULT , "Chinese") LNG(wxLANGUAGE_CHINESE_SIMPLIFIED, "zh_CN", LANG_CHINESE , SUBLANG_CHINESE_SIMPLIFIED , "Chinese (Simplified)") 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)") @@ -2492,7 +3403,7 @@ void wxLocale::InitLanguagesDB() LNG(wxLANGUAGE_SWAHILI, "sw_KE", LANG_SWAHILI , SUBLANG_DEFAULT , "Swahili") LNG(wxLANGUAGE_SWEDISH, "sv_SE", LANG_SWEDISH , SUBLANG_SWEDISH , "Swedish") LNG(wxLANGUAGE_SWEDISH_FINLAND, "sv_FI", LANG_SWEDISH , SUBLANG_SWEDISH_FINLAND , "Swedish (Finland)") - LNG(wxLANGUAGE_TAGALOG, "tl" , 0 , 0 , "Tagalog") + LNG(wxLANGUAGE_TAGALOG, "tl_PH", 0 , 0 , "Tagalog") LNG(wxLANGUAGE_TAJIK, "tg" , 0 , 0 , "Tajik") LNG(wxLANGUAGE_TAMIL, "ta" , LANG_TAMIL , SUBLANG_DEFAULT , "Tamil") LNG(wxLANGUAGE_TATAR, "tt" , LANG_TATAR , SUBLANG_DEFAULT , "Tatar")