]> git.saurik.com Git - wxWidgets.git/blame - src/common/translation.cpp
as we always have CoreText available under 10.5+, we can properly determine fixed...
[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
5e306229
VS
44#include "wx/arrstr.h"
45#include "wx/dir.h"
ea144923
VS
46#include "wx/file.h"
47#include "wx/filename.h"
48#include "wx/tokenzr.h"
49#include "wx/fontmap.h"
ea144923
VS
50#include "wx/stdpaths.h"
51#include "wx/hashset.h"
52
d98a58c5 53#ifdef __WINDOWS__
5e306229
VS
54 #include "wx/msw/wrapwin.h"
55#endif
56
ea144923
VS
57// ----------------------------------------------------------------------------
58// simple types
59// ----------------------------------------------------------------------------
60
61typedef wxUint32 size_t32;
62
63// ----------------------------------------------------------------------------
64// constants
65// ----------------------------------------------------------------------------
66
67// magic number identifying the .mo format file
68const size_t32 MSGCATALOG_MAGIC = 0x950412de;
69const size_t32 MSGCATALOG_MAGIC_SW = 0xde120495;
70
71#define TRACE_I18N wxS("i18n")
72
ea144923
VS
73// ============================================================================
74// implementation
75// ============================================================================
76
611bed35
VS
77namespace
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.
84wxStringToStringHashMap gs_msgIdCharset;
85#endif
86
87} // anonymous namespace
88
ea144923
VS
89// ----------------------------------------------------------------------------
90// Plural forms parser
91// ----------------------------------------------------------------------------
92
93/*
94 Simplified Grammar
95
96Expression:
97 LogicalOrExpression '?' Expression ':' Expression
98 LogicalOrExpression
99
100LogicalOrExpression:
101 LogicalAndExpression "||" LogicalOrExpression // to (a || b) || c
102 LogicalAndExpression
103
104LogicalAndExpression:
105 EqualityExpression "&&" LogicalAndExpression // to (a && b) && c
106 EqualityExpression
107
108EqualityExpression:
109 RelationalExpression "==" RelationalExperession
110 RelationalExpression "!=" RelationalExperession
111 RelationalExpression
112
113RelationalExpression:
114 MultiplicativeExpression '>' MultiplicativeExpression
115 MultiplicativeExpression '<' MultiplicativeExpression
116 MultiplicativeExpression ">=" MultiplicativeExpression
117 MultiplicativeExpression "<=" MultiplicativeExpression
118 MultiplicativeExpression
119
120MultiplicativeExpression:
121 PmExpression '%' PmExpression
122 PmExpression
123
124PmExpression:
125 N
126 Number
127 '(' Expression ')'
128*/
129
130class wxPluralFormsToken
131{
132public:
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; }
147private:
148 Type m_type;
149 Number m_number;
150};
151
152
153class wxPluralFormsScanner
154{
155public:
156 wxPluralFormsScanner(const char* s);
157 const wxPluralFormsToken& token() const { return m_token; }
158 bool nextToken(); // returns false if error
159private:
160 const char* m_s;
161 wxPluralFormsToken m_token;
162};
163
164wxPluralFormsScanner::wxPluralFormsScanner(const char* s) : m_s(s)
165{
166 nextToken();
167}
168
169bool 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
297class wxPluralFormsNode;
298
299// NB: Can't use wxDEFINE_SCOPED_PTR_TYPE because wxPluralFormsNode is not
300// fully defined yet:
301class wxPluralFormsNodePtr
302{
303public:
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
312private:
313 wxPluralFormsNode *m_p;
314};
315
316class wxPluralFormsNode
317{
318public:
319 wxPluralFormsNode(const wxPluralFormsToken& token) : m_token(token) {}
320 const wxPluralFormsToken& token() const { return m_token; }
dfbb5eff 321 const wxPluralFormsNode* node(unsigned i) const
ea144923 322 { return m_nodes[i].get(); }
dfbb5eff
VS
323 void setNode(unsigned i, wxPluralFormsNode* n);
324 wxPluralFormsNode* releaseNode(unsigned i);
ea144923
VS
325 wxPluralFormsToken::Number evaluate(wxPluralFormsToken::Number n) const;
326
327private:
328 wxPluralFormsToken m_token;
329 wxPluralFormsNodePtr m_nodes[3];
330};
331
332wxPluralFormsNodePtr::~wxPluralFormsNodePtr()
333{
334 delete m_p;
335}
336wxPluralFormsNode* wxPluralFormsNodePtr::release()
337{
338 wxPluralFormsNode *p = m_p;
339 m_p = NULL;
340 return p;
341}
342void wxPluralFormsNodePtr::reset(wxPluralFormsNode *p)
343{
344 if (p != m_p)
345 {
346 delete m_p;
347 m_p = p;
348 }
349}
350
351
dfbb5eff 352void wxPluralFormsNode::setNode(unsigned i, wxPluralFormsNode* n)
ea144923
VS
353{
354 m_nodes[i].reset(n);
355}
356
dfbb5eff 357wxPluralFormsNode* wxPluralFormsNode::releaseNode(unsigned i)
ea144923
VS
358{
359 return m_nodes[i].release();
360}
361
362wxPluralFormsToken::Number
363wxPluralFormsNode::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
412class wxPluralFormsCalculator
413{
414public:
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
429private:
430 wxPluralFormsToken::Number m_nplurals;
431 wxPluralFormsNodePtr m_plural;
432};
433
611bed35 434wxDEFINE_SCOPED_PTR(wxPluralFormsCalculator, wxPluralFormsCalculatorPtr)
ea144923
VS
435
436void wxPluralFormsCalculator::init(wxPluralFormsToken::Number nplurals,
437 wxPluralFormsNode* plural)
438{
439 m_nplurals = nplurals;
440 m_plural.reset(plural);
441}
442
443int 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
458class wxPluralFormsParser
459{
460public:
461 wxPluralFormsParser(wxPluralFormsScanner& scanner) : m_scanner(scanner) {}
462 bool parse(wxPluralFormsCalculator& rCalculator);
463
464private:
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
480bool 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
520wxPluralFormsNode* 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
535const wxPluralFormsToken& wxPluralFormsParser::token() const
536{
537 return m_scanner.token();
538}
539
540bool wxPluralFormsParser::nextToken()
541{
542 if (!m_scanner.nextToken())
543 return false;
544 return true;
545}
546
547wxPluralFormsNode* 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
586wxPluralFormsNode*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
622wxPluralFormsNode* 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
660wxPluralFormsNode* 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
686wxPluralFormsNode* 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
714wxPluralFormsNode* 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
739wxPluralFormsNode* 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
778wxPluralFormsCalculator* 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
ea144923
VS
805class wxMsgCatalogFile
806{
807public:
611bed35 808 typedef wxScopedCharBuffer DataBuffer;
bc71c3cd 809
ea144923
VS
810 // ctor & dtor
811 wxMsgCatalogFile();
812 ~wxMsgCatalogFile();
813
814 // load the catalog from disk
bc71c3cd
VS
815 bool LoadFile(const wxString& filename,
816 wxPluralFormsCalculatorPtr& rPluralFormsCalculator);
817 bool LoadData(const DataBuffer& data,
818 wxPluralFormsCalculatorPtr& rPluralFormsCalculator);
ea144923
VS
819
820 // fills the hash with string-translation pairs
611bed35 821 bool FillHash(wxStringToStringHashMap& hash, const wxString& domain) const;
ea144923
VS
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
bc71c3cd 850 DataBuffer m_data;
ea144923
VS
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
ea144923
VS
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);
bc71c3cd 874 if ( ofsString + Swap(ent->nLen) > m_data.length())
ea144923
VS
875 {
876 return NULL;
877 }
878
bc71c3cd 879 return m_data.data() + ofsString;
ea144923
VS
880 }
881
882 bool m_bSwapped; // wrong endianness?
883
884 wxDECLARE_NO_COPY_CLASS(wxMsgCatalogFile);
885};
886
ea144923 887// ----------------------------------------------------------------------------
37424888 888// wxMsgCatalogFile class
ea144923
VS
889// ----------------------------------------------------------------------------
890
891wxMsgCatalogFile::wxMsgCatalogFile()
892{
893}
894
895wxMsgCatalogFile::~wxMsgCatalogFile()
896{
897}
898
899// open disk file and read in it's contents
bc71c3cd
VS
900bool wxMsgCatalogFile::LoadFile(const wxString& filename,
901 wxPluralFormsCalculatorPtr& rPluralFormsCalculator)
ea144923
VS
902{
903 wxFile fileMsg(filename);
904 if ( !fileMsg.IsOpened() )
905 return false;
906
4c51a665 907 // get the file size (assume it is less than 4GB...)
ea144923
VS
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
bc71c3cd
VS
915 wxMemoryBuffer filedata;
916
ea144923 917 // read the whole file in memory
bc71c3cd 918 if ( fileMsg.Read(filedata.GetWriteBuf(nSize), nSize) != lenFile )
ea144923
VS
919 return false;
920
bc71c3cd
VS
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}
ea144923
VS
936
937
bc71c3cd
VS
938bool wxMsgCatalogFile::LoadData(const DataBuffer& data,
939 wxPluralFormsCalculatorPtr& rPluralFormsCalculator)
940{
ea144923 941 // examine header
bc71c3cd 942 bool bValid = data.length() > sizeof(wxMsgCatalogHeader);
ea144923 943
bc71c3cd 944 const wxMsgCatalogHeader *pHeader = (wxMsgCatalogHeader *)data.data();
ea144923
VS
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
bc71c3cd 955 wxLogWarning(_("Invalid message catalog."));
ea144923
VS
956 return false;
957 }
958
bc71c3cd
VS
959 m_data = data;
960
ea144923
VS
961 // initialize
962 m_numStrings = Swap(pHeader->numStrings);
bc71c3cd 963 m_pOrigTable = (wxMsgTableEntry *)(data.data() +
ea144923 964 Swap(pHeader->ofsOrigTable));
bc71c3cd 965 m_pTransTable = (wxMsgTableEntry *)(data.data() +
ea144923
VS
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
59859d3d 989 m_charset.clear();
ea144923
VS
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
611bed35
VS
1028bool wxMsgCatalogFile::FillHash(wxStringToStringHashMap& hash,
1029 const wxString& domain) const
ea144923 1030{
611bed35 1031 wxUnusedVar(domain); // silence warning in Unicode build
ea144923
VS
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
611bed35
VS
1059 wxString msgIdCharset = gs_msgIdCharset[domain];
1060
ea144923
VS
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
1136wxMsgCatalog::~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
611bed35
VS
1152/* static */
1153wxMsgCatalog *wxMsgCatalog::CreateFromFile(const wxString& filename,
1154 const wxString& domain)
ea144923 1155{
611bed35 1156 wxScopedPtr<wxMsgCatalog> cat(new wxMsgCatalog(domain));
ea144923 1157
611bed35 1158 wxMsgCatalogFile file;
ea144923 1159
611bed35
VS
1160 if ( !file.LoadFile(filename, cat->m_pluralFormsCalculator) )
1161 return NULL;
bc71c3cd 1162
611bed35
VS
1163 if ( !file.FillHash(cat->m_messages, domain) )
1164 return NULL;
bc71c3cd 1165
611bed35 1166 return cat.release();
bc71c3cd
VS
1167}
1168
611bed35
VS
1169/* static */
1170wxMsgCatalog *wxMsgCatalog::CreateFromData(const wxScopedCharBuffer& data,
1171 const wxString& domain)
bc71c3cd 1172{
611bed35 1173 wxScopedPtr<wxMsgCatalog> cat(new wxMsgCatalog(domain));
bc71c3cd 1174
611bed35 1175 wxMsgCatalogFile file;
bc71c3cd 1176
611bed35
VS
1177 if ( !file.LoadData(data, cat->m_pluralFormsCalculator) )
1178 return NULL;
ea144923 1179
611bed35
VS
1180 if ( !file.FillHash(cat->m_messages, domain) )
1181 return NULL;
ea144923 1182
611bed35 1183 return cat.release();
ea144923
VS
1184}
1185
dfbb5eff 1186const wxString *wxMsgCatalog::GetString(const wxString& str, unsigned n) const
ea144923
VS
1187{
1188 int index = 0;
dfbb5eff 1189 if (n != UINT_MAX)
ea144923
VS
1190 {
1191 index = m_pluralFormsCalculator->evaluate(n);
1192 }
611bed35 1193 wxStringToStringHashMap::const_iterator i;
ea144923
VS
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
1216namespace
1217{
1218
1219wxTranslations *gs_translations = NULL;
1220bool gs_translationsOwned = false;
1221
1222} // anonymous namespace
1223
1224
1225/*static*/
1226wxTranslations *wxTranslations::Get()
1227{
1228 return gs_translations;
1229}
1230
1231/*static*/
1232void 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*/
1241void wxTranslations::SetNonOwned(wxTranslations *t)
1242{
1243 if ( gs_translationsOwned )
1244 delete gs_translations;
1245 gs_translations = t;
1246 gs_translationsOwned = false;
1247}
1248
1249
1250wxTranslations::wxTranslations()
1251{
1252 m_pMsgCat = NULL;
1253 m_loader = new wxFileTranslationsLoader;
1254}
1255
1256
1257wxTranslations::~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
1272void 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
1281void wxTranslations::SetLanguage(wxLanguage lang)
1282{
1283 if ( lang == wxLANGUAGE_DEFAULT )
1284 SetLanguage("");
1285 else
1286 SetLanguage(wxLocale::GetLanguageCanonicalName(lang));
1287}
1288
1289void wxTranslations::SetLanguage(const wxString& lang)
1290{
1291 m_lang = lang;
1292}
1293
1294
5e306229
VS
1295wxArrayString wxTranslations::GetAvailableTranslations(const wxString& domain) const
1296{
4c83615f 1297 wxCHECK_MSG( m_loader, wxArrayString(), "loader can't be NULL" );
5e306229
VS
1298
1299 return m_loader->GetAvailableTranslations(domain);
1300}
1301
1302
ea144923
VS
1303bool 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
1320bool wxTranslations::AddCatalog(const wxString& domain)
1321{
1322 return AddCatalog(domain, wxLANGUAGE_ENGLISH_US);
1323}
1324
1325#if !wxUSE_UNICODE
1326bool wxTranslations::AddCatalog(const wxString& domain,
1327 wxLanguage msgIdLanguage,
1328 const wxString& msgIdCharset)
1329{
611bed35 1330 gs_msgIdCharset[domain] = msgIdCharset;
ea144923
VS
1331 return AddCatalog(domain, msgIdLanguage);
1332}
1333#endif // !wxUSE_UNICODE
1334
1335bool 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
076c0a8e
VS
1359 return LoadCatalog(domain, domain_lang);
1360}
1361
1362
1363bool wxTranslations::LoadCatalog(const wxString& domain, const wxString& lang)
1364{
5e306229 1365 m_loader->GetAvailableTranslations(domain);
ea144923 1366 wxCHECK_MSG( m_loader, false, "loader can't be NULL" );
076c0a8e 1367
611bed35
VS
1368 wxMsgCatalog *cat = NULL;
1369
076c0a8e
VS
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
611bed35 1381 cat = m_loader->LoadCatalog(domain, fullname);
076c0a8e
VS
1382 }
1383#endif // wxUSE_FONTMAP
1384
611bed35 1385 if ( !cat )
076c0a8e 1386 {
611bed35
VS
1387 // Next try: use the provided name language name:
1388 cat = m_loader->LoadCatalog(domain, lang);
076c0a8e
VS
1389 }
1390
611bed35 1391 if ( !cat )
bc71c3cd 1392 {
611bed35
VS
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);
bc71c3cd
VS
1399 }
1400
611bed35
VS
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;
ea144923 1407
611bed35
VS
1408 return true;
1409 }
1410 else
ea144923 1411 {
611bed35
VS
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);
ea144923
VS
1416 return false;
1417 }
611bed35 1418}
ea144923 1419
611bed35
VS
1420// check if the given catalog is loaded
1421bool wxTranslations::IsLoaded(const wxString& domain) const
1422{
1423 return FindCatalog(domain) != NULL;
ea144923
VS
1424}
1425
1426
1427wxString 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
1441namespace
1442{
8d9eee83 1443WX_DECLARE_HASH_SET(wxString, ::wxStringHash, ::wxStringEqual,
ea144923
VS
1444 wxLocaleUntranslatedStrings);
1445}
1446
1447/* static */
1448const 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
1460const wxString& wxTranslations::GetString(const wxString& origString,
1461 const wxString& domain) const
1462{
dfbb5eff 1463 return GetString(origString, origString, UINT_MAX, domain);
ea144923
VS
1464}
1465
1466const wxString& wxTranslations::GetString(const wxString& origString,
1467 const wxString& origString2,
dfbb5eff 1468 unsigned n,
ea144923
VS
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,
25859335
VZ
1503 (n != UINT_MAX ? wxString::Format("[%ld]", (long)n) : wxString()),
1504 (!domain.empty() ? wxString::Format("domain '%s' ", domain) : wxString()),
ea144923
VS
1505 m_lang
1506 );
1507
dfbb5eff 1508 if (n == UINT_MAX)
ea144923
VS
1509 return GetUntranslatedString(origString);
1510 else
1511 return GetUntranslatedString(n == 1 ? origString : origString2);
1512 }
1513
1514 return *trans;
1515}
1516
1517
1518wxString 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
dfbb5eff 1535 trans = pMsgCat->GetString(wxEmptyString, UINT_MAX);
ea144923
VS
1536 }
1537 else
1538 {
1539 // search in all domains
1540 for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext )
1541 {
dfbb5eff 1542 trans = pMsgCat->GetString(wxEmptyString, UINT_MAX);
ea144923
VS
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
1568wxMsgCatalog *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
1585namespace
1586{
1587
1588// the list of the directories to search for message catalog files
1589wxArrayString gs_searchPrefixes;
1590
1591// return the directories to search for message catalogs under the given
1592// prefix, separated by wxPATH_SEP
1593wxString 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
5e306229
VS
1617bool 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
1625wxArrayString GetSearchPrefixes(const wxString& lang = wxString())
ea144923 1626{
ea144923 1627 wxArrayString paths;
ea144923 1628
5e306229
VS
1629 // first take the entries explicitly added by the program
1630 paths = gs_searchPrefixes;
ea144923
VS
1631
1632#if wxUSE_STDPATHS
1633 // then look in the standard location
5e306229
VS
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 }
ea144923
VS
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 {
5e306229 1655 const wxString lcp = pszLcPath;
ea144923
VS
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 {
5e306229 1664 wxp += wxS("/share/locale");
ea144923
VS
1665 if ( paths.Index(wxp) == wxNOT_FOUND )
1666 paths.Add(wxp);
1667 }
1668#endif // __UNIX__
1669
5e306229
VS
1670 return paths;
1671}
ea144923 1672
5e306229
VS
1673// construct the search path for the given language
1674wxString GetFullSearchPath(const wxString& lang)
1675{
ea144923
VS
1676 wxString searchPath;
1677 searchPath.reserve(500);
5e306229
VS
1678
1679 const wxArrayString prefixes = GetSearchPrefixes(lang);
1680
1681 for ( wxArrayString::const_iterator i = prefixes.begin();
1682 i != prefixes.end();
1683 ++i )
ea144923 1684 {
5e306229
VS
1685 const wxString p = GetMsgCatalogSubdirs(*i, lang);
1686
1687 if ( !searchPath.empty() )
ea144923 1688 searchPath += wxPATH_SEP;
5e306229 1689 searchPath += p;
ea144923
VS
1690 }
1691
1692 return searchPath;
1693}
1694
1695} // anonymous namespace
1696
1697
1698void 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
611bed35
VS
1708wxMsgCatalog *wxFileTranslationsLoader::LoadCatalog(const wxString& domain,
1709 const wxString& lang)
ea144923 1710{
076c0a8e 1711 wxString searchPath = GetFullSearchPath(lang);
ea144923
VS
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()) )
611bed35 1721 return NULL;
ea144923
VS
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
611bed35 1727 return wxMsgCatalog::CreateFromFile(strFullName, domain);
ea144923
VS
1728}
1729
bc71c3cd 1730
5e306229
VS
1731wxArrayString 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 {
6636ef8d 1744 if ( i->empty() )
1662942c 1745 continue;
5e306229
VS
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
bc71c3cd
VS
1775// ----------------------------------------------------------------------------
1776// wxResourceTranslationsLoader
1777// ----------------------------------------------------------------------------
1778
1779#ifdef __WINDOWS__
5e306229 1780
611bed35
VS
1781wxMsgCatalog *wxResourceTranslationsLoader::LoadCatalog(const wxString& domain,
1782 const wxString& lang)
bc71c3cd 1783{
bc71c3cd
VS
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()) )
12f6edff 1793 return NULL;
bc71c3cd
VS
1794
1795 wxLogTrace(TRACE_I18N,
1796 "Using catalog from Windows resource \"%s\".", resname);
1797
611bed35
VS
1798 wxMsgCatalog *cat = wxMsgCatalog::CreateFromData(
1799 wxCharBuffer::CreateNonOwned(static_cast<const char*>(mo_data), mo_size),
1800 domain);
1801
1802 if ( !cat )
4cfee1ef 1803 {
bc71c3cd 1804 wxLogWarning(_("Resource '%s' is not a valid message catalog."), resname);
4cfee1ef 1805 }
bc71c3cd 1806
611bed35 1807 return cat;
bc71c3cd 1808}
5e306229
VS
1809
1810namespace
1811{
1812
1813struct EnumCallbackData
1814{
1815 wxString prefix;
1816 wxArrayString langs;
1817};
1818
1819BOOL 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
1839wxArrayString 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(),
a49b09f5 1846 GetResourceType().t_str(),
5e306229
VS
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 )
4cfee1ef 1852 {
5e306229 1853 wxLogSysError(_("Couldn't enumerate translations"));
4cfee1ef 1854 }
5e306229
VS
1855 }
1856
1857 return data.langs;
1858}
1859
bc71c3cd
VS
1860#endif // __WINDOWS__
1861
1862
ea144923
VS
1863// ----------------------------------------------------------------------------
1864// wxTranslationsModule module (for destruction of gs_translations)
1865// ----------------------------------------------------------------------------
1866
1867class 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
1887IMPLEMENT_DYNAMIC_CLASS(wxTranslationsModule, wxModule)
1888
1889#endif // wxUSE_INTL