]> git.saurik.com Git - wxWidgets.git/blob - src/common/translation.cpp
ce264439601b03b52c4b03ca694715874f95958f
[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 #endif
58 #ifdef __WXOSX__
59 #include "wx/osx/core/cfstring.h"
60 #include <CoreFoundation/CFBundle.h>
61 #include <CoreFoundation/CFLocale.h>
62 #endif
63
64 // ----------------------------------------------------------------------------
65 // simple types
66 // ----------------------------------------------------------------------------
67
68 typedef wxUint32 size_t32;
69
70 // ----------------------------------------------------------------------------
71 // constants
72 // ----------------------------------------------------------------------------
73
74 // magic number identifying the .mo format file
75 const size_t32 MSGCATALOG_MAGIC = 0x950412de;
76 const size_t32 MSGCATALOG_MAGIC_SW = 0xde120495;
77
78 #define TRACE_I18N wxS("i18n")
79
80 // ============================================================================
81 // implementation
82 // ============================================================================
83
84 namespace
85 {
86
87 #if !wxUSE_UNICODE
88 // We need to keep track of (char*) msgids in non-Unicode legacy builds. Instead
89 // of making the public wxMsgCatalog and wxTranslationsLoader APIs ugly, we
90 // store them in this global map.
91 wxStringToStringHashMap gs_msgIdCharset;
92 #endif
93
94 // ----------------------------------------------------------------------------
95 // Platform specific helpers
96 // ----------------------------------------------------------------------------
97
98 void LogTraceArray(const char *prefix, const wxArrayString& arr)
99 {
100 wxLogTrace(TRACE_I18N, "%s: [%s]", prefix, wxJoin(arr, ','));
101 }
102
103 // Use locale-based detection as a fallback
104 wxString GetPreferredUILanguageFallback(const wxArrayString& WXUNUSED(available))
105 {
106 const wxString lang = wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage());
107 wxLogTrace(TRACE_I18N, " - obtained best language from locale: %s", lang);
108 return lang;
109 }
110
111 #ifdef __WINDOWS__
112
113 wxString GetPreferredUILanguage(const wxArrayString& available)
114 {
115 typedef BOOL (WINAPI *GetUserPreferredUILanguages_t)(DWORD, PULONG, PWSTR, PULONG);
116 static GetUserPreferredUILanguages_t s_pfnGetUserPreferredUILanguages = NULL;
117 static bool s_initDone = false;
118 if ( !s_initDone )
119 {
120 wxLoadedDLL dllKernel32("kernel32.dll");
121 wxDL_INIT_FUNC(s_pfn, GetUserPreferredUILanguages, dllKernel32);
122 s_initDone = true;
123 }
124
125 if ( s_pfnGetUserPreferredUILanguages )
126 {
127 ULONG numLangs;
128 ULONG bufferSize = 0;
129 if ( (*s_pfnGetUserPreferredUILanguages)(MUI_LANGUAGE_NAME,
130 &numLangs,
131 NULL,
132 &bufferSize) )
133 {
134 wxScopedArray<WCHAR> langs(new WCHAR[bufferSize]);
135 if ( (*s_pfnGetUserPreferredUILanguages)(MUI_LANGUAGE_NAME,
136 &numLangs,
137 langs.get(),
138 &bufferSize) )
139 {
140 wxArrayString preferred;
141
142 WCHAR *buf = langs.get();
143 for ( unsigned i = 0; i < numLangs; i++ )
144 {
145 const wxString lang(buf);
146 preferred.push_back(lang);
147 buf += lang.length() + 1;
148 }
149 LogTraceArray(" - system preferred languages", preferred);
150
151 for ( wxArrayString::const_iterator i = preferred.begin();
152 i != preferred.end();
153 ++i )
154 {
155 wxString lang(*i);
156 lang.Replace("-", "_");
157 if ( available.Index(lang) != wxNOT_FOUND )
158 return lang;
159 size_t pos = lang.find('_');
160 if ( pos != wxString::npos )
161 {
162 lang = lang.substr(0, pos);
163 if ( available.Index(lang) != wxNOT_FOUND )
164 return lang;
165 }
166 }
167 }
168 }
169 }
170
171 return GetPreferredUILanguageFallback(available);
172 }
173
174 #elif defined(__WXOSX__)
175
176 void LogTraceArray(const char *prefix, CFArrayRef arr)
177 {
178 wxString s;
179 const unsigned count = CFArrayGetCount(arr);
180 if ( count )
181 {
182 s += wxCFStringRef::AsString((CFStringRef)CFArrayGetValueAtIndex(arr, 0));
183 for ( unsigned i = 1 ; i < count; i++ )
184 s += "," + wxCFStringRef::AsString((CFStringRef)CFArrayGetValueAtIndex(arr, i));
185 }
186 wxLogTrace(TRACE_I18N, "%s: [%s]", prefix, s);
187 }
188
189 wxString GetPreferredUILanguage(const wxArrayString& available)
190 {
191 wxStringToStringHashMap availableNormalized;
192 wxCFRef<CFMutableArrayRef> availableArr(
193 CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks));
194
195 for ( wxArrayString::const_iterator i = available.begin();
196 i != available.end();
197 ++i )
198 {
199 wxString lang(*i);
200 wxCFStringRef code_wx(*i);
201 wxCFStringRef code_norm(
202 CFLocaleCreateCanonicalLanguageIdentifierFromString(kCFAllocatorDefault, code_wx));
203 CFArrayAppendValue(availableArr, code_norm);
204 availableNormalized[code_norm.AsString()] = *i;
205 }
206 LogTraceArray(" - normalized available list", availableArr);
207
208 wxCFRef<CFArrayRef> prefArr(
209 CFBundleCopyLocalizationsForPreferences(availableArr, NULL));
210 LogTraceArray(" - system preferred languages", prefArr);
211
212 unsigned prefArrLength = CFArrayGetCount(prefArr);
213 if ( prefArrLength > 0 )
214 {
215 // Lookup the name in 'available' by index -- we need to get the
216 // original value corresponding to the normalized one chosen.
217 wxString lang(wxCFStringRef::AsString((CFStringRef)CFArrayGetValueAtIndex(prefArr, 0)));
218 wxStringToStringHashMap::const_iterator i = availableNormalized.find(lang);
219 if ( i == availableNormalized.end() )
220 return lang;
221 else
222 return i->second;
223 }
224
225 return GetPreferredUILanguageFallback(available);
226 }
227
228 #else
229
230 // On Unix, there's just one language=locale setting, so we should always
231 // use that.
232 #define GetPreferredUILanguage GetPreferredUILanguageFallback
233
234 #endif
235
236 } // anonymous namespace
237
238 // ----------------------------------------------------------------------------
239 // Plural forms parser
240 // ----------------------------------------------------------------------------
241
242 /*
243 Simplified Grammar
244
245 Expression:
246 LogicalOrExpression '?' Expression ':' Expression
247 LogicalOrExpression
248
249 LogicalOrExpression:
250 LogicalAndExpression "||" LogicalOrExpression // to (a || b) || c
251 LogicalAndExpression
252
253 LogicalAndExpression:
254 EqualityExpression "&&" LogicalAndExpression // to (a && b) && c
255 EqualityExpression
256
257 EqualityExpression:
258 RelationalExpression "==" RelationalExperession
259 RelationalExpression "!=" RelationalExperession
260 RelationalExpression
261
262 RelationalExpression:
263 MultiplicativeExpression '>' MultiplicativeExpression
264 MultiplicativeExpression '<' MultiplicativeExpression
265 MultiplicativeExpression ">=" MultiplicativeExpression
266 MultiplicativeExpression "<=" MultiplicativeExpression
267 MultiplicativeExpression
268
269 MultiplicativeExpression:
270 PmExpression '%' PmExpression
271 PmExpression
272
273 PmExpression:
274 N
275 Number
276 '(' Expression ')'
277 */
278
279 class wxPluralFormsToken
280 {
281 public:
282 enum Type
283 {
284 T_ERROR, T_EOF, T_NUMBER, T_N, T_PLURAL, T_NPLURALS, T_EQUAL, T_ASSIGN,
285 T_GREATER, T_GREATER_OR_EQUAL, T_LESS, T_LESS_OR_EQUAL,
286 T_REMINDER, T_NOT_EQUAL,
287 T_LOGICAL_AND, T_LOGICAL_OR, T_QUESTION, T_COLON, T_SEMICOLON,
288 T_LEFT_BRACKET, T_RIGHT_BRACKET
289 };
290 Type type() const { return m_type; }
291 void setType(Type type) { m_type = type; }
292 // for T_NUMBER only
293 typedef int Number;
294 Number number() const { return m_number; }
295 void setNumber(Number num) { m_number = num; }
296 private:
297 Type m_type;
298 Number m_number;
299 };
300
301
302 class wxPluralFormsScanner
303 {
304 public:
305 wxPluralFormsScanner(const char* s);
306 const wxPluralFormsToken& token() const { return m_token; }
307 bool nextToken(); // returns false if error
308 private:
309 const char* m_s;
310 wxPluralFormsToken m_token;
311 };
312
313 wxPluralFormsScanner::wxPluralFormsScanner(const char* s) : m_s(s)
314 {
315 nextToken();
316 }
317
318 bool wxPluralFormsScanner::nextToken()
319 {
320 wxPluralFormsToken::Type type = wxPluralFormsToken::T_ERROR;
321 while (isspace((unsigned char) *m_s))
322 {
323 ++m_s;
324 }
325 if (*m_s == 0)
326 {
327 type = wxPluralFormsToken::T_EOF;
328 }
329 else if (isdigit((unsigned char) *m_s))
330 {
331 wxPluralFormsToken::Number number = *m_s++ - '0';
332 while (isdigit((unsigned char) *m_s))
333 {
334 number = number * 10 + (*m_s++ - '0');
335 }
336 m_token.setNumber(number);
337 type = wxPluralFormsToken::T_NUMBER;
338 }
339 else if (isalpha((unsigned char) *m_s))
340 {
341 const char* begin = m_s++;
342 while (isalnum((unsigned char) *m_s))
343 {
344 ++m_s;
345 }
346 size_t size = m_s - begin;
347 if (size == 1 && memcmp(begin, "n", size) == 0)
348 {
349 type = wxPluralFormsToken::T_N;
350 }
351 else if (size == 6 && memcmp(begin, "plural", size) == 0)
352 {
353 type = wxPluralFormsToken::T_PLURAL;
354 }
355 else if (size == 8 && memcmp(begin, "nplurals", size) == 0)
356 {
357 type = wxPluralFormsToken::T_NPLURALS;
358 }
359 }
360 else if (*m_s == '=')
361 {
362 ++m_s;
363 if (*m_s == '=')
364 {
365 ++m_s;
366 type = wxPluralFormsToken::T_EQUAL;
367 }
368 else
369 {
370 type = wxPluralFormsToken::T_ASSIGN;
371 }
372 }
373 else if (*m_s == '>')
374 {
375 ++m_s;
376 if (*m_s == '=')
377 {
378 ++m_s;
379 type = wxPluralFormsToken::T_GREATER_OR_EQUAL;
380 }
381 else
382 {
383 type = wxPluralFormsToken::T_GREATER;
384 }
385 }
386 else if (*m_s == '<')
387 {
388 ++m_s;
389 if (*m_s == '=')
390 {
391 ++m_s;
392 type = wxPluralFormsToken::T_LESS_OR_EQUAL;
393 }
394 else
395 {
396 type = wxPluralFormsToken::T_LESS;
397 }
398 }
399 else if (*m_s == '%')
400 {
401 ++m_s;
402 type = wxPluralFormsToken::T_REMINDER;
403 }
404 else if (*m_s == '!' && m_s[1] == '=')
405 {
406 m_s += 2;
407 type = wxPluralFormsToken::T_NOT_EQUAL;
408 }
409 else if (*m_s == '&' && m_s[1] == '&')
410 {
411 m_s += 2;
412 type = wxPluralFormsToken::T_LOGICAL_AND;
413 }
414 else if (*m_s == '|' && m_s[1] == '|')
415 {
416 m_s += 2;
417 type = wxPluralFormsToken::T_LOGICAL_OR;
418 }
419 else if (*m_s == '?')
420 {
421 ++m_s;
422 type = wxPluralFormsToken::T_QUESTION;
423 }
424 else if (*m_s == ':')
425 {
426 ++m_s;
427 type = wxPluralFormsToken::T_COLON;
428 } else if (*m_s == ';') {
429 ++m_s;
430 type = wxPluralFormsToken::T_SEMICOLON;
431 }
432 else if (*m_s == '(')
433 {
434 ++m_s;
435 type = wxPluralFormsToken::T_LEFT_BRACKET;
436 }
437 else if (*m_s == ')')
438 {
439 ++m_s;
440 type = wxPluralFormsToken::T_RIGHT_BRACKET;
441 }
442 m_token.setType(type);
443 return type != wxPluralFormsToken::T_ERROR;
444 }
445
446 class wxPluralFormsNode;
447
448 // NB: Can't use wxDEFINE_SCOPED_PTR_TYPE because wxPluralFormsNode is not
449 // fully defined yet:
450 class wxPluralFormsNodePtr
451 {
452 public:
453 wxPluralFormsNodePtr(wxPluralFormsNode *p = NULL) : m_p(p) {}
454 ~wxPluralFormsNodePtr();
455 wxPluralFormsNode& operator*() const { return *m_p; }
456 wxPluralFormsNode* operator->() const { return m_p; }
457 wxPluralFormsNode* get() const { return m_p; }
458 wxPluralFormsNode* release();
459 void reset(wxPluralFormsNode *p);
460
461 private:
462 wxPluralFormsNode *m_p;
463 };
464
465 class wxPluralFormsNode
466 {
467 public:
468 wxPluralFormsNode(const wxPluralFormsToken& token) : m_token(token) {}
469 const wxPluralFormsToken& token() const { return m_token; }
470 const wxPluralFormsNode* node(unsigned i) const
471 { return m_nodes[i].get(); }
472 void setNode(unsigned i, wxPluralFormsNode* n);
473 wxPluralFormsNode* releaseNode(unsigned i);
474 wxPluralFormsToken::Number evaluate(wxPluralFormsToken::Number n) const;
475
476 private:
477 wxPluralFormsToken m_token;
478 wxPluralFormsNodePtr m_nodes[3];
479 };
480
481 wxPluralFormsNodePtr::~wxPluralFormsNodePtr()
482 {
483 delete m_p;
484 }
485 wxPluralFormsNode* wxPluralFormsNodePtr::release()
486 {
487 wxPluralFormsNode *p = m_p;
488 m_p = NULL;
489 return p;
490 }
491 void wxPluralFormsNodePtr::reset(wxPluralFormsNode *p)
492 {
493 if (p != m_p)
494 {
495 delete m_p;
496 m_p = p;
497 }
498 }
499
500
501 void wxPluralFormsNode::setNode(unsigned i, wxPluralFormsNode* n)
502 {
503 m_nodes[i].reset(n);
504 }
505
506 wxPluralFormsNode* wxPluralFormsNode::releaseNode(unsigned i)
507 {
508 return m_nodes[i].release();
509 }
510
511 wxPluralFormsToken::Number
512 wxPluralFormsNode::evaluate(wxPluralFormsToken::Number n) const
513 {
514 switch (token().type())
515 {
516 // leaf
517 case wxPluralFormsToken::T_NUMBER:
518 return token().number();
519 case wxPluralFormsToken::T_N:
520 return n;
521 // 2 args
522 case wxPluralFormsToken::T_EQUAL:
523 return node(0)->evaluate(n) == node(1)->evaluate(n);
524 case wxPluralFormsToken::T_NOT_EQUAL:
525 return node(0)->evaluate(n) != node(1)->evaluate(n);
526 case wxPluralFormsToken::T_GREATER:
527 return node(0)->evaluate(n) > node(1)->evaluate(n);
528 case wxPluralFormsToken::T_GREATER_OR_EQUAL:
529 return node(0)->evaluate(n) >= node(1)->evaluate(n);
530 case wxPluralFormsToken::T_LESS:
531 return node(0)->evaluate(n) < node(1)->evaluate(n);
532 case wxPluralFormsToken::T_LESS_OR_EQUAL:
533 return node(0)->evaluate(n) <= node(1)->evaluate(n);
534 case wxPluralFormsToken::T_REMINDER:
535 {
536 wxPluralFormsToken::Number number = node(1)->evaluate(n);
537 if (number != 0)
538 {
539 return node(0)->evaluate(n) % number;
540 }
541 else
542 {
543 return 0;
544 }
545 }
546 case wxPluralFormsToken::T_LOGICAL_AND:
547 return node(0)->evaluate(n) && node(1)->evaluate(n);
548 case wxPluralFormsToken::T_LOGICAL_OR:
549 return node(0)->evaluate(n) || node(1)->evaluate(n);
550 // 3 args
551 case wxPluralFormsToken::T_QUESTION:
552 return node(0)->evaluate(n)
553 ? node(1)->evaluate(n)
554 : node(2)->evaluate(n);
555 default:
556 return 0;
557 }
558 }
559
560
561 class wxPluralFormsCalculator
562 {
563 public:
564 wxPluralFormsCalculator() : m_nplurals(0), m_plural(0) {}
565
566 // input: number, returns msgstr index
567 int evaluate(int n) const;
568
569 // input: text after "Plural-Forms:" (e.g. "nplurals=2; plural=(n != 1);"),
570 // if s == 0, creates default handler
571 // returns 0 if error
572 static wxPluralFormsCalculator* make(const char* s = 0);
573
574 ~wxPluralFormsCalculator() {}
575
576 void init(wxPluralFormsToken::Number nplurals, wxPluralFormsNode* plural);
577
578 private:
579 wxPluralFormsToken::Number m_nplurals;
580 wxPluralFormsNodePtr m_plural;
581 };
582
583 wxDEFINE_SCOPED_PTR(wxPluralFormsCalculator, wxPluralFormsCalculatorPtr)
584
585 void wxPluralFormsCalculator::init(wxPluralFormsToken::Number nplurals,
586 wxPluralFormsNode* plural)
587 {
588 m_nplurals = nplurals;
589 m_plural.reset(plural);
590 }
591
592 int wxPluralFormsCalculator::evaluate(int n) const
593 {
594 if (m_plural.get() == 0)
595 {
596 return 0;
597 }
598 wxPluralFormsToken::Number number = m_plural->evaluate(n);
599 if (number < 0 || number > m_nplurals)
600 {
601 return 0;
602 }
603 return number;
604 }
605
606
607 class wxPluralFormsParser
608 {
609 public:
610 wxPluralFormsParser(wxPluralFormsScanner& scanner) : m_scanner(scanner) {}
611 bool parse(wxPluralFormsCalculator& rCalculator);
612
613 private:
614 wxPluralFormsNode* parsePlural();
615 // stops at T_SEMICOLON, returns 0 if error
616 wxPluralFormsScanner& m_scanner;
617 const wxPluralFormsToken& token() const;
618 bool nextToken();
619
620 wxPluralFormsNode* expression();
621 wxPluralFormsNode* logicalOrExpression();
622 wxPluralFormsNode* logicalAndExpression();
623 wxPluralFormsNode* equalityExpression();
624 wxPluralFormsNode* multiplicativeExpression();
625 wxPluralFormsNode* relationalExpression();
626 wxPluralFormsNode* pmExpression();
627 };
628
629 bool wxPluralFormsParser::parse(wxPluralFormsCalculator& rCalculator)
630 {
631 if (token().type() != wxPluralFormsToken::T_NPLURALS)
632 return false;
633 if (!nextToken())
634 return false;
635 if (token().type() != wxPluralFormsToken::T_ASSIGN)
636 return false;
637 if (!nextToken())
638 return false;
639 if (token().type() != wxPluralFormsToken::T_NUMBER)
640 return false;
641 wxPluralFormsToken::Number nplurals = token().number();
642 if (!nextToken())
643 return false;
644 if (token().type() != wxPluralFormsToken::T_SEMICOLON)
645 return false;
646 if (!nextToken())
647 return false;
648 if (token().type() != wxPluralFormsToken::T_PLURAL)
649 return false;
650 if (!nextToken())
651 return false;
652 if (token().type() != wxPluralFormsToken::T_ASSIGN)
653 return false;
654 if (!nextToken())
655 return false;
656 wxPluralFormsNode* plural = parsePlural();
657 if (plural == 0)
658 return false;
659 if (token().type() != wxPluralFormsToken::T_SEMICOLON)
660 return false;
661 if (!nextToken())
662 return false;
663 if (token().type() != wxPluralFormsToken::T_EOF)
664 return false;
665 rCalculator.init(nplurals, plural);
666 return true;
667 }
668
669 wxPluralFormsNode* wxPluralFormsParser::parsePlural()
670 {
671 wxPluralFormsNode* p = expression();
672 if (p == NULL)
673 {
674 return NULL;
675 }
676 wxPluralFormsNodePtr n(p);
677 if (token().type() != wxPluralFormsToken::T_SEMICOLON)
678 {
679 return NULL;
680 }
681 return n.release();
682 }
683
684 const wxPluralFormsToken& wxPluralFormsParser::token() const
685 {
686 return m_scanner.token();
687 }
688
689 bool wxPluralFormsParser::nextToken()
690 {
691 if (!m_scanner.nextToken())
692 return false;
693 return true;
694 }
695
696 wxPluralFormsNode* wxPluralFormsParser::expression()
697 {
698 wxPluralFormsNode* p = logicalOrExpression();
699 if (p == NULL)
700 return NULL;
701 wxPluralFormsNodePtr n(p);
702 if (token().type() == wxPluralFormsToken::T_QUESTION)
703 {
704 wxPluralFormsNodePtr qn(new wxPluralFormsNode(token()));
705 if (!nextToken())
706 {
707 return 0;
708 }
709 p = expression();
710 if (p == 0)
711 {
712 return 0;
713 }
714 qn->setNode(1, p);
715 if (token().type() != wxPluralFormsToken::T_COLON)
716 {
717 return 0;
718 }
719 if (!nextToken())
720 {
721 return 0;
722 }
723 p = expression();
724 if (p == 0)
725 {
726 return 0;
727 }
728 qn->setNode(2, p);
729 qn->setNode(0, n.release());
730 return qn.release();
731 }
732 return n.release();
733 }
734
735 wxPluralFormsNode*wxPluralFormsParser::logicalOrExpression()
736 {
737 wxPluralFormsNode* p = logicalAndExpression();
738 if (p == NULL)
739 return NULL;
740 wxPluralFormsNodePtr ln(p);
741 if (token().type() == wxPluralFormsToken::T_LOGICAL_OR)
742 {
743 wxPluralFormsNodePtr un(new wxPluralFormsNode(token()));
744 if (!nextToken())
745 {
746 return 0;
747 }
748 p = logicalOrExpression();
749 if (p == 0)
750 {
751 return 0;
752 }
753 wxPluralFormsNodePtr rn(p); // right
754 if (rn->token().type() == wxPluralFormsToken::T_LOGICAL_OR)
755 {
756 // see logicalAndExpression comment
757 un->setNode(0, ln.release());
758 un->setNode(1, rn->releaseNode(0));
759 rn->setNode(0, un.release());
760 return rn.release();
761 }
762
763
764 un->setNode(0, ln.release());
765 un->setNode(1, rn.release());
766 return un.release();
767 }
768 return ln.release();
769 }
770
771 wxPluralFormsNode* wxPluralFormsParser::logicalAndExpression()
772 {
773 wxPluralFormsNode* p = equalityExpression();
774 if (p == NULL)
775 return NULL;
776 wxPluralFormsNodePtr ln(p); // left
777 if (token().type() == wxPluralFormsToken::T_LOGICAL_AND)
778 {
779 wxPluralFormsNodePtr un(new wxPluralFormsNode(token())); // up
780 if (!nextToken())
781 {
782 return NULL;
783 }
784 p = logicalAndExpression();
785 if (p == 0)
786 {
787 return NULL;
788 }
789 wxPluralFormsNodePtr rn(p); // right
790 if (rn->token().type() == wxPluralFormsToken::T_LOGICAL_AND)
791 {
792 // transform 1 && (2 && 3) -> (1 && 2) && 3
793 // u r
794 // l r -> u 3
795 // 2 3 l 2
796 un->setNode(0, ln.release());
797 un->setNode(1, rn->releaseNode(0));
798 rn->setNode(0, un.release());
799 return rn.release();
800 }
801
802 un->setNode(0, ln.release());
803 un->setNode(1, rn.release());
804 return un.release();
805 }
806 return ln.release();
807 }
808
809 wxPluralFormsNode* wxPluralFormsParser::equalityExpression()
810 {
811 wxPluralFormsNode* p = relationalExpression();
812 if (p == NULL)
813 return NULL;
814 wxPluralFormsNodePtr n(p);
815 if (token().type() == wxPluralFormsToken::T_EQUAL
816 || token().type() == wxPluralFormsToken::T_NOT_EQUAL)
817 {
818 wxPluralFormsNodePtr qn(new wxPluralFormsNode(token()));
819 if (!nextToken())
820 {
821 return NULL;
822 }
823 p = relationalExpression();
824 if (p == NULL)
825 {
826 return NULL;
827 }
828 qn->setNode(1, p);
829 qn->setNode(0, n.release());
830 return qn.release();
831 }
832 return n.release();
833 }
834
835 wxPluralFormsNode* wxPluralFormsParser::relationalExpression()
836 {
837 wxPluralFormsNode* p = multiplicativeExpression();
838 if (p == NULL)
839 return NULL;
840 wxPluralFormsNodePtr n(p);
841 if (token().type() == wxPluralFormsToken::T_GREATER
842 || token().type() == wxPluralFormsToken::T_LESS
843 || token().type() == wxPluralFormsToken::T_GREATER_OR_EQUAL
844 || token().type() == wxPluralFormsToken::T_LESS_OR_EQUAL)
845 {
846 wxPluralFormsNodePtr qn(new wxPluralFormsNode(token()));
847 if (!nextToken())
848 {
849 return NULL;
850 }
851 p = multiplicativeExpression();
852 if (p == NULL)
853 {
854 return NULL;
855 }
856 qn->setNode(1, p);
857 qn->setNode(0, n.release());
858 return qn.release();
859 }
860 return n.release();
861 }
862
863 wxPluralFormsNode* wxPluralFormsParser::multiplicativeExpression()
864 {
865 wxPluralFormsNode* p = pmExpression();
866 if (p == NULL)
867 return NULL;
868 wxPluralFormsNodePtr n(p);
869 if (token().type() == wxPluralFormsToken::T_REMINDER)
870 {
871 wxPluralFormsNodePtr qn(new wxPluralFormsNode(token()));
872 if (!nextToken())
873 {
874 return NULL;
875 }
876 p = pmExpression();
877 if (p == NULL)
878 {
879 return NULL;
880 }
881 qn->setNode(1, p);
882 qn->setNode(0, n.release());
883 return qn.release();
884 }
885 return n.release();
886 }
887
888 wxPluralFormsNode* wxPluralFormsParser::pmExpression()
889 {
890 wxPluralFormsNodePtr n;
891 if (token().type() == wxPluralFormsToken::T_N
892 || token().type() == wxPluralFormsToken::T_NUMBER)
893 {
894 n.reset(new wxPluralFormsNode(token()));
895 if (!nextToken())
896 {
897 return NULL;
898 }
899 }
900 else if (token().type() == wxPluralFormsToken::T_LEFT_BRACKET) {
901 if (!nextToken())
902 {
903 return NULL;
904 }
905 wxPluralFormsNode* p = expression();
906 if (p == NULL)
907 {
908 return NULL;
909 }
910 n.reset(p);
911 if (token().type() != wxPluralFormsToken::T_RIGHT_BRACKET)
912 {
913 return NULL;
914 }
915 if (!nextToken())
916 {
917 return NULL;
918 }
919 }
920 else
921 {
922 return NULL;
923 }
924 return n.release();
925 }
926
927 wxPluralFormsCalculator* wxPluralFormsCalculator::make(const char* s)
928 {
929 wxPluralFormsCalculatorPtr calculator(new wxPluralFormsCalculator);
930 if (s != NULL)
931 {
932 wxPluralFormsScanner scanner(s);
933 wxPluralFormsParser p(scanner);
934 if (!p.parse(*calculator))
935 {
936 return NULL;
937 }
938 }
939 return calculator.release();
940 }
941
942
943
944
945 // ----------------------------------------------------------------------------
946 // wxMsgCatalogFile corresponds to one disk-file message catalog.
947 //
948 // This is a "low-level" class and is used only by wxMsgCatalog
949 // NOTE: for the documentation of the binary catalog (.MO) files refer to
950 // the GNU gettext manual:
951 // http://www.gnu.org/software/autoconf/manual/gettext/MO-Files.html
952 // ----------------------------------------------------------------------------
953
954 class wxMsgCatalogFile
955 {
956 public:
957 typedef wxScopedCharBuffer DataBuffer;
958
959 // ctor & dtor
960 wxMsgCatalogFile();
961 ~wxMsgCatalogFile();
962
963 // load the catalog from disk
964 bool LoadFile(const wxString& filename,
965 wxPluralFormsCalculatorPtr& rPluralFormsCalculator);
966 bool LoadData(const DataBuffer& data,
967 wxPluralFormsCalculatorPtr& rPluralFormsCalculator);
968
969 // fills the hash with string-translation pairs
970 bool FillHash(wxStringToStringHashMap& hash, const wxString& domain) const;
971
972 // return the charset of the strings in this catalog or empty string if
973 // none/unknown
974 wxString GetCharset() const { return m_charset; }
975
976 private:
977 // this implementation is binary compatible with GNU gettext() version 0.10
978
979 // an entry in the string table
980 struct wxMsgTableEntry
981 {
982 size_t32 nLen; // length of the string
983 size_t32 ofsString; // pointer to the string
984 };
985
986 // header of a .mo file
987 struct wxMsgCatalogHeader
988 {
989 size_t32 magic, // offset +00: magic id
990 revision, // +04: revision
991 numStrings; // +08: number of strings in the file
992 size_t32 ofsOrigTable, // +0C: start of original string table
993 ofsTransTable; // +10: start of translated string table
994 size_t32 nHashSize, // +14: hash table size
995 ofsHashTable; // +18: offset of hash table start
996 };
997
998 // all data is stored here
999 DataBuffer m_data;
1000
1001 // data description
1002 size_t32 m_numStrings; // number of strings in this domain
1003 wxMsgTableEntry *m_pOrigTable, // pointer to original strings
1004 *m_pTransTable; // translated
1005
1006 wxString m_charset; // from the message catalog header
1007
1008
1009 // swap the 2 halves of 32 bit integer if needed
1010 size_t32 Swap(size_t32 ui) const
1011 {
1012 return m_bSwapped ? (ui << 24) | ((ui & 0xff00) << 8) |
1013 ((ui >> 8) & 0xff00) | (ui >> 24)
1014 : ui;
1015 }
1016
1017 const char *StringAtOfs(wxMsgTableEntry *pTable, size_t32 n) const
1018 {
1019 const wxMsgTableEntry * const ent = pTable + n;
1020
1021 // this check could fail for a corrupt message catalog
1022 size_t32 ofsString = Swap(ent->ofsString);
1023 if ( ofsString + Swap(ent->nLen) > m_data.length())
1024 {
1025 return NULL;
1026 }
1027
1028 return m_data.data() + ofsString;
1029 }
1030
1031 bool m_bSwapped; // wrong endianness?
1032
1033 wxDECLARE_NO_COPY_CLASS(wxMsgCatalogFile);
1034 };
1035
1036 // ----------------------------------------------------------------------------
1037 // wxMsgCatalogFile class
1038 // ----------------------------------------------------------------------------
1039
1040 wxMsgCatalogFile::wxMsgCatalogFile()
1041 {
1042 }
1043
1044 wxMsgCatalogFile::~wxMsgCatalogFile()
1045 {
1046 }
1047
1048 // open disk file and read in it's contents
1049 bool wxMsgCatalogFile::LoadFile(const wxString& filename,
1050 wxPluralFormsCalculatorPtr& rPluralFormsCalculator)
1051 {
1052 wxFile fileMsg(filename);
1053 if ( !fileMsg.IsOpened() )
1054 return false;
1055
1056 // get the file size (assume it is less than 4GB...)
1057 wxFileOffset lenFile = fileMsg.Length();
1058 if ( lenFile == wxInvalidOffset )
1059 return false;
1060
1061 size_t nSize = wx_truncate_cast(size_t, lenFile);
1062 wxASSERT_MSG( nSize == lenFile + size_t(0), wxS("message catalog bigger than 4GB?") );
1063
1064 wxMemoryBuffer filedata;
1065
1066 // read the whole file in memory
1067 if ( fileMsg.Read(filedata.GetWriteBuf(nSize), nSize) != lenFile )
1068 return false;
1069
1070 filedata.UngetWriteBuf(nSize);
1071
1072 bool ok = LoadData
1073 (
1074 DataBuffer::CreateOwned((char*)filedata.release(), nSize),
1075 rPluralFormsCalculator
1076 );
1077 if ( !ok )
1078 {
1079 wxLogWarning(_("'%s' is not a valid message catalog."), filename.c_str());
1080 return false;
1081 }
1082
1083 return true;
1084 }
1085
1086
1087 bool wxMsgCatalogFile::LoadData(const DataBuffer& data,
1088 wxPluralFormsCalculatorPtr& rPluralFormsCalculator)
1089 {
1090 // examine header
1091 bool bValid = data.length() > sizeof(wxMsgCatalogHeader);
1092
1093 const wxMsgCatalogHeader *pHeader = (wxMsgCatalogHeader *)data.data();
1094 if ( bValid ) {
1095 // we'll have to swap all the integers if it's true
1096 m_bSwapped = pHeader->magic == MSGCATALOG_MAGIC_SW;
1097
1098 // check the magic number
1099 bValid = m_bSwapped || pHeader->magic == MSGCATALOG_MAGIC;
1100 }
1101
1102 if ( !bValid ) {
1103 // it's either too short or has incorrect magic number
1104 wxLogWarning(_("Invalid message catalog."));
1105 return false;
1106 }
1107
1108 m_data = data;
1109
1110 // initialize
1111 m_numStrings = Swap(pHeader->numStrings);
1112 m_pOrigTable = (wxMsgTableEntry *)(data.data() +
1113 Swap(pHeader->ofsOrigTable));
1114 m_pTransTable = (wxMsgTableEntry *)(data.data() +
1115 Swap(pHeader->ofsTransTable));
1116
1117 // now parse catalog's header and try to extract catalog charset and
1118 // plural forms formula from it:
1119
1120 const char* headerData = StringAtOfs(m_pOrigTable, 0);
1121 if ( headerData && headerData[0] == '\0' )
1122 {
1123 // Extract the charset:
1124 const char * const header = StringAtOfs(m_pTransTable, 0);
1125 const char *
1126 cset = strstr(header, "Content-Type: text/plain; charset=");
1127 if ( cset )
1128 {
1129 cset += 34; // strlen("Content-Type: text/plain; charset=")
1130
1131 const char * const csetEnd = strchr(cset, '\n');
1132 if ( csetEnd )
1133 {
1134 m_charset = wxString(cset, csetEnd - cset);
1135 if ( m_charset == wxS("CHARSET") )
1136 {
1137 // "CHARSET" is not valid charset, but lazy translator
1138 m_charset.clear();
1139 }
1140 }
1141 }
1142 // else: incorrectly filled Content-Type header
1143
1144 // Extract plural forms:
1145 const char * plurals = strstr(header, "Plural-Forms:");
1146 if ( plurals )
1147 {
1148 plurals += 13; // strlen("Plural-Forms:")
1149 const char * const pluralsEnd = strchr(plurals, '\n');
1150 if ( pluralsEnd )
1151 {
1152 const size_t pluralsLen = pluralsEnd - plurals;
1153 wxCharBuffer buf(pluralsLen);
1154 strncpy(buf.data(), plurals, pluralsLen);
1155 wxPluralFormsCalculator * const
1156 pCalculator = wxPluralFormsCalculator::make(buf);
1157 if ( pCalculator )
1158 {
1159 rPluralFormsCalculator.reset(pCalculator);
1160 }
1161 else
1162 {
1163 wxLogVerbose(_("Failed to parse Plural-Forms: '%s'"),
1164 buf.data());
1165 }
1166 }
1167 }
1168
1169 if ( !rPluralFormsCalculator.get() )
1170 rPluralFormsCalculator.reset(wxPluralFormsCalculator::make());
1171 }
1172
1173 // everything is fine
1174 return true;
1175 }
1176
1177 bool wxMsgCatalogFile::FillHash(wxStringToStringHashMap& hash,
1178 const wxString& domain) const
1179 {
1180 wxUnusedVar(domain); // silence warning in Unicode build
1181
1182 // conversion to use to convert catalog strings to the GUI encoding
1183 wxMBConv *inputConv = NULL;
1184 wxMBConv *inputConvPtr = NULL; // same as inputConv but safely deleteable
1185
1186 if ( !m_charset.empty() )
1187 {
1188 #if !wxUSE_UNICODE && wxUSE_FONTMAP
1189 // determine if we need any conversion at all
1190 wxFontEncoding encCat = wxFontMapperBase::GetEncodingFromName(m_charset);
1191 if ( encCat != wxLocale::GetSystemEncoding() )
1192 #endif
1193 {
1194 inputConvPtr =
1195 inputConv = new wxCSConv(m_charset);
1196 }
1197 }
1198 else // no need or not possible to convert the encoding
1199 {
1200 #if wxUSE_UNICODE
1201 // we must somehow convert the narrow strings in the message catalog to
1202 // wide strings, so use the default conversion if we have no charset
1203 inputConv = wxConvCurrent;
1204 #endif
1205 }
1206
1207 #if !wxUSE_UNICODE
1208 wxString msgIdCharset = gs_msgIdCharset[domain];
1209
1210 // conversion to apply to msgid strings before looking them up: we only
1211 // need it if the msgids are neither in 7 bit ASCII nor in the same
1212 // encoding as the catalog
1213 wxCSConv *sourceConv = msgIdCharset.empty() || (msgIdCharset == m_charset)
1214 ? NULL
1215 : new wxCSConv(msgIdCharset);
1216 #endif // !wxUSE_UNICODE
1217
1218 for (size_t32 i = 0; i < m_numStrings; i++)
1219 {
1220 const char *data = StringAtOfs(m_pOrigTable, i);
1221 if (!data)
1222 return false; // may happen for invalid MO files
1223
1224 wxString msgid;
1225 #if wxUSE_UNICODE
1226 msgid = wxString(data, *inputConv);
1227 #else // ASCII
1228 if ( inputConv && sourceConv )
1229 msgid = wxString(inputConv->cMB2WC(data), *sourceConv);
1230 else
1231 msgid = data;
1232 #endif // wxUSE_UNICODE
1233
1234 data = StringAtOfs(m_pTransTable, i);
1235 if (!data)
1236 return false; // may happen for invalid MO files
1237
1238 size_t length = Swap(m_pTransTable[i].nLen);
1239 size_t offset = 0;
1240 size_t index = 0;
1241 while (offset < length)
1242 {
1243 const char * const str = data + offset;
1244
1245 wxString msgstr;
1246 #if wxUSE_UNICODE
1247 msgstr = wxString(str, *inputConv);
1248 #else
1249 if ( inputConv )
1250 msgstr = wxString(inputConv->cMB2WC(str), *wxConvUI);
1251 else
1252 msgstr = str;
1253 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
1254
1255 if ( !msgstr.empty() )
1256 {
1257 hash[index == 0 ? msgid : msgid + wxChar(index)] = msgstr;
1258 }
1259
1260 // skip this string
1261 // IMPORTANT: accesses to the 'data' pointer are valid only for
1262 // the first 'length+1' bytes (GNU specs says that the
1263 // final NUL is not counted in length); using wxStrnlen()
1264 // we make sure we don't access memory beyond the valid range
1265 // (which otherwise may happen for invalid MO files):
1266 offset += wxStrnlen(str, length - offset) + 1;
1267 ++index;
1268 }
1269 }
1270
1271 #if !wxUSE_UNICODE
1272 delete sourceConv;
1273 #endif
1274 delete inputConvPtr;
1275
1276 return true;
1277 }
1278
1279
1280 // ----------------------------------------------------------------------------
1281 // wxMsgCatalog class
1282 // ----------------------------------------------------------------------------
1283
1284 #if !wxUSE_UNICODE
1285 wxMsgCatalog::~wxMsgCatalog()
1286 {
1287 if ( m_conv )
1288 {
1289 if ( wxConvUI == m_conv )
1290 {
1291 // we only change wxConvUI if it points to wxConvLocal so we reset
1292 // it back to it too
1293 wxConvUI = &wxConvLocal;
1294 }
1295
1296 delete m_conv;
1297 }
1298 }
1299 #endif // !wxUSE_UNICODE
1300
1301 /* static */
1302 wxMsgCatalog *wxMsgCatalog::CreateFromFile(const wxString& filename,
1303 const wxString& domain)
1304 {
1305 wxScopedPtr<wxMsgCatalog> cat(new wxMsgCatalog(domain));
1306
1307 wxMsgCatalogFile file;
1308
1309 if ( !file.LoadFile(filename, cat->m_pluralFormsCalculator) )
1310 return NULL;
1311
1312 if ( !file.FillHash(cat->m_messages, domain) )
1313 return NULL;
1314
1315 return cat.release();
1316 }
1317
1318 /* static */
1319 wxMsgCatalog *wxMsgCatalog::CreateFromData(const wxScopedCharBuffer& data,
1320 const wxString& domain)
1321 {
1322 wxScopedPtr<wxMsgCatalog> cat(new wxMsgCatalog(domain));
1323
1324 wxMsgCatalogFile file;
1325
1326 if ( !file.LoadData(data, cat->m_pluralFormsCalculator) )
1327 return NULL;
1328
1329 if ( !file.FillHash(cat->m_messages, domain) )
1330 return NULL;
1331
1332 return cat.release();
1333 }
1334
1335 const wxString *wxMsgCatalog::GetString(const wxString& str, unsigned n) const
1336 {
1337 int index = 0;
1338 if (n != UINT_MAX)
1339 {
1340 index = m_pluralFormsCalculator->evaluate(n);
1341 }
1342 wxStringToStringHashMap::const_iterator i;
1343 if (index != 0)
1344 {
1345 i = m_messages.find(wxString(str) + wxChar(index)); // plural
1346 }
1347 else
1348 {
1349 i = m_messages.find(str);
1350 }
1351
1352 if ( i != m_messages.end() )
1353 {
1354 return &i->second;
1355 }
1356 else
1357 return NULL;
1358 }
1359
1360
1361 // ----------------------------------------------------------------------------
1362 // wxTranslations
1363 // ----------------------------------------------------------------------------
1364
1365 namespace
1366 {
1367
1368 wxTranslations *gs_translations = NULL;
1369 bool gs_translationsOwned = false;
1370
1371 } // anonymous namespace
1372
1373
1374 /*static*/
1375 wxTranslations *wxTranslations::Get()
1376 {
1377 return gs_translations;
1378 }
1379
1380 /*static*/
1381 void wxTranslations::Set(wxTranslations *t)
1382 {
1383 if ( gs_translationsOwned )
1384 delete gs_translations;
1385 gs_translations = t;
1386 gs_translationsOwned = true;
1387 }
1388
1389 /*static*/
1390 void wxTranslations::SetNonOwned(wxTranslations *t)
1391 {
1392 if ( gs_translationsOwned )
1393 delete gs_translations;
1394 gs_translations = t;
1395 gs_translationsOwned = false;
1396 }
1397
1398
1399 wxTranslations::wxTranslations()
1400 {
1401 m_pMsgCat = NULL;
1402 m_loader = new wxFileTranslationsLoader;
1403 }
1404
1405
1406 wxTranslations::~wxTranslations()
1407 {
1408 delete m_loader;
1409
1410 // free catalogs memory
1411 wxMsgCatalog *pTmpCat;
1412 while ( m_pMsgCat != NULL )
1413 {
1414 pTmpCat = m_pMsgCat;
1415 m_pMsgCat = m_pMsgCat->m_pNext;
1416 delete pTmpCat;
1417 }
1418 }
1419
1420
1421 void wxTranslations::SetLoader(wxTranslationsLoader *loader)
1422 {
1423 wxCHECK_RET( loader, "loader can't be NULL" );
1424
1425 delete m_loader;
1426 m_loader = loader;
1427 }
1428
1429
1430 void wxTranslations::SetLanguage(wxLanguage lang)
1431 {
1432 if ( lang == wxLANGUAGE_DEFAULT )
1433 SetLanguage("");
1434 else
1435 SetLanguage(wxLocale::GetLanguageCanonicalName(lang));
1436 }
1437
1438 void wxTranslations::SetLanguage(const wxString& lang)
1439 {
1440 m_lang = lang;
1441 }
1442
1443
1444 wxArrayString wxTranslations::GetAvailableTranslations(const wxString& domain) const
1445 {
1446 wxCHECK_MSG( m_loader, wxArrayString(), "loader can't be NULL" );
1447
1448 return m_loader->GetAvailableTranslations(domain);
1449 }
1450
1451
1452 bool wxTranslations::AddStdCatalog()
1453 {
1454 if ( !AddCatalog(wxS("wxstd")) )
1455 return false;
1456
1457 // there may be a catalog with toolkit specific overrides, it is not
1458 // an error if this does not exist
1459 wxString port(wxPlatformInfo::Get().GetPortIdName());
1460 if ( !port.empty() )
1461 {
1462 AddCatalog(port.BeforeFirst(wxS('/')).MakeLower());
1463 }
1464
1465 return true;
1466 }
1467
1468
1469 bool wxTranslations::AddCatalog(const wxString& domain)
1470 {
1471 return AddCatalog(domain, wxLANGUAGE_ENGLISH_US);
1472 }
1473
1474 #if !wxUSE_UNICODE
1475 bool wxTranslations::AddCatalog(const wxString& domain,
1476 wxLanguage msgIdLanguage,
1477 const wxString& msgIdCharset)
1478 {
1479 gs_msgIdCharset[domain] = msgIdCharset;
1480 return AddCatalog(domain, msgIdLanguage);
1481 }
1482 #endif // !wxUSE_UNICODE
1483
1484 bool wxTranslations::AddCatalog(const wxString& domain,
1485 wxLanguage msgIdLanguage)
1486 {
1487 const wxString msgIdLang = wxLocale::GetLanguageCanonicalName(msgIdLanguage);
1488 const wxString domain_lang = GetBestTranslation(domain, msgIdLang);
1489
1490 if ( domain_lang.empty() )
1491 {
1492 wxLogTrace(TRACE_I18N,
1493 wxS("no suitable translation for domain '%s' found"),
1494 domain);
1495 return false;
1496 }
1497
1498 wxLogTrace(TRACE_I18N,
1499 wxS("adding '%s' translation for domain '%s' (msgid language '%s')"),
1500 domain_lang, domain, msgIdLang);
1501
1502 // It is OK to not load catalog if the msgid language and m_language match,
1503 // in which case we can directly display the texts embedded in program's
1504 // source code:
1505 if ( msgIdLang == domain_lang )
1506 return true;
1507
1508 return LoadCatalog(domain, domain_lang);
1509 }
1510
1511
1512 bool wxTranslations::LoadCatalog(const wxString& domain, const wxString& lang)
1513 {
1514 m_loader->GetAvailableTranslations(domain);
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 pathPrefix = wxFileName(prefix, lang).GetFullPath();
1768
1769 wxString searchPath;
1770 searchPath.reserve(4*pathPrefix.length());
1771 searchPath << pathPrefix << wxFILE_SEP_PATH << "LC_MESSAGES" << wxPATH_SEP
1772 << prefix << wxFILE_SEP_PATH << wxPATH_SEP
1773 << pathPrefix;
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(),
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