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"
59 #include "wx/osx/core/cfstring.h"
60 #include <CoreFoundation/CFBundle.h>
61 #include <CoreFoundation/CFLocale.h>
64 // ----------------------------------------------------------------------------
66 // ----------------------------------------------------------------------------
68 typedef wxUint32 size_t32
;
70 // ----------------------------------------------------------------------------
72 // ----------------------------------------------------------------------------
74 // magic number identifying the .mo format file
75 const size_t32 MSGCATALOG_MAGIC
= 0x950412de;
76 const size_t32 MSGCATALOG_MAGIC_SW
= 0xde120495;
78 #define TRACE_I18N wxS("i18n")
80 // ============================================================================
82 // ============================================================================
88 // We need to keep track of (char*) msgids in non-Unicode legacy builds. Instead
89 // of making the public wxMsgCatalog and wxTranslationsLoader APIs ugly, we
90 // store them in this global map.
91 wxStringToStringHashMap gs_msgIdCharset
;
94 // ----------------------------------------------------------------------------
95 // Platform specific helpers
96 // ----------------------------------------------------------------------------
98 void LogTraceArray(const char *prefix
, const wxArrayString
& arr
)
100 wxLogTrace(TRACE_I18N
, "%s: [%s]", prefix
, wxJoin(arr
, ','));
103 // Use locale-based detection as a fallback
104 wxString
GetPreferredUILanguageFallback(const wxArrayString
& WXUNUSED(available
))
106 const wxString lang
= wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage());
107 wxLogTrace(TRACE_I18N
, " - obtained best language from locale: %s", lang
);
113 wxString
GetPreferredUILanguage(const wxArrayString
& available
)
115 typedef BOOL (WINAPI
*GetUserPreferredUILanguages_t
)(DWORD
, PULONG
, PWSTR
, PULONG
);
116 static GetUserPreferredUILanguages_t s_pfnGetUserPreferredUILanguages
= NULL
;
117 static bool s_initDone
= false;
120 wxLoadedDLL
dllKernel32("kernel32.dll");
121 wxDL_INIT_FUNC(s_pfn
, GetUserPreferredUILanguages
, dllKernel32
);
125 if ( s_pfnGetUserPreferredUILanguages
)
128 ULONG bufferSize
= 0;
129 if ( (*s_pfnGetUserPreferredUILanguages
)(MUI_LANGUAGE_NAME
,
134 wxScopedArray
<WCHAR
> langs(new WCHAR
[bufferSize
]);
135 if ( (*s_pfnGetUserPreferredUILanguages
)(MUI_LANGUAGE_NAME
,
140 wxArrayString preferred
;
142 WCHAR
*buf
= langs
.get();
143 for ( unsigned i
= 0; i
< numLangs
; i
++ )
145 const wxString
lang(buf
);
146 preferred
.push_back(lang
);
147 buf
+= lang
.length() + 1;
149 LogTraceArray(" - system preferred languages", preferred
);
151 for ( wxArrayString::const_iterator i
= preferred
.begin();
152 i
!= preferred
.end();
156 lang
.Replace("-", "_");
157 if ( available
.Index(lang
) != wxNOT_FOUND
)
159 size_t pos
= lang
.find('_');
160 if ( pos
!= wxString::npos
)
162 lang
= lang
.substr(0, pos
);
163 if ( available
.Index(lang
) != wxNOT_FOUND
)
171 return GetPreferredUILanguageFallback(available
);
174 #elif defined(__WXOSX__)
176 void LogTraceArray(const char *prefix
, CFArrayRef arr
)
179 const unsigned count
= CFArrayGetCount(arr
);
182 s
+= wxCFStringRef::AsString((CFStringRef
)CFArrayGetValueAtIndex(arr
, 0));
183 for ( unsigned i
= 1 ; i
< count
; i
++ )
184 s
+= "," + wxCFStringRef::AsString((CFStringRef
)CFArrayGetValueAtIndex(arr
, i
));
186 wxLogTrace(TRACE_I18N
, "%s: [%s]", prefix
, s
);
189 wxString
GetPreferredUILanguage(const wxArrayString
& available
)
191 wxStringToStringHashMap availableNormalized
;
192 wxCFRef
<CFMutableArrayRef
> availableArr(
193 CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
));
195 for ( wxArrayString::const_iterator i
= available
.begin();
196 i
!= available
.end();
200 wxCFStringRef
code_wx(*i
);
201 wxCFStringRef
code_norm(
202 CFLocaleCreateCanonicalLanguageIdentifierFromString(kCFAllocatorDefault
, code_wx
));
203 CFArrayAppendValue(availableArr
, code_norm
);
204 availableNormalized
[code_norm
.AsString()] = *i
;
206 LogTraceArray(" - normalized available list", availableArr
);
208 wxCFRef
<CFArrayRef
> prefArr(
209 CFBundleCopyLocalizationsForPreferences(availableArr
, NULL
));
210 LogTraceArray(" - system preferred languages", prefArr
);
212 unsigned prefArrLength
= CFArrayGetCount(prefArr
);
213 if ( prefArrLength
> 0 )
215 // Lookup the name in 'available' by index -- we need to get the
216 // original value corresponding to the normalized one chosen.
217 wxString
lang(wxCFStringRef::AsString((CFStringRef
)CFArrayGetValueAtIndex(prefArr
, 0)));
218 wxStringToStringHashMap::const_iterator i
= availableNormalized
.find(lang
);
219 if ( i
== availableNormalized
.end() )
225 return GetPreferredUILanguageFallback(available
);
230 // On Unix, there's just one language=locale setting, so we should always
232 #define GetPreferredUILanguage GetPreferredUILanguageFallback
236 } // anonymous namespace
238 // ----------------------------------------------------------------------------
239 // Plural forms parser
240 // ----------------------------------------------------------------------------
246 LogicalOrExpression '?' Expression ':' Expression
250 LogicalAndExpression "||" LogicalOrExpression // to (a || b) || c
253 LogicalAndExpression:
254 EqualityExpression "&&" LogicalAndExpression // to (a && b) && c
258 RelationalExpression "==" RelationalExperession
259 RelationalExpression "!=" RelationalExperession
262 RelationalExpression:
263 MultiplicativeExpression '>' MultiplicativeExpression
264 MultiplicativeExpression '<' MultiplicativeExpression
265 MultiplicativeExpression ">=" MultiplicativeExpression
266 MultiplicativeExpression "<=" MultiplicativeExpression
267 MultiplicativeExpression
269 MultiplicativeExpression:
270 PmExpression '%' PmExpression
279 class wxPluralFormsToken
284 T_ERROR
, T_EOF
, T_NUMBER
, T_N
, T_PLURAL
, T_NPLURALS
, T_EQUAL
, T_ASSIGN
,
285 T_GREATER
, T_GREATER_OR_EQUAL
, T_LESS
, T_LESS_OR_EQUAL
,
286 T_REMINDER
, T_NOT_EQUAL
,
287 T_LOGICAL_AND
, T_LOGICAL_OR
, T_QUESTION
, T_COLON
, T_SEMICOLON
,
288 T_LEFT_BRACKET
, T_RIGHT_BRACKET
290 Type
type() const { return m_type
; }
291 void setType(Type type
) { m_type
= type
; }
294 Number
number() const { return m_number
; }
295 void setNumber(Number num
) { m_number
= num
; }
302 class wxPluralFormsScanner
305 wxPluralFormsScanner(const char* s
);
306 const wxPluralFormsToken
& token() const { return m_token
; }
307 bool nextToken(); // returns false if error
310 wxPluralFormsToken m_token
;
313 wxPluralFormsScanner::wxPluralFormsScanner(const char* s
) : m_s(s
)
318 bool wxPluralFormsScanner::nextToken()
320 wxPluralFormsToken::Type type
= wxPluralFormsToken::T_ERROR
;
321 while (isspace((unsigned char) *m_s
))
327 type
= wxPluralFormsToken::T_EOF
;
329 else if (isdigit((unsigned char) *m_s
))
331 wxPluralFormsToken::Number number
= *m_s
++ - '0';
332 while (isdigit((unsigned char) *m_s
))
334 number
= number
* 10 + (*m_s
++ - '0');
336 m_token
.setNumber(number
);
337 type
= wxPluralFormsToken::T_NUMBER
;
339 else if (isalpha((unsigned char) *m_s
))
341 const char* begin
= m_s
++;
342 while (isalnum((unsigned char) *m_s
))
346 size_t size
= m_s
- begin
;
347 if (size
== 1 && memcmp(begin
, "n", size
) == 0)
349 type
= wxPluralFormsToken::T_N
;
351 else if (size
== 6 && memcmp(begin
, "plural", size
) == 0)
353 type
= wxPluralFormsToken::T_PLURAL
;
355 else if (size
== 8 && memcmp(begin
, "nplurals", size
) == 0)
357 type
= wxPluralFormsToken::T_NPLURALS
;
360 else if (*m_s
== '=')
366 type
= wxPluralFormsToken::T_EQUAL
;
370 type
= wxPluralFormsToken::T_ASSIGN
;
373 else if (*m_s
== '>')
379 type
= wxPluralFormsToken::T_GREATER_OR_EQUAL
;
383 type
= wxPluralFormsToken::T_GREATER
;
386 else if (*m_s
== '<')
392 type
= wxPluralFormsToken::T_LESS_OR_EQUAL
;
396 type
= wxPluralFormsToken::T_LESS
;
399 else if (*m_s
== '%')
402 type
= wxPluralFormsToken::T_REMINDER
;
404 else if (*m_s
== '!' && m_s
[1] == '=')
407 type
= wxPluralFormsToken::T_NOT_EQUAL
;
409 else if (*m_s
== '&' && m_s
[1] == '&')
412 type
= wxPluralFormsToken::T_LOGICAL_AND
;
414 else if (*m_s
== '|' && m_s
[1] == '|')
417 type
= wxPluralFormsToken::T_LOGICAL_OR
;
419 else if (*m_s
== '?')
422 type
= wxPluralFormsToken::T_QUESTION
;
424 else if (*m_s
== ':')
427 type
= wxPluralFormsToken::T_COLON
;
428 } else if (*m_s
== ';') {
430 type
= wxPluralFormsToken::T_SEMICOLON
;
432 else if (*m_s
== '(')
435 type
= wxPluralFormsToken::T_LEFT_BRACKET
;
437 else if (*m_s
== ')')
440 type
= wxPluralFormsToken::T_RIGHT_BRACKET
;
442 m_token
.setType(type
);
443 return type
!= wxPluralFormsToken::T_ERROR
;
446 class wxPluralFormsNode
;
448 // NB: Can't use wxDEFINE_SCOPED_PTR_TYPE because wxPluralFormsNode is not
449 // fully defined yet:
450 class wxPluralFormsNodePtr
453 wxPluralFormsNodePtr(wxPluralFormsNode
*p
= NULL
) : m_p(p
) {}
454 ~wxPluralFormsNodePtr();
455 wxPluralFormsNode
& operator*() const { return *m_p
; }
456 wxPluralFormsNode
* operator->() const { return m_p
; }
457 wxPluralFormsNode
* get() const { return m_p
; }
458 wxPluralFormsNode
* release();
459 void reset(wxPluralFormsNode
*p
);
462 wxPluralFormsNode
*m_p
;
465 class wxPluralFormsNode
468 wxPluralFormsNode(const wxPluralFormsToken
& token
) : m_token(token
) {}
469 const wxPluralFormsToken
& token() const { return m_token
; }
470 const wxPluralFormsNode
* node(unsigned i
) const
471 { return m_nodes
[i
].get(); }
472 void setNode(unsigned i
, wxPluralFormsNode
* n
);
473 wxPluralFormsNode
* releaseNode(unsigned i
);
474 wxPluralFormsToken::Number
evaluate(wxPluralFormsToken::Number n
) const;
477 wxPluralFormsToken m_token
;
478 wxPluralFormsNodePtr m_nodes
[3];
481 wxPluralFormsNodePtr::~wxPluralFormsNodePtr()
485 wxPluralFormsNode
* wxPluralFormsNodePtr::release()
487 wxPluralFormsNode
*p
= m_p
;
491 void wxPluralFormsNodePtr::reset(wxPluralFormsNode
*p
)
501 void wxPluralFormsNode::setNode(unsigned i
, wxPluralFormsNode
* n
)
506 wxPluralFormsNode
* wxPluralFormsNode::releaseNode(unsigned i
)
508 return m_nodes
[i
].release();
511 wxPluralFormsToken::Number
512 wxPluralFormsNode::evaluate(wxPluralFormsToken::Number n
) const
514 switch (token().type())
517 case wxPluralFormsToken::T_NUMBER
:
518 return token().number();
519 case wxPluralFormsToken::T_N
:
522 case wxPluralFormsToken::T_EQUAL
:
523 return node(0)->evaluate(n
) == node(1)->evaluate(n
);
524 case wxPluralFormsToken::T_NOT_EQUAL
:
525 return node(0)->evaluate(n
) != node(1)->evaluate(n
);
526 case wxPluralFormsToken::T_GREATER
:
527 return node(0)->evaluate(n
) > node(1)->evaluate(n
);
528 case wxPluralFormsToken::T_GREATER_OR_EQUAL
:
529 return node(0)->evaluate(n
) >= node(1)->evaluate(n
);
530 case wxPluralFormsToken::T_LESS
:
531 return node(0)->evaluate(n
) < node(1)->evaluate(n
);
532 case wxPluralFormsToken::T_LESS_OR_EQUAL
:
533 return node(0)->evaluate(n
) <= node(1)->evaluate(n
);
534 case wxPluralFormsToken::T_REMINDER
:
536 wxPluralFormsToken::Number number
= node(1)->evaluate(n
);
539 return node(0)->evaluate(n
) % number
;
546 case wxPluralFormsToken::T_LOGICAL_AND
:
547 return node(0)->evaluate(n
) && node(1)->evaluate(n
);
548 case wxPluralFormsToken::T_LOGICAL_OR
:
549 return node(0)->evaluate(n
) || node(1)->evaluate(n
);
551 case wxPluralFormsToken::T_QUESTION
:
552 return node(0)->evaluate(n
)
553 ? node(1)->evaluate(n
)
554 : node(2)->evaluate(n
);
561 class wxPluralFormsCalculator
564 wxPluralFormsCalculator() : m_nplurals(0), m_plural(0) {}
566 // input: number, returns msgstr index
567 int evaluate(int n
) const;
569 // input: text after "Plural-Forms:" (e.g. "nplurals=2; plural=(n != 1);"),
570 // if s == 0, creates default handler
571 // returns 0 if error
572 static wxPluralFormsCalculator
* make(const char* s
= 0);
574 ~wxPluralFormsCalculator() {}
576 void init(wxPluralFormsToken::Number nplurals
, wxPluralFormsNode
* plural
);
579 wxPluralFormsToken::Number m_nplurals
;
580 wxPluralFormsNodePtr m_plural
;
583 wxDEFINE_SCOPED_PTR(wxPluralFormsCalculator
, wxPluralFormsCalculatorPtr
)
585 void wxPluralFormsCalculator::init(wxPluralFormsToken::Number nplurals
,
586 wxPluralFormsNode
* plural
)
588 m_nplurals
= nplurals
;
589 m_plural
.reset(plural
);
592 int wxPluralFormsCalculator::evaluate(int n
) const
594 if (m_plural
.get() == 0)
598 wxPluralFormsToken::Number number
= m_plural
->evaluate(n
);
599 if (number
< 0 || number
> m_nplurals
)
607 class wxPluralFormsParser
610 wxPluralFormsParser(wxPluralFormsScanner
& scanner
) : m_scanner(scanner
) {}
611 bool parse(wxPluralFormsCalculator
& rCalculator
);
614 wxPluralFormsNode
* parsePlural();
615 // stops at T_SEMICOLON, returns 0 if error
616 wxPluralFormsScanner
& m_scanner
;
617 const wxPluralFormsToken
& token() const;
620 wxPluralFormsNode
* expression();
621 wxPluralFormsNode
* logicalOrExpression();
622 wxPluralFormsNode
* logicalAndExpression();
623 wxPluralFormsNode
* equalityExpression();
624 wxPluralFormsNode
* multiplicativeExpression();
625 wxPluralFormsNode
* relationalExpression();
626 wxPluralFormsNode
* pmExpression();
629 bool wxPluralFormsParser::parse(wxPluralFormsCalculator
& rCalculator
)
631 if (token().type() != wxPluralFormsToken::T_NPLURALS
)
635 if (token().type() != wxPluralFormsToken::T_ASSIGN
)
639 if (token().type() != wxPluralFormsToken::T_NUMBER
)
641 wxPluralFormsToken::Number nplurals
= token().number();
644 if (token().type() != wxPluralFormsToken::T_SEMICOLON
)
648 if (token().type() != wxPluralFormsToken::T_PLURAL
)
652 if (token().type() != wxPluralFormsToken::T_ASSIGN
)
656 wxPluralFormsNode
* plural
= parsePlural();
659 if (token().type() != wxPluralFormsToken::T_SEMICOLON
)
663 if (token().type() != wxPluralFormsToken::T_EOF
)
665 rCalculator
.init(nplurals
, plural
);
669 wxPluralFormsNode
* wxPluralFormsParser::parsePlural()
671 wxPluralFormsNode
* p
= expression();
676 wxPluralFormsNodePtr
n(p
);
677 if (token().type() != wxPluralFormsToken::T_SEMICOLON
)
684 const wxPluralFormsToken
& wxPluralFormsParser::token() const
686 return m_scanner
.token();
689 bool wxPluralFormsParser::nextToken()
691 if (!m_scanner
.nextToken())
696 wxPluralFormsNode
* wxPluralFormsParser::expression()
698 wxPluralFormsNode
* p
= logicalOrExpression();
701 wxPluralFormsNodePtr
n(p
);
702 if (token().type() == wxPluralFormsToken::T_QUESTION
)
704 wxPluralFormsNodePtr
qn(new wxPluralFormsNode(token()));
715 if (token().type() != wxPluralFormsToken::T_COLON
)
729 qn
->setNode(0, n
.release());
735 wxPluralFormsNode
*wxPluralFormsParser::logicalOrExpression()
737 wxPluralFormsNode
* p
= logicalAndExpression();
740 wxPluralFormsNodePtr
ln(p
);
741 if (token().type() == wxPluralFormsToken::T_LOGICAL_OR
)
743 wxPluralFormsNodePtr
un(new wxPluralFormsNode(token()));
748 p
= logicalOrExpression();
753 wxPluralFormsNodePtr
rn(p
); // right
754 if (rn
->token().type() == wxPluralFormsToken::T_LOGICAL_OR
)
756 // see logicalAndExpression comment
757 un
->setNode(0, ln
.release());
758 un
->setNode(1, rn
->releaseNode(0));
759 rn
->setNode(0, un
.release());
764 un
->setNode(0, ln
.release());
765 un
->setNode(1, rn
.release());
771 wxPluralFormsNode
* wxPluralFormsParser::logicalAndExpression()
773 wxPluralFormsNode
* p
= equalityExpression();
776 wxPluralFormsNodePtr
ln(p
); // left
777 if (token().type() == wxPluralFormsToken::T_LOGICAL_AND
)
779 wxPluralFormsNodePtr
un(new wxPluralFormsNode(token())); // up
784 p
= logicalAndExpression();
789 wxPluralFormsNodePtr
rn(p
); // right
790 if (rn
->token().type() == wxPluralFormsToken::T_LOGICAL_AND
)
792 // transform 1 && (2 && 3) -> (1 && 2) && 3
796 un
->setNode(0, ln
.release());
797 un
->setNode(1, rn
->releaseNode(0));
798 rn
->setNode(0, un
.release());
802 un
->setNode(0, ln
.release());
803 un
->setNode(1, rn
.release());
809 wxPluralFormsNode
* wxPluralFormsParser::equalityExpression()
811 wxPluralFormsNode
* p
= relationalExpression();
814 wxPluralFormsNodePtr
n(p
);
815 if (token().type() == wxPluralFormsToken::T_EQUAL
816 || token().type() == wxPluralFormsToken::T_NOT_EQUAL
)
818 wxPluralFormsNodePtr
qn(new wxPluralFormsNode(token()));
823 p
= relationalExpression();
829 qn
->setNode(0, n
.release());
835 wxPluralFormsNode
* wxPluralFormsParser::relationalExpression()
837 wxPluralFormsNode
* p
= multiplicativeExpression();
840 wxPluralFormsNodePtr
n(p
);
841 if (token().type() == wxPluralFormsToken::T_GREATER
842 || token().type() == wxPluralFormsToken::T_LESS
843 || token().type() == wxPluralFormsToken::T_GREATER_OR_EQUAL
844 || token().type() == wxPluralFormsToken::T_LESS_OR_EQUAL
)
846 wxPluralFormsNodePtr
qn(new wxPluralFormsNode(token()));
851 p
= multiplicativeExpression();
857 qn
->setNode(0, n
.release());
863 wxPluralFormsNode
* wxPluralFormsParser::multiplicativeExpression()
865 wxPluralFormsNode
* p
= pmExpression();
868 wxPluralFormsNodePtr
n(p
);
869 if (token().type() == wxPluralFormsToken::T_REMINDER
)
871 wxPluralFormsNodePtr
qn(new wxPluralFormsNode(token()));
882 qn
->setNode(0, n
.release());
888 wxPluralFormsNode
* wxPluralFormsParser::pmExpression()
890 wxPluralFormsNodePtr n
;
891 if (token().type() == wxPluralFormsToken::T_N
892 || token().type() == wxPluralFormsToken::T_NUMBER
)
894 n
.reset(new wxPluralFormsNode(token()));
900 else if (token().type() == wxPluralFormsToken::T_LEFT_BRACKET
) {
905 wxPluralFormsNode
* p
= expression();
911 if (token().type() != wxPluralFormsToken::T_RIGHT_BRACKET
)
927 wxPluralFormsCalculator
* wxPluralFormsCalculator::make(const char* s
)
929 wxPluralFormsCalculatorPtr
calculator(new wxPluralFormsCalculator
);
932 wxPluralFormsScanner
scanner(s
);
933 wxPluralFormsParser
p(scanner
);
934 if (!p
.parse(*calculator
))
939 return calculator
.release();
945 // ----------------------------------------------------------------------------
946 // wxMsgCatalogFile corresponds to one disk-file message catalog.
948 // This is a "low-level" class and is used only by wxMsgCatalog
949 // NOTE: for the documentation of the binary catalog (.MO) files refer to
950 // the GNU gettext manual:
951 // http://www.gnu.org/software/autoconf/manual/gettext/MO-Files.html
952 // ----------------------------------------------------------------------------
954 class wxMsgCatalogFile
957 typedef wxScopedCharBuffer DataBuffer
;
963 // load the catalog from disk
964 bool LoadFile(const wxString
& filename
,
965 wxPluralFormsCalculatorPtr
& rPluralFormsCalculator
);
966 bool LoadData(const DataBuffer
& data
,
967 wxPluralFormsCalculatorPtr
& rPluralFormsCalculator
);
969 // fills the hash with string-translation pairs
970 bool FillHash(wxStringToStringHashMap
& hash
, const wxString
& domain
) const;
972 // return the charset of the strings in this catalog or empty string if
974 wxString
GetCharset() const { return m_charset
; }
977 // this implementation is binary compatible with GNU gettext() version 0.10
979 // an entry in the string table
980 struct wxMsgTableEntry
982 size_t32 nLen
; // length of the string
983 size_t32 ofsString
; // pointer to the string
986 // header of a .mo file
987 struct wxMsgCatalogHeader
989 size_t32 magic
, // offset +00: magic id
990 revision
, // +04: revision
991 numStrings
; // +08: number of strings in the file
992 size_t32 ofsOrigTable
, // +0C: start of original string table
993 ofsTransTable
; // +10: start of translated string table
994 size_t32 nHashSize
, // +14: hash table size
995 ofsHashTable
; // +18: offset of hash table start
998 // all data is stored here
1002 size_t32 m_numStrings
; // number of strings in this domain
1003 wxMsgTableEntry
*m_pOrigTable
, // pointer to original strings
1004 *m_pTransTable
; // translated
1006 wxString m_charset
; // from the message catalog header
1009 // swap the 2 halves of 32 bit integer if needed
1010 size_t32
Swap(size_t32 ui
) const
1012 return m_bSwapped
? (ui
<< 24) | ((ui
& 0xff00) << 8) |
1013 ((ui
>> 8) & 0xff00) | (ui
>> 24)
1017 const char *StringAtOfs(wxMsgTableEntry
*pTable
, size_t32 n
) const
1019 const wxMsgTableEntry
* const ent
= pTable
+ n
;
1021 // this check could fail for a corrupt message catalog
1022 size_t32 ofsString
= Swap(ent
->ofsString
);
1023 if ( ofsString
+ Swap(ent
->nLen
) > m_data
.length())
1028 return m_data
.data() + ofsString
;
1031 bool m_bSwapped
; // wrong endianness?
1033 wxDECLARE_NO_COPY_CLASS(wxMsgCatalogFile
);
1036 // ----------------------------------------------------------------------------
1037 // wxMsgCatalogFile class
1038 // ----------------------------------------------------------------------------
1040 wxMsgCatalogFile::wxMsgCatalogFile()
1044 wxMsgCatalogFile::~wxMsgCatalogFile()
1048 // open disk file and read in it's contents
1049 bool wxMsgCatalogFile::LoadFile(const wxString
& filename
,
1050 wxPluralFormsCalculatorPtr
& rPluralFormsCalculator
)
1052 wxFile
fileMsg(filename
);
1053 if ( !fileMsg
.IsOpened() )
1056 // get the file size (assume it is less than 4GB...)
1057 wxFileOffset lenFile
= fileMsg
.Length();
1058 if ( lenFile
== wxInvalidOffset
)
1061 size_t nSize
= wx_truncate_cast(size_t, lenFile
);
1062 wxASSERT_MSG( nSize
== lenFile
+ size_t(0), wxS("message catalog bigger than 4GB?") );
1064 wxMemoryBuffer filedata
;
1066 // read the whole file in memory
1067 if ( fileMsg
.Read(filedata
.GetWriteBuf(nSize
), nSize
) != lenFile
)
1070 filedata
.UngetWriteBuf(nSize
);
1074 DataBuffer::CreateOwned((char*)filedata
.release(), nSize
),
1075 rPluralFormsCalculator
1079 wxLogWarning(_("'%s' is not a valid message catalog."), filename
.c_str());
1087 bool wxMsgCatalogFile::LoadData(const DataBuffer
& data
,
1088 wxPluralFormsCalculatorPtr
& rPluralFormsCalculator
)
1091 bool bValid
= data
.length() > sizeof(wxMsgCatalogHeader
);
1093 const wxMsgCatalogHeader
*pHeader
= (wxMsgCatalogHeader
*)data
.data();
1095 // we'll have to swap all the integers if it's true
1096 m_bSwapped
= pHeader
->magic
== MSGCATALOG_MAGIC_SW
;
1098 // check the magic number
1099 bValid
= m_bSwapped
|| pHeader
->magic
== MSGCATALOG_MAGIC
;
1103 // it's either too short or has incorrect magic number
1104 wxLogWarning(_("Invalid message catalog."));
1111 m_numStrings
= Swap(pHeader
->numStrings
);
1112 m_pOrigTable
= (wxMsgTableEntry
*)(data
.data() +
1113 Swap(pHeader
->ofsOrigTable
));
1114 m_pTransTable
= (wxMsgTableEntry
*)(data
.data() +
1115 Swap(pHeader
->ofsTransTable
));
1117 // now parse catalog's header and try to extract catalog charset and
1118 // plural forms formula from it:
1120 const char* headerData
= StringAtOfs(m_pOrigTable
, 0);
1121 if ( headerData
&& headerData
[0] == '\0' )
1123 // Extract the charset:
1124 const char * const header
= StringAtOfs(m_pTransTable
, 0);
1126 cset
= strstr(header
, "Content-Type: text/plain; charset=");
1129 cset
+= 34; // strlen("Content-Type: text/plain; charset=")
1131 const char * const csetEnd
= strchr(cset
, '\n');
1134 m_charset
= wxString(cset
, csetEnd
- cset
);
1135 if ( m_charset
== wxS("CHARSET") )
1137 // "CHARSET" is not valid charset, but lazy translator
1142 // else: incorrectly filled Content-Type header
1144 // Extract plural forms:
1145 const char * plurals
= strstr(header
, "Plural-Forms:");
1148 plurals
+= 13; // strlen("Plural-Forms:")
1149 const char * const pluralsEnd
= strchr(plurals
, '\n');
1152 const size_t pluralsLen
= pluralsEnd
- plurals
;
1153 wxCharBuffer
buf(pluralsLen
);
1154 strncpy(buf
.data(), plurals
, pluralsLen
);
1155 wxPluralFormsCalculator
* const
1156 pCalculator
= wxPluralFormsCalculator::make(buf
);
1159 rPluralFormsCalculator
.reset(pCalculator
);
1163 wxLogVerbose(_("Failed to parse Plural-Forms: '%s'"),
1169 if ( !rPluralFormsCalculator
.get() )
1170 rPluralFormsCalculator
.reset(wxPluralFormsCalculator::make());
1173 // everything is fine
1177 bool wxMsgCatalogFile::FillHash(wxStringToStringHashMap
& hash
,
1178 const wxString
& domain
) const
1180 wxUnusedVar(domain
); // silence warning in Unicode build
1182 // conversion to use to convert catalog strings to the GUI encoding
1183 wxMBConv
*inputConv
= NULL
;
1184 wxMBConv
*inputConvPtr
= NULL
; // same as inputConv but safely deleteable
1186 if ( !m_charset
.empty() )
1188 #if !wxUSE_UNICODE && wxUSE_FONTMAP
1189 // determine if we need any conversion at all
1190 wxFontEncoding encCat
= wxFontMapperBase::GetEncodingFromName(m_charset
);
1191 if ( encCat
!= wxLocale::GetSystemEncoding() )
1195 inputConv
= new wxCSConv(m_charset
);
1198 else // no need or not possible to convert the encoding
1201 // we must somehow convert the narrow strings in the message catalog to
1202 // wide strings, so use the default conversion if we have no charset
1203 inputConv
= wxConvCurrent
;
1208 wxString msgIdCharset
= gs_msgIdCharset
[domain
];
1210 // conversion to apply to msgid strings before looking them up: we only
1211 // need it if the msgids are neither in 7 bit ASCII nor in the same
1212 // encoding as the catalog
1213 wxCSConv
*sourceConv
= msgIdCharset
.empty() || (msgIdCharset
== m_charset
)
1215 : new wxCSConv(msgIdCharset
);
1216 #endif // !wxUSE_UNICODE
1218 for (size_t32 i
= 0; i
< m_numStrings
; i
++)
1220 const char *data
= StringAtOfs(m_pOrigTable
, i
);
1222 return false; // may happen for invalid MO files
1226 msgid
= wxString(data
, *inputConv
);
1228 if ( inputConv
&& sourceConv
)
1229 msgid
= wxString(inputConv
->cMB2WC(data
), *sourceConv
);
1232 #endif // wxUSE_UNICODE
1234 data
= StringAtOfs(m_pTransTable
, i
);
1236 return false; // may happen for invalid MO files
1238 size_t length
= Swap(m_pTransTable
[i
].nLen
);
1241 while (offset
< length
)
1243 const char * const str
= data
+ offset
;
1247 msgstr
= wxString(str
, *inputConv
);
1250 msgstr
= wxString(inputConv
->cMB2WC(str
), *wxConvUI
);
1253 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
1255 if ( !msgstr
.empty() )
1257 hash
[index
== 0 ? msgid
: msgid
+ wxChar(index
)] = msgstr
;
1261 // IMPORTANT: accesses to the 'data' pointer are valid only for
1262 // the first 'length+1' bytes (GNU specs says that the
1263 // final NUL is not counted in length); using wxStrnlen()
1264 // we make sure we don't access memory beyond the valid range
1265 // (which otherwise may happen for invalid MO files):
1266 offset
+= wxStrnlen(str
, length
- offset
) + 1;
1274 delete inputConvPtr
;
1280 // ----------------------------------------------------------------------------
1281 // wxMsgCatalog class
1282 // ----------------------------------------------------------------------------
1285 wxMsgCatalog::~wxMsgCatalog()
1289 if ( wxConvUI
== m_conv
)
1291 // we only change wxConvUI if it points to wxConvLocal so we reset
1292 // it back to it too
1293 wxConvUI
= &wxConvLocal
;
1299 #endif // !wxUSE_UNICODE
1302 wxMsgCatalog
*wxMsgCatalog::CreateFromFile(const wxString
& filename
,
1303 const wxString
& domain
)
1305 wxScopedPtr
<wxMsgCatalog
> cat(new wxMsgCatalog(domain
));
1307 wxMsgCatalogFile file
;
1309 if ( !file
.LoadFile(filename
, cat
->m_pluralFormsCalculator
) )
1312 if ( !file
.FillHash(cat
->m_messages
, domain
) )
1315 return cat
.release();
1319 wxMsgCatalog
*wxMsgCatalog::CreateFromData(const wxScopedCharBuffer
& data
,
1320 const wxString
& domain
)
1322 wxScopedPtr
<wxMsgCatalog
> cat(new wxMsgCatalog(domain
));
1324 wxMsgCatalogFile file
;
1326 if ( !file
.LoadData(data
, cat
->m_pluralFormsCalculator
) )
1329 if ( !file
.FillHash(cat
->m_messages
, domain
) )
1332 return cat
.release();
1335 const wxString
*wxMsgCatalog::GetString(const wxString
& str
, unsigned n
) const
1340 index
= m_pluralFormsCalculator
->evaluate(n
);
1342 wxStringToStringHashMap::const_iterator i
;
1345 i
= m_messages
.find(wxString(str
) + wxChar(index
)); // plural
1349 i
= m_messages
.find(str
);
1352 if ( i
!= m_messages
.end() )
1361 // ----------------------------------------------------------------------------
1363 // ----------------------------------------------------------------------------
1368 wxTranslations
*gs_translations
= NULL
;
1369 bool gs_translationsOwned
= false;
1371 } // anonymous namespace
1375 wxTranslations
*wxTranslations::Get()
1377 return gs_translations
;
1381 void wxTranslations::Set(wxTranslations
*t
)
1383 if ( gs_translationsOwned
)
1384 delete gs_translations
;
1385 gs_translations
= t
;
1386 gs_translationsOwned
= true;
1390 void wxTranslations::SetNonOwned(wxTranslations
*t
)
1392 if ( gs_translationsOwned
)
1393 delete gs_translations
;
1394 gs_translations
= t
;
1395 gs_translationsOwned
= false;
1399 wxTranslations::wxTranslations()
1402 m_loader
= new wxFileTranslationsLoader
;
1406 wxTranslations::~wxTranslations()
1410 // free catalogs memory
1411 wxMsgCatalog
*pTmpCat
;
1412 while ( m_pMsgCat
!= NULL
)
1414 pTmpCat
= m_pMsgCat
;
1415 m_pMsgCat
= m_pMsgCat
->m_pNext
;
1421 void wxTranslations::SetLoader(wxTranslationsLoader
*loader
)
1423 wxCHECK_RET( loader
, "loader can't be NULL" );
1430 void wxTranslations::SetLanguage(wxLanguage lang
)
1432 if ( lang
== wxLANGUAGE_DEFAULT
)
1435 SetLanguage(wxLocale::GetLanguageCanonicalName(lang
));
1438 void wxTranslations::SetLanguage(const wxString
& lang
)
1444 wxArrayString
wxTranslations::GetAvailableTranslations(const wxString
& domain
) const
1446 wxCHECK_MSG( m_loader
, wxArrayString(), "loader can't be NULL" );
1448 return m_loader
->GetAvailableTranslations(domain
);
1452 bool wxTranslations::AddStdCatalog()
1454 if ( !AddCatalog(wxS("wxstd")) )
1457 // there may be a catalog with toolkit specific overrides, it is not
1458 // an error if this does not exist
1459 wxString
port(wxPlatformInfo::Get().GetPortIdName());
1460 if ( !port
.empty() )
1462 AddCatalog(port
.BeforeFirst(wxS('/')).MakeLower());
1469 bool wxTranslations::AddCatalog(const wxString
& domain
)
1471 return AddCatalog(domain
, wxLANGUAGE_ENGLISH_US
);
1475 bool wxTranslations::AddCatalog(const wxString
& domain
,
1476 wxLanguage msgIdLanguage
,
1477 const wxString
& msgIdCharset
)
1479 gs_msgIdCharset
[domain
] = msgIdCharset
;
1480 return AddCatalog(domain
, msgIdLanguage
);
1482 #endif // !wxUSE_UNICODE
1484 bool wxTranslations::AddCatalog(const wxString
& domain
,
1485 wxLanguage msgIdLanguage
)
1487 const wxString msgIdLang
= wxLocale::GetLanguageCanonicalName(msgIdLanguage
);
1488 const wxString domain_lang
= GetBestTranslation(domain
, msgIdLang
);
1490 if ( domain_lang
.empty() )
1492 wxLogTrace(TRACE_I18N
,
1493 wxS("no suitable translation for domain '%s' found"),
1498 wxLogTrace(TRACE_I18N
,
1499 wxS("adding '%s' translation for domain '%s' (msgid language '%s')"),
1500 domain_lang
, domain
, msgIdLang
);
1502 // It is OK to not load catalog if the msgid language and m_language match,
1503 // in which case we can directly display the texts embedded in program's
1505 if ( msgIdLang
== domain_lang
)
1508 return LoadCatalog(domain
, domain_lang
);
1512 bool wxTranslations::LoadCatalog(const wxString
& domain
, const wxString
& lang
)
1514 m_loader
->GetAvailableTranslations(domain
);
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 pathPrefix
= wxFileName(prefix
, lang
).GetFullPath();
1769 wxString searchPath
;
1770 searchPath
.reserve(4*pathPrefix
.length());
1771 searchPath
<< pathPrefix
<< wxFILE_SEP_PATH
<< "LC_MESSAGES" << wxPATH_SEP
1772 << prefix
<< wxFILE_SEP_PATH
<< 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
,
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