+// get everything else (including the leading '_')
+static inline wxString ExtractNotLang(const wxString& langFull)
+{
+    return langFull.Mid(LEN_LANG);
+}
+
+#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)