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