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