// Michael N. Filippov <michael@idisys.iae.nsk.su>
// (2003/09/30 - PluralForms support)
// Created: 2010-04-23
-// RCS-ID: $Id$
// Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
#include <ctype.h>
#include <stdlib.h>
+#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"
+#include "wx/private/threadinfo.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 <CoreFoundation/CFBundle.h>
+ #include <CoreFoundation/CFLocale.h>
+#endif
// ----------------------------------------------------------------------------
// simple types
#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<WCHAR> 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<CFMutableArrayRef> 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<CFArrayRef> 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
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; }
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:
}
-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();
}
wxPluralFormsNodePtr m_plural;
};
-wxDEFINE_SCOPED_PTR_TYPE(wxPluralFormsCalculator)
+wxDEFINE_SCOPED_PTR(wxPluralFormsCalculator, wxPluralFormsCalculatorPtr)
void wxPluralFormsCalculator::init(wxPluralFormsToken::Number nplurals,
wxPluralFormsNode* plural)
// 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
};
// all data is stored here
- wxMemoryBuffer m_data;
+ DataBuffer m_data;
// data description
size_t32 m_numStrings; // number of strings in this domain
: 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<char *>(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?
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()
}
// 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;
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;
+
+ 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;
+ }
- m_data.UngetWriteBuf(nSize);
+ 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;
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
if ( m_charset == wxS("CHARSET") )
{
// "CHARSET" is not valid charset, but lazy translator
- m_charset.empty();
+ m_charset.clear();
}
}
}
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;
}
#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
}
#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<wxMsgCatalog> 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<wxMsgCatalog> 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
}
+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")) )
wxLanguage msgIdLanguage,
const wxString& msgIdCharset)
{
- m_msgIdCharset[domain] = msgIdCharset;
+ gs_msgIdCharset[domain] = msgIdCharset;
return AddCatalog(domain, msgIdLanguage);
}
#endif // !wxUSE_UNICODE
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() )
{
wxS("adding '%s' translation for domain '%s' (msgid language '%s')"),
domain_lang, domain, msgIdLang);
- // It is OK to not load catalog if the msgid language and m_language match,
- // in which case we can directly display the texts embedded in program's
- // source code:
- if ( msgIdLang == domain_lang )
- return true;
-
- return LoadCatalog(domain, domain_lang);
+ return LoadCatalog(domain, domain_lang, msgIdLang);
}
-bool wxTranslations::LoadCatalog(const wxString& domain, const wxString& lang)
+bool wxTranslations::LoadCatalog(const wxString& domain, const wxString& lang, const wxString& msgIdLang)
{
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
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);
+ }
+
+ if ( !cat )
+ {
+ // 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);
+ }
- // 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)) )
+ // It is OK to not load catalog if the msgid language and m_language match,
+ // in which case we can directly display the texts embedded in program's
+ // source code:
+ if ( msgIdLang == lang )
return true;
}
- // 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
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('_'));
-namespace
-{
-WX_DECLARE_HASH_SET(wxString, wxStringHash, wxStringEqual,
- wxLocaleUntranslatedStrings);
+ 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;
}
+
/* static */
const wxString& wxTranslations::GetUntranslatedString(const wxString& str)
{
- static wxLocaleUntranslatedStrings s_strings;
+ wxLocaleUntranslatedStrings& strings = wxThreadInfo.untranslatedStrings;
- wxLocaleUntranslatedStrings::iterator i = s_strings.find(str);
- if ( i == s_strings.end() )
- return *s_strings.insert(str).first;
+ wxLocaleUntranslatedStrings::iterator i = strings.find(str);
+ if ( i == strings.end() )
+ return *strings.insert(str).first;
return *i;
}
-const wxString& wxTranslations::GetString(const wxString& origString,
- const wxString& domain) const
+const wxString *wxTranslations::GetTranslatedString(const wxString& origString,
+ const wxString& domain) const
{
- return GetString(origString, origString, size_t(-1), domain);
+ return GetTranslatedString(origString, UINT_MAX, domain);
}
-const wxString& wxTranslations::GetString(const wxString& origString,
- const wxString& origString2,
- size_t n,
- const wxString& domain) const
+const wxString *wxTranslations::GetTranslatedString(const wxString& origString,
+ unsigned n,
+ const wxString& domain) const
{
if ( origString.empty() )
- return GetUntranslatedString(origString);
+ return NULL;
const wxString *trans = NULL;
wxMsgCatalog *pMsgCat;
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))
- return GetUntranslatedString(origString);
- else
- return GetUntranslatedString(n == 1 ? origString : origString2);
}
- return *trans;
+ return trans;
}
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;
}
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
// 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
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);
}
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;
}
-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<const char*>(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<EnumCallbackData*>(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<LONG_PTR>(&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)
// ----------------------------------------------------------------------------