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