]> git.saurik.com Git - wxWidgets.git/blob - src/common/translation.cpp
Allow to specify the title used by wxPreferencesEditor window.
[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) != 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) != 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 m_loader->GetAvailableTranslations(domain);
1516 wxCHECK_MSG( m_loader, false, "loader can't be NULL" );
1517
1518 wxMsgCatalog *cat = NULL;
1519
1520 #if wxUSE_FONTMAP
1521 // first look for the catalog for this language and the current locale:
1522 // notice that we don't use the system name for the locale as this would
1523 // force us to install catalogs in different locations depending on the
1524 // system but always use the canonical name
1525 wxFontEncoding encSys = wxLocale::GetSystemEncoding();
1526 if ( encSys != wxFONTENCODING_SYSTEM )
1527 {
1528 wxString fullname(lang);
1529 fullname << wxS('.') << wxFontMapperBase::GetEncodingName(encSys);
1530
1531 cat = m_loader->LoadCatalog(domain, fullname);
1532 }
1533 #endif // wxUSE_FONTMAP
1534
1535 if ( !cat )
1536 {
1537 // Next try: use the provided name language name:
1538 cat = m_loader->LoadCatalog(domain, lang);
1539 }
1540
1541 if ( !cat )
1542 {
1543 // Also try just base locale name: for things like "fr_BE" (Belgium
1544 // French) we should use fall back on plain "fr" if no Belgium-specific
1545 // message catalogs exist
1546 wxString baselang = lang.BeforeFirst('_');
1547 if ( lang != baselang )
1548 cat = m_loader->LoadCatalog(domain, baselang);
1549 }
1550
1551 if ( cat )
1552 {
1553 // add it to the head of the list so that in GetString it will
1554 // be searched before the catalogs added earlier
1555 cat->m_pNext = m_pMsgCat;
1556 m_pMsgCat = cat;
1557
1558 return true;
1559 }
1560 else
1561 {
1562 // Nothing worked, the catalog just isn't there
1563 wxLogTrace(TRACE_I18N,
1564 "Catalog \"%s.mo\" not found for language \"%s\".",
1565 domain, lang);
1566 return false;
1567 }
1568 }
1569
1570 // check if the given catalog is loaded
1571 bool wxTranslations::IsLoaded(const wxString& domain) const
1572 {
1573 return FindCatalog(domain) != NULL;
1574 }
1575
1576 wxString wxTranslations::GetBestTranslation(const wxString& domain,
1577 wxLanguage msgIdLanguage)
1578 {
1579 const wxString lang = wxLocale::GetLanguageCanonicalName(msgIdLanguage);
1580 return GetBestTranslation(domain, lang);
1581 }
1582
1583 wxString wxTranslations::GetBestTranslation(const wxString& domain,
1584 const wxString& msgIdLanguage)
1585 {
1586 // explicitly set language should always be respected
1587 if ( !m_lang.empty() )
1588 return m_lang;
1589
1590 wxArrayString available(GetAvailableTranslations(domain));
1591 // it's OK to have duplicates, so just add msgid language
1592 available.push_back(msgIdLanguage);
1593 available.push_back(msgIdLanguage.BeforeFirst('_'));
1594
1595 wxLogTrace(TRACE_I18N, "choosing best language for domain '%s'", domain);
1596 LogTraceArray(" - available translations", available);
1597 const wxString lang = GetPreferredUILanguage(available);
1598 wxLogTrace(TRACE_I18N, " => using language '%s'", lang);
1599 return lang;
1600 }
1601
1602
1603 namespace
1604 {
1605 WX_DECLARE_HASH_SET(wxString, wxStringHash, wxStringEqual,
1606 wxLocaleUntranslatedStrings);
1607 }
1608
1609 /* static */
1610 const wxString& wxTranslations::GetUntranslatedString(const wxString& str)
1611 {
1612 static wxLocaleUntranslatedStrings s_strings;
1613
1614 wxLocaleUntranslatedStrings::iterator i = s_strings.find(str);
1615 if ( i == s_strings.end() )
1616 return *s_strings.insert(str).first;
1617
1618 return *i;
1619 }
1620
1621
1622 const wxString& wxTranslations::GetString(const wxString& origString,
1623 const wxString& domain) const
1624 {
1625 return GetString(origString, origString, UINT_MAX, domain);
1626 }
1627
1628 const wxString& wxTranslations::GetString(const wxString& origString,
1629 const wxString& origString2,
1630 unsigned n,
1631 const wxString& domain) const
1632 {
1633 if ( origString.empty() )
1634 return GetUntranslatedString(origString);
1635
1636 const wxString *trans = NULL;
1637 wxMsgCatalog *pMsgCat;
1638
1639 if ( !domain.empty() )
1640 {
1641 pMsgCat = FindCatalog(domain);
1642
1643 // does the catalog exist?
1644 if ( pMsgCat != NULL )
1645 trans = pMsgCat->GetString(origString, n);
1646 }
1647 else
1648 {
1649 // search in all domains
1650 for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext )
1651 {
1652 trans = pMsgCat->GetString(origString, n);
1653 if ( trans != NULL ) // take the first found
1654 break;
1655 }
1656 }
1657
1658 if ( trans == NULL )
1659 {
1660 wxLogTrace
1661 (
1662 TRACE_I18N,
1663 "string \"%s\"%s not found in %slocale '%s'.",
1664 origString,
1665 (n != UINT_MAX ? wxString::Format("[%ld]", (long)n) : wxString()),
1666 (!domain.empty() ? wxString::Format("domain '%s' ", domain) : wxString()),
1667 m_lang
1668 );
1669
1670 if (n == UINT_MAX)
1671 return GetUntranslatedString(origString);
1672 else
1673 return GetUntranslatedString(n == 1 ? origString : origString2);
1674 }
1675
1676 return *trans;
1677 }
1678
1679
1680 wxString wxTranslations::GetHeaderValue(const wxString& header,
1681 const wxString& domain) const
1682 {
1683 if ( header.empty() )
1684 return wxEmptyString;
1685
1686 const wxString *trans = NULL;
1687 wxMsgCatalog *pMsgCat;
1688
1689 if ( !domain.empty() )
1690 {
1691 pMsgCat = FindCatalog(domain);
1692
1693 // does the catalog exist?
1694 if ( pMsgCat == NULL )
1695 return wxEmptyString;
1696
1697 trans = pMsgCat->GetString(wxEmptyString, UINT_MAX);
1698 }
1699 else
1700 {
1701 // search in all domains
1702 for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext )
1703 {
1704 trans = pMsgCat->GetString(wxEmptyString, UINT_MAX);
1705 if ( trans != NULL ) // take the first found
1706 break;
1707 }
1708 }
1709
1710 if ( !trans || trans->empty() )
1711 return wxEmptyString;
1712
1713 size_t found = trans->find(header);
1714 if ( found == wxString::npos )
1715 return wxEmptyString;
1716
1717 found += header.length() + 2 /* ': ' */;
1718
1719 // Every header is separated by \n
1720
1721 size_t endLine = trans->find(wxS('\n'), found);
1722 size_t len = (endLine == wxString::npos) ?
1723 wxString::npos : (endLine - found);
1724
1725 return trans->substr(found, len);
1726 }
1727
1728
1729 // find catalog by name in a linked list, return NULL if !found
1730 wxMsgCatalog *wxTranslations::FindCatalog(const wxString& domain) const
1731 {
1732 // linear search in the linked list
1733 wxMsgCatalog *pMsgCat;
1734 for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext )
1735 {
1736 if ( pMsgCat->GetDomain() == domain )
1737 return pMsgCat;
1738 }
1739
1740 return NULL;
1741 }
1742
1743 // ----------------------------------------------------------------------------
1744 // wxFileTranslationsLoader
1745 // ----------------------------------------------------------------------------
1746
1747 namespace
1748 {
1749
1750 // the list of the directories to search for message catalog files
1751 wxArrayString gs_searchPrefixes;
1752
1753 // return the directories to search for message catalogs under the given
1754 // prefix, separated by wxPATH_SEP
1755 wxString GetMsgCatalogSubdirs(const wxString& prefix, const wxString& lang)
1756 {
1757 // Search first in Unix-standard prefix/lang/LC_MESSAGES, then in
1758 // prefix/lang and finally in just prefix.
1759 //
1760 // Note that we use LC_MESSAGES on all platforms and not just Unix, because
1761 // it doesn't cost much to look into one more directory and doing it this
1762 // way has two important benefits:
1763 // a) we don't break compatibility with wx-2.6 and older by stopping to
1764 // look in a directory where the catalogs used to be and thus silently
1765 // breaking apps after they are recompiled against the latest wx
1766 // b) it makes it possible to package app's support files in the same
1767 // way on all target platforms
1768 const wxString pathPrefix = wxFileName(prefix, lang).GetFullPath();
1769
1770 wxString searchPath;
1771 searchPath.reserve(4*pathPrefix.length());
1772 searchPath << pathPrefix << wxFILE_SEP_PATH << "LC_MESSAGES" << wxPATH_SEP
1773 << prefix << wxFILE_SEP_PATH << wxPATH_SEP
1774 << pathPrefix;
1775
1776 return searchPath;
1777 }
1778
1779 bool HasMsgCatalogInDir(const wxString& dir, const wxString& domain)
1780 {
1781 return wxFileName(dir, domain, "mo").FileExists() ||
1782 wxFileName(dir + wxFILE_SEP_PATH + "LC_MESSAGES", domain, "mo").FileExists();
1783 }
1784
1785 // get prefixes to locale directories; if lang is empty, don't point to
1786 // OSX's .lproj bundles
1787 wxArrayString GetSearchPrefixes(const wxString& lang = wxString())
1788 {
1789 wxArrayString paths;
1790
1791 // first take the entries explicitly added by the program
1792 paths = gs_searchPrefixes;
1793
1794 #if wxUSE_STDPATHS
1795 // then look in the standard location
1796 wxString stdp;
1797 if ( lang.empty() )
1798 {
1799 stdp = wxStandardPaths::Get().GetResourcesDir();
1800 }
1801 else
1802 {
1803 stdp = wxStandardPaths::Get().
1804 GetLocalizedResourcesDir(lang, wxStandardPaths::ResourceCat_Messages);
1805 }
1806 if ( paths.Index(stdp) == wxNOT_FOUND )
1807 paths.Add(stdp);
1808 #endif // wxUSE_STDPATHS
1809
1810 // last look in default locations
1811 #ifdef __UNIX__
1812 // LC_PATH is a standard env var containing the search path for the .mo
1813 // files
1814 const char *pszLcPath = wxGetenv("LC_PATH");
1815 if ( pszLcPath )
1816 {
1817 const wxString lcp = pszLcPath;
1818 if ( paths.Index(lcp) == wxNOT_FOUND )
1819 paths.Add(lcp);
1820 }
1821
1822 // also add the one from where wxWin was installed:
1823 wxString wxp = wxGetInstallPrefix();
1824 if ( !wxp.empty() )
1825 {
1826 wxp += wxS("/share/locale");
1827 if ( paths.Index(wxp) == wxNOT_FOUND )
1828 paths.Add(wxp);
1829 }
1830 #endif // __UNIX__
1831
1832 return paths;
1833 }
1834
1835 // construct the search path for the given language
1836 wxString GetFullSearchPath(const wxString& lang)
1837 {
1838 wxString searchPath;
1839 searchPath.reserve(500);
1840
1841 const wxArrayString prefixes = GetSearchPrefixes(lang);
1842
1843 for ( wxArrayString::const_iterator i = prefixes.begin();
1844 i != prefixes.end();
1845 ++i )
1846 {
1847 const wxString p = GetMsgCatalogSubdirs(*i, lang);
1848
1849 if ( !searchPath.empty() )
1850 searchPath += wxPATH_SEP;
1851 searchPath += p;
1852 }
1853
1854 return searchPath;
1855 }
1856
1857 } // anonymous namespace
1858
1859
1860 void wxFileTranslationsLoader::AddCatalogLookupPathPrefix(const wxString& prefix)
1861 {
1862 if ( gs_searchPrefixes.Index(prefix) == wxNOT_FOUND )
1863 {
1864 gs_searchPrefixes.Add(prefix);
1865 }
1866 //else: already have it
1867 }
1868
1869
1870 wxMsgCatalog *wxFileTranslationsLoader::LoadCatalog(const wxString& domain,
1871 const wxString& lang)
1872 {
1873 wxString searchPath = GetFullSearchPath(lang);
1874
1875 wxLogTrace(TRACE_I18N, wxS("Looking for \"%s.mo\" in search path \"%s\""),
1876 domain, searchPath);
1877
1878 wxFileName fn(domain);
1879 fn.SetExt(wxS("mo"));
1880
1881 wxString strFullName;
1882 if ( !wxFindFileInPath(&strFullName, searchPath, fn.GetFullPath()) )
1883 return NULL;
1884
1885 // open file and read its data
1886 wxLogVerbose(_("using catalog '%s' from '%s'."), domain, strFullName.c_str());
1887 wxLogTrace(TRACE_I18N, wxS("Using catalog \"%s\"."), strFullName.c_str());
1888
1889 return wxMsgCatalog::CreateFromFile(strFullName, domain);
1890 }
1891
1892
1893 wxArrayString wxFileTranslationsLoader::GetAvailableTranslations(const wxString& domain) const
1894 {
1895 wxArrayString langs;
1896 const wxArrayString prefixes = GetSearchPrefixes();
1897
1898 wxLogTrace(TRACE_I18N,
1899 "looking for available translations of \"%s\" in search path \"%s\"",
1900 domain, wxJoin(prefixes, wxPATH_SEP[0]));
1901
1902 for ( wxArrayString::const_iterator i = prefixes.begin();
1903 i != prefixes.end();
1904 ++i )
1905 {
1906 if ( i->empty() )
1907 continue;
1908 wxDir dir;
1909 if ( !dir.Open(*i) )
1910 continue;
1911
1912 wxString lang;
1913 for ( bool ok = dir.GetFirst(&lang, "", wxDIR_DIRS);
1914 ok;
1915 ok = dir.GetNext(&lang) )
1916 {
1917 const wxString langdir = *i + wxFILE_SEP_PATH + lang;
1918 if ( HasMsgCatalogInDir(langdir, domain) )
1919 {
1920 #ifdef __WXOSX__
1921 wxString rest;
1922 if ( lang.EndsWith(".lproj", &rest) )
1923 lang = rest;
1924 #endif // __WXOSX__
1925
1926 wxLogTrace(TRACE_I18N,
1927 "found %s translation of \"%s\"", lang, domain);
1928 langs.push_back(lang);
1929 }
1930 }
1931 }
1932
1933 return langs;
1934 }
1935
1936
1937 // ----------------------------------------------------------------------------
1938 // wxResourceTranslationsLoader
1939 // ----------------------------------------------------------------------------
1940
1941 #ifdef __WINDOWS__
1942
1943 wxMsgCatalog *wxResourceTranslationsLoader::LoadCatalog(const wxString& domain,
1944 const wxString& lang)
1945 {
1946 const void *mo_data = NULL;
1947 size_t mo_size = 0;
1948
1949 const wxString resname = wxString::Format("%s_%s", domain, lang);
1950
1951 if ( !wxLoadUserResource(&mo_data, &mo_size,
1952 resname,
1953 GetResourceType().t_str(),
1954 GetModule()) )
1955 return NULL;
1956
1957 wxLogTrace(TRACE_I18N,
1958 "Using catalog from Windows resource \"%s\".", resname);
1959
1960 wxMsgCatalog *cat = wxMsgCatalog::CreateFromData(
1961 wxCharBuffer::CreateNonOwned(static_cast<const char*>(mo_data), mo_size),
1962 domain);
1963
1964 if ( !cat )
1965 {
1966 wxLogWarning(_("Resource '%s' is not a valid message catalog."), resname);
1967 }
1968
1969 return cat;
1970 }
1971
1972 namespace
1973 {
1974
1975 struct EnumCallbackData
1976 {
1977 wxString prefix;
1978 wxArrayString langs;
1979 };
1980
1981 BOOL CALLBACK EnumTranslations(HMODULE WXUNUSED(hModule),
1982 LPCTSTR WXUNUSED(lpszType),
1983 LPTSTR lpszName,
1984 LONG_PTR lParam)
1985 {
1986 wxString name(lpszName);
1987 name.MakeLower(); // resource names are case insensitive
1988
1989 EnumCallbackData *data = reinterpret_cast<EnumCallbackData*>(lParam);
1990
1991 wxString lang;
1992 if ( name.StartsWith(data->prefix, &lang) && !lang.empty() )
1993 data->langs.push_back(lang);
1994
1995 return TRUE; // continue enumeration
1996 }
1997
1998 } // anonymous namespace
1999
2000
2001 wxArrayString wxResourceTranslationsLoader::GetAvailableTranslations(const wxString& domain) const
2002 {
2003 EnumCallbackData data;
2004 data.prefix = domain + "_";
2005 data.prefix.MakeLower(); // resource names are case insensitive
2006
2007 if ( !EnumResourceNames(GetModule(),
2008 GetResourceType().t_str(),
2009 EnumTranslations,
2010 reinterpret_cast<LONG_PTR>(&data)) )
2011 {
2012 const DWORD err = GetLastError();
2013 if ( err != NO_ERROR && err != ERROR_RESOURCE_TYPE_NOT_FOUND )
2014 {
2015 wxLogSysError(_("Couldn't enumerate translations"));
2016 }
2017 }
2018
2019 return data.langs;
2020 }
2021
2022 #endif // __WINDOWS__
2023
2024
2025 // ----------------------------------------------------------------------------
2026 // wxTranslationsModule module (for destruction of gs_translations)
2027 // ----------------------------------------------------------------------------
2028
2029 class wxTranslationsModule: public wxModule
2030 {
2031 DECLARE_DYNAMIC_CLASS(wxTranslationsModule)
2032 public:
2033 wxTranslationsModule() {}
2034
2035 bool OnInit()
2036 {
2037 return true;
2038 }
2039
2040 void OnExit()
2041 {
2042 if ( gs_translationsOwned )
2043 delete gs_translations;
2044 gs_translations = NULL;
2045 gs_translationsOwned = true;
2046 }
2047 };
2048
2049 IMPLEMENT_DYNAMIC_CLASS(wxTranslationsModule, wxModule)
2050
2051 #endif // wxUSE_INTL