Skip to content

Commit

Permalink
Add AddAvailableCatalog() and use it in AddStdCatalog()
Browse files Browse the repository at this point in the history
The new function only returns true if the catalog could be really
loaded and not if it is considered not to be needed because the message
ID language (which is typically "en-US") happens to be present in the
preferred UI languages list (which seems to always include "en-US" in at
least Western European MSW).

This allows to distinguish, albeit in a rather awkward (but
backwards-compatible) way between having a translation for the given
language and not needed such translation.

It is still not clear if it is really correct to return "en-US" from the
list of preferred languages even if the user has never intentionally
configured the OS to indicate that English is acceptable, but at least
now we can work around this issue and use AddAvailableCatalog() in
AddStdCatalog() to make sure we only skip loading unversioned wxstd.mo
if the versioned wxstd-x.y.mo file is really found instead of never
doing it, as was the case until now (see #23886).

Also add GetBestAvailableTranslation() helper which seems more useful
than the existing GetBestTranslation() one and is similarly related to
it.

See #18227.
  • Loading branch information
vadz committed Oct 2, 2023
1 parent fd29d33 commit 94b1a17
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 37 deletions.
14 changes: 11 additions & 3 deletions include/wx/translation.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,16 +151,24 @@ class WXDLLIMPEXP_BASE wxTranslations
// 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);

Expand All @@ -186,7 +194,7 @@ class WXDLLIMPEXP_BASE wxTranslations

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;
Expand Down
58 changes: 50 additions & 8 deletions interface/wx/translation.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,18 @@ class wxTranslations
*/
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.
Expand All @@ -98,6 +110,13 @@ class wxTranslations
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.
Expand All @@ -124,21 +143,43 @@ class wxTranslations
@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().
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
Expand All @@ -155,8 +196,10 @@ class wxTranslations
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);
Expand All @@ -167,7 +210,7 @@ class wxTranslations
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;

Expand Down Expand Up @@ -302,7 +345,7 @@ class wxFileTranslationsLoader : public wxTranslationsLoader
(in this order).
This only applies to subsequent invocations of
wxTranslations::AddCatalog().
wxTranslations::AddAvailableCatalog().
*/
static void AddCatalogLookupPathPrefix(const wxString& prefix);
};
Expand All @@ -312,8 +355,7 @@ class wxFileTranslationsLoader : public wxTranslationsLoader
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);
Expand Down
98 changes: 72 additions & 26 deletions src/common/translation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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")) )
Expand All @@ -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,
Expand All @@ -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" );

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
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);
}
}

return lang;
}

wxString wxTranslations::GetBestAvailableTranslation(const wxString& domain)
{
const wxArrayString available(GetAvailableTranslations(domain));
if ( !m_lang.empty() )
return m_lang;
{
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;
}

wxArrayString available(GetAvailableTranslations(domain));
// it's OK to have duplicates, so just add msgid language
available.push_back(msgIdLanguage);
available.push_back(msgIdLanguage.BeforeFirst('_'));
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);
Expand Down
33 changes: 33 additions & 0 deletions tests/intl/intltest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit 94b1a17

Please sign in to comment.