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/scopedptr.h"
49 #include "wx/stdpaths.h"
50 #include "wx/hashset.h"
52 // ----------------------------------------------------------------------------
54 // ----------------------------------------------------------------------------
56 typedef wxUint32 size_t32
;
58 // ----------------------------------------------------------------------------
60 // ----------------------------------------------------------------------------
62 // magic number identifying the .mo format file
63 const size_t32 MSGCATALOG_MAGIC
= 0x950412de;
64 const size_t32 MSGCATALOG_MAGIC_SW
= 0xde120495;
66 #define TRACE_I18N wxS("i18n")
68 // the constants describing the format of ll_CC locale string
69 static const size_t LEN_LANG
= 2;
71 // ----------------------------------------------------------------------------
73 // ----------------------------------------------------------------------------
78 // get just the language part
79 inline wxString
ExtractLang(const wxString
& langFull
)
81 return langFull
.Left(LEN_LANG
);
84 } // anonymous namespace
87 // ============================================================================
89 // ============================================================================
91 // ----------------------------------------------------------------------------
92 // Plural forms parser
93 // ----------------------------------------------------------------------------
99 LogicalOrExpression '?' Expression ':' Expression
103 LogicalAndExpression "||" LogicalOrExpression // to (a || b) || c
106 LogicalAndExpression:
107 EqualityExpression "&&" LogicalAndExpression // to (a && b) && c
111 RelationalExpression "==" RelationalExperession
112 RelationalExpression "!=" RelationalExperession
115 RelationalExpression:
116 MultiplicativeExpression '>' MultiplicativeExpression
117 MultiplicativeExpression '<' MultiplicativeExpression
118 MultiplicativeExpression ">=" MultiplicativeExpression
119 MultiplicativeExpression "<=" MultiplicativeExpression
120 MultiplicativeExpression
122 MultiplicativeExpression:
123 PmExpression '%' PmExpression
132 class wxPluralFormsToken
137 T_ERROR
, T_EOF
, T_NUMBER
, T_N
, T_PLURAL
, T_NPLURALS
, T_EQUAL
, T_ASSIGN
,
138 T_GREATER
, T_GREATER_OR_EQUAL
, T_LESS
, T_LESS_OR_EQUAL
,
139 T_REMINDER
, T_NOT_EQUAL
,
140 T_LOGICAL_AND
, T_LOGICAL_OR
, T_QUESTION
, T_COLON
, T_SEMICOLON
,
141 T_LEFT_BRACKET
, T_RIGHT_BRACKET
143 Type
type() const { return m_type
; }
144 void setType(Type type
) { m_type
= type
; }
147 Number
number() const { return m_number
; }
148 void setNumber(Number num
) { m_number
= num
; }
155 class wxPluralFormsScanner
158 wxPluralFormsScanner(const char* s
);
159 const wxPluralFormsToken
& token() const { return m_token
; }
160 bool nextToken(); // returns false if error
163 wxPluralFormsToken m_token
;
166 wxPluralFormsScanner::wxPluralFormsScanner(const char* s
) : m_s(s
)
171 bool wxPluralFormsScanner::nextToken()
173 wxPluralFormsToken::Type type
= wxPluralFormsToken::T_ERROR
;
174 while (isspace((unsigned char) *m_s
))
180 type
= wxPluralFormsToken::T_EOF
;
182 else if (isdigit((unsigned char) *m_s
))
184 wxPluralFormsToken::Number number
= *m_s
++ - '0';
185 while (isdigit((unsigned char) *m_s
))
187 number
= number
* 10 + (*m_s
++ - '0');
189 m_token
.setNumber(number
);
190 type
= wxPluralFormsToken::T_NUMBER
;
192 else if (isalpha((unsigned char) *m_s
))
194 const char* begin
= m_s
++;
195 while (isalnum((unsigned char) *m_s
))
199 size_t size
= m_s
- begin
;
200 if (size
== 1 && memcmp(begin
, "n", size
) == 0)
202 type
= wxPluralFormsToken::T_N
;
204 else if (size
== 6 && memcmp(begin
, "plural", size
) == 0)
206 type
= wxPluralFormsToken::T_PLURAL
;
208 else if (size
== 8 && memcmp(begin
, "nplurals", size
) == 0)
210 type
= wxPluralFormsToken::T_NPLURALS
;
213 else if (*m_s
== '=')
219 type
= wxPluralFormsToken::T_EQUAL
;
223 type
= wxPluralFormsToken::T_ASSIGN
;
226 else if (*m_s
== '>')
232 type
= wxPluralFormsToken::T_GREATER_OR_EQUAL
;
236 type
= wxPluralFormsToken::T_GREATER
;
239 else if (*m_s
== '<')
245 type
= wxPluralFormsToken::T_LESS_OR_EQUAL
;
249 type
= wxPluralFormsToken::T_LESS
;
252 else if (*m_s
== '%')
255 type
= wxPluralFormsToken::T_REMINDER
;
257 else if (*m_s
== '!' && m_s
[1] == '=')
260 type
= wxPluralFormsToken::T_NOT_EQUAL
;
262 else if (*m_s
== '&' && m_s
[1] == '&')
265 type
= wxPluralFormsToken::T_LOGICAL_AND
;
267 else if (*m_s
== '|' && m_s
[1] == '|')
270 type
= wxPluralFormsToken::T_LOGICAL_OR
;
272 else if (*m_s
== '?')
275 type
= wxPluralFormsToken::T_QUESTION
;
277 else if (*m_s
== ':')
280 type
= wxPluralFormsToken::T_COLON
;
281 } else if (*m_s
== ';') {
283 type
= wxPluralFormsToken::T_SEMICOLON
;
285 else if (*m_s
== '(')
288 type
= wxPluralFormsToken::T_LEFT_BRACKET
;
290 else if (*m_s
== ')')
293 type
= wxPluralFormsToken::T_RIGHT_BRACKET
;
295 m_token
.setType(type
);
296 return type
!= wxPluralFormsToken::T_ERROR
;
299 class wxPluralFormsNode
;
301 // NB: Can't use wxDEFINE_SCOPED_PTR_TYPE because wxPluralFormsNode is not
302 // fully defined yet:
303 class wxPluralFormsNodePtr
306 wxPluralFormsNodePtr(wxPluralFormsNode
*p
= NULL
) : m_p(p
) {}
307 ~wxPluralFormsNodePtr();
308 wxPluralFormsNode
& operator*() const { return *m_p
; }
309 wxPluralFormsNode
* operator->() const { return m_p
; }
310 wxPluralFormsNode
* get() const { return m_p
; }
311 wxPluralFormsNode
* release();
312 void reset(wxPluralFormsNode
*p
);
315 wxPluralFormsNode
*m_p
;
318 class wxPluralFormsNode
321 wxPluralFormsNode(const wxPluralFormsToken
& token
) : m_token(token
) {}
322 const wxPluralFormsToken
& token() const { return m_token
; }
323 const wxPluralFormsNode
* node(size_t i
) const
324 { return m_nodes
[i
].get(); }
325 void setNode(size_t i
, wxPluralFormsNode
* n
);
326 wxPluralFormsNode
* releaseNode(size_t i
);
327 wxPluralFormsToken::Number
evaluate(wxPluralFormsToken::Number n
) const;
330 wxPluralFormsToken m_token
;
331 wxPluralFormsNodePtr m_nodes
[3];
334 wxPluralFormsNodePtr::~wxPluralFormsNodePtr()
338 wxPluralFormsNode
* wxPluralFormsNodePtr::release()
340 wxPluralFormsNode
*p
= m_p
;
344 void wxPluralFormsNodePtr::reset(wxPluralFormsNode
*p
)
354 void wxPluralFormsNode::setNode(size_t i
, wxPluralFormsNode
* n
)
359 wxPluralFormsNode
* wxPluralFormsNode::releaseNode(size_t i
)
361 return m_nodes
[i
].release();
364 wxPluralFormsToken::Number
365 wxPluralFormsNode::evaluate(wxPluralFormsToken::Number n
) const
367 switch (token().type())
370 case wxPluralFormsToken::T_NUMBER
:
371 return token().number();
372 case wxPluralFormsToken::T_N
:
375 case wxPluralFormsToken::T_EQUAL
:
376 return node(0)->evaluate(n
) == node(1)->evaluate(n
);
377 case wxPluralFormsToken::T_NOT_EQUAL
:
378 return node(0)->evaluate(n
) != node(1)->evaluate(n
);
379 case wxPluralFormsToken::T_GREATER
:
380 return node(0)->evaluate(n
) > node(1)->evaluate(n
);
381 case wxPluralFormsToken::T_GREATER_OR_EQUAL
:
382 return node(0)->evaluate(n
) >= node(1)->evaluate(n
);
383 case wxPluralFormsToken::T_LESS
:
384 return node(0)->evaluate(n
) < node(1)->evaluate(n
);
385 case wxPluralFormsToken::T_LESS_OR_EQUAL
:
386 return node(0)->evaluate(n
) <= node(1)->evaluate(n
);
387 case wxPluralFormsToken::T_REMINDER
:
389 wxPluralFormsToken::Number number
= node(1)->evaluate(n
);
392 return node(0)->evaluate(n
) % number
;
399 case wxPluralFormsToken::T_LOGICAL_AND
:
400 return node(0)->evaluate(n
) && node(1)->evaluate(n
);
401 case wxPluralFormsToken::T_LOGICAL_OR
:
402 return node(0)->evaluate(n
) || node(1)->evaluate(n
);
404 case wxPluralFormsToken::T_QUESTION
:
405 return node(0)->evaluate(n
)
406 ? node(1)->evaluate(n
)
407 : node(2)->evaluate(n
);
414 class wxPluralFormsCalculator
417 wxPluralFormsCalculator() : m_nplurals(0), m_plural(0) {}
419 // input: number, returns msgstr index
420 int evaluate(int n
) const;
422 // input: text after "Plural-Forms:" (e.g. "nplurals=2; plural=(n != 1);"),
423 // if s == 0, creates default handler
424 // returns 0 if error
425 static wxPluralFormsCalculator
* make(const char* s
= 0);
427 ~wxPluralFormsCalculator() {}
429 void init(wxPluralFormsToken::Number nplurals
, wxPluralFormsNode
* plural
);
432 wxPluralFormsToken::Number m_nplurals
;
433 wxPluralFormsNodePtr m_plural
;
436 wxDEFINE_SCOPED_PTR_TYPE(wxPluralFormsCalculator
)
438 void wxPluralFormsCalculator::init(wxPluralFormsToken::Number nplurals
,
439 wxPluralFormsNode
* plural
)
441 m_nplurals
= nplurals
;
442 m_plural
.reset(plural
);
445 int wxPluralFormsCalculator::evaluate(int n
) const
447 if (m_plural
.get() == 0)
451 wxPluralFormsToken::Number number
= m_plural
->evaluate(n
);
452 if (number
< 0 || number
> m_nplurals
)
460 class wxPluralFormsParser
463 wxPluralFormsParser(wxPluralFormsScanner
& scanner
) : m_scanner(scanner
) {}
464 bool parse(wxPluralFormsCalculator
& rCalculator
);
467 wxPluralFormsNode
* parsePlural();
468 // stops at T_SEMICOLON, returns 0 if error
469 wxPluralFormsScanner
& m_scanner
;
470 const wxPluralFormsToken
& token() const;
473 wxPluralFormsNode
* expression();
474 wxPluralFormsNode
* logicalOrExpression();
475 wxPluralFormsNode
* logicalAndExpression();
476 wxPluralFormsNode
* equalityExpression();
477 wxPluralFormsNode
* multiplicativeExpression();
478 wxPluralFormsNode
* relationalExpression();
479 wxPluralFormsNode
* pmExpression();
482 bool wxPluralFormsParser::parse(wxPluralFormsCalculator
& rCalculator
)
484 if (token().type() != wxPluralFormsToken::T_NPLURALS
)
488 if (token().type() != wxPluralFormsToken::T_ASSIGN
)
492 if (token().type() != wxPluralFormsToken::T_NUMBER
)
494 wxPluralFormsToken::Number nplurals
= token().number();
497 if (token().type() != wxPluralFormsToken::T_SEMICOLON
)
501 if (token().type() != wxPluralFormsToken::T_PLURAL
)
505 if (token().type() != wxPluralFormsToken::T_ASSIGN
)
509 wxPluralFormsNode
* plural
= parsePlural();
512 if (token().type() != wxPluralFormsToken::T_SEMICOLON
)
516 if (token().type() != wxPluralFormsToken::T_EOF
)
518 rCalculator
.init(nplurals
, plural
);
522 wxPluralFormsNode
* wxPluralFormsParser::parsePlural()
524 wxPluralFormsNode
* p
= expression();
529 wxPluralFormsNodePtr
n(p
);
530 if (token().type() != wxPluralFormsToken::T_SEMICOLON
)
537 const wxPluralFormsToken
& wxPluralFormsParser::token() const
539 return m_scanner
.token();
542 bool wxPluralFormsParser::nextToken()
544 if (!m_scanner
.nextToken())
549 wxPluralFormsNode
* wxPluralFormsParser::expression()
551 wxPluralFormsNode
* p
= logicalOrExpression();
554 wxPluralFormsNodePtr
n(p
);
555 if (token().type() == wxPluralFormsToken::T_QUESTION
)
557 wxPluralFormsNodePtr
qn(new wxPluralFormsNode(token()));
568 if (token().type() != wxPluralFormsToken::T_COLON
)
582 qn
->setNode(0, n
.release());
588 wxPluralFormsNode
*wxPluralFormsParser::logicalOrExpression()
590 wxPluralFormsNode
* p
= logicalAndExpression();
593 wxPluralFormsNodePtr
ln(p
);
594 if (token().type() == wxPluralFormsToken::T_LOGICAL_OR
)
596 wxPluralFormsNodePtr
un(new wxPluralFormsNode(token()));
601 p
= logicalOrExpression();
606 wxPluralFormsNodePtr
rn(p
); // right
607 if (rn
->token().type() == wxPluralFormsToken::T_LOGICAL_OR
)
609 // see logicalAndExpression comment
610 un
->setNode(0, ln
.release());
611 un
->setNode(1, rn
->releaseNode(0));
612 rn
->setNode(0, un
.release());
617 un
->setNode(0, ln
.release());
618 un
->setNode(1, rn
.release());
624 wxPluralFormsNode
* wxPluralFormsParser::logicalAndExpression()
626 wxPluralFormsNode
* p
= equalityExpression();
629 wxPluralFormsNodePtr
ln(p
); // left
630 if (token().type() == wxPluralFormsToken::T_LOGICAL_AND
)
632 wxPluralFormsNodePtr
un(new wxPluralFormsNode(token())); // up
637 p
= logicalAndExpression();
642 wxPluralFormsNodePtr
rn(p
); // right
643 if (rn
->token().type() == wxPluralFormsToken::T_LOGICAL_AND
)
645 // transform 1 && (2 && 3) -> (1 && 2) && 3
649 un
->setNode(0, ln
.release());
650 un
->setNode(1, rn
->releaseNode(0));
651 rn
->setNode(0, un
.release());
655 un
->setNode(0, ln
.release());
656 un
->setNode(1, rn
.release());
662 wxPluralFormsNode
* wxPluralFormsParser::equalityExpression()
664 wxPluralFormsNode
* p
= relationalExpression();
667 wxPluralFormsNodePtr
n(p
);
668 if (token().type() == wxPluralFormsToken::T_EQUAL
669 || token().type() == wxPluralFormsToken::T_NOT_EQUAL
)
671 wxPluralFormsNodePtr
qn(new wxPluralFormsNode(token()));
676 p
= relationalExpression();
682 qn
->setNode(0, n
.release());
688 wxPluralFormsNode
* wxPluralFormsParser::relationalExpression()
690 wxPluralFormsNode
* p
= multiplicativeExpression();
693 wxPluralFormsNodePtr
n(p
);
694 if (token().type() == wxPluralFormsToken::T_GREATER
695 || token().type() == wxPluralFormsToken::T_LESS
696 || token().type() == wxPluralFormsToken::T_GREATER_OR_EQUAL
697 || token().type() == wxPluralFormsToken::T_LESS_OR_EQUAL
)
699 wxPluralFormsNodePtr
qn(new wxPluralFormsNode(token()));
704 p
= multiplicativeExpression();
710 qn
->setNode(0, n
.release());
716 wxPluralFormsNode
* wxPluralFormsParser::multiplicativeExpression()
718 wxPluralFormsNode
* p
= pmExpression();
721 wxPluralFormsNodePtr
n(p
);
722 if (token().type() == wxPluralFormsToken::T_REMINDER
)
724 wxPluralFormsNodePtr
qn(new wxPluralFormsNode(token()));
735 qn
->setNode(0, n
.release());
741 wxPluralFormsNode
* wxPluralFormsParser::pmExpression()
743 wxPluralFormsNodePtr n
;
744 if (token().type() == wxPluralFormsToken::T_N
745 || token().type() == wxPluralFormsToken::T_NUMBER
)
747 n
.reset(new wxPluralFormsNode(token()));
753 else if (token().type() == wxPluralFormsToken::T_LEFT_BRACKET
) {
758 wxPluralFormsNode
* p
= expression();
764 if (token().type() != wxPluralFormsToken::T_RIGHT_BRACKET
)
780 wxPluralFormsCalculator
* wxPluralFormsCalculator::make(const char* s
)
782 wxPluralFormsCalculatorPtr
calculator(new wxPluralFormsCalculator
);
785 wxPluralFormsScanner
scanner(s
);
786 wxPluralFormsParser
p(scanner
);
787 if (!p
.parse(*calculator
))
792 return calculator
.release();
798 // ----------------------------------------------------------------------------
799 // wxMsgCatalogFile corresponds to one disk-file message catalog.
801 // This is a "low-level" class and is used only by wxMsgCatalog
802 // NOTE: for the documentation of the binary catalog (.MO) files refer to
803 // the GNU gettext manual:
804 // http://www.gnu.org/software/autoconf/manual/gettext/MO-Files.html
805 // ----------------------------------------------------------------------------
807 WX_DECLARE_EXPORTED_STRING_HASH_MAP(wxString
, wxMessagesHash
);
809 class wxMsgCatalogFile
816 // load the catalog from disk
817 bool Load(const wxString
& filename
,
818 wxPluralFormsCalculatorPtr
& rPluralFormsCalculator
);
820 // fills the hash with string-translation pairs
821 bool FillHash(wxMessagesHash
& hash
, const wxString
& msgIdCharset
) 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
850 wxMemoryBuffer m_data
;
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 // just return the pointer to the start of the data as "char *" to
869 // facilitate doing pointer arithmetic with it
870 char *StringData() const
872 return static_cast<char *>(m_data
.GetData());
875 const char *StringAtOfs(wxMsgTableEntry
*pTable
, size_t32 n
) const
877 const wxMsgTableEntry
* const ent
= pTable
+ n
;
879 // this check could fail for a corrupt message catalog
880 size_t32 ofsString
= Swap(ent
->ofsString
);
881 if ( ofsString
+ Swap(ent
->nLen
) > m_data
.GetDataLen())
886 return StringData() + ofsString
;
889 bool m_bSwapped
; // wrong endianness?
891 wxDECLARE_NO_COPY_CLASS(wxMsgCatalogFile
);
895 // ----------------------------------------------------------------------------
896 // wxMsgCatalog corresponds to one loaded message catalog.
898 // This is a "low-level" class and is used only by wxLocale (that's why
899 // it's designed to be stored in a linked list)
900 // ----------------------------------------------------------------------------
906 wxMsgCatalog() { m_conv
= NULL
; }
910 // load the catalog from disk
911 bool Load(const wxString
& filename
,
912 const wxString
& domain
,
913 const wxString
& msgIdCharset
);
915 // get name of the catalog
916 wxString
GetDomain() const { return m_domain
; }
918 // get the translated string: returns NULL if not found
919 const wxString
*GetString(const wxString
& sz
, size_t n
= size_t(-1)) const;
921 // public variable pointing to the next element in a linked list (or NULL)
922 wxMsgCatalog
*m_pNext
;
925 wxMessagesHash m_messages
; // all messages in the catalog
926 wxString m_domain
; // name of the domain
929 // the conversion corresponding to this catalog charset if we installed it
934 wxPluralFormsCalculatorPtr m_pluralFormsCalculator
;
937 // ----------------------------------------------------------------------------
938 // wxMsgCatalogFile clas
939 // ----------------------------------------------------------------------------
941 wxMsgCatalogFile::wxMsgCatalogFile()
945 wxMsgCatalogFile::~wxMsgCatalogFile()
949 // open disk file and read in it's contents
950 bool wxMsgCatalogFile::Load(const wxString
& filename
,
951 wxPluralFormsCalculatorPtr
& rPluralFormsCalculator
)
953 wxFile
fileMsg(filename
);
954 if ( !fileMsg
.IsOpened() )
957 // get the file size (assume it is less than 4Gb...)
958 wxFileOffset lenFile
= fileMsg
.Length();
959 if ( lenFile
== wxInvalidOffset
)
962 size_t nSize
= wx_truncate_cast(size_t, lenFile
);
963 wxASSERT_MSG( nSize
== lenFile
+ size_t(0), wxS("message catalog bigger than 4GB?") );
965 // read the whole file in memory
966 if ( fileMsg
.Read(m_data
.GetWriteBuf(nSize
), nSize
) != lenFile
)
969 m_data
.UngetWriteBuf(nSize
);
973 bool bValid
= m_data
.GetDataLen() > sizeof(wxMsgCatalogHeader
);
975 const wxMsgCatalogHeader
*pHeader
= (wxMsgCatalogHeader
*)m_data
.GetData();
977 // we'll have to swap all the integers if it's true
978 m_bSwapped
= pHeader
->magic
== MSGCATALOG_MAGIC_SW
;
980 // check the magic number
981 bValid
= m_bSwapped
|| pHeader
->magic
== MSGCATALOG_MAGIC
;
985 // it's either too short or has incorrect magic number
986 wxLogWarning(_("'%s' is not a valid message catalog."), filename
.c_str());
992 m_numStrings
= Swap(pHeader
->numStrings
);
993 m_pOrigTable
= (wxMsgTableEntry
*)(StringData() +
994 Swap(pHeader
->ofsOrigTable
));
995 m_pTransTable
= (wxMsgTableEntry
*)(StringData() +
996 Swap(pHeader
->ofsTransTable
));
998 // now parse catalog's header and try to extract catalog charset and
999 // plural forms formula from it:
1001 const char* headerData
= StringAtOfs(m_pOrigTable
, 0);
1002 if ( headerData
&& headerData
[0] == '\0' )
1004 // Extract the charset:
1005 const char * const header
= StringAtOfs(m_pTransTable
, 0);
1007 cset
= strstr(header
, "Content-Type: text/plain; charset=");
1010 cset
+= 34; // strlen("Content-Type: text/plain; charset=")
1012 const char * const csetEnd
= strchr(cset
, '\n');
1015 m_charset
= wxString(cset
, csetEnd
- cset
);
1016 if ( m_charset
== wxS("CHARSET") )
1018 // "CHARSET" is not valid charset, but lazy translator
1023 // else: incorrectly filled Content-Type header
1025 // Extract plural forms:
1026 const char * plurals
= strstr(header
, "Plural-Forms:");
1029 plurals
+= 13; // strlen("Plural-Forms:")
1030 const char * const pluralsEnd
= strchr(plurals
, '\n');
1033 const size_t pluralsLen
= pluralsEnd
- plurals
;
1034 wxCharBuffer
buf(pluralsLen
);
1035 strncpy(buf
.data(), plurals
, pluralsLen
);
1036 wxPluralFormsCalculator
* const
1037 pCalculator
= wxPluralFormsCalculator::make(buf
);
1040 rPluralFormsCalculator
.reset(pCalculator
);
1044 wxLogVerbose(_("Failed to parse Plural-Forms: '%s'"),
1050 if ( !rPluralFormsCalculator
.get() )
1051 rPluralFormsCalculator
.reset(wxPluralFormsCalculator::make());
1054 // everything is fine
1058 bool wxMsgCatalogFile::FillHash(wxMessagesHash
& hash
,
1059 const wxString
& msgIdCharset
) const
1061 wxUnusedVar(msgIdCharset
); // silence warning in Unicode build
1063 // conversion to use to convert catalog strings to the GUI encoding
1064 wxMBConv
*inputConv
= NULL
;
1065 wxMBConv
*inputConvPtr
= NULL
; // same as inputConv but safely deleteable
1067 if ( !m_charset
.empty() )
1069 #if !wxUSE_UNICODE && wxUSE_FONTMAP
1070 // determine if we need any conversion at all
1071 wxFontEncoding encCat
= wxFontMapperBase::GetEncodingFromName(m_charset
);
1072 if ( encCat
!= wxLocale::GetSystemEncoding() )
1076 inputConv
= new wxCSConv(m_charset
);
1079 else // no need or not possible to convert the encoding
1082 // we must somehow convert the narrow strings in the message catalog to
1083 // wide strings, so use the default conversion if we have no charset
1084 inputConv
= wxConvCurrent
;
1089 // conversion to apply to msgid strings before looking them up: we only
1090 // need it if the msgids are neither in 7 bit ASCII nor in the same
1091 // encoding as the catalog
1092 wxCSConv
*sourceConv
= msgIdCharset
.empty() || (msgIdCharset
== m_charset
)
1094 : new wxCSConv(msgIdCharset
);
1095 #endif // !wxUSE_UNICODE
1097 for (size_t32 i
= 0; i
< m_numStrings
; i
++)
1099 const char *data
= StringAtOfs(m_pOrigTable
, i
);
1101 return false; // may happen for invalid MO files
1105 msgid
= wxString(data
, *inputConv
);
1107 if ( inputConv
&& sourceConv
)
1108 msgid
= wxString(inputConv
->cMB2WC(data
), *sourceConv
);
1111 #endif // wxUSE_UNICODE
1113 data
= StringAtOfs(m_pTransTable
, i
);
1115 return false; // may happen for invalid MO files
1117 size_t length
= Swap(m_pTransTable
[i
].nLen
);
1120 while (offset
< length
)
1122 const char * const str
= data
+ offset
;
1126 msgstr
= wxString(str
, *inputConv
);
1129 msgstr
= wxString(inputConv
->cMB2WC(str
), *wxConvUI
);
1132 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
1134 if ( !msgstr
.empty() )
1136 hash
[index
== 0 ? msgid
: msgid
+ wxChar(index
)] = msgstr
;
1140 // IMPORTANT: accesses to the 'data' pointer are valid only for
1141 // the first 'length+1' bytes (GNU specs says that the
1142 // final NUL is not counted in length); using wxStrnlen()
1143 // we make sure we don't access memory beyond the valid range
1144 // (which otherwise may happen for invalid MO files):
1145 offset
+= wxStrnlen(str
, length
- offset
) + 1;
1153 delete inputConvPtr
;
1159 // ----------------------------------------------------------------------------
1160 // wxMsgCatalog class
1161 // ----------------------------------------------------------------------------
1164 wxMsgCatalog::~wxMsgCatalog()
1168 if ( wxConvUI
== m_conv
)
1170 // we only change wxConvUI if it points to wxConvLocal so we reset
1171 // it back to it too
1172 wxConvUI
= &wxConvLocal
;
1178 #endif // !wxUSE_UNICODE
1180 bool wxMsgCatalog::Load(const wxString
& filename
,
1181 const wxString
& domain
,
1182 const wxString
& msgIdCharset
)
1184 wxMsgCatalogFile file
;
1188 if ( !file
.Load(filename
, m_pluralFormsCalculator
) )
1191 if ( !file
.FillHash(m_messages
, msgIdCharset
) )
1197 const wxString
*wxMsgCatalog::GetString(const wxString
& str
, size_t n
) const
1200 if (n
!= size_t(-1))
1202 index
= m_pluralFormsCalculator
->evaluate(n
);
1204 wxMessagesHash::const_iterator i
;
1207 i
= m_messages
.find(wxString(str
) + wxChar(index
)); // plural
1211 i
= m_messages
.find(str
);
1214 if ( i
!= m_messages
.end() )
1223 // ----------------------------------------------------------------------------
1225 // ----------------------------------------------------------------------------
1230 wxTranslations
*gs_translations
= NULL
;
1231 bool gs_translationsOwned
= false;
1233 } // anonymous namespace
1237 wxTranslations
*wxTranslations::Get()
1239 return gs_translations
;
1243 void wxTranslations::Set(wxTranslations
*t
)
1245 if ( gs_translationsOwned
)
1246 delete gs_translations
;
1247 gs_translations
= t
;
1248 gs_translationsOwned
= true;
1252 void wxTranslations::SetNonOwned(wxTranslations
*t
)
1254 if ( gs_translationsOwned
)
1255 delete gs_translations
;
1256 gs_translations
= t
;
1257 gs_translationsOwned
= false;
1261 wxTranslations::wxTranslations()
1264 m_loader
= new wxFileTranslationsLoader
;
1268 wxTranslations::~wxTranslations()
1272 // free catalogs memory
1273 wxMsgCatalog
*pTmpCat
;
1274 while ( m_pMsgCat
!= NULL
)
1276 pTmpCat
= m_pMsgCat
;
1277 m_pMsgCat
= m_pMsgCat
->m_pNext
;
1283 void wxTranslations::SetLoader(wxTranslationsLoader
*loader
)
1285 wxCHECK_RET( loader
, "loader can't be NULL" );
1292 void wxTranslations::SetLanguage(wxLanguage lang
)
1294 if ( lang
== wxLANGUAGE_DEFAULT
)
1297 SetLanguage(wxLocale::GetLanguageCanonicalName(lang
));
1300 void wxTranslations::SetLanguage(const wxString
& lang
)
1306 bool wxTranslations::AddStdCatalog()
1308 if ( !AddCatalog(wxS("wxstd")) )
1311 // there may be a catalog with toolkit specific overrides, it is not
1312 // an error if this does not exist
1313 wxString
port(wxPlatformInfo::Get().GetPortIdName());
1314 if ( !port
.empty() )
1316 AddCatalog(port
.BeforeFirst(wxS('/')).MakeLower());
1323 bool wxTranslations::AddCatalog(const wxString
& domain
)
1325 return AddCatalog(domain
, wxLANGUAGE_ENGLISH_US
);
1329 bool wxTranslations::AddCatalog(const wxString
& domain
,
1330 wxLanguage msgIdLanguage
,
1331 const wxString
& msgIdCharset
)
1333 m_msgIdCharset
[domain
] = msgIdCharset
;
1334 return AddCatalog(domain
, msgIdLanguage
);
1336 #endif // !wxUSE_UNICODE
1338 bool wxTranslations::AddCatalog(const wxString
& domain
,
1339 wxLanguage msgIdLanguage
)
1341 const wxString msgIdLang
= wxLocale::GetLanguageCanonicalName(msgIdLanguage
);
1342 const wxString domain_lang
= ChooseLanguageForDomain(domain
, msgIdLang
);
1344 if ( domain_lang
.empty() )
1346 wxLogTrace(TRACE_I18N
,
1347 wxS("no suitable translation for domain '%s' found"),
1352 wxLogTrace(TRACE_I18N
,
1353 wxS("adding '%s' translation for domain '%s' (msgid language '%s')"),
1354 domain_lang
, domain
, msgIdLang
);
1356 // It is OK to not load catalog if the msgid language and m_language match,
1357 // in which case we can directly display the texts embedded in program's
1359 if ( msgIdLang
== domain_lang
)
1362 return LoadCatalog(domain
, domain_lang
);
1366 bool wxTranslations::LoadCatalog(const wxString
& domain
, const wxString
& lang
)
1368 wxCHECK_MSG( m_loader
, false, "loader can't be 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 if ( m_loader
->LoadCatalog(this, domain
, fullname
) )
1384 #endif // wxUSE_FONTMAP
1386 // Next try: use the provided name language name:
1387 if ( m_loader
->LoadCatalog(this, domain
, lang
) )
1390 // Also try just base locale name: for things like "fr_BE" (Belgium
1391 // French) we should use fall back on plain "fr" if no Belgium-specific
1392 // message catalogs exist
1393 if ( lang
.length() > LEN_LANG
&& lang
[LEN_LANG
] == wxS('_') )
1395 if ( m_loader
->LoadCatalog(this, domain
, ExtractLang(lang
)) )
1399 // Nothing worked, the catalog just isn't there
1400 wxLogTrace(TRACE_I18N
,
1401 "Catalog \"%s.mo\" not found for language \"%s\".",
1407 // check if the given catalog is loaded
1408 bool wxTranslations::IsLoaded(const wxString
& domain
) const
1410 return FindCatalog(domain
) != NULL
;
1414 bool wxTranslations::LoadCatalogFile(const wxString
& filename
,
1415 const wxString
& domain
)
1417 wxMsgCatalog
*pMsgCat
= new wxMsgCatalog
;
1420 const bool ok
= pMsgCat
->Load(filename
, domain
, wxEmptyString
/*unused*/);
1422 const bool ok
= pMsgCat
->Load(filename
, domain
,
1423 m_msgIdCharset
[domain
]);
1428 // don't add it because it couldn't be loaded anyway
1433 // add it to the head of the list so that in GetString it will
1434 // be searched before the catalogs added earlier
1435 pMsgCat
->m_pNext
= m_pMsgCat
;
1436 m_pMsgCat
= pMsgCat
;
1442 wxString
wxTranslations::ChooseLanguageForDomain(const wxString
& WXUNUSED(domain
),
1443 const wxString
& WXUNUSED(msgIdLang
))
1445 // explicitly set language should always be respected
1446 if ( !m_lang
.empty() )
1449 // TODO: if the default language is used, pick the best (by comparing
1450 // available languages with user's preferences), instead of blindly
1451 // trusting availability of system language translation
1452 return wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage());
1458 WX_DECLARE_HASH_SET(wxString
, wxStringHash
, wxStringEqual
,
1459 wxLocaleUntranslatedStrings
);
1463 const wxString
& wxTranslations::GetUntranslatedString(const wxString
& str
)
1465 static wxLocaleUntranslatedStrings s_strings
;
1467 wxLocaleUntranslatedStrings::iterator i
= s_strings
.find(str
);
1468 if ( i
== s_strings
.end() )
1469 return *s_strings
.insert(str
).first
;
1475 const wxString
& wxTranslations::GetString(const wxString
& origString
,
1476 const wxString
& domain
) const
1478 return GetString(origString
, origString
, size_t(-1), domain
);
1481 const wxString
& wxTranslations::GetString(const wxString
& origString
,
1482 const wxString
& origString2
,
1484 const wxString
& domain
) const
1486 if ( origString
.empty() )
1487 return GetUntranslatedString(origString
);
1489 const wxString
*trans
= NULL
;
1490 wxMsgCatalog
*pMsgCat
;
1492 if ( !domain
.empty() )
1494 pMsgCat
= FindCatalog(domain
);
1496 // does the catalog exist?
1497 if ( pMsgCat
!= NULL
)
1498 trans
= pMsgCat
->GetString(origString
, n
);
1502 // search in all domains
1503 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
)
1505 trans
= pMsgCat
->GetString(origString
, n
);
1506 if ( trans
!= NULL
) // take the first found
1511 if ( trans
== NULL
)
1516 "string \"%s\"%s not found in %slocale '%s'.",
1518 ((long)n
) != -1 ? wxString::Format("[%ld]", (long)n
) : wxString(),
1519 !domain
.empty() ? wxString::Format("domain '%s' ", domain
) : wxString(),
1523 if (n
== size_t(-1))
1524 return GetUntranslatedString(origString
);
1526 return GetUntranslatedString(n
== 1 ? origString
: origString2
);
1533 wxString
wxTranslations::GetHeaderValue(const wxString
& header
,
1534 const wxString
& domain
) const
1536 if ( header
.empty() )
1537 return wxEmptyString
;
1539 const wxString
*trans
= NULL
;
1540 wxMsgCatalog
*pMsgCat
;
1542 if ( !domain
.empty() )
1544 pMsgCat
= FindCatalog(domain
);
1546 // does the catalog exist?
1547 if ( pMsgCat
== NULL
)
1548 return wxEmptyString
;
1550 trans
= pMsgCat
->GetString(wxEmptyString
, (size_t)-1);
1554 // search in all domains
1555 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
)
1557 trans
= pMsgCat
->GetString(wxEmptyString
, (size_t)-1);
1558 if ( trans
!= NULL
) // take the first found
1563 if ( !trans
|| trans
->empty() )
1564 return wxEmptyString
;
1566 size_t found
= trans
->find(header
);
1567 if ( found
== wxString::npos
)
1568 return wxEmptyString
;
1570 found
+= header
.length() + 2 /* ': ' */;
1572 // Every header is separated by \n
1574 size_t endLine
= trans
->find(wxS('\n'), found
);
1575 size_t len
= (endLine
== wxString::npos
) ?
1576 wxString::npos
: (endLine
- found
);
1578 return trans
->substr(found
, len
);
1582 // find catalog by name in a linked list, return NULL if !found
1583 wxMsgCatalog
*wxTranslations::FindCatalog(const wxString
& domain
) const
1585 // linear search in the linked list
1586 wxMsgCatalog
*pMsgCat
;
1587 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
)
1589 if ( pMsgCat
->GetDomain() == domain
)
1596 // ----------------------------------------------------------------------------
1597 // wxFileTranslationsLoader
1598 // ----------------------------------------------------------------------------
1603 // the list of the directories to search for message catalog files
1604 wxArrayString gs_searchPrefixes
;
1606 // return the directories to search for message catalogs under the given
1607 // prefix, separated by wxPATH_SEP
1608 wxString
GetMsgCatalogSubdirs(const wxString
& prefix
, const wxString
& lang
)
1610 // Search first in Unix-standard prefix/lang/LC_MESSAGES, then in
1611 // prefix/lang and finally in just prefix.
1613 // Note that we use LC_MESSAGES on all platforms and not just Unix, because
1614 // it doesn't cost much to look into one more directory and doing it this
1615 // way has two important benefits:
1616 // a) we don't break compatibility with wx-2.6 and older by stopping to
1617 // look in a directory where the catalogs used to be and thus silently
1618 // breaking apps after they are recompiled against the latest wx
1619 // b) it makes it possible to package app's support files in the same
1620 // way on all target platforms
1621 const wxString pathPrefix
= wxFileName(prefix
, lang
).GetFullPath();
1623 wxString searchPath
;
1624 searchPath
.reserve(4*pathPrefix
.length());
1625 searchPath
<< pathPrefix
<< wxFILE_SEP_PATH
<< "LC_MESSAGES" << wxPATH_SEP
1626 << prefix
<< wxFILE_SEP_PATH
<< wxPATH_SEP
1632 // construct the search path for the given language
1633 static wxString
GetFullSearchPath(const wxString
& lang
)
1635 // first take the entries explicitly added by the program
1636 wxArrayString paths
;
1637 paths
.reserve(gs_searchPrefixes
.size() + 1);
1639 count
= gs_searchPrefixes
.size();
1640 for ( n
= 0; n
< count
; n
++ )
1642 paths
.Add(GetMsgCatalogSubdirs(gs_searchPrefixes
[n
], lang
));
1647 // then look in the standard location
1648 const wxString stdp
= wxStandardPaths::Get().
1649 GetLocalizedResourcesDir(lang
, wxStandardPaths::ResourceCat_Messages
);
1651 if ( paths
.Index(stdp
) == wxNOT_FOUND
)
1653 #endif // wxUSE_STDPATHS
1655 // last look in default locations
1657 // LC_PATH is a standard env var containing the search path for the .mo
1659 const char *pszLcPath
= wxGetenv("LC_PATH");
1662 const wxString lcp
= GetMsgCatalogSubdirs(pszLcPath
, lang
);
1663 if ( paths
.Index(lcp
) == wxNOT_FOUND
)
1667 // also add the one from where wxWin was installed:
1668 wxString wxp
= wxGetInstallPrefix();
1671 wxp
= GetMsgCatalogSubdirs(wxp
+ wxS("/share/locale"), lang
);
1672 if ( paths
.Index(wxp
) == wxNOT_FOUND
)
1678 // finally construct the full search path
1679 wxString searchPath
;
1680 searchPath
.reserve(500);
1681 count
= paths
.size();
1682 for ( n
= 0; n
< count
; n
++ )
1684 searchPath
+= paths
[n
];
1685 if ( n
!= count
- 1 )
1686 searchPath
+= wxPATH_SEP
;
1692 } // anonymous namespace
1695 void wxFileTranslationsLoader::AddCatalogLookupPathPrefix(const wxString
& prefix
)
1697 if ( gs_searchPrefixes
.Index(prefix
) == wxNOT_FOUND
)
1699 gs_searchPrefixes
.Add(prefix
);
1701 //else: already have it
1705 bool wxFileTranslationsLoader::LoadCatalog(wxTranslations
*translations
,
1706 const wxString
& domain
,
1707 const wxString
& lang
)
1709 wxCHECK_MSG( lang
.length() >= LEN_LANG
, false,
1710 "invalid language specification" );
1712 wxString searchPath
= GetFullSearchPath(lang
);
1714 wxLogTrace(TRACE_I18N
, wxS("Looking for \"%s.mo\" in search path \"%s\""),
1715 domain
, searchPath
);
1717 wxFileName
fn(domain
);
1718 fn
.SetExt(wxS("mo"));
1720 wxString strFullName
;
1721 if ( !wxFindFileInPath(&strFullName
, searchPath
, fn
.GetFullPath()) )
1724 // open file and read its data
1725 wxLogVerbose(_("using catalog '%s' from '%s'."), domain
, strFullName
.c_str());
1726 wxLogTrace(TRACE_I18N
, wxS("Using catalog \"%s\"."), strFullName
.c_str());
1728 return translations
->LoadCatalogFile(strFullName
, domain
);
1731 // ----------------------------------------------------------------------------
1732 // wxTranslationsModule module (for destruction of gs_translations)
1733 // ----------------------------------------------------------------------------
1735 class wxTranslationsModule
: public wxModule
1737 DECLARE_DYNAMIC_CLASS(wxTranslationsModule
)
1739 wxTranslationsModule() {}
1748 if ( gs_translationsOwned
)
1749 delete gs_translations
;
1750 gs_translations
= NULL
;
1751 gs_translationsOwned
= true;
1755 IMPLEMENT_DYNAMIC_CLASS(wxTranslationsModule
, wxModule
)
1757 #endif // wxUSE_INTL