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