]> git.saurik.com Git - wxWidgets.git/blame - src/common/translation.cpp
Fix assert during separator items creation introduced by r64226.
[wxWidgets.git] / src / common / translation.cpp
CommitLineData
ea144923
VS
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)
7// Created: 2010-04-23
8// RCS-ID: $Id$
9// Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
10// Licence: wxWindows licence
11/////////////////////////////////////////////////////////////////////////////
12
13// ============================================================================
14// declaration
15// ============================================================================
16
17// ----------------------------------------------------------------------------
18// headers
19// ----------------------------------------------------------------------------
20
21// For compilers that support precompilation, includes "wx.h".
22#include "wx/wxprec.h"
23
24#ifdef __BORLANDC__
25 #pragma hdrstop
26#endif
27
28#if wxUSE_INTL
29
30#ifndef WX_PRECOMP
31 #include "wx/dynarray.h"
32 #include "wx/string.h"
33 #include "wx/intl.h"
34 #include "wx/log.h"
35 #include "wx/utils.h"
36 #include "wx/hashmap.h"
37 #include "wx/module.h"
38#endif // WX_PRECOMP
39
40// standard headers
41#include <ctype.h>
42#include <stdlib.h>
43
44#include "wx/file.h"
45#include "wx/filename.h"
46#include "wx/tokenzr.h"
47#include "wx/fontmap.h"
ea144923
VS
48#include "wx/stdpaths.h"
49#include "wx/hashset.h"
50
51// ----------------------------------------------------------------------------
52// simple types
53// ----------------------------------------------------------------------------
54
55typedef wxUint32 size_t32;
56
57// ----------------------------------------------------------------------------
58// constants
59// ----------------------------------------------------------------------------
60
61// magic number identifying the .mo format file
62const size_t32 MSGCATALOG_MAGIC = 0x950412de;
63const size_t32 MSGCATALOG_MAGIC_SW = 0xde120495;
64
65#define TRACE_I18N wxS("i18n")
66
ea144923
VS
67// ============================================================================
68// implementation
69// ============================================================================
70
611bed35
VS
71namespace
72{
73
74#if !wxUSE_UNICODE
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.
78wxStringToStringHashMap gs_msgIdCharset;
79#endif
80
81} // anonymous namespace
82
ea144923
VS
83// ----------------------------------------------------------------------------
84// Plural forms parser
85// ----------------------------------------------------------------------------
86
87/*
88 Simplified Grammar
89
90Expression:
91 LogicalOrExpression '?' Expression ':' Expression
92 LogicalOrExpression
93
94LogicalOrExpression:
95 LogicalAndExpression "||" LogicalOrExpression // to (a || b) || c
96 LogicalAndExpression
97
98LogicalAndExpression:
99 EqualityExpression "&&" LogicalAndExpression // to (a && b) && c
100 EqualityExpression
101
102EqualityExpression:
103 RelationalExpression "==" RelationalExperession
104 RelationalExpression "!=" RelationalExperession
105 RelationalExpression
106
107RelationalExpression:
108 MultiplicativeExpression '>' MultiplicativeExpression
109 MultiplicativeExpression '<' MultiplicativeExpression
110 MultiplicativeExpression ">=" MultiplicativeExpression
111 MultiplicativeExpression "<=" MultiplicativeExpression
112 MultiplicativeExpression
113
114MultiplicativeExpression:
115 PmExpression '%' PmExpression
116 PmExpression
117
118PmExpression:
119 N
120 Number
121 '(' Expression ')'
122*/
123
124class wxPluralFormsToken
125{
126public:
127 enum Type
128 {
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
134 };
135 Type type() const { return m_type; }
136 void setType(Type type) { m_type = type; }
137 // for T_NUMBER only
138 typedef int Number;
139 Number number() const { return m_number; }
140 void setNumber(Number num) { m_number = num; }
141private:
142 Type m_type;
143 Number m_number;
144};
145
146
147class wxPluralFormsScanner
148{
149public:
150 wxPluralFormsScanner(const char* s);
151 const wxPluralFormsToken& token() const { return m_token; }
152 bool nextToken(); // returns false if error
153private:
154 const char* m_s;
155 wxPluralFormsToken m_token;
156};
157
158wxPluralFormsScanner::wxPluralFormsScanner(const char* s) : m_s(s)
159{
160 nextToken();
161}
162
163bool wxPluralFormsScanner::nextToken()
164{
165 wxPluralFormsToken::Type type = wxPluralFormsToken::T_ERROR;
166 while (isspace((unsigned char) *m_s))
167 {
168 ++m_s;
169 }
170 if (*m_s == 0)
171 {
172 type = wxPluralFormsToken::T_EOF;
173 }
174 else if (isdigit((unsigned char) *m_s))
175 {
176 wxPluralFormsToken::Number number = *m_s++ - '0';
177 while (isdigit((unsigned char) *m_s))
178 {
179 number = number * 10 + (*m_s++ - '0');
180 }
181 m_token.setNumber(number);
182 type = wxPluralFormsToken::T_NUMBER;
183 }
184 else if (isalpha((unsigned char) *m_s))
185 {
186 const char* begin = m_s++;
187 while (isalnum((unsigned char) *m_s))
188 {
189 ++m_s;
190 }
191 size_t size = m_s - begin;
192 if (size == 1 && memcmp(begin, "n", size) == 0)
193 {
194 type = wxPluralFormsToken::T_N;
195 }
196 else if (size == 6 && memcmp(begin, "plural", size) == 0)
197 {
198 type = wxPluralFormsToken::T_PLURAL;
199 }
200 else if (size == 8 && memcmp(begin, "nplurals", size) == 0)
201 {
202 type = wxPluralFormsToken::T_NPLURALS;
203 }
204 }
205 else if (*m_s == '=')
206 {
207 ++m_s;
208 if (*m_s == '=')
209 {
210 ++m_s;
211 type = wxPluralFormsToken::T_EQUAL;
212 }
213 else
214 {
215 type = wxPluralFormsToken::T_ASSIGN;
216 }
217 }
218 else if (*m_s == '>')
219 {
220 ++m_s;
221 if (*m_s == '=')
222 {
223 ++m_s;
224 type = wxPluralFormsToken::T_GREATER_OR_EQUAL;
225 }
226 else
227 {
228 type = wxPluralFormsToken::T_GREATER;
229 }
230 }
231 else if (*m_s == '<')
232 {
233 ++m_s;
234 if (*m_s == '=')
235 {
236 ++m_s;
237 type = wxPluralFormsToken::T_LESS_OR_EQUAL;
238 }
239 else
240 {
241 type = wxPluralFormsToken::T_LESS;
242 }
243 }
244 else if (*m_s == '%')
245 {
246 ++m_s;
247 type = wxPluralFormsToken::T_REMINDER;
248 }
249 else if (*m_s == '!' && m_s[1] == '=')
250 {
251 m_s += 2;
252 type = wxPluralFormsToken::T_NOT_EQUAL;
253 }
254 else if (*m_s == '&' && m_s[1] == '&')
255 {
256 m_s += 2;
257 type = wxPluralFormsToken::T_LOGICAL_AND;
258 }
259 else if (*m_s == '|' && m_s[1] == '|')
260 {
261 m_s += 2;
262 type = wxPluralFormsToken::T_LOGICAL_OR;
263 }
264 else if (*m_s == '?')
265 {
266 ++m_s;
267 type = wxPluralFormsToken::T_QUESTION;
268 }
269 else if (*m_s == ':')
270 {
271 ++m_s;
272 type = wxPluralFormsToken::T_COLON;
273 } else if (*m_s == ';') {
274 ++m_s;
275 type = wxPluralFormsToken::T_SEMICOLON;
276 }
277 else if (*m_s == '(')
278 {
279 ++m_s;
280 type = wxPluralFormsToken::T_LEFT_BRACKET;
281 }
282 else if (*m_s == ')')
283 {
284 ++m_s;
285 type = wxPluralFormsToken::T_RIGHT_BRACKET;
286 }
287 m_token.setType(type);
288 return type != wxPluralFormsToken::T_ERROR;
289}
290
291class wxPluralFormsNode;
292
293// NB: Can't use wxDEFINE_SCOPED_PTR_TYPE because wxPluralFormsNode is not
294// fully defined yet:
295class wxPluralFormsNodePtr
296{
297public:
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);
305
306private:
307 wxPluralFormsNode *m_p;
308};
309
310class wxPluralFormsNode
311{
312public:
313 wxPluralFormsNode(const wxPluralFormsToken& token) : m_token(token) {}
314 const wxPluralFormsToken& token() const { return m_token; }
dfbb5eff 315 const wxPluralFormsNode* node(unsigned i) const
ea144923 316 { return m_nodes[i].get(); }
dfbb5eff
VS
317 void setNode(unsigned i, wxPluralFormsNode* n);
318 wxPluralFormsNode* releaseNode(unsigned i);
ea144923
VS
319 wxPluralFormsToken::Number evaluate(wxPluralFormsToken::Number n) const;
320
321private:
322 wxPluralFormsToken m_token;
323 wxPluralFormsNodePtr m_nodes[3];
324};
325
326wxPluralFormsNodePtr::~wxPluralFormsNodePtr()
327{
328 delete m_p;
329}
330wxPluralFormsNode* wxPluralFormsNodePtr::release()
331{
332 wxPluralFormsNode *p = m_p;
333 m_p = NULL;
334 return p;
335}
336void wxPluralFormsNodePtr::reset(wxPluralFormsNode *p)
337{
338 if (p != m_p)
339 {
340 delete m_p;
341 m_p = p;
342 }
343}
344
345
dfbb5eff 346void wxPluralFormsNode::setNode(unsigned i, wxPluralFormsNode* n)
ea144923
VS
347{
348 m_nodes[i].reset(n);
349}
350
dfbb5eff 351wxPluralFormsNode* wxPluralFormsNode::releaseNode(unsigned i)
ea144923
VS
352{
353 return m_nodes[i].release();
354}
355
356wxPluralFormsToken::Number
357wxPluralFormsNode::evaluate(wxPluralFormsToken::Number n) const
358{
359 switch (token().type())
360 {
361 // leaf
362 case wxPluralFormsToken::T_NUMBER:
363 return token().number();
364 case wxPluralFormsToken::T_N:
365 return n;
366 // 2 args
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:
380 {
381 wxPluralFormsToken::Number number = node(1)->evaluate(n);
382 if (number != 0)
383 {
384 return node(0)->evaluate(n) % number;
385 }
386 else
387 {
388 return 0;
389 }
390 }
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);
395 // 3 args
396 case wxPluralFormsToken::T_QUESTION:
397 return node(0)->evaluate(n)
398 ? node(1)->evaluate(n)
399 : node(2)->evaluate(n);
400 default:
401 return 0;
402 }
403}
404
405
406class wxPluralFormsCalculator
407{
408public:
409 wxPluralFormsCalculator() : m_nplurals(0), m_plural(0) {}
410
411 // input: number, returns msgstr index
412 int evaluate(int n) const;
413
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);
418
419 ~wxPluralFormsCalculator() {}
420
421 void init(wxPluralFormsToken::Number nplurals, wxPluralFormsNode* plural);
422
423private:
424 wxPluralFormsToken::Number m_nplurals;
425 wxPluralFormsNodePtr m_plural;
426};
427
611bed35 428wxDEFINE_SCOPED_PTR(wxPluralFormsCalculator, wxPluralFormsCalculatorPtr)
ea144923
VS
429
430void wxPluralFormsCalculator::init(wxPluralFormsToken::Number nplurals,
431 wxPluralFormsNode* plural)
432{
433 m_nplurals = nplurals;
434 m_plural.reset(plural);
435}
436
437int wxPluralFormsCalculator::evaluate(int n) const
438{
439 if (m_plural.get() == 0)
440 {
441 return 0;
442 }
443 wxPluralFormsToken::Number number = m_plural->evaluate(n);
444 if (number < 0 || number > m_nplurals)
445 {
446 return 0;
447 }
448 return number;
449}
450
451
452class wxPluralFormsParser
453{
454public:
455 wxPluralFormsParser(wxPluralFormsScanner& scanner) : m_scanner(scanner) {}
456 bool parse(wxPluralFormsCalculator& rCalculator);
457
458private:
459 wxPluralFormsNode* parsePlural();
460 // stops at T_SEMICOLON, returns 0 if error
461 wxPluralFormsScanner& m_scanner;
462 const wxPluralFormsToken& token() const;
463 bool nextToken();
464
465 wxPluralFormsNode* expression();
466 wxPluralFormsNode* logicalOrExpression();
467 wxPluralFormsNode* logicalAndExpression();
468 wxPluralFormsNode* equalityExpression();
469 wxPluralFormsNode* multiplicativeExpression();
470 wxPluralFormsNode* relationalExpression();
471 wxPluralFormsNode* pmExpression();
472};
473
474bool wxPluralFormsParser::parse(wxPluralFormsCalculator& rCalculator)
475{
476 if (token().type() != wxPluralFormsToken::T_NPLURALS)
477 return false;
478 if (!nextToken())
479 return false;
480 if (token().type() != wxPluralFormsToken::T_ASSIGN)
481 return false;
482 if (!nextToken())
483 return false;
484 if (token().type() != wxPluralFormsToken::T_NUMBER)
485 return false;
486 wxPluralFormsToken::Number nplurals = token().number();
487 if (!nextToken())
488 return false;
489 if (token().type() != wxPluralFormsToken::T_SEMICOLON)
490 return false;
491 if (!nextToken())
492 return false;
493 if (token().type() != wxPluralFormsToken::T_PLURAL)
494 return false;
495 if (!nextToken())
496 return false;
497 if (token().type() != wxPluralFormsToken::T_ASSIGN)
498 return false;
499 if (!nextToken())
500 return false;
501 wxPluralFormsNode* plural = parsePlural();
502 if (plural == 0)
503 return false;
504 if (token().type() != wxPluralFormsToken::T_SEMICOLON)
505 return false;
506 if (!nextToken())
507 return false;
508 if (token().type() != wxPluralFormsToken::T_EOF)
509 return false;
510 rCalculator.init(nplurals, plural);
511 return true;
512}
513
514wxPluralFormsNode* wxPluralFormsParser::parsePlural()
515{
516 wxPluralFormsNode* p = expression();
517 if (p == NULL)
518 {
519 return NULL;
520 }
521 wxPluralFormsNodePtr n(p);
522 if (token().type() != wxPluralFormsToken::T_SEMICOLON)
523 {
524 return NULL;
525 }
526 return n.release();
527}
528
529const wxPluralFormsToken& wxPluralFormsParser::token() const
530{
531 return m_scanner.token();
532}
533
534bool wxPluralFormsParser::nextToken()
535{
536 if (!m_scanner.nextToken())
537 return false;
538 return true;
539}
540
541wxPluralFormsNode* wxPluralFormsParser::expression()
542{
543 wxPluralFormsNode* p = logicalOrExpression();
544 if (p == NULL)
545 return NULL;
546 wxPluralFormsNodePtr n(p);
547 if (token().type() == wxPluralFormsToken::T_QUESTION)
548 {
549 wxPluralFormsNodePtr qn(new wxPluralFormsNode(token()));
550 if (!nextToken())
551 {
552 return 0;
553 }
554 p = expression();
555 if (p == 0)
556 {
557 return 0;
558 }
559 qn->setNode(1, p);
560 if (token().type() != wxPluralFormsToken::T_COLON)
561 {
562 return 0;
563 }
564 if (!nextToken())
565 {
566 return 0;
567 }
568 p = expression();
569 if (p == 0)
570 {
571 return 0;
572 }
573 qn->setNode(2, p);
574 qn->setNode(0, n.release());
575 return qn.release();
576 }
577 return n.release();
578}
579
580wxPluralFormsNode*wxPluralFormsParser::logicalOrExpression()
581{
582 wxPluralFormsNode* p = logicalAndExpression();
583 if (p == NULL)
584 return NULL;
585 wxPluralFormsNodePtr ln(p);
586 if (token().type() == wxPluralFormsToken::T_LOGICAL_OR)
587 {
588 wxPluralFormsNodePtr un(new wxPluralFormsNode(token()));
589 if (!nextToken())
590 {
591 return 0;
592 }
593 p = logicalOrExpression();
594 if (p == 0)
595 {
596 return 0;
597 }
598 wxPluralFormsNodePtr rn(p); // right
599 if (rn->token().type() == wxPluralFormsToken::T_LOGICAL_OR)
600 {
601 // see logicalAndExpression comment
602 un->setNode(0, ln.release());
603 un->setNode(1, rn->releaseNode(0));
604 rn->setNode(0, un.release());
605 return rn.release();
606 }
607
608
609 un->setNode(0, ln.release());
610 un->setNode(1, rn.release());
611 return un.release();
612 }
613 return ln.release();
614}
615
616wxPluralFormsNode* wxPluralFormsParser::logicalAndExpression()
617{
618 wxPluralFormsNode* p = equalityExpression();
619 if (p == NULL)
620 return NULL;
621 wxPluralFormsNodePtr ln(p); // left
622 if (token().type() == wxPluralFormsToken::T_LOGICAL_AND)
623 {
624 wxPluralFormsNodePtr un(new wxPluralFormsNode(token())); // up
625 if (!nextToken())
626 {
627 return NULL;
628 }
629 p = logicalAndExpression();
630 if (p == 0)
631 {
632 return NULL;
633 }
634 wxPluralFormsNodePtr rn(p); // right
635 if (rn->token().type() == wxPluralFormsToken::T_LOGICAL_AND)
636 {
637// transform 1 && (2 && 3) -> (1 && 2) && 3
638// u r
639// l r -> u 3
640// 2 3 l 2
641 un->setNode(0, ln.release());
642 un->setNode(1, rn->releaseNode(0));
643 rn->setNode(0, un.release());
644 return rn.release();
645 }
646
647 un->setNode(0, ln.release());
648 un->setNode(1, rn.release());
649 return un.release();
650 }
651 return ln.release();
652}
653
654wxPluralFormsNode* wxPluralFormsParser::equalityExpression()
655{
656 wxPluralFormsNode* p = relationalExpression();
657 if (p == NULL)
658 return NULL;
659 wxPluralFormsNodePtr n(p);
660 if (token().type() == wxPluralFormsToken::T_EQUAL
661 || token().type() == wxPluralFormsToken::T_NOT_EQUAL)
662 {
663 wxPluralFormsNodePtr qn(new wxPluralFormsNode(token()));
664 if (!nextToken())
665 {
666 return NULL;
667 }
668 p = relationalExpression();
669 if (p == NULL)
670 {
671 return NULL;
672 }
673 qn->setNode(1, p);
674 qn->setNode(0, n.release());
675 return qn.release();
676 }
677 return n.release();
678}
679
680wxPluralFormsNode* wxPluralFormsParser::relationalExpression()
681{
682 wxPluralFormsNode* p = multiplicativeExpression();
683 if (p == NULL)
684 return NULL;
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)
690 {
691 wxPluralFormsNodePtr qn(new wxPluralFormsNode(token()));
692 if (!nextToken())
693 {
694 return NULL;
695 }
696 p = multiplicativeExpression();
697 if (p == NULL)
698 {
699 return NULL;
700 }
701 qn->setNode(1, p);
702 qn->setNode(0, n.release());
703 return qn.release();
704 }
705 return n.release();
706}
707
708wxPluralFormsNode* wxPluralFormsParser::multiplicativeExpression()
709{
710 wxPluralFormsNode* p = pmExpression();
711 if (p == NULL)
712 return NULL;
713 wxPluralFormsNodePtr n(p);
714 if (token().type() == wxPluralFormsToken::T_REMINDER)
715 {
716 wxPluralFormsNodePtr qn(new wxPluralFormsNode(token()));
717 if (!nextToken())
718 {
719 return NULL;
720 }
721 p = pmExpression();
722 if (p == NULL)
723 {
724 return NULL;
725 }
726 qn->setNode(1, p);
727 qn->setNode(0, n.release());
728 return qn.release();
729 }
730 return n.release();
731}
732
733wxPluralFormsNode* wxPluralFormsParser::pmExpression()
734{
735 wxPluralFormsNodePtr n;
736 if (token().type() == wxPluralFormsToken::T_N
737 || token().type() == wxPluralFormsToken::T_NUMBER)
738 {
739 n.reset(new wxPluralFormsNode(token()));
740 if (!nextToken())
741 {
742 return NULL;
743 }
744 }
745 else if (token().type() == wxPluralFormsToken::T_LEFT_BRACKET) {
746 if (!nextToken())
747 {
748 return NULL;
749 }
750 wxPluralFormsNode* p = expression();
751 if (p == NULL)
752 {
753 return NULL;
754 }
755 n.reset(p);
756 if (token().type() != wxPluralFormsToken::T_RIGHT_BRACKET)
757 {
758 return NULL;
759 }
760 if (!nextToken())
761 {
762 return NULL;
763 }
764 }
765 else
766 {
767 return NULL;
768 }
769 return n.release();
770}
771
772wxPluralFormsCalculator* wxPluralFormsCalculator::make(const char* s)
773{
774 wxPluralFormsCalculatorPtr calculator(new wxPluralFormsCalculator);
775 if (s != NULL)
776 {
777 wxPluralFormsScanner scanner(s);
778 wxPluralFormsParser p(scanner);
779 if (!p.parse(*calculator))
780 {
781 return NULL;
782 }
783 }
784 return calculator.release();
785}
786
787
788
789
790// ----------------------------------------------------------------------------
791// wxMsgCatalogFile corresponds to one disk-file message catalog.
792//
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// ----------------------------------------------------------------------------
798
ea144923
VS
799class wxMsgCatalogFile
800{
801public:
611bed35 802 typedef wxScopedCharBuffer DataBuffer;
bc71c3cd 803
ea144923
VS
804 // ctor & dtor
805 wxMsgCatalogFile();
806 ~wxMsgCatalogFile();
807
808 // load the catalog from disk
bc71c3cd
VS
809 bool LoadFile(const wxString& filename,
810 wxPluralFormsCalculatorPtr& rPluralFormsCalculator);
811 bool LoadData(const DataBuffer& data,
812 wxPluralFormsCalculatorPtr& rPluralFormsCalculator);
ea144923
VS
813
814 // fills the hash with string-translation pairs
611bed35 815 bool FillHash(wxStringToStringHashMap& hash, const wxString& domain) const;
ea144923
VS
816
817 // return the charset of the strings in this catalog or empty string if
818 // none/unknown
819 wxString GetCharset() const { return m_charset; }
820
821private:
822 // this implementation is binary compatible with GNU gettext() version 0.10
823
824 // an entry in the string table
825 struct wxMsgTableEntry
826 {
827 size_t32 nLen; // length of the string
828 size_t32 ofsString; // pointer to the string
829 };
830
831 // header of a .mo file
832 struct wxMsgCatalogHeader
833 {
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
841 };
842
843 // all data is stored here
bc71c3cd 844 DataBuffer m_data;
ea144923
VS
845
846 // data description
847 size_t32 m_numStrings; // number of strings in this domain
848 wxMsgTableEntry *m_pOrigTable, // pointer to original strings
849 *m_pTransTable; // translated
850
851 wxString m_charset; // from the message catalog header
852
853
854 // swap the 2 halves of 32 bit integer if needed
855 size_t32 Swap(size_t32 ui) const
856 {
857 return m_bSwapped ? (ui << 24) | ((ui & 0xff00) << 8) |
858 ((ui >> 8) & 0xff00) | (ui >> 24)
859 : ui;
860 }
861
ea144923
VS
862 const char *StringAtOfs(wxMsgTableEntry *pTable, size_t32 n) const
863 {
864 const wxMsgTableEntry * const ent = pTable + n;
865
866 // this check could fail for a corrupt message catalog
867 size_t32 ofsString = Swap(ent->ofsString);
bc71c3cd 868 if ( ofsString + Swap(ent->nLen) > m_data.length())
ea144923
VS
869 {
870 return NULL;
871 }
872
bc71c3cd 873 return m_data.data() + ofsString;
ea144923
VS
874 }
875
876 bool m_bSwapped; // wrong endianness?
877
878 wxDECLARE_NO_COPY_CLASS(wxMsgCatalogFile);
879};
880
ea144923
VS
881// ----------------------------------------------------------------------------
882// wxMsgCatalogFile clas
883// ----------------------------------------------------------------------------
884
885wxMsgCatalogFile::wxMsgCatalogFile()
886{
887}
888
889wxMsgCatalogFile::~wxMsgCatalogFile()
890{
891}
892
893// open disk file and read in it's contents
bc71c3cd
VS
894bool wxMsgCatalogFile::LoadFile(const wxString& filename,
895 wxPluralFormsCalculatorPtr& rPluralFormsCalculator)
ea144923
VS
896{
897 wxFile fileMsg(filename);
898 if ( !fileMsg.IsOpened() )
899 return false;
900
901 // get the file size (assume it is less than 4Gb...)
902 wxFileOffset lenFile = fileMsg.Length();
903 if ( lenFile == wxInvalidOffset )
904 return false;
905
906 size_t nSize = wx_truncate_cast(size_t, lenFile);
907 wxASSERT_MSG( nSize == lenFile + size_t(0), wxS("message catalog bigger than 4GB?") );
908
bc71c3cd
VS
909 wxMemoryBuffer filedata;
910
ea144923 911 // read the whole file in memory
bc71c3cd 912 if ( fileMsg.Read(filedata.GetWriteBuf(nSize), nSize) != lenFile )
ea144923
VS
913 return false;
914
bc71c3cd
VS
915 filedata.UngetWriteBuf(nSize);
916
917 bool ok = LoadData
918 (
919 DataBuffer::CreateOwned((char*)filedata.release(), nSize),
920 rPluralFormsCalculator
921 );
922 if ( !ok )
923 {
924 wxLogWarning(_("'%s' is not a valid message catalog."), filename.c_str());
925 return false;
926 }
927
928 return true;
929}
ea144923
VS
930
931
bc71c3cd
VS
932bool wxMsgCatalogFile::LoadData(const DataBuffer& data,
933 wxPluralFormsCalculatorPtr& rPluralFormsCalculator)
934{
ea144923 935 // examine header
bc71c3cd 936 bool bValid = data.length() > sizeof(wxMsgCatalogHeader);
ea144923 937
bc71c3cd 938 const wxMsgCatalogHeader *pHeader = (wxMsgCatalogHeader *)data.data();
ea144923
VS
939 if ( bValid ) {
940 // we'll have to swap all the integers if it's true
941 m_bSwapped = pHeader->magic == MSGCATALOG_MAGIC_SW;
942
943 // check the magic number
944 bValid = m_bSwapped || pHeader->magic == MSGCATALOG_MAGIC;
945 }
946
947 if ( !bValid ) {
948 // it's either too short or has incorrect magic number
bc71c3cd 949 wxLogWarning(_("Invalid message catalog."));
ea144923
VS
950 return false;
951 }
952
bc71c3cd
VS
953 m_data = data;
954
ea144923
VS
955 // initialize
956 m_numStrings = Swap(pHeader->numStrings);
bc71c3cd 957 m_pOrigTable = (wxMsgTableEntry *)(data.data() +
ea144923 958 Swap(pHeader->ofsOrigTable));
bc71c3cd 959 m_pTransTable = (wxMsgTableEntry *)(data.data() +
ea144923
VS
960 Swap(pHeader->ofsTransTable));
961
962 // now parse catalog's header and try to extract catalog charset and
963 // plural forms formula from it:
964
965 const char* headerData = StringAtOfs(m_pOrigTable, 0);
966 if ( headerData && headerData[0] == '\0' )
967 {
968 // Extract the charset:
969 const char * const header = StringAtOfs(m_pTransTable, 0);
970 const char *
971 cset = strstr(header, "Content-Type: text/plain; charset=");
972 if ( cset )
973 {
974 cset += 34; // strlen("Content-Type: text/plain; charset=")
975
976 const char * const csetEnd = strchr(cset, '\n');
977 if ( csetEnd )
978 {
979 m_charset = wxString(cset, csetEnd - cset);
980 if ( m_charset == wxS("CHARSET") )
981 {
982 // "CHARSET" is not valid charset, but lazy translator
983 m_charset.empty();
984 }
985 }
986 }
987 // else: incorrectly filled Content-Type header
988
989 // Extract plural forms:
990 const char * plurals = strstr(header, "Plural-Forms:");
991 if ( plurals )
992 {
993 plurals += 13; // strlen("Plural-Forms:")
994 const char * const pluralsEnd = strchr(plurals, '\n');
995 if ( pluralsEnd )
996 {
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);
1002 if ( pCalculator )
1003 {
1004 rPluralFormsCalculator.reset(pCalculator);
1005 }
1006 else
1007 {
1008 wxLogVerbose(_("Failed to parse Plural-Forms: '%s'"),
1009 buf.data());
1010 }
1011 }
1012 }
1013
1014 if ( !rPluralFormsCalculator.get() )
1015 rPluralFormsCalculator.reset(wxPluralFormsCalculator::make());
1016 }
1017
1018 // everything is fine
1019 return true;
1020}
1021
611bed35
VS
1022bool wxMsgCatalogFile::FillHash(wxStringToStringHashMap& hash,
1023 const wxString& domain) const
ea144923 1024{
611bed35 1025 wxUnusedVar(domain); // silence warning in Unicode build
ea144923
VS
1026
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
1030
1031 if ( !m_charset.empty() )
1032 {
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() )
1037#endif
1038 {
1039 inputConvPtr =
1040 inputConv = new wxCSConv(m_charset);
1041 }
1042 }
1043 else // no need or not possible to convert the encoding
1044 {
1045#if wxUSE_UNICODE
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;
1049#endif
1050 }
1051
1052#if !wxUSE_UNICODE
611bed35
VS
1053 wxString msgIdCharset = gs_msgIdCharset[domain];
1054
ea144923
VS
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)
1059 ? NULL
1060 : new wxCSConv(msgIdCharset);
1061#endif // !wxUSE_UNICODE
1062
1063 for (size_t32 i = 0; i < m_numStrings; i++)
1064 {
1065 const char *data = StringAtOfs(m_pOrigTable, i);
1066 if (!data)
1067 return false; // may happen for invalid MO files
1068
1069 wxString msgid;
1070#if wxUSE_UNICODE
1071 msgid = wxString(data, *inputConv);
1072#else // ASCII
1073 if ( inputConv && sourceConv )
1074 msgid = wxString(inputConv->cMB2WC(data), *sourceConv);
1075 else
1076 msgid = data;
1077#endif // wxUSE_UNICODE
1078
1079 data = StringAtOfs(m_pTransTable, i);
1080 if (!data)
1081 return false; // may happen for invalid MO files
1082
1083 size_t length = Swap(m_pTransTable[i].nLen);
1084 size_t offset = 0;
1085 size_t index = 0;
1086 while (offset < length)
1087 {
1088 const char * const str = data + offset;
1089
1090 wxString msgstr;
1091#if wxUSE_UNICODE
1092 msgstr = wxString(str, *inputConv);
1093#else
1094 if ( inputConv )
1095 msgstr = wxString(inputConv->cMB2WC(str), *wxConvUI);
1096 else
1097 msgstr = str;
1098#endif // wxUSE_UNICODE/!wxUSE_UNICODE
1099
1100 if ( !msgstr.empty() )
1101 {
1102 hash[index == 0 ? msgid : msgid + wxChar(index)] = msgstr;
1103 }
1104
1105 // skip this string
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;
1112 ++index;
1113 }
1114 }
1115
1116#if !wxUSE_UNICODE
1117 delete sourceConv;
1118#endif
1119 delete inputConvPtr;
1120
1121 return true;
1122}
1123
1124
1125// ----------------------------------------------------------------------------
1126// wxMsgCatalog class
1127// ----------------------------------------------------------------------------
1128
1129#if !wxUSE_UNICODE
1130wxMsgCatalog::~wxMsgCatalog()
1131{
1132 if ( m_conv )
1133 {
1134 if ( wxConvUI == m_conv )
1135 {
1136 // we only change wxConvUI if it points to wxConvLocal so we reset
1137 // it back to it too
1138 wxConvUI = &wxConvLocal;
1139 }
1140
1141 delete m_conv;
1142 }
1143}
1144#endif // !wxUSE_UNICODE
1145
611bed35
VS
1146/* static */
1147wxMsgCatalog *wxMsgCatalog::CreateFromFile(const wxString& filename,
1148 const wxString& domain)
ea144923 1149{
611bed35 1150 wxScopedPtr<wxMsgCatalog> cat(new wxMsgCatalog(domain));
ea144923 1151
611bed35 1152 wxMsgCatalogFile file;
ea144923 1153
611bed35
VS
1154 if ( !file.LoadFile(filename, cat->m_pluralFormsCalculator) )
1155 return NULL;
bc71c3cd 1156
611bed35
VS
1157 if ( !file.FillHash(cat->m_messages, domain) )
1158 return NULL;
bc71c3cd 1159
611bed35 1160 return cat.release();
bc71c3cd
VS
1161}
1162
611bed35
VS
1163/* static */
1164wxMsgCatalog *wxMsgCatalog::CreateFromData(const wxScopedCharBuffer& data,
1165 const wxString& domain)
bc71c3cd 1166{
611bed35 1167 wxScopedPtr<wxMsgCatalog> cat(new wxMsgCatalog(domain));
bc71c3cd 1168
611bed35 1169 wxMsgCatalogFile file;
bc71c3cd 1170
611bed35
VS
1171 if ( !file.LoadData(data, cat->m_pluralFormsCalculator) )
1172 return NULL;
ea144923 1173
611bed35
VS
1174 if ( !file.FillHash(cat->m_messages, domain) )
1175 return NULL;
ea144923 1176
611bed35 1177 return cat.release();
ea144923
VS
1178}
1179
dfbb5eff 1180const wxString *wxMsgCatalog::GetString(const wxString& str, unsigned n) const
ea144923
VS
1181{
1182 int index = 0;
dfbb5eff 1183 if (n != UINT_MAX)
ea144923
VS
1184 {
1185 index = m_pluralFormsCalculator->evaluate(n);
1186 }
611bed35 1187 wxStringToStringHashMap::const_iterator i;
ea144923
VS
1188 if (index != 0)
1189 {
1190 i = m_messages.find(wxString(str) + wxChar(index)); // plural
1191 }
1192 else
1193 {
1194 i = m_messages.find(str);
1195 }
1196
1197 if ( i != m_messages.end() )
1198 {
1199 return &i->second;
1200 }
1201 else
1202 return NULL;
1203}
1204
1205
1206// ----------------------------------------------------------------------------
1207// wxTranslations
1208// ----------------------------------------------------------------------------
1209
1210namespace
1211{
1212
1213wxTranslations *gs_translations = NULL;
1214bool gs_translationsOwned = false;
1215
1216} // anonymous namespace
1217
1218
1219/*static*/
1220wxTranslations *wxTranslations::Get()
1221{
1222 return gs_translations;
1223}
1224
1225/*static*/
1226void wxTranslations::Set(wxTranslations *t)
1227{
1228 if ( gs_translationsOwned )
1229 delete gs_translations;
1230 gs_translations = t;
1231 gs_translationsOwned = true;
1232}
1233
1234/*static*/
1235void wxTranslations::SetNonOwned(wxTranslations *t)
1236{
1237 if ( gs_translationsOwned )
1238 delete gs_translations;
1239 gs_translations = t;
1240 gs_translationsOwned = false;
1241}
1242
1243
1244wxTranslations::wxTranslations()
1245{
1246 m_pMsgCat = NULL;
1247 m_loader = new wxFileTranslationsLoader;
1248}
1249
1250
1251wxTranslations::~wxTranslations()
1252{
1253 delete m_loader;
1254
1255 // free catalogs memory
1256 wxMsgCatalog *pTmpCat;
1257 while ( m_pMsgCat != NULL )
1258 {
1259 pTmpCat = m_pMsgCat;
1260 m_pMsgCat = m_pMsgCat->m_pNext;
1261 delete pTmpCat;
1262 }
1263}
1264
1265
1266void wxTranslations::SetLoader(wxTranslationsLoader *loader)
1267{
1268 wxCHECK_RET( loader, "loader can't be NULL" );
1269
1270 delete m_loader;
1271 m_loader = loader;
1272}
1273
1274
1275void wxTranslations::SetLanguage(wxLanguage lang)
1276{
1277 if ( lang == wxLANGUAGE_DEFAULT )
1278 SetLanguage("");
1279 else
1280 SetLanguage(wxLocale::GetLanguageCanonicalName(lang));
1281}
1282
1283void wxTranslations::SetLanguage(const wxString& lang)
1284{
1285 m_lang = lang;
1286}
1287
1288
1289bool wxTranslations::AddStdCatalog()
1290{
1291 if ( !AddCatalog(wxS("wxstd")) )
1292 return false;
1293
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() )
1298 {
1299 AddCatalog(port.BeforeFirst(wxS('/')).MakeLower());
1300 }
1301
1302 return true;
1303}
1304
1305
1306bool wxTranslations::AddCatalog(const wxString& domain)
1307{
1308 return AddCatalog(domain, wxLANGUAGE_ENGLISH_US);
1309}
1310
1311#if !wxUSE_UNICODE
1312bool wxTranslations::AddCatalog(const wxString& domain,
1313 wxLanguage msgIdLanguage,
1314 const wxString& msgIdCharset)
1315{
611bed35 1316 gs_msgIdCharset[domain] = msgIdCharset;
ea144923
VS
1317 return AddCatalog(domain, msgIdLanguage);
1318}
1319#endif // !wxUSE_UNICODE
1320
1321bool wxTranslations::AddCatalog(const wxString& domain,
1322 wxLanguage msgIdLanguage)
1323{
1324 const wxString msgIdLang = wxLocale::GetLanguageCanonicalName(msgIdLanguage);
1325 const wxString domain_lang = ChooseLanguageForDomain(domain, msgIdLang);
1326
1327 if ( domain_lang.empty() )
1328 {
1329 wxLogTrace(TRACE_I18N,
1330 wxS("no suitable translation for domain '%s' found"),
1331 domain);
1332 return false;
1333 }
1334
1335 wxLogTrace(TRACE_I18N,
1336 wxS("adding '%s' translation for domain '%s' (msgid language '%s')"),
1337 domain_lang, domain, msgIdLang);
1338
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
1341 // source code:
1342 if ( msgIdLang == domain_lang )
1343 return true;
1344
076c0a8e
VS
1345 return LoadCatalog(domain, domain_lang);
1346}
1347
1348
1349bool wxTranslations::LoadCatalog(const wxString& domain, const wxString& lang)
1350{
ea144923 1351 wxCHECK_MSG( m_loader, false, "loader can't be NULL" );
076c0a8e 1352
611bed35
VS
1353 wxMsgCatalog *cat = NULL;
1354
076c0a8e
VS
1355#if wxUSE_FONTMAP
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 )
1362 {
1363 wxString fullname(lang);
1364 fullname << wxS('.') << wxFontMapperBase::GetEncodingName(encSys);
1365
611bed35 1366 cat = m_loader->LoadCatalog(domain, fullname);
076c0a8e
VS
1367 }
1368#endif // wxUSE_FONTMAP
1369
611bed35 1370 if ( !cat )
076c0a8e 1371 {
611bed35
VS
1372 // Next try: use the provided name language name:
1373 cat = m_loader->LoadCatalog(domain, lang);
076c0a8e
VS
1374 }
1375
611bed35 1376 if ( !cat )
bc71c3cd 1377 {
611bed35
VS
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);
bc71c3cd
VS
1384 }
1385
611bed35
VS
1386 if ( cat )
1387 {
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;
1391 m_pMsgCat = cat;
ea144923 1392
611bed35
VS
1393 return true;
1394 }
1395 else
ea144923 1396 {
611bed35
VS
1397 // Nothing worked, the catalog just isn't there
1398 wxLogTrace(TRACE_I18N,
1399 "Catalog \"%s.mo\" not found for language \"%s\".",
1400 domain, lang);
ea144923
VS
1401 return false;
1402 }
611bed35 1403}
ea144923 1404
611bed35
VS
1405// check if the given catalog is loaded
1406bool wxTranslations::IsLoaded(const wxString& domain) const
1407{
1408 return FindCatalog(domain) != NULL;
ea144923
VS
1409}
1410
1411
1412wxString wxTranslations::ChooseLanguageForDomain(const wxString& WXUNUSED(domain),
1413 const wxString& WXUNUSED(msgIdLang))
1414{
1415 // explicitly set language should always be respected
1416 if ( !m_lang.empty() )
1417 return m_lang;
1418
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());
1423}
1424
1425
1426namespace
1427{
1428WX_DECLARE_HASH_SET(wxString, wxStringHash, wxStringEqual,
1429 wxLocaleUntranslatedStrings);
1430}
1431
1432/* static */
1433const wxString& wxTranslations::GetUntranslatedString(const wxString& str)
1434{
1435 static wxLocaleUntranslatedStrings s_strings;
1436
1437 wxLocaleUntranslatedStrings::iterator i = s_strings.find(str);
1438 if ( i == s_strings.end() )
1439 return *s_strings.insert(str).first;
1440
1441 return *i;
1442}
1443
1444
1445const wxString& wxTranslations::GetString(const wxString& origString,
1446 const wxString& domain) const
1447{
dfbb5eff 1448 return GetString(origString, origString, UINT_MAX, domain);
ea144923
VS
1449}
1450
1451const wxString& wxTranslations::GetString(const wxString& origString,
1452 const wxString& origString2,
dfbb5eff 1453 unsigned n,
ea144923
VS
1454 const wxString& domain) const
1455{
1456 if ( origString.empty() )
1457 return GetUntranslatedString(origString);
1458
1459 const wxString *trans = NULL;
1460 wxMsgCatalog *pMsgCat;
1461
1462 if ( !domain.empty() )
1463 {
1464 pMsgCat = FindCatalog(domain);
1465
1466 // does the catalog exist?
1467 if ( pMsgCat != NULL )
1468 trans = pMsgCat->GetString(origString, n);
1469 }
1470 else
1471 {
1472 // search in all domains
1473 for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext )
1474 {
1475 trans = pMsgCat->GetString(origString, n);
1476 if ( trans != NULL ) // take the first found
1477 break;
1478 }
1479 }
1480
1481 if ( trans == NULL )
1482 {
1483 wxLogTrace
1484 (
1485 TRACE_I18N,
1486 "string \"%s\"%s not found in %slocale '%s'.",
1487 origString,
1488 ((long)n) != -1 ? wxString::Format("[%ld]", (long)n) : wxString(),
1489 !domain.empty() ? wxString::Format("domain '%s' ", domain) : wxString(),
1490 m_lang
1491 );
1492
dfbb5eff 1493 if (n == UINT_MAX)
ea144923
VS
1494 return GetUntranslatedString(origString);
1495 else
1496 return GetUntranslatedString(n == 1 ? origString : origString2);
1497 }
1498
1499 return *trans;
1500}
1501
1502
1503wxString wxTranslations::GetHeaderValue(const wxString& header,
1504 const wxString& domain) const
1505{
1506 if ( header.empty() )
1507 return wxEmptyString;
1508
1509 const wxString *trans = NULL;
1510 wxMsgCatalog *pMsgCat;
1511
1512 if ( !domain.empty() )
1513 {
1514 pMsgCat = FindCatalog(domain);
1515
1516 // does the catalog exist?
1517 if ( pMsgCat == NULL )
1518 return wxEmptyString;
1519
dfbb5eff 1520 trans = pMsgCat->GetString(wxEmptyString, UINT_MAX);
ea144923
VS
1521 }
1522 else
1523 {
1524 // search in all domains
1525 for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext )
1526 {
dfbb5eff 1527 trans = pMsgCat->GetString(wxEmptyString, UINT_MAX);
ea144923
VS
1528 if ( trans != NULL ) // take the first found
1529 break;
1530 }
1531 }
1532
1533 if ( !trans || trans->empty() )
1534 return wxEmptyString;
1535
1536 size_t found = trans->find(header);
1537 if ( found == wxString::npos )
1538 return wxEmptyString;
1539
1540 found += header.length() + 2 /* ': ' */;
1541
1542 // Every header is separated by \n
1543
1544 size_t endLine = trans->find(wxS('\n'), found);
1545 size_t len = (endLine == wxString::npos) ?
1546 wxString::npos : (endLine - found);
1547
1548 return trans->substr(found, len);
1549}
1550
1551
1552// find catalog by name in a linked list, return NULL if !found
1553wxMsgCatalog *wxTranslations::FindCatalog(const wxString& domain) const
1554{
1555 // linear search in the linked list
1556 wxMsgCatalog *pMsgCat;
1557 for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext )
1558 {
1559 if ( pMsgCat->GetDomain() == domain )
1560 return pMsgCat;
1561 }
1562
1563 return NULL;
1564}
1565
1566// ----------------------------------------------------------------------------
1567// wxFileTranslationsLoader
1568// ----------------------------------------------------------------------------
1569
1570namespace
1571{
1572
1573// the list of the directories to search for message catalog files
1574wxArrayString gs_searchPrefixes;
1575
1576// return the directories to search for message catalogs under the given
1577// prefix, separated by wxPATH_SEP
1578wxString GetMsgCatalogSubdirs(const wxString& prefix, const wxString& lang)
1579{
1580 // Search first in Unix-standard prefix/lang/LC_MESSAGES, then in
1581 // prefix/lang and finally in just prefix.
1582 //
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();
1592
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
1597 << pathPrefix;
1598
1599 return searchPath;
1600}
1601
1602// construct the search path for the given language
1603static wxString GetFullSearchPath(const wxString& lang)
1604{
1605 // first take the entries explicitly added by the program
1606 wxArrayString paths;
1607 paths.reserve(gs_searchPrefixes.size() + 1);
1608 size_t n,
1609 count = gs_searchPrefixes.size();
1610 for ( n = 0; n < count; n++ )
1611 {
1612 paths.Add(GetMsgCatalogSubdirs(gs_searchPrefixes[n], lang));
1613 }
1614
1615
1616#if wxUSE_STDPATHS
1617 // then look in the standard location
1618 const wxString stdp = wxStandardPaths::Get().
1619 GetLocalizedResourcesDir(lang, wxStandardPaths::ResourceCat_Messages);
1620
1621 if ( paths.Index(stdp) == wxNOT_FOUND )
1622 paths.Add(stdp);
1623#endif // wxUSE_STDPATHS
1624
1625 // last look in default locations
1626#ifdef __UNIX__
1627 // LC_PATH is a standard env var containing the search path for the .mo
1628 // files
1629 const char *pszLcPath = wxGetenv("LC_PATH");
1630 if ( pszLcPath )
1631 {
1632 const wxString lcp = GetMsgCatalogSubdirs(pszLcPath, lang);
1633 if ( paths.Index(lcp) == wxNOT_FOUND )
1634 paths.Add(lcp);
1635 }
1636
1637 // also add the one from where wxWin was installed:
1638 wxString wxp = wxGetInstallPrefix();
1639 if ( !wxp.empty() )
1640 {
1641 wxp = GetMsgCatalogSubdirs(wxp + wxS("/share/locale"), lang);
1642 if ( paths.Index(wxp) == wxNOT_FOUND )
1643 paths.Add(wxp);
1644 }
1645#endif // __UNIX__
1646
1647
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++ )
1653 {
1654 searchPath += paths[n];
1655 if ( n != count - 1 )
1656 searchPath += wxPATH_SEP;
1657 }
1658
1659 return searchPath;
1660}
1661
1662} // anonymous namespace
1663
1664
1665void wxFileTranslationsLoader::AddCatalogLookupPathPrefix(const wxString& prefix)
1666{
1667 if ( gs_searchPrefixes.Index(prefix) == wxNOT_FOUND )
1668 {
1669 gs_searchPrefixes.Add(prefix);
1670 }
1671 //else: already have it
1672}
1673
1674
611bed35
VS
1675wxMsgCatalog *wxFileTranslationsLoader::LoadCatalog(const wxString& domain,
1676 const wxString& lang)
ea144923 1677{
076c0a8e 1678 wxString searchPath = GetFullSearchPath(lang);
ea144923
VS
1679
1680 wxLogTrace(TRACE_I18N, wxS("Looking for \"%s.mo\" in search path \"%s\""),
1681 domain, searchPath);
1682
1683 wxFileName fn(domain);
1684 fn.SetExt(wxS("mo"));
1685
1686 wxString strFullName;
1687 if ( !wxFindFileInPath(&strFullName, searchPath, fn.GetFullPath()) )
611bed35 1688 return NULL;
ea144923
VS
1689
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());
1693
611bed35 1694 return wxMsgCatalog::CreateFromFile(strFullName, domain);
ea144923
VS
1695}
1696
bc71c3cd
VS
1697
1698// ----------------------------------------------------------------------------
1699// wxResourceTranslationsLoader
1700// ----------------------------------------------------------------------------
1701
1702#ifdef __WINDOWS__
611bed35
VS
1703wxMsgCatalog *wxResourceTranslationsLoader::LoadCatalog(const wxString& domain,
1704 const wxString& lang)
bc71c3cd
VS
1705{
1706
1707 const void *mo_data = NULL;
1708 size_t mo_size = 0;
1709
1710 const wxString resname = wxString::Format("%s_%s", domain, lang);
1711
1712 if ( !wxLoadUserResource(&mo_data, &mo_size,
1713 resname,
1714 GetResourceType(),
1715 GetModule()) )
1716 return false;
1717
1718 wxLogTrace(TRACE_I18N,
1719 "Using catalog from Windows resource \"%s\".", resname);
1720
611bed35
VS
1721 wxMsgCatalog *cat = wxMsgCatalog::CreateFromData(
1722 wxCharBuffer::CreateNonOwned(static_cast<const char*>(mo_data), mo_size),
1723 domain);
1724
1725 if ( !cat )
bc71c3cd
VS
1726 wxLogWarning(_("Resource '%s' is not a valid message catalog."), resname);
1727
611bed35 1728 return cat;
bc71c3cd
VS
1729}
1730#endif // __WINDOWS__
1731
1732
ea144923
VS
1733// ----------------------------------------------------------------------------
1734// wxTranslationsModule module (for destruction of gs_translations)
1735// ----------------------------------------------------------------------------
1736
1737class wxTranslationsModule: public wxModule
1738{
1739 DECLARE_DYNAMIC_CLASS(wxTranslationsModule)
1740 public:
1741 wxTranslationsModule() {}
1742
1743 bool OnInit()
1744 {
1745 return true;
1746 }
1747
1748 void OnExit()
1749 {
1750 if ( gs_translationsOwned )
1751 delete gs_translations;
1752 gs_translations = NULL;
1753 gs_translationsOwned = true;
1754 }
1755};
1756
1757IMPLEMENT_DYNAMIC_CLASS(wxTranslationsModule, wxModule)
1758
1759#endif // wxUSE_INTL