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