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