]> git.saurik.com Git - wxWidgets.git/commitdiff
Add wxTranslations::GetBestTranslation().
authorVáclav Slavík <vslavik@fastmail.fm>
Sat, 8 Sep 2012 08:58:38 +0000 (08:58 +0000)
committerVáclav Slavík <vslavik@fastmail.fm>
Sat, 8 Sep 2012 08:58:38 +0000 (08:58 +0000)
Implement preferred language selection on modern systems (OS X, Windows
Vista+). User settings for locale (aka "regional settings") and UI
language are independent there and the UI language shouldn't be
determined from the locale.

Moreover, the OS provides a list of preferred languages, not a single
value (as with locale), so we should use the best language given user's
preferences and available translations. A Czech user may prefer Slovak
UI over English, for example, and we should use Slovak translation in
absence of Czech one in that case instead of falling back to English.

On Unix, locale is language and so things remain as before.

Notice that calling wxLocale::Init(wxLANGUAGE_DEFAULT) does the right
thing now: it sets the locale to whatever the user has configured in
regional settings and loads translations corresponding to default
wxTranslations language, which is determined as described above.
Previously, UI would be translated using a language corresponding to the
regional settings.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@72430 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775

include/wx/translation.h
interface/wx/intl.h
interface/wx/translation.h
src/common/translation.cpp

index 67b2bd8afcc7faf3ed3f2f87bf6512ab529e8a4f..c12ae52046d6fb7f05680400983f07faa5425a46 100644 (file)
@@ -132,6 +132,11 @@ public:
     // get languages available for this app
     wxArrayString GetAvailableTranslations(const wxString& domain) const;
 
+    // find best translation language for given domain
+    wxString GetBestTranslation(const wxString& domain, wxLanguage msgIdLanguage);
+    wxString GetBestTranslation(const wxString& domain,
+                                const wxString& msgIdLanguage = "en");
+
     // add standard wxWidgets catalog ("wxstd")
     bool AddStdCatalog();
 
@@ -168,10 +173,6 @@ private:
     // perform loading of the catalog via m_loader
     bool LoadCatalog(const wxString& domain, const wxString& lang);
 
-    // find best translation for given domain
-    wxString ChooseLanguageForDomain(const wxString& domain,
-                                     const wxString& msgIdLang);
-
     // find catalog by name in a linked list, return NULL if !found
     wxMsgCatalog *FindCatalog(const wxString& domain) const;
 
index 85264edfdff441ddf0d6ff00ef044f06cea7dc32..ca4587ae8ff7b1ddef512abbfb3afb46a302ef24 100644 (file)
@@ -391,10 +391,17 @@ public:
     static wxString GetSystemEncodingName();
 
     /**
-        Tries to detect the user's default language setting.
+        Tries to detect the user's default locale setting.
 
         Returns the ::wxLanguage value or @c wxLANGUAGE_UNKNOWN if the language-guessing
         algorithm failed.
+
+        @note This function works with @em locales and returns the user's default
+              locale. This may be, and usually is, the same as their preferred UI
+              language, but it's not the same thing. Use wxTranslation to obtain
+              @em language information.
+
+        @see wxTranslations::GetBestTranslation().
     */
     static int GetSystemLanguage();
 
index e0f258794004eecabb0bf888d955ed9c8652262d..791cb757a9658713dc15407a92c5d4501c4ec028 100644 (file)
@@ -30,7 +30,7 @@
 
     @since 2.9.1
 
-    @see wxLocale
+    @see wxLocale, wxTranslationsLoader, wxFileTranslationsLoader
  */
 class wxTranslations
 {
@@ -85,9 +85,58 @@ public:
         This method can be used e.g. to populate list of application's
         translations offered to the user. To do this, pass the app's main
         catalog as @a domain.
+
+        @see GetBestTranslation()
      */
     wxArrayString GetAvailableTranslations(const wxString& domain) const;
 
+    /**
+        Returns the best UI language for the @a domain.
+
+        The language is determined from the preferred UI language or languages
+        list the user configured in the OS. Notice that this may or may not
+        correspond to the default @em locale as obtained from
+        wxLocale::GetSystemLanguage(); modern operation systems (Windows
+        Vista+, OS X) have separate language and regional (= locale) settings.
+
+        @param domain
+            The catalog domain to look for.
+
+        @param msgIdLanguage
+            Specifies the language of "msgid" strings in source code
+            (i.e. arguments to GetString(), wxGetTranslation() and the _() macro).
+
+        @return Language code if a suitable match was found, empty string
+                otherwise.
+
+        @since 2.9.5
+     */
+    wxString GetBestTranslation(const wxString& domain, wxLanguage msgIdLanguage);
+
+    /**
+        Returns the best UI language for the @a domain.
+
+        The language is determined from the preferred UI language or languages
+        list the user configured in the OS. Notice that this may or may not
+        correspond to the default @em locale as obtained from
+        wxLocale::GetSystemLanguage(); modern operation systems (Windows
+        Vista+, OS X) have separate language and regional (= locale) settings.
+
+        @param domain
+            The catalog domain to look for.
+
+        @param msgIdLanguage
+            Specifies the language of "msgid" strings in source code
+            (i.e. arguments to GetString(), wxGetTranslation() and the _() macro).
+
+        @return Language code if a suitable match was found, empty string
+                otherwise.
+
+        @since 2.9.5
+     */
+    wxString GetBestTranslation(const wxString& domain,
+                                const wxString& msgIdLanguage = "en");
+
     /**
         Add standard wxWidgets catalogs ("wxstd" and possible port-specific
         catalogs).
index c3ee0d73f2235d49f5cd27fc2ce5bab46b25aa30..ce264439601b03b52c4b03ca694715874f95958f 100644 (file)
 #include "wx/hashset.h"
 
 #ifdef __WINDOWS__
+    #include "wx/dynlib.h"
+    #include "wx/scopedarray.h"
     #include "wx/msw/wrapwin.h"
 #endif
+#ifdef __WXOSX__
+    #include "wx/osx/core/cfstring.h"
+    #include <CoreFoundation/CFBundle.h>
+    #include <CoreFoundation/CFLocale.h>
+#endif
 
 // ----------------------------------------------------------------------------
 // simple types
@@ -84,6 +91,148 @@ namespace
 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 i = preferred.begin();
+                      i != preferred.end();
+                      ++i )
+                {
+                    wxString lang(*i);
+                    lang.Replace("-", "_");
+                    if ( available.Index(lang) != wxNOT_FOUND )
+                        return lang;
+                    size_t pos = lang.find('_');
+                    if ( pos != wxString::npos )
+                    {
+                        lang = lang.substr(0, pos);
+                        if ( available.Index(lang) != 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
 
 // ----------------------------------------------------------------------------
@@ -1336,7 +1485,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() )
     {
@@ -1423,18 +1572,30 @@ bool wxTranslations::IsLoaded(const wxString& domain) const
     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;
 
-    // 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;
 }