X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/076c0a8ee8c9238af2037b66ef97bda7cede4e4a..cc4d5638c66a409e421420ed7110917755a66788:/src/common/translation.cpp?ds=sidebyside diff --git a/src/common/translation.cpp b/src/common/translation.cpp index 6ea7c334ac..8816c08013 100644 --- a/src/common/translation.cpp +++ b/src/common/translation.cpp @@ -5,7 +5,6 @@ // Michael N. Filippov // (2003/09/30 - PluralForms support) // Created: 2010-04-23 -// RCS-ID: $Id$ // Copyright: (c) 1998 Vadim Zeitlin // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// @@ -41,14 +40,27 @@ #include #include +#include "wx/arrstr.h" +#include "wx/dir.h" #include "wx/file.h" #include "wx/filename.h" #include "wx/tokenzr.h" #include "wx/fontmap.h" -#include "wx/scopedptr.h" #include "wx/stdpaths.h" #include "wx/hashset.h" +#ifdef __WINDOWS__ + #include "wx/dynlib.h" + #include "wx/scopedarray.h" + #include "wx/msw/wrapwin.h" + #include "wx/msw/missing.h" +#endif +#ifdef __WXOSX__ + #include "wx/osx/core/cfstring.h" + #include + #include +#endif + // ---------------------------------------------------------------------------- // simple types // ---------------------------------------------------------------------------- @@ -65,28 +77,170 @@ const size_t32 MSGCATALOG_MAGIC_SW = 0xde120495; #define TRACE_I18N wxS("i18n") -// the constants describing the format of ll_CC locale string -static const size_t LEN_LANG = 2; +// ============================================================================ +// implementation +// ============================================================================ + +namespace +{ + +#if !wxUSE_UNICODE +// We need to keep track of (char*) msgids in non-Unicode legacy builds. Instead +// of making the public wxMsgCatalog and wxTranslationsLoader APIs ugly, we +// store them in this global map. +wxStringToStringHashMap gs_msgIdCharset; +#endif // ---------------------------------------------------------------------------- -// global functions +// Platform specific helpers // ---------------------------------------------------------------------------- -namespace +void LogTraceArray(const char *prefix, const wxArrayString& arr) { + wxLogTrace(TRACE_I18N, "%s: [%s]", prefix, wxJoin(arr, ',')); +} -// get just the language part -inline wxString ExtractLang(const wxString& langFull) +void LogTraceLargeArray(const wxString& prefix, const wxArrayString& arr) { - return langFull.Left(LEN_LANG); + wxLogTrace(TRACE_I18N, "%s:", prefix); + for ( wxArrayString::const_iterator i = arr.begin(); i != arr.end(); ++i ) + wxLogTrace(TRACE_I18N, " %s", *i); } -} // anonymous namespace +// Use locale-based detection as a fallback +wxString GetPreferredUILanguageFallback(const wxArrayString& WXUNUSED(available)) +{ + const wxString lang = wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage()); + wxLogTrace(TRACE_I18N, " - obtained best language from locale: %s", lang); + return lang; +} +#ifdef __WINDOWS__ -// ============================================================================ -// implementation -// ============================================================================ +wxString GetPreferredUILanguage(const wxArrayString& available) +{ + typedef BOOL (WINAPI *GetUserPreferredUILanguages_t)(DWORD, PULONG, PWSTR, PULONG); + static GetUserPreferredUILanguages_t s_pfnGetUserPreferredUILanguages = NULL; + static bool s_initDone = false; + if ( !s_initDone ) + { + wxLoadedDLL dllKernel32("kernel32.dll"); + wxDL_INIT_FUNC(s_pfn, GetUserPreferredUILanguages, dllKernel32); + s_initDone = true; + } + + if ( s_pfnGetUserPreferredUILanguages ) + { + ULONG numLangs; + ULONG bufferSize = 0; + if ( (*s_pfnGetUserPreferredUILanguages)(MUI_LANGUAGE_NAME, + &numLangs, + NULL, + &bufferSize) ) + { + wxScopedArray langs(new WCHAR[bufferSize]); + if ( (*s_pfnGetUserPreferredUILanguages)(MUI_LANGUAGE_NAME, + &numLangs, + langs.get(), + &bufferSize) ) + { + wxArrayString preferred; + + WCHAR *buf = langs.get(); + for ( unsigned i = 0; i < numLangs; i++ ) + { + const wxString lang(buf); + preferred.push_back(lang); + buf += lang.length() + 1; + } + LogTraceArray(" - system preferred languages", preferred); + + for ( wxArrayString::const_iterator j = preferred.begin(); + j != preferred.end(); + ++j ) + { + wxString lang(*j); + lang.Replace("-", "_"); + if ( available.Index(lang, /*bCase=*/false) != wxNOT_FOUND ) + return lang; + size_t pos = lang.find('_'); + if ( pos != wxString::npos ) + { + lang = lang.substr(0, pos); + if ( available.Index(lang, /*bCase=*/false) != wxNOT_FOUND ) + return lang; + } + } + } + } + } + + return GetPreferredUILanguageFallback(available); +} + +#elif defined(__WXOSX__) + +void LogTraceArray(const char *prefix, CFArrayRef arr) +{ + wxString s; + const unsigned count = CFArrayGetCount(arr); + if ( count ) + { + s += wxCFStringRef::AsString((CFStringRef)CFArrayGetValueAtIndex(arr, 0)); + for ( unsigned i = 1 ; i < count; i++ ) + s += "," + wxCFStringRef::AsString((CFStringRef)CFArrayGetValueAtIndex(arr, i)); + } + wxLogTrace(TRACE_I18N, "%s: [%s]", prefix, s); +} + +wxString GetPreferredUILanguage(const wxArrayString& available) +{ + wxStringToStringHashMap availableNormalized; + wxCFRef availableArr( + CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks)); + + for ( wxArrayString::const_iterator i = available.begin(); + i != available.end(); + ++i ) + { + wxString lang(*i); + wxCFStringRef code_wx(*i); + wxCFStringRef code_norm( + CFLocaleCreateCanonicalLanguageIdentifierFromString(kCFAllocatorDefault, code_wx)); + CFArrayAppendValue(availableArr, code_norm); + availableNormalized[code_norm.AsString()] = *i; + } + LogTraceArray(" - normalized available list", availableArr); + + wxCFRef prefArr( + CFBundleCopyLocalizationsForPreferences(availableArr, NULL)); + LogTraceArray(" - system preferred languages", prefArr); + + unsigned prefArrLength = CFArrayGetCount(prefArr); + if ( prefArrLength > 0 ) + { + // Lookup the name in 'available' by index -- we need to get the + // original value corresponding to the normalized one chosen. + wxString lang(wxCFStringRef::AsString((CFStringRef)CFArrayGetValueAtIndex(prefArr, 0))); + wxStringToStringHashMap::const_iterator i = availableNormalized.find(lang); + if ( i == availableNormalized.end() ) + return lang; + else + return i->second; + } + + return GetPreferredUILanguageFallback(available); +} + +#else + +// On Unix, there's just one language=locale setting, so we should always +// use that. +#define GetPreferredUILanguage GetPreferredUILanguageFallback + +#endif + +} // anonymous namespace // ---------------------------------------------------------------------------- // Plural forms parser @@ -141,7 +295,7 @@ public: T_LEFT_BRACKET, T_RIGHT_BRACKET }; Type type() const { return m_type; } - void setType(Type type) { m_type = type; } + void setType(Type t) { m_type = t; } // for T_NUMBER only typedef int Number; Number number() const { return m_number; } @@ -318,12 +472,12 @@ private: class wxPluralFormsNode { public: - wxPluralFormsNode(const wxPluralFormsToken& token) : m_token(token) {} + wxPluralFormsNode(const wxPluralFormsToken& t) : m_token(t) {} const wxPluralFormsToken& token() const { return m_token; } - const wxPluralFormsNode* node(size_t i) const + const wxPluralFormsNode* node(unsigned i) const { return m_nodes[i].get(); } - void setNode(size_t i, wxPluralFormsNode* n); - wxPluralFormsNode* releaseNode(size_t i); + void setNode(unsigned i, wxPluralFormsNode* n); + wxPluralFormsNode* releaseNode(unsigned i); wxPluralFormsToken::Number evaluate(wxPluralFormsToken::Number n) const; private: @@ -351,12 +505,12 @@ void wxPluralFormsNodePtr::reset(wxPluralFormsNode *p) } -void wxPluralFormsNode::setNode(size_t i, wxPluralFormsNode* n) +void wxPluralFormsNode::setNode(unsigned i, wxPluralFormsNode* n) { m_nodes[i].reset(n); } -wxPluralFormsNode* wxPluralFormsNode::releaseNode(size_t i) +wxPluralFormsNode* wxPluralFormsNode::releaseNode(unsigned i) { return m_nodes[i].release(); } @@ -433,7 +587,7 @@ private: wxPluralFormsNodePtr m_plural; }; -wxDEFINE_SCOPED_PTR_TYPE(wxPluralFormsCalculator) +wxDEFINE_SCOPED_PTR(wxPluralFormsCalculator, wxPluralFormsCalculatorPtr) void wxPluralFormsCalculator::init(wxPluralFormsToken::Number nplurals, wxPluralFormsNode* plural) @@ -804,21 +958,23 @@ wxPluralFormsCalculator* wxPluralFormsCalculator::make(const char* s) // http://www.gnu.org/software/autoconf/manual/gettext/MO-Files.html // ---------------------------------------------------------------------------- -WX_DECLARE_EXPORTED_STRING_HASH_MAP(wxString, wxMessagesHash); - class wxMsgCatalogFile { public: + typedef wxScopedCharBuffer DataBuffer; + // ctor & dtor wxMsgCatalogFile(); ~wxMsgCatalogFile(); // load the catalog from disk - bool Load(const wxString& filename, - wxPluralFormsCalculatorPtr& rPluralFormsCalculator); + bool LoadFile(const wxString& filename, + wxPluralFormsCalculatorPtr& rPluralFormsCalculator); + bool LoadData(const DataBuffer& data, + wxPluralFormsCalculatorPtr& rPluralFormsCalculator); // fills the hash with string-translation pairs - bool FillHash(wxMessagesHash& hash, const wxString& msgIdCharset) const; + bool FillHash(wxStringToStringHashMap& hash, const wxString& domain) const; // return the charset of the strings in this catalog or empty string if // none/unknown @@ -847,7 +1003,7 @@ private: }; // all data is stored here - wxMemoryBuffer m_data; + DataBuffer m_data; // data description size_t32 m_numStrings; // number of strings in this domain @@ -865,25 +1021,18 @@ private: : ui; } - // just return the pointer to the start of the data as "char *" to - // facilitate doing pointer arithmetic with it - char *StringData() const - { - return static_cast(m_data.GetData()); - } - const char *StringAtOfs(wxMsgTableEntry *pTable, size_t32 n) const { const wxMsgTableEntry * const ent = pTable + n; // this check could fail for a corrupt message catalog size_t32 ofsString = Swap(ent->ofsString); - if ( ofsString + Swap(ent->nLen) > m_data.GetDataLen()) + if ( ofsString + Swap(ent->nLen) > m_data.length()) { return NULL; } - return StringData() + ofsString; + return m_data.data() + ofsString; } bool m_bSwapped; // wrong endianness? @@ -891,51 +1040,8 @@ private: wxDECLARE_NO_COPY_CLASS(wxMsgCatalogFile); }; - // ---------------------------------------------------------------------------- -// wxMsgCatalog corresponds to one loaded message catalog. -// -// This is a "low-level" class and is used only by wxLocale (that's why -// it's designed to be stored in a linked list) -// ---------------------------------------------------------------------------- - -class wxMsgCatalog -{ -public: -#if !wxUSE_UNICODE - wxMsgCatalog() { m_conv = NULL; } - ~wxMsgCatalog(); -#endif - - // load the catalog from disk - bool Load(const wxString& filename, - const wxString& domain, - const wxString& msgIdCharset); - - // get name of the catalog - wxString GetDomain() const { return m_domain; } - - // get the translated string: returns NULL if not found - const wxString *GetString(const wxString& sz, size_t n = size_t(-1)) const; - - // public variable pointing to the next element in a linked list (or NULL) - wxMsgCatalog *m_pNext; - -private: - wxMessagesHash m_messages; // all messages in the catalog - wxString m_domain; // name of the domain - -#if !wxUSE_UNICODE - // the conversion corresponding to this catalog charset if we installed it - // as the global one - wxCSConv *m_conv; -#endif - - wxPluralFormsCalculatorPtr m_pluralFormsCalculator; -}; - -// ---------------------------------------------------------------------------- -// wxMsgCatalogFile clas +// wxMsgCatalogFile class // ---------------------------------------------------------------------------- wxMsgCatalogFile::wxMsgCatalogFile() @@ -947,14 +1053,14 @@ wxMsgCatalogFile::~wxMsgCatalogFile() } // open disk file and read in it's contents -bool wxMsgCatalogFile::Load(const wxString& filename, - wxPluralFormsCalculatorPtr& rPluralFormsCalculator) +bool wxMsgCatalogFile::LoadFile(const wxString& filename, + wxPluralFormsCalculatorPtr& rPluralFormsCalculator) { wxFile fileMsg(filename); if ( !fileMsg.IsOpened() ) return false; - // get the file size (assume it is less than 4Gb...) + // get the file size (assume it is less than 4GB...) wxFileOffset lenFile = fileMsg.Length(); if ( lenFile == wxInvalidOffset ) return false; @@ -962,17 +1068,36 @@ bool wxMsgCatalogFile::Load(const wxString& filename, size_t nSize = wx_truncate_cast(size_t, lenFile); wxASSERT_MSG( nSize == lenFile + size_t(0), wxS("message catalog bigger than 4GB?") ); + wxMemoryBuffer filedata; + // read the whole file in memory - if ( fileMsg.Read(m_data.GetWriteBuf(nSize), nSize) != lenFile ) + if ( fileMsg.Read(filedata.GetWriteBuf(nSize), nSize) != lenFile ) return false; - m_data.UngetWriteBuf(nSize); + filedata.UngetWriteBuf(nSize); + bool ok = LoadData + ( + DataBuffer::CreateOwned((char*)filedata.release(), nSize), + rPluralFormsCalculator + ); + if ( !ok ) + { + wxLogWarning(_("'%s' is not a valid message catalog."), filename.c_str()); + return false; + } + return true; +} + + +bool wxMsgCatalogFile::LoadData(const DataBuffer& data, + wxPluralFormsCalculatorPtr& rPluralFormsCalculator) +{ // examine header - bool bValid = m_data.GetDataLen() > sizeof(wxMsgCatalogHeader); + bool bValid = data.length() > sizeof(wxMsgCatalogHeader); - const wxMsgCatalogHeader *pHeader = (wxMsgCatalogHeader *)m_data.GetData(); + const wxMsgCatalogHeader *pHeader = (wxMsgCatalogHeader *)data.data(); if ( bValid ) { // we'll have to swap all the integers if it's true m_bSwapped = pHeader->magic == MSGCATALOG_MAGIC_SW; @@ -983,16 +1108,17 @@ bool wxMsgCatalogFile::Load(const wxString& filename, if ( !bValid ) { // it's either too short or has incorrect magic number - wxLogWarning(_("'%s' is not a valid message catalog."), filename.c_str()); - + wxLogWarning(_("Invalid message catalog.")); return false; } + m_data = data; + // initialize m_numStrings = Swap(pHeader->numStrings); - m_pOrigTable = (wxMsgTableEntry *)(StringData() + + m_pOrigTable = (wxMsgTableEntry *)(data.data() + Swap(pHeader->ofsOrigTable)); - m_pTransTable = (wxMsgTableEntry *)(StringData() + + m_pTransTable = (wxMsgTableEntry *)(data.data() + Swap(pHeader->ofsTransTable)); // now parse catalog's header and try to extract catalog charset and @@ -1016,7 +1142,7 @@ bool wxMsgCatalogFile::Load(const wxString& filename, if ( m_charset == wxS("CHARSET") ) { // "CHARSET" is not valid charset, but lazy translator - m_charset.empty(); + m_charset.clear(); } } } @@ -1055,10 +1181,10 @@ bool wxMsgCatalogFile::Load(const wxString& filename, return true; } -bool wxMsgCatalogFile::FillHash(wxMessagesHash& hash, - const wxString& msgIdCharset) const +bool wxMsgCatalogFile::FillHash(wxStringToStringHashMap& hash, + const wxString& domain) const { - wxUnusedVar(msgIdCharset); // silence warning in Unicode build + wxUnusedVar(domain); // silence warning in Unicode build // conversion to use to convert catalog strings to the GUI encoding wxMBConv *inputConv = NULL; @@ -1086,6 +1212,8 @@ bool wxMsgCatalogFile::FillHash(wxMessagesHash& hash, } #if !wxUSE_UNICODE + wxString msgIdCharset = gs_msgIdCharset[domain]; + // conversion to apply to msgid strings before looking them up: we only // need it if the msgids are neither in 7 bit ASCII nor in the same // encoding as the catalog @@ -1177,31 +1305,48 @@ wxMsgCatalog::~wxMsgCatalog() } #endif // !wxUSE_UNICODE -bool wxMsgCatalog::Load(const wxString& filename, - const wxString& domain, - const wxString& msgIdCharset) +/* static */ +wxMsgCatalog *wxMsgCatalog::CreateFromFile(const wxString& filename, + const wxString& domain) { + wxScopedPtr cat(new wxMsgCatalog(domain)); + wxMsgCatalogFile file; - m_domain = domain; + if ( !file.LoadFile(filename, cat->m_pluralFormsCalculator) ) + return NULL; - if ( !file.Load(filename, m_pluralFormsCalculator) ) - return false; + if ( !file.FillHash(cat->m_messages, domain) ) + return NULL; - if ( !file.FillHash(m_messages, msgIdCharset) ) - return false; + return cat.release(); +} - return true; +/* static */ +wxMsgCatalog *wxMsgCatalog::CreateFromData(const wxScopedCharBuffer& data, + const wxString& domain) +{ + wxScopedPtr cat(new wxMsgCatalog(domain)); + + wxMsgCatalogFile file; + + if ( !file.LoadData(data, cat->m_pluralFormsCalculator) ) + return NULL; + + if ( !file.FillHash(cat->m_messages, domain) ) + return NULL; + + return cat.release(); } -const wxString *wxMsgCatalog::GetString(const wxString& str, size_t n) const +const wxString *wxMsgCatalog::GetString(const wxString& str, unsigned n) const { int index = 0; - if (n != size_t(-1)) + if (n != UINT_MAX) { index = m_pluralFormsCalculator->evaluate(n); } - wxMessagesHash::const_iterator i; + wxStringToStringHashMap::const_iterator i; if (index != 0) { i = m_messages.find(wxString(str) + wxChar(index)); // plural @@ -1303,6 +1448,14 @@ void wxTranslations::SetLanguage(const wxString& lang) } +wxArrayString wxTranslations::GetAvailableTranslations(const wxString& domain) const +{ + wxCHECK_MSG( m_loader, wxArrayString(), "loader can't be NULL" ); + + return m_loader->GetAvailableTranslations(domain); +} + + bool wxTranslations::AddStdCatalog() { if ( !AddCatalog(wxS("wxstd")) ) @@ -1330,7 +1483,7 @@ bool wxTranslations::AddCatalog(const wxString& domain, wxLanguage msgIdLanguage, const wxString& msgIdCharset) { - m_msgIdCharset[domain] = msgIdCharset; + gs_msgIdCharset[domain] = msgIdCharset; return AddCatalog(domain, msgIdLanguage); } #endif // !wxUSE_UNICODE @@ -1339,7 +1492,7 @@ bool wxTranslations::AddCatalog(const wxString& domain, wxLanguage msgIdLanguage) { const wxString msgIdLang = wxLocale::GetLanguageCanonicalName(msgIdLanguage); - const wxString domain_lang = ChooseLanguageForDomain(domain, msgIdLang); + const wxString domain_lang = GetBestTranslation(domain, msgIdLang); if ( domain_lang.empty() ) { @@ -1367,6 +1520,8 @@ bool wxTranslations::LoadCatalog(const wxString& domain, const wxString& lang) { wxCHECK_MSG( m_loader, false, "loader can't be NULL" ); + wxMsgCatalog *cat = NULL; + #if wxUSE_FONTMAP // first look for the catalog for this language and the current locale: // notice that we don't use the system name for the locale as this would @@ -1378,31 +1533,44 @@ bool wxTranslations::LoadCatalog(const wxString& domain, const wxString& lang) wxString fullname(lang); fullname << wxS('.') << wxFontMapperBase::GetEncodingName(encSys); - if ( m_loader->LoadCatalog(this, domain, fullname) ) - return true; + cat = m_loader->LoadCatalog(domain, fullname); } #endif // wxUSE_FONTMAP - // Next try: use the provided name language name: - if ( m_loader->LoadCatalog(this, domain, lang) ) - return true; + if ( !cat ) + { + // Next try: use the provided name language name: + cat = m_loader->LoadCatalog(domain, lang); + } - // Also try just base locale name: for things like "fr_BE" (Belgium - // French) we should use fall back on plain "fr" if no Belgium-specific - // message catalogs exist - if ( lang.length() > LEN_LANG && lang[LEN_LANG] == wxS('_') ) + if ( !cat ) { - if ( m_loader->LoadCatalog(this, domain, ExtractLang(lang)) ) - return true; + // Also try just base locale name: for things like "fr_BE" (Belgium + // French) we should use fall back on plain "fr" if no Belgium-specific + // message catalogs exist + wxString baselang = lang.BeforeFirst('_'); + if ( lang != baselang ) + cat = m_loader->LoadCatalog(domain, baselang); } - // Nothing worked, the catalog just isn't there - wxLogTrace(TRACE_I18N, - "Catalog \"%s.mo\" not found for language \"%s\".", - domain, lang); - return false; -} + if ( cat ) + { + // add it to the head of the list so that in GetString it will + // be searched before the catalogs added earlier + cat->m_pNext = m_pMsgCat; + m_pMsgCat = cat; + return true; + } + else + { + // Nothing worked, the catalog just isn't there + wxLogTrace(TRACE_I18N, + "Catalog \"%s.mo\" not found for language \"%s\".", + domain, lang); + return false; + } +} // check if the given catalog is loaded bool wxTranslations::IsLoaded(const wxString& domain) const @@ -1410,46 +1578,30 @@ bool wxTranslations::IsLoaded(const wxString& domain) const return FindCatalog(domain) != NULL; } - -bool wxTranslations::LoadCatalogFile(const wxString& filename, - const wxString& domain) +wxString wxTranslations::GetBestTranslation(const wxString& domain, + wxLanguage msgIdLanguage) { - wxMsgCatalog *pMsgCat = new wxMsgCatalog; - -#if wxUSE_UNICODE - const bool ok = pMsgCat->Load(filename, domain, wxEmptyString/*unused*/); -#else - const bool ok = pMsgCat->Load(filename, domain, - m_msgIdCharset[domain]); -#endif - - if ( !ok ) - { - // don't add it because it couldn't be loaded anyway - delete pMsgCat; - return false; - } - - // add it to the head of the list so that in GetString it will - // be searched before the catalogs added earlier - pMsgCat->m_pNext = m_pMsgCat; - m_pMsgCat = pMsgCat; - - return true; + const wxString lang = wxLocale::GetLanguageCanonicalName(msgIdLanguage); + return GetBestTranslation(domain, lang); } - -wxString wxTranslations::ChooseLanguageForDomain(const wxString& WXUNUSED(domain), - const wxString& WXUNUSED(msgIdLang)) +wxString wxTranslations::GetBestTranslation(const wxString& domain, + const wxString& msgIdLanguage) { // explicitly set language should always be respected if ( !m_lang.empty() ) return m_lang; - // TODO: if the default language is used, pick the best (by comparing - // available languages with user's preferences), instead of blindly - // trusting availability of system language translation - return wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage()); + wxArrayString available(GetAvailableTranslations(domain)); + // it's OK to have duplicates, so just add msgid language + available.push_back(msgIdLanguage); + available.push_back(msgIdLanguage.BeforeFirst('_')); + + wxLogTrace(TRACE_I18N, "choosing best language for domain '%s'", domain); + LogTraceArray(" - available translations", available); + const wxString lang = GetPreferredUILanguage(available); + wxLogTrace(TRACE_I18N, " => using language '%s'", lang); + return lang; } @@ -1475,12 +1627,12 @@ const wxString& wxTranslations::GetUntranslatedString(const wxString& str) const wxString& wxTranslations::GetString(const wxString& origString, const wxString& domain) const { - return GetString(origString, origString, size_t(-1), domain); + return GetString(origString, origString, UINT_MAX, domain); } const wxString& wxTranslations::GetString(const wxString& origString, const wxString& origString2, - size_t n, + unsigned n, const wxString& domain) const { if ( origString.empty() ) @@ -1515,12 +1667,12 @@ const wxString& wxTranslations::GetString(const wxString& origString, TRACE_I18N, "string \"%s\"%s not found in %slocale '%s'.", origString, - ((long)n) != -1 ? wxString::Format("[%ld]", (long)n) : wxString(), - !domain.empty() ? wxString::Format("domain '%s' ", domain) : wxString(), + (n != UINT_MAX ? wxString::Format("[%ld]", (long)n) : wxString()), + (!domain.empty() ? wxString::Format("domain '%s' ", domain) : wxString()), m_lang ); - if (n == size_t(-1)) + if (n == UINT_MAX) return GetUntranslatedString(origString); else return GetUntranslatedString(n == 1 ? origString : origString2); @@ -1547,14 +1699,14 @@ wxString wxTranslations::GetHeaderValue(const wxString& header, if ( pMsgCat == NULL ) return wxEmptyString; - trans = pMsgCat->GetString(wxEmptyString, (size_t)-1); + trans = pMsgCat->GetString(wxEmptyString, UINT_MAX); } else { // search in all domains for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext ) { - trans = pMsgCat->GetString(wxEmptyString, (size_t)-1); + trans = pMsgCat->GetString(wxEmptyString, UINT_MAX); if ( trans != NULL ) // take the first found break; } @@ -1608,7 +1760,7 @@ wxArrayString gs_searchPrefixes; wxString GetMsgCatalogSubdirs(const wxString& prefix, const wxString& lang) { // Search first in Unix-standard prefix/lang/LC_MESSAGES, then in - // prefix/lang and finally in just prefix. + // prefix/lang. // // Note that we use LC_MESSAGES on all platforms and not just Unix, because // it doesn't cost much to look into one more directory and doing it this @@ -1618,36 +1770,42 @@ wxString GetMsgCatalogSubdirs(const wxString& prefix, const wxString& lang) // breaking apps after they are recompiled against the latest wx // b) it makes it possible to package app's support files in the same // way on all target platforms - const wxString pathPrefix = wxFileName(prefix, lang).GetFullPath(); + const wxString prefixAndLang = wxFileName(prefix, lang).GetFullPath(); wxString searchPath; - searchPath.reserve(4*pathPrefix.length()); - searchPath << pathPrefix << wxFILE_SEP_PATH << "LC_MESSAGES" << wxPATH_SEP - << prefix << wxFILE_SEP_PATH << wxPATH_SEP - << pathPrefix; + searchPath.reserve(4*prefixAndLang.length()); + + searchPath +#ifdef __WXOSX__ + << prefixAndLang << ".lproj/LC_MESSAGES" << wxPATH_SEP + << prefixAndLang << ".lproj" << wxPATH_SEP +#endif + << prefixAndLang << wxFILE_SEP_PATH << "LC_MESSAGES" << wxPATH_SEP + << prefixAndLang << wxPATH_SEP + ; return searchPath; } -// construct the search path for the given language -static wxString GetFullSearchPath(const wxString& lang) +bool HasMsgCatalogInDir(const wxString& dir, const wxString& domain) +{ + return wxFileName(dir, domain, "mo").FileExists() || + wxFileName(dir + wxFILE_SEP_PATH + "LC_MESSAGES", domain, "mo").FileExists(); +} + +// get prefixes to locale directories; if lang is empty, don't point to +// OSX's .lproj bundles +wxArrayString GetSearchPrefixes() { - // first take the entries explicitly added by the program wxArrayString paths; - paths.reserve(gs_searchPrefixes.size() + 1); - size_t n, - count = gs_searchPrefixes.size(); - for ( n = 0; n < count; n++ ) - { - paths.Add(GetMsgCatalogSubdirs(gs_searchPrefixes[n], lang)); - } + // first take the entries explicitly added by the program + paths = gs_searchPrefixes; #if wxUSE_STDPATHS // then look in the standard location - const wxString stdp = wxStandardPaths::Get(). - GetLocalizedResourcesDir(lang, wxStandardPaths::ResourceCat_Messages); - + wxString stdp; + stdp = wxStandardPaths::Get().GetResourcesDir(); if ( paths.Index(stdp) == wxNOT_FOUND ) paths.Add(stdp); #endif // wxUSE_STDPATHS @@ -1659,7 +1817,7 @@ static wxString GetFullSearchPath(const wxString& lang) const char *pszLcPath = wxGetenv("LC_PATH"); if ( pszLcPath ) { - const wxString lcp = GetMsgCatalogSubdirs(pszLcPath, lang); + const wxString lcp = pszLcPath; if ( paths.Index(lcp) == wxNOT_FOUND ) paths.Add(lcp); } @@ -1668,22 +1826,32 @@ static wxString GetFullSearchPath(const wxString& lang) wxString wxp = wxGetInstallPrefix(); if ( !wxp.empty() ) { - wxp = GetMsgCatalogSubdirs(wxp + wxS("/share/locale"), lang); + wxp += wxS("/share/locale"); if ( paths.Index(wxp) == wxNOT_FOUND ) paths.Add(wxp); } #endif // __UNIX__ + return paths; +} - // finally construct the full search path +// construct the search path for the given language +wxString GetFullSearchPath(const wxString& lang) +{ wxString searchPath; searchPath.reserve(500); - count = paths.size(); - for ( n = 0; n < count; n++ ) + + const wxArrayString prefixes = GetSearchPrefixes(); + + for ( wxArrayString::const_iterator i = prefixes.begin(); + i != prefixes.end(); + ++i ) { - searchPath += paths[n]; - if ( n != count - 1 ) + const wxString p = GetMsgCatalogSubdirs(*i, lang); + + if ( !searchPath.empty() ) searchPath += wxPATH_SEP; + searchPath += p; } return searchPath; @@ -1702,32 +1870,167 @@ void wxFileTranslationsLoader::AddCatalogLookupPathPrefix(const wxString& prefix } -bool wxFileTranslationsLoader::LoadCatalog(wxTranslations *translations, - const wxString& domain, - const wxString& lang) +wxMsgCatalog *wxFileTranslationsLoader::LoadCatalog(const wxString& domain, + const wxString& lang) { - wxCHECK_MSG( lang.length() >= LEN_LANG, false, - "invalid language specification" ); - wxString searchPath = GetFullSearchPath(lang); - wxLogTrace(TRACE_I18N, wxS("Looking for \"%s.mo\" in search path \"%s\""), - domain, searchPath); + LogTraceLargeArray + ( + wxString::Format("looking for \"%s.mo\" in search path", domain), + wxSplit(searchPath, wxPATH_SEP[0]) + ); wxFileName fn(domain); fn.SetExt(wxS("mo")); wxString strFullName; if ( !wxFindFileInPath(&strFullName, searchPath, fn.GetFullPath()) ) - return false; + return NULL; // open file and read its data wxLogVerbose(_("using catalog '%s' from '%s'."), domain, strFullName.c_str()); wxLogTrace(TRACE_I18N, wxS("Using catalog \"%s\"."), strFullName.c_str()); - return translations->LoadCatalogFile(strFullName, domain); + return wxMsgCatalog::CreateFromFile(strFullName, domain); +} + + +wxArrayString wxFileTranslationsLoader::GetAvailableTranslations(const wxString& domain) const +{ + wxArrayString langs; + const wxArrayString prefixes = GetSearchPrefixes(); + + LogTraceLargeArray + ( + wxString::Format("looking for available translations of \"%s\" in search path", domain), + prefixes + ); + + for ( wxArrayString::const_iterator i = prefixes.begin(); + i != prefixes.end(); + ++i ) + { + if ( i->empty() ) + continue; + wxDir dir; + if ( !dir.Open(*i) ) + continue; + + wxString lang; + for ( bool ok = dir.GetFirst(&lang, "", wxDIR_DIRS); + ok; + ok = dir.GetNext(&lang) ) + { + const wxString langdir = *i + wxFILE_SEP_PATH + lang; + if ( HasMsgCatalogInDir(langdir, domain) ) + { +#ifdef __WXOSX__ + wxString rest; + if ( lang.EndsWith(".lproj", &rest) ) + lang = rest; +#endif // __WXOSX__ + + wxLogTrace(TRACE_I18N, + "found %s translation of \"%s\" in %s", + lang, domain, langdir); + langs.push_back(lang); + } + } + } + + return langs; } + +// ---------------------------------------------------------------------------- +// wxResourceTranslationsLoader +// ---------------------------------------------------------------------------- + +#ifdef __WINDOWS__ + +wxMsgCatalog *wxResourceTranslationsLoader::LoadCatalog(const wxString& domain, + const wxString& lang) +{ + const void *mo_data = NULL; + size_t mo_size = 0; + + const wxString resname = wxString::Format("%s_%s", domain, lang); + + if ( !wxLoadUserResource(&mo_data, &mo_size, + resname, + GetResourceType().t_str(), + GetModule()) ) + return NULL; + + wxLogTrace(TRACE_I18N, + "Using catalog from Windows resource \"%s\".", resname); + + wxMsgCatalog *cat = wxMsgCatalog::CreateFromData( + wxCharBuffer::CreateNonOwned(static_cast(mo_data), mo_size), + domain); + + if ( !cat ) + { + wxLogWarning(_("Resource '%s' is not a valid message catalog."), resname); + } + + return cat; +} + +namespace +{ + +struct EnumCallbackData +{ + wxString prefix; + wxArrayString langs; +}; + +BOOL CALLBACK EnumTranslations(HMODULE WXUNUSED(hModule), + LPCTSTR WXUNUSED(lpszType), + LPTSTR lpszName, + LONG_PTR lParam) +{ + wxString name(lpszName); + name.MakeLower(); // resource names are case insensitive + + EnumCallbackData *data = reinterpret_cast(lParam); + + wxString lang; + if ( name.StartsWith(data->prefix, &lang) && !lang.empty() ) + data->langs.push_back(lang); + + return TRUE; // continue enumeration +} + +} // anonymous namespace + + +wxArrayString wxResourceTranslationsLoader::GetAvailableTranslations(const wxString& domain) const +{ + EnumCallbackData data; + data.prefix = domain + "_"; + data.prefix.MakeLower(); // resource names are case insensitive + + if ( !EnumResourceNames(GetModule(), + GetResourceType().t_str(), + EnumTranslations, + reinterpret_cast(&data)) ) + { + const DWORD err = GetLastError(); + if ( err != NO_ERROR && err != ERROR_RESOURCE_TYPE_NOT_FOUND ) + { + wxLogSysError(_("Couldn't enumerate translations")); + } + } + + return data.langs; +} + +#endif // __WINDOWS__ + + // ---------------------------------------------------------------------------- // wxTranslationsModule module (for destruction of gs_translations) // ----------------------------------------------------------------------------