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