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 void LogTraceLargeArray(const char *prefix
, const wxArrayString
& arr
)
106 wxLogTrace(TRACE_I18N
, "%s:", prefix
);
107 for ( wxArrayString::const_iterator i
= arr
.begin(); i
!= arr
.end(); ++i
)
108 wxLogTrace(TRACE_I18N
, " %s", *i
);
111 // Use locale-based detection as a fallback
112 wxString
GetPreferredUILanguageFallback(const wxArrayString
& WXUNUSED(available
))
114 const wxString lang
= wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage());
115 wxLogTrace(TRACE_I18N
, " - obtained best language from locale: %s", lang
);
121 wxString
GetPreferredUILanguage(const wxArrayString
& available
)
123 typedef BOOL (WINAPI
*GetUserPreferredUILanguages_t
)(DWORD
, PULONG
, PWSTR
, PULONG
);
124 static GetUserPreferredUILanguages_t s_pfnGetUserPreferredUILanguages
= NULL
;
125 static bool s_initDone
= false;
128 wxLoadedDLL
dllKernel32("kernel32.dll");
129 wxDL_INIT_FUNC(s_pfn
, GetUserPreferredUILanguages
, dllKernel32
);
133 if ( s_pfnGetUserPreferredUILanguages
)
136 ULONG bufferSize
= 0;
137 if ( (*s_pfnGetUserPreferredUILanguages
)(MUI_LANGUAGE_NAME
,
142 wxScopedArray
<WCHAR
> langs(new WCHAR
[bufferSize
]);
143 if ( (*s_pfnGetUserPreferredUILanguages
)(MUI_LANGUAGE_NAME
,
148 wxArrayString preferred
;
150 WCHAR
*buf
= langs
.get();
151 for ( unsigned i
= 0; i
< numLangs
; i
++ )
153 const wxString
lang(buf
);
154 preferred
.push_back(lang
);
155 buf
+= lang
.length() + 1;
157 LogTraceArray(" - system preferred languages", preferred
);
159 for ( wxArrayString::const_iterator j
= preferred
.begin();
160 j
!= preferred
.end();
164 lang
.Replace("-", "_");
165 if ( available
.Index(lang
, /*bCase=*/false) != wxNOT_FOUND
)
167 size_t pos
= lang
.find('_');
168 if ( pos
!= wxString::npos
)
170 lang
= lang
.substr(0, pos
);
171 if ( available
.Index(lang
, /*bCase=*/false) != wxNOT_FOUND
)
179 return GetPreferredUILanguageFallback(available
);
182 #elif defined(__WXOSX__)
184 void LogTraceArray(const char *prefix
, CFArrayRef arr
)
187 const unsigned count
= CFArrayGetCount(arr
);
190 s
+= wxCFStringRef::AsString((CFStringRef
)CFArrayGetValueAtIndex(arr
, 0));
191 for ( unsigned i
= 1 ; i
< count
; i
++ )
192 s
+= "," + wxCFStringRef::AsString((CFStringRef
)CFArrayGetValueAtIndex(arr
, i
));
194 wxLogTrace(TRACE_I18N
, "%s: [%s]", prefix
, s
);
197 wxString
GetPreferredUILanguage(const wxArrayString
& available
)
199 wxStringToStringHashMap availableNormalized
;
200 wxCFRef
<CFMutableArrayRef
> availableArr(
201 CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
));
203 for ( wxArrayString::const_iterator i
= available
.begin();
204 i
!= available
.end();
208 wxCFStringRef
code_wx(*i
);
209 wxCFStringRef
code_norm(
210 CFLocaleCreateCanonicalLanguageIdentifierFromString(kCFAllocatorDefault
, code_wx
));
211 CFArrayAppendValue(availableArr
, code_norm
);
212 availableNormalized
[code_norm
.AsString()] = *i
;
214 LogTraceArray(" - normalized available list", availableArr
);
216 wxCFRef
<CFArrayRef
> prefArr(
217 CFBundleCopyLocalizationsForPreferences(availableArr
, NULL
));
218 LogTraceArray(" - system preferred languages", prefArr
);
220 unsigned prefArrLength
= CFArrayGetCount(prefArr
);
221 if ( prefArrLength
> 0 )
223 // Lookup the name in 'available' by index -- we need to get the
224 // original value corresponding to the normalized one chosen.
225 wxString
lang(wxCFStringRef::AsString((CFStringRef
)CFArrayGetValueAtIndex(prefArr
, 0)));
226 wxStringToStringHashMap::const_iterator i
= availableNormalized
.find(lang
);
227 if ( i
== availableNormalized
.end() )
233 return GetPreferredUILanguageFallback(available
);
238 // On Unix, there's just one language=locale setting, so we should always
240 #define GetPreferredUILanguage GetPreferredUILanguageFallback
244 } // anonymous namespace
246 // ----------------------------------------------------------------------------
247 // Plural forms parser
248 // ----------------------------------------------------------------------------
254 LogicalOrExpression '?' Expression ':' Expression
258 LogicalAndExpression "||" LogicalOrExpression // to (a || b) || c
261 LogicalAndExpression:
262 EqualityExpression "&&" LogicalAndExpression // to (a && b) && c
266 RelationalExpression "==" RelationalExperession
267 RelationalExpression "!=" RelationalExperession
270 RelationalExpression:
271 MultiplicativeExpression '>' MultiplicativeExpression
272 MultiplicativeExpression '<' MultiplicativeExpression
273 MultiplicativeExpression ">=" MultiplicativeExpression
274 MultiplicativeExpression "<=" MultiplicativeExpression
275 MultiplicativeExpression
277 MultiplicativeExpression:
278 PmExpression '%' PmExpression
287 class wxPluralFormsToken
292 T_ERROR
, T_EOF
, T_NUMBER
, T_N
, T_PLURAL
, T_NPLURALS
, T_EQUAL
, T_ASSIGN
,
293 T_GREATER
, T_GREATER_OR_EQUAL
, T_LESS
, T_LESS_OR_EQUAL
,
294 T_REMINDER
, T_NOT_EQUAL
,
295 T_LOGICAL_AND
, T_LOGICAL_OR
, T_QUESTION
, T_COLON
, T_SEMICOLON
,
296 T_LEFT_BRACKET
, T_RIGHT_BRACKET
298 Type
type() const { return m_type
; }
299 void setType(Type t
) { m_type
= t
; }
302 Number
number() const { return m_number
; }
303 void setNumber(Number num
) { m_number
= num
; }
310 class wxPluralFormsScanner
313 wxPluralFormsScanner(const char* s
);
314 const wxPluralFormsToken
& token() const { return m_token
; }
315 bool nextToken(); // returns false if error
318 wxPluralFormsToken m_token
;
321 wxPluralFormsScanner::wxPluralFormsScanner(const char* s
) : m_s(s
)
326 bool wxPluralFormsScanner::nextToken()
328 wxPluralFormsToken::Type type
= wxPluralFormsToken::T_ERROR
;
329 while (isspace((unsigned char) *m_s
))
335 type
= wxPluralFormsToken::T_EOF
;
337 else if (isdigit((unsigned char) *m_s
))
339 wxPluralFormsToken::Number number
= *m_s
++ - '0';
340 while (isdigit((unsigned char) *m_s
))
342 number
= number
* 10 + (*m_s
++ - '0');
344 m_token
.setNumber(number
);
345 type
= wxPluralFormsToken::T_NUMBER
;
347 else if (isalpha((unsigned char) *m_s
))
349 const char* begin
= m_s
++;
350 while (isalnum((unsigned char) *m_s
))
354 size_t size
= m_s
- begin
;
355 if (size
== 1 && memcmp(begin
, "n", size
) == 0)
357 type
= wxPluralFormsToken::T_N
;
359 else if (size
== 6 && memcmp(begin
, "plural", size
) == 0)
361 type
= wxPluralFormsToken::T_PLURAL
;
363 else if (size
== 8 && memcmp(begin
, "nplurals", size
) == 0)
365 type
= wxPluralFormsToken::T_NPLURALS
;
368 else if (*m_s
== '=')
374 type
= wxPluralFormsToken::T_EQUAL
;
378 type
= wxPluralFormsToken::T_ASSIGN
;
381 else if (*m_s
== '>')
387 type
= wxPluralFormsToken::T_GREATER_OR_EQUAL
;
391 type
= wxPluralFormsToken::T_GREATER
;
394 else if (*m_s
== '<')
400 type
= wxPluralFormsToken::T_LESS_OR_EQUAL
;
404 type
= wxPluralFormsToken::T_LESS
;
407 else if (*m_s
== '%')
410 type
= wxPluralFormsToken::T_REMINDER
;
412 else if (*m_s
== '!' && m_s
[1] == '=')
415 type
= wxPluralFormsToken::T_NOT_EQUAL
;
417 else if (*m_s
== '&' && m_s
[1] == '&')
420 type
= wxPluralFormsToken::T_LOGICAL_AND
;
422 else if (*m_s
== '|' && m_s
[1] == '|')
425 type
= wxPluralFormsToken::T_LOGICAL_OR
;
427 else if (*m_s
== '?')
430 type
= wxPluralFormsToken::T_QUESTION
;
432 else if (*m_s
== ':')
435 type
= wxPluralFormsToken::T_COLON
;
436 } else if (*m_s
== ';') {
438 type
= wxPluralFormsToken::T_SEMICOLON
;
440 else if (*m_s
== '(')
443 type
= wxPluralFormsToken::T_LEFT_BRACKET
;
445 else if (*m_s
== ')')
448 type
= wxPluralFormsToken::T_RIGHT_BRACKET
;
450 m_token
.setType(type
);
451 return type
!= wxPluralFormsToken::T_ERROR
;
454 class wxPluralFormsNode
;
456 // NB: Can't use wxDEFINE_SCOPED_PTR_TYPE because wxPluralFormsNode is not
457 // fully defined yet:
458 class wxPluralFormsNodePtr
461 wxPluralFormsNodePtr(wxPluralFormsNode
*p
= NULL
) : m_p(p
) {}
462 ~wxPluralFormsNodePtr();
463 wxPluralFormsNode
& operator*() const { return *m_p
; }
464 wxPluralFormsNode
* operator->() const { return m_p
; }
465 wxPluralFormsNode
* get() const { return m_p
; }
466 wxPluralFormsNode
* release();
467 void reset(wxPluralFormsNode
*p
);
470 wxPluralFormsNode
*m_p
;
473 class wxPluralFormsNode
476 wxPluralFormsNode(const wxPluralFormsToken
& t
) : m_token(t
) {}
477 const wxPluralFormsToken
& token() const { return m_token
; }
478 const wxPluralFormsNode
* node(unsigned i
) const
479 { return m_nodes
[i
].get(); }
480 void setNode(unsigned i
, wxPluralFormsNode
* n
);
481 wxPluralFormsNode
* releaseNode(unsigned i
);
482 wxPluralFormsToken::Number
evaluate(wxPluralFormsToken::Number n
) const;
485 wxPluralFormsToken m_token
;
486 wxPluralFormsNodePtr m_nodes
[3];
489 wxPluralFormsNodePtr::~wxPluralFormsNodePtr()
493 wxPluralFormsNode
* wxPluralFormsNodePtr::release()
495 wxPluralFormsNode
*p
= m_p
;
499 void wxPluralFormsNodePtr::reset(wxPluralFormsNode
*p
)
509 void wxPluralFormsNode::setNode(unsigned i
, wxPluralFormsNode
* n
)
514 wxPluralFormsNode
* wxPluralFormsNode::releaseNode(unsigned i
)
516 return m_nodes
[i
].release();
519 wxPluralFormsToken::Number
520 wxPluralFormsNode::evaluate(wxPluralFormsToken::Number n
) const
522 switch (token().type())
525 case wxPluralFormsToken::T_NUMBER
:
526 return token().number();
527 case wxPluralFormsToken::T_N
:
530 case wxPluralFormsToken::T_EQUAL
:
531 return node(0)->evaluate(n
) == node(1)->evaluate(n
);
532 case wxPluralFormsToken::T_NOT_EQUAL
:
533 return node(0)->evaluate(n
) != node(1)->evaluate(n
);
534 case wxPluralFormsToken::T_GREATER
:
535 return node(0)->evaluate(n
) > node(1)->evaluate(n
);
536 case wxPluralFormsToken::T_GREATER_OR_EQUAL
:
537 return node(0)->evaluate(n
) >= node(1)->evaluate(n
);
538 case wxPluralFormsToken::T_LESS
:
539 return node(0)->evaluate(n
) < node(1)->evaluate(n
);
540 case wxPluralFormsToken::T_LESS_OR_EQUAL
:
541 return node(0)->evaluate(n
) <= node(1)->evaluate(n
);
542 case wxPluralFormsToken::T_REMINDER
:
544 wxPluralFormsToken::Number number
= node(1)->evaluate(n
);
547 return node(0)->evaluate(n
) % number
;
554 case wxPluralFormsToken::T_LOGICAL_AND
:
555 return node(0)->evaluate(n
) && node(1)->evaluate(n
);
556 case wxPluralFormsToken::T_LOGICAL_OR
:
557 return node(0)->evaluate(n
) || node(1)->evaluate(n
);
559 case wxPluralFormsToken::T_QUESTION
:
560 return node(0)->evaluate(n
)
561 ? node(1)->evaluate(n
)
562 : node(2)->evaluate(n
);
569 class wxPluralFormsCalculator
572 wxPluralFormsCalculator() : m_nplurals(0), m_plural(0) {}
574 // input: number, returns msgstr index
575 int evaluate(int n
) const;
577 // input: text after "Plural-Forms:" (e.g. "nplurals=2; plural=(n != 1);"),
578 // if s == 0, creates default handler
579 // returns 0 if error
580 static wxPluralFormsCalculator
* make(const char* s
= 0);
582 ~wxPluralFormsCalculator() {}
584 void init(wxPluralFormsToken::Number nplurals
, wxPluralFormsNode
* plural
);
587 wxPluralFormsToken::Number m_nplurals
;
588 wxPluralFormsNodePtr m_plural
;
591 wxDEFINE_SCOPED_PTR(wxPluralFormsCalculator
, wxPluralFormsCalculatorPtr
)
593 void wxPluralFormsCalculator::init(wxPluralFormsToken::Number nplurals
,
594 wxPluralFormsNode
* plural
)
596 m_nplurals
= nplurals
;
597 m_plural
.reset(plural
);
600 int wxPluralFormsCalculator::evaluate(int n
) const
602 if (m_plural
.get() == 0)
606 wxPluralFormsToken::Number number
= m_plural
->evaluate(n
);
607 if (number
< 0 || number
> m_nplurals
)
615 class wxPluralFormsParser
618 wxPluralFormsParser(wxPluralFormsScanner
& scanner
) : m_scanner(scanner
) {}
619 bool parse(wxPluralFormsCalculator
& rCalculator
);
622 wxPluralFormsNode
* parsePlural();
623 // stops at T_SEMICOLON, returns 0 if error
624 wxPluralFormsScanner
& m_scanner
;
625 const wxPluralFormsToken
& token() const;
628 wxPluralFormsNode
* expression();
629 wxPluralFormsNode
* logicalOrExpression();
630 wxPluralFormsNode
* logicalAndExpression();
631 wxPluralFormsNode
* equalityExpression();
632 wxPluralFormsNode
* multiplicativeExpression();
633 wxPluralFormsNode
* relationalExpression();
634 wxPluralFormsNode
* pmExpression();
637 bool wxPluralFormsParser::parse(wxPluralFormsCalculator
& rCalculator
)
639 if (token().type() != wxPluralFormsToken::T_NPLURALS
)
643 if (token().type() != wxPluralFormsToken::T_ASSIGN
)
647 if (token().type() != wxPluralFormsToken::T_NUMBER
)
649 wxPluralFormsToken::Number nplurals
= token().number();
652 if (token().type() != wxPluralFormsToken::T_SEMICOLON
)
656 if (token().type() != wxPluralFormsToken::T_PLURAL
)
660 if (token().type() != wxPluralFormsToken::T_ASSIGN
)
664 wxPluralFormsNode
* plural
= parsePlural();
667 if (token().type() != wxPluralFormsToken::T_SEMICOLON
)
671 if (token().type() != wxPluralFormsToken::T_EOF
)
673 rCalculator
.init(nplurals
, plural
);
677 wxPluralFormsNode
* wxPluralFormsParser::parsePlural()
679 wxPluralFormsNode
* p
= expression();
684 wxPluralFormsNodePtr
n(p
);
685 if (token().type() != wxPluralFormsToken::T_SEMICOLON
)
692 const wxPluralFormsToken
& wxPluralFormsParser::token() const
694 return m_scanner
.token();
697 bool wxPluralFormsParser::nextToken()
699 if (!m_scanner
.nextToken())
704 wxPluralFormsNode
* wxPluralFormsParser::expression()
706 wxPluralFormsNode
* p
= logicalOrExpression();
709 wxPluralFormsNodePtr
n(p
);
710 if (token().type() == wxPluralFormsToken::T_QUESTION
)
712 wxPluralFormsNodePtr
qn(new wxPluralFormsNode(token()));
723 if (token().type() != wxPluralFormsToken::T_COLON
)
737 qn
->setNode(0, n
.release());
743 wxPluralFormsNode
*wxPluralFormsParser::logicalOrExpression()
745 wxPluralFormsNode
* p
= logicalAndExpression();
748 wxPluralFormsNodePtr
ln(p
);
749 if (token().type() == wxPluralFormsToken::T_LOGICAL_OR
)
751 wxPluralFormsNodePtr
un(new wxPluralFormsNode(token()));
756 p
= logicalOrExpression();
761 wxPluralFormsNodePtr
rn(p
); // right
762 if (rn
->token().type() == wxPluralFormsToken::T_LOGICAL_OR
)
764 // see logicalAndExpression comment
765 un
->setNode(0, ln
.release());
766 un
->setNode(1, rn
->releaseNode(0));
767 rn
->setNode(0, un
.release());
772 un
->setNode(0, ln
.release());
773 un
->setNode(1, rn
.release());
779 wxPluralFormsNode
* wxPluralFormsParser::logicalAndExpression()
781 wxPluralFormsNode
* p
= equalityExpression();
784 wxPluralFormsNodePtr
ln(p
); // left
785 if (token().type() == wxPluralFormsToken::T_LOGICAL_AND
)
787 wxPluralFormsNodePtr
un(new wxPluralFormsNode(token())); // up
792 p
= logicalAndExpression();
797 wxPluralFormsNodePtr
rn(p
); // right
798 if (rn
->token().type() == wxPluralFormsToken::T_LOGICAL_AND
)
800 // transform 1 && (2 && 3) -> (1 && 2) && 3
804 un
->setNode(0, ln
.release());
805 un
->setNode(1, rn
->releaseNode(0));
806 rn
->setNode(0, un
.release());
810 un
->setNode(0, ln
.release());
811 un
->setNode(1, rn
.release());
817 wxPluralFormsNode
* wxPluralFormsParser::equalityExpression()
819 wxPluralFormsNode
* p
= relationalExpression();
822 wxPluralFormsNodePtr
n(p
);
823 if (token().type() == wxPluralFormsToken::T_EQUAL
824 || token().type() == wxPluralFormsToken::T_NOT_EQUAL
)
826 wxPluralFormsNodePtr
qn(new wxPluralFormsNode(token()));
831 p
= relationalExpression();
837 qn
->setNode(0, n
.release());
843 wxPluralFormsNode
* wxPluralFormsParser::relationalExpression()
845 wxPluralFormsNode
* p
= multiplicativeExpression();
848 wxPluralFormsNodePtr
n(p
);
849 if (token().type() == wxPluralFormsToken::T_GREATER
850 || token().type() == wxPluralFormsToken::T_LESS
851 || token().type() == wxPluralFormsToken::T_GREATER_OR_EQUAL
852 || token().type() == wxPluralFormsToken::T_LESS_OR_EQUAL
)
854 wxPluralFormsNodePtr
qn(new wxPluralFormsNode(token()));
859 p
= multiplicativeExpression();
865 qn
->setNode(0, n
.release());
871 wxPluralFormsNode
* wxPluralFormsParser::multiplicativeExpression()
873 wxPluralFormsNode
* p
= pmExpression();
876 wxPluralFormsNodePtr
n(p
);
877 if (token().type() == wxPluralFormsToken::T_REMINDER
)
879 wxPluralFormsNodePtr
qn(new wxPluralFormsNode(token()));
890 qn
->setNode(0, n
.release());
896 wxPluralFormsNode
* wxPluralFormsParser::pmExpression()
898 wxPluralFormsNodePtr n
;
899 if (token().type() == wxPluralFormsToken::T_N
900 || token().type() == wxPluralFormsToken::T_NUMBER
)
902 n
.reset(new wxPluralFormsNode(token()));
908 else if (token().type() == wxPluralFormsToken::T_LEFT_BRACKET
) {
913 wxPluralFormsNode
* p
= expression();
919 if (token().type() != wxPluralFormsToken::T_RIGHT_BRACKET
)
935 wxPluralFormsCalculator
* wxPluralFormsCalculator::make(const char* s
)
937 wxPluralFormsCalculatorPtr
calculator(new wxPluralFormsCalculator
);
940 wxPluralFormsScanner
scanner(s
);
941 wxPluralFormsParser
p(scanner
);
942 if (!p
.parse(*calculator
))
947 return calculator
.release();
953 // ----------------------------------------------------------------------------
954 // wxMsgCatalogFile corresponds to one disk-file message catalog.
956 // This is a "low-level" class and is used only by wxMsgCatalog
957 // NOTE: for the documentation of the binary catalog (.MO) files refer to
958 // the GNU gettext manual:
959 // http://www.gnu.org/software/autoconf/manual/gettext/MO-Files.html
960 // ----------------------------------------------------------------------------
962 class wxMsgCatalogFile
965 typedef wxScopedCharBuffer DataBuffer
;
971 // load the catalog from disk
972 bool LoadFile(const wxString
& filename
,
973 wxPluralFormsCalculatorPtr
& rPluralFormsCalculator
);
974 bool LoadData(const DataBuffer
& data
,
975 wxPluralFormsCalculatorPtr
& rPluralFormsCalculator
);
977 // fills the hash with string-translation pairs
978 bool FillHash(wxStringToStringHashMap
& hash
, const wxString
& domain
) const;
980 // return the charset of the strings in this catalog or empty string if
982 wxString
GetCharset() const { return m_charset
; }
985 // this implementation is binary compatible with GNU gettext() version 0.10
987 // an entry in the string table
988 struct wxMsgTableEntry
990 size_t32 nLen
; // length of the string
991 size_t32 ofsString
; // pointer to the string
994 // header of a .mo file
995 struct wxMsgCatalogHeader
997 size_t32 magic
, // offset +00: magic id
998 revision
, // +04: revision
999 numStrings
; // +08: number of strings in the file
1000 size_t32 ofsOrigTable
, // +0C: start of original string table
1001 ofsTransTable
; // +10: start of translated string table
1002 size_t32 nHashSize
, // +14: hash table size
1003 ofsHashTable
; // +18: offset of hash table start
1006 // all data is stored here
1010 size_t32 m_numStrings
; // number of strings in this domain
1011 wxMsgTableEntry
*m_pOrigTable
, // pointer to original strings
1012 *m_pTransTable
; // translated
1014 wxString m_charset
; // from the message catalog header
1017 // swap the 2 halves of 32 bit integer if needed
1018 size_t32
Swap(size_t32 ui
) const
1020 return m_bSwapped
? (ui
<< 24) | ((ui
& 0xff00) << 8) |
1021 ((ui
>> 8) & 0xff00) | (ui
>> 24)
1025 const char *StringAtOfs(wxMsgTableEntry
*pTable
, size_t32 n
) const
1027 const wxMsgTableEntry
* const ent
= pTable
+ n
;
1029 // this check could fail for a corrupt message catalog
1030 size_t32 ofsString
= Swap(ent
->ofsString
);
1031 if ( ofsString
+ Swap(ent
->nLen
) > m_data
.length())
1036 return m_data
.data() + ofsString
;
1039 bool m_bSwapped
; // wrong endianness?
1041 wxDECLARE_NO_COPY_CLASS(wxMsgCatalogFile
);
1044 // ----------------------------------------------------------------------------
1045 // wxMsgCatalogFile class
1046 // ----------------------------------------------------------------------------
1048 wxMsgCatalogFile::wxMsgCatalogFile()
1052 wxMsgCatalogFile::~wxMsgCatalogFile()
1056 // open disk file and read in it's contents
1057 bool wxMsgCatalogFile::LoadFile(const wxString
& filename
,
1058 wxPluralFormsCalculatorPtr
& rPluralFormsCalculator
)
1060 wxFile
fileMsg(filename
);
1061 if ( !fileMsg
.IsOpened() )
1064 // get the file size (assume it is less than 4GB...)
1065 wxFileOffset lenFile
= fileMsg
.Length();
1066 if ( lenFile
== wxInvalidOffset
)
1069 size_t nSize
= wx_truncate_cast(size_t, lenFile
);
1070 wxASSERT_MSG( nSize
== lenFile
+ size_t(0), wxS("message catalog bigger than 4GB?") );
1072 wxMemoryBuffer filedata
;
1074 // read the whole file in memory
1075 if ( fileMsg
.Read(filedata
.GetWriteBuf(nSize
), nSize
) != lenFile
)
1078 filedata
.UngetWriteBuf(nSize
);
1082 DataBuffer::CreateOwned((char*)filedata
.release(), nSize
),
1083 rPluralFormsCalculator
1087 wxLogWarning(_("'%s' is not a valid message catalog."), filename
.c_str());
1095 bool wxMsgCatalogFile::LoadData(const DataBuffer
& data
,
1096 wxPluralFormsCalculatorPtr
& rPluralFormsCalculator
)
1099 bool bValid
= data
.length() > sizeof(wxMsgCatalogHeader
);
1101 const wxMsgCatalogHeader
*pHeader
= (wxMsgCatalogHeader
*)data
.data();
1103 // we'll have to swap all the integers if it's true
1104 m_bSwapped
= pHeader
->magic
== MSGCATALOG_MAGIC_SW
;
1106 // check the magic number
1107 bValid
= m_bSwapped
|| pHeader
->magic
== MSGCATALOG_MAGIC
;
1111 // it's either too short or has incorrect magic number
1112 wxLogWarning(_("Invalid message catalog."));
1119 m_numStrings
= Swap(pHeader
->numStrings
);
1120 m_pOrigTable
= (wxMsgTableEntry
*)(data
.data() +
1121 Swap(pHeader
->ofsOrigTable
));
1122 m_pTransTable
= (wxMsgTableEntry
*)(data
.data() +
1123 Swap(pHeader
->ofsTransTable
));
1125 // now parse catalog's header and try to extract catalog charset and
1126 // plural forms formula from it:
1128 const char* headerData
= StringAtOfs(m_pOrigTable
, 0);
1129 if ( headerData
&& headerData
[0] == '\0' )
1131 // Extract the charset:
1132 const char * const header
= StringAtOfs(m_pTransTable
, 0);
1134 cset
= strstr(header
, "Content-Type: text/plain; charset=");
1137 cset
+= 34; // strlen("Content-Type: text/plain; charset=")
1139 const char * const csetEnd
= strchr(cset
, '\n');
1142 m_charset
= wxString(cset
, csetEnd
- cset
);
1143 if ( m_charset
== wxS("CHARSET") )
1145 // "CHARSET" is not valid charset, but lazy translator
1150 // else: incorrectly filled Content-Type header
1152 // Extract plural forms:
1153 const char * plurals
= strstr(header
, "Plural-Forms:");
1156 plurals
+= 13; // strlen("Plural-Forms:")
1157 const char * const pluralsEnd
= strchr(plurals
, '\n');
1160 const size_t pluralsLen
= pluralsEnd
- plurals
;
1161 wxCharBuffer
buf(pluralsLen
);
1162 strncpy(buf
.data(), plurals
, pluralsLen
);
1163 wxPluralFormsCalculator
* const
1164 pCalculator
= wxPluralFormsCalculator::make(buf
);
1167 rPluralFormsCalculator
.reset(pCalculator
);
1171 wxLogVerbose(_("Failed to parse Plural-Forms: '%s'"),
1177 if ( !rPluralFormsCalculator
.get() )
1178 rPluralFormsCalculator
.reset(wxPluralFormsCalculator::make());
1181 // everything is fine
1185 bool wxMsgCatalogFile::FillHash(wxStringToStringHashMap
& hash
,
1186 const wxString
& domain
) const
1188 wxUnusedVar(domain
); // silence warning in Unicode build
1190 // conversion to use to convert catalog strings to the GUI encoding
1191 wxMBConv
*inputConv
= NULL
;
1192 wxMBConv
*inputConvPtr
= NULL
; // same as inputConv but safely deleteable
1194 if ( !m_charset
.empty() )
1196 #if !wxUSE_UNICODE && wxUSE_FONTMAP
1197 // determine if we need any conversion at all
1198 wxFontEncoding encCat
= wxFontMapperBase::GetEncodingFromName(m_charset
);
1199 if ( encCat
!= wxLocale::GetSystemEncoding() )
1203 inputConv
= new wxCSConv(m_charset
);
1206 else // no need or not possible to convert the encoding
1209 // we must somehow convert the narrow strings in the message catalog to
1210 // wide strings, so use the default conversion if we have no charset
1211 inputConv
= wxConvCurrent
;
1216 wxString msgIdCharset
= gs_msgIdCharset
[domain
];
1218 // conversion to apply to msgid strings before looking them up: we only
1219 // need it if the msgids are neither in 7 bit ASCII nor in the same
1220 // encoding as the catalog
1221 wxCSConv
*sourceConv
= msgIdCharset
.empty() || (msgIdCharset
== m_charset
)
1223 : new wxCSConv(msgIdCharset
);
1224 #endif // !wxUSE_UNICODE
1226 for (size_t32 i
= 0; i
< m_numStrings
; i
++)
1228 const char *data
= StringAtOfs(m_pOrigTable
, i
);
1230 return false; // may happen for invalid MO files
1234 msgid
= wxString(data
, *inputConv
);
1236 if ( inputConv
&& sourceConv
)
1237 msgid
= wxString(inputConv
->cMB2WC(data
), *sourceConv
);
1240 #endif // wxUSE_UNICODE
1242 data
= StringAtOfs(m_pTransTable
, i
);
1244 return false; // may happen for invalid MO files
1246 size_t length
= Swap(m_pTransTable
[i
].nLen
);
1249 while (offset
< length
)
1251 const char * const str
= data
+ offset
;
1255 msgstr
= wxString(str
, *inputConv
);
1258 msgstr
= wxString(inputConv
->cMB2WC(str
), *wxConvUI
);
1261 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
1263 if ( !msgstr
.empty() )
1265 hash
[index
== 0 ? msgid
: msgid
+ wxChar(index
)] = msgstr
;
1269 // IMPORTANT: accesses to the 'data' pointer are valid only for
1270 // the first 'length+1' bytes (GNU specs says that the
1271 // final NUL is not counted in length); using wxStrnlen()
1272 // we make sure we don't access memory beyond the valid range
1273 // (which otherwise may happen for invalid MO files):
1274 offset
+= wxStrnlen(str
, length
- offset
) + 1;
1282 delete inputConvPtr
;
1288 // ----------------------------------------------------------------------------
1289 // wxMsgCatalog class
1290 // ----------------------------------------------------------------------------
1293 wxMsgCatalog::~wxMsgCatalog()
1297 if ( wxConvUI
== m_conv
)
1299 // we only change wxConvUI if it points to wxConvLocal so we reset
1300 // it back to it too
1301 wxConvUI
= &wxConvLocal
;
1307 #endif // !wxUSE_UNICODE
1310 wxMsgCatalog
*wxMsgCatalog::CreateFromFile(const wxString
& filename
,
1311 const wxString
& domain
)
1313 wxScopedPtr
<wxMsgCatalog
> cat(new wxMsgCatalog(domain
));
1315 wxMsgCatalogFile file
;
1317 if ( !file
.LoadFile(filename
, cat
->m_pluralFormsCalculator
) )
1320 if ( !file
.FillHash(cat
->m_messages
, domain
) )
1323 return cat
.release();
1327 wxMsgCatalog
*wxMsgCatalog::CreateFromData(const wxScopedCharBuffer
& data
,
1328 const wxString
& domain
)
1330 wxScopedPtr
<wxMsgCatalog
> cat(new wxMsgCatalog(domain
));
1332 wxMsgCatalogFile file
;
1334 if ( !file
.LoadData(data
, cat
->m_pluralFormsCalculator
) )
1337 if ( !file
.FillHash(cat
->m_messages
, domain
) )
1340 return cat
.release();
1343 const wxString
*wxMsgCatalog::GetString(const wxString
& str
, unsigned n
) const
1348 index
= m_pluralFormsCalculator
->evaluate(n
);
1350 wxStringToStringHashMap::const_iterator i
;
1353 i
= m_messages
.find(wxString(str
) + wxChar(index
)); // plural
1357 i
= m_messages
.find(str
);
1360 if ( i
!= m_messages
.end() )
1369 // ----------------------------------------------------------------------------
1371 // ----------------------------------------------------------------------------
1376 wxTranslations
*gs_translations
= NULL
;
1377 bool gs_translationsOwned
= false;
1379 } // anonymous namespace
1383 wxTranslations
*wxTranslations::Get()
1385 return gs_translations
;
1389 void wxTranslations::Set(wxTranslations
*t
)
1391 if ( gs_translationsOwned
)
1392 delete gs_translations
;
1393 gs_translations
= t
;
1394 gs_translationsOwned
= true;
1398 void wxTranslations::SetNonOwned(wxTranslations
*t
)
1400 if ( gs_translationsOwned
)
1401 delete gs_translations
;
1402 gs_translations
= t
;
1403 gs_translationsOwned
= false;
1407 wxTranslations::wxTranslations()
1410 m_loader
= new wxFileTranslationsLoader
;
1414 wxTranslations::~wxTranslations()
1418 // free catalogs memory
1419 wxMsgCatalog
*pTmpCat
;
1420 while ( m_pMsgCat
!= NULL
)
1422 pTmpCat
= m_pMsgCat
;
1423 m_pMsgCat
= m_pMsgCat
->m_pNext
;
1429 void wxTranslations::SetLoader(wxTranslationsLoader
*loader
)
1431 wxCHECK_RET( loader
, "loader can't be NULL" );
1438 void wxTranslations::SetLanguage(wxLanguage lang
)
1440 if ( lang
== wxLANGUAGE_DEFAULT
)
1443 SetLanguage(wxLocale::GetLanguageCanonicalName(lang
));
1446 void wxTranslations::SetLanguage(const wxString
& lang
)
1452 wxArrayString
wxTranslations::GetAvailableTranslations(const wxString
& domain
) const
1454 wxCHECK_MSG( m_loader
, wxArrayString(), "loader can't be NULL" );
1456 return m_loader
->GetAvailableTranslations(domain
);
1460 bool wxTranslations::AddStdCatalog()
1462 if ( !AddCatalog(wxS("wxstd")) )
1465 // there may be a catalog with toolkit specific overrides, it is not
1466 // an error if this does not exist
1467 wxString
port(wxPlatformInfo::Get().GetPortIdName());
1468 if ( !port
.empty() )
1470 AddCatalog(port
.BeforeFirst(wxS('/')).MakeLower());
1477 bool wxTranslations::AddCatalog(const wxString
& domain
)
1479 return AddCatalog(domain
, wxLANGUAGE_ENGLISH_US
);
1483 bool wxTranslations::AddCatalog(const wxString
& domain
,
1484 wxLanguage msgIdLanguage
,
1485 const wxString
& msgIdCharset
)
1487 gs_msgIdCharset
[domain
] = msgIdCharset
;
1488 return AddCatalog(domain
, msgIdLanguage
);
1490 #endif // !wxUSE_UNICODE
1492 bool wxTranslations::AddCatalog(const wxString
& domain
,
1493 wxLanguage msgIdLanguage
)
1495 const wxString msgIdLang
= wxLocale::GetLanguageCanonicalName(msgIdLanguage
);
1496 const wxString domain_lang
= GetBestTranslation(domain
, msgIdLang
);
1498 if ( domain_lang
.empty() )
1500 wxLogTrace(TRACE_I18N
,
1501 wxS("no suitable translation for domain '%s' found"),
1506 wxLogTrace(TRACE_I18N
,
1507 wxS("adding '%s' translation for domain '%s' (msgid language '%s')"),
1508 domain_lang
, domain
, msgIdLang
);
1510 // It is OK to not load catalog if the msgid language and m_language match,
1511 // in which case we can directly display the texts embedded in program's
1513 if ( msgIdLang
== domain_lang
)
1516 return LoadCatalog(domain
, domain_lang
);
1520 bool wxTranslations::LoadCatalog(const wxString
& domain
, const wxString
& lang
)
1522 wxCHECK_MSG( m_loader
, false, "loader can't be NULL" );
1524 wxMsgCatalog
*cat
= NULL
;
1527 // first look for the catalog for this language and the current locale:
1528 // notice that we don't use the system name for the locale as this would
1529 // force us to install catalogs in different locations depending on the
1530 // system but always use the canonical name
1531 wxFontEncoding encSys
= wxLocale::GetSystemEncoding();
1532 if ( encSys
!= wxFONTENCODING_SYSTEM
)
1534 wxString
fullname(lang
);
1535 fullname
<< wxS('.') << wxFontMapperBase::GetEncodingName(encSys
);
1537 cat
= m_loader
->LoadCatalog(domain
, fullname
);
1539 #endif // wxUSE_FONTMAP
1543 // Next try: use the provided name language name:
1544 cat
= m_loader
->LoadCatalog(domain
, lang
);
1549 // Also try just base locale name: for things like "fr_BE" (Belgium
1550 // French) we should use fall back on plain "fr" if no Belgium-specific
1551 // message catalogs exist
1552 wxString baselang
= lang
.BeforeFirst('_');
1553 if ( lang
!= baselang
)
1554 cat
= m_loader
->LoadCatalog(domain
, baselang
);
1559 // add it to the head of the list so that in GetString it will
1560 // be searched before the catalogs added earlier
1561 cat
->m_pNext
= m_pMsgCat
;
1568 // Nothing worked, the catalog just isn't there
1569 wxLogTrace(TRACE_I18N
,
1570 "Catalog \"%s.mo\" not found for language \"%s\".",
1576 // check if the given catalog is loaded
1577 bool wxTranslations::IsLoaded(const wxString
& domain
) const
1579 return FindCatalog(domain
) != NULL
;
1582 wxString
wxTranslations::GetBestTranslation(const wxString
& domain
,
1583 wxLanguage msgIdLanguage
)
1585 const wxString lang
= wxLocale::GetLanguageCanonicalName(msgIdLanguage
);
1586 return GetBestTranslation(domain
, lang
);
1589 wxString
wxTranslations::GetBestTranslation(const wxString
& domain
,
1590 const wxString
& msgIdLanguage
)
1592 // explicitly set language should always be respected
1593 if ( !m_lang
.empty() )
1596 wxArrayString
available(GetAvailableTranslations(domain
));
1597 // it's OK to have duplicates, so just add msgid language
1598 available
.push_back(msgIdLanguage
);
1599 available
.push_back(msgIdLanguage
.BeforeFirst('_'));
1601 wxLogTrace(TRACE_I18N
, "choosing best language for domain '%s'", domain
);
1602 LogTraceArray(" - available translations", available
);
1603 const wxString lang
= GetPreferredUILanguage(available
);
1604 wxLogTrace(TRACE_I18N
, " => using language '%s'", lang
);
1611 WX_DECLARE_HASH_SET(wxString
, wxStringHash
, wxStringEqual
,
1612 wxLocaleUntranslatedStrings
);
1616 const wxString
& wxTranslations::GetUntranslatedString(const wxString
& str
)
1618 static wxLocaleUntranslatedStrings s_strings
;
1620 wxLocaleUntranslatedStrings::iterator i
= s_strings
.find(str
);
1621 if ( i
== s_strings
.end() )
1622 return *s_strings
.insert(str
).first
;
1628 const wxString
& wxTranslations::GetString(const wxString
& origString
,
1629 const wxString
& domain
) const
1631 return GetString(origString
, origString
, UINT_MAX
, domain
);
1634 const wxString
& wxTranslations::GetString(const wxString
& origString
,
1635 const wxString
& origString2
,
1637 const wxString
& domain
) const
1639 if ( origString
.empty() )
1640 return GetUntranslatedString(origString
);
1642 const wxString
*trans
= NULL
;
1643 wxMsgCatalog
*pMsgCat
;
1645 if ( !domain
.empty() )
1647 pMsgCat
= FindCatalog(domain
);
1649 // does the catalog exist?
1650 if ( pMsgCat
!= NULL
)
1651 trans
= pMsgCat
->GetString(origString
, n
);
1655 // search in all domains
1656 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
)
1658 trans
= pMsgCat
->GetString(origString
, n
);
1659 if ( trans
!= NULL
) // take the first found
1664 if ( trans
== NULL
)
1669 "string \"%s\"%s not found in %slocale '%s'.",
1671 (n
!= UINT_MAX
? wxString::Format("[%ld]", (long)n
) : wxString()),
1672 (!domain
.empty() ? wxString::Format("domain '%s' ", domain
) : wxString()),
1677 return GetUntranslatedString(origString
);
1679 return GetUntranslatedString(n
== 1 ? origString
: origString2
);
1686 wxString
wxTranslations::GetHeaderValue(const wxString
& header
,
1687 const wxString
& domain
) const
1689 if ( header
.empty() )
1690 return wxEmptyString
;
1692 const wxString
*trans
= NULL
;
1693 wxMsgCatalog
*pMsgCat
;
1695 if ( !domain
.empty() )
1697 pMsgCat
= FindCatalog(domain
);
1699 // does the catalog exist?
1700 if ( pMsgCat
== NULL
)
1701 return wxEmptyString
;
1703 trans
= pMsgCat
->GetString(wxEmptyString
, UINT_MAX
);
1707 // search in all domains
1708 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
)
1710 trans
= pMsgCat
->GetString(wxEmptyString
, UINT_MAX
);
1711 if ( trans
!= NULL
) // take the first found
1716 if ( !trans
|| trans
->empty() )
1717 return wxEmptyString
;
1719 size_t found
= trans
->find(header
);
1720 if ( found
== wxString::npos
)
1721 return wxEmptyString
;
1723 found
+= header
.length() + 2 /* ': ' */;
1725 // Every header is separated by \n
1727 size_t endLine
= trans
->find(wxS('\n'), found
);
1728 size_t len
= (endLine
== wxString::npos
) ?
1729 wxString::npos
: (endLine
- found
);
1731 return trans
->substr(found
, len
);
1735 // find catalog by name in a linked list, return NULL if !found
1736 wxMsgCatalog
*wxTranslations::FindCatalog(const wxString
& domain
) const
1738 // linear search in the linked list
1739 wxMsgCatalog
*pMsgCat
;
1740 for ( pMsgCat
= m_pMsgCat
; pMsgCat
!= NULL
; pMsgCat
= pMsgCat
->m_pNext
)
1742 if ( pMsgCat
->GetDomain() == domain
)
1749 // ----------------------------------------------------------------------------
1750 // wxFileTranslationsLoader
1751 // ----------------------------------------------------------------------------
1756 // the list of the directories to search for message catalog files
1757 wxArrayString gs_searchPrefixes
;
1759 // return the directories to search for message catalogs under the given
1760 // prefix, separated by wxPATH_SEP
1761 wxString
GetMsgCatalogSubdirs(const wxString
& prefix
, const wxString
& lang
)
1763 // Search first in Unix-standard prefix/lang/LC_MESSAGES, then in
1766 // Note that we use LC_MESSAGES on all platforms and not just Unix, because
1767 // it doesn't cost much to look into one more directory and doing it this
1768 // way has two important benefits:
1769 // a) we don't break compatibility with wx-2.6 and older by stopping to
1770 // look in a directory where the catalogs used to be and thus silently
1771 // breaking apps after they are recompiled against the latest wx
1772 // b) it makes it possible to package app's support files in the same
1773 // way on all target platforms
1774 const wxString prefixAndLang
= wxFileName(prefix
, lang
).GetFullPath();
1776 wxString searchPath
;
1777 searchPath
.reserve(4*prefixAndLang
.length());
1781 << prefixAndLang
<< ".lproj/LC_MESSAGES" << wxPATH_SEP
1782 << prefixAndLang
<< ".lproj" << wxPATH_SEP
1784 << prefixAndLang
<< wxFILE_SEP_PATH
<< "LC_MESSAGES" << wxPATH_SEP
1785 << prefixAndLang
<< wxPATH_SEP
1791 bool HasMsgCatalogInDir(const wxString
& dir
, const wxString
& domain
)
1793 return wxFileName(dir
, domain
, "mo").FileExists() ||
1794 wxFileName(dir
+ wxFILE_SEP_PATH
+ "LC_MESSAGES", domain
, "mo").FileExists();
1797 // get prefixes to locale directories; if lang is empty, don't point to
1798 // OSX's .lproj bundles
1799 wxArrayString
GetSearchPrefixes()
1801 wxArrayString paths
;
1803 // first take the entries explicitly added by the program
1804 paths
= gs_searchPrefixes
;
1807 // then look in the standard location
1809 stdp
= wxStandardPaths::Get().GetResourcesDir();
1810 if ( paths
.Index(stdp
) == wxNOT_FOUND
)
1812 #endif // wxUSE_STDPATHS
1814 // last look in default locations
1816 // LC_PATH is a standard env var containing the search path for the .mo
1818 const char *pszLcPath
= wxGetenv("LC_PATH");
1821 const wxString lcp
= pszLcPath
;
1822 if ( paths
.Index(lcp
) == wxNOT_FOUND
)
1826 // also add the one from where wxWin was installed:
1827 wxString wxp
= wxGetInstallPrefix();
1830 wxp
+= wxS("/share/locale");
1831 if ( paths
.Index(wxp
) == wxNOT_FOUND
)
1839 // construct the search path for the given language
1840 wxString
GetFullSearchPath(const wxString
& lang
)
1842 wxString searchPath
;
1843 searchPath
.reserve(500);
1845 const wxArrayString prefixes
= GetSearchPrefixes();
1847 for ( wxArrayString::const_iterator i
= prefixes
.begin();
1848 i
!= prefixes
.end();
1851 const wxString p
= GetMsgCatalogSubdirs(*i
, lang
);
1853 if ( !searchPath
.empty() )
1854 searchPath
+= wxPATH_SEP
;
1861 } // anonymous namespace
1864 void wxFileTranslationsLoader::AddCatalogLookupPathPrefix(const wxString
& prefix
)
1866 if ( gs_searchPrefixes
.Index(prefix
) == wxNOT_FOUND
)
1868 gs_searchPrefixes
.Add(prefix
);
1870 //else: already have it
1874 wxMsgCatalog
*wxFileTranslationsLoader::LoadCatalog(const wxString
& domain
,
1875 const wxString
& lang
)
1877 wxString searchPath
= GetFullSearchPath(lang
);
1881 wxString::Format("looking for \"%s.mo\" in search path", domain
),
1882 wxSplit(searchPath
, wxPATH_SEP
[0])
1885 wxFileName
fn(domain
);
1886 fn
.SetExt(wxS("mo"));
1888 wxString strFullName
;
1889 if ( !wxFindFileInPath(&strFullName
, searchPath
, fn
.GetFullPath()) )
1892 // open file and read its data
1893 wxLogVerbose(_("using catalog '%s' from '%s'."), domain
, strFullName
.c_str());
1894 wxLogTrace(TRACE_I18N
, wxS("Using catalog \"%s\"."), strFullName
.c_str());
1896 return wxMsgCatalog::CreateFromFile(strFullName
, domain
);
1900 wxArrayString
wxFileTranslationsLoader::GetAvailableTranslations(const wxString
& domain
) const
1902 wxArrayString langs
;
1903 const wxArrayString prefixes
= GetSearchPrefixes();
1907 wxString::Format("looking for available translations of \"%s\" in search path", domain
),
1911 for ( wxArrayString::const_iterator i
= prefixes
.begin();
1912 i
!= prefixes
.end();
1918 if ( !dir
.Open(*i
) )
1922 for ( bool ok
= dir
.GetFirst(&lang
, "", wxDIR_DIRS
);
1924 ok
= dir
.GetNext(&lang
) )
1926 const wxString langdir
= *i
+ wxFILE_SEP_PATH
+ lang
;
1927 if ( HasMsgCatalogInDir(langdir
, domain
) )
1931 if ( lang
.EndsWith(".lproj", &rest
) )
1935 wxLogTrace(TRACE_I18N
,
1936 "found %s translation of \"%s\" in %s",
1937 lang
, domain
, langdir
);
1938 langs
.push_back(lang
);
1947 // ----------------------------------------------------------------------------
1948 // wxResourceTranslationsLoader
1949 // ----------------------------------------------------------------------------
1953 wxMsgCatalog
*wxResourceTranslationsLoader::LoadCatalog(const wxString
& domain
,
1954 const wxString
& lang
)
1956 const void *mo_data
= NULL
;
1959 const wxString resname
= wxString::Format("%s_%s", domain
, lang
);
1961 if ( !wxLoadUserResource(&mo_data
, &mo_size
,
1963 GetResourceType().t_str(),
1967 wxLogTrace(TRACE_I18N
,
1968 "Using catalog from Windows resource \"%s\".", resname
);
1970 wxMsgCatalog
*cat
= wxMsgCatalog::CreateFromData(
1971 wxCharBuffer::CreateNonOwned(static_cast<const char*>(mo_data
), mo_size
),
1976 wxLogWarning(_("Resource '%s' is not a valid message catalog."), resname
);
1985 struct EnumCallbackData
1988 wxArrayString langs
;
1991 BOOL CALLBACK
EnumTranslations(HMODULE
WXUNUSED(hModule
),
1992 LPCTSTR
WXUNUSED(lpszType
),
1996 wxString
name(lpszName
);
1997 name
.MakeLower(); // resource names are case insensitive
1999 EnumCallbackData
*data
= reinterpret_cast<EnumCallbackData
*>(lParam
);
2002 if ( name
.StartsWith(data
->prefix
, &lang
) && !lang
.empty() )
2003 data
->langs
.push_back(lang
);
2005 return TRUE
; // continue enumeration
2008 } // anonymous namespace
2011 wxArrayString
wxResourceTranslationsLoader::GetAvailableTranslations(const wxString
& domain
) const
2013 EnumCallbackData data
;
2014 data
.prefix
= domain
+ "_";
2015 data
.prefix
.MakeLower(); // resource names are case insensitive
2017 if ( !EnumResourceNames(GetModule(),
2018 GetResourceType().t_str(),
2020 reinterpret_cast<LONG_PTR
>(&data
)) )
2022 const DWORD err
= GetLastError();
2023 if ( err
!= NO_ERROR
&& err
!= ERROR_RESOURCE_TYPE_NOT_FOUND
)
2025 wxLogSysError(_("Couldn't enumerate translations"));
2032 #endif // __WINDOWS__
2035 // ----------------------------------------------------------------------------
2036 // wxTranslationsModule module (for destruction of gs_translations)
2037 // ----------------------------------------------------------------------------
2039 class wxTranslationsModule
: public wxModule
2041 DECLARE_DYNAMIC_CLASS(wxTranslationsModule
)
2043 wxTranslationsModule() {}
2052 if ( gs_translationsOwned
)
2053 delete gs_translations
;
2054 gs_translations
= NULL
;
2055 gs_translationsOwned
= true;
2059 IMPLEMENT_DYNAMIC_CLASS(wxTranslationsModule
, wxModule
)
2061 #endif // wxUSE_INTL