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