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