From 2be4e716b3e03ed4705c12965ff01633abf59866 Mon Sep 17 00:00:00 2001 From: Sharrnah <55756126+Sharrnah@users.noreply.github.com> Date: Wed, 24 Jul 2024 13:37:41 +0000 Subject: [PATCH 01/13] [FEATURE] Add SetPreferredLanguage to allow setting a preferred locale for app translations. --- lang/lang.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lang/lang.go b/lang/lang.go index 3f0b6b867b..ae3c41b634 100644 --- a/lang/lang.go +++ b/lang/lang.go @@ -35,6 +35,8 @@ var ( // More info available on the `LocalizePluralKey` function. XN = LocalizePluralKey + preferredLanguage *fyne.Locale + bundle *i18n.Bundle localizer *i18n.Localizer @@ -159,6 +161,15 @@ func AddTranslationsFS(fs embed.FS, dir string) (retErr error) { return retErr } +// SetPreferredLanguage allows an app to set the preferred language for translations, overwriting the System Locale. +func SetPreferredLanguage(languageKey string) error { + tag, err := language.Parse(languageKey) + fyneLocale := fyne.Locale(tag.String()) + forcedLanguage = &fyneLocale + updateLocalizer() + return err +} + func addLanguage(data []byte, name string) error { f, err := bundle.ParseMessageFileBytes(data, name) translated = append(translated, f.Tag) @@ -198,6 +209,9 @@ func updateLocalizer() { fyne.LogError("Failed to load user locales", err) all = []string{"en"} } + if preferredLanguage != nil { + all = append([]string{preferredLanguage.String()}, all...) + } str := closestSupportedLocale(all).LanguageString() setupLang(str) localizer = i18n.NewLocalizer(bundle, str) From 54104a26bbe9fb9a17fc7ff51ffe8684d355e6f1 Mon Sep 17 00:00:00 2001 From: Sharrnah <55756126+Sharrnah@users.noreply.github.com> Date: Wed, 24 Jul 2024 13:53:02 +0000 Subject: [PATCH 02/13] Fix varname --- lang/lang.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/lang.go b/lang/lang.go index ae3c41b634..7875b84a80 100644 --- a/lang/lang.go +++ b/lang/lang.go @@ -165,7 +165,7 @@ func AddTranslationsFS(fs embed.FS, dir string) (retErr error) { func SetPreferredLanguage(languageKey string) error { tag, err := language.Parse(languageKey) fyneLocale := fyne.Locale(tag.String()) - forcedLanguage = &fyneLocale + preferredLanguage = &fyneLocale updateLocalizer() return err } From 5b79ca1c6668abad1a06a416c7354c692da6eb1c Mon Sep 17 00:00:00 2001 From: Sharrnah <55756126+Sharrnah@users.noreply.github.com> Date: Wed, 24 Jul 2024 13:57:38 +0000 Subject: [PATCH 03/13] fix codestyle --- lang/lang.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/lang.go b/lang/lang.go index 7875b84a80..e8a50e2fe0 100644 --- a/lang/lang.go +++ b/lang/lang.go @@ -166,7 +166,7 @@ func SetPreferredLanguage(languageKey string) error { tag, err := language.Parse(languageKey) fyneLocale := fyne.Locale(tag.String()) preferredLanguage = &fyneLocale - updateLocalizer() + updateLocalizer() return err } From fd70a2fa71724f2b78ed01b2873ccf1487598132 Mon Sep 17 00:00:00 2001 From: Sharrnah <55756126+Sharrnah@users.noreply.github.com> Date: Wed, 24 Jul 2024 19:22:55 +0000 Subject: [PATCH 04/13] Add test for SetPreferredLanguage --- lang/lang_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lang/lang_test.go b/lang/lang_test.go index 6d090201ec..dc3fcb3a15 100644 --- a/lang/lang_test.go +++ b/lang/lang_test.go @@ -76,3 +76,18 @@ func TestLocalizePluralKey_Fallback(t *testing.T) { assert.Equal(t, "Apple", XN("appleID", "Apple", 1)) assert.Equal(t, "Apples", XN("appleID", "Apple", 2)) } + +func TestSetPreferredLanguage(t *testing.T) { + _ = AddTranslations(fyne.NewStaticResource("en.json", []byte(`{ + "Test": "Match" +}`))) + setupLang("en") + + _ = AddTranslations(fyne.NewStaticResource("fr.json", []byte(`{ + "Test2": "Match2" +}`))) + setupLang("fr") + assert.Equal(t, "Match2", L("Test2")) + SetPreferredLanguage("en") + assert.Equal(t, "Match", L("Test")) +} From b9c194ec7597178c2f176edf8cffc25368b42b43 Mon Sep 17 00:00:00 2001 From: Sharrnah <55756126+Sharrnah@users.noreply.github.com> Date: Wed, 24 Jul 2024 19:28:16 +0000 Subject: [PATCH 05/13] Fix codestyle --- lang/lang_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lang/lang_test.go b/lang/lang_test.go index dc3fcb3a15..f12b86c1a9 100644 --- a/lang/lang_test.go +++ b/lang/lang_test.go @@ -86,8 +86,8 @@ func TestSetPreferredLanguage(t *testing.T) { _ = AddTranslations(fyne.NewStaticResource("fr.json", []byte(`{ "Test2": "Match2" }`))) - setupLang("fr") + setupLang("fr") assert.Equal(t, "Match2", L("Test2")) - SetPreferredLanguage("en") + SetPreferredLanguage("en") assert.Equal(t, "Match", L("Test")) } From 64dd0b086f412a330203dbb9d5e26339e141ba79 Mon Sep 17 00:00:00 2001 From: Sharrnah <55756126+Sharrnah@users.noreply.github.com> Date: Wed, 24 Jul 2024 19:35:05 +0000 Subject: [PATCH 06/13] remove obsolete setupLang call in SetPreferredLanguage test --- lang/lang_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/lang/lang_test.go b/lang/lang_test.go index f12b86c1a9..29deec1a7a 100644 --- a/lang/lang_test.go +++ b/lang/lang_test.go @@ -81,8 +81,6 @@ func TestSetPreferredLanguage(t *testing.T) { _ = AddTranslations(fyne.NewStaticResource("en.json", []byte(`{ "Test": "Match" }`))) - setupLang("en") - _ = AddTranslations(fyne.NewStaticResource("fr.json", []byte(`{ "Test2": "Match2" }`))) From c19cf640629424206c15d70b23dc5a387f8946c4 Mon Sep 17 00:00:00 2001 From: Sharrnah <55756126+Sharrnah@users.noreply.github.com> Date: Wed, 31 Jul 2024 11:57:08 +0000 Subject: [PATCH 07/13] Add SetLanguageOrder function and simplify SetPreferredLocale --- lang/lang.go | 61 ++++++++++++++++++++++++++++++++++++++++------- lang/lang_test.go | 20 ++++++++++++++-- 2 files changed, 70 insertions(+), 11 deletions(-) diff --git a/lang/lang.go b/lang/lang.go index e8a50e2fe0..b421312302 100644 --- a/lang/lang.go +++ b/lang/lang.go @@ -16,6 +16,7 @@ import ( "fyne.io/fyne/v2" "golang.org/x/text/language" + "slices" ) var ( @@ -35,7 +36,11 @@ var ( // More info available on the `LocalizePluralKey` function. XN = LocalizePluralKey - preferredLanguage *fyne.Locale + // This defines an order in which it will try to find a fallback in case localizer does not find a match. + // All other languages will be in order as the system reads them (which is most likely alphabetical). + languageOrder = []string{"en"} + + preferredLanguage string bundle *i18n.Bundle localizer *i18n.Localizer @@ -161,13 +166,20 @@ func AddTranslationsFS(fs embed.FS, dir string) (retErr error) { return retErr } -// SetPreferredLanguage allows an app to set the preferred language for translations, overwriting the System Locale. -func SetPreferredLanguage(languageKey string) error { - tag, err := language.Parse(languageKey) - fyneLocale := fyne.Locale(tag.String()) - preferredLanguage = &fyneLocale +// SetLanguageOrder allows an app to set the order in which translations are checked in case no locale matches. +// Since 2.6 +func SetLanguageOrder(order []string) { + languageOrder = order + updateLocalizer() +} + + +// SetPreferredLocale allows an app to set the preferred locale for translations, overwriting the System Locale. +// locale can be in format en_US_someVariant, en_US, en-US-someVariant, en-US, en +// Since 2.6 +func SetPreferredLocale(locale string) { + preferredLanguage = locale updateLocalizer() - return err } func addLanguage(data []byte, name string) error { @@ -197,6 +209,32 @@ func fallbackWithData(key, fallback string, data any) string { return str.String() } +func orderLanguages(a, b language.Tag) int { + indexA := -1 + indexB := -1 + for i, l := range languageOrder { + if a.String() == l { + indexA = i + } + if b.String() == l { + indexB = i + } + } + // Order both languages as defined in languageOrder + if indexA != -1 && indexB != -1 { + return indexA - indexB + } + // If it is the only language in languageOrder, it comes first + if indexA != -1 { + return -1 + } + if indexB != -1 { + return 1 + } + // If no language is in languageOrder, sort alphabetically + return strings.Compare(a.String(), b.String()) +} + // A utility for setting up languages - available to unit tests for overriding system func setupLang(lang string) { localizer = i18n.NewLocalizer(bundle, lang) @@ -204,13 +242,18 @@ func setupLang(lang string) { // updateLocalizer Finds the closest translation from the user's locale list and sets it up func updateLocalizer() { + // Sort the translated slice using the orderLanguages function + slices.SortStableFunc(translated, func(a, b language.Tag) int { + return orderLanguages(a, b) + }) + all, err := locale.GetLocales() if err != nil { fyne.LogError("Failed to load user locales", err) all = []string{"en"} } - if preferredLanguage != nil { - all = append([]string{preferredLanguage.String()}, all...) + if preferredLanguage != "" { + all = []string{preferredLanguage} } str := closestSupportedLocale(all).LanguageString() setupLang(str) diff --git a/lang/lang_test.go b/lang/lang_test.go index 29deec1a7a..6faea71bd7 100644 --- a/lang/lang_test.go +++ b/lang/lang_test.go @@ -77,7 +77,7 @@ func TestLocalizePluralKey_Fallback(t *testing.T) { assert.Equal(t, "Apples", XN("appleID", "Apple", 2)) } -func TestSetPreferredLanguage(t *testing.T) { +func TestSetPreferredLocale(t *testing.T) { _ = AddTranslations(fyne.NewStaticResource("en.json", []byte(`{ "Test": "Match" }`))) @@ -86,6 +86,22 @@ func TestSetPreferredLanguage(t *testing.T) { }`))) setupLang("fr") assert.Equal(t, "Match2", L("Test2")) - SetPreferredLanguage("en") + SetPreferredLocale("en") + assert.Equal(t, "Match", L("Test")) +} + +func TestSetLanguageOrder(t *testing.T) { + _ = AddTranslations(fyne.NewStaticResource("en.json", []byte(`{ + "Test": "Match" +}`))) + _ = AddTranslations(fyne.NewStaticResource("fr.json", []byte(`{ + "Test2": "Match2" +}`))) + setupLang("xyz") // invalid language to test fallback + SetLanguageOrder("en") + assert.Equal(t, "Match", L("Test")) + SetLanguageOrder("fr") + assert.Equal(t, "Match2", L("Test2")) + setupLang("en") assert.Equal(t, "Match", L("Test")) } From f4f3ebedb155876b0d7eccdeaa20485c0b73c3ec Mon Sep 17 00:00:00 2001 From: Sharrnah <55756126+Sharrnah@users.noreply.github.com> Date: Wed, 31 Jul 2024 12:15:15 +0000 Subject: [PATCH 08/13] use older go sdk compatible sort function and fix test --- lang/lang.go | 16 ++++++++-------- lang/lang_test.go | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lang/lang.go b/lang/lang.go index b421312302..aea369f335 100644 --- a/lang/lang.go +++ b/lang/lang.go @@ -7,6 +7,7 @@ import ( "embed" "encoding/json" "log" + "sort" "strings" "text/template" @@ -16,7 +17,6 @@ import ( "fyne.io/fyne/v2" "golang.org/x/text/language" - "slices" ) var ( @@ -209,7 +209,7 @@ func fallbackWithData(key, fallback string, data any) string { return str.String() } -func orderLanguages(a, b language.Tag) int { +func orderLanguages(a, b language.Tag) bool { indexA := -1 indexB := -1 for i, l := range languageOrder { @@ -222,17 +222,17 @@ func orderLanguages(a, b language.Tag) int { } // Order both languages as defined in languageOrder if indexA != -1 && indexB != -1 { - return indexA - indexB + return indexA < indexB } // If it is the only language in languageOrder, it comes first if indexA != -1 { - return -1 + return true } if indexB != -1 { - return 1 + return false } // If no language is in languageOrder, sort alphabetically - return strings.Compare(a.String(), b.String()) + return strings.Compare(a.String(), b.String()) < 0 } // A utility for setting up languages - available to unit tests for overriding system @@ -243,8 +243,8 @@ func setupLang(lang string) { // updateLocalizer Finds the closest translation from the user's locale list and sets it up func updateLocalizer() { // Sort the translated slice using the orderLanguages function - slices.SortStableFunc(translated, func(a, b language.Tag) int { - return orderLanguages(a, b) + sort.SliceStable(translated, func(i, j int) bool { + return orderLanguages(translated[i], translated[j]) }) all, err := locale.GetLocales() diff --git a/lang/lang_test.go b/lang/lang_test.go index 6faea71bd7..aa9883251e 100644 --- a/lang/lang_test.go +++ b/lang/lang_test.go @@ -98,9 +98,9 @@ func TestSetLanguageOrder(t *testing.T) { "Test2": "Match2" }`))) setupLang("xyz") // invalid language to test fallback - SetLanguageOrder("en") + SetLanguageOrder([]string{"en"}) assert.Equal(t, "Match", L("Test")) - SetLanguageOrder("fr") + SetLanguageOrder([]string{"fr"}) assert.Equal(t, "Match2", L("Test2")) setupLang("en") assert.Equal(t, "Match", L("Test")) From 03af947dbe35a03e3631bda38f9829b4d8e51e63 Mon Sep 17 00:00:00 2001 From: Sharrnah <55756126+Sharrnah@users.noreply.github.com> Date: Wed, 31 Jul 2024 12:42:41 +0000 Subject: [PATCH 09/13] fix test --- lang/lang_test.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lang/lang_test.go b/lang/lang_test.go index aa9883251e..50f6340ebe 100644 --- a/lang/lang_test.go +++ b/lang/lang_test.go @@ -97,11 +97,10 @@ func TestSetLanguageOrder(t *testing.T) { _ = AddTranslations(fyne.NewStaticResource("fr.json", []byte(`{ "Test2": "Match2" }`))) - setupLang("xyz") // invalid language to test fallback - SetLanguageOrder([]string{"en"}) - assert.Equal(t, "Match", L("Test")) - SetLanguageOrder([]string{"fr"}) - assert.Equal(t, "Match2", L("Test2")) setupLang("en") + SetPreferredLocale("xyz") // invalid language to test fallback + SetLanguageOrder([]string{"fr","en"}) + assert.Equal(t, "Match2", L("Test2")) + SetLanguageOrder([]string{"en", "fr"}) assert.Equal(t, "Match", L("Test")) } From 5a35d1a0bd918a6f4e2fe6c499686e64eff349a8 Mon Sep 17 00:00:00 2001 From: Sharrnah <55756126+Sharrnah@users.noreply.github.com> Date: Wed, 31 Jul 2024 12:53:16 +0000 Subject: [PATCH 10/13] Fix codestyle --- lang/lang.go | 1 - lang/lang_test.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lang/lang.go b/lang/lang.go index aea369f335..5091c50d0d 100644 --- a/lang/lang.go +++ b/lang/lang.go @@ -173,7 +173,6 @@ func SetLanguageOrder(order []string) { updateLocalizer() } - // SetPreferredLocale allows an app to set the preferred locale for translations, overwriting the System Locale. // locale can be in format en_US_someVariant, en_US, en-US-someVariant, en-US, en // Since 2.6 diff --git a/lang/lang_test.go b/lang/lang_test.go index 50f6340ebe..5e4f4fbcae 100644 --- a/lang/lang_test.go +++ b/lang/lang_test.go @@ -99,7 +99,7 @@ func TestSetLanguageOrder(t *testing.T) { }`))) setupLang("en") SetPreferredLocale("xyz") // invalid language to test fallback - SetLanguageOrder([]string{"fr","en"}) + SetLanguageOrder([]string{"fr", "en"}) assert.Equal(t, "Match2", L("Test2")) SetLanguageOrder([]string{"en", "fr"}) assert.Equal(t, "Match", L("Test")) From a28b575ddb102b8ec9f89994d507d0558013865c Mon Sep 17 00:00:00 2001 From: Sharrnah <55756126+Sharrnah@users.noreply.github.com> Date: Wed, 31 Jul 2024 13:38:13 +0000 Subject: [PATCH 11/13] fix comment --- lang/lang.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/lang.go b/lang/lang.go index 5091c50d0d..fe0a7453a6 100644 --- a/lang/lang.go +++ b/lang/lang.go @@ -37,7 +37,7 @@ var ( XN = LocalizePluralKey // This defines an order in which it will try to find a fallback in case localizer does not find a match. - // All other languages will be in order as the system reads them (which is most likely alphabetical). + // All other languages will be in alphabetical order. languageOrder = []string{"en"} preferredLanguage string From 49d6134bb4fae87f384810120e1e32527bb554bf Mon Sep 17 00:00:00 2001 From: Sharrnah <55756126+Sharrnah@users.noreply.github.com> Date: Sun, 15 Sep 2024 14:41:21 +0000 Subject: [PATCH 12/13] [TASK] Remove obsolete translated default --- lang/lang.go | 1 - lang/lang_test.go | 5 ----- 2 files changed, 6 deletions(-) diff --git a/lang/lang.go b/lang/lang.go index 0c0e2b2b72..fe0a7453a6 100644 --- a/lang/lang.go +++ b/lang/lang.go @@ -191,7 +191,6 @@ func init() { bundle = i18n.NewBundle(language.English) bundle.RegisterUnmarshalFunc("json", json.Unmarshal) - translated = []language.Tag{language.Make("en")} // the first item in this list will be the fallback if none match err := AddTranslationsFS(translations, "translations") if err != nil { fyne.LogError("Error occurred loading built-in translations", err) diff --git a/lang/lang_test.go b/lang/lang_test.go index 1ed96135a4..5e4f4fbcae 100644 --- a/lang/lang_test.go +++ b/lang/lang_test.go @@ -23,11 +23,6 @@ func TestAddTranslations(t *testing.T) { assert.Equal(t, "Match2", L("Test2")) } -func TestLocalize_Default(t *testing.T) { - fallback := closestSupportedLocale([]string{"xx"}) - assert.Equal(t, fyne.Locale("en"), fallback[0:2]) -} - func TestLocalize_Fallback(t *testing.T) { assert.Equal(t, "Missing", L("Missing")) } From 4a875d9d5677ce8a98bd15787d6f74d4263cac16 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Sun, 15 Dec 2024 10:13:05 +0000 Subject: [PATCH 13/13] Also update password revealer icon --- widget/entry_password.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widget/entry_password.go b/widget/entry_password.go index ff8f75d4c3..45f989271e 100644 --- a/widget/entry_password.go +++ b/widget/entry_password.go @@ -83,5 +83,5 @@ func (r *passwordRevealerRenderer) Refresh() { if r.entry.disabled.Load() { r.icon.Resource = theme.NewDisabledResource(r.icon.Resource) } - canvas.Refresh(r.icon) + r.icon.Refresh() }