From 5d08e404c76c1745e070d9c96055b90ffddd7177 Mon Sep 17 00:00:00 2001 From: Lauri Nurmi Date: Tue, 2 Oct 2018 19:49:50 +0300 Subject: [PATCH 1/3] Allow getting all usable translations languages Add a method to return the full list of translations that can be used, generalizing the existing GetBestTranslation(). --- include/wx/translation.h | 5 ++++ interface/wx/translation.h | 43 +++++++++++++++++++++++++++ src/common/translation.cpp | 59 ++++++++++++++++++++++++++------------ 3 files changed, 89 insertions(+), 18 deletions(-) diff --git a/include/wx/translation.h b/include/wx/translation.h index 3d5d5b0b83..266166019c 100644 --- a/include/wx/translation.h +++ b/include/wx/translation.h @@ -145,6 +145,11 @@ public: wxString GetBestTranslation(const wxString& domain, const wxString& msgIdLanguage = "en"); + // find best and all other suitable translation languages for given domain + wxArrayString GetAllGoodTranslations(const wxString& domain, wxLanguage msgIdLanguage); + wxArrayString GetAllGoodTranslations(const wxString& domain, + const wxString& msgIdLanguage = "en"); + // add standard wxWidgets catalog ("wxstd") bool AddStdCatalog(); diff --git a/interface/wx/translation.h b/interface/wx/translation.h index eed6355d0b..a57c9930d0 100644 --- a/interface/wx/translation.h +++ b/interface/wx/translation.h @@ -137,6 +137,49 @@ public: const wxString& msgIdLanguage = "en"); /** + Returns the best and all other suitable UI languages for the @a domain. + + This is nearly the same as GetBestTranslation(), but returns the + whole list of preferred UI languages for which a translation for the + @a domain was found. + + @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 GetAllGoodTranslations(const wxString& domain, wxLanguage msgIdLanguage); + + /** + Returns the best and all other suitable UI languages for the @a domain. + + This is nearly the same as GetBestTranslation(), but returns the + whole list of preferred UI languages for which a translation for the + @a domain was found. + + @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 GetAllGoodTranslations(const wxString& domain, + const wxString& msgIdLanguage = "en"); + + /** Add standard wxWidgets catalogs ("wxstd" and possible port-specific catalogs). diff --git a/src/common/translation.cpp b/src/common/translation.cpp index bf92ed2942..a64327eaef 100644 --- a/src/common/translation.cpp +++ b/src/common/translation.cpp @@ -127,7 +127,7 @@ wxString GetPreferredUILanguageFallback(const wxArrayString& WXUNUSED(available) #ifdef __WINDOWS__ -wxString GetPreferredUILanguage(const wxArrayString& available) +wxString GetPreferredUILanguage(const wxArrayString& available, wxArrayString& allPreferred) { typedef BOOL (WINAPI *GetUserPreferredUILanguages_t)(DWORD, PULONG, PWSTR, PULONG); static GetUserPreferredUILanguages_t s_pfnGetUserPreferredUILanguages = NULL; @@ -172,18 +172,20 @@ wxString GetPreferredUILanguage(const wxArrayString& available) wxString lang(*j); lang.Replace("-", "_"); if ( available.Index(lang, /*bCase=*/false) != wxNOT_FOUND ) - return lang; + allPreferred.Add(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; + allPreferred.Add(lang); } } } } } + if ( !allPreferred.empty() ) + return allPreferred[0]; return GetPreferredUILanguageFallback(available); } @@ -207,7 +209,7 @@ void LogTraceArray(const char *prefix, CFArrayRef arr) #endif // wxUSE_LOG_TRACE -wxString GetPreferredUILanguage(const wxArrayString& available) +wxString GetPreferredUILanguage(const wxArrayString& available, wxArrayString &allPreferred) { wxStringToStringHashMap availableNormalized; wxCFRef availableArr( @@ -231,17 +233,19 @@ wxString GetPreferredUILanguage(const wxArrayString& available) LogTraceArray(" - system preferred languages", 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 // 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); if ( i == availableNormalized.end() ) - return lang; + allPreferred.push_back(lang); else - return i->second; + allPreferred.push_back(i->second); } + if ( allPreferred.empty() == false ) + return allPreferred[0]; return GetPreferredUILanguageFallback(available); } @@ -255,7 +259,7 @@ wxString GetPreferredUILanguage(const wxArrayString& available) // The LANGUAGE variable may contain a colon separated list of language // codes in the order of preference. // 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; wxArrayString preferred; @@ -283,15 +287,17 @@ wxString GetPreferredUILanguage(const wxArrayString& available) { wxString lang(*j); if ( available.Index(lang) != wxNOT_FOUND ) - return lang; + allPreferred.Add(lang); size_t pos = lang.find('_'); if ( pos != wxString::npos ) { lang = lang.substr(0, pos); if ( available.Index(lang) != wxNOT_FOUND ) - return lang; + allPreferred.Add(lang); } } + if ( allPreferred.empty() == false ) + return allPreferred[0]; return GetPreferredUILanguageFallback(available); } @@ -1650,20 +1656,37 @@ wxString wxTranslations::GetBestTranslation(const wxString& domain, wxString wxTranslations::GetBestTranslation(const wxString& domain, const wxString& msgIdLanguage) { - // explicitly set language should always be respected - if ( !m_lang.empty() ) - return m_lang; + const wxArrayString allGoodOnes = GetAllGoodTranslations(domain, msgIdLanguage); + wxLogTrace(TRACE_I18N, " => using language '%s'", allGoodOnes[0]); + return allGoodOnes[0]; +} + +wxArrayString wxTranslations::GetAllGoodTranslations(const wxString& domain, + wxLanguage msgIdLanguage) +{ + const wxString lang = wxLocale::GetLanguageCanonicalName(msgIdLanguage); + return GetAllGoodTranslations(domain, lang); +} + +wxArrayString wxTranslations::GetAllGoodTranslations(const wxString& domain, + const wxString& msgIdLanguage) +{ 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); + wxLogTrace(TRACE_I18N, "choosing best languages for domain '%s'", domain); LogTraceArray(" - available translations", available); - const wxString lang = GetPreferredUILanguage(available); - wxLogTrace(TRACE_I18N, " => using language '%s'", lang); - return lang; + wxArrayString allPreferred; + GetPreferredUILanguage(available, allPreferred); + + // explicitly set language should always be preferred the most + if ( !m_lang.empty() ) + allPreferred.insert(allPreferred.begin(), m_lang); + + return allPreferred; } From 2d784da2ee12cc5a6d89b011827cff6361a12c23 Mon Sep 17 00:00:00 2001 From: Lauri Nurmi Date: Tue, 2 Oct 2018 19:49:50 +0300 Subject: [PATCH 2/3] Load catalogs for all preferred languages, if they exist This way the first and only fallback language isn't necessarily the msgid language (which is English most often). This is how GNU gettext works -- it uses multiple fallback languages when multiple preferred languages are set. As a side effect, fixes #18227 in one possible way. --- interface/wx/translation.h | 15 +++++++++------ src/common/translation.cpp | 31 +++++++++++++++++++++++++------ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/interface/wx/translation.h b/interface/wx/translation.h index a57c9930d0..fd39fd6b18 100644 --- a/interface/wx/translation.h +++ b/interface/wx/translation.h @@ -190,9 +190,10 @@ public: 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 directories to the search path with wxFileTranslationsLoader::AddCatalogLookupPathPrefix(). @@ -216,8 +217,9 @@ public: code are used instead. @return - @true if catalog was successfully loaded, @false otherwise (which might - mean that the catalog is not found or that it isn't in the correct format). + @true if catalog in the most preferred language was successfully loaded, + @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, wxLanguage msgIdLanguage = wxLANGUAGE_ENGLISH_US); @@ -243,8 +245,9 @@ public: in case they use 8-bit characters (e.g. German or French strings). @return - @true if catalog was successfully loaded, @false otherwise (which might - mean that the catalog is not found or that it isn't in the correct format). + @true if catalog in the most preferred language was successfully loaded, + @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, wxLanguage msgIdLanguage, diff --git a/src/common/translation.cpp b/src/common/translation.cpp index a64327eaef..377dc533f1 100644 --- a/src/common/translation.cpp +++ b/src/common/translation.cpp @@ -1555,9 +1555,9 @@ bool wxTranslations::AddCatalog(const wxString& domain, wxLanguage msgIdLanguage) { const wxString msgIdLang = wxLocale::GetLanguageCanonicalName(msgIdLanguage); - const wxString domain_lang = GetBestTranslation(domain, msgIdLang); + const wxArrayString domain_langs = GetAllGoodTranslations(domain, msgIdLanguage); - if ( domain_lang.empty() ) + if ( domain_langs.empty() ) { wxLogTrace(TRACE_I18N, wxS("no suitable translation for domain '%s' found"), @@ -1565,11 +1565,30 @@ bool wxTranslations::AddCatalog(const wxString& domain, return false; } - wxLogTrace(TRACE_I18N, - wxS("adding '%s' translation for domain '%s' (msgid language '%s')"), - domain_lang, domain, msgIdLang); + bool success = false; + for ( wxArrayString::const_iterator lang = domain_langs.begin(); + 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; } From 20b02d6169fed3ae68caa6a12aa1003a205672f2 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 18 Nov 2018 01:43:11 +0100 Subject: [PATCH 3/3] Rename new wxTranslations method to GetAcceptableTranslations() This name seems to be more precise than a very generic "all good" one used previously. --- include/wx/translation.h | 7 +++--- interface/wx/translation.h | 45 +++++++++++++------------------------- src/common/translation.cpp | 14 ++++++------ 3 files changed, 26 insertions(+), 40 deletions(-) diff --git a/include/wx/translation.h b/include/wx/translation.h index 266166019c..9b931a088a 100644 --- a/include/wx/translation.h +++ b/include/wx/translation.h @@ -146,9 +146,10 @@ public: const wxString& msgIdLanguage = "en"); // find best and all other suitable translation languages for given domain - wxArrayString GetAllGoodTranslations(const wxString& domain, wxLanguage msgIdLanguage); - wxArrayString GetAllGoodTranslations(const wxString& domain, - const wxString& msgIdLanguage = "en"); + wxArrayString GetAcceptableTranslations(const wxString& domain, + wxLanguage msgIdLanguage); + wxArrayString GetAcceptableTranslations(const wxString& domain, + const wxString& msgIdLanguage = "en"); // add standard wxWidgets catalog ("wxstd") bool AddStdCatalog(); diff --git a/interface/wx/translation.h b/interface/wx/translation.h index fd39fd6b18..23ca5128c7 100644 --- a/interface/wx/translation.h +++ b/interface/wx/translation.h @@ -137,47 +137,32 @@ public: const wxString& msgIdLanguage = "en"); /** - Returns the best and all other suitable UI languages for the @a domain. + Returns the languages of all translations that can be used for the @a + domain. - This is nearly the same as GetBestTranslation(), but returns the - whole list of preferred UI languages for which a translation for the - @a domain was found. + 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). + 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. + @return An array of language codes if any suitable matches were found, + empty array otherwise. @since 3.1.2 */ - wxArrayString GetAllGoodTranslations(const wxString& domain, wxLanguage msgIdLanguage); + wxArrayString GetAcceptableTranslations(const wxString& domain, + wxLanguage msgIdLanguage); - /** - Returns the best and all other suitable UI languages for the @a domain. - - This is nearly the same as GetBestTranslation(), but returns the - whole list of preferred UI languages for which a translation for the - @a domain was found. - - @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 GetAllGoodTranslations(const wxString& domain, - const wxString& msgIdLanguage = "en"); + /// @overload + wxArrayString GetAcceptableTranslations(const wxString& domain, + const wxString& msgIdLanguage = "en"); /** Add standard wxWidgets catalogs ("wxstd" and possible port-specific diff --git a/src/common/translation.cpp b/src/common/translation.cpp index 377dc533f1..7d9937cd18 100644 --- a/src/common/translation.cpp +++ b/src/common/translation.cpp @@ -1555,7 +1555,7 @@ bool wxTranslations::AddCatalog(const wxString& domain, wxLanguage msgIdLanguage) { const wxString msgIdLang = wxLocale::GetLanguageCanonicalName(msgIdLanguage); - const wxArrayString domain_langs = GetAllGoodTranslations(domain, msgIdLanguage); + const wxArrayString domain_langs = GetAcceptableTranslations(domain, msgIdLanguage); if ( domain_langs.empty() ) { @@ -1675,21 +1675,21 @@ wxString wxTranslations::GetBestTranslation(const wxString& domain, wxString wxTranslations::GetBestTranslation(const wxString& domain, const wxString& msgIdLanguage) { - const wxArrayString allGoodOnes = GetAllGoodTranslations(domain, msgIdLanguage); + const wxArrayString allGoodOnes = GetAcceptableTranslations(domain, msgIdLanguage); wxLogTrace(TRACE_I18N, " => using language '%s'", allGoodOnes[0]); return allGoodOnes[0]; } -wxArrayString wxTranslations::GetAllGoodTranslations(const wxString& domain, - wxLanguage msgIdLanguage) +wxArrayString wxTranslations::GetAcceptableTranslations(const wxString& domain, + wxLanguage msgIdLanguage) { const wxString lang = wxLocale::GetLanguageCanonicalName(msgIdLanguage); - return GetAllGoodTranslations(domain, lang); + return GetAcceptableTranslations(domain, lang); } -wxArrayString wxTranslations::GetAllGoodTranslations(const wxString& domain, - const wxString& msgIdLanguage) +wxArrayString wxTranslations::GetAcceptableTranslations(const wxString& domain, + const wxString& msgIdLanguage) { wxArrayString available(GetAvailableTranslations(domain)); // it's OK to have duplicates, so just add msgid language