Fix processing of events for MRU entries #10 and more in docview.
[wxWidgets.git] / src / common / translation.cpp
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"
48 #include "wx/stdpaths.h"
49 #include "wx/hashset.h"
50
51 // ----------------------------------------------------------------------------
52 // simple types
53 // ----------------------------------------------------------------------------
54
55 typedef wxUint32 size_t32;
56
57 // ----------------------------------------------------------------------------
58 // constants
59 // ----------------------------------------------------------------------------
60
61 // magic number identifying the .mo format file
62 const size_t32 MSGCATALOG_MAGIC = 0x950412de;
63 const size_t32 MSGCATALOG_MAGIC_SW = 0xde120495;
64
65 #define TRACE_I18N wxS("i18n")
66
67 // ============================================================================
68 // implementation
69 // ============================================================================
70
71 namespace
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.
78 wxStringToStringHashMap gs_msgIdCharset;
79 #endif
80
81 } // anonymous namespace
82
83 // ----------------------------------------------------------------------------
84 // Plural forms parser
85 // ----------------------------------------------------------------------------
86
87 /*
88 Simplified Grammar
89
90 Expression:
91 LogicalOrExpression '?' Expression ':' Expression
92 LogicalOrExpression
93
94 LogicalOrExpression:
95 LogicalAndExpression "||" LogicalOrExpression // to (a || b) || c
96 LogicalAndExpression
97
98 LogicalAndExpression:
99 EqualityExpression "&&" LogicalAndExpression // to (a && b) && c
100 EqualityExpression
101
102 EqualityExpression:
103 RelationalExpression "==" RelationalExperession
104 RelationalExpression "!=" RelationalExperession
105 RelationalExpression
106
107 RelationalExpression:
108 MultiplicativeExpression '>' MultiplicativeExpression
109 MultiplicativeExpression '<' MultiplicativeExpression
110 MultiplicativeExpression ">=" MultiplicativeExpression
111 MultiplicativeExpression "<=" MultiplicativeExpression
112 MultiplicativeExpression
113
114 MultiplicativeExpression:
115 PmExpression '%' PmExpression
116 PmExpression
117
118 PmExpression:
119 N
120 Number
121 '(' Expression ')'
122 */
123
124 class wxPluralFormsToken
125 {
126 public:
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; }
141 private:
142 Type m_type;
143 Number m_number;
144 };
145
146
147 class wxPluralFormsScanner
148 {
149 public:
150 wxPluralFormsScanner(const char* s);
151 const wxPluralFormsToken& token() const { return m_token; }
152 bool nextToken(); // returns false if error
153 private:
154 const char* m_s;
155 wxPluralFormsToken m_token;
156 };
157
158 wxPluralFormsScanner::wxPluralFormsScanner(const char* s) : m_s(s)
159 {
160 nextToken();
161 }
162
163 bool 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
291 class wxPluralFormsNode;
292
293 // NB: Can't use wxDEFINE_SCOPED_PTR_TYPE because wxPluralFormsNode is not
294 // fully defined yet:
295 class wxPluralFormsNodePtr
296 {
297 public:
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
306 private:
307 wxPluralFormsNode *m_p;
308 };
309
310 class wxPluralFormsNode
311 {
312 public:
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;
320
321 private:
322 wxPluralFormsToken m_token;
323 wxPluralFormsNodePtr m_nodes[3];
324 };
325
326 wxPluralFormsNodePtr::~wxPluralFormsNodePtr()
327 {
328 delete m_p;
329 }
330 wxPluralFormsNode* wxPluralFormsNodePtr::release()
331 {
332 wxPluralFormsNode *p = m_p;
333 m_p = NULL;
334 return p;
335 }
336 void wxPluralFormsNodePtr::reset(wxPluralFormsNode *p)
337 {
338 if (p != m_p)
339 {
340 delete m_p;
341 m_p = p;
342 }
343 }
344
345
346 void wxPluralFormsNode::setNode(unsigned i, wxPluralFormsNode* n)
347 {
348 m_nodes[i].reset(n);
349 }
350
351 wxPluralFormsNode* wxPluralFormsNode::releaseNode(unsigned i)
352 {
353 return m_nodes[i].release();
354 }
355
356 wxPluralFormsToken::Number
357 wxPluralFormsNode::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
406 class wxPluralFormsCalculator
407 {
408 public:
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
423 private:
424 wxPluralFormsToken::Number m_nplurals;
425 wxPluralFormsNodePtr m_plural;
426 };
427
428 wxDEFINE_SCOPED_PTR(wxPluralFormsCalculator, wxPluralFormsCalculatorPtr)
429
430 void wxPluralFormsCalculator::init(wxPluralFormsToken::Number nplurals,
431 wxPluralFormsNode* plural)
432 {
433 m_nplurals = nplurals;
434 m_plural.reset(plural);
435 }
436
437 int 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
452 class wxPluralFormsParser
453 {
454 public:
455 wxPluralFormsParser(wxPluralFormsScanner& scanner) : m_scanner(scanner) {}
456 bool parse(wxPluralFormsCalculator& rCalculator);
457
458 private:
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
474 bool 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
514 wxPluralFormsNode* 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
529 const wxPluralFormsToken& wxPluralFormsParser::token() const
530 {
531 return m_scanner.token();
532 }
533
534 bool wxPluralFormsParser::nextToken()
535 {
536 if (!m_scanner.nextToken())
537 return false;
538 return true;
539 }
540
541 wxPluralFormsNode* 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
580 wxPluralFormsNode*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
616 wxPluralFormsNode* 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
654 wxPluralFormsNode* 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
680 wxPluralFormsNode* 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
708 wxPluralFormsNode* 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
733 wxPluralFormsNode* 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
772 wxPluralFormsCalculator* 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
799 class wxMsgCatalogFile
800 {
801 public:
802 typedef wxScopedCharBuffer DataBuffer;
803
804 // ctor & dtor
805 wxMsgCatalogFile();
806 ~wxMsgCatalogFile();
807
808 // load the catalog from disk
809 bool LoadFile(const wxString& filename,
810 wxPluralFormsCalculatorPtr& rPluralFormsCalculator);
811 bool LoadData(const DataBuffer& data,
812 wxPluralFormsCalculatorPtr& rPluralFormsCalculator);
813
814 // fills the hash with string-translation pairs
815 bool FillHash(wxStringToStringHashMap& hash, const wxString& domain) const;
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
821 private:
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
844 DataBuffer m_data;
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
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);
868 if ( ofsString + Swap(ent->nLen) > m_data.length())
869 {
870 return NULL;
871 }
872
873 return m_data.data() + ofsString;
874 }
875
876 bool m_bSwapped; // wrong endianness?
877
878 wxDECLARE_NO_COPY_CLASS(wxMsgCatalogFile);
879 };
880
881 // ----------------------------------------------------------------------------
882 // wxMsgCatalogFile clas
883 // ----------------------------------------------------------------------------
884
885 wxMsgCatalogFile::wxMsgCatalogFile()
886 {
887 }
888
889 wxMsgCatalogFile::~wxMsgCatalogFile()
890 {
891 }
892
893 // open disk file and read in it's contents
894 bool wxMsgCatalogFile::LoadFile(const wxString& filename,
895 wxPluralFormsCalculatorPtr& rPluralFormsCalculator)
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
909 wxMemoryBuffer filedata;
910
911 // read the whole file in memory
912 if ( fileMsg.Read(filedata.GetWriteBuf(nSize), nSize) != lenFile )
913 return false;
914
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 }
930
931
932 bool wxMsgCatalogFile::LoadData(const DataBuffer& data,
933 wxPluralFormsCalculatorPtr& rPluralFormsCalculator)
934 {
935 // examine header
936 bool bValid = data.length() > sizeof(wxMsgCatalogHeader);
937
938 const wxMsgCatalogHeader *pHeader = (wxMsgCatalogHeader *)data.data();
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
949 wxLogWarning(_("Invalid message catalog."));
950 return false;
951 }
952
953 m_data = data;
954
955 // initialize
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));
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
1022 bool wxMsgCatalogFile::FillHash(wxStringToStringHashMap& hash,
1023 const wxString& domain) const
1024 {
1025 wxUnusedVar(domain); // silence warning in Unicode build
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
1053 wxString msgIdCharset = gs_msgIdCharset[domain];
1054
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
1130 wxMsgCatalog::~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
1146 /* static */
1147 wxMsgCatalog *wxMsgCatalog::CreateFromFile(const wxString& filename,
1148 const wxString& domain)
1149 {
1150 wxScopedPtr<wxMsgCatalog> cat(new wxMsgCatalog(domain));
1151
1152 wxMsgCatalogFile file;
1153
1154 if ( !file.LoadFile(filename, cat->m_pluralFormsCalculator) )
1155 return NULL;
1156
1157 if ( !file.FillHash(cat->m_messages, domain) )
1158 return NULL;
1159
1160 return cat.release();
1161 }
1162
1163 /* static */
1164 wxMsgCatalog *wxMsgCatalog::CreateFromData(const wxScopedCharBuffer& data,
1165 const wxString& domain)
1166 {
1167 wxScopedPtr<wxMsgCatalog> cat(new wxMsgCatalog(domain));
1168
1169 wxMsgCatalogFile file;
1170
1171 if ( !file.LoadData(data, cat->m_pluralFormsCalculator) )
1172 return NULL;
1173
1174 if ( !file.FillHash(cat->m_messages, domain) )
1175 return NULL;
1176
1177 return cat.release();
1178 }
1179
1180 const wxString *wxMsgCatalog::GetString(const wxString& str, unsigned n) const
1181 {
1182 int index = 0;
1183 if (n != UINT_MAX)
1184 {
1185 index = m_pluralFormsCalculator->evaluate(n);
1186 }
1187 wxStringToStringHashMap::const_iterator i;
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
1210 namespace
1211 {
1212
1213 wxTranslations *gs_translations = NULL;
1214 bool gs_translationsOwned = false;
1215
1216 } // anonymous namespace
1217
1218
1219 /*static*/
1220 wxTranslations *wxTranslations::Get()
1221 {
1222 return gs_translations;
1223 }
1224
1225 /*static*/
1226 void 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*/
1235 void wxTranslations::SetNonOwned(wxTranslations *t)
1236 {
1237 if ( gs_translationsOwned )
1238 delete gs_translations;
1239 gs_translations = t;
1240 gs_translationsOwned = false;
1241 }
1242
1243
1244 wxTranslations::wxTranslations()
1245 {
1246 m_pMsgCat = NULL;
1247 m_loader = new wxFileTranslationsLoader;
1248 }
1249
1250
1251 wxTranslations::~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
1266 void 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
1275 void wxTranslations::SetLanguage(wxLanguage lang)
1276 {
1277 if ( lang == wxLANGUAGE_DEFAULT )
1278 SetLanguage("");
1279 else
1280 SetLanguage(wxLocale::GetLanguageCanonicalName(lang));
1281 }
1282
1283 void wxTranslations::SetLanguage(const wxString& lang)
1284 {
1285 m_lang = lang;
1286 }
1287
1288
1289 bool 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
1306 bool wxTranslations::AddCatalog(const wxString& domain)
1307 {
1308 return AddCatalog(domain, wxLANGUAGE_ENGLISH_US);
1309 }
1310
1311 #if !wxUSE_UNICODE
1312 bool wxTranslations::AddCatalog(const wxString& domain,
1313 wxLanguage msgIdLanguage,
1314 const wxString& msgIdCharset)
1315 {
1316 gs_msgIdCharset[domain] = msgIdCharset;
1317 return AddCatalog(domain, msgIdLanguage);
1318 }
1319 #endif // !wxUSE_UNICODE
1320
1321 bool 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
1345 return LoadCatalog(domain, domain_lang);
1346 }
1347
1348
1349 bool wxTranslations::LoadCatalog(const wxString& domain, const wxString& lang)
1350 {
1351 wxCHECK_MSG( m_loader, false, "loader can't be NULL" );
1352
1353 wxMsgCatalog *cat = NULL;
1354
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
1366 cat = m_loader->LoadCatalog(domain, fullname);
1367 }
1368 #endif // wxUSE_FONTMAP
1369
1370 if ( !cat )
1371 {
1372 // Next try: use the provided name language name:
1373 cat = m_loader->LoadCatalog(domain, lang);
1374 }
1375
1376 if ( !cat )
1377 {
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);
1384 }
1385
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;
1392
1393 return true;
1394 }
1395 else
1396 {
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);
1401 return false;
1402 }
1403 }
1404
1405 // check if the given catalog is loaded
1406 bool wxTranslations::IsLoaded(const wxString& domain) const
1407 {
1408 return FindCatalog(domain) != NULL;
1409 }
1410
1411
1412 wxString 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
1426 namespace
1427 {
1428 WX_DECLARE_HASH_SET(wxString, wxStringHash, wxStringEqual,
1429 wxLocaleUntranslatedStrings);
1430 }
1431
1432 /* static */
1433 const 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
1445 const wxString& wxTranslations::GetString(const wxString& origString,
1446 const wxString& domain) const
1447 {
1448 return GetString(origString, origString, UINT_MAX, domain);
1449 }
1450
1451 const wxString& wxTranslations::GetString(const wxString& origString,
1452 const wxString& origString2,
1453 unsigned n,
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 n != UINT_MAX ? wxString::Format("[%ld]", (long)n) : wxString(),
1489 !domain.empty() ? wxString::Format("domain '%s' ", domain) : wxString(),
1490 m_lang
1491 );
1492
1493 if (n == UINT_MAX)
1494 return GetUntranslatedString(origString);
1495 else
1496 return GetUntranslatedString(n == 1 ? origString : origString2);
1497 }
1498
1499 return *trans;
1500 }
1501
1502
1503 wxString 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
1520 trans = pMsgCat->GetString(wxEmptyString, UINT_MAX);
1521 }
1522 else
1523 {
1524 // search in all domains
1525 for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext )
1526 {
1527 trans = pMsgCat->GetString(wxEmptyString, UINT_MAX);
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
1553 wxMsgCatalog *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
1570 namespace
1571 {
1572
1573 // the list of the directories to search for message catalog files
1574 wxArrayString gs_searchPrefixes;
1575
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)
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
1603 static 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
1665 void 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
1675 wxMsgCatalog *wxFileTranslationsLoader::LoadCatalog(const wxString& domain,
1676 const wxString& lang)
1677 {
1678 wxString searchPath = GetFullSearchPath(lang);
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()) )
1688 return NULL;
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
1694 return wxMsgCatalog::CreateFromFile(strFullName, domain);
1695 }
1696
1697
1698 // ----------------------------------------------------------------------------
1699 // wxResourceTranslationsLoader
1700 // ----------------------------------------------------------------------------
1701
1702 #ifdef __WINDOWS__
1703 wxMsgCatalog *wxResourceTranslationsLoader::LoadCatalog(const wxString& domain,
1704 const wxString& lang)
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
1721 wxMsgCatalog *cat = wxMsgCatalog::CreateFromData(
1722 wxCharBuffer::CreateNonOwned(static_cast<const char*>(mo_data), mo_size),
1723 domain);
1724
1725 if ( !cat )
1726 wxLogWarning(_("Resource '%s' is not a valid message catalog."), resname);
1727
1728 return cat;
1729 }
1730 #endif // __WINDOWS__
1731
1732
1733 // ----------------------------------------------------------------------------
1734 // wxTranslationsModule module (for destruction of gs_translations)
1735 // ----------------------------------------------------------------------------
1736
1737 class 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
1757 IMPLEMENT_DYNAMIC_CLASS(wxTranslationsModule, wxModule)
1758
1759 #endif // wxUSE_INTL