wxwidgets/src/common/translation.cpp
Vadim Zeitlin 39079cbf23 Return true from AddCatalog() if message ID matches language
When the original messages language matches the language of the locale
being used, these strings can be used directly and so AddCatalog()
should still return true for them but it didn't do it any more after the
changes of 94b1a17aeb (Add AddAvailableCatalog() and use it in
AddStdCatalog(), 2023-09-30).

See #18227.

Closes #24019.

Closes #24037.
2023-11-07 15:11:26 +01:00

1960 lines
55 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"
#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/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/stdpaths.h"
#include "wx/version.h"
#include "wx/uilocale.h"
#ifdef __WINDOWS__
#include "wx/dynlib.h"
#include "wx/scopedarray.h"
#include "wx/msw/wrapwin.h"
#include "wx/msw/missing.h"
#endif
#include <memory>
#include <unordered_set>
// ----------------------------------------------------------------------------
// 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
{
// ----------------------------------------------------------------------------
// Platform specific helpers
// ----------------------------------------------------------------------------
#if wxUSE_LOG_TRACE
void LogTraceArray(const char* prefix, const wxArrayString& arr)
{
wxLogTrace(TRACE_I18N, "%s: [%s]", prefix, wxJoin(arr, ','));
}
void LogTraceArray(const char *prefix, const wxVector<wxString>& arr)
{
wxString s;
for (wxVector<wxString>::const_iterator j = arr.begin(); j != arr.end(); ++j)
{
if (j != arr.begin())
s += ",";
s += *j;
}
wxLogTrace(TRACE_I18N, "%s: [%s]", prefix, s);
}
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 = wxUILocale::GetLanguageCanonicalName(wxUILocale::GetSystemLocale());
wxLogTrace(TRACE_I18N, " - obtained best language from locale: %s", lang);
return lang;
}
wxString GetPreferredUILanguage(const wxArrayString& available)
{
wxVector<wxString> preferred = wxUILocale::GetPreferredUILanguages();
LogTraceArray(" - system preferred languages", preferred);
wxString langNoMatchRegion;
for ( wxVector<wxString>::const_iterator j = preferred.begin();
j != preferred.end();
++j )
{
wxLocaleIdent localeId = wxLocaleIdent::FromTag(*j);
wxString lang = localeId.GetTag(wxLOCALE_TAGTYPE_POSIX);
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;
}
if (langNoMatchRegion.empty())
{
// lang now holds only the language
// check for an available language with potentially non-matching region
for ( wxArrayString::const_iterator k = available.begin();
k != available.end();
++k )
{
if ((*k).Lower().StartsWith(lang.Lower()))
{
langNoMatchRegion = *k;
break;
}
}
}
}
if (!langNoMatchRegion.empty())
return langNoMatchRegion;
return GetPreferredUILanguageFallback(available);
}
} // 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;
using wxPluralFormsNodePtr = std::unique_ptr<wxPluralFormsNode>;
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];
};
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(nullptr) {}
// 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 = nullptr);
~wxPluralFormsCalculator() {}
void init(wxPluralFormsToken::Number nplurals, wxPluralFormsNode* plural);
private:
wxPluralFormsToken::Number m_nplurals;
wxPluralFormsNodePtr m_plural;
};
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() == nullptr)
{
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 == nullptr)
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 == nullptr)
{
return nullptr;
}
wxPluralFormsNodePtr n(p);
if (token().type() != wxPluralFormsToken::T_SEMICOLON)
{
return nullptr;
}
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 == nullptr)
return nullptr;
wxPluralFormsNodePtr n(p);
if (token().type() == wxPluralFormsToken::T_QUESTION)
{
wxPluralFormsNodePtr qn(new wxPluralFormsNode(token()));
if (!nextToken())
{
return nullptr;
}
p = expression();
if (p == nullptr)
{
return nullptr;
}
qn->setNode(1, p);
if (token().type() != wxPluralFormsToken::T_COLON)
{
return nullptr;
}
if (!nextToken())
{
return nullptr;
}
p = expression();
if (p == nullptr)
{
return nullptr;
}
qn->setNode(2, p);
qn->setNode(0, n.release());
return qn.release();
}
return n.release();
}
wxPluralFormsNode*wxPluralFormsParser::logicalOrExpression()
{
wxPluralFormsNode* p = logicalAndExpression();
if (p == nullptr)
return nullptr;
wxPluralFormsNodePtr ln(p);
if (token().type() == wxPluralFormsToken::T_LOGICAL_OR)
{
wxPluralFormsNodePtr un(new wxPluralFormsNode(token()));
if (!nextToken())
{
return nullptr;
}
p = logicalOrExpression();
if (p == nullptr)
{
return nullptr;
}
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 == nullptr)
return nullptr;
wxPluralFormsNodePtr ln(p); // left
if (token().type() == wxPluralFormsToken::T_LOGICAL_AND)
{
wxPluralFormsNodePtr un(new wxPluralFormsNode(token())); // up
if (!nextToken())
{
return nullptr;
}
p = logicalAndExpression();
if (p == nullptr)
{
return nullptr;
}
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 == nullptr)
return nullptr;
wxPluralFormsNodePtr n(p);
if (token().type() == wxPluralFormsToken::T_EQUAL
|| token().type() == wxPluralFormsToken::T_NOT_EQUAL)
{
wxPluralFormsNodePtr qn(new wxPluralFormsNode(token()));
if (!nextToken())
{
return nullptr;
}
p = relationalExpression();
if (p == nullptr)
{
return nullptr;
}
qn->setNode(1, p);
qn->setNode(0, n.release());
return qn.release();
}
return n.release();
}
wxPluralFormsNode* wxPluralFormsParser::relationalExpression()
{
wxPluralFormsNode* p = multiplicativeExpression();
if (p == nullptr)
return nullptr;
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 nullptr;
}
p = multiplicativeExpression();
if (p == nullptr)
{
return nullptr;
}
qn->setNode(1, p);
qn->setNode(0, n.release());
return qn.release();
}
return n.release();
}
wxPluralFormsNode* wxPluralFormsParser::multiplicativeExpression()
{
wxPluralFormsNode* p = pmExpression();
if (p == nullptr)
return nullptr;
wxPluralFormsNodePtr n(p);
if (token().type() == wxPluralFormsToken::T_REMINDER)
{
wxPluralFormsNodePtr qn(new wxPluralFormsNode(token()));
if (!nextToken())
{
return nullptr;
}
p = pmExpression();
if (p == nullptr)
{
return nullptr;
}
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 nullptr;
}
}
else if (token().type() == wxPluralFormsToken::T_LEFT_BRACKET) {
if (!nextToken())
{
return nullptr;
}
wxPluralFormsNode* p = expression();
if (p == nullptr)
{
return nullptr;
}
n.reset(p);
if (token().type() != wxPluralFormsToken::T_RIGHT_BRACKET)
{
return nullptr;
}
if (!nextToken())
{
return nullptr;
}
}
else
{
return nullptr;
}
return n.release();
}
wxPluralFormsCalculator* wxPluralFormsCalculator::make(const char* s)
{
wxPluralFormsCalculatorPtr calculator(new wxPluralFormsCalculator);
if (s != nullptr)
{
wxPluralFormsScanner scanner(s);
wxPluralFormsParser p(scanner);
if (!p.parse(*calculator))
{
return nullptr;
}
}
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(wxTranslationsHashMap& 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 nullptr;
}
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 its 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);
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(wxTranslationsHashMap& 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 = nullptr;
std::unique_ptr<wxMBConv> inputConvPtr; // just to delete inputConv if needed
if ( !m_charset.empty() )
{
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 to convert the encoding
{
// 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;
}
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;
msgid = wxString(data, *inputConv);
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;
msgstr = wxString(str, *inputConv);
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
// ----------------------------------------------------------------------------
wxMsgCatalog::wxMsgCatalog(const wxString& domain)
: m_pNext(nullptr), m_domain(domain)
{
}
wxMsgCatalog::~wxMsgCatalog() = default;
/* static */
wxMsgCatalog *wxMsgCatalog::CreateFromFile(const wxString& filename,
const wxString& domain)
{
std::unique_ptr<wxMsgCatalog> cat(new wxMsgCatalog(domain));
wxMsgCatalogFile file;
if ( !file.LoadFile(filename, cat->m_pluralFormsCalculator) )
return nullptr;
if ( !file.FillHash(cat->m_messages, domain) )
return nullptr;
return cat.release();
}
/* static */
wxMsgCatalog *wxMsgCatalog::CreateFromData(const wxScopedCharBuffer& data,
const wxString& domain)
{
std::unique_ptr<wxMsgCatalog> cat(new wxMsgCatalog(domain));
wxMsgCatalogFile file;
if ( !file.LoadData(data, cat->m_pluralFormsCalculator) )
return nullptr;
if ( !file.FillHash(cat->m_messages, domain) )
return nullptr;
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);
}
wxTranslationsHashMap::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 nullptr;
}
// ----------------------------------------------------------------------------
// wxTranslations
// ----------------------------------------------------------------------------
namespace
{
wxTranslations *gs_translations = nullptr;
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 = nullptr;
m_loader = new wxFileTranslationsLoader;
}
wxTranslations::~wxTranslations()
{
delete m_loader;
// free catalogs memory
while ( m_pMsgCat != nullptr )
{
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(wxUILocale::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()
{
// Try loading the message catalog for this version first, but fall back to
// the name without the version if it's not found, as message catalogs
// typically won't have the version in their names under non-Unix platforms
// (i.e. where they're not installed by our own "make install").
if ( AddAvailableCatalog("wxstd-" wxSTRINGIZE(wxMAJOR_VERSION) "." wxSTRINGIZE(wxMINOR_VERSION)) )
return true;
if ( AddCatalog(wxS("wxstd")) )
return true;
return false;
}
bool wxTranslations::AddAvailableCatalog(const wxString& domain)
{
const wxString domain_lang = GetBestAvailableTranslation(domain);
if ( domain_lang.empty() )
{
wxLogTrace(TRACE_I18N,
wxS("no suitable translation for domain '%s' found"),
domain);
return false;
}
return LoadCatalog(domain, domain_lang);
}
bool wxTranslations::AddCatalog(const wxString& domain,
wxLanguage msgIdLanguage)
{
if ( AddAvailableCatalog(domain) )
return true;
const wxString msgIdLang = wxUILocale::GetLanguageCanonicalName(msgIdLanguage);
// Check if the original strings can be used directly.
bool canUseUntranslated = false;
if ( m_lang.empty() )
{
// If we are using the default language, check if the message ID
// language is acceptable for this system.
const wxString domain_lang = GetBestTranslation(domain, msgIdLang);
if ( msgIdLang == domain_lang )
canUseUntranslated = true;
}
else // But if we have a fixed language, we should just check it instead.
{
// Consider message IDs for another region using the same language
// acceptable.
if ( msgIdLang.BeforeFirst('_') == m_lang.BeforeFirst('_') )
canUseUntranslated = true;
}
if ( canUseUntranslated )
{
wxLogTrace(TRACE_I18N,
wxS("not using translations for domain '%s' with msgid language '%s'"),
domain, msgIdLang);
return true;
}
return false;
}
bool wxTranslations::LoadCatalog(const wxString& domain, const wxString& lang)
{
wxCHECK_MSG( m_loader, false, "loader can't be null" );
wxMsgCatalog *cat = nullptr;
#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 )
{
// 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) != nullptr;
}
wxString wxTranslations::GetBestTranslation(const wxString& domain,
wxLanguage msgIdLanguage)
{
const wxString lang = wxUILocale::GetLanguageCanonicalName(msgIdLanguage);
return GetBestTranslation(domain, lang);
}
wxString wxTranslations::GetBestTranslation(const wxString& domain,
const wxString& msgIdLanguage)
{
wxString lang = GetBestAvailableTranslation(domain);
if ( lang.empty() )
{
wxArrayString available;
available.push_back(msgIdLanguage);
available.push_back(msgIdLanguage.BeforeFirst('_'));
lang = GetPreferredUILanguage(available);
if ( lang.empty() )
{
wxLogTrace(TRACE_I18N,
"no available language for domain '%s'", domain);
}
else
{
wxLogTrace(TRACE_I18N,
"using message ID language '%s' for domain '%s'", lang);
}
}
return lang;
}
wxString wxTranslations::GetBestAvailableTranslation(const wxString& domain)
{
const wxArrayString available(GetAvailableTranslations(domain));
if ( !m_lang.empty() )
{
wxLogTrace(TRACE_I18N,
"searching for best translation to %s for domain '%s'",
m_lang, domain);
wxString lang;
if ( available.Index(m_lang) != wxNOT_FOUND )
{
lang = m_lang;
}
else
{
const wxString baselang = m_lang.BeforeFirst('_');
if ( baselang != m_lang && available.Index(baselang) != wxNOT_FOUND )
lang = baselang;
}
if ( lang.empty() )
wxLogTrace(TRACE_I18N, " => no available translations found");
else
wxLogTrace(TRACE_I18N, " => found '%s'", lang);
return lang;
}
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)
{
thread_local std::unordered_set<wxString> wxPerThreadStrings;
return *wxPerThreadStrings.insert(str).first;
}
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 nullptr;
const wxString *trans = nullptr;
wxMsgCatalog *pMsgCat;
if ( !domain.empty() )
{
pMsgCat = FindCatalog(domain);
// does the catalog exist?
if ( pMsgCat != nullptr )
trans = pMsgCat->GetString(origString, n, context);
}
else
{
// search in all domains
for ( pMsgCat = m_pMsgCat; pMsgCat != nullptr; pMsgCat = pMsgCat->m_pNext )
{
trans = pMsgCat->GetString(origString, n, context);
if ( trans != nullptr ) // take the first found
break;
}
}
if ( trans == nullptr )
{
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 = nullptr;
wxMsgCatalog *pMsgCat;
if ( !domain.empty() )
{
pMsgCat = FindCatalog(domain);
// does the catalog exist?
if ( pMsgCat == nullptr )
return wxEmptyString;
trans = pMsgCat->GetString(wxEmptyString, UINT_MAX);
}
else
{
// search in all domains
for ( pMsgCat = m_pMsgCat; pMsgCat != nullptr; pMsgCat = pMsgCat->m_pNext )
{
trans = pMsgCat->GetString(wxEmptyString, UINT_MAX);
if ( trans != nullptr ) // 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() ? nullptr : 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(wxString(), domain, wxS("mo"));
wxString strFullName;
if ( !wxFindFileInPath(&strFullName, searchPath, fn.GetFullPath()) )
return nullptr;
// open file and read its data
wxLogVerbose(_("using catalog '%s' from '%s'."), domain, strFullName);
wxLogTrace(TRACE_I18N, wxS("Using catalog \"%s\"."), strFullName);
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 = nullptr;
size_t mo_size = 0;
// Language may contain non-alphabetic characters that are not allowed in the
// resource names that must be valid identifiers, so sanitize the language
// before using it as part of the resource name.
wxString lang_sanitized = lang;
for ( wxString::iterator it = lang_sanitized.begin(); it != lang_sanitized.end(); ++it )
{
const wxChar c = *it;
if ( !((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) )
*it = '_';
}
const wxString resname = wxString::Format("%s_%s", domain, lang_sanitized);
if ( !wxLoadUserResource(&mo_data, &mo_size,
resname,
GetResourceType().t_str(),
GetModule()) )
return nullptr;
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() override
{
return true;
}
void OnExit() override
{
if ( gs_translationsOwned )
delete gs_translations;
gs_translations = nullptr;
gs_translationsOwned = true;
}
};
wxIMPLEMENT_DYNAMIC_CLASS(wxTranslationsModule, wxModule);
#endif // wxUSE_INTL