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)
8 // Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 // ============================================================================
14 // ============================================================================
16 // ----------------------------------------------------------------------------
18 // ----------------------------------------------------------------------------
20 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
30 #include "wx/dynarray.h"
31 #include "wx/string.h"
35 #include "wx/hashmap.h"
36 #include "wx/module.h"
43 #include "wx/arrstr.h"
46 #include "wx/filename.h"
47 #include "wx/tokenzr.h"
48 #include "wx/fontmap.h"
49 #include "wx/stdpaths.h"
50 #include "wx/hashset.h"
53 #include "wx/dynlib.h"
54 #include "wx/scopedarray.h"
55 #include "wx/msw/wrapwin.h"
56 #include "wx/msw/missing.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 void LogTraceLargeArray(const wxString
& prefix
, const wxArrayString
& arr
)
105 wxLogTrace(TRACE_I18N
, "%s:", prefix
);
106 for ( wxArrayString::const_iterator i
= arr
.begin(); i
!= arr
.end(); ++i
)
107 wxLogTrace(TRACE_I18N
, " %s", *i
);
110 // Use locale-based detection as a fallback
111 wxString
GetPreferredUILanguageFallback(const wxArrayString
& WXUNUSED(available
))
113 const wxString lang
= wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage());
114 wxLogTrace(TRACE_I18N
, " - obtained best language from locale: %s", lang
);
120 wxString
GetPreferredUILanguage(const wxArrayString
& available
)
122 typedef BOOL (WINAPI
*GetUserPreferredUILanguages_t
)(DWORD
, PULONG
, PWSTR
, PULONG
);
123 static GetUserPreferredUILanguages_t s_pfnGetUserPreferredUILanguages
= NULL
;
124 static bool s_initDone
= false;
127 wxLoadedDLL
dllKernel32("kernel32.dll");
128 wxDL_INIT_FUNC(s_pfn
, GetUserPreferredUILanguages
, dllKernel32
);
132 if ( s_pfnGetUserPreferredUILanguages
)
135 ULONG bufferSize
= 0;
136 if ( (*s_pfnGetUserPreferredUILanguages
)(MUI_LANGUAGE_NAME
,
141 wxScopedArray
<WCHAR
> langs(new WCHAR
[bufferSize
]);
142 if ( (*s_pfnGetUserPreferredUILanguages
)(MUI_LANGUAGE_NAME
,
147 wxArrayString preferred
;
149 WCHAR
*buf
= langs
.get();
150 for ( unsigned i
= 0; i
< numLangs
; i
++ )
152 const wxString
lang(buf
);
153 preferred
.push_back(lang
);
154 buf
+= lang
.length() + 1;
156 LogTraceArray(" - system preferred languages", preferred
);
158 for ( wxArrayString::const_iterator j
= preferred
.begin();
159 j
!= preferred
.end();
163 lang
.Replace("-", "_");
164 if ( available
.Index(lang
, /*bCase=*/false) != wxNOT_FOUND
)
166 size_t pos
= lang
.find('_');
167 if ( pos
!= wxString::npos
)
169 lang
= lang
.substr(0, pos
);
170 if ( available
.Index(lang
, /*bCase=*/false) != wxNOT_FOUND
)
178 return GetPreferredUILanguageFallback(available
);
181 #elif defined(__WXOSX__)
183 void LogTraceArray(const char *prefix
, CFArrayRef arr
)
186 const unsigned count
= CFArrayGetCount(arr
);
189 s
+= wxCFStringRef::AsString((CFStringRef
)CFArrayGetValueAtIndex(arr
, 0));
190 for ( unsigned i
= 1 ; i
< count
; i
++ )
191 s
+= "," + wxCFStringRef::AsString((CFStringRef
)CFArrayGetValueAtIndex(arr
, i
));
193 wxLogTrace(TRACE_I18N
, "%s: [%s]", prefix
, s
);
196 wxString
GetPreferredUILanguage(const wxArrayString
& available
)
198 wxStringToStringHashMap availableNormalized
;
199 wxCFRef
<CFMutableArrayRef
> availableArr(
200 CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
));
202 for ( wxArrayString::const_iterator i
= available
.begin();
203 i
!= available
.end();
207 wxCFStringRef
code_wx(*i
);
208 wxCFStringRef
code_norm(
209 CFLocaleCreateCanonicalLanguageIdentifierFromString(kCFAllocatorDefault
, code_wx
));
210 CFArrayAppendValue(availableArr
, code_norm
);
211 availableNormalized
[code_norm
.AsString()] = *i
;
213 LogTraceArray(" - normalized available list", availableArr
);
215 wxCFRef
<CFArrayRef
> prefArr(
216 CFBundleCopyLocalizationsForPreferences(availableArr
, NULL
));
217 LogTraceArray(" - system preferred languages", prefArr
);
219 unsigned prefArrLength
= CFArrayGetCount(prefArr
);
220 if ( prefArrLength
> 0 )
222 // Lookup the name in 'available' by index -- we need to get the
223 // original value corresponding to the normalized one chosen.
224 wxString
lang(wxCFStringRef::AsString((CFStringRef
)CFArrayGetValueAtIndex(prefArr
, 0)));
225 wxStringToStringHashMap::const_iterator i
= availableNormalized
.find(lang
);
226 if ( i
== availableNormalized
.end() )
232 return GetPreferredUILanguageFallback(available
);
237 // On Unix, there's just one language=locale setting, so we should always
239 #define GetPreferredUILanguage GetPreferredUILanguageFallback
243 } // anonymous namespace
245 // ----------------------------------------------------------------------------
246 // Plural forms parser
247 // ----------------------------------------------------------------------------
253 LogicalOrExpression '?' Expression ':' Expression
257 LogicalAndExpression "||" LogicalOrExpression // to (a || b) || c
260 LogicalAndExpression:
261 EqualityExpression "&&" LogicalAndExpression // to (a && b) && c
265 RelationalExpression "==" RelationalExperession
266 RelationalExpression "!=" RelationalExperession
269 RelationalExpression:
270 MultiplicativeExpression '>' MultiplicativeExpression
271 MultiplicativeExpression '<' MultiplicativeExpression
272 MultiplicativeExpression ">=" MultiplicativeExpression
273 MultiplicativeExpression "<=" MultiplicativeExpression
274 MultiplicativeExpression
276 MultiplicativeExpression:
277 PmExpression '%' PmExpression
286 class wxPluralFormsToken
291 T_ERROR
, T_EOF
, T_NUMBER
, T_N
, T_PLURAL
, T_NPLURALS
, T_EQUAL
, T_ASSIGN
,
292 T_GREATER
, T_GREATER_OR_EQUAL
, T_LESS
, T_LESS_OR_EQUAL
,
293 T_REMINDER
, T_NOT_EQUAL
,
294 T_LOGICAL_AND
, T_LOGICAL_OR
, T_QUESTION
, T_COLON
, T_SEMICOLON
,
295 T_LEFT_BRACKET
, T_RIGHT_BRACKET
297 Type
type() const { return m_type
; }
298 void setType(Type t
) { m_type
= t
; }
301 Number
number() const { return m_number
; }
302 void setNumber(Number num
) { m_number
= num
; }
309 class wxPluralFormsScanner
312 wxPluralFormsScanner(const char* s
);
313 const wxPluralFormsToken
& token() const { return m_token
; }
314 bool nextToken(); // returns false if error
317 wxPluralFormsToken m_token
;
320 wxPluralFormsScanner::wxPluralFormsScanner(const char* s
) : m_s(s
)
325 bool wxPluralFormsScanner::nextToken()
327 wxPluralFormsToken::Type type
= wxPluralFormsToken::T_ERROR
;
328 while (isspace((unsigned char) *m_s
))
334 type
= wxPluralFormsToken::T_EOF
;
336 else if (isdigit((unsigned char) *m_s
))
338 wxPluralFormsToken::Number number
= *m_s
++ - '0';
339 while (isdigit((unsigned char) *m_s
))
341 number
= number
* 10 + (*m_s
++ - '0');
343 m_token
.setNumber(number
);
344 type
= wxPluralFormsToken::T_NUMBER
;
346 else if (isalpha((unsigned char) *m_s
))
348 const char* begin
= m_s
++;
349 while (isalnum((unsigned char) *m_s
))
353 size_t size
= m_s
- begin
;
354 if (size
== 1 && memcmp(begin
, "n", size
) == 0)
356 type
= wxPluralFormsToken::T_N
;
358 else if (size
== 6 && memcmp(begin
, "plural", size
) == 0)
360 type
= wxPluralFormsToken::T_PLURAL
;
362 else if (size
== 8 && memcmp(begin
, "nplurals", size
) == 0)
364 type
= wxPluralFormsToken::T_NPLURALS
;
367 else if (*m_s
== '=')
373 type
= wxPluralFormsToken::T_EQUAL
;
377 type
= wxPluralFormsToken::T_ASSIGN
;
380 else if (*m_s
== '>')
386 type
= wxPluralFormsToken::T_GREATER_OR_EQUAL
;
390 type
= wxPluralFormsToken::T_GREATER
;
393 else if (*m_s
== '<')
399 type
= wxPluralFormsToken::T_LESS_OR_EQUAL
;
403 type
= wxPluralFormsToken::T_LESS
;
406 else if (*m_s
== '%')
409 type
= wxPluralFormsToken::T_REMINDER
;
411 else if (*m_s
== '!' && m_s
[1] == '=')
414 type
= wxPluralFormsToken::T_NOT_EQUAL
;
416 else if (*m_s
== '&' && m_s
[1] == '&')
419 type
= wxPluralFormsToken::T_LOGICAL_AND
;
421 else if (*m_s
== '|' && m_s
[1] == '|')
424 type
= wxPluralFormsToken::T_LOGICAL_OR
;
426 else if (*m_s
== '?')
429 type
= wxPluralFormsToken::T_QUESTION
;
431 else if (*m_s
== ':')
434 type
= wxPluralFormsToken::T_COLON
;
435 } else if (*m_s
== ';') {
437 type
= wxPluralFormsToken::T_SEMICOLON
;
439 else if (*m_s
== '(')
442 type
= wxPluralFormsToken::T_LEFT_BRACKET
;
444 else if (*m_s
== ')')
447 type
= wxPluralFormsToken::T_RIGHT_BRACKET
;
449 m_token
.setType(type
);
450 return type
!= wxPluralFormsToken::T_ERROR
;
453 class wxPluralFormsNode
;
455 // NB: Can't use wxDEFINE_SCOPED_PTR_TYPE because wxPluralFormsNode is not
456 // fully defined yet:
457 class wxPluralFormsNodePtr
460 wxPluralFormsNodePtr(wxPluralFormsNode
*p
= NULL
) : m_p(p
) {}
461 ~wxPluralFormsNodePtr();
462 wxPluralFormsNode
& operator*() const { return *m_p
; }
463 wxPluralFormsNode
* operator->() const { return m_p
; }
464 wxPluralFormsNode
* get() const { return m_p
; }
465 wxPluralFormsNode
* release();
466 void reset(wxPluralFormsNode
*p
);
469 wxPluralFormsNode
*m_p
;
472 class wxPluralFormsNode
475 wxPluralFormsNode(const wxPluralFormsToken
& t
) : m_token(t
) {}
476 const wxPluralFormsToken
& token() const { return m_token
; }
477 const wxPluralFormsNode
* node(unsigned i
) const
478 { return m_nodes
[i
].get(); }
479 void setNode(unsigned i
, wxPluralFormsNode
* n
);
480 wxPluralFormsNode
* releaseNode(unsigned i
);
481 wxPluralFormsToken::Number
evaluate(wxPluralFormsToken::Number n
) const;
484 wxPluralFormsToken m_token
;
485 wxPluralFormsNodePtr m_nodes
[3];
488 wxPluralFormsNodePtr::~wxPluralFormsNodePtr()
492 wxPluralFormsNode
* wxPluralFormsNodePtr::release()
494 wxPluralFormsNode
*p
= m_p
;
498 void wxPluralFormsNodePtr::reset(wxPluralFormsNode
*p
)
508 void wxPluralFormsNode::setNode(unsigned i
, wxPluralFormsNode
* n
)
513 wxPluralFormsNode
* wxPluralFormsNode::releaseNode(unsigned i
)
515 return m_nodes
[i
].release();
518 wxPluralFormsToken::Number
519 wxPluralFormsNode::evaluate(wxPluralFormsToken::Number n
) const
521 switch (token().type())
524 case wxPluralFormsToken::T_NUMBER
:
525 return token().number();
526 case wxPluralFormsToken::T_N
:
529 case wxPluralFormsToken::T_EQUAL
:
530 return node(0)->evaluate(n
) == node(1)->evaluate(n
);
531 case wxPluralFormsToken::T_NOT_EQUAL
:
532 return node(0)->evaluate(n
) != node(1)->evaluate(n
);
533 case wxPluralFormsToken::T_GREATER
:
534 return node(0)->evaluate(n
) > node(1)->evaluate(n
);
535 case wxPluralFormsToken::T_GREATER_OR_EQUAL
:
536 return node(0)->evaluate(n
) >= node(1)->evaluate(n
);
537 case wxPluralFormsToken::T_LESS
:
538 return node(0)->evaluate(n
) < node(1)->evaluate(n
);
539 case wxPluralFormsToken::T_LESS_OR_EQUAL
:
540 return node(0)->evaluate(n
) <= node(1)->evaluate(n
);
541 case wxPluralFormsToken::T_REMINDER
:
543 wxPluralFormsToken::Number number
= node(1)->evaluate(n
);
546 return node(0)->evaluate(n
) % number
;
553 case wxPluralFormsToken::T_LOGICAL_AND
:
554 return node(0)->evaluate(n
) && node(1)->evaluate(n
);
555 case wxPluralFormsToken::T_LOGICAL_OR
:
556 return node(0)->evaluate(n
) || node(1)->evaluate(n
);
558 case wxPluralFormsToken::T_QUESTION
:
559 return node(0)->evaluate(n
)
560 ? node(1)->evaluate(n
)
561 : node(2)->evaluate(n
);
568 class wxPluralFormsCalculator
571 wxPluralFormsCalculator() : m_nplurals(0), m_plural(0) {}
573 // input: number, returns msgstr index
574 int evaluate(int n
) const;
576 // input: text after "Plural-Forms:" (e.g. "nplurals=2; plural=(n != 1);"),
577 // if s == 0, creates default handler
578 // returns 0 if error
579 static wxPluralFormsCalculator
* make(const char* s
= 0);
581 ~wxPluralFormsCalculator() {}
583 void init(wxPluralFormsToken::Number nplurals
, wxPluralFormsNode
* plural
);
586 wxPluralFormsToken::Number m_nplurals
;
587 wxPluralFormsNodePtr m_plural
;
590 wxDEFINE_SCOPED_PTR(wxPluralFormsCalculator
, wxPluralFormsCalculatorPtr
)
592 void wxPluralFormsCalculator::init(wxPluralFormsToken::Number nplurals
,
593 wxPluralFormsNode
* plural
)
595 m_nplurals
= nplurals
;
596 m_plural
.reset(plural
);
599 int wxPluralFormsCalculator::evaluate(int n
) const
601 if (m_plural
.get() == 0)
605 wxPluralFormsToken::Number number
= m_plural
->evaluate(n
);
606 if (number
< 0 || number
> m_nplurals
)
614 class wxPluralFormsParser
617 wxPluralFormsParser(wxPluralFormsScanner
& scanner
) : m_scanner(scanner
) {}
618 bool parse(wxPluralFormsCalculator
& rCalculator
);
621 wxPluralFormsNode
* parsePlural();
622 // stops at T_SEMICOLON, returns 0 if error
623 wxPluralFormsScanner
& m_scanner
;
624 const wxPluralFormsToken
& token() const;
627 wxPluralFormsNode
* expression();
628 wxPluralFormsNode
* logicalOrExpression();
629 wxPluralFormsNode
* logicalAndExpression();
630 wxPluralFormsNode
* equalityExpression();
631 wxPluralFormsNode
* multiplicativeExpression();
632 wxPluralFormsNode
* relationalExpression();
633 wxPluralFormsNode
* pmExpression();
636 bool wxPluralFormsParser::parse(wxPluralFormsCalculator
& rCalculator
)
638 if (token().type() != wxPluralFormsToken::T_NPLURALS
)
642 if (token().type() != wxPluralFormsToken::T_ASSIGN
)
646 if (token().type() != wxPluralFormsToken::T_NUMBER
)
648 wxPluralFormsToken::Number nplurals
= token().number();
651 if (token().type() != wxPluralFormsToken::T_SEMICOLON
)
655 if (token().type() != wxPluralFormsToken::T_PLURAL
)
659 if (token().type() != wxPluralFormsToken::T_ASSIGN
)
663 wxPluralFormsNode
* plural
= parsePlural();
666 if (token().type() != wxPluralFormsToken::T_SEMICOLON
)
670 if (token().type() != wxPluralFormsToken::T_EOF
)
672 rCalculator
.init(nplurals
, plural
);
676 wxPluralFormsNode
* wxPluralFormsParser::parsePlural()
678 wxPluralFormsNode
* p
= expression();
683 wxPluralFormsNodePtr
n(p
);
684 if (token().type() != wxPluralFormsToken::T_SEMICOLON
)
691 const wxPluralFormsToken
& wxPluralFormsParser::token() const
693 return m_scanner
.token();
696 bool wxPluralFormsParser::nextToken()
698 if (!m_scanner
.nextToken())
703 wxPluralFormsNode
* wxPluralFormsParser::expression()
705 wxPluralFormsNode
* p
= logicalOrExpression();
708 wxPluralFormsNodePtr
n(p
);
709 if (token().type() == wxPluralFormsToken::T_QUESTION
)
711 wxPluralFormsNodePtr
qn(new wxPluralFormsNode(token()));
722 if (token().type() != wxPluralFormsToken::T_COLON
)
736 qn
->setNode(0, n
.release());
742 wxPluralFormsNode
*wxPluralFormsParser::logicalOrExpression()
744 wxPluralFormsNode
* p
= logicalAndExpression();
747 wxPluralFormsNodePtr
ln(p
);
748 if (token().type() == wxPluralFormsToken::T_LOGICAL_OR
)
750 wxPluralFormsNodePtr
un(new wxPluralFormsNode(token()));
755 p
= logicalOrExpression();
760 wxPluralFormsNodePtr
rn(p
); // right
761 if (rn
->token().type() == wxPluralFormsToken::T_LOGICAL_OR
)
763 // see logicalAndExpression comment
764 un
->setNode(0, ln
.release());
765 un
->setNode(1, rn
->releaseNode(0));
766 rn
->setNode(0, un
.release());
771 un
->setNode(0, ln
.release());
772 un
->setNode(1, rn
.release());
778 wxPluralFormsNode
* wxPluralFormsParser::logicalAndExpression()
780 wxPluralFormsNode
* p
= equalityExpression();
783 wxPluralFormsNodePtr
ln(p
); // left
784 if (token().type() == wxPluralFormsToken::T_LOGICAL_AND
)
786 wxPluralFormsNodePtr
un(new wxPluralFormsNode(token())); // up
791 p
= logicalAndExpression();
796 wxPluralFormsNodePtr
rn(p
); // right
797 if (rn
->token().type() == wxPluralFormsToken::T_LOGICAL_AND
)
799 // transform 1 && (2 && 3) -> (1 && 2) && 3
803 un
->setNode(0, ln
.release());
804 un
->setNode(1, rn
->releaseNode(0));
805 rn
->setNode(0, un
.release());
809 un
->setNode(0, ln
.release());
810 un
->setNode(1, rn
.release());
816 wxPluralFormsNode
* wxPluralFormsParser::equalityExpression()
818 wxPluralFormsNode
* p
= relationalExpression();
821 wxPluralFormsNodePtr
n(p
);
822 if (token().type() == wxPluralFormsToken::T_EQUAL
823 || token().type() == wxPluralFormsToken::T_NOT_EQUAL
)
825 wxPluralFormsNodePtr
qn(new wxPluralFormsNode(token()));
830 p
= relationalExpression();
836 qn
->setNode(0, n
.release());
842 wxPluralFormsNode
* wxPluralFormsParser::relationalExpression()
844 wxPluralFormsNode
* p
= multiplicativeExpression();
847 wxPluralFormsNodePtr
n(p
);
848 if (token().type() == wxPluralFormsToken::T_GREATER
849 || token().type() == wxPluralFormsToken::T_LESS
850 || token().type() == wxPluralFormsToken::T_GREATER_OR_EQUAL
851 || token().type() == wxPluralFormsToken::T_LESS_OR_EQUAL
)
853 wxPluralFormsNodePtr
qn(new wxPluralFormsNode(token()));
858 p
= multiplicativeExpression();
864 qn
->setNode(0, n
.release());
870 wxPluralFormsNode
* wxPluralFormsParser::multiplicativeExpression()
872 wxPluralFormsNode
* p
= pmExpression();
875 wxPluralFormsNodePtr
n(p
);
876 if (token().type() == wxPluralFormsToken::T_REMINDER
)
878 wxPluralFormsNodePtr
qn(new wxPluralFormsNode(token()));
889 qn
->setNode(0, n
.release());
895 wxPluralFormsNode
* wxPluralFormsParser::pmExpression()
897 wxPluralFormsNodePtr n
;
898 if (token().type() == wxPluralFormsToken::T_N
899 || token().type() == wxPluralFormsToken::T_NUMBER
)
901 n
.reset(new wxPluralFormsNode(token()));
907 else if (token().type() == wxPluralFormsToken::T_LEFT_BRACKET
) {
912 wxPluralFormsNode
* p
= expression();
918 if (token().type() != wxPluralFormsToken::T_RIGHT_BRACKET
)
934 wxPluralFormsCalculator
* wxPluralFormsCalculator::make(const char* s
)
936 wxPluralFormsCalculatorPtr
calculator(new wxPluralFormsCalculator
);
939 wxPluralFormsScanner
scanner(s
);
940 wxPluralFormsParser
p(scanner
);
941 if (!p
.parse(*calculator
))
946 return calculator
.release();
952 // ----------------------------------------------------------------------------
953 // wxMsgCatalogFile corresponds to one disk-file message catalog.
955 // This is a "low-level" class and is used only by wxMsgCatalog
956 // NOTE: for the documentation of the binary catalog (.MO) files refer to
957 // the GNU gettext manual:
958 // http://www.gnu.org/software/autoconf/manual/gettext/MO-Files.html
959 // ----------------------------------------------------------------------------
961 class wxMsgCatalogFile
964 typedef wxScopedCharBuffer DataBuffer
;
970 // load the catalog from disk
971 bool LoadFile(const wxString
& filename
,
972 wxPluralFormsCalculatorPtr
& rPluralFormsCalculator
);
973 bool LoadData(const DataBuffer
& data
,
974 wxPluralFormsCalculatorPtr
& rPluralFormsCalculator
);
976 // fills the hash with string-translation pairs
977 bool FillHash(wxStringToStringHashMap
& hash
, const wxString
& domain
) const;
979 // return the charset of the strings in this catalog or empty string if
981 wxString
GetCharset() const { return m_charset
; }
984 // this implementation is binary compatible with GNU gettext() version 0.10
986 // an entry in the string table
987 struct wxMsgTableEntry
989 size_t32 nLen
; // length of the string
990 size_t32 ofsString
; // pointer to the string
993 // header of a .mo file
994 struct wxMsgCatalogHeader
996 size_t32 magic
, // offset +00: magic id
997 revision
, // +04: revision
998 numStrings
; // +08: number of strings in the file
999 size_t32 ofsOrigTable
, // +0C: start of original string table
1000 ofsTransTable
; // +10: start of translated string table
1001 size_t32 nHashSize
, // +14: hash table size
1002 ofsHashTable
; // +18: offset of hash table start
1005 // all data is stored here
1009 size_t32 m_numStrings
; // number of strings in this domain
1010 wxMsgTableEntry
*m_pOrigTable
, // pointer to original strings
1011 *m_pTransTable
; // translated
1013 wxString m_charset
; // from the message catalog header
1016 // swap the 2 halves of 32 bit integer if needed
1017 size_t32
Swap(size_t32 ui
) const
1019 return m_bSwapped
? (ui
<< 24) | ((ui
& 0xff00) << 8) |
1020 ((ui
>> 8) & 0xff00) | (ui
>> 24)
1024 const char *StringAtOfs(wxMsgTableEntry
*pTable
, size_t32 n
) const
1026 const wxMsgTableEntry
* const ent
= pTable
+ n
;
1028 // this check could fail for a corrupt message catalog
1029 size_t32 ofsString
= Swap(ent
->ofsString
);
1030 if ( ofsString
+ Swap(ent
->nLen
) > m_data
.length())
1035 return m_data
.data() + ofsString
;
1038 bool m_bSwapped
; // wrong endianness?
1040 wxDECLARE_NO_COPY_CLASS(wxMsgCatalogFile
);
1043 // ----------------------------------------------------------------------------
1044 // wxMsgCatalogFile class
1045 // ----------------------------------------------------------------------------
1047 wxMsgCatalogFile::wxMsgCatalogFile()
1051 wxMsgCatalogFile::~wxMsgCatalogFile()
1055 // open disk file and read in it's contents
1056 bool wxMsgCatalogFile::LoadFile(const wxString
& filename
,
1057 wxPluralFormsCalculatorPtr
& rPluralFormsCalculator
)
1059 wxFile
fileMsg(filename
);
1060 if ( !fileMsg
.IsOpened() )
1063 // get the file size (assume it is less than 4GB...)
1064 wxFileOffset lenFile
= fileMsg
.Length();
1065 if ( lenFile
== wxInvalidOffset
)
1068 size_t nSize
= wx_truncate_cast(size_t, lenFile
);
1069 wxASSERT_MSG( nSize
== lenFile
+ size_t(0), wxS("message catalog bigger than 4GB?") );
1071 wxMemoryBuffer filedata
;
1073 // read the whole file in memory
1074 if ( fileMsg
.Read(filedata
.GetWriteBuf(nSize
), nSize
) != lenFile
)
1077 filedata
.UngetWriteBuf(nSize
);
1081 DataBuffer::CreateOwned((char*)filedata
.release(), nSize
),
1082 rPluralFormsCalculator
1086 wxLogWarning(_("'%s' is not a valid message catalog."), filename
.c_str());
1094 bool wxMsgCatalogFile::LoadData(const DataBuffer
& data
,
1095 wxPluralFormsCalculatorPtr
& rPluralFormsCalculator
)
1098 bool bValid
= data
.length() > sizeof(wxMsgCatalogHeader
);
1100 const wxMsgCatalogHeader
*pHeader
= (wxMsgCatalogHeader
*)data
.data();
1102 // we'll have to swap all the integers if it's true
1103 m_bSwapped
= pHeader
->magic
== MSGCATALOG_MAGIC_SW
;
1105 // check the magic number
1106 bValid
= m_bSwapped
|| pHeader
->magic
== MSGCATALOG_MAGIC
;
1110 // it's either too short or has incorrect magic number
1111 wxLogWarning(_("Invalid message catalog."));
1118 m_numStrings
= Swap(pHeader
->numStrings
);
1119 m_pOrigTable
= (wxMsgTableEntry
*)(data
.data() +
1120 Swap(pHeader
->ofsOrigTable
));
1121 m_pTransTable
= (wxMsgTableEntry
*)(data
.data() +
1122 Swap(pHeader
->ofsTransTable
));
1124 // now parse catalog's header and try to extract catalog charset and
1125 // plural forms formula from it:
1127 const char* headerData
= StringAtOfs(m_pOrigTable
, 0);
1128 if ( headerData
&& headerData
[0] == '\0' )
1130 // Extract the charset:
1131 const char * const header
= StringAtOfs(m_pTransTable
, 0);
1133 cset
= strstr(header
, "Content-Type: text/plain; charset=");
1136 cset
+= 34; // strlen("Content-Type: text/plain; charset=")
1138 const char * const csetEnd
= strchr(cset
, '\n');
1141 m_charset
= wxString(cset
, csetEnd
- cset
);
1142 if ( m_charset
== wxS("CHARSET") )
1144 // "CHARSET" is not valid charset, but lazy translator
1149 // else: incorrectly filled Content-Type header
1151 // Extract plural forms:
1152 const char * plurals
= strstr(header
, "Plural-Forms:");
1155 plurals
+= 13; // strlen("Plural-Forms:")
1156 const char * const pluralsEnd
= strchr(plurals
, '\n');
1159 const size_t pluralsLen
= pluralsEnd
- plurals
;
1160 wxCharBuffer
buf(pluralsLen
);
1161 strncpy(buf
.data(), plurals
, pluralsLen
);
1162 wxPluralFormsCalculator
* const
1163 pCalculator
= wxPluralFormsCalculator::make(buf
);
1166 rPluralFormsCalculator
.reset(pCalculator
);
1170 wxLogVerbose(_("Failed to parse Plural-Forms: '%s'"),
1176 if ( !rPluralFormsCalculator
.get() )
1177 rPluralFormsCalculator
.reset(wxPluralFormsCalculator::make());
1180 // everything is fine
1184 bool wxMsgCatalogFile::FillHash(wxStringToStringHashMap
& hash
,
1185 const wxString
& domain
) const
1187 wxUnusedVar(domain
); // silence warning in Unicode build
1189 // conversion to use to convert catalog strings to the GUI encoding
1190 wxMBConv
*inputConv
= NULL
;
1191 wxMBConv
*inputConvPtr
= NULL
; // same as inputConv but safely deleteable
1193 if ( !m_charset
.empty() )
1195 #if !wxUSE_UNICODE && wxUSE_FONTMAP
1196 // determine if we need any conversion at all
1197 wxFontEncoding encCat
= wxFontMapperBase::GetEncodingFromName(m_charset
);
1198 if ( encCat
!= wxLocale::GetSystemEncoding() )
1202 inputConv
= new wxCSConv(m_charset
);
1205 else // no need or not possible to convert the encoding
1208 // we must somehow convert the narrow strings in the message catalog to
1209 // wide strings, so use the default conversion if we have no charset
1210 inputConv
= wxConvCurrent
;
1215 wxString msgIdCharset
= gs_msgIdCharset
[domain
];
1217 // conversion to apply to msgid strings before looking them up: we only
1218 // need it if the msgids are neither in 7 bit ASCII nor in the same
1219 // encoding as the catalog
1220 wxCSConv
*sourceConv
= msgIdCharset
.empty() || (msgIdCharset
== m_charset
)
1222 : new wxCSConv(msgIdCharset
);
1223 #endif // !wxUSE_UNICODE
1225 for (size_t32 i
= 0; i
< m_numStrings
; i
++)
1227 const char *data
= StringAtOfs(m_pOrigTable
, i
);
1229 return false; // may happen for invalid MO files
1233 msgid
= wxString(data
, *inputConv
);
1235 if ( inputConv
&& sourceConv
)
1236 msgid
= wxString(inputConv
->cMB2WC(data
), *sourceConv
);
1239 #endif // wxUSE_UNICODE
1241 data
= StringAtOfs(m_pTransTable
, i
);
1243 return false; // may happen for invalid MO files
1245 size_t length
= Swap(m_pTransTable
[i
].nLen
);
1248 while (offset
< length
)
1250 const char * const str
= data
+ offset
;
1254 msgstr
= wxString(str
, *inputConv
);
1257 msgstr
= wxString(inputConv
->cMB2WC(str
), *wxConvUI
);
1260 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
1262 if ( !msgstr
.empty() )
1264 hash
[index
== 0 ? msgid
: msgid
+ wxChar(index
)] = msgstr
;
1268 // IMPORTANT: accesses to the 'data' pointer are valid only for
1269 // the first 'length+1' bytes (GNU specs says that the
1270 // final NUL is not counted in length); using wxStrnlen()
1271 // we make sure we don't access memory beyond the valid range
1272 // (which otherwise may happen for invalid MO files):
1273 offset
+= wxStrnlen(str
, length
- offset
) + 1;
1281 delete inputConvPtr
;
1287 // ----------------------------------------------------------------------------
1288 // wxMsgCatalog class
1289 // ----------------------------------------------------------------------------
1292 wxMsgCatalog::~wxMsgCatalog()
1296 if ( wxConvUI
== m_conv
)
1298 // we only change wxConvUI if it points to wxConvLocal so we reset
1299 // it back to it too
1300 wxConvUI
= &wxConvLocal
;
1306 #endif // !wxUSE_UNICODE
1309 wxMsgCatalog
*wxMsgCatalog::CreateFromFile(const wxString
& filename
,
1310 const wxString
& domain
)
1312 wxScopedPtr
<wxMsgCatalog
> cat(new wxMsgCatalog(domain
));
1314 wxMsgCatalogFile file
;
1316 if ( !file
.LoadFile(filename
, cat
->m_pluralFormsCalculator
) )
1319 if ( !file
.FillHash(cat
->m_messages
, domain
) )
1322 return cat
.release();
1326 wxMsgCatalog
*wxMsgCatalog::CreateFromData(const wxScopedCharBuffer
& data
,
1327 const wxString
& domain
)
1329 wxScopedPtr
<wxMsgCatalog
> cat(new wxMsgCatalog(domain
));
1331 wxMsgCatalogFile file
;
1333 if ( !file
.LoadData(data
, cat
->m_pluralFormsCalculator
) )
1336 if ( !file
.FillHash(cat
->m_messages
, domain
) )
1339 return cat
.release();
1342 const wxString
*wxMsgCatalog::GetString(const wxString
& str
, unsigned n
) const
1347 index
= m_pluralFormsCalculator
->evaluate(n
);
1349 wxStringToStringHashMap::const_iterator i
;
1352 i
= m_messages
.find(wxString(str
) + wxChar(index
)); // plural
1356 i
= m_messages
.find(str
);
1359 if ( i
!= m_messages
.end() )
1368 // ----------------------------------------------------------------------------
1370 // ----------------------------------------------------------------------------
1375 wxTranslations
*gs_translations
= NULL
;
1376 bool gs_translationsOwned
= false;
1378 } // anonymous namespace
1382 wxTranslations
*wxTranslations::Get()
1384 return gs_translations
;
1388 void wxTranslations::Set(wxTranslations
*t
)
1390 if ( gs_translationsOwned
)
1391 delete gs_translations
;
1392 gs_translations
= t
;
1393 gs_translationsOwned
= true;
1397 void wxTranslations::SetNonOwned(wxTranslations
*t
)
1399 if ( gs_translationsOwned
)
1400 delete gs_translations
;
1401 gs_translations
= t
;
1402 gs_translationsOwned
= false;
1406 wxTranslations::wxTranslations()
1409 m_loader
= new wxFileTranslationsLoader
;
1413 wxTranslations::~wxTranslations()
1417 // free catalogs memory
1418 wxMsgCatalog
*pTmpCat
;
1419 while ( m_pMsgCat
!= NULL
)
1421 pTmpCat
= m_pMsgCat
;
1422 m_pMsgCat
= m_pMsgCat
->m_pNext
;
1428 void wxTranslations::SetLoader(wxTranslationsLoader
*loader
)
1430 wxCHECK_RET( loader
, "loader can't be NULL" );
1437 void wxTranslations::SetLanguage(wxLanguage lang
)
1439 if ( lang
== wxLANGUAGE_DEFAULT
)
1442 SetLanguage(wxLocale::GetLanguageCanonicalName(lang
));
1445 void wxTranslations::SetLanguage(const wxString
& lang
)
1451 wxArrayString
wxTranslations::GetAvailableTranslations(const wxString
& domain
) const
1453 wxCHECK_MSG( m_loader
, wxArrayString(), "loader can't be NULL" );
1455 return m_loader
->GetAvailableTranslations(domain
);
1459 bool wxTranslations::AddStdCatalog()
1461 if ( !AddCatalog(wxS("wxstd")) )
1464 // there may be a catalog with toolkit specific overrides, it is not
1465 // an error if this does not exist
1466 wxString
port(wxPlatformInfo::Get().GetPortIdName());
1467 if ( !port
.empty() )
1469 AddCatalog(port
.BeforeFirst(wxS('/')).MakeLower());
1476 bool wxTranslations::AddCatalog(const wxString
& domain
)
1478 return AddCatalog(domain
, wxLANGUAGE_ENGLISH_US
);
1482 bool wxTranslations::AddCatalog(const wxString
& domain
,
1483 wxLanguage msgIdLanguage
,
1484 const wxString
& msgIdCharset
)
1486 gs_msgIdCharset
[domain
] = msgIdCharset
;
1487 return AddCatalog(domain
, msgIdLanguage
);
1489 #endif // !wxUSE_UNICODE
1491 bool wxTranslations::AddCatalog(const wxString
& domain
,
1492 wxLanguage msgIdLanguage
)
1494 const wxString msgIdLang
= wxLocale::GetLanguageCanonicalName(msgIdLanguage
);
1495 const wxString domain_lang
= GetBestTranslation(domain
, msgIdLang
);
1497 if ( domain_lang
.empty() )
1499 wxLogTrace(TRACE_I18N
,
1500 wxS("no suitable translation for domain '%s' found"),
1505 wxLogTrace(TRACE_I18N
,
1506 wxS("adding '%s' translation for domain '%s' (msgid language '%s')"),
1507 domain_lang
, domain
, msgIdLang
);
1509 // It is OK to not load catalog if the msgid language and m_language match,
1510 // in which case we can directly display the texts embedded in program's
1512 if ( msgIdLang
== domain_lang
)
1515 return LoadCatalog(domain
, domain_lang
);
1519 bool wxTranslations::LoadCatalog(const wxString
& domain
, const wxString
& lang
)
1521 wxCHECK_MSG( m_loader
, false, "loader can't be NULL" );
1523 wxMsgCatalog
*cat
= NULL
;
1526 // first look for the catalog for this language and the current locale:
1527 // notice that we don't use the system name for the locale as this would
1528 // force us to install catalogs in different locations depending on the
1529 // system but always use the canonical name
1530 wxFontEncoding encSys
= wxLocale::GetSystemEncoding();
1531 if ( encSys
!= wxFONTENCODING_SYSTEM
)
1533 wxString
fullname(lang
);
1534 fullname
<< wxS('.') << wxFontMapperBase::GetEncodingName(encSys
);
1536 cat
= m_loader
->LoadCatalog(domain
, fullname
);
1538 #endif // wxUSE_FONTMAP
1542 // Next try: use the provided name language name:
1543 cat
= m_loader
->LoadCatalog(domain
, lang
);
1548 // Also try just base locale name: for things like "fr_BE" (Belgium
1549 // French) we should use fall back on plain "fr" if no Belgium-specific
1550 // message catalogs exist
1551 wxString baselang
= lang
.BeforeFirst('_');
1552 if ( lang
!= baselang
)
1553 cat
= m_loader
->LoadCatalog(domain
, baselang
);
1558 // add it to the head of the list so that in GetString it will
1559 // be searched before the catalogs added earlier
1560 cat
->m_pNext
= m_pMsgCat
;
1567 // Nothing worked, the catalog just isn't there
1568 wxLogTrace(TRACE_I18N
,
1569 "Catalog \"%s.mo\" not found for language \"%s\".",
1575 // check if the given catalog is loaded
1576 bool wxTranslations::IsLoaded(const wxString
& domain
) const
1578 return FindCatalog(domain
) != NULL
;
1581 wxString
wxTranslations::GetBestTranslation(const wxString
& domain
,
1582 wxLanguage msgIdLanguage
)
1584 const wxString lang
= wxLocale::GetLanguageCanonicalName(msgIdLanguage
);
1585 return GetBestTranslation(domain
, lang
);
1588 wxString
wxTranslations::GetBestTranslation(const wxString
& domain
,
1589 const wxString
& msgIdLanguage
)
1591 // explicitly set language should always be respected
1592 if ( !m_lang
.empty() )
1595 wxArrayString
available(GetAvailableTranslations(domain
));
1596 // it's OK to have duplicates, so just add msgid language
1597 available
.push_back(msgIdLanguage
);
1598 available
.push_back(msgIdLanguage
.BeforeFirst('_'));
1600 wxLogTrace(TRACE_I18N
, "choosing best language for domain '%s'", domain
);
1601 LogTraceArray(" - available translations", available
);
1602 const wxString lang
= GetPreferredUILanguage(available
);
1603 wxLogTrace(TRACE_I18N
, " => using language '%s'", lang
);
1610 WX_DECLARE_HASH_SET(wxString
, wxStringHash
, wxStringEqual
,
1611 wxLocaleUntranslatedStrings
);
1615 const wxString
& wxTranslations::GetUntranslatedString(const wxString
& str
)
1617 static wxLocaleUntranslatedStrings s_strings
;
1619 wxLocaleUntranslatedStrings::iterator i
= s_strings
.find(str
);
1620 if ( i
== s_strings
.end() )
1621 return *s_strings
.insert(str
).first
;
1627 const wxString
& wxTranslations::GetString(const wxString
& origString
,
1628 const wxString
& domain
) const
1630 return GetString(origString
, origString
, UINT_MAX
, domain
);
1633 const wxString
& wxTranslations::GetString(const wxString
& origString
,
1634 const wxString
& origString2
,
1636 const wxString
& domain
) const
1638 if ( origString
.empty() )
1639 return GetUntranslatedString(origString
);
1641 const wxString
*trans
= NULL
;
1642 wxMsgCatalog
*pMsgCat
;
1644 if ( !domain
.empty() )
1646 pMsgCat
= FindCatalog(domain
);
1648 // does the catalog exist?
1649 if ( pMsgCat
!= NULL
)
1650 trans
= pMsgCat
->GetString(origString
, n
);
1654 // search in all domains
1655 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
)
1657 trans
= pMsgCat
->GetString(origString
, n
);
1658 if ( trans
!= NULL
) // take the first found
1663 if ( trans
== NULL
)
1668 "string \"%s\"%s not found in %slocale '%s'.",
1670 (n
!= UINT_MAX
? wxString::Format("[%ld]", (long)n
) : wxString()),
1671 (!domain
.empty() ? wxString::Format("domain '%s' ", domain
) : wxString()),
1676 return GetUntranslatedString(origString
);
1678 return GetUntranslatedString(n
== 1 ? origString
: origString2
);
1685 wxString
wxTranslations::GetHeaderValue(const wxString
& header
,
1686 const wxString
& domain
) const
1688 if ( header
.empty() )
1689 return wxEmptyString
;
1691 const wxString
*trans
= NULL
;
1692 wxMsgCatalog
*pMsgCat
;
1694 if ( !domain
.empty() )
1696 pMsgCat
= FindCatalog(domain
);
1698 // does the catalog exist?
1699 if ( pMsgCat
== NULL
)
1700 return wxEmptyString
;
1702 trans
= pMsgCat
->GetString(wxEmptyString
, UINT_MAX
);
1706 // search in all domains
1707 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
)
1709 trans
= pMsgCat
->GetString(wxEmptyString
, UINT_MAX
);
1710 if ( trans
!= NULL
) // take the first found
1715 if ( !trans
|| trans
->empty() )
1716 return wxEmptyString
;
1718 size_t found
= trans
->find(header
);
1719 if ( found
== wxString::npos
)
1720 return wxEmptyString
;
1722 found
+= header
.length() + 2 /* ': ' */;
1724 // Every header is separated by \n
1726 size_t endLine
= trans
->find(wxS('\n'), found
);
1727 size_t len
= (endLine
== wxString::npos
) ?
1728 wxString::npos
: (endLine
- found
);
1730 return trans
->substr(found
, len
);
1734 // find catalog by name in a linked list, return NULL if !found
1735 wxMsgCatalog
*wxTranslations::FindCatalog(const wxString
& domain
) const
1737 // linear search in the linked list
1738 wxMsgCatalog
*pMsgCat
;
1739 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
)
1741 if ( pMsgCat
->GetDomain() == domain
)
1748 // ----------------------------------------------------------------------------
1749 // wxFileTranslationsLoader
1750 // ----------------------------------------------------------------------------
1755 // the list of the directories to search for message catalog files
1756 wxArrayString gs_searchPrefixes
;
1758 // return the directories to search for message catalogs under the given
1759 // prefix, separated by wxPATH_SEP
1760 wxString
GetMsgCatalogSubdirs(const wxString
& prefix
, const wxString
& lang
)
1762 // Search first in Unix-standard prefix/lang/LC_MESSAGES, then in
1765 // Note that we use LC_MESSAGES on all platforms and not just Unix, because
1766 // it doesn't cost much to look into one more directory and doing it this
1767 // way has two important benefits:
1768 // a) we don't break compatibility with wx-2.6 and older by stopping to
1769 // look in a directory where the catalogs used to be and thus silently
1770 // breaking apps after they are recompiled against the latest wx
1771 // b) it makes it possible to package app's support files in the same
1772 // way on all target platforms
1773 const wxString prefixAndLang
= wxFileName(prefix
, lang
).GetFullPath();
1775 wxString searchPath
;
1776 searchPath
.reserve(4*prefixAndLang
.length());
1780 << prefixAndLang
<< ".lproj/LC_MESSAGES" << wxPATH_SEP
1781 << prefixAndLang
<< ".lproj" << wxPATH_SEP
1783 << prefixAndLang
<< wxFILE_SEP_PATH
<< "LC_MESSAGES" << wxPATH_SEP
1784 << prefixAndLang
<< wxPATH_SEP
1790 bool HasMsgCatalogInDir(const wxString
& dir
, const wxString
& domain
)
1792 return wxFileName(dir
, domain
, "mo").FileExists() ||
1793 wxFileName(dir
+ wxFILE_SEP_PATH
+ "LC_MESSAGES", domain
, "mo").FileExists();
1796 // get prefixes to locale directories; if lang is empty, don't point to
1797 // OSX's .lproj bundles
1798 wxArrayString
GetSearchPrefixes()
1800 wxArrayString paths
;
1802 // first take the entries explicitly added by the program
1803 paths
= gs_searchPrefixes
;
1806 // then look in the standard location
1808 stdp
= wxStandardPaths::Get().GetResourcesDir();
1809 if ( paths
.Index(stdp
) == wxNOT_FOUND
)
1811 #endif // wxUSE_STDPATHS
1813 // last look in default locations
1815 // LC_PATH is a standard env var containing the search path for the .mo
1817 const char *pszLcPath
= wxGetenv("LC_PATH");
1820 const wxString lcp
= pszLcPath
;
1821 if ( paths
.Index(lcp
) == wxNOT_FOUND
)
1825 // also add the one from where wxWin was installed:
1826 wxString wxp
= wxGetInstallPrefix();
1829 wxp
+= wxS("/share/locale");
1830 if ( paths
.Index(wxp
) == wxNOT_FOUND
)
1838 // construct the search path for the given language
1839 wxString
GetFullSearchPath(const wxString
& lang
)
1841 wxString searchPath
;
1842 searchPath
.reserve(500);
1844 const wxArrayString prefixes
= GetSearchPrefixes();
1846 for ( wxArrayString::const_iterator i
= prefixes
.begin();
1847 i
!= prefixes
.end();
1850 const wxString p
= GetMsgCatalogSubdirs(*i
, lang
);
1852 if ( !searchPath
.empty() )
1853 searchPath
+= wxPATH_SEP
;
1860 } // anonymous namespace
1863 void wxFileTranslationsLoader::AddCatalogLookupPathPrefix(const wxString
& prefix
)
1865 if ( gs_searchPrefixes
.Index(prefix
) == wxNOT_FOUND
)
1867 gs_searchPrefixes
.Add(prefix
);
1869 //else: already have it
1873 wxMsgCatalog
*wxFileTranslationsLoader::LoadCatalog(const wxString
& domain
,
1874 const wxString
& lang
)
1876 wxString searchPath
= GetFullSearchPath(lang
);
1880 wxString::Format("looking for \"%s.mo\" in search path", domain
),
1881 wxSplit(searchPath
, wxPATH_SEP
[0])
1884 wxFileName
fn(domain
);
1885 fn
.SetExt(wxS("mo"));
1887 wxString strFullName
;
1888 if ( !wxFindFileInPath(&strFullName
, searchPath
, fn
.GetFullPath()) )
1891 // open file and read its data
1892 wxLogVerbose(_("using catalog '%s' from '%s'."), domain
, strFullName
.c_str());
1893 wxLogTrace(TRACE_I18N
, wxS("Using catalog \"%s\"."), strFullName
.c_str());
1895 return wxMsgCatalog::CreateFromFile(strFullName
, domain
);
1899 wxArrayString
wxFileTranslationsLoader::GetAvailableTranslations(const wxString
& domain
) const
1901 wxArrayString langs
;
1902 const wxArrayString prefixes
= GetSearchPrefixes();
1906 wxString::Format("looking for available translations of \"%s\" in search path", domain
),
1910 for ( wxArrayString::const_iterator i
= prefixes
.begin();
1911 i
!= prefixes
.end();
1917 if ( !dir
.Open(*i
) )
1921 for ( bool ok
= dir
.GetFirst(&lang
, "", wxDIR_DIRS
);
1923 ok
= dir
.GetNext(&lang
) )
1925 const wxString langdir
= *i
+ wxFILE_SEP_PATH
+ lang
;
1926 if ( HasMsgCatalogInDir(langdir
, domain
) )
1930 if ( lang
.EndsWith(".lproj", &rest
) )
1934 wxLogTrace(TRACE_I18N
,
1935 "found %s translation of \"%s\" in %s",
1936 lang
, domain
, langdir
);
1937 langs
.push_back(lang
);
1946 // ----------------------------------------------------------------------------
1947 // wxResourceTranslationsLoader
1948 // ----------------------------------------------------------------------------
1952 wxMsgCatalog
*wxResourceTranslationsLoader::LoadCatalog(const wxString
& domain
,
1953 const wxString
& lang
)
1955 const void *mo_data
= NULL
;
1958 const wxString resname
= wxString::Format("%s_%s", domain
, lang
);
1960 if ( !wxLoadUserResource(&mo_data
, &mo_size
,
1962 GetResourceType().t_str(),
1966 wxLogTrace(TRACE_I18N
,
1967 "Using catalog from Windows resource \"%s\".", resname
);
1969 wxMsgCatalog
*cat
= wxMsgCatalog::CreateFromData(
1970 wxCharBuffer::CreateNonOwned(static_cast<const char*>(mo_data
), mo_size
),
1975 wxLogWarning(_("Resource '%s' is not a valid message catalog."), resname
);
1984 struct EnumCallbackData
1987 wxArrayString langs
;
1990 BOOL CALLBACK
EnumTranslations(HMODULE
WXUNUSED(hModule
),
1991 LPCTSTR
WXUNUSED(lpszType
),
1995 wxString
name(lpszName
);
1996 name
.MakeLower(); // resource names are case insensitive
1998 EnumCallbackData
*data
= reinterpret_cast<EnumCallbackData
*>(lParam
);
2001 if ( name
.StartsWith(data
->prefix
, &lang
) && !lang
.empty() )
2002 data
->langs
.push_back(lang
);
2004 return TRUE
; // continue enumeration
2007 } // anonymous namespace
2010 wxArrayString
wxResourceTranslationsLoader::GetAvailableTranslations(const wxString
& domain
) const
2012 EnumCallbackData data
;
2013 data
.prefix
= domain
+ "_";
2014 data
.prefix
.MakeLower(); // resource names are case insensitive
2016 if ( !EnumResourceNames(GetModule(),
2017 GetResourceType().t_str(),
2019 reinterpret_cast<LONG_PTR
>(&data
)) )
2021 const DWORD err
= GetLastError();
2022 if ( err
!= NO_ERROR
&& err
!= ERROR_RESOURCE_TYPE_NOT_FOUND
)
2024 wxLogSysError(_("Couldn't enumerate translations"));
2031 #endif // __WINDOWS__
2034 // ----------------------------------------------------------------------------
2035 // wxTranslationsModule module (for destruction of gs_translations)
2036 // ----------------------------------------------------------------------------
2038 class wxTranslationsModule
: public wxModule
2040 DECLARE_DYNAMIC_CLASS(wxTranslationsModule
)
2042 wxTranslationsModule() {}
2051 if ( gs_translationsOwned
)
2052 delete gs_translations
;
2053 gs_translations
= NULL
;
2054 gs_translationsOwned
= true;
2058 IMPLEMENT_DYNAMIC_CLASS(wxTranslationsModule
, wxModule
)
2060 #endif // wxUSE_INTL