Add wxTranslations::GetBestTranslation().

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
This commit is contained in:
Václav Slavík
2012-09-08 08:58:38 +00:00
parent 8566fe6edf
commit 01f953efb2
4 changed files with 231 additions and 13 deletions

View 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;

View 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();

View 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).

View File

@@ -51,8 +51,15 @@
#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;
}