1
/////////////////////////////////////////////////////////////////////////////
2
// Name: src/common/translation.cpp
3
// Purpose: Internationalization and localisation for wxWidgets
4
// Author: Vadim Zeitlin, Vaclav Slavik,
5
// Michael N. Filippov <michael@idisys.iae.nsk.su>
6
// (2003/09/30 - PluralForms support)
8
// RCS-ID: $Id: translation.cpp 71375 2012-05-07 13:12:27Z VZ $
9
// Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
10
// Licence: wxWindows licence
11
/////////////////////////////////////////////////////////////////////////////
13
// ============================================================================
15
// ============================================================================
17
// ----------------------------------------------------------------------------
19
// ----------------------------------------------------------------------------
21
// For compilers that support precompilation, includes "wx.h".
22
#include "wx/wxprec.h"
31
#include "wx/dynarray.h"
32
#include "wx/string.h"
36
#include "wx/hashmap.h"
37
#include "wx/module.h"
44
#include "wx/arrstr.h"
47
#include "wx/filename.h"
48
#include "wx/tokenzr.h"
49
#include "wx/fontmap.h"
50
#include "wx/stdpaths.h"
51
#include "wx/hashset.h"
54
#include "wx/msw/wrapwin.h"
57
// ----------------------------------------------------------------------------
59
// ----------------------------------------------------------------------------
61
typedef wxUint32 size_t32;
63
// ----------------------------------------------------------------------------
65
// ----------------------------------------------------------------------------
67
// magic number identifying the .mo format file
68
const size_t32 MSGCATALOG_MAGIC = 0x950412de;
69
const size_t32 MSGCATALOG_MAGIC_SW = 0xde120495;
71
#define TRACE_I18N wxS("i18n")
73
// ============================================================================
75
// ============================================================================
81
// We need to keep track of (char*) msgids in non-Unicode legacy builds. Instead
82
// of making the public wxMsgCatalog and wxTranslationsLoader APIs ugly, we
83
// store them in this global map.
84
wxStringToStringHashMap gs_msgIdCharset;
87
} // anonymous namespace
89
// ----------------------------------------------------------------------------
90
// Plural forms parser
91
// ----------------------------------------------------------------------------
97
LogicalOrExpression '?' Expression ':' Expression
101
LogicalAndExpression "||" LogicalOrExpression // to (a || b) || c
104
LogicalAndExpression:
105
EqualityExpression "&&" LogicalAndExpression // to (a && b) && c
109
RelationalExpression "==" RelationalExperession
110
RelationalExpression "!=" RelationalExperession
113
RelationalExpression:
114
MultiplicativeExpression '>' MultiplicativeExpression
115
MultiplicativeExpression '<' MultiplicativeExpression
116
MultiplicativeExpression ">=" MultiplicativeExpression
117
MultiplicativeExpression "<=" MultiplicativeExpression
118
MultiplicativeExpression
120
MultiplicativeExpression:
121
PmExpression '%' PmExpression
130
class wxPluralFormsToken
135
T_ERROR, T_EOF, T_NUMBER, T_N, T_PLURAL, T_NPLURALS, T_EQUAL, T_ASSIGN,
136
T_GREATER, T_GREATER_OR_EQUAL, T_LESS, T_LESS_OR_EQUAL,
137
T_REMINDER, T_NOT_EQUAL,
138
T_LOGICAL_AND, T_LOGICAL_OR, T_QUESTION, T_COLON, T_SEMICOLON,
139
T_LEFT_BRACKET, T_RIGHT_BRACKET
141
Type type() const { return m_type; }
142
void setType(Type type) { m_type = type; }
145
Number number() const { return m_number; }
146
void setNumber(Number num) { m_number = num; }
153
class wxPluralFormsScanner
156
wxPluralFormsScanner(const char* s);
157
const wxPluralFormsToken& token() const { return m_token; }
158
bool nextToken(); // returns false if error
161
wxPluralFormsToken m_token;
164
wxPluralFormsScanner::wxPluralFormsScanner(const char* s) : m_s(s)
169
bool wxPluralFormsScanner::nextToken()
171
wxPluralFormsToken::Type type = wxPluralFormsToken::T_ERROR;
172
while (isspace((unsigned char) *m_s))
178
type = wxPluralFormsToken::T_EOF;
180
else if (isdigit((unsigned char) *m_s))
182
wxPluralFormsToken::Number number = *m_s++ - '0';
183
while (isdigit((unsigned char) *m_s))
185
number = number * 10 + (*m_s++ - '0');
187
m_token.setNumber(number);
188
type = wxPluralFormsToken::T_NUMBER;
190
else if (isalpha((unsigned char) *m_s))
192
const char* begin = m_s++;
193
while (isalnum((unsigned char) *m_s))
197
size_t size = m_s - begin;
198
if (size == 1 && memcmp(begin, "n", size) == 0)
200
type = wxPluralFormsToken::T_N;
202
else if (size == 6 && memcmp(begin, "plural", size) == 0)
204
type = wxPluralFormsToken::T_PLURAL;
206
else if (size == 8 && memcmp(begin, "nplurals", size) == 0)
208
type = wxPluralFormsToken::T_NPLURALS;
211
else if (*m_s == '=')
217
type = wxPluralFormsToken::T_EQUAL;
221
type = wxPluralFormsToken::T_ASSIGN;
224
else if (*m_s == '>')
230
type = wxPluralFormsToken::T_GREATER_OR_EQUAL;
234
type = wxPluralFormsToken::T_GREATER;
237
else if (*m_s == '<')
243
type = wxPluralFormsToken::T_LESS_OR_EQUAL;
247
type = wxPluralFormsToken::T_LESS;
250
else if (*m_s == '%')
253
type = wxPluralFormsToken::T_REMINDER;
255
else if (*m_s == '!' && m_s[1] == '=')
258
type = wxPluralFormsToken::T_NOT_EQUAL;
260
else if (*m_s == '&' && m_s[1] == '&')
263
type = wxPluralFormsToken::T_LOGICAL_AND;
265
else if (*m_s == '|' && m_s[1] == '|')
268
type = wxPluralFormsToken::T_LOGICAL_OR;
270
else if (*m_s == '?')
273
type = wxPluralFormsToken::T_QUESTION;
275
else if (*m_s == ':')
278
type = wxPluralFormsToken::T_COLON;
279
} else if (*m_s == ';') {
281
type = wxPluralFormsToken::T_SEMICOLON;
283
else if (*m_s == '(')
286
type = wxPluralFormsToken::T_LEFT_BRACKET;
288
else if (*m_s == ')')
291
type = wxPluralFormsToken::T_RIGHT_BRACKET;
293
m_token.setType(type);
294
return type != wxPluralFormsToken::T_ERROR;
297
class wxPluralFormsNode;
299
// NB: Can't use wxDEFINE_SCOPED_PTR_TYPE because wxPluralFormsNode is not
300
// fully defined yet:
301
class wxPluralFormsNodePtr
304
wxPluralFormsNodePtr(wxPluralFormsNode *p = NULL) : m_p(p) {}
305
~wxPluralFormsNodePtr();
306
wxPluralFormsNode& operator*() const { return *m_p; }
307
wxPluralFormsNode* operator->() const { return m_p; }
308
wxPluralFormsNode* get() const { return m_p; }
309
wxPluralFormsNode* release();
310
void reset(wxPluralFormsNode *p);
313
wxPluralFormsNode *m_p;
316
class wxPluralFormsNode
319
wxPluralFormsNode(const wxPluralFormsToken& token) : m_token(token) {}
320
const wxPluralFormsToken& token() const { return m_token; }
321
const wxPluralFormsNode* node(unsigned i) const
322
{ return m_nodes[i].get(); }
323
void setNode(unsigned i, wxPluralFormsNode* n);
324
wxPluralFormsNode* releaseNode(unsigned i);
325
wxPluralFormsToken::Number evaluate(wxPluralFormsToken::Number n) const;
328
wxPluralFormsToken m_token;
329
wxPluralFormsNodePtr m_nodes[3];
332
wxPluralFormsNodePtr::~wxPluralFormsNodePtr()
336
wxPluralFormsNode* wxPluralFormsNodePtr::release()
338
wxPluralFormsNode *p = m_p;
342
void wxPluralFormsNodePtr::reset(wxPluralFormsNode *p)
352
void wxPluralFormsNode::setNode(unsigned i, wxPluralFormsNode* n)
357
wxPluralFormsNode* wxPluralFormsNode::releaseNode(unsigned i)
359
return m_nodes[i].release();
362
wxPluralFormsToken::Number
363
wxPluralFormsNode::evaluate(wxPluralFormsToken::Number n) const
365
switch (token().type())
368
case wxPluralFormsToken::T_NUMBER:
369
return token().number();
370
case wxPluralFormsToken::T_N:
373
case wxPluralFormsToken::T_EQUAL:
374
return node(0)->evaluate(n) == node(1)->evaluate(n);
375
case wxPluralFormsToken::T_NOT_EQUAL:
376
return node(0)->evaluate(n) != node(1)->evaluate(n);
377
case wxPluralFormsToken::T_GREATER:
378
return node(0)->evaluate(n) > node(1)->evaluate(n);
379
case wxPluralFormsToken::T_GREATER_OR_EQUAL:
380
return node(0)->evaluate(n) >= node(1)->evaluate(n);
381
case wxPluralFormsToken::T_LESS:
382
return node(0)->evaluate(n) < node(1)->evaluate(n);
383
case wxPluralFormsToken::T_LESS_OR_EQUAL:
384
return node(0)->evaluate(n) <= node(1)->evaluate(n);
385
case wxPluralFormsToken::T_REMINDER:
387
wxPluralFormsToken::Number number = node(1)->evaluate(n);
390
return node(0)->evaluate(n) % number;
397
case wxPluralFormsToken::T_LOGICAL_AND:
398
return node(0)->evaluate(n) && node(1)->evaluate(n);
399
case wxPluralFormsToken::T_LOGICAL_OR:
400
return node(0)->evaluate(n) || node(1)->evaluate(n);
402
case wxPluralFormsToken::T_QUESTION:
403
return node(0)->evaluate(n)
404
? node(1)->evaluate(n)
405
: node(2)->evaluate(n);
412
class wxPluralFormsCalculator
415
wxPluralFormsCalculator() : m_nplurals(0), m_plural(0) {}
417
// input: number, returns msgstr index
418
int evaluate(int n) const;
420
// input: text after "Plural-Forms:" (e.g. "nplurals=2; plural=(n != 1);"),
421
// if s == 0, creates default handler
422
// returns 0 if error
423
static wxPluralFormsCalculator* make(const char* s = 0);
425
~wxPluralFormsCalculator() {}
427
void init(wxPluralFormsToken::Number nplurals, wxPluralFormsNode* plural);
430
wxPluralFormsToken::Number m_nplurals;
431
wxPluralFormsNodePtr m_plural;
434
wxDEFINE_SCOPED_PTR(wxPluralFormsCalculator, wxPluralFormsCalculatorPtr)
436
void wxPluralFormsCalculator::init(wxPluralFormsToken::Number nplurals,
437
wxPluralFormsNode* plural)
439
m_nplurals = nplurals;
440
m_plural.reset(plural);
443
int wxPluralFormsCalculator::evaluate(int n) const
445
if (m_plural.get() == 0)
449
wxPluralFormsToken::Number number = m_plural->evaluate(n);
450
if (number < 0 || number > m_nplurals)
458
class wxPluralFormsParser
461
wxPluralFormsParser(wxPluralFormsScanner& scanner) : m_scanner(scanner) {}
462
bool parse(wxPluralFormsCalculator& rCalculator);
465
wxPluralFormsNode* parsePlural();
466
// stops at T_SEMICOLON, returns 0 if error
467
wxPluralFormsScanner& m_scanner;
468
const wxPluralFormsToken& token() const;
471
wxPluralFormsNode* expression();
472
wxPluralFormsNode* logicalOrExpression();
473
wxPluralFormsNode* logicalAndExpression();
474
wxPluralFormsNode* equalityExpression();
475
wxPluralFormsNode* multiplicativeExpression();
476
wxPluralFormsNode* relationalExpression();
477
wxPluralFormsNode* pmExpression();
480
bool wxPluralFormsParser::parse(wxPluralFormsCalculator& rCalculator)
482
if (token().type() != wxPluralFormsToken::T_NPLURALS)
486
if (token().type() != wxPluralFormsToken::T_ASSIGN)
490
if (token().type() != wxPluralFormsToken::T_NUMBER)
492
wxPluralFormsToken::Number nplurals = token().number();
495
if (token().type() != wxPluralFormsToken::T_SEMICOLON)
499
if (token().type() != wxPluralFormsToken::T_PLURAL)
503
if (token().type() != wxPluralFormsToken::T_ASSIGN)
507
wxPluralFormsNode* plural = parsePlural();
510
if (token().type() != wxPluralFormsToken::T_SEMICOLON)
514
if (token().type() != wxPluralFormsToken::T_EOF)
516
rCalculator.init(nplurals, plural);
520
wxPluralFormsNode* wxPluralFormsParser::parsePlural()
522
wxPluralFormsNode* p = expression();
527
wxPluralFormsNodePtr n(p);
528
if (token().type() != wxPluralFormsToken::T_SEMICOLON)
535
const wxPluralFormsToken& wxPluralFormsParser::token() const
537
return m_scanner.token();
540
bool wxPluralFormsParser::nextToken()
542
if (!m_scanner.nextToken())
547
wxPluralFormsNode* wxPluralFormsParser::expression()
549
wxPluralFormsNode* p = logicalOrExpression();
552
wxPluralFormsNodePtr n(p);
553
if (token().type() == wxPluralFormsToken::T_QUESTION)
555
wxPluralFormsNodePtr qn(new wxPluralFormsNode(token()));
566
if (token().type() != wxPluralFormsToken::T_COLON)
580
qn->setNode(0, n.release());
586
wxPluralFormsNode*wxPluralFormsParser::logicalOrExpression()
588
wxPluralFormsNode* p = logicalAndExpression();
591
wxPluralFormsNodePtr ln(p);
592
if (token().type() == wxPluralFormsToken::T_LOGICAL_OR)
594
wxPluralFormsNodePtr un(new wxPluralFormsNode(token()));
599
p = logicalOrExpression();
604
wxPluralFormsNodePtr rn(p); // right
605
if (rn->token().type() == wxPluralFormsToken::T_LOGICAL_OR)
607
// see logicalAndExpression comment
608
un->setNode(0, ln.release());
609
un->setNode(1, rn->releaseNode(0));
610
rn->setNode(0, un.release());
615
un->setNode(0, ln.release());
616
un->setNode(1, rn.release());
622
wxPluralFormsNode* wxPluralFormsParser::logicalAndExpression()
624
wxPluralFormsNode* p = equalityExpression();
627
wxPluralFormsNodePtr ln(p); // left
628
if (token().type() == wxPluralFormsToken::T_LOGICAL_AND)
630
wxPluralFormsNodePtr un(new wxPluralFormsNode(token())); // up
635
p = logicalAndExpression();
640
wxPluralFormsNodePtr rn(p); // right
641
if (rn->token().type() == wxPluralFormsToken::T_LOGICAL_AND)
643
// transform 1 && (2 && 3) -> (1 && 2) && 3
647
un->setNode(0, ln.release());
648
un->setNode(1, rn->releaseNode(0));
649
rn->setNode(0, un.release());
653
un->setNode(0, ln.release());
654
un->setNode(1, rn.release());
660
wxPluralFormsNode* wxPluralFormsParser::equalityExpression()
662
wxPluralFormsNode* p = relationalExpression();
665
wxPluralFormsNodePtr n(p);
666
if (token().type() == wxPluralFormsToken::T_EQUAL
667
|| token().type() == wxPluralFormsToken::T_NOT_EQUAL)
669
wxPluralFormsNodePtr qn(new wxPluralFormsNode(token()));
674
p = relationalExpression();
680
qn->setNode(0, n.release());
686
wxPluralFormsNode* wxPluralFormsParser::relationalExpression()
688
wxPluralFormsNode* p = multiplicativeExpression();
691
wxPluralFormsNodePtr n(p);
692
if (token().type() == wxPluralFormsToken::T_GREATER
693
|| token().type() == wxPluralFormsToken::T_LESS
694
|| token().type() == wxPluralFormsToken::T_GREATER_OR_EQUAL
695
|| token().type() == wxPluralFormsToken::T_LESS_OR_EQUAL)
697
wxPluralFormsNodePtr qn(new wxPluralFormsNode(token()));
702
p = multiplicativeExpression();
708
qn->setNode(0, n.release());
714
wxPluralFormsNode* wxPluralFormsParser::multiplicativeExpression()
716
wxPluralFormsNode* p = pmExpression();
719
wxPluralFormsNodePtr n(p);
720
if (token().type() == wxPluralFormsToken::T_REMINDER)
722
wxPluralFormsNodePtr qn(new wxPluralFormsNode(token()));
733
qn->setNode(0, n.release());
739
wxPluralFormsNode* wxPluralFormsParser::pmExpression()
741
wxPluralFormsNodePtr n;
742
if (token().type() == wxPluralFormsToken::T_N
743
|| token().type() == wxPluralFormsToken::T_NUMBER)
745
n.reset(new wxPluralFormsNode(token()));
751
else if (token().type() == wxPluralFormsToken::T_LEFT_BRACKET) {
756
wxPluralFormsNode* p = expression();
762
if (token().type() != wxPluralFormsToken::T_RIGHT_BRACKET)
778
wxPluralFormsCalculator* wxPluralFormsCalculator::make(const char* s)
780
wxPluralFormsCalculatorPtr calculator(new wxPluralFormsCalculator);
783
wxPluralFormsScanner scanner(s);
784
wxPluralFormsParser p(scanner);
785
if (!p.parse(*calculator))
790
return calculator.release();
796
// ----------------------------------------------------------------------------
797
// wxMsgCatalogFile corresponds to one disk-file message catalog.
799
// This is a "low-level" class and is used only by wxMsgCatalog
800
// NOTE: for the documentation of the binary catalog (.MO) files refer to
801
// the GNU gettext manual:
802
// http://www.gnu.org/software/autoconf/manual/gettext/MO-Files.html
803
// ----------------------------------------------------------------------------
805
class wxMsgCatalogFile
808
typedef wxScopedCharBuffer DataBuffer;
814
// load the catalog from disk
815
bool LoadFile(const wxString& filename,
816
wxPluralFormsCalculatorPtr& rPluralFormsCalculator);
817
bool LoadData(const DataBuffer& data,
818
wxPluralFormsCalculatorPtr& rPluralFormsCalculator);
820
// fills the hash with string-translation pairs
821
bool FillHash(wxStringToStringHashMap& hash, const wxString& domain) const;
823
// return the charset of the strings in this catalog or empty string if
825
wxString GetCharset() const { return m_charset; }
828
// this implementation is binary compatible with GNU gettext() version 0.10
830
// an entry in the string table
831
struct wxMsgTableEntry
833
size_t32 nLen; // length of the string
834
size_t32 ofsString; // pointer to the string
837
// header of a .mo file
838
struct wxMsgCatalogHeader
840
size_t32 magic, // offset +00: magic id
841
revision, // +04: revision
842
numStrings; // +08: number of strings in the file
843
size_t32 ofsOrigTable, // +0C: start of original string table
844
ofsTransTable; // +10: start of translated string table
845
size_t32 nHashSize, // +14: hash table size
846
ofsHashTable; // +18: offset of hash table start
849
// all data is stored here
853
size_t32 m_numStrings; // number of strings in this domain
854
wxMsgTableEntry *m_pOrigTable, // pointer to original strings
855
*m_pTransTable; // translated
857
wxString m_charset; // from the message catalog header
860
// swap the 2 halves of 32 bit integer if needed
861
size_t32 Swap(size_t32 ui) const
863
return m_bSwapped ? (ui << 24) | ((ui & 0xff00) << 8) |
864
((ui >> 8) & 0xff00) | (ui >> 24)
868
const char *StringAtOfs(wxMsgTableEntry *pTable, size_t32 n) const
870
const wxMsgTableEntry * const ent = pTable + n;
872
// this check could fail for a corrupt message catalog
873
size_t32 ofsString = Swap(ent->ofsString);
874
if ( ofsString + Swap(ent->nLen) > m_data.length())
879
return m_data.data() + ofsString;
882
bool m_bSwapped; // wrong endianness?
884
wxDECLARE_NO_COPY_CLASS(wxMsgCatalogFile);
887
// ----------------------------------------------------------------------------
888
// wxMsgCatalogFile class
889
// ----------------------------------------------------------------------------
891
wxMsgCatalogFile::wxMsgCatalogFile()
895
wxMsgCatalogFile::~wxMsgCatalogFile()
899
// open disk file and read in it's contents
900
bool wxMsgCatalogFile::LoadFile(const wxString& filename,
901
wxPluralFormsCalculatorPtr& rPluralFormsCalculator)
903
wxFile fileMsg(filename);
904
if ( !fileMsg.IsOpened() )
907
// get the file size (assume it is less than 4GB...)
908
wxFileOffset lenFile = fileMsg.Length();
909
if ( lenFile == wxInvalidOffset )
912
size_t nSize = wx_truncate_cast(size_t, lenFile);
913
wxASSERT_MSG( nSize == lenFile + size_t(0), wxS("message catalog bigger than 4GB?") );
915
wxMemoryBuffer filedata;
917
// read the whole file in memory
918
if ( fileMsg.Read(filedata.GetWriteBuf(nSize), nSize) != lenFile )
921
filedata.UngetWriteBuf(nSize);
925
DataBuffer::CreateOwned((char*)filedata.release(), nSize),
926
rPluralFormsCalculator
930
wxLogWarning(_("'%s' is not a valid message catalog."), filename.c_str());
938
bool wxMsgCatalogFile::LoadData(const DataBuffer& data,
939
wxPluralFormsCalculatorPtr& rPluralFormsCalculator)
942
bool bValid = data.length() > sizeof(wxMsgCatalogHeader);
944
const wxMsgCatalogHeader *pHeader = (wxMsgCatalogHeader *)data.data();
946
// we'll have to swap all the integers if it's true
947
m_bSwapped = pHeader->magic == MSGCATALOG_MAGIC_SW;
949
// check the magic number
950
bValid = m_bSwapped || pHeader->magic == MSGCATALOG_MAGIC;
954
// it's either too short or has incorrect magic number
955
wxLogWarning(_("Invalid message catalog."));
962
m_numStrings = Swap(pHeader->numStrings);
963
m_pOrigTable = (wxMsgTableEntry *)(data.data() +
964
Swap(pHeader->ofsOrigTable));
965
m_pTransTable = (wxMsgTableEntry *)(data.data() +
966
Swap(pHeader->ofsTransTable));
968
// now parse catalog's header and try to extract catalog charset and
969
// plural forms formula from it:
971
const char* headerData = StringAtOfs(m_pOrigTable, 0);
972
if ( headerData && headerData[0] == '\0' )
974
// Extract the charset:
975
const char * const header = StringAtOfs(m_pTransTable, 0);
977
cset = strstr(header, "Content-Type: text/plain; charset=");
980
cset += 34; // strlen("Content-Type: text/plain; charset=")
982
const char * const csetEnd = strchr(cset, '\n');
985
m_charset = wxString(cset, csetEnd - cset);
986
if ( m_charset == wxS("CHARSET") )
988
// "CHARSET" is not valid charset, but lazy translator
993
// else: incorrectly filled Content-Type header
995
// Extract plural forms:
996
const char * plurals = strstr(header, "Plural-Forms:");
999
plurals += 13; // strlen("Plural-Forms:")
1000
const char * const pluralsEnd = strchr(plurals, '\n');
1003
const size_t pluralsLen = pluralsEnd - plurals;
1004
wxCharBuffer buf(pluralsLen);
1005
strncpy(buf.data(), plurals, pluralsLen);
1006
wxPluralFormsCalculator * const
1007
pCalculator = wxPluralFormsCalculator::make(buf);
1010
rPluralFormsCalculator.reset(pCalculator);
1014
wxLogVerbose(_("Failed to parse Plural-Forms: '%s'"),
1020
if ( !rPluralFormsCalculator.get() )
1021
rPluralFormsCalculator.reset(wxPluralFormsCalculator::make());
1024
// everything is fine
1028
bool wxMsgCatalogFile::FillHash(wxStringToStringHashMap& hash,
1029
const wxString& domain) const
1031
wxUnusedVar(domain); // silence warning in Unicode build
1033
// conversion to use to convert catalog strings to the GUI encoding
1034
wxMBConv *inputConv = NULL;
1035
wxMBConv *inputConvPtr = NULL; // same as inputConv but safely deleteable
1037
if ( !m_charset.empty() )
1039
#if !wxUSE_UNICODE && wxUSE_FONTMAP
1040
// determine if we need any conversion at all
1041
wxFontEncoding encCat = wxFontMapperBase::GetEncodingFromName(m_charset);
1042
if ( encCat != wxLocale::GetSystemEncoding() )
1046
inputConv = new wxCSConv(m_charset);
1049
else // no need or not possible to convert the encoding
1052
// we must somehow convert the narrow strings in the message catalog to
1053
// wide strings, so use the default conversion if we have no charset
1054
inputConv = wxConvCurrent;
1059
wxString msgIdCharset = gs_msgIdCharset[domain];
1061
// conversion to apply to msgid strings before looking them up: we only
1062
// need it if the msgids are neither in 7 bit ASCII nor in the same
1063
// encoding as the catalog
1064
wxCSConv *sourceConv = msgIdCharset.empty() || (msgIdCharset == m_charset)
1066
: new wxCSConv(msgIdCharset);
1067
#endif // !wxUSE_UNICODE
1069
for (size_t32 i = 0; i < m_numStrings; i++)
1071
const char *data = StringAtOfs(m_pOrigTable, i);
1073
return false; // may happen for invalid MO files
1077
msgid = wxString(data, *inputConv);
1079
if ( inputConv && sourceConv )
1080
msgid = wxString(inputConv->cMB2WC(data), *sourceConv);
1083
#endif // wxUSE_UNICODE
1085
data = StringAtOfs(m_pTransTable, i);
1087
return false; // may happen for invalid MO files
1089
size_t length = Swap(m_pTransTable[i].nLen);
1092
while (offset < length)
1094
const char * const str = data + offset;
1098
msgstr = wxString(str, *inputConv);
1101
msgstr = wxString(inputConv->cMB2WC(str), *wxConvUI);
1104
#endif // wxUSE_UNICODE/!wxUSE_UNICODE
1106
if ( !msgstr.empty() )
1108
hash[index == 0 ? msgid : msgid + wxChar(index)] = msgstr;
1112
// IMPORTANT: accesses to the 'data' pointer are valid only for
1113
// the first 'length+1' bytes (GNU specs says that the
1114
// final NUL is not counted in length); using wxStrnlen()
1115
// we make sure we don't access memory beyond the valid range
1116
// (which otherwise may happen for invalid MO files):
1117
offset += wxStrnlen(str, length - offset) + 1;
1125
delete inputConvPtr;
1131
// ----------------------------------------------------------------------------
1132
// wxMsgCatalog class
1133
// ----------------------------------------------------------------------------
1136
wxMsgCatalog::~wxMsgCatalog()
1140
if ( wxConvUI == m_conv )
1142
// we only change wxConvUI if it points to wxConvLocal so we reset
1143
// it back to it too
1144
wxConvUI = &wxConvLocal;
1150
#endif // !wxUSE_UNICODE
1153
wxMsgCatalog *wxMsgCatalog::CreateFromFile(const wxString& filename,
1154
const wxString& domain)
1156
wxScopedPtr<wxMsgCatalog> cat(new wxMsgCatalog(domain));
1158
wxMsgCatalogFile file;
1160
if ( !file.LoadFile(filename, cat->m_pluralFormsCalculator) )
1163
if ( !file.FillHash(cat->m_messages, domain) )
1166
return cat.release();
1170
wxMsgCatalog *wxMsgCatalog::CreateFromData(const wxScopedCharBuffer& data,
1171
const wxString& domain)
1173
wxScopedPtr<wxMsgCatalog> cat(new wxMsgCatalog(domain));
1175
wxMsgCatalogFile file;
1177
if ( !file.LoadData(data, cat->m_pluralFormsCalculator) )
1180
if ( !file.FillHash(cat->m_messages, domain) )
1183
return cat.release();
1186
const wxString *wxMsgCatalog::GetString(const wxString& str, unsigned n) const
1191
index = m_pluralFormsCalculator->evaluate(n);
1193
wxStringToStringHashMap::const_iterator i;
1196
i = m_messages.find(wxString(str) + wxChar(index)); // plural
1200
i = m_messages.find(str);
1203
if ( i != m_messages.end() )
1212
// ----------------------------------------------------------------------------
1214
// ----------------------------------------------------------------------------
1219
wxTranslations *gs_translations = NULL;
1220
bool gs_translationsOwned = false;
1222
} // anonymous namespace
1226
wxTranslations *wxTranslations::Get()
1228
return gs_translations;
1232
void wxTranslations::Set(wxTranslations *t)
1234
if ( gs_translationsOwned )
1235
delete gs_translations;
1236
gs_translations = t;
1237
gs_translationsOwned = true;
1241
void wxTranslations::SetNonOwned(wxTranslations *t)
1243
if ( gs_translationsOwned )
1244
delete gs_translations;
1245
gs_translations = t;
1246
gs_translationsOwned = false;
1250
wxTranslations::wxTranslations()
1253
m_loader = new wxFileTranslationsLoader;
1257
wxTranslations::~wxTranslations()
1261
// free catalogs memory
1262
wxMsgCatalog *pTmpCat;
1263
while ( m_pMsgCat != NULL )
1265
pTmpCat = m_pMsgCat;
1266
m_pMsgCat = m_pMsgCat->m_pNext;
1272
void wxTranslations::SetLoader(wxTranslationsLoader *loader)
1274
wxCHECK_RET( loader, "loader can't be NULL" );
1281
void wxTranslations::SetLanguage(wxLanguage lang)
1283
if ( lang == wxLANGUAGE_DEFAULT )
1286
SetLanguage(wxLocale::GetLanguageCanonicalName(lang));
1289
void wxTranslations::SetLanguage(const wxString& lang)
1295
wxArrayString wxTranslations::GetAvailableTranslations(const wxString& domain) const
1297
wxCHECK_MSG( m_loader, wxArrayString(), "loader can't be NULL" );
1299
return m_loader->GetAvailableTranslations(domain);
1303
bool wxTranslations::AddStdCatalog()
1305
if ( !AddCatalog(wxS("wxstd")) )
1308
// there may be a catalog with toolkit specific overrides, it is not
1309
// an error if this does not exist
1310
wxString port(wxPlatformInfo::Get().GetPortIdName());
1311
if ( !port.empty() )
1313
AddCatalog(port.BeforeFirst(wxS('/')).MakeLower());
1320
bool wxTranslations::AddCatalog(const wxString& domain)
1322
return AddCatalog(domain, wxLANGUAGE_ENGLISH_US);
1326
bool wxTranslations::AddCatalog(const wxString& domain,
1327
wxLanguage msgIdLanguage,
1328
const wxString& msgIdCharset)
1330
gs_msgIdCharset[domain] = msgIdCharset;
1331
return AddCatalog(domain, msgIdLanguage);
1333
#endif // !wxUSE_UNICODE
1335
bool wxTranslations::AddCatalog(const wxString& domain,
1336
wxLanguage msgIdLanguage)
1338
const wxString msgIdLang = wxLocale::GetLanguageCanonicalName(msgIdLanguage);
1339
const wxString domain_lang = ChooseLanguageForDomain(domain, msgIdLang);
1341
if ( domain_lang.empty() )
1343
wxLogTrace(TRACE_I18N,
1344
wxS("no suitable translation for domain '%s' found"),
1349
wxLogTrace(TRACE_I18N,
1350
wxS("adding '%s' translation for domain '%s' (msgid language '%s')"),
1351
domain_lang, domain, msgIdLang);
1353
// It is OK to not load catalog if the msgid language and m_language match,
1354
// in which case we can directly display the texts embedded in program's
1356
if ( msgIdLang == domain_lang )
1359
return LoadCatalog(domain, domain_lang);
1363
bool wxTranslations::LoadCatalog(const wxString& domain, const wxString& lang)
1365
m_loader->GetAvailableTranslations(domain);
1366
wxCHECK_MSG( m_loader, false, "loader can't be NULL" );
1368
wxMsgCatalog *cat = NULL;
1371
// first look for the catalog for this language and the current locale:
1372
// notice that we don't use the system name for the locale as this would
1373
// force us to install catalogs in different locations depending on the
1374
// system but always use the canonical name
1375
wxFontEncoding encSys = wxLocale::GetSystemEncoding();
1376
if ( encSys != wxFONTENCODING_SYSTEM )
1378
wxString fullname(lang);
1379
fullname << wxS('.') << wxFontMapperBase::GetEncodingName(encSys);
1381
cat = m_loader->LoadCatalog(domain, fullname);
1383
#endif // wxUSE_FONTMAP
1387
// Next try: use the provided name language name:
1388
cat = m_loader->LoadCatalog(domain, lang);
1393
// Also try just base locale name: for things like "fr_BE" (Belgium
1394
// French) we should use fall back on plain "fr" if no Belgium-specific
1395
// message catalogs exist
1396
wxString baselang = lang.BeforeFirst('_');
1397
if ( lang != baselang )
1398
cat = m_loader->LoadCatalog(domain, baselang);
1403
// add it to the head of the list so that in GetString it will
1404
// be searched before the catalogs added earlier
1405
cat->m_pNext = m_pMsgCat;
1412
// Nothing worked, the catalog just isn't there
1413
wxLogTrace(TRACE_I18N,
1414
"Catalog \"%s.mo\" not found for language \"%s\".",
1420
// check if the given catalog is loaded
1421
bool wxTranslations::IsLoaded(const wxString& domain) const
1423
return FindCatalog(domain) != NULL;
1427
wxString wxTranslations::ChooseLanguageForDomain(const wxString& WXUNUSED(domain),
1428
const wxString& WXUNUSED(msgIdLang))
1430
// explicitly set language should always be respected
1431
if ( !m_lang.empty() )
1434
// TODO: if the default language is used, pick the best (by comparing
1435
// available languages with user's preferences), instead of blindly
1436
// trusting availability of system language translation
1437
return wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage());
1443
WX_DECLARE_HASH_SET(wxString, ::wxStringHash, ::wxStringEqual,
1444
wxLocaleUntranslatedStrings);
1448
const wxString& wxTranslations::GetUntranslatedString(const wxString& str)
1450
static wxLocaleUntranslatedStrings s_strings;
1452
wxLocaleUntranslatedStrings::iterator i = s_strings.find(str);
1453
if ( i == s_strings.end() )
1454
return *s_strings.insert(str).first;
1460
const wxString& wxTranslations::GetString(const wxString& origString,
1461
const wxString& domain) const
1463
return GetString(origString, origString, UINT_MAX, domain);
1466
const wxString& wxTranslations::GetString(const wxString& origString,
1467
const wxString& origString2,
1469
const wxString& domain) const
1471
if ( origString.empty() )
1472
return GetUntranslatedString(origString);
1474
const wxString *trans = NULL;
1475
wxMsgCatalog *pMsgCat;
1477
if ( !domain.empty() )
1479
pMsgCat = FindCatalog(domain);
1481
// does the catalog exist?
1482
if ( pMsgCat != NULL )
1483
trans = pMsgCat->GetString(origString, n);
1487
// search in all domains
1488
for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext )
1490
trans = pMsgCat->GetString(origString, n);
1491
if ( trans != NULL ) // take the first found
1496
if ( trans == NULL )
1501
"string \"%s\"%s not found in %slocale '%s'.",
1503
(n != UINT_MAX ? wxString::Format("[%ld]", (long)n) : wxString()),
1504
(!domain.empty() ? wxString::Format("domain '%s' ", domain) : wxString()),
1509
return GetUntranslatedString(origString);
1511
return GetUntranslatedString(n == 1 ? origString : origString2);
1518
wxString wxTranslations::GetHeaderValue(const wxString& header,
1519
const wxString& domain) const
1521
if ( header.empty() )
1522
return wxEmptyString;
1524
const wxString *trans = NULL;
1525
wxMsgCatalog *pMsgCat;
1527
if ( !domain.empty() )
1529
pMsgCat = FindCatalog(domain);
1531
// does the catalog exist?
1532
if ( pMsgCat == NULL )
1533
return wxEmptyString;
1535
trans = pMsgCat->GetString(wxEmptyString, UINT_MAX);
1539
// search in all domains
1540
for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext )
1542
trans = pMsgCat->GetString(wxEmptyString, UINT_MAX);
1543
if ( trans != NULL ) // take the first found
1548
if ( !trans || trans->empty() )
1549
return wxEmptyString;
1551
size_t found = trans->find(header);
1552
if ( found == wxString::npos )
1553
return wxEmptyString;
1555
found += header.length() + 2 /* ': ' */;
1557
// Every header is separated by \n
1559
size_t endLine = trans->find(wxS('\n'), found);
1560
size_t len = (endLine == wxString::npos) ?
1561
wxString::npos : (endLine - found);
1563
return trans->substr(found, len);
1567
// find catalog by name in a linked list, return NULL if !found
1568
wxMsgCatalog *wxTranslations::FindCatalog(const wxString& domain) const
1570
// linear search in the linked list
1571
wxMsgCatalog *pMsgCat;
1572
for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext )
1574
if ( pMsgCat->GetDomain() == domain )
1581
// ----------------------------------------------------------------------------
1582
// wxFileTranslationsLoader
1583
// ----------------------------------------------------------------------------
1588
// the list of the directories to search for message catalog files
1589
wxArrayString gs_searchPrefixes;
1591
// return the directories to search for message catalogs under the given
1592
// prefix, separated by wxPATH_SEP
1593
wxString GetMsgCatalogSubdirs(const wxString& prefix, const wxString& lang)
1595
// Search first in Unix-standard prefix/lang/LC_MESSAGES, then in
1596
// prefix/lang and finally in just prefix.
1598
// Note that we use LC_MESSAGES on all platforms and not just Unix, because
1599
// it doesn't cost much to look into one more directory and doing it this
1600
// way has two important benefits:
1601
// a) we don't break compatibility with wx-2.6 and older by stopping to
1602
// look in a directory where the catalogs used to be and thus silently
1603
// breaking apps after they are recompiled against the latest wx
1604
// b) it makes it possible to package app's support files in the same
1605
// way on all target platforms
1606
const wxString pathPrefix = wxFileName(prefix, lang).GetFullPath();
1608
wxString searchPath;
1609
searchPath.reserve(4*pathPrefix.length());
1610
searchPath << pathPrefix << wxFILE_SEP_PATH << "LC_MESSAGES" << wxPATH_SEP
1611
<< prefix << wxFILE_SEP_PATH << wxPATH_SEP
1617
bool HasMsgCatalogInDir(const wxString& dir, const wxString& domain)
1619
return wxFileName(dir, domain, "mo").FileExists() ||
1620
wxFileName(dir + wxFILE_SEP_PATH + "LC_MESSAGES", domain, "mo").FileExists();
1623
// get prefixes to locale directories; if lang is empty, don't point to
1624
// OSX's .lproj bundles
1625
wxArrayString GetSearchPrefixes(const wxString& lang = wxString())
1627
wxArrayString paths;
1629
// first take the entries explicitly added by the program
1630
paths = gs_searchPrefixes;
1633
// then look in the standard location
1637
stdp = wxStandardPaths::Get().GetResourcesDir();
1641
stdp = wxStandardPaths::Get().
1642
GetLocalizedResourcesDir(lang, wxStandardPaths::ResourceCat_Messages);
1644
if ( paths.Index(stdp) == wxNOT_FOUND )
1646
#endif // wxUSE_STDPATHS
1648
// last look in default locations
1650
// LC_PATH is a standard env var containing the search path for the .mo
1652
const char *pszLcPath = wxGetenv("LC_PATH");
1655
const wxString lcp = pszLcPath;
1656
if ( paths.Index(lcp) == wxNOT_FOUND )
1660
// also add the one from where wxWin was installed:
1661
wxString wxp = wxGetInstallPrefix();
1664
wxp += wxS("/share/locale");
1665
if ( paths.Index(wxp) == wxNOT_FOUND )
1673
// construct the search path for the given language
1674
wxString GetFullSearchPath(const wxString& lang)
1676
wxString searchPath;
1677
searchPath.reserve(500);
1679
const wxArrayString prefixes = GetSearchPrefixes(lang);
1681
for ( wxArrayString::const_iterator i = prefixes.begin();
1682
i != prefixes.end();
1685
const wxString p = GetMsgCatalogSubdirs(*i, lang);
1687
if ( !searchPath.empty() )
1688
searchPath += wxPATH_SEP;
1695
} // anonymous namespace
1698
void wxFileTranslationsLoader::AddCatalogLookupPathPrefix(const wxString& prefix)
1700
if ( gs_searchPrefixes.Index(prefix) == wxNOT_FOUND )
1702
gs_searchPrefixes.Add(prefix);
1704
//else: already have it
1708
wxMsgCatalog *wxFileTranslationsLoader::LoadCatalog(const wxString& domain,
1709
const wxString& lang)
1711
wxString searchPath = GetFullSearchPath(lang);
1713
wxLogTrace(TRACE_I18N, wxS("Looking for \"%s.mo\" in search path \"%s\""),
1714
domain, searchPath);
1716
wxFileName fn(domain);
1717
fn.SetExt(wxS("mo"));
1719
wxString strFullName;
1720
if ( !wxFindFileInPath(&strFullName, searchPath, fn.GetFullPath()) )
1723
// open file and read its data
1724
wxLogVerbose(_("using catalog '%s' from '%s'."), domain, strFullName.c_str());
1725
wxLogTrace(TRACE_I18N, wxS("Using catalog \"%s\"."), strFullName.c_str());
1727
return wxMsgCatalog::CreateFromFile(strFullName, domain);
1731
wxArrayString wxFileTranslationsLoader::GetAvailableTranslations(const wxString& domain) const
1733
wxArrayString langs;
1734
const wxArrayString prefixes = GetSearchPrefixes();
1736
wxLogTrace(TRACE_I18N,
1737
"looking for available translations of \"%s\" in search path \"%s\"",
1738
domain, wxJoin(prefixes, wxPATH_SEP[0]));
1740
for ( wxArrayString::const_iterator i = prefixes.begin();
1741
i != prefixes.end();
1747
if ( !dir.Open(*i) )
1751
for ( bool ok = dir.GetFirst(&lang, "", wxDIR_DIRS);
1753
ok = dir.GetNext(&lang) )
1755
const wxString langdir = *i + wxFILE_SEP_PATH + lang;
1756
if ( HasMsgCatalogInDir(langdir, domain) )
1760
if ( lang.EndsWith(".lproj", &rest) )
1764
wxLogTrace(TRACE_I18N,
1765
"found %s translation of \"%s\"", lang, domain);
1766
langs.push_back(lang);
1775
// ----------------------------------------------------------------------------
1776
// wxResourceTranslationsLoader
1777
// ----------------------------------------------------------------------------
1781
wxMsgCatalog *wxResourceTranslationsLoader::LoadCatalog(const wxString& domain,
1782
const wxString& lang)
1784
const void *mo_data = NULL;
1787
const wxString resname = wxString::Format("%s_%s", domain, lang);
1789
if ( !wxLoadUserResource(&mo_data, &mo_size,
1795
wxLogTrace(TRACE_I18N,
1796
"Using catalog from Windows resource \"%s\".", resname);
1798
wxMsgCatalog *cat = wxMsgCatalog::CreateFromData(
1799
wxCharBuffer::CreateNonOwned(static_cast<const char*>(mo_data), mo_size),
1804
wxLogWarning(_("Resource '%s' is not a valid message catalog."), resname);
1813
struct EnumCallbackData
1816
wxArrayString langs;
1819
BOOL CALLBACK EnumTranslations(HMODULE WXUNUSED(hModule),
1820
LPCTSTR WXUNUSED(lpszType),
1824
wxString name(lpszName);
1825
name.MakeLower(); // resource names are case insensitive
1827
EnumCallbackData *data = reinterpret_cast<EnumCallbackData*>(lParam);
1830
if ( name.StartsWith(data->prefix, &lang) && !lang.empty() )
1831
data->langs.push_back(lang);
1833
return TRUE; // continue enumeration
1836
} // anonymous namespace
1839
wxArrayString wxResourceTranslationsLoader::GetAvailableTranslations(const wxString& domain) const
1841
EnumCallbackData data;
1842
data.prefix = domain + "_";
1843
data.prefix.MakeLower(); // resource names are case insensitive
1845
if ( !EnumResourceNames(GetModule(),
1846
GetResourceType().t_str(),
1848
reinterpret_cast<LONG_PTR>(&data)) )
1850
const DWORD err = GetLastError();
1851
if ( err != NO_ERROR && err != ERROR_RESOURCE_TYPE_NOT_FOUND )
1853
wxLogSysError(_("Couldn't enumerate translations"));
1860
#endif // __WINDOWS__
1863
// ----------------------------------------------------------------------------
1864
// wxTranslationsModule module (for destruction of gs_translations)
1865
// ----------------------------------------------------------------------------
1867
class wxTranslationsModule: public wxModule
1869
DECLARE_DYNAMIC_CLASS(wxTranslationsModule)
1871
wxTranslationsModule() {}
1880
if ( gs_translationsOwned )
1881
delete gs_translations;
1882
gs_translations = NULL;
1883
gs_translationsOwned = true;
1887
IMPLEMENT_DYNAMIC_CLASS(wxTranslationsModule, wxModule)
1889
#endif // wxUSE_INTL