]> git.saurik.com Git - wxWidgets.git/blame - src/common/translation.cpp
Allow absent checkbox when transferring dimension data
[wxWidgets.git] / src / common / translation.cpp
CommitLineData
ea144923
VS
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
ea144923
VS
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
5e306229
VS
43#include "wx/arrstr.h"
44#include "wx/dir.h"
ea144923
VS
45#include "wx/file.h"
46#include "wx/filename.h"
47#include "wx/tokenzr.h"
48#include "wx/fontmap.h"
ea144923 49#include "wx/stdpaths.h"
d2740de5 50#include "wx/private/threadinfo.h"
ea144923 51
d98a58c5 52#ifdef __WINDOWS__
01f953ef
VS
53 #include "wx/dynlib.h"
54 #include "wx/scopedarray.h"
5e306229 55 #include "wx/msw/wrapwin.h"
c4dae34d 56 #include "wx/msw/missing.h"
5e306229 57#endif
01f953ef
VS
58#ifdef __WXOSX__
59 #include "wx/osx/core/cfstring.h"
60 #include <CoreFoundation/CFBundle.h>
61 #include <CoreFoundation/CFLocale.h>
62#endif
5e306229 63
ea144923
VS
64// ----------------------------------------------------------------------------
65// simple types
66// ----------------------------------------------------------------------------
67
68typedef wxUint32 size_t32;
69
70// ----------------------------------------------------------------------------
71// constants
72// ----------------------------------------------------------------------------
73
74// magic number identifying the .mo format file
75const size_t32 MSGCATALOG_MAGIC = 0x950412de;
76const size_t32 MSGCATALOG_MAGIC_SW = 0xde120495;
77
78#define TRACE_I18N wxS("i18n")
79
ea144923
VS
80// ============================================================================
81// implementation
82// ============================================================================
83
611bed35
VS
84namespace
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.
91wxStringToStringHashMap gs_msgIdCharset;
92#endif
93
01f953ef
VS
94// ----------------------------------------------------------------------------
95// Platform specific helpers
96// ----------------------------------------------------------------------------
97
98void LogTraceArray(const char *prefix, const wxArrayString& arr)
99{
100 wxLogTrace(TRACE_I18N, "%s: [%s]", prefix, wxJoin(arr, ','));
101}
102
a66bf817 103void LogTraceLargeArray(const wxString& prefix, const wxArrayString& arr)
92d609f4
VS
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
01f953ef
VS
110// Use locale-based detection as a fallback
111wxString 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
120wxString 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
616ae1e8
VS
158 for ( wxArrayString::const_iterator j = preferred.begin();
159 j != preferred.end();
160 ++j )
01f953ef 161 {
616ae1e8 162 wxString lang(*j);
01f953ef 163 lang.Replace("-", "_");
e0c22690 164 if ( available.Index(lang, /*bCase=*/false) != wxNOT_FOUND )
01f953ef
VS
165 return lang;
166 size_t pos = lang.find('_');
167 if ( pos != wxString::npos )
168 {
169 lang = lang.substr(0, pos);
e0c22690 170 if ( available.Index(lang, /*bCase=*/false) != wxNOT_FOUND )
01f953ef
VS
171 return lang;
172 }
173 }
174 }
175 }
176 }
177
178 return GetPreferredUILanguageFallback(available);
179}
180
181#elif defined(__WXOSX__)
182
183void 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
196wxString 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
611bed35
VS
243} // anonymous namespace
244
ea144923
VS
245// ----------------------------------------------------------------------------
246// Plural forms parser
247// ----------------------------------------------------------------------------
248
249/*
250 Simplified Grammar
251
252Expression:
253 LogicalOrExpression '?' Expression ':' Expression
254 LogicalOrExpression
255
256LogicalOrExpression:
257 LogicalAndExpression "||" LogicalOrExpression // to (a || b) || c
258 LogicalAndExpression
259
260LogicalAndExpression:
261 EqualityExpression "&&" LogicalAndExpression // to (a && b) && c
262 EqualityExpression
263
264EqualityExpression:
265 RelationalExpression "==" RelationalExperession
266 RelationalExpression "!=" RelationalExperession
267 RelationalExpression
268
269RelationalExpression:
270 MultiplicativeExpression '>' MultiplicativeExpression
271 MultiplicativeExpression '<' MultiplicativeExpression
272 MultiplicativeExpression ">=" MultiplicativeExpression
273 MultiplicativeExpression "<=" MultiplicativeExpression
274 MultiplicativeExpression
275
276MultiplicativeExpression:
277 PmExpression '%' PmExpression
278 PmExpression
279
280PmExpression:
281 N
282 Number
283 '(' Expression ')'
284*/
285
286class wxPluralFormsToken
287{
288public:
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; }
7d1214cd 298 void setType(Type t) { m_type = t; }
ea144923
VS
299 // for T_NUMBER only
300 typedef int Number;
301 Number number() const { return m_number; }
302 void setNumber(Number num) { m_number = num; }
303private:
304 Type m_type;
305 Number m_number;
306};
307
308
309class wxPluralFormsScanner
310{
311public:
312 wxPluralFormsScanner(const char* s);
313 const wxPluralFormsToken& token() const { return m_token; }
314 bool nextToken(); // returns false if error
315private:
316 const char* m_s;
317 wxPluralFormsToken m_token;
318};
319
320wxPluralFormsScanner::wxPluralFormsScanner(const char* s) : m_s(s)
321{
322 nextToken();
323}
324
325bool 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
453class wxPluralFormsNode;
454
455// NB: Can't use wxDEFINE_SCOPED_PTR_TYPE because wxPluralFormsNode is not
456// fully defined yet:
457class wxPluralFormsNodePtr
458{
459public:
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
468private:
469 wxPluralFormsNode *m_p;
470};
471
472class wxPluralFormsNode
473{
474public:
7d1214cd 475 wxPluralFormsNode(const wxPluralFormsToken& t) : m_token(t) {}
ea144923 476 const wxPluralFormsToken& token() const { return m_token; }
dfbb5eff 477 const wxPluralFormsNode* node(unsigned i) const
ea144923 478 { return m_nodes[i].get(); }
dfbb5eff
VS
479 void setNode(unsigned i, wxPluralFormsNode* n);
480 wxPluralFormsNode* releaseNode(unsigned i);
ea144923
VS
481 wxPluralFormsToken::Number evaluate(wxPluralFormsToken::Number n) const;
482
483private:
484 wxPluralFormsToken m_token;
485 wxPluralFormsNodePtr m_nodes[3];
486};
487
488wxPluralFormsNodePtr::~wxPluralFormsNodePtr()
489{
490 delete m_p;
491}
492wxPluralFormsNode* wxPluralFormsNodePtr::release()
493{
494 wxPluralFormsNode *p = m_p;
495 m_p = NULL;
496 return p;
497}
498void wxPluralFormsNodePtr::reset(wxPluralFormsNode *p)
499{
500 if (p != m_p)
501 {
502 delete m_p;
503 m_p = p;
504 }
505}
506
507
dfbb5eff 508void wxPluralFormsNode::setNode(unsigned i, wxPluralFormsNode* n)
ea144923
VS
509{
510 m_nodes[i].reset(n);
511}
512
dfbb5eff 513wxPluralFormsNode* wxPluralFormsNode::releaseNode(unsigned i)
ea144923
VS
514{
515 return m_nodes[i].release();
516}
517
518wxPluralFormsToken::Number
519wxPluralFormsNode::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
568class wxPluralFormsCalculator
569{
570public:
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
585private:
586 wxPluralFormsToken::Number m_nplurals;
587 wxPluralFormsNodePtr m_plural;
588};
589
611bed35 590wxDEFINE_SCOPED_PTR(wxPluralFormsCalculator, wxPluralFormsCalculatorPtr)
ea144923
VS
591
592void wxPluralFormsCalculator::init(wxPluralFormsToken::Number nplurals,
593 wxPluralFormsNode* plural)
594{
595 m_nplurals = nplurals;
596 m_plural.reset(plural);
597}
598
599int 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
614class wxPluralFormsParser
615{
616public:
617 wxPluralFormsParser(wxPluralFormsScanner& scanner) : m_scanner(scanner) {}
618 bool parse(wxPluralFormsCalculator& rCalculator);
619
620private:
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
636bool 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
676wxPluralFormsNode* 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
691const wxPluralFormsToken& wxPluralFormsParser::token() const
692{
693 return m_scanner.token();
694}
695
696bool wxPluralFormsParser::nextToken()
697{
698 if (!m_scanner.nextToken())
699 return false;
700 return true;
701}
702
703wxPluralFormsNode* 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
742wxPluralFormsNode*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
778wxPluralFormsNode* 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
816wxPluralFormsNode* 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
842wxPluralFormsNode* 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
870wxPluralFormsNode* 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
895wxPluralFormsNode* 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
934wxPluralFormsCalculator* 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
ea144923
VS
961class wxMsgCatalogFile
962{
963public:
611bed35 964 typedef wxScopedCharBuffer DataBuffer;
bc71c3cd 965
ea144923
VS
966 // ctor & dtor
967 wxMsgCatalogFile();
968 ~wxMsgCatalogFile();
969
970 // load the catalog from disk
bc71c3cd
VS
971 bool LoadFile(const wxString& filename,
972 wxPluralFormsCalculatorPtr& rPluralFormsCalculator);
973 bool LoadData(const DataBuffer& data,
974 wxPluralFormsCalculatorPtr& rPluralFormsCalculator);
ea144923
VS
975
976 // fills the hash with string-translation pairs
611bed35 977 bool FillHash(wxStringToStringHashMap& hash, const wxString& domain) const;
ea144923
VS
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
983private:
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
bc71c3cd 1006 DataBuffer m_data;
ea144923
VS
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
ea144923
VS
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);
bc71c3cd 1030 if ( ofsString + Swap(ent->nLen) > m_data.length())
ea144923
VS
1031 {
1032 return NULL;
1033 }
1034
bc71c3cd 1035 return m_data.data() + ofsString;
ea144923
VS
1036 }
1037
1038 bool m_bSwapped; // wrong endianness?
1039
1040 wxDECLARE_NO_COPY_CLASS(wxMsgCatalogFile);
1041};
1042
ea144923 1043// ----------------------------------------------------------------------------
37424888 1044// wxMsgCatalogFile class
ea144923
VS
1045// ----------------------------------------------------------------------------
1046
1047wxMsgCatalogFile::wxMsgCatalogFile()
1048{
1049}
1050
1051wxMsgCatalogFile::~wxMsgCatalogFile()
1052{
1053}
1054
1055// open disk file and read in it's contents
bc71c3cd
VS
1056bool wxMsgCatalogFile::LoadFile(const wxString& filename,
1057 wxPluralFormsCalculatorPtr& rPluralFormsCalculator)
ea144923
VS
1058{
1059 wxFile fileMsg(filename);
1060 if ( !fileMsg.IsOpened() )
1061 return false;
1062
4c51a665 1063 // get the file size (assume it is less than 4GB...)
ea144923
VS
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
bc71c3cd
VS
1071 wxMemoryBuffer filedata;
1072
ea144923 1073 // read the whole file in memory
bc71c3cd 1074 if ( fileMsg.Read(filedata.GetWriteBuf(nSize), nSize) != lenFile )
ea144923
VS
1075 return false;
1076
bc71c3cd
VS
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}
ea144923
VS
1092
1093
bc71c3cd
VS
1094bool wxMsgCatalogFile::LoadData(const DataBuffer& data,
1095 wxPluralFormsCalculatorPtr& rPluralFormsCalculator)
1096{
ea144923 1097 // examine header
bc71c3cd 1098 bool bValid = data.length() > sizeof(wxMsgCatalogHeader);
ea144923 1099
bc71c3cd 1100 const wxMsgCatalogHeader *pHeader = (wxMsgCatalogHeader *)data.data();
ea144923
VS
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
bc71c3cd 1111 wxLogWarning(_("Invalid message catalog."));
ea144923
VS
1112 return false;
1113 }
1114
bc71c3cd
VS
1115 m_data = data;
1116
ea144923
VS
1117 // initialize
1118 m_numStrings = Swap(pHeader->numStrings);
bc71c3cd 1119 m_pOrigTable = (wxMsgTableEntry *)(data.data() +
ea144923 1120 Swap(pHeader->ofsOrigTable));
bc71c3cd 1121 m_pTransTable = (wxMsgTableEntry *)(data.data() +
ea144923
VS
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
59859d3d 1145 m_charset.clear();
ea144923
VS
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
611bed35
VS
1184bool wxMsgCatalogFile::FillHash(wxStringToStringHashMap& hash,
1185 const wxString& domain) const
ea144923 1186{
611bed35 1187 wxUnusedVar(domain); // silence warning in Unicode build
ea144923
VS
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
611bed35
VS
1215 wxString msgIdCharset = gs_msgIdCharset[domain];
1216
ea144923
VS
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
1292wxMsgCatalog::~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
611bed35
VS
1308/* static */
1309wxMsgCatalog *wxMsgCatalog::CreateFromFile(const wxString& filename,
1310 const wxString& domain)
ea144923 1311{
611bed35 1312 wxScopedPtr<wxMsgCatalog> cat(new wxMsgCatalog(domain));
ea144923 1313
611bed35 1314 wxMsgCatalogFile file;
ea144923 1315
611bed35
VS
1316 if ( !file.LoadFile(filename, cat->m_pluralFormsCalculator) )
1317 return NULL;
bc71c3cd 1318
611bed35
VS
1319 if ( !file.FillHash(cat->m_messages, domain) )
1320 return NULL;
bc71c3cd 1321
611bed35 1322 return cat.release();
bc71c3cd
VS
1323}
1324
611bed35
VS
1325/* static */
1326wxMsgCatalog *wxMsgCatalog::CreateFromData(const wxScopedCharBuffer& data,
1327 const wxString& domain)
bc71c3cd 1328{
611bed35 1329 wxScopedPtr<wxMsgCatalog> cat(new wxMsgCatalog(domain));
bc71c3cd 1330
611bed35 1331 wxMsgCatalogFile file;
bc71c3cd 1332
611bed35
VS
1333 if ( !file.LoadData(data, cat->m_pluralFormsCalculator) )
1334 return NULL;
ea144923 1335
611bed35
VS
1336 if ( !file.FillHash(cat->m_messages, domain) )
1337 return NULL;
ea144923 1338
611bed35 1339 return cat.release();
ea144923
VS
1340}
1341
dfbb5eff 1342const wxString *wxMsgCatalog::GetString(const wxString& str, unsigned n) const
ea144923
VS
1343{
1344 int index = 0;
dfbb5eff 1345 if (n != UINT_MAX)
ea144923
VS
1346 {
1347 index = m_pluralFormsCalculator->evaluate(n);
1348 }
611bed35 1349 wxStringToStringHashMap::const_iterator i;
ea144923
VS
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
1372namespace
1373{
1374
1375wxTranslations *gs_translations = NULL;
1376bool gs_translationsOwned = false;
1377
1378} // anonymous namespace
1379
1380
1381/*static*/
1382wxTranslations *wxTranslations::Get()
1383{
1384 return gs_translations;
1385}
1386
1387/*static*/
1388void 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*/
1397void wxTranslations::SetNonOwned(wxTranslations *t)
1398{
1399 if ( gs_translationsOwned )
1400 delete gs_translations;
1401 gs_translations = t;
1402 gs_translationsOwned = false;
1403}
1404
1405
1406wxTranslations::wxTranslations()
1407{
1408 m_pMsgCat = NULL;
1409 m_loader = new wxFileTranslationsLoader;
1410}
1411
1412
1413wxTranslations::~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
1428void 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
1437void wxTranslations::SetLanguage(wxLanguage lang)
1438{
1439 if ( lang == wxLANGUAGE_DEFAULT )
1440 SetLanguage("");
1441 else
1442 SetLanguage(wxLocale::GetLanguageCanonicalName(lang));
1443}
1444
1445void wxTranslations::SetLanguage(const wxString& lang)
1446{
1447 m_lang = lang;
1448}
1449
1450
5e306229
VS
1451wxArrayString wxTranslations::GetAvailableTranslations(const wxString& domain) const
1452{
4c83615f 1453 wxCHECK_MSG( m_loader, wxArrayString(), "loader can't be NULL" );
5e306229
VS
1454
1455 return m_loader->GetAvailableTranslations(domain);
1456}
1457
1458
ea144923
VS
1459bool 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
1476bool wxTranslations::AddCatalog(const wxString& domain)
1477{
1478 return AddCatalog(domain, wxLANGUAGE_ENGLISH_US);
1479}
1480
1481#if !wxUSE_UNICODE
1482bool wxTranslations::AddCatalog(const wxString& domain,
1483 wxLanguage msgIdLanguage,
1484 const wxString& msgIdCharset)
1485{
611bed35 1486 gs_msgIdCharset[domain] = msgIdCharset;
ea144923
VS
1487 return AddCatalog(domain, msgIdLanguage);
1488}
1489#endif // !wxUSE_UNICODE
1490
1491bool wxTranslations::AddCatalog(const wxString& domain,
1492 wxLanguage msgIdLanguage)
1493{
1494 const wxString msgIdLang = wxLocale::GetLanguageCanonicalName(msgIdLanguage);
01f953ef 1495 const wxString domain_lang = GetBestTranslation(domain, msgIdLang);
ea144923
VS
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
a255c995 1509 return LoadCatalog(domain, domain_lang, msgIdLang);
076c0a8e
VS
1510}
1511
1512
a255c995 1513bool wxTranslations::LoadCatalog(const wxString& domain, const wxString& lang, const wxString& msgIdLang)
076c0a8e 1514{
ea144923 1515 wxCHECK_MSG( m_loader, false, "loader can't be NULL" );
076c0a8e 1516
611bed35
VS
1517 wxMsgCatalog *cat = NULL;
1518
076c0a8e
VS
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
611bed35 1530 cat = m_loader->LoadCatalog(domain, fullname);
076c0a8e
VS
1531 }
1532#endif // wxUSE_FONTMAP
1533
611bed35 1534 if ( !cat )
076c0a8e 1535 {
611bed35
VS
1536 // Next try: use the provided name language name:
1537 cat = m_loader->LoadCatalog(domain, lang);
076c0a8e
VS
1538 }
1539
611bed35 1540 if ( !cat )
bc71c3cd 1541 {
611bed35
VS
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);
bc71c3cd
VS
1548 }
1549
a255c995
VS
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
611bed35
VS
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;
ea144923 1565
611bed35
VS
1566 return true;
1567 }
1568 else
ea144923 1569 {
611bed35
VS
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);
ea144923
VS
1574 return false;
1575 }
611bed35 1576}
ea144923 1577
611bed35
VS
1578// check if the given catalog is loaded
1579bool wxTranslations::IsLoaded(const wxString& domain) const
1580{
1581 return FindCatalog(domain) != NULL;
ea144923
VS
1582}
1583
01f953ef
VS
1584wxString wxTranslations::GetBestTranslation(const wxString& domain,
1585 wxLanguage msgIdLanguage)
1586{
1587 const wxString lang = wxLocale::GetLanguageCanonicalName(msgIdLanguage);
1588 return GetBestTranslation(domain, lang);
1589}
ea144923 1590
01f953ef
VS
1591wxString wxTranslations::GetBestTranslation(const wxString& domain,
1592 const wxString& msgIdLanguage)
ea144923
VS
1593{
1594 // explicitly set language should always be respected
1595 if ( !m_lang.empty() )
1596 return m_lang;
1597
01f953ef
VS
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;
ea144923
VS
1608}
1609
1610
ea144923
VS
1611/* static */
1612const wxString& wxTranslations::GetUntranslatedString(const wxString& str)
1613{
d2740de5 1614 wxLocaleUntranslatedStrings& strings = wxThreadInfo.untranslatedStrings;
ea144923 1615
d2740de5
VS
1616 wxLocaleUntranslatedStrings::iterator i = strings.find(str);
1617 if ( i == strings.end() )
1618 return *strings.insert(str).first;
ea144923
VS
1619
1620 return *i;
1621}
1622
1623
f2959820
VS
1624const wxString *wxTranslations::GetTranslatedString(const wxString& origString,
1625 const wxString& domain) const
ea144923 1626{
f2959820 1627 return GetTranslatedString(origString, UINT_MAX, domain);
ea144923
VS
1628}
1629
f2959820
VS
1630const wxString *wxTranslations::GetTranslatedString(const wxString& origString,
1631 unsigned n,
1632 const wxString& domain) const
ea144923
VS
1633{
1634 if ( origString.empty() )
f2959820 1635 return NULL;
ea144923
VS
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,
25859335
VZ
1666 (n != UINT_MAX ? wxString::Format("[%ld]", (long)n) : wxString()),
1667 (!domain.empty() ? wxString::Format("domain '%s' ", domain) : wxString()),
ea144923
VS
1668 m_lang
1669 );
ea144923
VS
1670 }
1671
f2959820 1672 return trans;
ea144923
VS
1673}
1674
1675
1676wxString 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
dfbb5eff 1693 trans = pMsgCat->GetString(wxEmptyString, UINT_MAX);
ea144923
VS
1694 }
1695 else
1696 {
1697 // search in all domains
1698 for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext )
1699 {
dfbb5eff 1700 trans = pMsgCat->GetString(wxEmptyString, UINT_MAX);
ea144923
VS
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
1726wxMsgCatalog *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
1743namespace
1744{
1745
1746// the list of the directories to search for message catalog files
1747wxArrayString gs_searchPrefixes;
1748
1749// return the directories to search for message catalogs under the given
1750// prefix, separated by wxPATH_SEP
1751wxString GetMsgCatalogSubdirs(const wxString& prefix, const wxString& lang)
1752{
1753 // Search first in Unix-standard prefix/lang/LC_MESSAGES, then in
ea160f01 1754 // prefix/lang.
ea144923
VS
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
d3def4c6 1764 const wxString prefixAndLang = wxFileName(prefix, lang).GetFullPath();
ea144923
VS
1765
1766 wxString searchPath;
d3def4c6 1767 searchPath.reserve(4*prefixAndLang.length());
ea160f01
VS
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
d3def4c6 1775 << prefixAndLang << wxPATH_SEP
ea160f01 1776 ;
ea144923
VS
1777
1778 return searchPath;
1779}
1780
5e306229
VS
1781bool 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
ea160f01 1789wxArrayString GetSearchPrefixes()
ea144923 1790{
ea144923 1791 wxArrayString paths;
ea144923 1792
5e306229
VS
1793 // first take the entries explicitly added by the program
1794 paths = gs_searchPrefixes;
ea144923
VS
1795
1796#if wxUSE_STDPATHS
1797 // then look in the standard location
5e306229 1798 wxString stdp;
ea160f01 1799 stdp = wxStandardPaths::Get().GetResourcesDir();
ea144923
VS
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 {
5e306229 1811 const wxString lcp = pszLcPath;
ea144923
VS
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 {
5e306229 1820 wxp += wxS("/share/locale");
ea144923
VS
1821 if ( paths.Index(wxp) == wxNOT_FOUND )
1822 paths.Add(wxp);
1823 }
1824#endif // __UNIX__
1825
5e306229
VS
1826 return paths;
1827}
ea144923 1828
5e306229
VS
1829// construct the search path for the given language
1830wxString GetFullSearchPath(const wxString& lang)
1831{
ea144923
VS
1832 wxString searchPath;
1833 searchPath.reserve(500);
5e306229 1834
ea160f01 1835 const wxArrayString prefixes = GetSearchPrefixes();
5e306229
VS
1836
1837 for ( wxArrayString::const_iterator i = prefixes.begin();
1838 i != prefixes.end();
1839 ++i )
ea144923 1840 {
5e306229
VS
1841 const wxString p = GetMsgCatalogSubdirs(*i, lang);
1842
1843 if ( !searchPath.empty() )
ea144923 1844 searchPath += wxPATH_SEP;
5e306229 1845 searchPath += p;
ea144923
VS
1846 }
1847
1848 return searchPath;
1849}
1850
1851} // anonymous namespace
1852
1853
1854void 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
611bed35
VS
1864wxMsgCatalog *wxFileTranslationsLoader::LoadCatalog(const wxString& domain,
1865 const wxString& lang)
ea144923 1866{
076c0a8e 1867 wxString searchPath = GetFullSearchPath(lang);
ea144923 1868
92d609f4
VS
1869 LogTraceLargeArray
1870 (
1871 wxString::Format("looking for \"%s.mo\" in search path", domain),
1872 wxSplit(searchPath, wxPATH_SEP[0])
1873 );
ea144923
VS
1874
1875 wxFileName fn(domain);
1876 fn.SetExt(wxS("mo"));
1877
1878 wxString strFullName;
1879 if ( !wxFindFileInPath(&strFullName, searchPath, fn.GetFullPath()) )
611bed35 1880 return NULL;
ea144923
VS
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
611bed35 1886 return wxMsgCatalog::CreateFromFile(strFullName, domain);
ea144923
VS
1887}
1888
bc71c3cd 1889
5e306229
VS
1890wxArrayString wxFileTranslationsLoader::GetAvailableTranslations(const wxString& domain) const
1891{
1892 wxArrayString langs;
1893 const wxArrayString prefixes = GetSearchPrefixes();
1894
92d609f4
VS
1895 LogTraceLargeArray
1896 (
1897 wxString::Format("looking for available translations of \"%s\" in search path", domain),
1898 prefixes
1899 );
5e306229
VS
1900
1901 for ( wxArrayString::const_iterator i = prefixes.begin();
1902 i != prefixes.end();
1903 ++i )
1904 {
6636ef8d 1905 if ( i->empty() )
1662942c 1906 continue;
5e306229
VS
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,
92d609f4
VS
1926 "found %s translation of \"%s\" in %s",
1927 lang, domain, langdir);
5e306229
VS
1928 langs.push_back(lang);
1929 }
1930 }
1931 }
1932
1933 return langs;
1934}
1935
1936
bc71c3cd
VS
1937// ----------------------------------------------------------------------------
1938// wxResourceTranslationsLoader
1939// ----------------------------------------------------------------------------
1940
1941#ifdef __WINDOWS__
5e306229 1942
611bed35
VS
1943wxMsgCatalog *wxResourceTranslationsLoader::LoadCatalog(const wxString& domain,
1944 const wxString& lang)
bc71c3cd 1945{
bc71c3cd
VS
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,
f45f43ca 1953 GetResourceType().t_str(),
bc71c3cd 1954 GetModule()) )
12f6edff 1955 return NULL;
bc71c3cd
VS
1956
1957 wxLogTrace(TRACE_I18N,
1958 "Using catalog from Windows resource \"%s\".", resname);
1959
611bed35
VS
1960 wxMsgCatalog *cat = wxMsgCatalog::CreateFromData(
1961 wxCharBuffer::CreateNonOwned(static_cast<const char*>(mo_data), mo_size),
1962 domain);
1963
1964 if ( !cat )
4cfee1ef 1965 {
bc71c3cd 1966 wxLogWarning(_("Resource '%s' is not a valid message catalog."), resname);
4cfee1ef 1967 }
bc71c3cd 1968
611bed35 1969 return cat;
bc71c3cd 1970}
5e306229
VS
1971
1972namespace
1973{
1974
1975struct EnumCallbackData
1976{
1977 wxString prefix;
1978 wxArrayString langs;
1979};
1980
1981BOOL 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
2001wxArrayString 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(),
a49b09f5 2008 GetResourceType().t_str(),
5e306229
VS
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 )
4cfee1ef 2014 {
5e306229 2015 wxLogSysError(_("Couldn't enumerate translations"));
4cfee1ef 2016 }
5e306229
VS
2017 }
2018
2019 return data.langs;
2020}
2021
bc71c3cd
VS
2022#endif // __WINDOWS__
2023
2024
ea144923
VS
2025// ----------------------------------------------------------------------------
2026// wxTranslationsModule module (for destruction of gs_translations)
2027// ----------------------------------------------------------------------------
2028
2029class 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
2049IMPLEMENT_DYNAMIC_CLASS(wxTranslationsModule, wxModule)
2050
2051#endif // wxUSE_INTL