Merge branch 'multi-fallback-langs'

Closes https://github.com/wxWidgets/wxWidgets/pull/1006
This commit is contained in:
Vadim Zeitlin
2018-11-18 01:45:38 +01:00
3 changed files with 109 additions and 30 deletions

View File

@@ -145,6 +145,12 @@ public:
wxString GetBestTranslation(const wxString& domain, wxString GetBestTranslation(const wxString& domain,
const wxString& msgIdLanguage = "en"); const wxString& msgIdLanguage = "en");
// find best and all other suitable translation languages for given domain
wxArrayString GetAcceptableTranslations(const wxString& domain,
wxLanguage msgIdLanguage);
wxArrayString GetAcceptableTranslations(const wxString& domain,
const wxString& msgIdLanguage = "en");
// add standard wxWidgets catalog ("wxstd") // add standard wxWidgets catalog ("wxstd")
bool AddStdCatalog(); bool AddStdCatalog();

View File

@@ -137,6 +137,34 @@ public:
const wxString& msgIdLanguage = "en"); const wxString& msgIdLanguage = "en");
/** /**
Returns the languages of all translations that can be used for the @a
domain.
This is a more general version of GetBestTranslation(), which returns
the whole list of preferred UI languages for which a translation for
the @a domain was found instead of just the first, i.e. the most
preferred, element of this list.
@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 An array of language codes if any suitable matches were found,
empty array otherwise.
@since 3.1.2
*/
wxArrayString GetAcceptableTranslations(const wxString& domain,
wxLanguage msgIdLanguage);
/// @overload
wxArrayString GetAcceptableTranslations(const wxString& domain,
const wxString& msgIdLanguage = "en");
/**
Add standard wxWidgets catalogs ("wxstd" and possible port-specific Add standard wxWidgets catalogs ("wxstd" and possible port-specific
catalogs). catalogs).
@@ -147,9 +175,10 @@ public:
bool AddStdCatalog(); bool AddStdCatalog();
/** /**
Add a catalog for use with the current locale. Add a catalog for the preferred UI language. In case of multiple
preferences, add catalog for each language, if available.
By default, it is searched for in standard places (see By default, the catalog is searched for in standard places (see
wxFileTranslationsLoader), but you may also prepend additional wxFileTranslationsLoader), but you may also prepend additional
directories to the search path with directories to the search path with
wxFileTranslationsLoader::AddCatalogLookupPathPrefix(). wxFileTranslationsLoader::AddCatalogLookupPathPrefix().
@@ -173,8 +202,9 @@ public:
code are used instead. code are used instead.
@return @return
@true if catalog was successfully loaded, @false otherwise (which might @true if catalog in the most preferred language was successfully loaded,
mean that the catalog is not found or that it isn't in the correct format). @false otherwise (which might mean that the catalog is not found or that
it isn't in the correct format).
*/ */
bool AddCatalog(const wxString& domain, bool AddCatalog(const wxString& domain,
wxLanguage msgIdLanguage = wxLANGUAGE_ENGLISH_US); wxLanguage msgIdLanguage = wxLANGUAGE_ENGLISH_US);
@@ -200,8 +230,9 @@ public:
in case they use 8-bit characters (e.g. German or French strings). in case they use 8-bit characters (e.g. German or French strings).
@return @return
@true if catalog was successfully loaded, @false otherwise (which might @true if catalog in the most preferred language was successfully loaded,
mean that the catalog is not found or that it isn't in the correct format). @false otherwise (which might mean that the catalog is not found or that
it isn't in the correct format).
*/ */
bool AddCatalog(const wxString& domain, bool AddCatalog(const wxString& domain,
wxLanguage msgIdLanguage, wxLanguage msgIdLanguage,

View File

@@ -127,7 +127,7 @@ wxString GetPreferredUILanguageFallback(const wxArrayString& WXUNUSED(available)
#ifdef __WINDOWS__ #ifdef __WINDOWS__
wxString GetPreferredUILanguage(const wxArrayString& available) wxString GetPreferredUILanguage(const wxArrayString& available, wxArrayString& allPreferred)
{ {
typedef BOOL (WINAPI *GetUserPreferredUILanguages_t)(DWORD, PULONG, PWSTR, PULONG); typedef BOOL (WINAPI *GetUserPreferredUILanguages_t)(DWORD, PULONG, PWSTR, PULONG);
static GetUserPreferredUILanguages_t s_pfnGetUserPreferredUILanguages = NULL; static GetUserPreferredUILanguages_t s_pfnGetUserPreferredUILanguages = NULL;
@@ -172,18 +172,20 @@ wxString GetPreferredUILanguage(const wxArrayString& available)
wxString lang(*j); wxString lang(*j);
lang.Replace("-", "_"); lang.Replace("-", "_");
if ( available.Index(lang, /*bCase=*/false) != wxNOT_FOUND ) if ( available.Index(lang, /*bCase=*/false) != wxNOT_FOUND )
return lang; allPreferred.Add(lang);
size_t pos = lang.find('_'); size_t pos = lang.find('_');
if ( pos != wxString::npos ) if ( pos != wxString::npos )
{ {
lang = lang.substr(0, pos); lang = lang.substr(0, pos);
if ( available.Index(lang, /*bCase=*/false) != wxNOT_FOUND ) if ( available.Index(lang, /*bCase=*/false) != wxNOT_FOUND )
return lang; allPreferred.Add(lang);
} }
} }
} }
} }
} }
if ( !allPreferred.empty() )
return allPreferred[0];
return GetPreferredUILanguageFallback(available); return GetPreferredUILanguageFallback(available);
} }
@@ -207,7 +209,7 @@ void LogTraceArray(const char *prefix, CFArrayRef arr)
#endif // wxUSE_LOG_TRACE #endif // wxUSE_LOG_TRACE
wxString GetPreferredUILanguage(const wxArrayString& available) wxString GetPreferredUILanguage(const wxArrayString& available, wxArrayString &allPreferred)
{ {
wxStringToStringHashMap availableNormalized; wxStringToStringHashMap availableNormalized;
wxCFRef<CFMutableArrayRef> availableArr( wxCFRef<CFMutableArrayRef> availableArr(
@@ -231,17 +233,19 @@ wxString GetPreferredUILanguage(const wxArrayString& available)
LogTraceArray(" - system preferred languages", prefArr); LogTraceArray(" - system preferred languages", prefArr);
unsigned prefArrLength = CFArrayGetCount(prefArr); unsigned prefArrLength = CFArrayGetCount(prefArr);
if ( prefArrLength > 0 ) for ( size_t x = 0; x < prefArrLength; ++x )
{ {
// Lookup the name in 'available' by index -- we need to get the // Lookup the name in 'available' by index -- we need to get the
// original value corresponding to the normalized one chosen. // original value corresponding to the normalized one chosen.
wxString lang(wxCFStringRef::AsString((CFStringRef)CFArrayGetValueAtIndex(prefArr, 0))); wxString lang(wxCFStringRef::AsString((CFStringRef)CFArrayGetValueAtIndex(prefArr, x)));
wxStringToStringHashMap::const_iterator i = availableNormalized.find(lang); wxStringToStringHashMap::const_iterator i = availableNormalized.find(lang);
if ( i == availableNormalized.end() ) if ( i == availableNormalized.end() )
return lang; allPreferred.push_back(lang);
else else
return i->second; allPreferred.push_back(i->second);
} }
if ( allPreferred.empty() == false )
return allPreferred[0];
return GetPreferredUILanguageFallback(available); return GetPreferredUILanguageFallback(available);
} }
@@ -255,7 +259,7 @@ wxString GetPreferredUILanguage(const wxArrayString& available)
// The LANGUAGE variable may contain a colon separated list of language // The LANGUAGE variable may contain a colon separated list of language
// codes in the order of preference. // codes in the order of preference.
// http://www.gnu.org/software/gettext/manual/html_node/The-LANGUAGE-variable.html // http://www.gnu.org/software/gettext/manual/html_node/The-LANGUAGE-variable.html
wxString GetPreferredUILanguage(const wxArrayString& available) wxString GetPreferredUILanguage(const wxArrayString& available, wxArrayString &allPreferred)
{ {
wxString languageFromEnv; wxString languageFromEnv;
wxArrayString preferred; wxArrayString preferred;
@@ -283,15 +287,17 @@ wxString GetPreferredUILanguage(const wxArrayString& available)
{ {
wxString lang(*j); wxString lang(*j);
if ( available.Index(lang) != wxNOT_FOUND ) if ( available.Index(lang) != wxNOT_FOUND )
return lang; allPreferred.Add(lang);
size_t pos = lang.find('_'); size_t pos = lang.find('_');
if ( pos != wxString::npos ) if ( pos != wxString::npos )
{ {
lang = lang.substr(0, pos); lang = lang.substr(0, pos);
if ( available.Index(lang) != wxNOT_FOUND ) if ( available.Index(lang) != wxNOT_FOUND )
return lang; allPreferred.Add(lang);
} }
} }
if ( allPreferred.empty() == false )
return allPreferred[0];
return GetPreferredUILanguageFallback(available); return GetPreferredUILanguageFallback(available);
} }
@@ -1549,9 +1555,9 @@ bool wxTranslations::AddCatalog(const wxString& domain,
wxLanguage msgIdLanguage) wxLanguage msgIdLanguage)
{ {
const wxString msgIdLang = wxLocale::GetLanguageCanonicalName(msgIdLanguage); const wxString msgIdLang = wxLocale::GetLanguageCanonicalName(msgIdLanguage);
const wxString domain_lang = GetBestTranslation(domain, msgIdLang); const wxArrayString domain_langs = GetAcceptableTranslations(domain, msgIdLanguage);
if ( domain_lang.empty() ) if ( domain_langs.empty() )
{ {
wxLogTrace(TRACE_I18N, wxLogTrace(TRACE_I18N,
wxS("no suitable translation for domain '%s' found"), wxS("no suitable translation for domain '%s' found"),
@@ -1559,11 +1565,30 @@ bool wxTranslations::AddCatalog(const wxString& domain,
return false; return false;
} }
wxLogTrace(TRACE_I18N, bool success = false;
wxS("adding '%s' translation for domain '%s' (msgid language '%s')"), for ( wxArrayString::const_iterator lang = domain_langs.begin();
domain_lang, domain, msgIdLang); lang != domain_langs.end();
++lang )
{
wxLogTrace(TRACE_I18N,
wxS("adding '%s' translation for domain '%s' (msgid language '%s')"),
*lang, domain, msgIdLang);
return LoadCatalog(domain, domain_lang, msgIdLang); // No use loading languages that are less preferred than the
// msgid language, as by definition it contains all the strings
// in the msgid language.
if ( msgIdLang == *lang )
break;
// We determine success by the success of loading/failing to load
// the most preferred (i.e. the first one) language's catalog:
if ( lang == domain_langs.begin() )
success = LoadCatalog(domain, *lang, msgIdLang);
else
LoadCatalog(domain, *lang, msgIdLang);
}
return success;
} }
@@ -1650,20 +1675,37 @@ wxString wxTranslations::GetBestTranslation(const wxString& domain,
wxString wxTranslations::GetBestTranslation(const wxString& domain, wxString wxTranslations::GetBestTranslation(const wxString& domain,
const wxString& msgIdLanguage) const wxString& msgIdLanguage)
{ {
// explicitly set language should always be respected const wxArrayString allGoodOnes = GetAcceptableTranslations(domain, msgIdLanguage);
if ( !m_lang.empty() )
return m_lang;
wxLogTrace(TRACE_I18N, " => using language '%s'", allGoodOnes[0]);
return allGoodOnes[0];
}
wxArrayString wxTranslations::GetAcceptableTranslations(const wxString& domain,
wxLanguage msgIdLanguage)
{
const wxString lang = wxLocale::GetLanguageCanonicalName(msgIdLanguage);
return GetAcceptableTranslations(domain, lang);
}
wxArrayString wxTranslations::GetAcceptableTranslations(const wxString& domain,
const wxString& msgIdLanguage)
{
wxArrayString available(GetAvailableTranslations(domain)); wxArrayString available(GetAvailableTranslations(domain));
// it's OK to have duplicates, so just add msgid language // it's OK to have duplicates, so just add msgid language
available.push_back(msgIdLanguage); available.push_back(msgIdLanguage);
available.push_back(msgIdLanguage.BeforeFirst('_')); available.push_back(msgIdLanguage.BeforeFirst('_'));
wxLogTrace(TRACE_I18N, "choosing best language for domain '%s'", domain); wxLogTrace(TRACE_I18N, "choosing best languages for domain '%s'", domain);
LogTraceArray(" - available translations", available); LogTraceArray(" - available translations", available);
const wxString lang = GetPreferredUILanguage(available); wxArrayString allPreferred;
wxLogTrace(TRACE_I18N, " => using language '%s'", lang); GetPreferredUILanguage(available, allPreferred);
return lang;
// explicitly set language should always be preferred the most
if ( !m_lang.empty() )
allPreferred.insert(allPreferred.begin(), m_lang);
return allPreferred;
} }