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)
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 clas
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
, false, "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();
1745 if ( !dir
.Open(*i
) )
1749 for ( bool ok
= dir
.GetFirst(&lang
, "", wxDIR_DIRS
);
1751 ok
= dir
.GetNext(&lang
) )
1753 const wxString langdir
= *i
+ wxFILE_SEP_PATH
+ lang
;
1754 if ( HasMsgCatalogInDir(langdir
, domain
) )
1758 if ( lang
.EndsWith(".lproj", &rest
) )
1762 wxLogTrace(TRACE_I18N
,
1763 "found %s translation of \"%s\"", lang
, domain
);
1764 langs
.push_back(lang
);
1773 // ----------------------------------------------------------------------------
1774 // wxResourceTranslationsLoader
1775 // ----------------------------------------------------------------------------
1779 wxMsgCatalog
*wxResourceTranslationsLoader::LoadCatalog(const wxString
& domain
,
1780 const wxString
& lang
)
1782 const void *mo_data
= NULL
;
1785 const wxString resname
= wxString::Format("%s_%s", domain
, lang
);
1787 if ( !wxLoadUserResource(&mo_data
, &mo_size
,
1793 wxLogTrace(TRACE_I18N
,
1794 "Using catalog from Windows resource \"%s\".", resname
);
1796 wxMsgCatalog
*cat
= wxMsgCatalog::CreateFromData(
1797 wxCharBuffer::CreateNonOwned(static_cast<const char*>(mo_data
), mo_size
),
1801 wxLogWarning(_("Resource '%s' is not a valid message catalog."), resname
);
1809 struct EnumCallbackData
1812 wxArrayString langs
;
1815 BOOL CALLBACK
EnumTranslations(HMODULE
WXUNUSED(hModule
),
1816 LPCTSTR
WXUNUSED(lpszType
),
1820 wxString
name(lpszName
);
1821 name
.MakeLower(); // resource names are case insensitive
1823 EnumCallbackData
*data
= reinterpret_cast<EnumCallbackData
*>(lParam
);
1826 if ( name
.StartsWith(data
->prefix
, &lang
) && !lang
.empty() )
1827 data
->langs
.push_back(lang
);
1829 return TRUE
; // continue enumeration
1832 } // anonymous namespace
1835 wxArrayString
wxResourceTranslationsLoader::GetAvailableTranslations(const wxString
& domain
) const
1837 EnumCallbackData data
;
1838 data
.prefix
= domain
+ "_";
1839 data
.prefix
.MakeLower(); // resource names are case insensitive
1841 if ( !EnumResourceNames(GetModule(),
1844 reinterpret_cast<LONG_PTR
>(&data
)) )
1846 const DWORD err
= GetLastError();
1847 if ( err
!= NO_ERROR
&& err
!= ERROR_RESOURCE_TYPE_NOT_FOUND
)
1848 wxLogSysError(_("Couldn't enumerate translations"));
1854 #endif // __WINDOWS__
1857 // ----------------------------------------------------------------------------
1858 // wxTranslationsModule module (for destruction of gs_translations)
1859 // ----------------------------------------------------------------------------
1861 class wxTranslationsModule
: public wxModule
1863 DECLARE_DYNAMIC_CLASS(wxTranslationsModule
)
1865 wxTranslationsModule() {}
1874 if ( gs_translationsOwned
)
1875 delete gs_translations
;
1876 gs_translations
= NULL
;
1877 gs_translationsOwned
= true;
1881 IMPLEMENT_DYNAMIC_CLASS(wxTranslationsModule
, wxModule
)
1883 #endif // wxUSE_INTL