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)
9 // Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
10 // Licence: wxWindows licence
11 /////////////////////////////////////////////////////////////////////////////
13 // ============================================================================
15 // ============================================================================
17 // ----------------------------------------------------------------------------
19 // ----------------------------------------------------------------------------
21 // For compilers that support precompilation, includes "wx.h".
22 #include "wx/wxprec.h"
31 #include "wx/dynarray.h"
32 #include "wx/string.h"
36 #include "wx/hashmap.h"
37 #include "wx/module.h"
44 #include "wx/arrstr.h"
47 #include "wx/filename.h"
48 #include "wx/tokenzr.h"
49 #include "wx/fontmap.h"
50 #include "wx/stdpaths.h"
51 #include "wx/hashset.h"
54 #include "wx/dynlib.h"
55 #include "wx/scopedarray.h"
56 #include "wx/msw/wrapwin.h"
57 #include "wx/msw/missing.h"
60 #include "wx/osx/core/cfstring.h"
61 #include <CoreFoundation/CFBundle.h>
62 #include <CoreFoundation/CFLocale.h>
65 // ----------------------------------------------------------------------------
67 // ----------------------------------------------------------------------------
69 typedef wxUint32 size_t32
;
71 // ----------------------------------------------------------------------------
73 // ----------------------------------------------------------------------------
75 // magic number identifying the .mo format file
76 const size_t32 MSGCATALOG_MAGIC
= 0x950412de;
77 const size_t32 MSGCATALOG_MAGIC_SW
= 0xde120495;
79 #define TRACE_I18N wxS("i18n")
81 // ============================================================================
83 // ============================================================================
89 // We need to keep track of (char*) msgids in non-Unicode legacy builds. Instead
90 // of making the public wxMsgCatalog and wxTranslationsLoader APIs ugly, we
91 // store them in this global map.
92 wxStringToStringHashMap gs_msgIdCharset
;
95 // ----------------------------------------------------------------------------
96 // Platform specific helpers
97 // ----------------------------------------------------------------------------
99 void LogTraceArray(const char *prefix
, const wxArrayString
& arr
)
101 wxLogTrace(TRACE_I18N
, "%s: [%s]", prefix
, wxJoin(arr
, ','));
104 // Use locale-based detection as a fallback
105 wxString
GetPreferredUILanguageFallback(const wxArrayString
& WXUNUSED(available
))
107 const wxString lang
= wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage());
108 wxLogTrace(TRACE_I18N
, " - obtained best language from locale: %s", lang
);
114 wxString
GetPreferredUILanguage(const wxArrayString
& available
)
116 typedef BOOL (WINAPI
*GetUserPreferredUILanguages_t
)(DWORD
, PULONG
, PWSTR
, PULONG
);
117 static GetUserPreferredUILanguages_t s_pfnGetUserPreferredUILanguages
= NULL
;
118 static bool s_initDone
= false;
121 wxLoadedDLL
dllKernel32("kernel32.dll");
122 wxDL_INIT_FUNC(s_pfn
, GetUserPreferredUILanguages
, dllKernel32
);
126 if ( s_pfnGetUserPreferredUILanguages
)
129 ULONG bufferSize
= 0;
130 if ( (*s_pfnGetUserPreferredUILanguages
)(MUI_LANGUAGE_NAME
,
135 wxScopedArray
<WCHAR
> langs(new WCHAR
[bufferSize
]);
136 if ( (*s_pfnGetUserPreferredUILanguages
)(MUI_LANGUAGE_NAME
,
141 wxArrayString preferred
;
143 WCHAR
*buf
= langs
.get();
144 for ( unsigned i
= 0; i
< numLangs
; i
++ )
146 const wxString
lang(buf
);
147 preferred
.push_back(lang
);
148 buf
+= lang
.length() + 1;
150 LogTraceArray(" - system preferred languages", preferred
);
152 for ( wxArrayString::const_iterator j
= preferred
.begin();
153 j
!= preferred
.end();
157 lang
.Replace("-", "_");
158 if ( available
.Index(lang
, /*bCase=*/false) != wxNOT_FOUND
)
160 size_t pos
= lang
.find('_');
161 if ( pos
!= wxString::npos
)
163 lang
= lang
.substr(0, pos
);
164 if ( available
.Index(lang
, /*bCase=*/false) != wxNOT_FOUND
)
172 return GetPreferredUILanguageFallback(available
);
175 #elif defined(__WXOSX__)
177 void LogTraceArray(const char *prefix
, CFArrayRef arr
)
180 const unsigned count
= CFArrayGetCount(arr
);
183 s
+= wxCFStringRef::AsString((CFStringRef
)CFArrayGetValueAtIndex(arr
, 0));
184 for ( unsigned i
= 1 ; i
< count
; i
++ )
185 s
+= "," + wxCFStringRef::AsString((CFStringRef
)CFArrayGetValueAtIndex(arr
, i
));
187 wxLogTrace(TRACE_I18N
, "%s: [%s]", prefix
, s
);
190 wxString
GetPreferredUILanguage(const wxArrayString
& available
)
192 wxStringToStringHashMap availableNormalized
;
193 wxCFRef
<CFMutableArrayRef
> availableArr(
194 CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
));
196 for ( wxArrayString::const_iterator i
= available
.begin();
197 i
!= available
.end();
201 wxCFStringRef
code_wx(*i
);
202 wxCFStringRef
code_norm(
203 CFLocaleCreateCanonicalLanguageIdentifierFromString(kCFAllocatorDefault
, code_wx
));
204 CFArrayAppendValue(availableArr
, code_norm
);
205 availableNormalized
[code_norm
.AsString()] = *i
;
207 LogTraceArray(" - normalized available list", availableArr
);
209 wxCFRef
<CFArrayRef
> prefArr(
210 CFBundleCopyLocalizationsForPreferences(availableArr
, NULL
));
211 LogTraceArray(" - system preferred languages", prefArr
);
213 unsigned prefArrLength
= CFArrayGetCount(prefArr
);
214 if ( prefArrLength
> 0 )
216 // Lookup the name in 'available' by index -- we need to get the
217 // original value corresponding to the normalized one chosen.
218 wxString
lang(wxCFStringRef::AsString((CFStringRef
)CFArrayGetValueAtIndex(prefArr
, 0)));
219 wxStringToStringHashMap::const_iterator i
= availableNormalized
.find(lang
);
220 if ( i
== availableNormalized
.end() )
226 return GetPreferredUILanguageFallback(available
);
231 // On Unix, there's just one language=locale setting, so we should always
233 #define GetPreferredUILanguage GetPreferredUILanguageFallback
237 } // anonymous namespace
239 // ----------------------------------------------------------------------------
240 // Plural forms parser
241 // ----------------------------------------------------------------------------
247 LogicalOrExpression '?' Expression ':' Expression
251 LogicalAndExpression "||" LogicalOrExpression // to (a || b) || c
254 LogicalAndExpression:
255 EqualityExpression "&&" LogicalAndExpression // to (a && b) && c
259 RelationalExpression "==" RelationalExperession
260 RelationalExpression "!=" RelationalExperession
263 RelationalExpression:
264 MultiplicativeExpression '>' MultiplicativeExpression
265 MultiplicativeExpression '<' MultiplicativeExpression
266 MultiplicativeExpression ">=" MultiplicativeExpression
267 MultiplicativeExpression "<=" MultiplicativeExpression
268 MultiplicativeExpression
270 MultiplicativeExpression:
271 PmExpression '%' PmExpression
280 class wxPluralFormsToken
285 T_ERROR
, T_EOF
, T_NUMBER
, T_N
, T_PLURAL
, T_NPLURALS
, T_EQUAL
, T_ASSIGN
,
286 T_GREATER
, T_GREATER_OR_EQUAL
, T_LESS
, T_LESS_OR_EQUAL
,
287 T_REMINDER
, T_NOT_EQUAL
,
288 T_LOGICAL_AND
, T_LOGICAL_OR
, T_QUESTION
, T_COLON
, T_SEMICOLON
,
289 T_LEFT_BRACKET
, T_RIGHT_BRACKET
291 Type
type() const { return m_type
; }
292 void setType(Type t
) { m_type
= t
; }
295 Number
number() const { return m_number
; }
296 void setNumber(Number num
) { m_number
= num
; }
303 class wxPluralFormsScanner
306 wxPluralFormsScanner(const char* s
);
307 const wxPluralFormsToken
& token() const { return m_token
; }
308 bool nextToken(); // returns false if error
311 wxPluralFormsToken m_token
;
314 wxPluralFormsScanner::wxPluralFormsScanner(const char* s
) : m_s(s
)
319 bool wxPluralFormsScanner::nextToken()
321 wxPluralFormsToken::Type type
= wxPluralFormsToken::T_ERROR
;
322 while (isspace((unsigned char) *m_s
))
328 type
= wxPluralFormsToken::T_EOF
;
330 else if (isdigit((unsigned char) *m_s
))
332 wxPluralFormsToken::Number number
= *m_s
++ - '0';
333 while (isdigit((unsigned char) *m_s
))
335 number
= number
* 10 + (*m_s
++ - '0');
337 m_token
.setNumber(number
);
338 type
= wxPluralFormsToken::T_NUMBER
;
340 else if (isalpha((unsigned char) *m_s
))
342 const char* begin
= m_s
++;
343 while (isalnum((unsigned char) *m_s
))
347 size_t size
= m_s
- begin
;
348 if (size
== 1 && memcmp(begin
, "n", size
) == 0)
350 type
= wxPluralFormsToken::T_N
;
352 else if (size
== 6 && memcmp(begin
, "plural", size
) == 0)
354 type
= wxPluralFormsToken::T_PLURAL
;
356 else if (size
== 8 && memcmp(begin
, "nplurals", size
) == 0)
358 type
= wxPluralFormsToken::T_NPLURALS
;
361 else if (*m_s
== '=')
367 type
= wxPluralFormsToken::T_EQUAL
;
371 type
= wxPluralFormsToken::T_ASSIGN
;
374 else if (*m_s
== '>')
380 type
= wxPluralFormsToken::T_GREATER_OR_EQUAL
;
384 type
= wxPluralFormsToken::T_GREATER
;
387 else if (*m_s
== '<')
393 type
= wxPluralFormsToken::T_LESS_OR_EQUAL
;
397 type
= wxPluralFormsToken::T_LESS
;
400 else if (*m_s
== '%')
403 type
= wxPluralFormsToken::T_REMINDER
;
405 else if (*m_s
== '!' && m_s
[1] == '=')
408 type
= wxPluralFormsToken::T_NOT_EQUAL
;
410 else if (*m_s
== '&' && m_s
[1] == '&')
413 type
= wxPluralFormsToken::T_LOGICAL_AND
;
415 else if (*m_s
== '|' && m_s
[1] == '|')
418 type
= wxPluralFormsToken::T_LOGICAL_OR
;
420 else if (*m_s
== '?')
423 type
= wxPluralFormsToken::T_QUESTION
;
425 else if (*m_s
== ':')
428 type
= wxPluralFormsToken::T_COLON
;
429 } else if (*m_s
== ';') {
431 type
= wxPluralFormsToken::T_SEMICOLON
;
433 else if (*m_s
== '(')
436 type
= wxPluralFormsToken::T_LEFT_BRACKET
;
438 else if (*m_s
== ')')
441 type
= wxPluralFormsToken::T_RIGHT_BRACKET
;
443 m_token
.setType(type
);
444 return type
!= wxPluralFormsToken::T_ERROR
;
447 class wxPluralFormsNode
;
449 // NB: Can't use wxDEFINE_SCOPED_PTR_TYPE because wxPluralFormsNode is not
450 // fully defined yet:
451 class wxPluralFormsNodePtr
454 wxPluralFormsNodePtr(wxPluralFormsNode
*p
= NULL
) : m_p(p
) {}
455 ~wxPluralFormsNodePtr();
456 wxPluralFormsNode
& operator*() const { return *m_p
; }
457 wxPluralFormsNode
* operator->() const { return m_p
; }
458 wxPluralFormsNode
* get() const { return m_p
; }
459 wxPluralFormsNode
* release();
460 void reset(wxPluralFormsNode
*p
);
463 wxPluralFormsNode
*m_p
;
466 class wxPluralFormsNode
469 wxPluralFormsNode(const wxPluralFormsToken
& t
) : m_token(t
) {}
470 const wxPluralFormsToken
& token() const { return m_token
; }
471 const wxPluralFormsNode
* node(unsigned i
) const
472 { return m_nodes
[i
].get(); }
473 void setNode(unsigned i
, wxPluralFormsNode
* n
);
474 wxPluralFormsNode
* releaseNode(unsigned i
);
475 wxPluralFormsToken::Number
evaluate(wxPluralFormsToken::Number n
) const;
478 wxPluralFormsToken m_token
;
479 wxPluralFormsNodePtr m_nodes
[3];
482 wxPluralFormsNodePtr::~wxPluralFormsNodePtr()
486 wxPluralFormsNode
* wxPluralFormsNodePtr::release()
488 wxPluralFormsNode
*p
= m_p
;
492 void wxPluralFormsNodePtr::reset(wxPluralFormsNode
*p
)
502 void wxPluralFormsNode::setNode(unsigned i
, wxPluralFormsNode
* n
)
507 wxPluralFormsNode
* wxPluralFormsNode::releaseNode(unsigned i
)
509 return m_nodes
[i
].release();
512 wxPluralFormsToken::Number
513 wxPluralFormsNode::evaluate(wxPluralFormsToken::Number n
) const
515 switch (token().type())
518 case wxPluralFormsToken::T_NUMBER
:
519 return token().number();
520 case wxPluralFormsToken::T_N
:
523 case wxPluralFormsToken::T_EQUAL
:
524 return node(0)->evaluate(n
) == node(1)->evaluate(n
);
525 case wxPluralFormsToken::T_NOT_EQUAL
:
526 return node(0)->evaluate(n
) != node(1)->evaluate(n
);
527 case wxPluralFormsToken::T_GREATER
:
528 return node(0)->evaluate(n
) > node(1)->evaluate(n
);
529 case wxPluralFormsToken::T_GREATER_OR_EQUAL
:
530 return node(0)->evaluate(n
) >= node(1)->evaluate(n
);
531 case wxPluralFormsToken::T_LESS
:
532 return node(0)->evaluate(n
) < node(1)->evaluate(n
);
533 case wxPluralFormsToken::T_LESS_OR_EQUAL
:
534 return node(0)->evaluate(n
) <= node(1)->evaluate(n
);
535 case wxPluralFormsToken::T_REMINDER
:
537 wxPluralFormsToken::Number number
= node(1)->evaluate(n
);
540 return node(0)->evaluate(n
) % number
;
547 case wxPluralFormsToken::T_LOGICAL_AND
:
548 return node(0)->evaluate(n
) && node(1)->evaluate(n
);
549 case wxPluralFormsToken::T_LOGICAL_OR
:
550 return node(0)->evaluate(n
) || node(1)->evaluate(n
);
552 case wxPluralFormsToken::T_QUESTION
:
553 return node(0)->evaluate(n
)
554 ? node(1)->evaluate(n
)
555 : node(2)->evaluate(n
);
562 class wxPluralFormsCalculator
565 wxPluralFormsCalculator() : m_nplurals(0), m_plural(0) {}
567 // input: number, returns msgstr index
568 int evaluate(int n
) const;
570 // input: text after "Plural-Forms:" (e.g. "nplurals=2; plural=(n != 1);"),
571 // if s == 0, creates default handler
572 // returns 0 if error
573 static wxPluralFormsCalculator
* make(const char* s
= 0);
575 ~wxPluralFormsCalculator() {}
577 void init(wxPluralFormsToken::Number nplurals
, wxPluralFormsNode
* plural
);
580 wxPluralFormsToken::Number m_nplurals
;
581 wxPluralFormsNodePtr m_plural
;
584 wxDEFINE_SCOPED_PTR(wxPluralFormsCalculator
, wxPluralFormsCalculatorPtr
)
586 void wxPluralFormsCalculator::init(wxPluralFormsToken::Number nplurals
,
587 wxPluralFormsNode
* plural
)
589 m_nplurals
= nplurals
;
590 m_plural
.reset(plural
);
593 int wxPluralFormsCalculator::evaluate(int n
) const
595 if (m_plural
.get() == 0)
599 wxPluralFormsToken::Number number
= m_plural
->evaluate(n
);
600 if (number
< 0 || number
> m_nplurals
)
608 class wxPluralFormsParser
611 wxPluralFormsParser(wxPluralFormsScanner
& scanner
) : m_scanner(scanner
) {}
612 bool parse(wxPluralFormsCalculator
& rCalculator
);
615 wxPluralFormsNode
* parsePlural();
616 // stops at T_SEMICOLON, returns 0 if error
617 wxPluralFormsScanner
& m_scanner
;
618 const wxPluralFormsToken
& token() const;
621 wxPluralFormsNode
* expression();
622 wxPluralFormsNode
* logicalOrExpression();
623 wxPluralFormsNode
* logicalAndExpression();
624 wxPluralFormsNode
* equalityExpression();
625 wxPluralFormsNode
* multiplicativeExpression();
626 wxPluralFormsNode
* relationalExpression();
627 wxPluralFormsNode
* pmExpression();
630 bool wxPluralFormsParser::parse(wxPluralFormsCalculator
& rCalculator
)
632 if (token().type() != wxPluralFormsToken::T_NPLURALS
)
636 if (token().type() != wxPluralFormsToken::T_ASSIGN
)
640 if (token().type() != wxPluralFormsToken::T_NUMBER
)
642 wxPluralFormsToken::Number nplurals
= token().number();
645 if (token().type() != wxPluralFormsToken::T_SEMICOLON
)
649 if (token().type() != wxPluralFormsToken::T_PLURAL
)
653 if (token().type() != wxPluralFormsToken::T_ASSIGN
)
657 wxPluralFormsNode
* plural
= parsePlural();
660 if (token().type() != wxPluralFormsToken::T_SEMICOLON
)
664 if (token().type() != wxPluralFormsToken::T_EOF
)
666 rCalculator
.init(nplurals
, plural
);
670 wxPluralFormsNode
* wxPluralFormsParser::parsePlural()
672 wxPluralFormsNode
* p
= expression();
677 wxPluralFormsNodePtr
n(p
);
678 if (token().type() != wxPluralFormsToken::T_SEMICOLON
)
685 const wxPluralFormsToken
& wxPluralFormsParser::token() const
687 return m_scanner
.token();
690 bool wxPluralFormsParser::nextToken()
692 if (!m_scanner
.nextToken())
697 wxPluralFormsNode
* wxPluralFormsParser::expression()
699 wxPluralFormsNode
* p
= logicalOrExpression();
702 wxPluralFormsNodePtr
n(p
);
703 if (token().type() == wxPluralFormsToken::T_QUESTION
)
705 wxPluralFormsNodePtr
qn(new wxPluralFormsNode(token()));
716 if (token().type() != wxPluralFormsToken::T_COLON
)
730 qn
->setNode(0, n
.release());
736 wxPluralFormsNode
*wxPluralFormsParser::logicalOrExpression()
738 wxPluralFormsNode
* p
= logicalAndExpression();
741 wxPluralFormsNodePtr
ln(p
);
742 if (token().type() == wxPluralFormsToken::T_LOGICAL_OR
)
744 wxPluralFormsNodePtr
un(new wxPluralFormsNode(token()));
749 p
= logicalOrExpression();
754 wxPluralFormsNodePtr
rn(p
); // right
755 if (rn
->token().type() == wxPluralFormsToken::T_LOGICAL_OR
)
757 // see logicalAndExpression comment
758 un
->setNode(0, ln
.release());
759 un
->setNode(1, rn
->releaseNode(0));
760 rn
->setNode(0, un
.release());
765 un
->setNode(0, ln
.release());
766 un
->setNode(1, rn
.release());
772 wxPluralFormsNode
* wxPluralFormsParser::logicalAndExpression()
774 wxPluralFormsNode
* p
= equalityExpression();
777 wxPluralFormsNodePtr
ln(p
); // left
778 if (token().type() == wxPluralFormsToken::T_LOGICAL_AND
)
780 wxPluralFormsNodePtr
un(new wxPluralFormsNode(token())); // up
785 p
= logicalAndExpression();
790 wxPluralFormsNodePtr
rn(p
); // right
791 if (rn
->token().type() == wxPluralFormsToken::T_LOGICAL_AND
)
793 // transform 1 && (2 && 3) -> (1 && 2) && 3
797 un
->setNode(0, ln
.release());
798 un
->setNode(1, rn
->releaseNode(0));
799 rn
->setNode(0, un
.release());
803 un
->setNode(0, ln
.release());
804 un
->setNode(1, rn
.release());
810 wxPluralFormsNode
* wxPluralFormsParser::equalityExpression()
812 wxPluralFormsNode
* p
= relationalExpression();
815 wxPluralFormsNodePtr
n(p
);
816 if (token().type() == wxPluralFormsToken::T_EQUAL
817 || token().type() == wxPluralFormsToken::T_NOT_EQUAL
)
819 wxPluralFormsNodePtr
qn(new wxPluralFormsNode(token()));
824 p
= relationalExpression();
830 qn
->setNode(0, n
.release());
836 wxPluralFormsNode
* wxPluralFormsParser::relationalExpression()
838 wxPluralFormsNode
* p
= multiplicativeExpression();
841 wxPluralFormsNodePtr
n(p
);
842 if (token().type() == wxPluralFormsToken::T_GREATER
843 || token().type() == wxPluralFormsToken::T_LESS
844 || token().type() == wxPluralFormsToken::T_GREATER_OR_EQUAL
845 || token().type() == wxPluralFormsToken::T_LESS_OR_EQUAL
)
847 wxPluralFormsNodePtr
qn(new wxPluralFormsNode(token()));
852 p
= multiplicativeExpression();
858 qn
->setNode(0, n
.release());
864 wxPluralFormsNode
* wxPluralFormsParser::multiplicativeExpression()
866 wxPluralFormsNode
* p
= pmExpression();
869 wxPluralFormsNodePtr
n(p
);
870 if (token().type() == wxPluralFormsToken::T_REMINDER
)
872 wxPluralFormsNodePtr
qn(new wxPluralFormsNode(token()));
883 qn
->setNode(0, n
.release());
889 wxPluralFormsNode
* wxPluralFormsParser::pmExpression()
891 wxPluralFormsNodePtr n
;
892 if (token().type() == wxPluralFormsToken::T_N
893 || token().type() == wxPluralFormsToken::T_NUMBER
)
895 n
.reset(new wxPluralFormsNode(token()));
901 else if (token().type() == wxPluralFormsToken::T_LEFT_BRACKET
) {
906 wxPluralFormsNode
* p
= expression();
912 if (token().type() != wxPluralFormsToken::T_RIGHT_BRACKET
)
928 wxPluralFormsCalculator
* wxPluralFormsCalculator::make(const char* s
)
930 wxPluralFormsCalculatorPtr
calculator(new wxPluralFormsCalculator
);
933 wxPluralFormsScanner
scanner(s
);
934 wxPluralFormsParser
p(scanner
);
935 if (!p
.parse(*calculator
))
940 return calculator
.release();
946 // ----------------------------------------------------------------------------
947 // wxMsgCatalogFile corresponds to one disk-file message catalog.
949 // This is a "low-level" class and is used only by wxMsgCatalog
950 // NOTE: for the documentation of the binary catalog (.MO) files refer to
951 // the GNU gettext manual:
952 // http://www.gnu.org/software/autoconf/manual/gettext/MO-Files.html
953 // ----------------------------------------------------------------------------
955 class wxMsgCatalogFile
958 typedef wxScopedCharBuffer DataBuffer
;
964 // load the catalog from disk
965 bool LoadFile(const wxString
& filename
,
966 wxPluralFormsCalculatorPtr
& rPluralFormsCalculator
);
967 bool LoadData(const DataBuffer
& data
,
968 wxPluralFormsCalculatorPtr
& rPluralFormsCalculator
);
970 // fills the hash with string-translation pairs
971 bool FillHash(wxStringToStringHashMap
& hash
, const wxString
& domain
) const;
973 // return the charset of the strings in this catalog or empty string if
975 wxString
GetCharset() const { return m_charset
; }
978 // this implementation is binary compatible with GNU gettext() version 0.10
980 // an entry in the string table
981 struct wxMsgTableEntry
983 size_t32 nLen
; // length of the string
984 size_t32 ofsString
; // pointer to the string
987 // header of a .mo file
988 struct wxMsgCatalogHeader
990 size_t32 magic
, // offset +00: magic id
991 revision
, // +04: revision
992 numStrings
; // +08: number of strings in the file
993 size_t32 ofsOrigTable
, // +0C: start of original string table
994 ofsTransTable
; // +10: start of translated string table
995 size_t32 nHashSize
, // +14: hash table size
996 ofsHashTable
; // +18: offset of hash table start
999 // all data is stored here
1003 size_t32 m_numStrings
; // number of strings in this domain
1004 wxMsgTableEntry
*m_pOrigTable
, // pointer to original strings
1005 *m_pTransTable
; // translated
1007 wxString m_charset
; // from the message catalog header
1010 // swap the 2 halves of 32 bit integer if needed
1011 size_t32
Swap(size_t32 ui
) const
1013 return m_bSwapped
? (ui
<< 24) | ((ui
& 0xff00) << 8) |
1014 ((ui
>> 8) & 0xff00) | (ui
>> 24)
1018 const char *StringAtOfs(wxMsgTableEntry
*pTable
, size_t32 n
) const
1020 const wxMsgTableEntry
* const ent
= pTable
+ n
;
1022 // this check could fail for a corrupt message catalog
1023 size_t32 ofsString
= Swap(ent
->ofsString
);
1024 if ( ofsString
+ Swap(ent
->nLen
) > m_data
.length())
1029 return m_data
.data() + ofsString
;
1032 bool m_bSwapped
; // wrong endianness?
1034 wxDECLARE_NO_COPY_CLASS(wxMsgCatalogFile
);
1037 // ----------------------------------------------------------------------------
1038 // wxMsgCatalogFile class
1039 // ----------------------------------------------------------------------------
1041 wxMsgCatalogFile::wxMsgCatalogFile()
1045 wxMsgCatalogFile::~wxMsgCatalogFile()
1049 // open disk file and read in it's contents
1050 bool wxMsgCatalogFile::LoadFile(const wxString
& filename
,
1051 wxPluralFormsCalculatorPtr
& rPluralFormsCalculator
)
1053 wxFile
fileMsg(filename
);
1054 if ( !fileMsg
.IsOpened() )
1057 // get the file size (assume it is less than 4GB...)
1058 wxFileOffset lenFile
= fileMsg
.Length();
1059 if ( lenFile
== wxInvalidOffset
)
1062 size_t nSize
= wx_truncate_cast(size_t, lenFile
);
1063 wxASSERT_MSG( nSize
== lenFile
+ size_t(0), wxS("message catalog bigger than 4GB?") );
1065 wxMemoryBuffer filedata
;
1067 // read the whole file in memory
1068 if ( fileMsg
.Read(filedata
.GetWriteBuf(nSize
), nSize
) != lenFile
)
1071 filedata
.UngetWriteBuf(nSize
);
1075 DataBuffer::CreateOwned((char*)filedata
.release(), nSize
),
1076 rPluralFormsCalculator
1080 wxLogWarning(_("'%s' is not a valid message catalog."), filename
.c_str());
1088 bool wxMsgCatalogFile::LoadData(const DataBuffer
& data
,
1089 wxPluralFormsCalculatorPtr
& rPluralFormsCalculator
)
1092 bool bValid
= data
.length() > sizeof(wxMsgCatalogHeader
);
1094 const wxMsgCatalogHeader
*pHeader
= (wxMsgCatalogHeader
*)data
.data();
1096 // we'll have to swap all the integers if it's true
1097 m_bSwapped
= pHeader
->magic
== MSGCATALOG_MAGIC_SW
;
1099 // check the magic number
1100 bValid
= m_bSwapped
|| pHeader
->magic
== MSGCATALOG_MAGIC
;
1104 // it's either too short or has incorrect magic number
1105 wxLogWarning(_("Invalid message catalog."));
1112 m_numStrings
= Swap(pHeader
->numStrings
);
1113 m_pOrigTable
= (wxMsgTableEntry
*)(data
.data() +
1114 Swap(pHeader
->ofsOrigTable
));
1115 m_pTransTable
= (wxMsgTableEntry
*)(data
.data() +
1116 Swap(pHeader
->ofsTransTable
));
1118 // now parse catalog's header and try to extract catalog charset and
1119 // plural forms formula from it:
1121 const char* headerData
= StringAtOfs(m_pOrigTable
, 0);
1122 if ( headerData
&& headerData
[0] == '\0' )
1124 // Extract the charset:
1125 const char * const header
= StringAtOfs(m_pTransTable
, 0);
1127 cset
= strstr(header
, "Content-Type: text/plain; charset=");
1130 cset
+= 34; // strlen("Content-Type: text/plain; charset=")
1132 const char * const csetEnd
= strchr(cset
, '\n');
1135 m_charset
= wxString(cset
, csetEnd
- cset
);
1136 if ( m_charset
== wxS("CHARSET") )
1138 // "CHARSET" is not valid charset, but lazy translator
1143 // else: incorrectly filled Content-Type header
1145 // Extract plural forms:
1146 const char * plurals
= strstr(header
, "Plural-Forms:");
1149 plurals
+= 13; // strlen("Plural-Forms:")
1150 const char * const pluralsEnd
= strchr(plurals
, '\n');
1153 const size_t pluralsLen
= pluralsEnd
- plurals
;
1154 wxCharBuffer
buf(pluralsLen
);
1155 strncpy(buf
.data(), plurals
, pluralsLen
);
1156 wxPluralFormsCalculator
* const
1157 pCalculator
= wxPluralFormsCalculator::make(buf
);
1160 rPluralFormsCalculator
.reset(pCalculator
);
1164 wxLogVerbose(_("Failed to parse Plural-Forms: '%s'"),
1170 if ( !rPluralFormsCalculator
.get() )
1171 rPluralFormsCalculator
.reset(wxPluralFormsCalculator::make());
1174 // everything is fine
1178 bool wxMsgCatalogFile::FillHash(wxStringToStringHashMap
& hash
,
1179 const wxString
& domain
) const
1181 wxUnusedVar(domain
); // silence warning in Unicode build
1183 // conversion to use to convert catalog strings to the GUI encoding
1184 wxMBConv
*inputConv
= NULL
;
1185 wxMBConv
*inputConvPtr
= NULL
; // same as inputConv but safely deleteable
1187 if ( !m_charset
.empty() )
1189 #if !wxUSE_UNICODE && wxUSE_FONTMAP
1190 // determine if we need any conversion at all
1191 wxFontEncoding encCat
= wxFontMapperBase::GetEncodingFromName(m_charset
);
1192 if ( encCat
!= wxLocale::GetSystemEncoding() )
1196 inputConv
= new wxCSConv(m_charset
);
1199 else // no need or not possible to convert the encoding
1202 // we must somehow convert the narrow strings in the message catalog to
1203 // wide strings, so use the default conversion if we have no charset
1204 inputConv
= wxConvCurrent
;
1209 wxString msgIdCharset
= gs_msgIdCharset
[domain
];
1211 // conversion to apply to msgid strings before looking them up: we only
1212 // need it if the msgids are neither in 7 bit ASCII nor in the same
1213 // encoding as the catalog
1214 wxCSConv
*sourceConv
= msgIdCharset
.empty() || (msgIdCharset
== m_charset
)
1216 : new wxCSConv(msgIdCharset
);
1217 #endif // !wxUSE_UNICODE
1219 for (size_t32 i
= 0; i
< m_numStrings
; i
++)
1221 const char *data
= StringAtOfs(m_pOrigTable
, i
);
1223 return false; // may happen for invalid MO files
1227 msgid
= wxString(data
, *inputConv
);
1229 if ( inputConv
&& sourceConv
)
1230 msgid
= wxString(inputConv
->cMB2WC(data
), *sourceConv
);
1233 #endif // wxUSE_UNICODE
1235 data
= StringAtOfs(m_pTransTable
, i
);
1237 return false; // may happen for invalid MO files
1239 size_t length
= Swap(m_pTransTable
[i
].nLen
);
1242 while (offset
< length
)
1244 const char * const str
= data
+ offset
;
1248 msgstr
= wxString(str
, *inputConv
);
1251 msgstr
= wxString(inputConv
->cMB2WC(str
), *wxConvUI
);
1254 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
1256 if ( !msgstr
.empty() )
1258 hash
[index
== 0 ? msgid
: msgid
+ wxChar(index
)] = msgstr
;
1262 // IMPORTANT: accesses to the 'data' pointer are valid only for
1263 // the first 'length+1' bytes (GNU specs says that the
1264 // final NUL is not counted in length); using wxStrnlen()
1265 // we make sure we don't access memory beyond the valid range
1266 // (which otherwise may happen for invalid MO files):
1267 offset
+= wxStrnlen(str
, length
- offset
) + 1;
1275 delete inputConvPtr
;
1281 // ----------------------------------------------------------------------------
1282 // wxMsgCatalog class
1283 // ----------------------------------------------------------------------------
1286 wxMsgCatalog::~wxMsgCatalog()
1290 if ( wxConvUI
== m_conv
)
1292 // we only change wxConvUI if it points to wxConvLocal so we reset
1293 // it back to it too
1294 wxConvUI
= &wxConvLocal
;
1300 #endif // !wxUSE_UNICODE
1303 wxMsgCatalog
*wxMsgCatalog::CreateFromFile(const wxString
& filename
,
1304 const wxString
& domain
)
1306 wxScopedPtr
<wxMsgCatalog
> cat(new wxMsgCatalog(domain
));
1308 wxMsgCatalogFile file
;
1310 if ( !file
.LoadFile(filename
, cat
->m_pluralFormsCalculator
) )
1313 if ( !file
.FillHash(cat
->m_messages
, domain
) )
1316 return cat
.release();
1320 wxMsgCatalog
*wxMsgCatalog::CreateFromData(const wxScopedCharBuffer
& data
,
1321 const wxString
& domain
)
1323 wxScopedPtr
<wxMsgCatalog
> cat(new wxMsgCatalog(domain
));
1325 wxMsgCatalogFile file
;
1327 if ( !file
.LoadData(data
, cat
->m_pluralFormsCalculator
) )
1330 if ( !file
.FillHash(cat
->m_messages
, domain
) )
1333 return cat
.release();
1336 const wxString
*wxMsgCatalog::GetString(const wxString
& str
, unsigned n
) const
1341 index
= m_pluralFormsCalculator
->evaluate(n
);
1343 wxStringToStringHashMap::const_iterator i
;
1346 i
= m_messages
.find(wxString(str
) + wxChar(index
)); // plural
1350 i
= m_messages
.find(str
);
1353 if ( i
!= m_messages
.end() )
1362 // ----------------------------------------------------------------------------
1364 // ----------------------------------------------------------------------------
1369 wxTranslations
*gs_translations
= NULL
;
1370 bool gs_translationsOwned
= false;
1372 } // anonymous namespace
1376 wxTranslations
*wxTranslations::Get()
1378 return gs_translations
;
1382 void wxTranslations::Set(wxTranslations
*t
)
1384 if ( gs_translationsOwned
)
1385 delete gs_translations
;
1386 gs_translations
= t
;
1387 gs_translationsOwned
= true;
1391 void wxTranslations::SetNonOwned(wxTranslations
*t
)
1393 if ( gs_translationsOwned
)
1394 delete gs_translations
;
1395 gs_translations
= t
;
1396 gs_translationsOwned
= false;
1400 wxTranslations::wxTranslations()
1403 m_loader
= new wxFileTranslationsLoader
;
1407 wxTranslations::~wxTranslations()
1411 // free catalogs memory
1412 wxMsgCatalog
*pTmpCat
;
1413 while ( m_pMsgCat
!= NULL
)
1415 pTmpCat
= m_pMsgCat
;
1416 m_pMsgCat
= m_pMsgCat
->m_pNext
;
1422 void wxTranslations::SetLoader(wxTranslationsLoader
*loader
)
1424 wxCHECK_RET( loader
, "loader can't be NULL" );
1431 void wxTranslations::SetLanguage(wxLanguage lang
)
1433 if ( lang
== wxLANGUAGE_DEFAULT
)
1436 SetLanguage(wxLocale::GetLanguageCanonicalName(lang
));
1439 void wxTranslations::SetLanguage(const wxString
& lang
)
1445 wxArrayString
wxTranslations::GetAvailableTranslations(const wxString
& domain
) const
1447 wxCHECK_MSG( m_loader
, wxArrayString(), "loader can't be NULL" );
1449 return m_loader
->GetAvailableTranslations(domain
);
1453 bool wxTranslations::AddStdCatalog()
1455 if ( !AddCatalog(wxS("wxstd")) )
1458 // there may be a catalog with toolkit specific overrides, it is not
1459 // an error if this does not exist
1460 wxString
port(wxPlatformInfo::Get().GetPortIdName());
1461 if ( !port
.empty() )
1463 AddCatalog(port
.BeforeFirst(wxS('/')).MakeLower());
1470 bool wxTranslations::AddCatalog(const wxString
& domain
)
1472 return AddCatalog(domain
, wxLANGUAGE_ENGLISH_US
);
1476 bool wxTranslations::AddCatalog(const wxString
& domain
,
1477 wxLanguage msgIdLanguage
,
1478 const wxString
& msgIdCharset
)
1480 gs_msgIdCharset
[domain
] = msgIdCharset
;
1481 return AddCatalog(domain
, msgIdLanguage
);
1483 #endif // !wxUSE_UNICODE
1485 bool wxTranslations::AddCatalog(const wxString
& domain
,
1486 wxLanguage msgIdLanguage
)
1488 const wxString msgIdLang
= wxLocale::GetLanguageCanonicalName(msgIdLanguage
);
1489 const wxString domain_lang
= GetBestTranslation(domain
, msgIdLang
);
1491 if ( domain_lang
.empty() )
1493 wxLogTrace(TRACE_I18N
,
1494 wxS("no suitable translation for domain '%s' found"),
1499 wxLogTrace(TRACE_I18N
,
1500 wxS("adding '%s' translation for domain '%s' (msgid language '%s')"),
1501 domain_lang
, domain
, msgIdLang
);
1503 // It is OK to not load catalog if the msgid language and m_language match,
1504 // in which case we can directly display the texts embedded in program's
1506 if ( msgIdLang
== domain_lang
)
1509 return LoadCatalog(domain
, domain_lang
);
1513 bool wxTranslations::LoadCatalog(const wxString
& domain
, const wxString
& lang
)
1515 wxCHECK_MSG( m_loader
, false, "loader can't be NULL" );
1517 wxMsgCatalog
*cat
= NULL
;
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
)
1527 wxString
fullname(lang
);
1528 fullname
<< wxS('.') << wxFontMapperBase::GetEncodingName(encSys
);
1530 cat
= m_loader
->LoadCatalog(domain
, fullname
);
1532 #endif // wxUSE_FONTMAP
1536 // Next try: use the provided name language name:
1537 cat
= m_loader
->LoadCatalog(domain
, lang
);
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
);
1552 // add it to the head of the list so that in GetString it will
1553 // be searched before the catalogs added earlier
1554 cat
->m_pNext
= m_pMsgCat
;
1561 // Nothing worked, the catalog just isn't there
1562 wxLogTrace(TRACE_I18N
,
1563 "Catalog \"%s.mo\" not found for language \"%s\".",
1569 // check if the given catalog is loaded
1570 bool wxTranslations::IsLoaded(const wxString
& domain
) const
1572 return FindCatalog(domain
) != NULL
;
1575 wxString
wxTranslations::GetBestTranslation(const wxString
& domain
,
1576 wxLanguage msgIdLanguage
)
1578 const wxString lang
= wxLocale::GetLanguageCanonicalName(msgIdLanguage
);
1579 return GetBestTranslation(domain
, lang
);
1582 wxString
wxTranslations::GetBestTranslation(const wxString
& domain
,
1583 const wxString
& msgIdLanguage
)
1585 // explicitly set language should always be respected
1586 if ( !m_lang
.empty() )
1589 wxArrayString
available(GetAvailableTranslations(domain
));
1590 // it's OK to have duplicates, so just add msgid language
1591 available
.push_back(msgIdLanguage
);
1592 available
.push_back(msgIdLanguage
.BeforeFirst('_'));
1594 wxLogTrace(TRACE_I18N
, "choosing best language for domain '%s'", domain
);
1595 LogTraceArray(" - available translations", available
);
1596 const wxString lang
= GetPreferredUILanguage(available
);
1597 wxLogTrace(TRACE_I18N
, " => using language '%s'", lang
);
1604 WX_DECLARE_HASH_SET(wxString
, wxStringHash
, wxStringEqual
,
1605 wxLocaleUntranslatedStrings
);
1609 const wxString
& wxTranslations::GetUntranslatedString(const wxString
& str
)
1611 static wxLocaleUntranslatedStrings s_strings
;
1613 wxLocaleUntranslatedStrings::iterator i
= s_strings
.find(str
);
1614 if ( i
== s_strings
.end() )
1615 return *s_strings
.insert(str
).first
;
1621 const wxString
& wxTranslations::GetString(const wxString
& origString
,
1622 const wxString
& domain
) const
1624 return GetString(origString
, origString
, UINT_MAX
, domain
);
1627 const wxString
& wxTranslations::GetString(const wxString
& origString
,
1628 const wxString
& origString2
,
1630 const wxString
& domain
) const
1632 if ( origString
.empty() )
1633 return GetUntranslatedString(origString
);
1635 const wxString
*trans
= NULL
;
1636 wxMsgCatalog
*pMsgCat
;
1638 if ( !domain
.empty() )
1640 pMsgCat
= FindCatalog(domain
);
1642 // does the catalog exist?
1643 if ( pMsgCat
!= NULL
)
1644 trans
= pMsgCat
->GetString(origString
, n
);
1648 // search in all domains
1649 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
)
1651 trans
= pMsgCat
->GetString(origString
, n
);
1652 if ( trans
!= NULL
) // take the first found
1657 if ( trans
== NULL
)
1662 "string \"%s\"%s not found in %slocale '%s'.",
1664 (n
!= UINT_MAX
? wxString::Format("[%ld]", (long)n
) : wxString()),
1665 (!domain
.empty() ? wxString::Format("domain '%s' ", domain
) : wxString()),
1670 return GetUntranslatedString(origString
);
1672 return GetUntranslatedString(n
== 1 ? origString
: origString2
);
1679 wxString
wxTranslations::GetHeaderValue(const wxString
& header
,
1680 const wxString
& domain
) const
1682 if ( header
.empty() )
1683 return wxEmptyString
;
1685 const wxString
*trans
= NULL
;
1686 wxMsgCatalog
*pMsgCat
;
1688 if ( !domain
.empty() )
1690 pMsgCat
= FindCatalog(domain
);
1692 // does the catalog exist?
1693 if ( pMsgCat
== NULL
)
1694 return wxEmptyString
;
1696 trans
= pMsgCat
->GetString(wxEmptyString
, UINT_MAX
);
1700 // search in all domains
1701 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
)
1703 trans
= pMsgCat
->GetString(wxEmptyString
, UINT_MAX
);
1704 if ( trans
!= NULL
) // take the first found
1709 if ( !trans
|| trans
->empty() )
1710 return wxEmptyString
;
1712 size_t found
= trans
->find(header
);
1713 if ( found
== wxString::npos
)
1714 return wxEmptyString
;
1716 found
+= header
.length() + 2 /* ': ' */;
1718 // Every header is separated by \n
1720 size_t endLine
= trans
->find(wxS('\n'), found
);
1721 size_t len
= (endLine
== wxString::npos
) ?
1722 wxString::npos
: (endLine
- found
);
1724 return trans
->substr(found
, len
);
1728 // find catalog by name in a linked list, return NULL if !found
1729 wxMsgCatalog
*wxTranslations::FindCatalog(const wxString
& domain
) const
1731 // linear search in the linked list
1732 wxMsgCatalog
*pMsgCat
;
1733 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
)
1735 if ( pMsgCat
->GetDomain() == domain
)
1742 // ----------------------------------------------------------------------------
1743 // wxFileTranslationsLoader
1744 // ----------------------------------------------------------------------------
1749 // the list of the directories to search for message catalog files
1750 wxArrayString gs_searchPrefixes
;
1752 // return the directories to search for message catalogs under the given
1753 // prefix, separated by wxPATH_SEP
1754 wxString
GetMsgCatalogSubdirs(const wxString
& prefix
, const wxString
& lang
)
1756 // Search first in Unix-standard prefix/lang/LC_MESSAGES, then in
1757 // prefix/lang and finally in just prefix.
1759 // Note that we use LC_MESSAGES on all platforms and not just Unix, because
1760 // it doesn't cost much to look into one more directory and doing it this
1761 // way has two important benefits:
1762 // a) we don't break compatibility with wx-2.6 and older by stopping to
1763 // look in a directory where the catalogs used to be and thus silently
1764 // breaking apps after they are recompiled against the latest wx
1765 // b) it makes it possible to package app's support files in the same
1766 // way on all target platforms
1767 const wxString prefixAndLang
= wxFileName(prefix
, lang
).GetFullPath();
1769 wxString searchPath
;
1770 searchPath
.reserve(4*prefixAndLang
.length());
1771 searchPath
<< prefixAndLang
<< wxFILE_SEP_PATH
<< "LC_MESSAGES" << wxPATH_SEP
1772 << prefixAndLang
<< wxPATH_SEP
1778 bool HasMsgCatalogInDir(const wxString
& dir
, const wxString
& domain
)
1780 return wxFileName(dir
, domain
, "mo").FileExists() ||
1781 wxFileName(dir
+ wxFILE_SEP_PATH
+ "LC_MESSAGES", domain
, "mo").FileExists();
1784 // get prefixes to locale directories; if lang is empty, don't point to
1785 // OSX's .lproj bundles
1786 wxArrayString
GetSearchPrefixes(const wxString
& lang
= wxString())
1788 wxArrayString paths
;
1790 // first take the entries explicitly added by the program
1791 paths
= gs_searchPrefixes
;
1794 // then look in the standard location
1798 stdp
= wxStandardPaths::Get().GetResourcesDir();
1802 stdp
= wxStandardPaths::Get().
1803 GetLocalizedResourcesDir(lang
, wxStandardPaths::ResourceCat_Messages
);
1805 if ( paths
.Index(stdp
) == wxNOT_FOUND
)
1807 #endif // wxUSE_STDPATHS
1809 // last look in default locations
1811 // LC_PATH is a standard env var containing the search path for the .mo
1813 const char *pszLcPath
= wxGetenv("LC_PATH");
1816 const wxString lcp
= pszLcPath
;
1817 if ( paths
.Index(lcp
) == wxNOT_FOUND
)
1821 // also add the one from where wxWin was installed:
1822 wxString wxp
= wxGetInstallPrefix();
1825 wxp
+= wxS("/share/locale");
1826 if ( paths
.Index(wxp
) == wxNOT_FOUND
)
1834 // construct the search path for the given language
1835 wxString
GetFullSearchPath(const wxString
& lang
)
1837 wxString searchPath
;
1838 searchPath
.reserve(500);
1840 const wxArrayString prefixes
= GetSearchPrefixes(lang
);
1842 for ( wxArrayString::const_iterator i
= prefixes
.begin();
1843 i
!= prefixes
.end();
1846 const wxString p
= GetMsgCatalogSubdirs(*i
, lang
);
1848 if ( !searchPath
.empty() )
1849 searchPath
+= wxPATH_SEP
;
1856 } // anonymous namespace
1859 void wxFileTranslationsLoader::AddCatalogLookupPathPrefix(const wxString
& prefix
)
1861 if ( gs_searchPrefixes
.Index(prefix
) == wxNOT_FOUND
)
1863 gs_searchPrefixes
.Add(prefix
);
1865 //else: already have it
1869 wxMsgCatalog
*wxFileTranslationsLoader::LoadCatalog(const wxString
& domain
,
1870 const wxString
& lang
)
1872 wxString searchPath
= GetFullSearchPath(lang
);
1874 wxLogTrace(TRACE_I18N
, wxS("Looking for \"%s.mo\" in search path \"%s\""),
1875 domain
, searchPath
);
1877 wxFileName
fn(domain
);
1878 fn
.SetExt(wxS("mo"));
1880 wxString strFullName
;
1881 if ( !wxFindFileInPath(&strFullName
, searchPath
, fn
.GetFullPath()) )
1884 // open file and read its data
1885 wxLogVerbose(_("using catalog '%s' from '%s'."), domain
, strFullName
.c_str());
1886 wxLogTrace(TRACE_I18N
, wxS("Using catalog \"%s\"."), strFullName
.c_str());
1888 return wxMsgCatalog::CreateFromFile(strFullName
, domain
);
1892 wxArrayString
wxFileTranslationsLoader::GetAvailableTranslations(const wxString
& domain
) const
1894 wxArrayString langs
;
1895 const wxArrayString prefixes
= GetSearchPrefixes();
1897 wxLogTrace(TRACE_I18N
,
1898 "looking for available translations of \"%s\" in search path \"%s\"",
1899 domain
, wxJoin(prefixes
, wxPATH_SEP
[0]));
1901 for ( wxArrayString::const_iterator i
= prefixes
.begin();
1902 i
!= prefixes
.end();
1908 if ( !dir
.Open(*i
) )
1912 for ( bool ok
= dir
.GetFirst(&lang
, "", wxDIR_DIRS
);
1914 ok
= dir
.GetNext(&lang
) )
1916 const wxString langdir
= *i
+ wxFILE_SEP_PATH
+ lang
;
1917 if ( HasMsgCatalogInDir(langdir
, domain
) )
1921 if ( lang
.EndsWith(".lproj", &rest
) )
1925 wxLogTrace(TRACE_I18N
,
1926 "found %s translation of \"%s\"", lang
, domain
);
1927 langs
.push_back(lang
);
1936 // ----------------------------------------------------------------------------
1937 // wxResourceTranslationsLoader
1938 // ----------------------------------------------------------------------------
1942 wxMsgCatalog
*wxResourceTranslationsLoader::LoadCatalog(const wxString
& domain
,
1943 const wxString
& lang
)
1945 const void *mo_data
= NULL
;
1948 const wxString resname
= wxString::Format("%s_%s", domain
, lang
);
1950 if ( !wxLoadUserResource(&mo_data
, &mo_size
,
1952 GetResourceType().t_str(),
1956 wxLogTrace(TRACE_I18N
,
1957 "Using catalog from Windows resource \"%s\".", resname
);
1959 wxMsgCatalog
*cat
= wxMsgCatalog::CreateFromData(
1960 wxCharBuffer::CreateNonOwned(static_cast<const char*>(mo_data
), mo_size
),
1965 wxLogWarning(_("Resource '%s' is not a valid message catalog."), resname
);
1974 struct EnumCallbackData
1977 wxArrayString langs
;
1980 BOOL CALLBACK
EnumTranslations(HMODULE
WXUNUSED(hModule
),
1981 LPCTSTR
WXUNUSED(lpszType
),
1985 wxString
name(lpszName
);
1986 name
.MakeLower(); // resource names are case insensitive
1988 EnumCallbackData
*data
= reinterpret_cast<EnumCallbackData
*>(lParam
);
1991 if ( name
.StartsWith(data
->prefix
, &lang
) && !lang
.empty() )
1992 data
->langs
.push_back(lang
);
1994 return TRUE
; // continue enumeration
1997 } // anonymous namespace
2000 wxArrayString
wxResourceTranslationsLoader::GetAvailableTranslations(const wxString
& domain
) const
2002 EnumCallbackData data
;
2003 data
.prefix
= domain
+ "_";
2004 data
.prefix
.MakeLower(); // resource names are case insensitive
2006 if ( !EnumResourceNames(GetModule(),
2007 GetResourceType().t_str(),
2009 reinterpret_cast<LONG_PTR
>(&data
)) )
2011 const DWORD err
= GetLastError();
2012 if ( err
!= NO_ERROR
&& err
!= ERROR_RESOURCE_TYPE_NOT_FOUND
)
2014 wxLogSysError(_("Couldn't enumerate translations"));
2021 #endif // __WINDOWS__
2024 // ----------------------------------------------------------------------------
2025 // wxTranslationsModule module (for destruction of gs_translations)
2026 // ----------------------------------------------------------------------------
2028 class wxTranslationsModule
: public wxModule
2030 DECLARE_DYNAMIC_CLASS(wxTranslationsModule
)
2032 wxTranslationsModule() {}
2041 if ( gs_translationsOwned
)
2042 delete gs_translations
;
2043 gs_translations
= NULL
;
2044 gs_translationsOwned
= true;
2048 IMPLEMENT_DYNAMIC_CLASS(wxTranslationsModule
, wxModule
)
2050 #endif // wxUSE_INTL