diff --git a/include/wx/translation.h b/include/wx/translation.h index c988b648c4..4bba2766f7 100644 --- a/include/wx/translation.h +++ b/include/wx/translation.h @@ -151,16 +151,24 @@ public: // get languages available for this app wxArrayString GetAvailableTranslations(const wxString& domain) const; - // find best translation language for given domain + // find best available translation language for given domain + wxString GetBestAvailableTranslation(const wxString& domain); + wxString GetBestTranslation(const wxString& domain, wxLanguage msgIdLanguage); wxString GetBestTranslation(const wxString& domain, const wxString& msgIdLanguage = wxASCII_STR("en")); + // add catalog for the given domain returning true if it could be found by + // wxTranslationsLoader + bool AddAvailableCatalog(const wxString& domain); + // add standard wxWidgets catalog ("wxstd") bool AddStdCatalog(); // add catalog with given domain name and language, looking it up via - // wxTranslationsLoader + // wxTranslationsLoader -- unlike AddAvailableCatalog(), this function also + // returns true if this catalog is not needed at all because msgIdLanguage + // is an acceptable language to use directly bool AddCatalog(const wxString& domain, wxLanguage msgIdLanguage = wxLANGUAGE_ENGLISH_US); @@ -186,7 +194,7 @@ public: private: // perform loading of the catalog via m_loader - bool LoadCatalog(const wxString& domain, const wxString& lang, const wxString& msgIdLang); + bool LoadCatalog(const wxString& domain, const wxString& lang); // find catalog by name in a linked list, return nullptr if !found wxMsgCatalog *FindCatalog(const wxString& domain) const; diff --git a/interface/wx/translation.h b/interface/wx/translation.h index 0268f3a4a8..401a421bdd 100644 --- a/interface/wx/translation.h +++ b/interface/wx/translation.h @@ -89,6 +89,18 @@ public: */ wxArrayString GetAvailableTranslations(const wxString& domain) const; + /** + Returns the best available translation for the required language. + + For wxLANGUAGE_DEFAULT, this function returns the available translation + best matching one of wxUILocale::GetPreferredUILanguages(). Otherwise + it simply returns the language set with SetLanguage() if it's available + or empty string otherwise. + + @since 3.3.0 + */ + wxString GetBestAvailableTranslation(const wxString& domain); + /** Returns the best UI language for the @a domain. @@ -98,6 +110,13 @@ public: wxLocale::GetSystemLanguage() as operating systems have separate language and regional (i.e. locale) settings. + Please note that that this function may return the language + corresponding to @a msgIdLanguage if this language is considered to be + acceptable, i.e. is part of wxUILocale::GetPreferredUILanguages(), + indicating that it is fine not to use translations at all on this + system. If this is undesirable, GetBestAvailableTranslation() should be + used which doesn't consider the messages ID language as being available. + @param domain The catalog domain to look for. @@ -124,14 +143,14 @@ public: @return @true if a suitable catalog was found, @false otherwise - @see AddCatalog() + @see AddAvailableCatalog() */ bool AddStdCatalog(); /** Add a catalog for use with the current locale. - 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(). @@ -139,6 +158,28 @@ public: All loaded catalogs will be used for message lookup by GetString() for the current locale. + @return + @true if catalog was successfully loaded, @false otherwise, usually + because it wasn't found. Note that unlike AddCatalog() this + function returns @false even if the language of the original + strings (usually English) can be used directly, i.e. its return + value only indicates that there are no catalogs available for the + selected or system-default languages, but is not necessarily an + error if no translations are needed in the first place. + + @since 3.3.0 + */ + bool AddAvailableCatalog(const wxString& domain); + + /** + Add a catalog for use with the current locale or fall back to the + original messages language. + + This function behaves like AddAvailableCatalog() but also checks if the + strings used in the program, written in @a msgIdLanguage, can be used + without any translations on the current system and also returns @true + in this case, unlike AddAvailableCatalog(). + By default, i.e. if @a msgIdLanguage is not given, @c msgid strings are assumed to be in English and written only using 7-bit ASCII characters. If you have to deal with non-English strings or 8-bit characters in the @@ -155,8 +196,10 @@ 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 was successfully loaded or loading it is + unnecessary because the original messages can be used directly, + @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); @@ -167,7 +210,7 @@ public: According to GNU gettext tradition, each catalog normally corresponds to 'domain' which is more or less the application name. - @see AddCatalog() + @see AddAvailableCatalog() */ bool IsLoaded(const wxString& domain) const; @@ -302,7 +345,7 @@ public: (in this order). This only applies to subsequent invocations of - wxTranslations::AddCatalog(). + wxTranslations::AddAvailableCatalog(). */ static void AddCatalogLookupPathPrefix(const wxString& prefix); }; @@ -312,8 +355,7 @@ public: resources. If you wish to store translation MO files in resources, you have to - enable this loader before calling wxTranslations::AddCatalog() or - wxLocale::AddCatalog(): + enable this loader before calling wxTranslations::AddAvailableCatalog(): @code wxTranslations::Get()->SetLoader(new wxResourceTranslationsLoader); diff --git a/src/common/translation.cpp b/src/common/translation.cpp index eb8e680e28..d731165af0 100644 --- a/src/common/translation.cpp +++ b/src/common/translation.cpp @@ -1316,7 +1316,7 @@ bool wxTranslations::AddStdCatalog() // the name without the version if it's not found, as message catalogs // typically won't have the version in their names under non-Unix platforms // (i.e. where they're not installed by our own "make install"). - if ( AddCatalog("wxstd-" wxSTRINGIZE(wxMAJOR_VERSION) "." wxSTRINGIZE(wxMINOR_VERSION)) ) + if ( AddAvailableCatalog("wxstd-" wxSTRINGIZE(wxMAJOR_VERSION) "." wxSTRINGIZE(wxMINOR_VERSION)) ) return true; if ( AddCatalog(wxS("wxstd")) ) @@ -1325,12 +1325,9 @@ bool wxTranslations::AddStdCatalog() return false; } -bool wxTranslations::AddCatalog(const wxString& domain, - wxLanguage msgIdLanguage) +bool wxTranslations::AddAvailableCatalog(const wxString& domain) { - const wxString msgIdLang = wxUILocale::GetLanguageCanonicalName(msgIdLanguage); - const wxString domain_lang = GetBestTranslation(domain, msgIdLang); - + const wxString domain_lang = GetBestAvailableTranslation(domain); if ( domain_lang.empty() ) { wxLogTrace(TRACE_I18N, @@ -1339,15 +1336,31 @@ 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); + return LoadCatalog(domain, domain_lang); +} - return LoadCatalog(domain, domain_lang, msgIdLang); +bool wxTranslations::AddCatalog(const wxString& domain, + wxLanguage msgIdLanguage) +{ + if ( AddAvailableCatalog(domain) ) + return true; + + const wxString msgIdLang = wxUILocale::GetLanguageCanonicalName(msgIdLanguage); + const wxString domain_lang = GetBestTranslation(domain, msgIdLang); + + if ( msgIdLang == domain_lang ) + { + wxLogTrace(TRACE_I18N, + wxS("not using translations for domain '%s' with msgid language '%s'"), + domain, msgIdLang); + return true; + } + + return false; } -bool wxTranslations::LoadCatalog(const wxString& domain, const wxString& lang, const wxString& msgIdLang) +bool wxTranslations::LoadCatalog(const wxString& domain, const wxString& lang) { wxCHECK_MSG( m_loader, false, "loader can't be null" ); @@ -1384,15 +1397,6 @@ bool wxTranslations::LoadCatalog(const wxString& domain, const wxString& lang, c cat = m_loader->LoadCatalog(domain, baselang); } - if ( !cat ) - { - // It is OK to not load catalog if the msgid language and m_language match, - // in which case we can directly display the texts embedded in program's - // source code: - if ( msgIdLang == lang ) - return true; - } - if ( cat ) { // add it to the head of the list so that in GetString it will @@ -1430,14 +1434,56 @@ 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; + wxString lang = GetBestAvailableTranslation(domain); + if ( lang.empty() ) + { + wxArrayString available; + available.push_back(msgIdLanguage); + available.push_back(msgIdLanguage.BeforeFirst('_')); + lang = GetPreferredUILanguage(available); + if ( lang.empty() ) + { + wxLogTrace(TRACE_I18N, + "no available language for domain '%s'", domain); + } + else + { + wxLogTrace(TRACE_I18N, + "using message ID language '%s' for domain '%s'", lang); + } + } - wxArrayString available(GetAvailableTranslations(domain)); - // it's OK to have duplicates, so just add msgid language - available.push_back(msgIdLanguage); - available.push_back(msgIdLanguage.BeforeFirst('_')); + return lang; +} + +wxString wxTranslations::GetBestAvailableTranslation(const wxString& domain) +{ + const wxArrayString available(GetAvailableTranslations(domain)); + if ( !m_lang.empty() ) + { + wxLogTrace(TRACE_I18N, + "searching for best translation to %s for domain '%s'", + m_lang, domain); + + wxString lang; + if ( available.Index(m_lang) != wxNOT_FOUND ) + { + lang = m_lang; + } + else + { + const wxString baselang = m_lang.BeforeFirst('_'); + if ( baselang != m_lang && available.Index(baselang) != wxNOT_FOUND ) + lang = baselang; + } + + if ( lang.empty() ) + wxLogTrace(TRACE_I18N, " => no available translations found"); + else + wxLogTrace(TRACE_I18N, " => found '%s'", lang); + + return lang; + } wxLogTrace(TRACE_I18N, "choosing best language for domain '%s'", domain); LogTraceArray(" - available translations", available); diff --git a/tests/intl/intltest.cpp b/tests/intl/intltest.cpp index 5a7b78de2e..ca3ceebdf3 100644 --- a/tests/intl/intltest.cpp +++ b/tests/intl/intltest.cpp @@ -239,6 +239,39 @@ void IntlTestCase::IsAvailable() CPPUNIT_ASSERT_EQUAL( origLocale, setlocale(LC_ALL, nullptr) ); } +TEST_CASE("wxTranslations::Available", "[translations]") +{ + // We currently have translations for French and Japanese in this test + // directory, check that loading those succeeds but loading others doesn't. + wxFileTranslationsLoader::AddCatalogLookupPathPrefix("./intl"); + + const wxString domain("internat"); + + wxTranslations trans; + + SECTION("All") + { + auto available = trans.GetAvailableTranslations(domain); + REQUIRE( available.size() == 2 ); + + available.Sort(); + CHECK( available[0] == "fr" ); + CHECK( available[1] == "ja" ); + } + + SECTION("French") + { + trans.SetLanguage(wxLANGUAGE_FRENCH); + CHECK( trans.AddAvailableCatalog(domain) ); + } + + SECTION("Italian") + { + trans.SetLanguage(wxLANGUAGE_ITALIAN); + CHECK_FALSE( trans.AddAvailableCatalog(domain) ); + } +} + TEST_CASE("wxLocale::Default", "[locale]") { const int langDef = wxUILocale::GetSystemLanguage();