]> git.saurik.com Git - wxWidgets.git/blob - src/common/translation.cpp
Shorten lang names in wxTranslations, not wxFileTranslationsLoader.
[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 // ctor & dtor
813 wxMsgCatalogFile();
814 ~wxMsgCatalogFile();
815
816 // load the catalog from disk
817 bool Load(const wxString& filename,
818 wxPluralFormsCalculatorPtr& rPluralFormsCalculator);
819
820 // fills the hash with string-translation pairs
821 bool FillHash(wxMessagesHash& hash, const wxString& msgIdCharset) const;
822
823 // return the charset of the strings in this catalog or empty string if
824 // none/unknown
825 wxString GetCharset() const { return m_charset; }
826
827 private:
828 // this implementation is binary compatible with GNU gettext() version 0.10
829
830 // an entry in the string table
831 struct wxMsgTableEntry
832 {
833 size_t32 nLen; // length of the string
834 size_t32 ofsString; // pointer to the string
835 };
836
837 // header of a .mo file
838 struct wxMsgCatalogHeader
839 {
840 size_t32 magic, // offset +00: magic id
841 revision, // +04: revision
842 numStrings; // +08: number of strings in the file
843 size_t32 ofsOrigTable, // +0C: start of original string table
844 ofsTransTable; // +10: start of translated string table
845 size_t32 nHashSize, // +14: hash table size
846 ofsHashTable; // +18: offset of hash table start
847 };
848
849 // all data is stored here
850 wxMemoryBuffer m_data;
851
852 // data description
853 size_t32 m_numStrings; // number of strings in this domain
854 wxMsgTableEntry *m_pOrigTable, // pointer to original strings
855 *m_pTransTable; // translated
856
857 wxString m_charset; // from the message catalog header
858
859
860 // swap the 2 halves of 32 bit integer if needed
861 size_t32 Swap(size_t32 ui) const
862 {
863 return m_bSwapped ? (ui << 24) | ((ui & 0xff00) << 8) |
864 ((ui >> 8) & 0xff00) | (ui >> 24)
865 : ui;
866 }
867
868 // just return the pointer to the start of the data as "char *" to
869 // facilitate doing pointer arithmetic with it
870 char *StringData() const
871 {
872 return static_cast<char *>(m_data.GetData());
873 }
874
875 const char *StringAtOfs(wxMsgTableEntry *pTable, size_t32 n) const
876 {
877 const wxMsgTableEntry * const ent = pTable + n;
878
879 // this check could fail for a corrupt message catalog
880 size_t32 ofsString = Swap(ent->ofsString);
881 if ( ofsString + Swap(ent->nLen) > m_data.GetDataLen())
882 {
883 return NULL;
884 }
885
886 return StringData() + ofsString;
887 }
888
889 bool m_bSwapped; // wrong endianness?
890
891 wxDECLARE_NO_COPY_CLASS(wxMsgCatalogFile);
892 };
893
894
895 // ----------------------------------------------------------------------------
896 // wxMsgCatalog corresponds to one loaded message catalog.
897 //
898 // This is a "low-level" class and is used only by wxLocale (that's why
899 // it's designed to be stored in a linked list)
900 // ----------------------------------------------------------------------------
901
902 class wxMsgCatalog
903 {
904 public:
905 #if !wxUSE_UNICODE
906 wxMsgCatalog() { m_conv = NULL; }
907 ~wxMsgCatalog();
908 #endif
909
910 // load the catalog from disk
911 bool Load(const wxString& filename,
912 const wxString& domain,
913 const wxString& msgIdCharset);
914
915 // get name of the catalog
916 wxString GetDomain() const { return m_domain; }
917
918 // get the translated string: returns NULL if not found
919 const wxString *GetString(const wxString& sz, size_t n = size_t(-1)) const;
920
921 // public variable pointing to the next element in a linked list (or NULL)
922 wxMsgCatalog *m_pNext;
923
924 private:
925 wxMessagesHash m_messages; // all messages in the catalog
926 wxString m_domain; // name of the domain
927
928 #if !wxUSE_UNICODE
929 // the conversion corresponding to this catalog charset if we installed it
930 // as the global one
931 wxCSConv *m_conv;
932 #endif
933
934 wxPluralFormsCalculatorPtr m_pluralFormsCalculator;
935 };
936
937 // ----------------------------------------------------------------------------
938 // wxMsgCatalogFile clas
939 // ----------------------------------------------------------------------------
940
941 wxMsgCatalogFile::wxMsgCatalogFile()
942 {
943 }
944
945 wxMsgCatalogFile::~wxMsgCatalogFile()
946 {
947 }
948
949 // open disk file and read in it's contents
950 bool wxMsgCatalogFile::Load(const wxString& filename,
951 wxPluralFormsCalculatorPtr& rPluralFormsCalculator)
952 {
953 wxFile fileMsg(filename);
954 if ( !fileMsg.IsOpened() )
955 return false;
956
957 // get the file size (assume it is less than 4Gb...)
958 wxFileOffset lenFile = fileMsg.Length();
959 if ( lenFile == wxInvalidOffset )
960 return false;
961
962 size_t nSize = wx_truncate_cast(size_t, lenFile);
963 wxASSERT_MSG( nSize == lenFile + size_t(0), wxS("message catalog bigger than 4GB?") );
964
965 // read the whole file in memory
966 if ( fileMsg.Read(m_data.GetWriteBuf(nSize), nSize) != lenFile )
967 return false;
968
969 m_data.UngetWriteBuf(nSize);
970
971
972 // examine header
973 bool bValid = m_data.GetDataLen() > sizeof(wxMsgCatalogHeader);
974
975 const wxMsgCatalogHeader *pHeader = (wxMsgCatalogHeader *)m_data.GetData();
976 if ( bValid ) {
977 // we'll have to swap all the integers if it's true
978 m_bSwapped = pHeader->magic == MSGCATALOG_MAGIC_SW;
979
980 // check the magic number
981 bValid = m_bSwapped || pHeader->magic == MSGCATALOG_MAGIC;
982 }
983
984 if ( !bValid ) {
985 // it's either too short or has incorrect magic number
986 wxLogWarning(_("'%s' is not a valid message catalog."), filename.c_str());
987
988 return false;
989 }
990
991 // initialize
992 m_numStrings = Swap(pHeader->numStrings);
993 m_pOrigTable = (wxMsgTableEntry *)(StringData() +
994 Swap(pHeader->ofsOrigTable));
995 m_pTransTable = (wxMsgTableEntry *)(StringData() +
996 Swap(pHeader->ofsTransTable));
997
998 // now parse catalog's header and try to extract catalog charset and
999 // plural forms formula from it:
1000
1001 const char* headerData = StringAtOfs(m_pOrigTable, 0);
1002 if ( headerData && headerData[0] == '\0' )
1003 {
1004 // Extract the charset:
1005 const char * const header = StringAtOfs(m_pTransTable, 0);
1006 const char *
1007 cset = strstr(header, "Content-Type: text/plain; charset=");
1008 if ( cset )
1009 {
1010 cset += 34; // strlen("Content-Type: text/plain; charset=")
1011
1012 const char * const csetEnd = strchr(cset, '\n');
1013 if ( csetEnd )
1014 {
1015 m_charset = wxString(cset, csetEnd - cset);
1016 if ( m_charset == wxS("CHARSET") )
1017 {
1018 // "CHARSET" is not valid charset, but lazy translator
1019 m_charset.empty();
1020 }
1021 }
1022 }
1023 // else: incorrectly filled Content-Type header
1024
1025 // Extract plural forms:
1026 const char * plurals = strstr(header, "Plural-Forms:");
1027 if ( plurals )
1028 {
1029 plurals += 13; // strlen("Plural-Forms:")
1030 const char * const pluralsEnd = strchr(plurals, '\n');
1031 if ( pluralsEnd )
1032 {
1033 const size_t pluralsLen = pluralsEnd - plurals;
1034 wxCharBuffer buf(pluralsLen);
1035 strncpy(buf.data(), plurals, pluralsLen);
1036 wxPluralFormsCalculator * const
1037 pCalculator = wxPluralFormsCalculator::make(buf);
1038 if ( pCalculator )
1039 {
1040 rPluralFormsCalculator.reset(pCalculator);
1041 }
1042 else
1043 {
1044 wxLogVerbose(_("Failed to parse Plural-Forms: '%s'"),
1045 buf.data());
1046 }
1047 }
1048 }
1049
1050 if ( !rPluralFormsCalculator.get() )
1051 rPluralFormsCalculator.reset(wxPluralFormsCalculator::make());
1052 }
1053
1054 // everything is fine
1055 return true;
1056 }
1057
1058 bool wxMsgCatalogFile::FillHash(wxMessagesHash& hash,
1059 const wxString& msgIdCharset) const
1060 {
1061 wxUnusedVar(msgIdCharset); // silence warning in Unicode build
1062
1063 // conversion to use to convert catalog strings to the GUI encoding
1064 wxMBConv *inputConv = NULL;
1065 wxMBConv *inputConvPtr = NULL; // same as inputConv but safely deleteable
1066
1067 if ( !m_charset.empty() )
1068 {
1069 #if !wxUSE_UNICODE && wxUSE_FONTMAP
1070 // determine if we need any conversion at all
1071 wxFontEncoding encCat = wxFontMapperBase::GetEncodingFromName(m_charset);
1072 if ( encCat != wxLocale::GetSystemEncoding() )
1073 #endif
1074 {
1075 inputConvPtr =
1076 inputConv = new wxCSConv(m_charset);
1077 }
1078 }
1079 else // no need or not possible to convert the encoding
1080 {
1081 #if wxUSE_UNICODE
1082 // we must somehow convert the narrow strings in the message catalog to
1083 // wide strings, so use the default conversion if we have no charset
1084 inputConv = wxConvCurrent;
1085 #endif
1086 }
1087
1088 #if !wxUSE_UNICODE
1089 // conversion to apply to msgid strings before looking them up: we only
1090 // need it if the msgids are neither in 7 bit ASCII nor in the same
1091 // encoding as the catalog
1092 wxCSConv *sourceConv = msgIdCharset.empty() || (msgIdCharset == m_charset)
1093 ? NULL
1094 : new wxCSConv(msgIdCharset);
1095 #endif // !wxUSE_UNICODE
1096
1097 for (size_t32 i = 0; i < m_numStrings; i++)
1098 {
1099 const char *data = StringAtOfs(m_pOrigTable, i);
1100 if (!data)
1101 return false; // may happen for invalid MO files
1102
1103 wxString msgid;
1104 #if wxUSE_UNICODE
1105 msgid = wxString(data, *inputConv);
1106 #else // ASCII
1107 if ( inputConv && sourceConv )
1108 msgid = wxString(inputConv->cMB2WC(data), *sourceConv);
1109 else
1110 msgid = data;
1111 #endif // wxUSE_UNICODE
1112
1113 data = StringAtOfs(m_pTransTable, i);
1114 if (!data)
1115 return false; // may happen for invalid MO files
1116
1117 size_t length = Swap(m_pTransTable[i].nLen);
1118 size_t offset = 0;
1119 size_t index = 0;
1120 while (offset < length)
1121 {
1122 const char * const str = data + offset;
1123
1124 wxString msgstr;
1125 #if wxUSE_UNICODE
1126 msgstr = wxString(str, *inputConv);
1127 #else
1128 if ( inputConv )
1129 msgstr = wxString(inputConv->cMB2WC(str), *wxConvUI);
1130 else
1131 msgstr = str;
1132 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
1133
1134 if ( !msgstr.empty() )
1135 {
1136 hash[index == 0 ? msgid : msgid + wxChar(index)] = msgstr;
1137 }
1138
1139 // skip this string
1140 // IMPORTANT: accesses to the 'data' pointer are valid only for
1141 // the first 'length+1' bytes (GNU specs says that the
1142 // final NUL is not counted in length); using wxStrnlen()
1143 // we make sure we don't access memory beyond the valid range
1144 // (which otherwise may happen for invalid MO files):
1145 offset += wxStrnlen(str, length - offset) + 1;
1146 ++index;
1147 }
1148 }
1149
1150 #if !wxUSE_UNICODE
1151 delete sourceConv;
1152 #endif
1153 delete inputConvPtr;
1154
1155 return true;
1156 }
1157
1158
1159 // ----------------------------------------------------------------------------
1160 // wxMsgCatalog class
1161 // ----------------------------------------------------------------------------
1162
1163 #if !wxUSE_UNICODE
1164 wxMsgCatalog::~wxMsgCatalog()
1165 {
1166 if ( m_conv )
1167 {
1168 if ( wxConvUI == m_conv )
1169 {
1170 // we only change wxConvUI if it points to wxConvLocal so we reset
1171 // it back to it too
1172 wxConvUI = &wxConvLocal;
1173 }
1174
1175 delete m_conv;
1176 }
1177 }
1178 #endif // !wxUSE_UNICODE
1179
1180 bool wxMsgCatalog::Load(const wxString& filename,
1181 const wxString& domain,
1182 const wxString& msgIdCharset)
1183 {
1184 wxMsgCatalogFile file;
1185
1186 m_domain = domain;
1187
1188 if ( !file.Load(filename, m_pluralFormsCalculator) )
1189 return false;
1190
1191 if ( !file.FillHash(m_messages, msgIdCharset) )
1192 return false;
1193
1194 return true;
1195 }
1196
1197 const wxString *wxMsgCatalog::GetString(const wxString& str, size_t n) const
1198 {
1199 int index = 0;
1200 if (n != size_t(-1))
1201 {
1202 index = m_pluralFormsCalculator->evaluate(n);
1203 }
1204 wxMessagesHash::const_iterator i;
1205 if (index != 0)
1206 {
1207 i = m_messages.find(wxString(str) + wxChar(index)); // plural
1208 }
1209 else
1210 {
1211 i = m_messages.find(str);
1212 }
1213
1214 if ( i != m_messages.end() )
1215 {
1216 return &i->second;
1217 }
1218 else
1219 return NULL;
1220 }
1221
1222
1223 // ----------------------------------------------------------------------------
1224 // wxTranslations
1225 // ----------------------------------------------------------------------------
1226
1227 namespace
1228 {
1229
1230 wxTranslations *gs_translations = NULL;
1231 bool gs_translationsOwned = false;
1232
1233 } // anonymous namespace
1234
1235
1236 /*static*/
1237 wxTranslations *wxTranslations::Get()
1238 {
1239 return gs_translations;
1240 }
1241
1242 /*static*/
1243 void wxTranslations::Set(wxTranslations *t)
1244 {
1245 if ( gs_translationsOwned )
1246 delete gs_translations;
1247 gs_translations = t;
1248 gs_translationsOwned = true;
1249 }
1250
1251 /*static*/
1252 void wxTranslations::SetNonOwned(wxTranslations *t)
1253 {
1254 if ( gs_translationsOwned )
1255 delete gs_translations;
1256 gs_translations = t;
1257 gs_translationsOwned = false;
1258 }
1259
1260
1261 wxTranslations::wxTranslations()
1262 {
1263 m_pMsgCat = NULL;
1264 m_loader = new wxFileTranslationsLoader;
1265 }
1266
1267
1268 wxTranslations::~wxTranslations()
1269 {
1270 delete m_loader;
1271
1272 // free catalogs memory
1273 wxMsgCatalog *pTmpCat;
1274 while ( m_pMsgCat != NULL )
1275 {
1276 pTmpCat = m_pMsgCat;
1277 m_pMsgCat = m_pMsgCat->m_pNext;
1278 delete pTmpCat;
1279 }
1280 }
1281
1282
1283 void wxTranslations::SetLoader(wxTranslationsLoader *loader)
1284 {
1285 wxCHECK_RET( loader, "loader can't be NULL" );
1286
1287 delete m_loader;
1288 m_loader = loader;
1289 }
1290
1291
1292 void wxTranslations::SetLanguage(wxLanguage lang)
1293 {
1294 if ( lang == wxLANGUAGE_DEFAULT )
1295 SetLanguage("");
1296 else
1297 SetLanguage(wxLocale::GetLanguageCanonicalName(lang));
1298 }
1299
1300 void wxTranslations::SetLanguage(const wxString& lang)
1301 {
1302 m_lang = lang;
1303 }
1304
1305
1306 bool wxTranslations::AddStdCatalog()
1307 {
1308 if ( !AddCatalog(wxS("wxstd")) )
1309 return false;
1310
1311 // there may be a catalog with toolkit specific overrides, it is not
1312 // an error if this does not exist
1313 wxString port(wxPlatformInfo::Get().GetPortIdName());
1314 if ( !port.empty() )
1315 {
1316 AddCatalog(port.BeforeFirst(wxS('/')).MakeLower());
1317 }
1318
1319 return true;
1320 }
1321
1322
1323 bool wxTranslations::AddCatalog(const wxString& domain)
1324 {
1325 return AddCatalog(domain, wxLANGUAGE_ENGLISH_US);
1326 }
1327
1328 #if !wxUSE_UNICODE
1329 bool wxTranslations::AddCatalog(const wxString& domain,
1330 wxLanguage msgIdLanguage,
1331 const wxString& msgIdCharset)
1332 {
1333 m_msgIdCharset[domain] = msgIdCharset;
1334 return AddCatalog(domain, msgIdLanguage);
1335 }
1336 #endif // !wxUSE_UNICODE
1337
1338 bool wxTranslations::AddCatalog(const wxString& domain,
1339 wxLanguage msgIdLanguage)
1340 {
1341 const wxString msgIdLang = wxLocale::GetLanguageCanonicalName(msgIdLanguage);
1342 const wxString domain_lang = ChooseLanguageForDomain(domain, msgIdLang);
1343
1344 if ( domain_lang.empty() )
1345 {
1346 wxLogTrace(TRACE_I18N,
1347 wxS("no suitable translation for domain '%s' found"),
1348 domain);
1349 return false;
1350 }
1351
1352 wxLogTrace(TRACE_I18N,
1353 wxS("adding '%s' translation for domain '%s' (msgid language '%s')"),
1354 domain_lang, domain, msgIdLang);
1355
1356 // It is OK to not load catalog if the msgid language and m_language match,
1357 // in which case we can directly display the texts embedded in program's
1358 // source code:
1359 if ( msgIdLang == domain_lang )
1360 return true;
1361
1362 return LoadCatalog(domain, domain_lang);
1363 }
1364
1365
1366 bool wxTranslations::LoadCatalog(const wxString& domain, const wxString& lang)
1367 {
1368 wxCHECK_MSG( m_loader, false, "loader can't be NULL" );
1369
1370 #if wxUSE_FONTMAP
1371 // first look for the catalog for this language and the current locale:
1372 // notice that we don't use the system name for the locale as this would
1373 // force us to install catalogs in different locations depending on the
1374 // system but always use the canonical name
1375 wxFontEncoding encSys = wxLocale::GetSystemEncoding();
1376 if ( encSys != wxFONTENCODING_SYSTEM )
1377 {
1378 wxString fullname(lang);
1379 fullname << wxS('.') << wxFontMapperBase::GetEncodingName(encSys);
1380
1381 if ( m_loader->LoadCatalog(this, domain, fullname) )
1382 return true;
1383 }
1384 #endif // wxUSE_FONTMAP
1385
1386 // Next try: use the provided name language name:
1387 if ( m_loader->LoadCatalog(this, domain, lang) )
1388 return true;
1389
1390 // Also try just base locale name: for things like "fr_BE" (Belgium
1391 // French) we should use fall back on plain "fr" if no Belgium-specific
1392 // message catalogs exist
1393 if ( lang.length() > LEN_LANG && lang[LEN_LANG] == wxS('_') )
1394 {
1395 if ( m_loader->LoadCatalog(this, domain, ExtractLang(lang)) )
1396 return true;
1397 }
1398
1399 // Nothing worked, the catalog just isn't there
1400 wxLogTrace(TRACE_I18N,
1401 "Catalog \"%s.mo\" not found for language \"%s\".",
1402 domain, lang);
1403 return false;
1404 }
1405
1406
1407 // check if the given catalog is loaded
1408 bool wxTranslations::IsLoaded(const wxString& domain) const
1409 {
1410 return FindCatalog(domain) != NULL;
1411 }
1412
1413
1414 bool wxTranslations::LoadCatalogFile(const wxString& filename,
1415 const wxString& domain)
1416 {
1417 wxMsgCatalog *pMsgCat = new wxMsgCatalog;
1418
1419 #if wxUSE_UNICODE
1420 const bool ok = pMsgCat->Load(filename, domain, wxEmptyString/*unused*/);
1421 #else
1422 const bool ok = pMsgCat->Load(filename, domain,
1423 m_msgIdCharset[domain]);
1424 #endif
1425
1426 if ( !ok )
1427 {
1428 // don't add it because it couldn't be loaded anyway
1429 delete pMsgCat;
1430 return false;
1431 }
1432
1433 // add it to the head of the list so that in GetString it will
1434 // be searched before the catalogs added earlier
1435 pMsgCat->m_pNext = m_pMsgCat;
1436 m_pMsgCat = pMsgCat;
1437
1438 return true;
1439 }
1440
1441
1442 wxString wxTranslations::ChooseLanguageForDomain(const wxString& WXUNUSED(domain),
1443 const wxString& WXUNUSED(msgIdLang))
1444 {
1445 // explicitly set language should always be respected
1446 if ( !m_lang.empty() )
1447 return m_lang;
1448
1449 // TODO: if the default language is used, pick the best (by comparing
1450 // available languages with user's preferences), instead of blindly
1451 // trusting availability of system language translation
1452 return wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage());
1453 }
1454
1455
1456 namespace
1457 {
1458 WX_DECLARE_HASH_SET(wxString, wxStringHash, wxStringEqual,
1459 wxLocaleUntranslatedStrings);
1460 }
1461
1462 /* static */
1463 const wxString& wxTranslations::GetUntranslatedString(const wxString& str)
1464 {
1465 static wxLocaleUntranslatedStrings s_strings;
1466
1467 wxLocaleUntranslatedStrings::iterator i = s_strings.find(str);
1468 if ( i == s_strings.end() )
1469 return *s_strings.insert(str).first;
1470
1471 return *i;
1472 }
1473
1474
1475 const wxString& wxTranslations::GetString(const wxString& origString,
1476 const wxString& domain) const
1477 {
1478 return GetString(origString, origString, size_t(-1), domain);
1479 }
1480
1481 const wxString& wxTranslations::GetString(const wxString& origString,
1482 const wxString& origString2,
1483 size_t n,
1484 const wxString& domain) const
1485 {
1486 if ( origString.empty() )
1487 return GetUntranslatedString(origString);
1488
1489 const wxString *trans = NULL;
1490 wxMsgCatalog *pMsgCat;
1491
1492 if ( !domain.empty() )
1493 {
1494 pMsgCat = FindCatalog(domain);
1495
1496 // does the catalog exist?
1497 if ( pMsgCat != NULL )
1498 trans = pMsgCat->GetString(origString, n);
1499 }
1500 else
1501 {
1502 // search in all domains
1503 for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext )
1504 {
1505 trans = pMsgCat->GetString(origString, n);
1506 if ( trans != NULL ) // take the first found
1507 break;
1508 }
1509 }
1510
1511 if ( trans == NULL )
1512 {
1513 wxLogTrace
1514 (
1515 TRACE_I18N,
1516 "string \"%s\"%s not found in %slocale '%s'.",
1517 origString,
1518 ((long)n) != -1 ? wxString::Format("[%ld]", (long)n) : wxString(),
1519 !domain.empty() ? wxString::Format("domain '%s' ", domain) : wxString(),
1520 m_lang
1521 );
1522
1523 if (n == size_t(-1))
1524 return GetUntranslatedString(origString);
1525 else
1526 return GetUntranslatedString(n == 1 ? origString : origString2);
1527 }
1528
1529 return *trans;
1530 }
1531
1532
1533 wxString wxTranslations::GetHeaderValue(const wxString& header,
1534 const wxString& domain) const
1535 {
1536 if ( header.empty() )
1537 return wxEmptyString;
1538
1539 const wxString *trans = NULL;
1540 wxMsgCatalog *pMsgCat;
1541
1542 if ( !domain.empty() )
1543 {
1544 pMsgCat = FindCatalog(domain);
1545
1546 // does the catalog exist?
1547 if ( pMsgCat == NULL )
1548 return wxEmptyString;
1549
1550 trans = pMsgCat->GetString(wxEmptyString, (size_t)-1);
1551 }
1552 else
1553 {
1554 // search in all domains
1555 for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext )
1556 {
1557 trans = pMsgCat->GetString(wxEmptyString, (size_t)-1);
1558 if ( trans != NULL ) // take the first found
1559 break;
1560 }
1561 }
1562
1563 if ( !trans || trans->empty() )
1564 return wxEmptyString;
1565
1566 size_t found = trans->find(header);
1567 if ( found == wxString::npos )
1568 return wxEmptyString;
1569
1570 found += header.length() + 2 /* ': ' */;
1571
1572 // Every header is separated by \n
1573
1574 size_t endLine = trans->find(wxS('\n'), found);
1575 size_t len = (endLine == wxString::npos) ?
1576 wxString::npos : (endLine - found);
1577
1578 return trans->substr(found, len);
1579 }
1580
1581
1582 // find catalog by name in a linked list, return NULL if !found
1583 wxMsgCatalog *wxTranslations::FindCatalog(const wxString& domain) const
1584 {
1585 // linear search in the linked list
1586 wxMsgCatalog *pMsgCat;
1587 for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext )
1588 {
1589 if ( pMsgCat->GetDomain() == domain )
1590 return pMsgCat;
1591 }
1592
1593 return NULL;
1594 }
1595
1596 // ----------------------------------------------------------------------------
1597 // wxFileTranslationsLoader
1598 // ----------------------------------------------------------------------------
1599
1600 namespace
1601 {
1602
1603 // the list of the directories to search for message catalog files
1604 wxArrayString gs_searchPrefixes;
1605
1606 // return the directories to search for message catalogs under the given
1607 // prefix, separated by wxPATH_SEP
1608 wxString GetMsgCatalogSubdirs(const wxString& prefix, const wxString& lang)
1609 {
1610 // Search first in Unix-standard prefix/lang/LC_MESSAGES, then in
1611 // prefix/lang and finally in just prefix.
1612 //
1613 // Note that we use LC_MESSAGES on all platforms and not just Unix, because
1614 // it doesn't cost much to look into one more directory and doing it this
1615 // way has two important benefits:
1616 // a) we don't break compatibility with wx-2.6 and older by stopping to
1617 // look in a directory where the catalogs used to be and thus silently
1618 // breaking apps after they are recompiled against the latest wx
1619 // b) it makes it possible to package app's support files in the same
1620 // way on all target platforms
1621 const wxString pathPrefix = wxFileName(prefix, lang).GetFullPath();
1622
1623 wxString searchPath;
1624 searchPath.reserve(4*pathPrefix.length());
1625 searchPath << pathPrefix << wxFILE_SEP_PATH << "LC_MESSAGES" << wxPATH_SEP
1626 << prefix << wxFILE_SEP_PATH << wxPATH_SEP
1627 << pathPrefix;
1628
1629 return searchPath;
1630 }
1631
1632 // construct the search path for the given language
1633 static wxString GetFullSearchPath(const wxString& lang)
1634 {
1635 // first take the entries explicitly added by the program
1636 wxArrayString paths;
1637 paths.reserve(gs_searchPrefixes.size() + 1);
1638 size_t n,
1639 count = gs_searchPrefixes.size();
1640 for ( n = 0; n < count; n++ )
1641 {
1642 paths.Add(GetMsgCatalogSubdirs(gs_searchPrefixes[n], lang));
1643 }
1644
1645
1646 #if wxUSE_STDPATHS
1647 // then look in the standard location
1648 const wxString stdp = wxStandardPaths::Get().
1649 GetLocalizedResourcesDir(lang, wxStandardPaths::ResourceCat_Messages);
1650
1651 if ( paths.Index(stdp) == wxNOT_FOUND )
1652 paths.Add(stdp);
1653 #endif // wxUSE_STDPATHS
1654
1655 // last look in default locations
1656 #ifdef __UNIX__
1657 // LC_PATH is a standard env var containing the search path for the .mo
1658 // files
1659 const char *pszLcPath = wxGetenv("LC_PATH");
1660 if ( pszLcPath )
1661 {
1662 const wxString lcp = GetMsgCatalogSubdirs(pszLcPath, lang);
1663 if ( paths.Index(lcp) == wxNOT_FOUND )
1664 paths.Add(lcp);
1665 }
1666
1667 // also add the one from where wxWin was installed:
1668 wxString wxp = wxGetInstallPrefix();
1669 if ( !wxp.empty() )
1670 {
1671 wxp = GetMsgCatalogSubdirs(wxp + wxS("/share/locale"), lang);
1672 if ( paths.Index(wxp) == wxNOT_FOUND )
1673 paths.Add(wxp);
1674 }
1675 #endif // __UNIX__
1676
1677
1678 // finally construct the full search path
1679 wxString searchPath;
1680 searchPath.reserve(500);
1681 count = paths.size();
1682 for ( n = 0; n < count; n++ )
1683 {
1684 searchPath += paths[n];
1685 if ( n != count - 1 )
1686 searchPath += wxPATH_SEP;
1687 }
1688
1689 return searchPath;
1690 }
1691
1692 } // anonymous namespace
1693
1694
1695 void wxFileTranslationsLoader::AddCatalogLookupPathPrefix(const wxString& prefix)
1696 {
1697 if ( gs_searchPrefixes.Index(prefix) == wxNOT_FOUND )
1698 {
1699 gs_searchPrefixes.Add(prefix);
1700 }
1701 //else: already have it
1702 }
1703
1704
1705 bool wxFileTranslationsLoader::LoadCatalog(wxTranslations *translations,
1706 const wxString& domain,
1707 const wxString& lang)
1708 {
1709 wxCHECK_MSG( lang.length() >= LEN_LANG, false,
1710 "invalid language specification" );
1711
1712 wxString searchPath = GetFullSearchPath(lang);
1713
1714 wxLogTrace(TRACE_I18N, wxS("Looking for \"%s.mo\" in search path \"%s\""),
1715 domain, searchPath);
1716
1717 wxFileName fn(domain);
1718 fn.SetExt(wxS("mo"));
1719
1720 wxString strFullName;
1721 if ( !wxFindFileInPath(&strFullName, searchPath, fn.GetFullPath()) )
1722 return false;
1723
1724 // open file and read its data
1725 wxLogVerbose(_("using catalog '%s' from '%s'."), domain, strFullName.c_str());
1726 wxLogTrace(TRACE_I18N, wxS("Using catalog \"%s\"."), strFullName.c_str());
1727
1728 return translations->LoadCatalogFile(strFullName, domain);
1729 }
1730
1731 // ----------------------------------------------------------------------------
1732 // wxTranslationsModule module (for destruction of gs_translations)
1733 // ----------------------------------------------------------------------------
1734
1735 class wxTranslationsModule: public wxModule
1736 {
1737 DECLARE_DYNAMIC_CLASS(wxTranslationsModule)
1738 public:
1739 wxTranslationsModule() {}
1740
1741 bool OnInit()
1742 {
1743 return true;
1744 }
1745
1746 void OnExit()
1747 {
1748 if ( gs_translationsOwned )
1749 delete gs_translations;
1750 gs_translations = NULL;
1751 gs_translationsOwned = true;
1752 }
1753 };
1754
1755 IMPLEMENT_DYNAMIC_CLASS(wxTranslationsModule, wxModule)
1756
1757 #endif // wxUSE_INTL