]> git.saurik.com Git - wxWidgets.git/blobdiff - src/common/translation.cpp
adding ContentScaleFactor support to dc - defaulting to 1.0
[wxWidgets.git] / src / common / translation.cpp
index 71a16ca84726251bb0b4cdbdd058275f18d2ef9a..00350928a5417e57fde4f2c0765808533fa0a92a 100644 (file)
 #include "wx/stdpaths.h"
 #include "wx/hashset.h"
 
 #include "wx/stdpaths.h"
 #include "wx/hashset.h"
 
-#ifdef __WXMSW__
+#ifdef __WINDOWS__
+    #include "wx/dynlib.h"
+    #include "wx/scopedarray.h"
     #include "wx/msw/wrapwin.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
 
 // ----------------------------------------------------------------------------
 #endif
 
 // ----------------------------------------------------------------------------
@@ -84,6 +92,148 @@ namespace
 wxStringToStringHashMap gs_msgIdCharset;
 #endif
 
 wxStringToStringHashMap gs_msgIdCharset;
 #endif
 
+// ----------------------------------------------------------------------------
+// Platform specific helpers
+// ----------------------------------------------------------------------------
+
+void LogTraceArray(const char *prefix, const wxArrayString& arr)
+{
+    wxLogTrace(TRACE_I18N, "%s: [%s]", prefix, wxJoin(arr, ','));
+}
+
+// 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__
+
+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
 
 // ----------------------------------------------------------------------------
 } // anonymous namespace
 
 // ----------------------------------------------------------------------------
@@ -139,7 +289,7 @@ public:
         T_LEFT_BRACKET, T_RIGHT_BRACKET
     };
     Type type() const { return m_type; }
         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; }
     // for T_NUMBER only
     typedef int Number;
     Number number() const { return m_number; }
@@ -316,7 +466,7 @@ private:
 class wxPluralFormsNode
 {
 public:
 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(unsigned i) const
         { return m_nodes[i].get(); }
     const wxPluralFormsToken& token() const { return m_token; }
     const wxPluralFormsNode* node(unsigned i) const
         { return m_nodes[i].get(); }
@@ -885,7 +1035,7 @@ private:
 };
 
 // ----------------------------------------------------------------------------
 };
 
 // ----------------------------------------------------------------------------
-// wxMsgCatalogFile clas
+// wxMsgCatalogFile class
 // ----------------------------------------------------------------------------
 
 wxMsgCatalogFile::wxMsgCatalogFile()
 // ----------------------------------------------------------------------------
 
 wxMsgCatalogFile::wxMsgCatalogFile()
@@ -904,7 +1054,7 @@ bool wxMsgCatalogFile::LoadFile(const wxString& filename,
     if ( !fileMsg.IsOpened() )
         return false;
 
     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;
     wxFileOffset lenFile = fileMsg.Length();
     if ( lenFile == wxInvalidOffset )
         return false;
@@ -986,7 +1136,7 @@ bool wxMsgCatalogFile::LoadData(const DataBuffer& data,
                 if ( m_charset == wxS("CHARSET") )
                 {
                     // "CHARSET" is not valid charset, but lazy translator
                 if ( m_charset == wxS("CHARSET") )
                 {
                     // "CHARSET" is not valid charset, but lazy translator
-                    m_charset.empty();
+                    m_charset.clear();
                 }
             }
         }
                 }
             }
         }
@@ -1294,7 +1444,7 @@ void wxTranslations::SetLanguage(const wxString& lang)
 
 wxArrayString wxTranslations::GetAvailableTranslations(const wxString& domain) const
 {
 
 wxArrayString wxTranslations::GetAvailableTranslations(const wxString& domain) const
 {
-    wxCHECK_MSG( m_loader, false, "loader can't be NULL" );
+    wxCHECK_MSG( m_loader, wxArrayString(), "loader can't be NULL" );
 
     return m_loader->GetAvailableTranslations(domain);
 }
 
     return m_loader->GetAvailableTranslations(domain);
 }
@@ -1336,7 +1486,7 @@ bool wxTranslations::AddCatalog(const wxString& domain,
                                 wxLanguage msgIdLanguage)
 {
     const wxString msgIdLang = wxLocale::GetLanguageCanonicalName(msgIdLanguage);
                                 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() )
     {
 
     if ( domain_lang.empty() )
     {
@@ -1362,7 +1512,6 @@ bool wxTranslations::AddCatalog(const wxString& domain,
 
 bool wxTranslations::LoadCatalog(const wxString& domain, const wxString& lang)
 {
 
 bool wxTranslations::LoadCatalog(const wxString& domain, const wxString& lang)
 {
-    m_loader->GetAvailableTranslations(domain);
     wxCHECK_MSG( m_loader, false, "loader can't be NULL" );
 
     wxMsgCatalog *cat = NULL;
     wxCHECK_MSG( m_loader, false, "loader can't be NULL" );
 
     wxMsgCatalog *cat = NULL;
@@ -1423,18 +1572,30 @@ bool wxTranslations::IsLoaded(const wxString& domain) const
     return FindCatalog(domain) != NULL;
 }
 
     return FindCatalog(domain) != NULL;
 }
 
+wxString wxTranslations::GetBestTranslation(const wxString& domain,
+                                            wxLanguage msgIdLanguage)
+{
+    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;
 
 {
     // 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;
 }
 
 
 }
 
 
@@ -1500,8 +1661,8 @@ const wxString& wxTranslations::GetString(const wxString& origString,
             TRACE_I18N,
             "string \"%s\"%s not found in %slocale '%s'.",
             origString,
             TRACE_I18N,
             "string \"%s\"%s not found in %slocale '%s'.",
             origString,
-            n != UINT_MAX ? 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
         );
 
             m_lang
         );
 
@@ -1603,13 +1764,13 @@ 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
     //    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;
 
     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 << prefixAndLang << wxFILE_SEP_PATH << "LC_MESSAGES" << wxPATH_SEP
+               << prefixAndLang << wxPATH_SEP
+               << prefix;
 
     return searchPath;
 }
 
     return searchPath;
 }
@@ -1741,6 +1902,8 @@ wxArrayString wxFileTranslationsLoader::GetAvailableTranslations(const wxString&
           i != prefixes.end();
           ++i )
     {
           i != prefixes.end();
           ++i )
     {
+        if ( i->empty() )
+            continue;
         wxDir dir;
         if ( !dir.Open(*i) )
             continue;
         wxDir dir;
         if ( !dir.Open(*i) )
             continue;
@@ -1786,9 +1949,9 @@ wxMsgCatalog *wxResourceTranslationsLoader::LoadCatalog(const wxString& domain,
 
     if ( !wxLoadUserResource(&mo_data, &mo_size,
                              resname,
 
     if ( !wxLoadUserResource(&mo_data, &mo_size,
                              resname,
-                             GetResourceType(),
+                             GetResourceType().t_str(),
                              GetModule()) )
                              GetModule()) )
-        return false;
+        return NULL;
 
     wxLogTrace(TRACE_I18N,
                "Using catalog from Windows resource \"%s\".", resname);
 
     wxLogTrace(TRACE_I18N,
                "Using catalog from Windows resource \"%s\".", resname);
@@ -1798,7 +1961,9 @@ wxMsgCatalog *wxResourceTranslationsLoader::LoadCatalog(const wxString& domain,
         domain);
 
     if ( !cat )
         domain);
 
     if ( !cat )
+    {
         wxLogWarning(_("Resource '%s' is not a valid message catalog."), resname);
         wxLogWarning(_("Resource '%s' is not a valid message catalog."), resname);
+    }
 
     return cat;
 }
 
     return cat;
 }
@@ -1839,13 +2004,15 @@ wxArrayString wxResourceTranslationsLoader::GetAvailableTranslations(const wxStr
     data.prefix.MakeLower(); // resource names are case insensitive
 
     if ( !EnumResourceNames(GetModule(),
     data.prefix.MakeLower(); // resource names are case insensitive
 
     if ( !EnumResourceNames(GetModule(),
-                            GetResourceType(),
+                            GetResourceType().t_str(),
                             EnumTranslations,
                             reinterpret_cast<LONG_PTR>(&data)) )
     {
         const DWORD err = GetLastError();
         if ( err != NO_ERROR && err != ERROR_RESOURCE_TYPE_NOT_FOUND )
                             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"));
             wxLogSysError(_("Couldn't enumerate translations"));
+        }
     }
 
     return data.langs;
     }
 
     return data.langs;