]> git.saurik.com Git - wxWidgets.git/blame - src/common/translation.cpp
non-pch build fix
[wxWidgets.git] / src / common / translation.cpp
CommitLineData
ea144923
VS
1/////////////////////////////////////////////////////////////////////////////
2// Name: src/common/translation.cpp
3// Purpose: Internationalization and localisation for wxWidgets
4// Author: Vadim Zeitlin, Vaclav Slavik,
5// Michael N. Filippov <michael@idisys.iae.nsk.su>
6// (2003/09/30 - PluralForms support)
7// Created: 2010-04-23
8// RCS-ID: $Id$
9// Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
10// Licence: wxWindows licence
11/////////////////////////////////////////////////////////////////////////////
12
13// ============================================================================
14// declaration
15// ============================================================================
16
17// ----------------------------------------------------------------------------
18// headers
19// ----------------------------------------------------------------------------
20
21// For compilers that support precompilation, includes "wx.h".
22#include "wx/wxprec.h"
23
24#ifdef __BORLANDC__
25 #pragma hdrstop
26#endif
27
28#if wxUSE_INTL
29
30#ifndef WX_PRECOMP
31 #include "wx/dynarray.h"
32 #include "wx/string.h"
33 #include "wx/intl.h"
34 #include "wx/log.h"
35 #include "wx/utils.h"
36 #include "wx/hashmap.h"
37 #include "wx/module.h"
38#endif // WX_PRECOMP
39
40// standard headers
41#include <ctype.h>
42#include <stdlib.h>
43
44#include "wx/file.h"
45#include "wx/filename.h"
46#include "wx/tokenzr.h"
47#include "wx/fontmap.h"
48#include "wx/scopedptr.h"
49#include "wx/stdpaths.h"
50#include "wx/hashset.h"
51
52// ----------------------------------------------------------------------------
53// simple types
54// ----------------------------------------------------------------------------
55
56typedef wxUint32 size_t32;
57
58// ----------------------------------------------------------------------------
59// constants
60// ----------------------------------------------------------------------------
61
62// magic number identifying the .mo format file
63const size_t32 MSGCATALOG_MAGIC = 0x950412de;
64const 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
69static const size_t LEN_LANG = 2;
70
71// ----------------------------------------------------------------------------
72// global functions
73// ----------------------------------------------------------------------------
74
75namespace
76{
77
78// get just the language part
79inline 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
98Expression:
99 LogicalOrExpression '?' Expression ':' Expression
100 LogicalOrExpression
101
102LogicalOrExpression:
103 LogicalAndExpression "||" LogicalOrExpression // to (a || b) || c
104 LogicalAndExpression
105
106LogicalAndExpression:
107 EqualityExpression "&&" LogicalAndExpression // to (a && b) && c
108 EqualityExpression
109
110EqualityExpression:
111 RelationalExpression "==" RelationalExperession
112 RelationalExpression "!=" RelationalExperession
113 RelationalExpression
114
115RelationalExpression:
116 MultiplicativeExpression '>' MultiplicativeExpression
117 MultiplicativeExpression '<' MultiplicativeExpression
118 MultiplicativeExpression ">=" MultiplicativeExpression
119 MultiplicativeExpression "<=" MultiplicativeExpression
120 MultiplicativeExpression
121
122MultiplicativeExpression:
123 PmExpression '%' PmExpression
124 PmExpression
125
126PmExpression:
127 N
128 Number
129 '(' Expression ')'
130*/
131
132class wxPluralFormsToken
133{
134public:
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; }
149private:
150 Type m_type;
151 Number m_number;
152};
153
154
155class wxPluralFormsScanner
156{
157public:
158 wxPluralFormsScanner(const char* s);
159 const wxPluralFormsToken& token() const { return m_token; }
160 bool nextToken(); // returns false if error
161private:
162 const char* m_s;
163 wxPluralFormsToken m_token;
164};
165
166wxPluralFormsScanner::wxPluralFormsScanner(const char* s) : m_s(s)
167{
168 nextToken();
169}
170
171bool 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
299class wxPluralFormsNode;
300
301// NB: Can't use wxDEFINE_SCOPED_PTR_TYPE because wxPluralFormsNode is not
302// fully defined yet:
303class wxPluralFormsNodePtr
304{
305public:
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
314private:
315 wxPluralFormsNode *m_p;
316};
317
318class wxPluralFormsNode
319{
320public:
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
329private:
330 wxPluralFormsToken m_token;
331 wxPluralFormsNodePtr m_nodes[3];
332};
333
334wxPluralFormsNodePtr::~wxPluralFormsNodePtr()
335{
336 delete m_p;
337}
338wxPluralFormsNode* wxPluralFormsNodePtr::release()
339{
340 wxPluralFormsNode *p = m_p;
341 m_p = NULL;
342 return p;
343}
344void wxPluralFormsNodePtr::reset(wxPluralFormsNode *p)
345{
346 if (p != m_p)
347 {
348 delete m_p;
349 m_p = p;
350 }
351}
352
353
354void wxPluralFormsNode::setNode(size_t i, wxPluralFormsNode* n)
355{
356 m_nodes[i].reset(n);
357}
358
359wxPluralFormsNode* wxPluralFormsNode::releaseNode(size_t i)
360{
361 return m_nodes[i].release();
362}
363
364wxPluralFormsToken::Number
365wxPluralFormsNode::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
414class wxPluralFormsCalculator
415{
416public:
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
431private:
432 wxPluralFormsToken::Number m_nplurals;
433 wxPluralFormsNodePtr m_plural;
434};
435
436wxDEFINE_SCOPED_PTR_TYPE(wxPluralFormsCalculator)
437
438void wxPluralFormsCalculator::init(wxPluralFormsToken::Number nplurals,
439 wxPluralFormsNode* plural)
440{
441 m_nplurals = nplurals;
442 m_plural.reset(plural);
443}
444
445int 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
460class wxPluralFormsParser
461{
462public:
463 wxPluralFormsParser(wxPluralFormsScanner& scanner) : m_scanner(scanner) {}
464 bool parse(wxPluralFormsCalculator& rCalculator);
465
466private:
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
482bool 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
522wxPluralFormsNode* 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
537const wxPluralFormsToken& wxPluralFormsParser::token() const
538{
539 return m_scanner.token();
540}
541
542bool wxPluralFormsParser::nextToken()
543{
544 if (!m_scanner.nextToken())
545 return false;
546 return true;
547}
548
549wxPluralFormsNode* 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
588wxPluralFormsNode*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
624wxPluralFormsNode* 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
662wxPluralFormsNode* 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
688wxPluralFormsNode* 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
716wxPluralFormsNode* 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
741wxPluralFormsNode* 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
780wxPluralFormsCalculator* 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
807WX_DECLARE_EXPORTED_STRING_HASH_MAP(wxString, wxMessagesHash);
808
809class wxMsgCatalogFile
810{
811public:
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
827private:
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
902class wxMsgCatalog
903{
904public:
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
924private:
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
941wxMsgCatalogFile::wxMsgCatalogFile()
942{
943}
944
945wxMsgCatalogFile::~wxMsgCatalogFile()
946{
947}
948
949// open disk file and read in it's contents
950bool 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
1058bool 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
1164wxMsgCatalog::~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
1180bool 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
1197const 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
1227namespace
1228{
1229
1230wxTranslations *gs_translations = NULL;
1231bool gs_translationsOwned = false;
1232
1233} // anonymous namespace
1234
1235
1236/*static*/
1237wxTranslations *wxTranslations::Get()
1238{
1239 return gs_translations;
1240}
1241
1242/*static*/
1243void 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*/
1252void wxTranslations::SetNonOwned(wxTranslations *t)
1253{
1254 if ( gs_translationsOwned )
1255 delete gs_translations;
1256 gs_translations = t;
1257 gs_translationsOwned = false;
1258}
1259
1260
1261wxTranslations::wxTranslations()
1262{
1263 m_pMsgCat = NULL;
1264 m_loader = new wxFileTranslationsLoader;
1265}
1266
1267
1268wxTranslations::~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
1283void 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
1292void wxTranslations::SetLanguage(wxLanguage lang)
1293{
1294 if ( lang == wxLANGUAGE_DEFAULT )
1295 SetLanguage("");
1296 else
1297 SetLanguage(wxLocale::GetLanguageCanonicalName(lang));
1298}
1299
1300void wxTranslations::SetLanguage(const wxString& lang)
1301{
1302 m_lang = lang;
1303}
1304
1305
1306bool 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
1323bool wxTranslations::AddCatalog(const wxString& domain)
1324{
1325 return AddCatalog(domain, wxLANGUAGE_ENGLISH_US);
1326}
1327
1328#if !wxUSE_UNICODE
1329bool 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
1338bool 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 wxCHECK_MSG( m_loader, false, "loader can't be NULL" );
1363 return m_loader->LoadCatalog(this, domain, domain_lang);
1364}
1365
1366
1367// check if the given catalog is loaded
1368bool wxTranslations::IsLoaded(const wxString& domain) const
1369{
1370 return FindCatalog(domain) != NULL;
1371}
1372
1373
1374bool wxTranslations::LoadCatalogFile(const wxString& filename,
1375 const wxString& domain)
1376{
1377 wxMsgCatalog *pMsgCat = new wxMsgCatalog;
1378
1379#if wxUSE_UNICODE
1380 const bool ok = pMsgCat->Load(filename, domain, wxEmptyString/*unused*/);
1381#else
1382 const bool ok = pMsgCat->Load(filename, domain,
1383 m_msgIdCharset[domain]);
1384#endif
1385
1386 if ( !ok )
1387 {
1388 // don't add it because it couldn't be loaded anyway
1389 delete pMsgCat;
1390 return false;
1391 }
1392
1393 // add it to the head of the list so that in GetString it will
1394 // be searched before the catalogs added earlier
1395 pMsgCat->m_pNext = m_pMsgCat;
1396 m_pMsgCat = pMsgCat;
1397
1398 return true;
1399}
1400
1401
1402wxString wxTranslations::ChooseLanguageForDomain(const wxString& WXUNUSED(domain),
1403 const wxString& WXUNUSED(msgIdLang))
1404{
1405 // explicitly set language should always be respected
1406 if ( !m_lang.empty() )
1407 return m_lang;
1408
1409 // TODO: if the default language is used, pick the best (by comparing
1410 // available languages with user's preferences), instead of blindly
1411 // trusting availability of system language translation
1412 return wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage());
1413}
1414
1415
1416namespace
1417{
1418WX_DECLARE_HASH_SET(wxString, wxStringHash, wxStringEqual,
1419 wxLocaleUntranslatedStrings);
1420}
1421
1422/* static */
1423const wxString& wxTranslations::GetUntranslatedString(const wxString& str)
1424{
1425 static wxLocaleUntranslatedStrings s_strings;
1426
1427 wxLocaleUntranslatedStrings::iterator i = s_strings.find(str);
1428 if ( i == s_strings.end() )
1429 return *s_strings.insert(str).first;
1430
1431 return *i;
1432}
1433
1434
1435const wxString& wxTranslations::GetString(const wxString& origString,
1436 const wxString& domain) const
1437{
1438 return GetString(origString, origString, size_t(-1), domain);
1439}
1440
1441const wxString& wxTranslations::GetString(const wxString& origString,
1442 const wxString& origString2,
1443 size_t n,
1444 const wxString& domain) const
1445{
1446 if ( origString.empty() )
1447 return GetUntranslatedString(origString);
1448
1449 const wxString *trans = NULL;
1450 wxMsgCatalog *pMsgCat;
1451
1452 if ( !domain.empty() )
1453 {
1454 pMsgCat = FindCatalog(domain);
1455
1456 // does the catalog exist?
1457 if ( pMsgCat != NULL )
1458 trans = pMsgCat->GetString(origString, n);
1459 }
1460 else
1461 {
1462 // search in all domains
1463 for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext )
1464 {
1465 trans = pMsgCat->GetString(origString, n);
1466 if ( trans != NULL ) // take the first found
1467 break;
1468 }
1469 }
1470
1471 if ( trans == NULL )
1472 {
1473 wxLogTrace
1474 (
1475 TRACE_I18N,
1476 "string \"%s\"%s not found in %slocale '%s'.",
1477 origString,
1478 ((long)n) != -1 ? wxString::Format("[%ld]", (long)n) : wxString(),
1479 !domain.empty() ? wxString::Format("domain '%s' ", domain) : wxString(),
1480 m_lang
1481 );
1482
1483 if (n == size_t(-1))
1484 return GetUntranslatedString(origString);
1485 else
1486 return GetUntranslatedString(n == 1 ? origString : origString2);
1487 }
1488
1489 return *trans;
1490}
1491
1492
1493wxString wxTranslations::GetHeaderValue(const wxString& header,
1494 const wxString& domain) const
1495{
1496 if ( header.empty() )
1497 return wxEmptyString;
1498
1499 const wxString *trans = NULL;
1500 wxMsgCatalog *pMsgCat;
1501
1502 if ( !domain.empty() )
1503 {
1504 pMsgCat = FindCatalog(domain);
1505
1506 // does the catalog exist?
1507 if ( pMsgCat == NULL )
1508 return wxEmptyString;
1509
1510 trans = pMsgCat->GetString(wxEmptyString, (size_t)-1);
1511 }
1512 else
1513 {
1514 // search in all domains
1515 for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext )
1516 {
1517 trans = pMsgCat->GetString(wxEmptyString, (size_t)-1);
1518 if ( trans != NULL ) // take the first found
1519 break;
1520 }
1521 }
1522
1523 if ( !trans || trans->empty() )
1524 return wxEmptyString;
1525
1526 size_t found = trans->find(header);
1527 if ( found == wxString::npos )
1528 return wxEmptyString;
1529
1530 found += header.length() + 2 /* ': ' */;
1531
1532 // Every header is separated by \n
1533
1534 size_t endLine = trans->find(wxS('\n'), found);
1535 size_t len = (endLine == wxString::npos) ?
1536 wxString::npos : (endLine - found);
1537
1538 return trans->substr(found, len);
1539}
1540
1541
1542// find catalog by name in a linked list, return NULL if !found
1543wxMsgCatalog *wxTranslations::FindCatalog(const wxString& domain) const
1544{
1545 // linear search in the linked list
1546 wxMsgCatalog *pMsgCat;
1547 for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext )
1548 {
1549 if ( pMsgCat->GetDomain() == domain )
1550 return pMsgCat;
1551 }
1552
1553 return NULL;
1554}
1555
1556// ----------------------------------------------------------------------------
1557// wxFileTranslationsLoader
1558// ----------------------------------------------------------------------------
1559
1560namespace
1561{
1562
1563// the list of the directories to search for message catalog files
1564wxArrayString gs_searchPrefixes;
1565
1566// return the directories to search for message catalogs under the given
1567// prefix, separated by wxPATH_SEP
1568wxString GetMsgCatalogSubdirs(const wxString& prefix, const wxString& lang)
1569{
1570 // Search first in Unix-standard prefix/lang/LC_MESSAGES, then in
1571 // prefix/lang and finally in just prefix.
1572 //
1573 // Note that we use LC_MESSAGES on all platforms and not just Unix, because
1574 // it doesn't cost much to look into one more directory and doing it this
1575 // way has two important benefits:
1576 // a) we don't break compatibility with wx-2.6 and older by stopping to
1577 // look in a directory where the catalogs used to be and thus silently
1578 // breaking apps after they are recompiled against the latest wx
1579 // b) it makes it possible to package app's support files in the same
1580 // way on all target platforms
1581 const wxString pathPrefix = wxFileName(prefix, lang).GetFullPath();
1582
1583 wxString searchPath;
1584 searchPath.reserve(4*pathPrefix.length());
1585 searchPath << pathPrefix << wxFILE_SEP_PATH << "LC_MESSAGES" << wxPATH_SEP
1586 << prefix << wxFILE_SEP_PATH << wxPATH_SEP
1587 << pathPrefix;
1588
1589 return searchPath;
1590}
1591
1592// construct the search path for the given language
1593static wxString GetFullSearchPath(const wxString& lang)
1594{
1595 // first take the entries explicitly added by the program
1596 wxArrayString paths;
1597 paths.reserve(gs_searchPrefixes.size() + 1);
1598 size_t n,
1599 count = gs_searchPrefixes.size();
1600 for ( n = 0; n < count; n++ )
1601 {
1602 paths.Add(GetMsgCatalogSubdirs(gs_searchPrefixes[n], lang));
1603 }
1604
1605
1606#if wxUSE_STDPATHS
1607 // then look in the standard location
1608 const wxString stdp = wxStandardPaths::Get().
1609 GetLocalizedResourcesDir(lang, wxStandardPaths::ResourceCat_Messages);
1610
1611 if ( paths.Index(stdp) == wxNOT_FOUND )
1612 paths.Add(stdp);
1613#endif // wxUSE_STDPATHS
1614
1615 // last look in default locations
1616#ifdef __UNIX__
1617 // LC_PATH is a standard env var containing the search path for the .mo
1618 // files
1619 const char *pszLcPath = wxGetenv("LC_PATH");
1620 if ( pszLcPath )
1621 {
1622 const wxString lcp = GetMsgCatalogSubdirs(pszLcPath, lang);
1623 if ( paths.Index(lcp) == wxNOT_FOUND )
1624 paths.Add(lcp);
1625 }
1626
1627 // also add the one from where wxWin was installed:
1628 wxString wxp = wxGetInstallPrefix();
1629 if ( !wxp.empty() )
1630 {
1631 wxp = GetMsgCatalogSubdirs(wxp + wxS("/share/locale"), lang);
1632 if ( paths.Index(wxp) == wxNOT_FOUND )
1633 paths.Add(wxp);
1634 }
1635#endif // __UNIX__
1636
1637
1638 // finally construct the full search path
1639 wxString searchPath;
1640 searchPath.reserve(500);
1641 count = paths.size();
1642 for ( n = 0; n < count; n++ )
1643 {
1644 searchPath += paths[n];
1645 if ( n != count - 1 )
1646 searchPath += wxPATH_SEP;
1647 }
1648
1649 return searchPath;
1650}
1651
1652} // anonymous namespace
1653
1654
1655void wxFileTranslationsLoader::AddCatalogLookupPathPrefix(const wxString& prefix)
1656{
1657 if ( gs_searchPrefixes.Index(prefix) == wxNOT_FOUND )
1658 {
1659 gs_searchPrefixes.Add(prefix);
1660 }
1661 //else: already have it
1662}
1663
1664
1665bool wxFileTranslationsLoader::LoadCatalog(wxTranslations *translations,
1666 const wxString& domain,
1667 const wxString& lang)
1668{
1669 wxCHECK_MSG( lang.length() >= LEN_LANG, false,
1670 "invalid language specification" );
1671
1672 wxString searchPath;
1673
1674#if wxUSE_FONTMAP
1675 // first look for the catalog for this language and the current locale:
1676 // notice that we don't use the system name for the locale as this would
1677 // force us to install catalogs in different locations depending on the
1678 // system but always use the canonical name
1679 wxFontEncoding encSys = wxLocale::GetSystemEncoding();
1680 if ( encSys != wxFONTENCODING_SYSTEM )
1681 {
1682 wxString fullname(lang);
1683 fullname << wxS('.') << wxFontMapperBase::GetEncodingName(encSys);
1684 searchPath << GetFullSearchPath(fullname) << wxPATH_SEP;
1685 }
1686#endif // wxUSE_FONTMAP
1687
1688 searchPath += GetFullSearchPath(lang);
1689 if ( lang.length() > LEN_LANG && lang[LEN_LANG] == wxS('_') )
1690 {
1691 // also add just base locale name: for things like "fr_BE" (Belgium
1692 // French) we should use fall back on plain "fr" if no Belgium-specific
1693 // message catalogs exist
1694 searchPath << wxPATH_SEP
1695 << GetFullSearchPath(ExtractLang(lang));
1696 }
1697
1698 wxLogTrace(TRACE_I18N, wxS("Looking for \"%s.mo\" in search path \"%s\""),
1699 domain, searchPath);
1700
1701 wxFileName fn(domain);
1702 fn.SetExt(wxS("mo"));
1703
1704 wxString strFullName;
1705 if ( !wxFindFileInPath(&strFullName, searchPath, fn.GetFullPath()) )
1706 {
1707 wxLogVerbose(_("catalog file for domain '%s' not found."), domain);
1708 wxLogTrace(TRACE_I18N, wxS("Catalog \"%s.mo\" not found"), domain);
1709 return false;
1710 }
1711
1712 // open file and read its data
1713 wxLogVerbose(_("using catalog '%s' from '%s'."), domain, strFullName.c_str());
1714 wxLogTrace(TRACE_I18N, wxS("Using catalog \"%s\"."), strFullName.c_str());
1715
1716 return translations->LoadCatalogFile(strFullName, domain);
1717}
1718
1719// ----------------------------------------------------------------------------
1720// wxTranslationsModule module (for destruction of gs_translations)
1721// ----------------------------------------------------------------------------
1722
1723class wxTranslationsModule: public wxModule
1724{
1725 DECLARE_DYNAMIC_CLASS(wxTranslationsModule)
1726 public:
1727 wxTranslationsModule() {}
1728
1729 bool OnInit()
1730 {
1731 return true;
1732 }
1733
1734 void OnExit()
1735 {
1736 if ( gs_translationsOwned )
1737 delete gs_translations;
1738 gs_translations = NULL;
1739 gs_translationsOwned = true;
1740 }
1741};
1742
1743IMPLEMENT_DYNAMIC_CLASS(wxTranslationsModule, wxModule)
1744
1745#endif // wxUSE_INTL