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