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
) != wxNOT_FOUND
)
160 size_t pos
= lang
.find('_');
161 if ( pos
!= wxString::npos
)
163 lang
= lang
.substr(0, pos
);
164 if ( available
.Index(lang
) != 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 m_loader
->GetAvailableTranslations(domain
);
1516 wxCHECK_MSG( m_loader
, false, "loader can't be NULL" );
1518 wxMsgCatalog
*cat
= NULL
;
1521 // first look for the catalog for this language and the current locale:
1522 // notice that we don't use the system name for the locale as this would
1523 // force us to install catalogs in different locations depending on the
1524 // system but always use the canonical name
1525 wxFontEncoding encSys
= wxLocale::GetSystemEncoding();
1526 if ( encSys
!= wxFONTENCODING_SYSTEM
)
1528 wxString
fullname(lang
);
1529 fullname
<< wxS('.') << wxFontMapperBase::GetEncodingName(encSys
);
1531 cat
= m_loader
->LoadCatalog(domain
, fullname
);
1533 #endif // wxUSE_FONTMAP
1537 // Next try: use the provided name language name:
1538 cat
= m_loader
->LoadCatalog(domain
, lang
);
1543 // Also try just base locale name: for things like "fr_BE" (Belgium
1544 // French) we should use fall back on plain "fr" if no Belgium-specific
1545 // message catalogs exist
1546 wxString baselang
= lang
.BeforeFirst('_');
1547 if ( lang
!= baselang
)
1548 cat
= m_loader
->LoadCatalog(domain
, baselang
);
1553 // add it to the head of the list so that in GetString it will
1554 // be searched before the catalogs added earlier
1555 cat
->m_pNext
= m_pMsgCat
;
1562 // Nothing worked, the catalog just isn't there
1563 wxLogTrace(TRACE_I18N
,
1564 "Catalog \"%s.mo\" not found for language \"%s\".",
1570 // check if the given catalog is loaded
1571 bool wxTranslations::IsLoaded(const wxString
& domain
) const
1573 return FindCatalog(domain
) != NULL
;
1576 wxString
wxTranslations::GetBestTranslation(const wxString
& domain
,
1577 wxLanguage msgIdLanguage
)
1579 const wxString lang
= wxLocale::GetLanguageCanonicalName(msgIdLanguage
);
1580 return GetBestTranslation(domain
, lang
);
1583 wxString
wxTranslations::GetBestTranslation(const wxString
& domain
,
1584 const wxString
& msgIdLanguage
)
1586 // explicitly set language should always be respected
1587 if ( !m_lang
.empty() )
1590 wxArrayString
available(GetAvailableTranslations(domain
));
1591 // it's OK to have duplicates, so just add msgid language
1592 available
.push_back(msgIdLanguage
);
1593 available
.push_back(msgIdLanguage
.BeforeFirst('_'));
1595 wxLogTrace(TRACE_I18N
, "choosing best language for domain '%s'", domain
);
1596 LogTraceArray(" - available translations", available
);
1597 const wxString lang
= GetPreferredUILanguage(available
);
1598 wxLogTrace(TRACE_I18N
, " => using language '%s'", lang
);
1605 WX_DECLARE_HASH_SET(wxString
, wxStringHash
, wxStringEqual
,
1606 wxLocaleUntranslatedStrings
);
1610 const wxString
& wxTranslations::GetUntranslatedString(const wxString
& str
)
1612 static wxLocaleUntranslatedStrings s_strings
;
1614 wxLocaleUntranslatedStrings::iterator i
= s_strings
.find(str
);
1615 if ( i
== s_strings
.end() )
1616 return *s_strings
.insert(str
).first
;
1622 const wxString
& wxTranslations::GetString(const wxString
& origString
,
1623 const wxString
& domain
) const
1625 return GetString(origString
, origString
, UINT_MAX
, domain
);
1628 const wxString
& wxTranslations::GetString(const wxString
& origString
,
1629 const wxString
& origString2
,
1631 const wxString
& domain
) const
1633 if ( origString
.empty() )
1634 return GetUntranslatedString(origString
);
1636 const wxString
*trans
= NULL
;
1637 wxMsgCatalog
*pMsgCat
;
1639 if ( !domain
.empty() )
1641 pMsgCat
= FindCatalog(domain
);
1643 // does the catalog exist?
1644 if ( pMsgCat
!= NULL
)
1645 trans
= pMsgCat
->GetString(origString
, n
);
1649 // search in all domains
1650 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
)
1652 trans
= pMsgCat
->GetString(origString
, n
);
1653 if ( trans
!= NULL
) // take the first found
1658 if ( trans
== NULL
)
1663 "string \"%s\"%s not found in %slocale '%s'.",
1665 (n
!= UINT_MAX
? wxString::Format("[%ld]", (long)n
) : wxString()),
1666 (!domain
.empty() ? wxString::Format("domain '%s' ", domain
) : wxString()),
1671 return GetUntranslatedString(origString
);
1673 return GetUntranslatedString(n
== 1 ? origString
: origString2
);
1680 wxString
wxTranslations::GetHeaderValue(const wxString
& header
,
1681 const wxString
& domain
) const
1683 if ( header
.empty() )
1684 return wxEmptyString
;
1686 const wxString
*trans
= NULL
;
1687 wxMsgCatalog
*pMsgCat
;
1689 if ( !domain
.empty() )
1691 pMsgCat
= FindCatalog(domain
);
1693 // does the catalog exist?
1694 if ( pMsgCat
== NULL
)
1695 return wxEmptyString
;
1697 trans
= pMsgCat
->GetString(wxEmptyString
, UINT_MAX
);
1701 // search in all domains
1702 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
)
1704 trans
= pMsgCat
->GetString(wxEmptyString
, UINT_MAX
);
1705 if ( trans
!= NULL
) // take the first found
1710 if ( !trans
|| trans
->empty() )
1711 return wxEmptyString
;
1713 size_t found
= trans
->find(header
);
1714 if ( found
== wxString::npos
)
1715 return wxEmptyString
;
1717 found
+= header
.length() + 2 /* ': ' */;
1719 // Every header is separated by \n
1721 size_t endLine
= trans
->find(wxS('\n'), found
);
1722 size_t len
= (endLine
== wxString::npos
) ?
1723 wxString::npos
: (endLine
- found
);
1725 return trans
->substr(found
, len
);
1729 // find catalog by name in a linked list, return NULL if !found
1730 wxMsgCatalog
*wxTranslations::FindCatalog(const wxString
& domain
) const
1732 // linear search in the linked list
1733 wxMsgCatalog
*pMsgCat
;
1734 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
)
1736 if ( pMsgCat
->GetDomain() == domain
)
1743 // ----------------------------------------------------------------------------
1744 // wxFileTranslationsLoader
1745 // ----------------------------------------------------------------------------
1750 // the list of the directories to search for message catalog files
1751 wxArrayString gs_searchPrefixes
;
1753 // return the directories to search for message catalogs under the given
1754 // prefix, separated by wxPATH_SEP
1755 wxString
GetMsgCatalogSubdirs(const wxString
& prefix
, const wxString
& lang
)
1757 // Search first in Unix-standard prefix/lang/LC_MESSAGES, then in
1758 // prefix/lang and finally in just prefix.
1760 // Note that we use LC_MESSAGES on all platforms and not just Unix, because
1761 // it doesn't cost much to look into one more directory and doing it this
1762 // way has two important benefits:
1763 // a) we don't break compatibility with wx-2.6 and older by stopping to
1764 // look in a directory where the catalogs used to be and thus silently
1765 // breaking apps after they are recompiled against the latest wx
1766 // b) it makes it possible to package app's support files in the same
1767 // way on all target platforms
1768 const wxString pathPrefix
= wxFileName(prefix
, lang
).GetFullPath();
1770 wxString searchPath
;
1771 searchPath
.reserve(4*pathPrefix
.length());
1772 searchPath
<< pathPrefix
<< wxFILE_SEP_PATH
<< "LC_MESSAGES" << wxPATH_SEP
1773 << prefix
<< wxFILE_SEP_PATH
<< wxPATH_SEP
1779 bool HasMsgCatalogInDir(const wxString
& dir
, const wxString
& domain
)
1781 return wxFileName(dir
, domain
, "mo").FileExists() ||
1782 wxFileName(dir
+ wxFILE_SEP_PATH
+ "LC_MESSAGES", domain
, "mo").FileExists();
1785 // get prefixes to locale directories; if lang is empty, don't point to
1786 // OSX's .lproj bundles
1787 wxArrayString
GetSearchPrefixes(const wxString
& lang
= wxString())
1789 wxArrayString paths
;
1791 // first take the entries explicitly added by the program
1792 paths
= gs_searchPrefixes
;
1795 // then look in the standard location
1799 stdp
= wxStandardPaths::Get().GetResourcesDir();
1803 stdp
= wxStandardPaths::Get().
1804 GetLocalizedResourcesDir(lang
, wxStandardPaths::ResourceCat_Messages
);
1806 if ( paths
.Index(stdp
) == wxNOT_FOUND
)
1808 #endif // wxUSE_STDPATHS
1810 // last look in default locations
1812 // LC_PATH is a standard env var containing the search path for the .mo
1814 const char *pszLcPath
= wxGetenv("LC_PATH");
1817 const wxString lcp
= pszLcPath
;
1818 if ( paths
.Index(lcp
) == wxNOT_FOUND
)
1822 // also add the one from where wxWin was installed:
1823 wxString wxp
= wxGetInstallPrefix();
1826 wxp
+= wxS("/share/locale");
1827 if ( paths
.Index(wxp
) == wxNOT_FOUND
)
1835 // construct the search path for the given language
1836 wxString
GetFullSearchPath(const wxString
& lang
)
1838 wxString searchPath
;
1839 searchPath
.reserve(500);
1841 const wxArrayString prefixes
= GetSearchPrefixes(lang
);
1843 for ( wxArrayString::const_iterator i
= prefixes
.begin();
1844 i
!= prefixes
.end();
1847 const wxString p
= GetMsgCatalogSubdirs(*i
, lang
);
1849 if ( !searchPath
.empty() )
1850 searchPath
+= wxPATH_SEP
;
1857 } // anonymous namespace
1860 void wxFileTranslationsLoader::AddCatalogLookupPathPrefix(const wxString
& prefix
)
1862 if ( gs_searchPrefixes
.Index(prefix
) == wxNOT_FOUND
)
1864 gs_searchPrefixes
.Add(prefix
);
1866 //else: already have it
1870 wxMsgCatalog
*wxFileTranslationsLoader::LoadCatalog(const wxString
& domain
,
1871 const wxString
& lang
)
1873 wxString searchPath
= GetFullSearchPath(lang
);
1875 wxLogTrace(TRACE_I18N
, wxS("Looking for \"%s.mo\" in search path \"%s\""),
1876 domain
, searchPath
);
1878 wxFileName
fn(domain
);
1879 fn
.SetExt(wxS("mo"));
1881 wxString strFullName
;
1882 if ( !wxFindFileInPath(&strFullName
, searchPath
, fn
.GetFullPath()) )
1885 // open file and read its data
1886 wxLogVerbose(_("using catalog '%s' from '%s'."), domain
, strFullName
.c_str());
1887 wxLogTrace(TRACE_I18N
, wxS("Using catalog \"%s\"."), strFullName
.c_str());
1889 return wxMsgCatalog::CreateFromFile(strFullName
, domain
);
1893 wxArrayString
wxFileTranslationsLoader::GetAvailableTranslations(const wxString
& domain
) const
1895 wxArrayString langs
;
1896 const wxArrayString prefixes
= GetSearchPrefixes();
1898 wxLogTrace(TRACE_I18N
,
1899 "looking for available translations of \"%s\" in search path \"%s\"",
1900 domain
, wxJoin(prefixes
, wxPATH_SEP
[0]));
1902 for ( wxArrayString::const_iterator i
= prefixes
.begin();
1903 i
!= prefixes
.end();
1909 if ( !dir
.Open(*i
) )
1913 for ( bool ok
= dir
.GetFirst(&lang
, "", wxDIR_DIRS
);
1915 ok
= dir
.GetNext(&lang
) )
1917 const wxString langdir
= *i
+ wxFILE_SEP_PATH
+ lang
;
1918 if ( HasMsgCatalogInDir(langdir
, domain
) )
1922 if ( lang
.EndsWith(".lproj", &rest
) )
1926 wxLogTrace(TRACE_I18N
,
1927 "found %s translation of \"%s\"", lang
, domain
);
1928 langs
.push_back(lang
);
1937 // ----------------------------------------------------------------------------
1938 // wxResourceTranslationsLoader
1939 // ----------------------------------------------------------------------------
1943 wxMsgCatalog
*wxResourceTranslationsLoader::LoadCatalog(const wxString
& domain
,
1944 const wxString
& lang
)
1946 const void *mo_data
= NULL
;
1949 const wxString resname
= wxString::Format("%s_%s", domain
, lang
);
1951 if ( !wxLoadUserResource(&mo_data
, &mo_size
,
1953 GetResourceType().t_str(),
1957 wxLogTrace(TRACE_I18N
,
1958 "Using catalog from Windows resource \"%s\".", resname
);
1960 wxMsgCatalog
*cat
= wxMsgCatalog::CreateFromData(
1961 wxCharBuffer::CreateNonOwned(static_cast<const char*>(mo_data
), mo_size
),
1966 wxLogWarning(_("Resource '%s' is not a valid message catalog."), resname
);
1975 struct EnumCallbackData
1978 wxArrayString langs
;
1981 BOOL CALLBACK
EnumTranslations(HMODULE
WXUNUSED(hModule
),
1982 LPCTSTR
WXUNUSED(lpszType
),
1986 wxString
name(lpszName
);
1987 name
.MakeLower(); // resource names are case insensitive
1989 EnumCallbackData
*data
= reinterpret_cast<EnumCallbackData
*>(lParam
);
1992 if ( name
.StartsWith(data
->prefix
, &lang
) && !lang
.empty() )
1993 data
->langs
.push_back(lang
);
1995 return TRUE
; // continue enumeration
1998 } // anonymous namespace
2001 wxArrayString
wxResourceTranslationsLoader::GetAvailableTranslations(const wxString
& domain
) const
2003 EnumCallbackData data
;
2004 data
.prefix
= domain
+ "_";
2005 data
.prefix
.MakeLower(); // resource names are case insensitive
2007 if ( !EnumResourceNames(GetModule(),
2008 GetResourceType().t_str(),
2010 reinterpret_cast<LONG_PTR
>(&data
)) )
2012 const DWORD err
= GetLastError();
2013 if ( err
!= NO_ERROR
&& err
!= ERROR_RESOURCE_TYPE_NOT_FOUND
)
2015 wxLogSysError(_("Couldn't enumerate translations"));
2022 #endif // __WINDOWS__
2025 // ----------------------------------------------------------------------------
2026 // wxTranslationsModule module (for destruction of gs_translations)
2027 // ----------------------------------------------------------------------------
2029 class wxTranslationsModule
: public wxModule
2031 DECLARE_DYNAMIC_CLASS(wxTranslationsModule
)
2033 wxTranslationsModule() {}
2042 if ( gs_translationsOwned
)
2043 delete gs_translations
;
2044 gs_translations
= NULL
;
2045 gs_translationsOwned
= true;
2049 IMPLEMENT_DYNAMIC_CLASS(wxTranslationsModule
, wxModule
)
2051 #endif // wxUSE_INTL