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