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