]> git.saurik.com Git - wxWidgets.git/blobdiff - src/common/translation.cpp
wxMessageBox off the main thread lost result code.
[wxWidgets.git] / src / common / translation.cpp
index 6ea7c334ac17b6154774664449c02f13d7366313..455fb315d07a52ed418ac1ed88abfc2e03d7fa63 100644 (file)
@@ -5,7 +5,6 @@
 //              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
@@ -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<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
@@ -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<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?
@@ -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;
+
+    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;
@@ -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<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
@@ -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() )
     {
@@ -1353,20 +1506,16 @@ bool wxTranslations::AddCatalog(const wxString& domain,
                 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
@@ -1378,31 +1527,53 @@ 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);
+    }
+
+    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
@@ -1410,81 +1581,58 @@ 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;
@@ -1515,18 +1663,13 @@ 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))
-            return GetUntranslatedString(origString);
-        else
-            return GetUntranslatedString(n == 1 ? origString : origString2);
     }
 
-    return *trans;
+    return trans;
 }
 
 
@@ -1547,14 +1690,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 +1751,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 +1761,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 +1808,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 +1817,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 +1861,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<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)
 // ----------------------------------------------------------------------------