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"
45 #include "wx/filename.h"
46 #include "wx/tokenzr.h"
47 #include "wx/fontmap.h"
48 #include "wx/stdpaths.h"
49 #include "wx/hashset.h"
51 // ----------------------------------------------------------------------------
53 // ----------------------------------------------------------------------------
55 typedef wxUint32 size_t32
;
57 // ----------------------------------------------------------------------------
59 // ----------------------------------------------------------------------------
61 // magic number identifying the .mo format file
62 const size_t32 MSGCATALOG_MAGIC
= 0x950412de;
63 const size_t32 MSGCATALOG_MAGIC_SW
= 0xde120495;
65 #define TRACE_I18N wxS("i18n")
67 // ============================================================================
69 // ============================================================================
75 // We need to keep track of (char*) msgids in non-Unicode legacy builds. Instead
76 // of making the public wxMsgCatalog and wxTranslationsLoader APIs ugly, we
77 // store them in this global map.
78 wxStringToStringHashMap gs_msgIdCharset
;
81 } // anonymous namespace
83 // ----------------------------------------------------------------------------
84 // Plural forms parser
85 // ----------------------------------------------------------------------------
91 LogicalOrExpression '?' Expression ':' Expression
95 LogicalAndExpression "||" LogicalOrExpression // to (a || b) || c
99 EqualityExpression "&&" LogicalAndExpression // to (a && b) && c
103 RelationalExpression "==" RelationalExperession
104 RelationalExpression "!=" RelationalExperession
107 RelationalExpression:
108 MultiplicativeExpression '>' MultiplicativeExpression
109 MultiplicativeExpression '<' MultiplicativeExpression
110 MultiplicativeExpression ">=" MultiplicativeExpression
111 MultiplicativeExpression "<=" MultiplicativeExpression
112 MultiplicativeExpression
114 MultiplicativeExpression:
115 PmExpression '%' PmExpression
124 class wxPluralFormsToken
129 T_ERROR
, T_EOF
, T_NUMBER
, T_N
, T_PLURAL
, T_NPLURALS
, T_EQUAL
, T_ASSIGN
,
130 T_GREATER
, T_GREATER_OR_EQUAL
, T_LESS
, T_LESS_OR_EQUAL
,
131 T_REMINDER
, T_NOT_EQUAL
,
132 T_LOGICAL_AND
, T_LOGICAL_OR
, T_QUESTION
, T_COLON
, T_SEMICOLON
,
133 T_LEFT_BRACKET
, T_RIGHT_BRACKET
135 Type
type() const { return m_type
; }
136 void setType(Type type
) { m_type
= type
; }
139 Number
number() const { return m_number
; }
140 void setNumber(Number num
) { m_number
= num
; }
147 class wxPluralFormsScanner
150 wxPluralFormsScanner(const char* s
);
151 const wxPluralFormsToken
& token() const { return m_token
; }
152 bool nextToken(); // returns false if error
155 wxPluralFormsToken m_token
;
158 wxPluralFormsScanner::wxPluralFormsScanner(const char* s
) : m_s(s
)
163 bool wxPluralFormsScanner::nextToken()
165 wxPluralFormsToken::Type type
= wxPluralFormsToken::T_ERROR
;
166 while (isspace((unsigned char) *m_s
))
172 type
= wxPluralFormsToken::T_EOF
;
174 else if (isdigit((unsigned char) *m_s
))
176 wxPluralFormsToken::Number number
= *m_s
++ - '0';
177 while (isdigit((unsigned char) *m_s
))
179 number
= number
* 10 + (*m_s
++ - '0');
181 m_token
.setNumber(number
);
182 type
= wxPluralFormsToken::T_NUMBER
;
184 else if (isalpha((unsigned char) *m_s
))
186 const char* begin
= m_s
++;
187 while (isalnum((unsigned char) *m_s
))
191 size_t size
= m_s
- begin
;
192 if (size
== 1 && memcmp(begin
, "n", size
) == 0)
194 type
= wxPluralFormsToken::T_N
;
196 else if (size
== 6 && memcmp(begin
, "plural", size
) == 0)
198 type
= wxPluralFormsToken::T_PLURAL
;
200 else if (size
== 8 && memcmp(begin
, "nplurals", size
) == 0)
202 type
= wxPluralFormsToken::T_NPLURALS
;
205 else if (*m_s
== '=')
211 type
= wxPluralFormsToken::T_EQUAL
;
215 type
= wxPluralFormsToken::T_ASSIGN
;
218 else if (*m_s
== '>')
224 type
= wxPluralFormsToken::T_GREATER_OR_EQUAL
;
228 type
= wxPluralFormsToken::T_GREATER
;
231 else if (*m_s
== '<')
237 type
= wxPluralFormsToken::T_LESS_OR_EQUAL
;
241 type
= wxPluralFormsToken::T_LESS
;
244 else if (*m_s
== '%')
247 type
= wxPluralFormsToken::T_REMINDER
;
249 else if (*m_s
== '!' && m_s
[1] == '=')
252 type
= wxPluralFormsToken::T_NOT_EQUAL
;
254 else if (*m_s
== '&' && m_s
[1] == '&')
257 type
= wxPluralFormsToken::T_LOGICAL_AND
;
259 else if (*m_s
== '|' && m_s
[1] == '|')
262 type
= wxPluralFormsToken::T_LOGICAL_OR
;
264 else if (*m_s
== '?')
267 type
= wxPluralFormsToken::T_QUESTION
;
269 else if (*m_s
== ':')
272 type
= wxPluralFormsToken::T_COLON
;
273 } else if (*m_s
== ';') {
275 type
= wxPluralFormsToken::T_SEMICOLON
;
277 else if (*m_s
== '(')
280 type
= wxPluralFormsToken::T_LEFT_BRACKET
;
282 else if (*m_s
== ')')
285 type
= wxPluralFormsToken::T_RIGHT_BRACKET
;
287 m_token
.setType(type
);
288 return type
!= wxPluralFormsToken::T_ERROR
;
291 class wxPluralFormsNode
;
293 // NB: Can't use wxDEFINE_SCOPED_PTR_TYPE because wxPluralFormsNode is not
294 // fully defined yet:
295 class wxPluralFormsNodePtr
298 wxPluralFormsNodePtr(wxPluralFormsNode
*p
= NULL
) : m_p(p
) {}
299 ~wxPluralFormsNodePtr();
300 wxPluralFormsNode
& operator*() const { return *m_p
; }
301 wxPluralFormsNode
* operator->() const { return m_p
; }
302 wxPluralFormsNode
* get() const { return m_p
; }
303 wxPluralFormsNode
* release();
304 void reset(wxPluralFormsNode
*p
);
307 wxPluralFormsNode
*m_p
;
310 class wxPluralFormsNode
313 wxPluralFormsNode(const wxPluralFormsToken
& token
) : m_token(token
) {}
314 const wxPluralFormsToken
& token() const { return m_token
; }
315 const wxPluralFormsNode
* node(unsigned i
) const
316 { return m_nodes
[i
].get(); }
317 void setNode(unsigned i
, wxPluralFormsNode
* n
);
318 wxPluralFormsNode
* releaseNode(unsigned i
);
319 wxPluralFormsToken::Number
evaluate(wxPluralFormsToken::Number n
) const;
322 wxPluralFormsToken m_token
;
323 wxPluralFormsNodePtr m_nodes
[3];
326 wxPluralFormsNodePtr::~wxPluralFormsNodePtr()
330 wxPluralFormsNode
* wxPluralFormsNodePtr::release()
332 wxPluralFormsNode
*p
= m_p
;
336 void wxPluralFormsNodePtr::reset(wxPluralFormsNode
*p
)
346 void wxPluralFormsNode::setNode(unsigned i
, wxPluralFormsNode
* n
)
351 wxPluralFormsNode
* wxPluralFormsNode::releaseNode(unsigned i
)
353 return m_nodes
[i
].release();
356 wxPluralFormsToken::Number
357 wxPluralFormsNode::evaluate(wxPluralFormsToken::Number n
) const
359 switch (token().type())
362 case wxPluralFormsToken::T_NUMBER
:
363 return token().number();
364 case wxPluralFormsToken::T_N
:
367 case wxPluralFormsToken::T_EQUAL
:
368 return node(0)->evaluate(n
) == node(1)->evaluate(n
);
369 case wxPluralFormsToken::T_NOT_EQUAL
:
370 return node(0)->evaluate(n
) != node(1)->evaluate(n
);
371 case wxPluralFormsToken::T_GREATER
:
372 return node(0)->evaluate(n
) > node(1)->evaluate(n
);
373 case wxPluralFormsToken::T_GREATER_OR_EQUAL
:
374 return node(0)->evaluate(n
) >= node(1)->evaluate(n
);
375 case wxPluralFormsToken::T_LESS
:
376 return node(0)->evaluate(n
) < node(1)->evaluate(n
);
377 case wxPluralFormsToken::T_LESS_OR_EQUAL
:
378 return node(0)->evaluate(n
) <= node(1)->evaluate(n
);
379 case wxPluralFormsToken::T_REMINDER
:
381 wxPluralFormsToken::Number number
= node(1)->evaluate(n
);
384 return node(0)->evaluate(n
) % number
;
391 case wxPluralFormsToken::T_LOGICAL_AND
:
392 return node(0)->evaluate(n
) && node(1)->evaluate(n
);
393 case wxPluralFormsToken::T_LOGICAL_OR
:
394 return node(0)->evaluate(n
) || node(1)->evaluate(n
);
396 case wxPluralFormsToken::T_QUESTION
:
397 return node(0)->evaluate(n
)
398 ? node(1)->evaluate(n
)
399 : node(2)->evaluate(n
);
406 class wxPluralFormsCalculator
409 wxPluralFormsCalculator() : m_nplurals(0), m_plural(0) {}
411 // input: number, returns msgstr index
412 int evaluate(int n
) const;
414 // input: text after "Plural-Forms:" (e.g. "nplurals=2; plural=(n != 1);"),
415 // if s == 0, creates default handler
416 // returns 0 if error
417 static wxPluralFormsCalculator
* make(const char* s
= 0);
419 ~wxPluralFormsCalculator() {}
421 void init(wxPluralFormsToken::Number nplurals
, wxPluralFormsNode
* plural
);
424 wxPluralFormsToken::Number m_nplurals
;
425 wxPluralFormsNodePtr m_plural
;
428 wxDEFINE_SCOPED_PTR(wxPluralFormsCalculator
, wxPluralFormsCalculatorPtr
)
430 void wxPluralFormsCalculator::init(wxPluralFormsToken::Number nplurals
,
431 wxPluralFormsNode
* plural
)
433 m_nplurals
= nplurals
;
434 m_plural
.reset(plural
);
437 int wxPluralFormsCalculator::evaluate(int n
) const
439 if (m_plural
.get() == 0)
443 wxPluralFormsToken::Number number
= m_plural
->evaluate(n
);
444 if (number
< 0 || number
> m_nplurals
)
452 class wxPluralFormsParser
455 wxPluralFormsParser(wxPluralFormsScanner
& scanner
) : m_scanner(scanner
) {}
456 bool parse(wxPluralFormsCalculator
& rCalculator
);
459 wxPluralFormsNode
* parsePlural();
460 // stops at T_SEMICOLON, returns 0 if error
461 wxPluralFormsScanner
& m_scanner
;
462 const wxPluralFormsToken
& token() const;
465 wxPluralFormsNode
* expression();
466 wxPluralFormsNode
* logicalOrExpression();
467 wxPluralFormsNode
* logicalAndExpression();
468 wxPluralFormsNode
* equalityExpression();
469 wxPluralFormsNode
* multiplicativeExpression();
470 wxPluralFormsNode
* relationalExpression();
471 wxPluralFormsNode
* pmExpression();
474 bool wxPluralFormsParser::parse(wxPluralFormsCalculator
& rCalculator
)
476 if (token().type() != wxPluralFormsToken::T_NPLURALS
)
480 if (token().type() != wxPluralFormsToken::T_ASSIGN
)
484 if (token().type() != wxPluralFormsToken::T_NUMBER
)
486 wxPluralFormsToken::Number nplurals
= token().number();
489 if (token().type() != wxPluralFormsToken::T_SEMICOLON
)
493 if (token().type() != wxPluralFormsToken::T_PLURAL
)
497 if (token().type() != wxPluralFormsToken::T_ASSIGN
)
501 wxPluralFormsNode
* plural
= parsePlural();
504 if (token().type() != wxPluralFormsToken::T_SEMICOLON
)
508 if (token().type() != wxPluralFormsToken::T_EOF
)
510 rCalculator
.init(nplurals
, plural
);
514 wxPluralFormsNode
* wxPluralFormsParser::parsePlural()
516 wxPluralFormsNode
* p
= expression();
521 wxPluralFormsNodePtr
n(p
);
522 if (token().type() != wxPluralFormsToken::T_SEMICOLON
)
529 const wxPluralFormsToken
& wxPluralFormsParser::token() const
531 return m_scanner
.token();
534 bool wxPluralFormsParser::nextToken()
536 if (!m_scanner
.nextToken())
541 wxPluralFormsNode
* wxPluralFormsParser::expression()
543 wxPluralFormsNode
* p
= logicalOrExpression();
546 wxPluralFormsNodePtr
n(p
);
547 if (token().type() == wxPluralFormsToken::T_QUESTION
)
549 wxPluralFormsNodePtr
qn(new wxPluralFormsNode(token()));
560 if (token().type() != wxPluralFormsToken::T_COLON
)
574 qn
->setNode(0, n
.release());
580 wxPluralFormsNode
*wxPluralFormsParser::logicalOrExpression()
582 wxPluralFormsNode
* p
= logicalAndExpression();
585 wxPluralFormsNodePtr
ln(p
);
586 if (token().type() == wxPluralFormsToken::T_LOGICAL_OR
)
588 wxPluralFormsNodePtr
un(new wxPluralFormsNode(token()));
593 p
= logicalOrExpression();
598 wxPluralFormsNodePtr
rn(p
); // right
599 if (rn
->token().type() == wxPluralFormsToken::T_LOGICAL_OR
)
601 // see logicalAndExpression comment
602 un
->setNode(0, ln
.release());
603 un
->setNode(1, rn
->releaseNode(0));
604 rn
->setNode(0, un
.release());
609 un
->setNode(0, ln
.release());
610 un
->setNode(1, rn
.release());
616 wxPluralFormsNode
* wxPluralFormsParser::logicalAndExpression()
618 wxPluralFormsNode
* p
= equalityExpression();
621 wxPluralFormsNodePtr
ln(p
); // left
622 if (token().type() == wxPluralFormsToken::T_LOGICAL_AND
)
624 wxPluralFormsNodePtr
un(new wxPluralFormsNode(token())); // up
629 p
= logicalAndExpression();
634 wxPluralFormsNodePtr
rn(p
); // right
635 if (rn
->token().type() == wxPluralFormsToken::T_LOGICAL_AND
)
637 // transform 1 && (2 && 3) -> (1 && 2) && 3
641 un
->setNode(0, ln
.release());
642 un
->setNode(1, rn
->releaseNode(0));
643 rn
->setNode(0, un
.release());
647 un
->setNode(0, ln
.release());
648 un
->setNode(1, rn
.release());
654 wxPluralFormsNode
* wxPluralFormsParser::equalityExpression()
656 wxPluralFormsNode
* p
= relationalExpression();
659 wxPluralFormsNodePtr
n(p
);
660 if (token().type() == wxPluralFormsToken::T_EQUAL
661 || token().type() == wxPluralFormsToken::T_NOT_EQUAL
)
663 wxPluralFormsNodePtr
qn(new wxPluralFormsNode(token()));
668 p
= relationalExpression();
674 qn
->setNode(0, n
.release());
680 wxPluralFormsNode
* wxPluralFormsParser::relationalExpression()
682 wxPluralFormsNode
* p
= multiplicativeExpression();
685 wxPluralFormsNodePtr
n(p
);
686 if (token().type() == wxPluralFormsToken::T_GREATER
687 || token().type() == wxPluralFormsToken::T_LESS
688 || token().type() == wxPluralFormsToken::T_GREATER_OR_EQUAL
689 || token().type() == wxPluralFormsToken::T_LESS_OR_EQUAL
)
691 wxPluralFormsNodePtr
qn(new wxPluralFormsNode(token()));
696 p
= multiplicativeExpression();
702 qn
->setNode(0, n
.release());
708 wxPluralFormsNode
* wxPluralFormsParser::multiplicativeExpression()
710 wxPluralFormsNode
* p
= pmExpression();
713 wxPluralFormsNodePtr
n(p
);
714 if (token().type() == wxPluralFormsToken::T_REMINDER
)
716 wxPluralFormsNodePtr
qn(new wxPluralFormsNode(token()));
727 qn
->setNode(0, n
.release());
733 wxPluralFormsNode
* wxPluralFormsParser::pmExpression()
735 wxPluralFormsNodePtr n
;
736 if (token().type() == wxPluralFormsToken::T_N
737 || token().type() == wxPluralFormsToken::T_NUMBER
)
739 n
.reset(new wxPluralFormsNode(token()));
745 else if (token().type() == wxPluralFormsToken::T_LEFT_BRACKET
) {
750 wxPluralFormsNode
* p
= expression();
756 if (token().type() != wxPluralFormsToken::T_RIGHT_BRACKET
)
772 wxPluralFormsCalculator
* wxPluralFormsCalculator::make(const char* s
)
774 wxPluralFormsCalculatorPtr
calculator(new wxPluralFormsCalculator
);
777 wxPluralFormsScanner
scanner(s
);
778 wxPluralFormsParser
p(scanner
);
779 if (!p
.parse(*calculator
))
784 return calculator
.release();
790 // ----------------------------------------------------------------------------
791 // wxMsgCatalogFile corresponds to one disk-file message catalog.
793 // This is a "low-level" class and is used only by wxMsgCatalog
794 // NOTE: for the documentation of the binary catalog (.MO) files refer to
795 // the GNU gettext manual:
796 // http://www.gnu.org/software/autoconf/manual/gettext/MO-Files.html
797 // ----------------------------------------------------------------------------
799 class wxMsgCatalogFile
802 typedef wxScopedCharBuffer DataBuffer
;
808 // load the catalog from disk
809 bool LoadFile(const wxString
& filename
,
810 wxPluralFormsCalculatorPtr
& rPluralFormsCalculator
);
811 bool LoadData(const DataBuffer
& data
,
812 wxPluralFormsCalculatorPtr
& rPluralFormsCalculator
);
814 // fills the hash with string-translation pairs
815 bool FillHash(wxStringToStringHashMap
& hash
, const wxString
& domain
) const;
817 // return the charset of the strings in this catalog or empty string if
819 wxString
GetCharset() const { return m_charset
; }
822 // this implementation is binary compatible with GNU gettext() version 0.10
824 // an entry in the string table
825 struct wxMsgTableEntry
827 size_t32 nLen
; // length of the string
828 size_t32 ofsString
; // pointer to the string
831 // header of a .mo file
832 struct wxMsgCatalogHeader
834 size_t32 magic
, // offset +00: magic id
835 revision
, // +04: revision
836 numStrings
; // +08: number of strings in the file
837 size_t32 ofsOrigTable
, // +0C: start of original string table
838 ofsTransTable
; // +10: start of translated string table
839 size_t32 nHashSize
, // +14: hash table size
840 ofsHashTable
; // +18: offset of hash table start
843 // all data is stored here
847 size_t32 m_numStrings
; // number of strings in this domain
848 wxMsgTableEntry
*m_pOrigTable
, // pointer to original strings
849 *m_pTransTable
; // translated
851 wxString m_charset
; // from the message catalog header
854 // swap the 2 halves of 32 bit integer if needed
855 size_t32
Swap(size_t32 ui
) const
857 return m_bSwapped
? (ui
<< 24) | ((ui
& 0xff00) << 8) |
858 ((ui
>> 8) & 0xff00) | (ui
>> 24)
862 const char *StringAtOfs(wxMsgTableEntry
*pTable
, size_t32 n
) const
864 const wxMsgTableEntry
* const ent
= pTable
+ n
;
866 // this check could fail for a corrupt message catalog
867 size_t32 ofsString
= Swap(ent
->ofsString
);
868 if ( ofsString
+ Swap(ent
->nLen
) > m_data
.length())
873 return m_data
.data() + ofsString
;
876 bool m_bSwapped
; // wrong endianness?
878 wxDECLARE_NO_COPY_CLASS(wxMsgCatalogFile
);
881 // ----------------------------------------------------------------------------
882 // wxMsgCatalogFile clas
883 // ----------------------------------------------------------------------------
885 wxMsgCatalogFile::wxMsgCatalogFile()
889 wxMsgCatalogFile::~wxMsgCatalogFile()
893 // open disk file and read in it's contents
894 bool wxMsgCatalogFile::LoadFile(const wxString
& filename
,
895 wxPluralFormsCalculatorPtr
& rPluralFormsCalculator
)
897 wxFile
fileMsg(filename
);
898 if ( !fileMsg
.IsOpened() )
901 // get the file size (assume it is less than 4Gb...)
902 wxFileOffset lenFile
= fileMsg
.Length();
903 if ( lenFile
== wxInvalidOffset
)
906 size_t nSize
= wx_truncate_cast(size_t, lenFile
);
907 wxASSERT_MSG( nSize
== lenFile
+ size_t(0), wxS("message catalog bigger than 4GB?") );
909 wxMemoryBuffer filedata
;
911 // read the whole file in memory
912 if ( fileMsg
.Read(filedata
.GetWriteBuf(nSize
), nSize
) != lenFile
)
915 filedata
.UngetWriteBuf(nSize
);
919 DataBuffer::CreateOwned((char*)filedata
.release(), nSize
),
920 rPluralFormsCalculator
924 wxLogWarning(_("'%s' is not a valid message catalog."), filename
.c_str());
932 bool wxMsgCatalogFile::LoadData(const DataBuffer
& data
,
933 wxPluralFormsCalculatorPtr
& rPluralFormsCalculator
)
936 bool bValid
= data
.length() > sizeof(wxMsgCatalogHeader
);
938 const wxMsgCatalogHeader
*pHeader
= (wxMsgCatalogHeader
*)data
.data();
940 // we'll have to swap all the integers if it's true
941 m_bSwapped
= pHeader
->magic
== MSGCATALOG_MAGIC_SW
;
943 // check the magic number
944 bValid
= m_bSwapped
|| pHeader
->magic
== MSGCATALOG_MAGIC
;
948 // it's either too short or has incorrect magic number
949 wxLogWarning(_("Invalid message catalog."));
956 m_numStrings
= Swap(pHeader
->numStrings
);
957 m_pOrigTable
= (wxMsgTableEntry
*)(data
.data() +
958 Swap(pHeader
->ofsOrigTable
));
959 m_pTransTable
= (wxMsgTableEntry
*)(data
.data() +
960 Swap(pHeader
->ofsTransTable
));
962 // now parse catalog's header and try to extract catalog charset and
963 // plural forms formula from it:
965 const char* headerData
= StringAtOfs(m_pOrigTable
, 0);
966 if ( headerData
&& headerData
[0] == '\0' )
968 // Extract the charset:
969 const char * const header
= StringAtOfs(m_pTransTable
, 0);
971 cset
= strstr(header
, "Content-Type: text/plain; charset=");
974 cset
+= 34; // strlen("Content-Type: text/plain; charset=")
976 const char * const csetEnd
= strchr(cset
, '\n');
979 m_charset
= wxString(cset
, csetEnd
- cset
);
980 if ( m_charset
== wxS("CHARSET") )
982 // "CHARSET" is not valid charset, but lazy translator
987 // else: incorrectly filled Content-Type header
989 // Extract plural forms:
990 const char * plurals
= strstr(header
, "Plural-Forms:");
993 plurals
+= 13; // strlen("Plural-Forms:")
994 const char * const pluralsEnd
= strchr(plurals
, '\n');
997 const size_t pluralsLen
= pluralsEnd
- plurals
;
998 wxCharBuffer
buf(pluralsLen
);
999 strncpy(buf
.data(), plurals
, pluralsLen
);
1000 wxPluralFormsCalculator
* const
1001 pCalculator
= wxPluralFormsCalculator::make(buf
);
1004 rPluralFormsCalculator
.reset(pCalculator
);
1008 wxLogVerbose(_("Failed to parse Plural-Forms: '%s'"),
1014 if ( !rPluralFormsCalculator
.get() )
1015 rPluralFormsCalculator
.reset(wxPluralFormsCalculator::make());
1018 // everything is fine
1022 bool wxMsgCatalogFile::FillHash(wxStringToStringHashMap
& hash
,
1023 const wxString
& domain
) const
1025 wxUnusedVar(domain
); // silence warning in Unicode build
1027 // conversion to use to convert catalog strings to the GUI encoding
1028 wxMBConv
*inputConv
= NULL
;
1029 wxMBConv
*inputConvPtr
= NULL
; // same as inputConv but safely deleteable
1031 if ( !m_charset
.empty() )
1033 #if !wxUSE_UNICODE && wxUSE_FONTMAP
1034 // determine if we need any conversion at all
1035 wxFontEncoding encCat
= wxFontMapperBase::GetEncodingFromName(m_charset
);
1036 if ( encCat
!= wxLocale::GetSystemEncoding() )
1040 inputConv
= new wxCSConv(m_charset
);
1043 else // no need or not possible to convert the encoding
1046 // we must somehow convert the narrow strings in the message catalog to
1047 // wide strings, so use the default conversion if we have no charset
1048 inputConv
= wxConvCurrent
;
1053 wxString msgIdCharset
= gs_msgIdCharset
[domain
];
1055 // conversion to apply to msgid strings before looking them up: we only
1056 // need it if the msgids are neither in 7 bit ASCII nor in the same
1057 // encoding as the catalog
1058 wxCSConv
*sourceConv
= msgIdCharset
.empty() || (msgIdCharset
== m_charset
)
1060 : new wxCSConv(msgIdCharset
);
1061 #endif // !wxUSE_UNICODE
1063 for (size_t32 i
= 0; i
< m_numStrings
; i
++)
1065 const char *data
= StringAtOfs(m_pOrigTable
, i
);
1067 return false; // may happen for invalid MO files
1071 msgid
= wxString(data
, *inputConv
);
1073 if ( inputConv
&& sourceConv
)
1074 msgid
= wxString(inputConv
->cMB2WC(data
), *sourceConv
);
1077 #endif // wxUSE_UNICODE
1079 data
= StringAtOfs(m_pTransTable
, i
);
1081 return false; // may happen for invalid MO files
1083 size_t length
= Swap(m_pTransTable
[i
].nLen
);
1086 while (offset
< length
)
1088 const char * const str
= data
+ offset
;
1092 msgstr
= wxString(str
, *inputConv
);
1095 msgstr
= wxString(inputConv
->cMB2WC(str
), *wxConvUI
);
1098 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
1100 if ( !msgstr
.empty() )
1102 hash
[index
== 0 ? msgid
: msgid
+ wxChar(index
)] = msgstr
;
1106 // IMPORTANT: accesses to the 'data' pointer are valid only for
1107 // the first 'length+1' bytes (GNU specs says that the
1108 // final NUL is not counted in length); using wxStrnlen()
1109 // we make sure we don't access memory beyond the valid range
1110 // (which otherwise may happen for invalid MO files):
1111 offset
+= wxStrnlen(str
, length
- offset
) + 1;
1119 delete inputConvPtr
;
1125 // ----------------------------------------------------------------------------
1126 // wxMsgCatalog class
1127 // ----------------------------------------------------------------------------
1130 wxMsgCatalog::~wxMsgCatalog()
1134 if ( wxConvUI
== m_conv
)
1136 // we only change wxConvUI if it points to wxConvLocal so we reset
1137 // it back to it too
1138 wxConvUI
= &wxConvLocal
;
1144 #endif // !wxUSE_UNICODE
1147 wxMsgCatalog
*wxMsgCatalog::CreateFromFile(const wxString
& filename
,
1148 const wxString
& domain
)
1150 wxScopedPtr
<wxMsgCatalog
> cat(new wxMsgCatalog(domain
));
1152 wxMsgCatalogFile file
;
1154 if ( !file
.LoadFile(filename
, cat
->m_pluralFormsCalculator
) )
1157 if ( !file
.FillHash(cat
->m_messages
, domain
) )
1160 return cat
.release();
1164 wxMsgCatalog
*wxMsgCatalog::CreateFromData(const wxScopedCharBuffer
& data
,
1165 const wxString
& domain
)
1167 wxScopedPtr
<wxMsgCatalog
> cat(new wxMsgCatalog(domain
));
1169 wxMsgCatalogFile file
;
1171 if ( !file
.LoadData(data
, cat
->m_pluralFormsCalculator
) )
1174 if ( !file
.FillHash(cat
->m_messages
, domain
) )
1177 return cat
.release();
1180 const wxString
*wxMsgCatalog::GetString(const wxString
& str
, unsigned n
) const
1185 index
= m_pluralFormsCalculator
->evaluate(n
);
1187 wxStringToStringHashMap::const_iterator i
;
1190 i
= m_messages
.find(wxString(str
) + wxChar(index
)); // plural
1194 i
= m_messages
.find(str
);
1197 if ( i
!= m_messages
.end() )
1206 // ----------------------------------------------------------------------------
1208 // ----------------------------------------------------------------------------
1213 wxTranslations
*gs_translations
= NULL
;
1214 bool gs_translationsOwned
= false;
1216 } // anonymous namespace
1220 wxTranslations
*wxTranslations::Get()
1222 return gs_translations
;
1226 void wxTranslations::Set(wxTranslations
*t
)
1228 if ( gs_translationsOwned
)
1229 delete gs_translations
;
1230 gs_translations
= t
;
1231 gs_translationsOwned
= true;
1235 void wxTranslations::SetNonOwned(wxTranslations
*t
)
1237 if ( gs_translationsOwned
)
1238 delete gs_translations
;
1239 gs_translations
= t
;
1240 gs_translationsOwned
= false;
1244 wxTranslations::wxTranslations()
1247 m_loader
= new wxFileTranslationsLoader
;
1251 wxTranslations::~wxTranslations()
1255 // free catalogs memory
1256 wxMsgCatalog
*pTmpCat
;
1257 while ( m_pMsgCat
!= NULL
)
1259 pTmpCat
= m_pMsgCat
;
1260 m_pMsgCat
= m_pMsgCat
->m_pNext
;
1266 void wxTranslations::SetLoader(wxTranslationsLoader
*loader
)
1268 wxCHECK_RET( loader
, "loader can't be NULL" );
1275 void wxTranslations::SetLanguage(wxLanguage lang
)
1277 if ( lang
== wxLANGUAGE_DEFAULT
)
1280 SetLanguage(wxLocale::GetLanguageCanonicalName(lang
));
1283 void wxTranslations::SetLanguage(const wxString
& lang
)
1289 bool wxTranslations::AddStdCatalog()
1291 if ( !AddCatalog(wxS("wxstd")) )
1294 // there may be a catalog with toolkit specific overrides, it is not
1295 // an error if this does not exist
1296 wxString
port(wxPlatformInfo::Get().GetPortIdName());
1297 if ( !port
.empty() )
1299 AddCatalog(port
.BeforeFirst(wxS('/')).MakeLower());
1306 bool wxTranslations::AddCatalog(const wxString
& domain
)
1308 return AddCatalog(domain
, wxLANGUAGE_ENGLISH_US
);
1312 bool wxTranslations::AddCatalog(const wxString
& domain
,
1313 wxLanguage msgIdLanguage
,
1314 const wxString
& msgIdCharset
)
1316 gs_msgIdCharset
[domain
] = msgIdCharset
;
1317 return AddCatalog(domain
, msgIdLanguage
);
1319 #endif // !wxUSE_UNICODE
1321 bool wxTranslations::AddCatalog(const wxString
& domain
,
1322 wxLanguage msgIdLanguage
)
1324 const wxString msgIdLang
= wxLocale::GetLanguageCanonicalName(msgIdLanguage
);
1325 const wxString domain_lang
= ChooseLanguageForDomain(domain
, msgIdLang
);
1327 if ( domain_lang
.empty() )
1329 wxLogTrace(TRACE_I18N
,
1330 wxS("no suitable translation for domain '%s' found"),
1335 wxLogTrace(TRACE_I18N
,
1336 wxS("adding '%s' translation for domain '%s' (msgid language '%s')"),
1337 domain_lang
, domain
, msgIdLang
);
1339 // It is OK to not load catalog if the msgid language and m_language match,
1340 // in which case we can directly display the texts embedded in program's
1342 if ( msgIdLang
== domain_lang
)
1345 return LoadCatalog(domain
, domain_lang
);
1349 bool wxTranslations::LoadCatalog(const wxString
& domain
, const wxString
& lang
)
1351 wxCHECK_MSG( m_loader
, false, "loader can't be NULL" );
1353 wxMsgCatalog
*cat
= NULL
;
1356 // first look for the catalog for this language and the current locale:
1357 // notice that we don't use the system name for the locale as this would
1358 // force us to install catalogs in different locations depending on the
1359 // system but always use the canonical name
1360 wxFontEncoding encSys
= wxLocale::GetSystemEncoding();
1361 if ( encSys
!= wxFONTENCODING_SYSTEM
)
1363 wxString
fullname(lang
);
1364 fullname
<< wxS('.') << wxFontMapperBase::GetEncodingName(encSys
);
1366 cat
= m_loader
->LoadCatalog(domain
, fullname
);
1368 #endif // wxUSE_FONTMAP
1372 // Next try: use the provided name language name:
1373 cat
= m_loader
->LoadCatalog(domain
, lang
);
1378 // Also try just base locale name: for things like "fr_BE" (Belgium
1379 // French) we should use fall back on plain "fr" if no Belgium-specific
1380 // message catalogs exist
1381 wxString baselang
= lang
.BeforeFirst('_');
1382 if ( lang
!= baselang
)
1383 cat
= m_loader
->LoadCatalog(domain
, baselang
);
1388 // add it to the head of the list so that in GetString it will
1389 // be searched before the catalogs added earlier
1390 cat
->m_pNext
= m_pMsgCat
;
1397 // Nothing worked, the catalog just isn't there
1398 wxLogTrace(TRACE_I18N
,
1399 "Catalog \"%s.mo\" not found for language \"%s\".",
1405 // check if the given catalog is loaded
1406 bool wxTranslations::IsLoaded(const wxString
& domain
) const
1408 return FindCatalog(domain
) != NULL
;
1412 wxString
wxTranslations::ChooseLanguageForDomain(const wxString
& WXUNUSED(domain
),
1413 const wxString
& WXUNUSED(msgIdLang
))
1415 // explicitly set language should always be respected
1416 if ( !m_lang
.empty() )
1419 // TODO: if the default language is used, pick the best (by comparing
1420 // available languages with user's preferences), instead of blindly
1421 // trusting availability of system language translation
1422 return wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage());
1428 WX_DECLARE_HASH_SET(wxString
, wxStringHash
, wxStringEqual
,
1429 wxLocaleUntranslatedStrings
);
1433 const wxString
& wxTranslations::GetUntranslatedString(const wxString
& str
)
1435 static wxLocaleUntranslatedStrings s_strings
;
1437 wxLocaleUntranslatedStrings::iterator i
= s_strings
.find(str
);
1438 if ( i
== s_strings
.end() )
1439 return *s_strings
.insert(str
).first
;
1445 const wxString
& wxTranslations::GetString(const wxString
& origString
,
1446 const wxString
& domain
) const
1448 return GetString(origString
, origString
, UINT_MAX
, domain
);
1451 const wxString
& wxTranslations::GetString(const wxString
& origString
,
1452 const wxString
& origString2
,
1454 const wxString
& domain
) const
1456 if ( origString
.empty() )
1457 return GetUntranslatedString(origString
);
1459 const wxString
*trans
= NULL
;
1460 wxMsgCatalog
*pMsgCat
;
1462 if ( !domain
.empty() )
1464 pMsgCat
= FindCatalog(domain
);
1466 // does the catalog exist?
1467 if ( pMsgCat
!= NULL
)
1468 trans
= pMsgCat
->GetString(origString
, n
);
1472 // search in all domains
1473 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
)
1475 trans
= pMsgCat
->GetString(origString
, n
);
1476 if ( trans
!= NULL
) // take the first found
1481 if ( trans
== NULL
)
1486 "string \"%s\"%s not found in %slocale '%s'.",
1488 n
!= UINT_MAX
? wxString::Format("[%ld]", (long)n
) : wxString(),
1489 !domain
.empty() ? wxString::Format("domain '%s' ", domain
) : wxString(),
1494 return GetUntranslatedString(origString
);
1496 return GetUntranslatedString(n
== 1 ? origString
: origString2
);
1503 wxString
wxTranslations::GetHeaderValue(const wxString
& header
,
1504 const wxString
& domain
) const
1506 if ( header
.empty() )
1507 return wxEmptyString
;
1509 const wxString
*trans
= NULL
;
1510 wxMsgCatalog
*pMsgCat
;
1512 if ( !domain
.empty() )
1514 pMsgCat
= FindCatalog(domain
);
1516 // does the catalog exist?
1517 if ( pMsgCat
== NULL
)
1518 return wxEmptyString
;
1520 trans
= pMsgCat
->GetString(wxEmptyString
, UINT_MAX
);
1524 // search in all domains
1525 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
)
1527 trans
= pMsgCat
->GetString(wxEmptyString
, UINT_MAX
);
1528 if ( trans
!= NULL
) // take the first found
1533 if ( !trans
|| trans
->empty() )
1534 return wxEmptyString
;
1536 size_t found
= trans
->find(header
);
1537 if ( found
== wxString::npos
)
1538 return wxEmptyString
;
1540 found
+= header
.length() + 2 /* ': ' */;
1542 // Every header is separated by \n
1544 size_t endLine
= trans
->find(wxS('\n'), found
);
1545 size_t len
= (endLine
== wxString::npos
) ?
1546 wxString::npos
: (endLine
- found
);
1548 return trans
->substr(found
, len
);
1552 // find catalog by name in a linked list, return NULL if !found
1553 wxMsgCatalog
*wxTranslations::FindCatalog(const wxString
& domain
) const
1555 // linear search in the linked list
1556 wxMsgCatalog
*pMsgCat
;
1557 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
)
1559 if ( pMsgCat
->GetDomain() == domain
)
1566 // ----------------------------------------------------------------------------
1567 // wxFileTranslationsLoader
1568 // ----------------------------------------------------------------------------
1573 // the list of the directories to search for message catalog files
1574 wxArrayString gs_searchPrefixes
;
1576 // return the directories to search for message catalogs under the given
1577 // prefix, separated by wxPATH_SEP
1578 wxString
GetMsgCatalogSubdirs(const wxString
& prefix
, const wxString
& lang
)
1580 // Search first in Unix-standard prefix/lang/LC_MESSAGES, then in
1581 // prefix/lang and finally in just prefix.
1583 // Note that we use LC_MESSAGES on all platforms and not just Unix, because
1584 // it doesn't cost much to look into one more directory and doing it this
1585 // way has two important benefits:
1586 // a) we don't break compatibility with wx-2.6 and older by stopping to
1587 // look in a directory where the catalogs used to be and thus silently
1588 // breaking apps after they are recompiled against the latest wx
1589 // b) it makes it possible to package app's support files in the same
1590 // way on all target platforms
1591 const wxString pathPrefix
= wxFileName(prefix
, lang
).GetFullPath();
1593 wxString searchPath
;
1594 searchPath
.reserve(4*pathPrefix
.length());
1595 searchPath
<< pathPrefix
<< wxFILE_SEP_PATH
<< "LC_MESSAGES" << wxPATH_SEP
1596 << prefix
<< wxFILE_SEP_PATH
<< wxPATH_SEP
1602 // construct the search path for the given language
1603 static wxString
GetFullSearchPath(const wxString
& lang
)
1605 // first take the entries explicitly added by the program
1606 wxArrayString paths
;
1607 paths
.reserve(gs_searchPrefixes
.size() + 1);
1609 count
= gs_searchPrefixes
.size();
1610 for ( n
= 0; n
< count
; n
++ )
1612 paths
.Add(GetMsgCatalogSubdirs(gs_searchPrefixes
[n
], lang
));
1617 // then look in the standard location
1618 const wxString stdp
= wxStandardPaths::Get().
1619 GetLocalizedResourcesDir(lang
, wxStandardPaths::ResourceCat_Messages
);
1621 if ( paths
.Index(stdp
) == wxNOT_FOUND
)
1623 #endif // wxUSE_STDPATHS
1625 // last look in default locations
1627 // LC_PATH is a standard env var containing the search path for the .mo
1629 const char *pszLcPath
= wxGetenv("LC_PATH");
1632 const wxString lcp
= GetMsgCatalogSubdirs(pszLcPath
, lang
);
1633 if ( paths
.Index(lcp
) == wxNOT_FOUND
)
1637 // also add the one from where wxWin was installed:
1638 wxString wxp
= wxGetInstallPrefix();
1641 wxp
= GetMsgCatalogSubdirs(wxp
+ wxS("/share/locale"), lang
);
1642 if ( paths
.Index(wxp
) == wxNOT_FOUND
)
1648 // finally construct the full search path
1649 wxString searchPath
;
1650 searchPath
.reserve(500);
1651 count
= paths
.size();
1652 for ( n
= 0; n
< count
; n
++ )
1654 searchPath
+= paths
[n
];
1655 if ( n
!= count
- 1 )
1656 searchPath
+= wxPATH_SEP
;
1662 } // anonymous namespace
1665 void wxFileTranslationsLoader::AddCatalogLookupPathPrefix(const wxString
& prefix
)
1667 if ( gs_searchPrefixes
.Index(prefix
) == wxNOT_FOUND
)
1669 gs_searchPrefixes
.Add(prefix
);
1671 //else: already have it
1675 wxMsgCatalog
*wxFileTranslationsLoader::LoadCatalog(const wxString
& domain
,
1676 const wxString
& lang
)
1678 wxString searchPath
= GetFullSearchPath(lang
);
1680 wxLogTrace(TRACE_I18N
, wxS("Looking for \"%s.mo\" in search path \"%s\""),
1681 domain
, searchPath
);
1683 wxFileName
fn(domain
);
1684 fn
.SetExt(wxS("mo"));
1686 wxString strFullName
;
1687 if ( !wxFindFileInPath(&strFullName
, searchPath
, fn
.GetFullPath()) )
1690 // open file and read its data
1691 wxLogVerbose(_("using catalog '%s' from '%s'."), domain
, strFullName
.c_str());
1692 wxLogTrace(TRACE_I18N
, wxS("Using catalog \"%s\"."), strFullName
.c_str());
1694 return wxMsgCatalog::CreateFromFile(strFullName
, domain
);
1698 // ----------------------------------------------------------------------------
1699 // wxResourceTranslationsLoader
1700 // ----------------------------------------------------------------------------
1703 wxMsgCatalog
*wxResourceTranslationsLoader::LoadCatalog(const wxString
& domain
,
1704 const wxString
& lang
)
1707 const void *mo_data
= NULL
;
1710 const wxString resname
= wxString::Format("%s_%s", domain
, lang
);
1712 if ( !wxLoadUserResource(&mo_data
, &mo_size
,
1718 wxLogTrace(TRACE_I18N
,
1719 "Using catalog from Windows resource \"%s\".", resname
);
1721 wxMsgCatalog
*cat
= wxMsgCatalog::CreateFromData(
1722 wxCharBuffer::CreateNonOwned(static_cast<const char*>(mo_data
), mo_size
),
1726 wxLogWarning(_("Resource '%s' is not a valid message catalog."), resname
);
1730 #endif // __WINDOWS__
1733 // ----------------------------------------------------------------------------
1734 // wxTranslationsModule module (for destruction of gs_translations)
1735 // ----------------------------------------------------------------------------
1737 class wxTranslationsModule
: public wxModule
1739 DECLARE_DYNAMIC_CLASS(wxTranslationsModule
)
1741 wxTranslationsModule() {}
1750 if ( gs_translationsOwned
)
1751 delete gs_translations
;
1752 gs_translations
= NULL
;
1753 gs_translationsOwned
= true;
1757 IMPLEMENT_DYNAMIC_CLASS(wxTranslationsModule
, wxModule
)
1759 #endif // wxUSE_INTL