Files
wxWidgets/src/common/translation.cpp
Lauri Nurmi 2d784da2ee Load catalogs for all preferred languages, if they exist
This way the first and only fallback language isn't necessarily the
msgid language (which is English most often). This is how GNU gettext
works -- it uses multiple fallback languages when multiple preferred
languages are set.

As a side effect, fixes #18227 in one possible way.
2018-11-18 01:47:12 +01:00

2155 lines
61 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, wxArrayString& allPreferred)
{
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 )
allPreferred.Add(lang);
size_t pos = lang.find('_');
if ( pos != wxString::npos )
{
lang = lang.substr(0, pos);
if ( available.Index(lang, /*bCase=*/false) != wxNOT_FOUND )
allPreferred.Add(lang);
}
}
}
}
}
if ( !allPreferred.empty() )
return allPreferred[0];
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, wxArrayString &allPreferred)
{
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);
for ( size_t x = 0; x < prefArrLength; ++x )
{
// 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, x)));
wxStringToStringHashMap::const_iterator i = availableNormalized.find(lang);
if ( i == availableNormalized.end() )
allPreferred.push_back(lang);
else
allPreferred.push_back(i->second);
}
if ( allPreferred.empty() == false )
return allPreferred[0];
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, wxArrayString &allPreferred)
{
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 )
allPreferred.Add(lang);
size_t pos = lang.find('_');
if ( pos != wxString::npos )
{
lang = lang.substr(0, pos);
if ( available.Index(lang) != wxNOT_FOUND )
allPreferred.Add(lang);
}
}
if ( allPreferred.empty() == false )
return allPreferred[0];
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
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(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 = (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 = (wxMsgTableEntry *)(data.data() +
Swap(pHeader->ofsOrigTable));
m_pTransTable = (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
wxMsgCatalog *pTmpCat;
while ( m_pMsgCat != NULL )
{
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 wxArrayString domain_langs = GetAllGoodTranslations(domain, msgIdLanguage);
if ( domain_langs.empty() )
{
wxLogTrace(TRACE_I18N,
wxS("no suitable translation for domain '%s' found"),
domain);
return false;
}
bool success = false;
for ( wxArrayString::const_iterator lang = domain_langs.begin();
lang != domain_langs.end();
++lang )
{
wxLogTrace(TRACE_I18N,
wxS("adding '%s' translation for domain '%s' (msgid language '%s')"),
*lang, domain, msgIdLang);
// No use loading languages that are less preferred than the
// msgid language, as by definition it contains all the strings
// in the msgid language.
if ( msgIdLang == *lang )
break;
// We determine success by the success of loading/failing to load
// the most preferred (i.e. the first one) language's catalog:
if ( lang == domain_langs.begin() )
success = LoadCatalog(domain, *lang, msgIdLang);
else
LoadCatalog(domain, *lang, msgIdLang);
}
return success;
}
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)
{
const wxArrayString allGoodOnes = GetAllGoodTranslations(domain, msgIdLanguage);
wxLogTrace(TRACE_I18N, " => using language '%s'", allGoodOnes[0]);
return allGoodOnes[0];
}
wxArrayString wxTranslations::GetAllGoodTranslations(const wxString& domain,
wxLanguage msgIdLanguage)
{
const wxString lang = wxLocale::GetLanguageCanonicalName(msgIdLanguage);
return GetAllGoodTranslations(domain, lang);
}
wxArrayString wxTranslations::GetAllGoodTranslations(const wxString& domain,
const wxString& msgIdLanguage)
{
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 languages for domain '%s'", domain);
LogTraceArray(" - available translations", available);
wxArrayString allPreferred;
GetPreferredUILanguage(available, allPreferred);
// explicitly set language should always be preferred the most
if ( !m_lang.empty() )
allPreferred.insert(allPreferred.begin(), m_lang);
return allPreferred;
}
/* 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);
#endif // wxUSE_INTL