Files
wxWidgets/src/common/translation.cpp

2126 lines
60 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// Name: src/common/translation.cpp
// Purpose: Internationalization and localisation for wxWidgets
// Author: Vadim Zeitlin, Vaclav Slavik,
// Michael N. Filippov <michael@idisys.iae.nsk.su>
// (2003/09/30 - PluralForms support)
// Created: 2010-04-23
// Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
// ============================================================================
// declaration
// ============================================================================
// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------
// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#if wxUSE_INTL
#ifndef WX_PRECOMP
#include "wx/dynarray.h"
#include "wx/string.h"
#include "wx/intl.h"
#include "wx/log.h"
#include "wx/utils.h"
#include "wx/hashmap.h"
#include "wx/module.h"
#endif // WX_PRECOMP
// standard headers
#include <ctype.h>
#include <stdlib.h>
#include "wx/arrstr.h"
#include "wx/dir.h"
#include "wx/file.h"
#include "wx/filename.h"
#include "wx/tokenzr.h"
#include "wx/fontmap.h"
#include "wx/scopedptr.h"
#include "wx/stdpaths.h"
#include "wx/private/threadinfo.h"
#ifdef __WINDOWS__
#include "wx/dynlib.h"
#include "wx/scopedarray.h"
#include "wx/msw/wrapwin.h"
#include "wx/msw/missing.h"
#endif
#ifdef __WXOSX__
#include "wx/osx/core/cfstring.h"
#include <CoreFoundation/CFBundle.h>
#include <CoreFoundation/CFLocale.h>
#endif
// ----------------------------------------------------------------------------
// simple types
// ----------------------------------------------------------------------------
typedef wxUint32 size_t32;
// ----------------------------------------------------------------------------
// constants
// ----------------------------------------------------------------------------
// magic number identifying the .mo format file
const size_t32 MSGCATALOG_MAGIC = 0x950412de;
const size_t32 MSGCATALOG_MAGIC_SW = 0xde120495;
#define TRACE_I18N wxS("i18n")
// ============================================================================
// implementation
// ============================================================================
namespace
{
#if !wxUSE_UNICODE
// We need to keep track of (char*) msgids in non-Unicode legacy builds. Instead
// of making the public wxMsgCatalog and wxTranslationsLoader APIs ugly, we
// store them in this global map.
wxStringToStringHashMap gs_msgIdCharset;
#endif
// ----------------------------------------------------------------------------
// Platform specific helpers
// ----------------------------------------------------------------------------
#if wxUSE_LOG_TRACE
void LogTraceArray(const char *prefix, const wxArrayString& arr)
{
wxLogTrace(TRACE_I18N, "%s: [%s]", prefix, wxJoin(arr, ','));
}
void LogTraceLargeArray(const wxString& prefix, const wxArrayString& arr)
{
wxLogTrace(TRACE_I18N, "%s:", prefix);
for ( wxArrayString::const_iterator i = arr.begin(); i != arr.end(); ++i )
wxLogTrace(TRACE_I18N, " %s", *i);
}
#else // !wxUSE_LOG_TRACE
#define LogTraceArray(prefix, arr)
#define LogTraceLargeArray(prefix, arr)
#endif // wxUSE_LOG_TRACE/!wxUSE_LOG_TRACE
// Use locale-based detection as a fallback
wxString GetPreferredUILanguageFallback(const wxArrayString& WXUNUSED(available))
{
const wxString lang = wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage());
wxLogTrace(TRACE_I18N, " - obtained best language from locale: %s", lang);
return lang;
}
#ifdef __WINDOWS__
wxString GetPreferredUILanguage(const wxArrayString& available)
{
typedef BOOL (WINAPI *GetUserPreferredUILanguages_t)(DWORD, PULONG, PWSTR, PULONG);
static GetUserPreferredUILanguages_t s_pfnGetUserPreferredUILanguages = NULL;
static bool s_initDone = false;
if ( !s_initDone )
{
wxLoadedDLL dllKernel32("kernel32.dll");
wxDL_INIT_FUNC(s_pfn, GetUserPreferredUILanguages, dllKernel32);
s_initDone = true;
}
if ( s_pfnGetUserPreferredUILanguages )
{
ULONG numLangs;
ULONG bufferSize = 0;
if ( (*s_pfnGetUserPreferredUILanguages)(MUI_LANGUAGE_NAME,
&numLangs,
NULL,
&bufferSize) )
{
wxScopedArray<WCHAR> langs(bufferSize);
if ( (*s_pfnGetUserPreferredUILanguages)(MUI_LANGUAGE_NAME,
&numLangs,
langs.get(),
&bufferSize) )
{
wxArrayString preferred;
WCHAR *buf = langs.get();
for ( unsigned i = 0; i < numLangs; i++ )
{
const wxString lang(buf);
preferred.push_back(lang);
buf += lang.length() + 1;
}
LogTraceArray(" - system preferred languages", preferred);
for ( wxArrayString::const_iterator j = preferred.begin();
j != preferred.end();
++j )
{
wxString lang(*j);
lang.Replace("-", "_");
if ( available.Index(lang, /*bCase=*/false) != wxNOT_FOUND )
return lang;
size_t pos = lang.find('_');
if ( pos != wxString::npos )
{
lang = lang.substr(0, pos);
if ( available.Index(lang, /*bCase=*/false) != wxNOT_FOUND )
return lang;
}
}
}
}
}
return GetPreferredUILanguageFallback(available);
}
#elif defined(__WXOSX__)
#if wxUSE_LOG_TRACE
void LogTraceArray(const char *prefix, CFArrayRef arr)
{
wxString s;
const unsigned count = CFArrayGetCount(arr);
if ( count )
{
s += wxCFStringRef::AsString((CFStringRef)CFArrayGetValueAtIndex(arr, 0));
for ( unsigned i = 1 ; i < count; i++ )
s += "," + wxCFStringRef::AsString((CFStringRef)CFArrayGetValueAtIndex(arr, i));
}
wxLogTrace(TRACE_I18N, "%s: [%s]", prefix, s);
}
#endif // wxUSE_LOG_TRACE
wxString GetPreferredUILanguage(const wxArrayString& available)
{
wxStringToStringHashMap availableNormalized;
wxCFRef<CFMutableArrayRef> availableArr(
CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks));
for ( wxArrayString::const_iterator i = available.begin();
i != available.end();
++i )
{
wxString lang(*i);
wxCFStringRef code_wx(*i);
wxCFStringRef code_norm(
CFLocaleCreateCanonicalLanguageIdentifierFromString(kCFAllocatorDefault, code_wx));
CFArrayAppendValue(availableArr, code_norm);
availableNormalized[code_norm.AsString()] = *i;
}
LogTraceArray(" - normalized available list", availableArr);
wxCFRef<CFArrayRef> prefArr(
CFBundleCopyLocalizationsForPreferences(availableArr, NULL));
LogTraceArray(" - system preferred languages", prefArr);
unsigned prefArrLength = CFArrayGetCount(prefArr);
if ( prefArrLength > 0 )
{
// Lookup the name in 'available' by index -- we need to get the
// original value corresponding to the normalized one chosen.
wxString lang(wxCFStringRef::AsString((CFStringRef)CFArrayGetValueAtIndex(prefArr, 0)));
wxStringToStringHashMap::const_iterator i = availableNormalized.find(lang);
if ( i == availableNormalized.end() )
return lang;
else
return i->second;
}
return GetPreferredUILanguageFallback(available);
}
#else
// When the preferred UI language is determined, the LANGUAGE environment
// variable is the primary source of preference.
// http://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html
//
// The LANGUAGE variable may contain a colon separated list of language
// codes in the order of preference.
// http://www.gnu.org/software/gettext/manual/html_node/The-LANGUAGE-variable.html
wxString GetPreferredUILanguage(const wxArrayString& available)
{
wxString languageFromEnv;
wxArrayString preferred;
if ( wxGetEnv("LANGUAGE", &languageFromEnv) )
{
wxStringTokenizer tknzr(languageFromEnv, ":");
while ( tknzr.HasMoreTokens() )
{
const wxString tok = tknzr.GetNextToken();
if ( const wxLanguageInfo *li = wxLocale::FindLanguageInfo(tok) )
{
preferred.push_back(li->CanonicalName);
}
}
if ( preferred.empty() )
{
wxLogTrace(TRACE_I18N, " - LANGUAGE was set, but it didn't contain any languages recognized by the system");
}
}
LogTraceArray(" - preferred languages from environment", preferred);
for ( wxArrayString::const_iterator j = preferred.begin();
j != preferred.end();
++j )
{
wxString lang(*j);
if ( available.Index(lang) != wxNOT_FOUND )
return lang;
size_t pos = lang.find('_');
if ( pos != wxString::npos )
{
lang = lang.substr(0, pos);
if ( available.Index(lang) != wxNOT_FOUND )
return lang;
}
}
return GetPreferredUILanguageFallback(available);
}
#endif
} // anonymous namespace
// ----------------------------------------------------------------------------
// 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 t) { m_type = t; }
// 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((unsigned char) *m_s))
{
++m_s;
}
if (*m_s == 0)
{
type = wxPluralFormsToken::T_EOF;
}
else if (isdigit((unsigned char) *m_s))
{
wxPluralFormsToken::Number number = *m_s++ - '0';
while (isdigit((unsigned char) *m_s))
{
number = number * 10 + (*m_s++ - '0');
}
m_token.setNumber(number);
type = wxPluralFormsToken::T_NUMBER;
}
else if (isalpha((unsigned char) *m_s))
{
const char* begin = m_s++;
while (isalnum((unsigned char) *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& t) : m_token(t) {}
const wxPluralFormsToken& token() const { return m_token; }
const wxPluralFormsNode* node(unsigned i) const
{ return m_nodes[i].get(); }
void setNode(unsigned i, wxPluralFormsNode* n);
wxPluralFormsNode* releaseNode(unsigned 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(unsigned i, wxPluralFormsNode* n)
{
m_nodes[i].reset(n);
}
wxPluralFormsNode* wxPluralFormsNode::releaseNode(unsigned 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);
private:
wxPluralFormsToken::Number m_nplurals;
wxPluralFormsNodePtr m_plural;
};
wxDEFINE_SCOPED_PTR(wxPluralFormsCalculator, wxPluralFormsCalculatorPtr)
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.
//
// This is a "low-level" class and is used only by wxMsgCatalog
// NOTE: for the documentation of the binary catalog (.MO) files refer to
// the GNU gettext manual:
// http://www.gnu.org/software/autoconf/manual/gettext/MO-Files.html
// ----------------------------------------------------------------------------
class wxMsgCatalogFile
{
public:
typedef wxScopedCharBuffer DataBuffer;
// ctor & dtor
wxMsgCatalogFile();
~wxMsgCatalogFile();
// load the catalog from disk
bool LoadFile(const wxString& filename,
wxPluralFormsCalculatorPtr& rPluralFormsCalculator);
bool LoadData(const DataBuffer& data,
wxPluralFormsCalculatorPtr& rPluralFormsCalculator);
// fills the hash with string-translation pairs
bool FillHash(wxStringToStringHashMap& hash, const wxString& domain) const;
// return the charset of the strings in this catalog or empty string if
// none/unknown
wxString GetCharset() const { return m_charset; }
private:
// this implementation is binary compatible with GNU gettext() version 0.10
// an entry in the string table
struct wxMsgTableEntry
{
size_t32 nLen; // length of the string
size_t32 ofsString; // pointer to the string
};
// header of a .mo file
struct wxMsgCatalogHeader
{
size_t32 magic, // offset +00: magic id
revision, // +04: revision
numStrings; // +08: number of strings in the file
size_t32 ofsOrigTable, // +0C: start of original string table
ofsTransTable; // +10: start of translated string table
size_t32 nHashSize, // +14: hash table size
ofsHashTable; // +18: offset of hash table start
};
// all data is stored here
DataBuffer m_data;
// data description
size_t32 m_numStrings; // number of strings in this domain
const
wxMsgTableEntry *m_pOrigTable, // pointer to original strings
*m_pTransTable; // translated
wxString m_charset; // from the message catalog header
// swap the 2 halves of 32 bit integer if needed
size_t32 Swap(size_t32 ui) const
{
return m_bSwapped ? (ui << 24) | ((ui & 0xff00) << 8) |
((ui >> 8) & 0xff00) | (ui >> 24)
: ui;
}
const char* StringAtOfs(const wxMsgTableEntry* pTable, size_t32 n) const
{
const wxMsgTableEntry * const ent = pTable + n;
// this check could fail for a corrupt message catalog
size_t32 ofsString = Swap(ent->ofsString);
if ( ofsString + Swap(ent->nLen) > m_data.length())
{
return NULL;
}
return m_data.data() + ofsString;
}
bool m_bSwapped; // wrong endianness?
wxDECLARE_NO_COPY_CLASS(wxMsgCatalogFile);
};
// ----------------------------------------------------------------------------
// wxMsgCatalogFile class
// ----------------------------------------------------------------------------
wxMsgCatalogFile::wxMsgCatalogFile()
{
}
wxMsgCatalogFile::~wxMsgCatalogFile()
{
}
// open disk file and read in it's contents
bool wxMsgCatalogFile::LoadFile(const wxString& filename,
wxPluralFormsCalculatorPtr& rPluralFormsCalculator)
{
wxFile fileMsg(filename);
if ( !fileMsg.IsOpened() )
return false;
// get the file size (assume it is less than 4GB...)
wxFileOffset lenFile = fileMsg.Length();
if ( lenFile == wxInvalidOffset )
return false;
size_t nSize = wx_truncate_cast(size_t, lenFile);
wxASSERT_MSG( nSize == lenFile + size_t(0), wxS("message catalog bigger than 4GB?") );
wxMemoryBuffer filedata;
// read the whole file in memory
if ( fileMsg.Read(filedata.GetWriteBuf(nSize), nSize) != lenFile )
return false;
filedata.UngetWriteBuf(nSize);
bool ok = LoadData
(
DataBuffer::CreateOwned((char*)filedata.release(), nSize),
rPluralFormsCalculator
);
if ( !ok )
{
wxLogWarning(_("'%s' is not a valid message catalog."), filename.c_str());
return false;
}
return true;
}
bool wxMsgCatalogFile::LoadData(const DataBuffer& data,
wxPluralFormsCalculatorPtr& rPluralFormsCalculator)
{
// examine header
bool bValid = data.length() > sizeof(wxMsgCatalogHeader);
const wxMsgCatalogHeader* pHeader = reinterpret_cast<const wxMsgCatalogHeader*>(data.data());
if ( bValid ) {
// we'll have to swap all the integers if it's true
m_bSwapped = pHeader->magic == MSGCATALOG_MAGIC_SW;
// check the magic number
bValid = m_bSwapped || pHeader->magic == MSGCATALOG_MAGIC;
}
if ( !bValid ) {
// it's either too short or has incorrect magic number
wxLogWarning(_("Invalid message catalog."));
return false;
}
m_data = data;
// initialize
m_numStrings = Swap(pHeader->numStrings);
m_pOrigTable = reinterpret_cast<const wxMsgTableEntry*>(data.data() +
Swap(pHeader->ofsOrigTable));
m_pTransTable = reinterpret_cast<const wxMsgTableEntry*>(data.data() +
Swap(pHeader->ofsTransTable));
// now parse catalog's header and try to extract catalog charset and
// plural forms formula from it:
const char* headerData = StringAtOfs(m_pOrigTable, 0);
if ( headerData && headerData[0] == '\0' )
{
// Extract the charset:
const char * const header = StringAtOfs(m_pTransTable, 0);
const char *
cset = strstr(header, "Content-Type: text/plain; charset=");
if ( cset )
{
cset += 34; // strlen("Content-Type: text/plain; charset=")
const char * const csetEnd = strchr(cset, '\n');
if ( csetEnd )
{
m_charset = wxString(cset, csetEnd - cset);
if ( m_charset == wxS("CHARSET") )
{
// "CHARSET" is not valid charset, but lazy translator
m_charset.clear();
}
}
}
// else: incorrectly filled Content-Type header
// Extract plural forms:
const char * plurals = strstr(header, "Plural-Forms:");
if ( plurals )
{
plurals += 13; // strlen("Plural-Forms:")
const char * const pluralsEnd = strchr(plurals, '\n');
if ( pluralsEnd )
{
const size_t pluralsLen = pluralsEnd - plurals;
wxCharBuffer buf(pluralsLen);
strncpy(buf.data(), plurals, pluralsLen);
wxPluralFormsCalculator * const
pCalculator = wxPluralFormsCalculator::make(buf);
if ( pCalculator )
{
rPluralFormsCalculator.reset(pCalculator);
}
else
{
wxLogVerbose(_("Failed to parse Plural-Forms: '%s'"),
buf.data());
}
}
}
if ( !rPluralFormsCalculator.get() )
rPluralFormsCalculator.reset(wxPluralFormsCalculator::make());
}
// everything is fine
return true;
}
bool wxMsgCatalogFile::FillHash(wxStringToStringHashMap& hash,
const wxString& domain) const
{
wxUnusedVar(domain); // silence warning in Unicode build
// conversion to use to convert catalog strings to the GUI encoding
wxMBConv *inputConv = NULL;
wxScopedPtr<wxMBConv> inputConvPtr; // just to delete inputConv if needed
if ( !m_charset.empty() )
{
#if !wxUSE_UNICODE && wxUSE_FONTMAP
// determine if we need any conversion at all
wxFontEncoding encCat = wxFontMapperBase::GetEncodingFromName(m_charset);
if ( encCat != wxLocale::GetSystemEncoding() )
#endif
{
inputConv = new wxCSConv(m_charset);
// As we allocated it ourselves, we need to delete it, so ensure
// this happens.
inputConvPtr.reset(inputConv);
}
}
else // no need or not possible to convert the encoding
{
#if wxUSE_UNICODE
// we must somehow convert the narrow strings in the message catalog to
// wide strings, so use the default conversion if we have no charset
inputConv = wxConvCurrent;
#endif
}
#if !wxUSE_UNICODE
wxString msgIdCharset = gs_msgIdCharset[domain];
// conversion to apply to msgid strings before looking them up: we only
// need it if the msgids are neither in 7 bit ASCII nor in the same
// encoding as the catalog
wxScopedPtr<wxCSConv> sourceConv;
if ( !msgIdCharset.empty() && (msgIdCharset != m_charset) )
sourceConv.reset(new wxCSConv(msgIdCharset));
#endif // !wxUSE_UNICODE
for (size_t32 i = 0; i < m_numStrings; i++)
{
const char *data = StringAtOfs(m_pOrigTable, i);
if (!data)
return false; // may happen for invalid MO files
wxString msgid;
#if wxUSE_UNICODE
msgid = wxString(data, *inputConv);
#else // ASCII
if ( inputConv && sourceConv )
msgid = wxString(inputConv->cMB2WC(data), *sourceConv);
else
msgid = data;
#endif // wxUSE_UNICODE
data = StringAtOfs(m_pTransTable, i);
if (!data)
return false; // may happen for invalid MO files
size_t length = Swap(m_pTransTable[i].nLen);
size_t offset = 0;
size_t index = 0;
while (offset < length)
{
const char * const str = data + offset;
wxString msgstr;
#if wxUSE_UNICODE
msgstr = wxString(str, *inputConv);
#else
if ( inputConv )
msgstr = wxString(inputConv->cMB2WC(str), *wxConvUI);
else
msgstr = str;
#endif // wxUSE_UNICODE/!wxUSE_UNICODE
if ( !msgstr.empty() )
{
hash[index == 0 ? msgid : msgid + wxChar(index)] = msgstr;
}
// skip this string
// IMPORTANT: accesses to the 'data' pointer are valid only for
// the first 'length+1' bytes (GNU specs says that the
// final NUL is not counted in length); using wxStrnlen()
// we make sure we don't access memory beyond the valid range
// (which otherwise may happen for invalid MO files):
offset += wxStrnlen(str, length - offset) + 1;
++index;
}
}
return true;
}
// ----------------------------------------------------------------------------
// wxMsgCatalog class
// ----------------------------------------------------------------------------
#if !wxUSE_UNICODE
wxMsgCatalog::~wxMsgCatalog()
{
if ( m_conv )
{
if ( wxConvUI == m_conv )
{
// we only change wxConvUI if it points to wxConvLocal so we reset
// it back to it too
wxConvUI = &wxConvLocal;
}
delete m_conv;
}
}
#endif // !wxUSE_UNICODE
/* static */
wxMsgCatalog *wxMsgCatalog::CreateFromFile(const wxString& filename,
const wxString& domain)
{
wxScopedPtr<wxMsgCatalog> cat(new wxMsgCatalog(domain));
wxMsgCatalogFile file;
if ( !file.LoadFile(filename, cat->m_pluralFormsCalculator) )
return NULL;
if ( !file.FillHash(cat->m_messages, domain) )
return NULL;
return cat.release();
}
/* static */
wxMsgCatalog *wxMsgCatalog::CreateFromData(const wxScopedCharBuffer& data,
const wxString& domain)
{
wxScopedPtr<wxMsgCatalog> cat(new wxMsgCatalog(domain));
wxMsgCatalogFile file;
if ( !file.LoadData(data, cat->m_pluralFormsCalculator) )
return NULL;
if ( !file.FillHash(cat->m_messages, domain) )
return NULL;
return cat.release();
}
const wxString *wxMsgCatalog::GetString(const wxString& str, unsigned n, const wxString& context) const
{
int index = 0;
if (n != UINT_MAX)
{
index = m_pluralFormsCalculator->evaluate(n);
}
wxStringToStringHashMap::const_iterator i;
if (index != 0)
{
if (context.IsEmpty())
i = m_messages.find(wxString(str) + wxChar(index)); // plural, no context
else
i = m_messages.find(wxString(context) + wxString('\x04') + wxString(str) + wxChar(index)); // plural, context
}
else
{
if (context.IsEmpty())
i = m_messages.find(str); // no context
else
i = m_messages.find(wxString(context) + wxString('\x04') + wxString(str)); // context
}
if ( i != m_messages.end() )
{
return &i->second;
}
else
return NULL;
}
// ----------------------------------------------------------------------------
// wxTranslations
// ----------------------------------------------------------------------------
namespace
{
wxTranslations *gs_translations = NULL;
bool gs_translationsOwned = false;
} // anonymous namespace
/*static*/
wxTranslations *wxTranslations::Get()
{
return gs_translations;
}
/*static*/
void wxTranslations::Set(wxTranslations *t)
{
if ( gs_translationsOwned )
delete gs_translations;
gs_translations = t;
gs_translationsOwned = true;
}
/*static*/
void wxTranslations::SetNonOwned(wxTranslations *t)
{
if ( gs_translationsOwned )
delete gs_translations;
gs_translations = t;
gs_translationsOwned = false;
}
wxTranslations::wxTranslations()
{
m_pMsgCat = NULL;
m_loader = new wxFileTranslationsLoader;
}
wxTranslations::~wxTranslations()
{
delete m_loader;
// free catalogs memory
while ( m_pMsgCat != NULL )
{
wxMsgCatalog* pTmpCat;
pTmpCat = m_pMsgCat;
m_pMsgCat = m_pMsgCat->m_pNext;
delete pTmpCat;
}
}
void wxTranslations::SetLoader(wxTranslationsLoader *loader)
{
wxCHECK_RET( loader, "loader can't be NULL" );
delete m_loader;
m_loader = loader;
}
void wxTranslations::SetLanguage(wxLanguage lang)
{
if ( lang == wxLANGUAGE_DEFAULT )
SetLanguage(wxString());
else
SetLanguage(wxLocale::GetLanguageCanonicalName(lang));
}
void wxTranslations::SetLanguage(const wxString& lang)
{
m_lang = lang;
}
wxArrayString wxTranslations::GetAvailableTranslations(const wxString& domain) const
{
wxCHECK_MSG( m_loader, wxArrayString(), "loader can't be NULL" );
return m_loader->GetAvailableTranslations(domain);
}
bool wxTranslations::AddStdCatalog()
{
if ( !AddCatalog(wxS("wxstd")) )
return false;
// there may be a catalog with toolkit specific overrides, it is not
// an error if this does not exist
wxString port(wxPlatformInfo::Get().GetPortIdName());
if ( !port.empty() )
{
AddCatalog(port.BeforeFirst(wxS('/')).MakeLower());
}
return true;
}
#if !wxUSE_UNICODE
bool wxTranslations::AddCatalog(const wxString& domain,
wxLanguage msgIdLanguage,
const wxString& msgIdCharset)
{
gs_msgIdCharset[domain] = msgIdCharset;
return AddCatalog(domain, msgIdLanguage);
}
#endif // !wxUSE_UNICODE
bool wxTranslations::AddCatalog(const wxString& domain,
wxLanguage msgIdLanguage)
{
const wxString msgIdLang = wxLocale::GetLanguageCanonicalName(msgIdLanguage);
const wxString domain_lang = GetBestTranslation(domain, msgIdLang);
if ( domain_lang.empty() )
{
wxLogTrace(TRACE_I18N,
wxS("no suitable translation for domain '%s' found"),
domain);
return false;
}
wxLogTrace(TRACE_I18N,
wxS("adding '%s' translation for domain '%s' (msgid language '%s')"),
domain_lang, domain, msgIdLang);
return LoadCatalog(domain, domain_lang, msgIdLang);
}
bool wxTranslations::LoadCatalog(const wxString& domain, const wxString& lang, const wxString& msgIdLang)
{
wxCHECK_MSG( m_loader, false, "loader can't be NULL" );
wxMsgCatalog *cat = NULL;
#if wxUSE_FONTMAP
// first look for the catalog for this language and the current locale:
// notice that we don't use the system name for the locale as this would
// force us to install catalogs in different locations depending on the
// system but always use the canonical name
wxFontEncoding encSys = wxLocale::GetSystemEncoding();
if ( encSys != wxFONTENCODING_SYSTEM )
{
wxString fullname(lang);
fullname << wxS('.') << wxFontMapperBase::GetEncodingName(encSys);
cat = m_loader->LoadCatalog(domain, fullname);
}
#endif // wxUSE_FONTMAP
if ( !cat )
{
// Next try: use the provided name language name:
cat = m_loader->LoadCatalog(domain, lang);
}
if ( !cat )
{
// Also try just base locale name: for things like "fr_BE" (Belgium
// French) we should use fall back on plain "fr" if no Belgium-specific
// message catalogs exist
wxString baselang = lang.BeforeFirst('_');
if ( lang != baselang )
cat = m_loader->LoadCatalog(domain, baselang);
}
if ( !cat )
{
// 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 ( msgIdLang == lang )
return true;
}
if ( cat )
{
// add it to the head of the list so that in GetString it will
// be searched before the catalogs added earlier
cat->m_pNext = m_pMsgCat;
m_pMsgCat = cat;
m_catalogMap[domain] = cat;
return true;
}
else
{
// Nothing worked, the catalog just isn't there
wxLogTrace(TRACE_I18N,
"Catalog \"%s.mo\" not found for language \"%s\".",
domain, lang);
return false;
}
}
// check if the given catalog is loaded
bool wxTranslations::IsLoaded(const wxString& domain) const
{
return FindCatalog(domain) != NULL;
}
wxString wxTranslations::GetBestTranslation(const wxString& domain,
wxLanguage msgIdLanguage)
{
const wxString lang = wxLocale::GetLanguageCanonicalName(msgIdLanguage);
return GetBestTranslation(domain, lang);
}
wxString wxTranslations::GetBestTranslation(const wxString& domain,
const wxString& msgIdLanguage)
{
// explicitly set language should always be respected
if ( !m_lang.empty() )
return m_lang;
wxArrayString available(GetAvailableTranslations(domain));
// it's OK to have duplicates, so just add msgid language
available.push_back(msgIdLanguage);
available.push_back(msgIdLanguage.BeforeFirst('_'));
wxLogTrace(TRACE_I18N, "choosing best language for domain '%s'", domain);
LogTraceArray(" - available translations", available);
const wxString lang = GetPreferredUILanguage(available);
wxLogTrace(TRACE_I18N, " => using language '%s'", lang);
return lang;
}
/* static */
const wxString& wxTranslations::GetUntranslatedString(const wxString& str)
{
wxLocaleUntranslatedStrings& strings = wxThreadInfo.untranslatedStrings;
wxLocaleUntranslatedStrings::iterator i = strings.find(str);
if ( i == strings.end() )
return *strings.insert(str).first;
return *i;
}
const wxString *wxTranslations::GetTranslatedString(const wxString& origString,
const wxString& domain,
const wxString& context) const
{
return GetTranslatedString(origString, UINT_MAX, domain, context);
}
const wxString *wxTranslations::GetTranslatedString(const wxString& origString,
unsigned n,
const wxString& domain,
const wxString& context) const
{
if ( origString.empty() )
return NULL;
const wxString *trans = NULL;
wxMsgCatalog *pMsgCat;
if ( !domain.empty() )
{
pMsgCat = FindCatalog(domain);
// does the catalog exist?
if ( pMsgCat != NULL )
trans = pMsgCat->GetString(origString, n, context);
}
else
{
// search in all domains
for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext )
{
trans = pMsgCat->GetString(origString, n, context);
if ( trans != NULL ) // take the first found
break;
}
}
if ( trans == NULL )
{
wxLogTrace
(
TRACE_I18N,
"string \"%s\"%s not found in %s%slocale '%s'.",
origString,
(n != UINT_MAX ? wxString::Format("[%ld]", (long)n) : wxString()),
(!domain.empty() ? wxString::Format("domain '%s' ", domain) : wxString()),
(!context.empty() ? wxString::Format("context '%s' ", context) : wxString()),
m_lang
);
}
return trans;
}
wxString wxTranslations::GetHeaderValue(const wxString& header,
const wxString& domain) const
{
if ( header.empty() )
return wxEmptyString;
const wxString *trans = NULL;
wxMsgCatalog *pMsgCat;
if ( !domain.empty() )
{
pMsgCat = FindCatalog(domain);
// does the catalog exist?
if ( pMsgCat == NULL )
return wxEmptyString;
trans = pMsgCat->GetString(wxEmptyString, UINT_MAX);
}
else
{
// search in all domains
for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext )
{
trans = pMsgCat->GetString(wxEmptyString, UINT_MAX);
if ( trans != NULL ) // take the first found
break;
}
}
if ( !trans || trans->empty() )
return wxEmptyString;
size_t found = trans->find(header + wxS(": "));
if ( found == wxString::npos )
return wxEmptyString;
found += header.length() + 2 /* ': ' */;
// Every header is separated by \n
size_t endLine = trans->find(wxS('\n'), found);
size_t len = (endLine == wxString::npos) ?
wxString::npos : (endLine - found);
return trans->substr(found, len);
}
// find catalog by name
wxMsgCatalog *wxTranslations::FindCatalog(const wxString& domain) const
{
const wxMsgCatalogMap::const_iterator found = m_catalogMap.find(domain);
return found == m_catalogMap.end() ? NULL : found->second;
}
// ----------------------------------------------------------------------------
// wxFileTranslationsLoader
// ----------------------------------------------------------------------------
namespace
{
// the list of the directories to search for message catalog files
wxArrayString gs_searchPrefixes;
// return the directories to search for message catalogs under the given
// prefix, separated by wxPATH_SEP
wxString GetMsgCatalogSubdirs(const wxString& prefix, const wxString& lang)
{
// Search first in Unix-standard prefix/lang/LC_MESSAGES, then in
// prefix/lang.
//
// Note that we use LC_MESSAGES on all platforms and not just Unix, because
// it doesn't cost much to look into one more directory and doing it this
// way has two important benefits:
// a) we don't break compatibility with wx-2.6 and older by stopping to
// look in a directory where the catalogs used to be and thus silently
// breaking apps after they are recompiled against the latest wx
// b) it makes it possible to package app's support files in the same
// way on all target platforms
const wxString prefixAndLang = wxFileName(prefix, lang).GetFullPath();
wxString searchPath;
searchPath.reserve(4*prefixAndLang.length());
searchPath
#ifdef __WXOSX__
<< prefixAndLang << ".lproj/LC_MESSAGES" << wxPATH_SEP
<< prefixAndLang << ".lproj" << wxPATH_SEP
#endif
<< prefixAndLang << wxFILE_SEP_PATH << "LC_MESSAGES" << wxPATH_SEP
<< prefixAndLang << wxPATH_SEP
;
return searchPath;
}
bool HasMsgCatalogInDir(const wxString& dir, const wxString& domain)
{
return wxFileName(dir, domain, "mo").FileExists() ||
wxFileName(dir + wxFILE_SEP_PATH + "LC_MESSAGES", domain, "mo").FileExists();
}
// get prefixes to locale directories; if lang is empty, don't point to
// OSX's .lproj bundles
wxArrayString GetSearchPrefixes()
{
wxArrayString paths;
// first take the entries explicitly added by the program
paths = gs_searchPrefixes;
#if wxUSE_STDPATHS
// then look in the standard location
wxString stdp;
stdp = wxStandardPaths::Get().GetResourcesDir();
if ( paths.Index(stdp) == wxNOT_FOUND )
paths.Add(stdp);
#ifdef wxHAS_STDPATHS_INSTALL_PREFIX
stdp = wxStandardPaths::Get().GetInstallPrefix() + "/share/locale";
if ( paths.Index(stdp) == wxNOT_FOUND )
paths.Add(stdp);
#endif
#endif // wxUSE_STDPATHS
// last look in default locations
#ifdef __UNIX__
// LC_PATH is a standard env var containing the search path for the .mo
// files
const char *pszLcPath = wxGetenv("LC_PATH");
if ( pszLcPath )
{
const wxString lcp = pszLcPath;
if ( paths.Index(lcp) == wxNOT_FOUND )
paths.Add(lcp);
}
// also add the one from where wxWin was installed:
wxString wxp = wxGetInstallPrefix();
if ( !wxp.empty() )
{
wxp += wxS("/share/locale");
if ( paths.Index(wxp) == wxNOT_FOUND )
paths.Add(wxp);
}
#endif // __UNIX__
return paths;
}
// construct the search path for the given language
wxString GetFullSearchPath(const wxString& lang)
{
wxString searchPath;
searchPath.reserve(500);
const wxArrayString prefixes = GetSearchPrefixes();
for ( wxArrayString::const_iterator i = prefixes.begin();
i != prefixes.end();
++i )
{
const wxString p = GetMsgCatalogSubdirs(*i, lang);
if ( !searchPath.empty() )
searchPath += wxPATH_SEP;
searchPath += p;
}
return searchPath;
}
} // anonymous namespace
void wxFileTranslationsLoader::AddCatalogLookupPathPrefix(const wxString& prefix)
{
if ( gs_searchPrefixes.Index(prefix) == wxNOT_FOUND )
{
gs_searchPrefixes.Add(prefix);
}
//else: already have it
}
wxMsgCatalog *wxFileTranslationsLoader::LoadCatalog(const wxString& domain,
const wxString& lang)
{
wxString searchPath = GetFullSearchPath(lang);
LogTraceLargeArray
(
wxString::Format("looking for \"%s.mo\" in search path", domain),
wxSplit(searchPath, wxPATH_SEP[0])
);
wxFileName fn(domain);
fn.SetExt(wxS("mo"));
wxString strFullName;
if ( !wxFindFileInPath(&strFullName, searchPath, fn.GetFullPath()) )
return NULL;
// open file and read its data
wxLogVerbose(_("using catalog '%s' from '%s'."), domain, strFullName.c_str());
wxLogTrace(TRACE_I18N, wxS("Using catalog \"%s\"."), strFullName.c_str());
return wxMsgCatalog::CreateFromFile(strFullName, domain);
}
wxArrayString wxFileTranslationsLoader::GetAvailableTranslations(const wxString& domain) const
{
wxArrayString langs;
const wxArrayString prefixes = GetSearchPrefixes();
LogTraceLargeArray
(
wxString::Format("looking for available translations of \"%s\" in search path", domain),
prefixes
);
for ( wxArrayString::const_iterator i = prefixes.begin();
i != prefixes.end();
++i )
{
if ( i->empty() )
continue;
wxDir dir;
if ( !dir.Open(*i) )
continue;
wxString lang;
for ( bool ok = dir.GetFirst(&lang, wxString(), wxDIR_DIRS);
ok;
ok = dir.GetNext(&lang) )
{
const wxString langdir = *i + wxFILE_SEP_PATH + lang;
if ( HasMsgCatalogInDir(langdir, domain) )
{
#ifdef __WXOSX__
wxString rest;
if ( lang.EndsWith(".lproj", &rest) )
lang = rest;
#endif // __WXOSX__
wxLogTrace(TRACE_I18N,
"found %s translation of \"%s\" in %s",
lang, domain, langdir);
langs.push_back(lang);
}
}
}
return langs;
}
// ----------------------------------------------------------------------------
// wxResourceTranslationsLoader
// ----------------------------------------------------------------------------
#ifdef __WINDOWS__
wxMsgCatalog *wxResourceTranslationsLoader::LoadCatalog(const wxString& domain,
const wxString& lang)
{
const void *mo_data = NULL;
size_t mo_size = 0;
const wxString resname = wxString::Format("%s_%s", domain, lang);
if ( !wxLoadUserResource(&mo_data, &mo_size,
resname,
GetResourceType().t_str(),
GetModule()) )
return NULL;
wxLogTrace(TRACE_I18N,
"Using catalog from Windows resource \"%s\".", resname);
wxMsgCatalog *cat = wxMsgCatalog::CreateFromData(
wxCharBuffer::CreateNonOwned(static_cast<const char*>(mo_data), mo_size),
domain);
if ( !cat )
{
wxLogWarning(_("Resource '%s' is not a valid message catalog."), resname);
}
return cat;
}
namespace
{
struct EnumCallbackData
{
wxString prefix;
wxArrayString langs;
};
BOOL CALLBACK EnumTranslations(HMODULE WXUNUSED(hModule),
LPCTSTR WXUNUSED(lpszType),
LPTSTR lpszName,
LONG_PTR lParam)
{
wxString name(lpszName);
name.MakeLower(); // resource names are case insensitive
EnumCallbackData *data = reinterpret_cast<EnumCallbackData*>(lParam);
wxString lang;
if ( name.StartsWith(data->prefix, &lang) && !lang.empty() )
data->langs.push_back(lang);
return TRUE; // continue enumeration
}
} // anonymous namespace
wxArrayString wxResourceTranslationsLoader::GetAvailableTranslations(const wxString& domain) const
{
EnumCallbackData data;
data.prefix = domain + "_";
data.prefix.MakeLower(); // resource names are case insensitive
if ( !EnumResourceNames(GetModule(),
GetResourceType().t_str(),
EnumTranslations,
reinterpret_cast<LONG_PTR>(&data)) )
{
const DWORD err = GetLastError();
if ( err != NO_ERROR && err != ERROR_RESOURCE_TYPE_NOT_FOUND )
{
wxLogSysError(_("Couldn't enumerate translations"));
}
}
return data.langs;
}
#endif // __WINDOWS__
// ----------------------------------------------------------------------------
// wxTranslationsModule module (for destruction of gs_translations)
// ----------------------------------------------------------------------------
class wxTranslationsModule: public wxModule
{
wxDECLARE_DYNAMIC_CLASS(wxTranslationsModule);
public:
wxTranslationsModule() {}
bool OnInit() wxOVERRIDE
{
return true;
}
void OnExit() wxOVERRIDE
{
if ( gs_translationsOwned )
delete gs_translations;
gs_translations = NULL;
gs_translationsOwned = true;
}
};
wxIMPLEMENT_DYNAMIC_CLASS(wxTranslationsModule, wxModule);
// Encoding for strings to be translated, passed as const char *.
static const wxMBConv *inlineEncoding = &wxConvLibc;
WXDLLIMPEXP_BASE const wxMBConv &wxGetInlineEncoding() {
return *inlineEncoding;
}
WXDLLIMPEXP_BASE void wxSetInlineEncoding(const wxMBConv &conv) {
inlineEncoding = &conv;
}
#endif // wxUSE_INTL