Skip to content

fix(i18n): register Chinese translations under correct Fyne bundle tag - #965

Open
tofuliang wants to merge 4 commits into
supersonic-app:mainfrom
tofuliang:fix/i18n-chinese-translations
Open

fix(i18n): register Chinese translations under correct Fyne bundle tag#965
tofuliang wants to merge 4 commits into
supersonic-app:mainfrom
tofuliang:fix/i18n-chinese-translations

Conversation

@tofuliang

@tofuliang tofuliang commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Description

Preserve Fyne translations when an explicitly selected language does not match the OS-inferred system locale.

Problem

When the OS locale implies one Chinese script but the user explicitly selects the other in Settings, or when any language is selected on a system where Fyne's active locale uses a different preference order, application UI strings would either fall back to English or clobber Fyne's built-in shared translations.

Fix

  • Load translations from the complete Fyne locale preference list (go-locale.GetLocales()), matching the same source Fyne uses for its active bundle.
  • For each preference, register the configured translation under the canonical locale, region, base language, and language-script tags.
  • Replace identity placeholders with the pre-registration Fyne value, or drop keys where Fyne has no translation, protecting shared UI strings.
  • Drop the previous Chinese-specific script switch and manual Zzzz script inference; the solution now generalizes to all languages.
  • Validate all translation filenames as canonical BCP47 locales.

Testing

  • go test ./... passes: 45 tests across 27 packages.
  • macOS packaging succeeds.
  • Unit tests cover the locale plan (Chinese scripts, Portuguese, multiple preferences, explicit non-default scripts, invalid preferences), alias payload filtering/preservation, and the zh-CN/zh-Hant override case.

@tofuliang tofuliang closed this Jun 26, 2026
@tofuliang
tofuliang deleted the fix/i18n-chinese-translations branch June 26, 2026 02:52
@tofuliang
tofuliang restored the fix/i18n-chinese-translations branch June 26, 2026 02:53
@tofuliang tofuliang reopened this Jun 26, 2026
Comment thread main.go Outdated
@tofuliang
tofuliang force-pushed the fix/i18n-chinese-translations branch 4 times, most recently from 8b5b27e to 0cc354b Compare June 29, 2026 10:28
@tofuliang
tofuliang requested a review from dweymouth July 14, 2026 10:16
Comment thread main.go Outdated
@tofuliang

Copy link
Copy Markdown
Contributor Author

Thanks for the feedback. I have pushed a simplified revision:

  • Renamed the translation files to canonical BCP47 names: zh-Hans.json, zh-Hant.json, and pt-BR.json.
  • Removed the BCP47Locale field and the automatic-mode re-registration loop.
  • Preserved explicit language selection overriding the OS language. This still needs a small configured-language registration helper because Fyne resolves a zh-CN system locale through its zh-Hans script bundle; registering only the language-level zh bundle leaves explicit selections untranslated on Chinese systems.
  • Added regression coverage for explicit language override and the canonical resource names.

Verification: go test ./... passes, the macOS app packages successfully, and manual testing confirmed that selecting Chinese now displays the Chinese UI.

tofuliang and others added 2 commits August 6, 2026 18:28
Fix test file names to match actual files on disk (zhHans.json not zh-Hans.json).

Register translations under BOTH the system locale (original trick to ensure
configured language always takes effect) AND the BCP47 locale (so go-i18n's
matcher correctly resolves queries like 'zh' → 'zh-Hans' for Chinese).

Add BCP47Locale field to TranslationInfo struct for explicit locale mapping
(zhHans→zh-Hans, zhHant→zh-Hant, pt_BR→pt-BR, etc.).

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@tofuliang
tofuliang force-pushed the fix/i18n-chinese-translations branch from 03c22d6 to ef5c9fe Compare August 6, 2026 10:31
@dweymouth

Copy link
Copy Markdown
Collaborator

addTranslationsForConfiguredLanguage (main.go:31-46) derives the target BCP47 tag for the second registration from lang.SystemLocale() rather than from the translation the user actually selected (tr.TranslationFileName). That only matters when someone's OS locale implies one Chinese script but they explicitly pick the other in Settings (e.g. OS is zh-CN → Fyne infers Hans, but the user selects "中文 (trad.)"). In that case the Traditional content gets merged into Fyne's built-in zh-Hans bucket via AddTranslationsForLocale.

That's not just mislabeling — zh-Hant.json still has literal English placeholders for a few keys it shares with Fyne's built-in dictionary (Advanced, Error, Save, e.g. "Advanced": "Advanced"). Since go-i18n's Bundle.AddMessages overwrites by exact key and this registration runs after Fyne's own init(), those placeholders clobber Fyne's working Simplified translations (高级/错误/保存 → literal English) for that specific mismatch case.

Since the translation files are now canonically named, this can key off the selected translation instead of the OS locale, which fixes the clobbering and also drops the golang.org/x/text/language dependency and the Zzzz script-inference logic entirely:

func addTranslationsForConfiguredLanguage(content []byte, tr res.TranslationInfo) error {
	systemLocale := lang.SystemLocale()
	if err := lang.AddTranslations(fyne.NewStaticResource(systemLocale.LanguageString()+".json", content)); err != nil {
		return err
	}

	// Translation filenames are canonical BCP47 tags (e.g. "zh-Hans.json"), so
	// also register directly under that tag. This lets an explicitly selected
	// Chinese variant correctly override Fyne's built-in zh-Hans/zh-Hant script
	// bundle, regardless of what script the OS locale implies.
	locale := strings.TrimSuffix(tr.TranslationFileName, ".json")
	if locale != "zh-Hans" && locale != "zh-Hant" {
		return nil
	}
	return lang.AddTranslationsForLocale(content, fyne.Locale(locale))
}

(call site becomes addTranslationsForConfiguredLanguage(content, tr))

I'd avoid just deleting the second registration to sidestep this — that reverts to exactly the single system-locale trick the original bug report described as broken for Chinese ("didn't match Fyne's built-in bundle tag format... go-i18n's matcher couldn't find the registered messages"), which would likely reopen the issue for anyone manually selecting a Chinese language on a Chinese-locale system.

Non-Chinese language selection (e.g. Spanish on an en-US system) is unaffected either way — verified locally that it never touches this code path once the zh-Hans/zh-Hant gate is added.

Might also be worth strengthening TestConfiguredLanguageOverridesSystemLocale to assert lang.L("Advanced") still returns "高级" after loading zh-Hant.json — that would catch this specific clobbering regression directly, since the current test's one assertion ("Settings") doesn't share a key with Fyne's built-in dictionary.

@dweymouth

Copy link
Copy Markdown
Collaborator

Claude suggested one fix above. If this seems valid to you, you can go ahead and implement it, but if not, let me know and I'll merge it as-is

@tofuliang

Copy link
Copy Markdown
Contributor Author

Implemented the selected-script fix and strengthened the regression in commit 9433ef8.

The canonical filename now determines the selected script. One additional detail was necessary for the explicit opposite-script case: Fyne's localizer still follows the OS-inferred Chinese script, so translated app messages are also registered into that active alias. Identity entries such as "Advanced": "Advanced" are removed from the alias payload, which preserves Fyne's built-in 高级/错误/保存 translations instead of clobbering them.

Verification:

  • go test ./...: 26 tests across 27 packages
  • macOS package build succeeded
  • manually tested zh-CN OS locale with Language = 'zhHant'; Traditional app strings rendered and shared Fyne strings remained translated

- Load and register translations from Fyne complete preference list.
- Register the configured translation under canonical, region, base,
  and language-script tags for each preference, without hardcoding
  language-specific scripts.
- Replace identity placeholders with pre-registration Fyne values and
  drop keys where Fyne has no translation, protecting shared UI strings.
- Add focused unit tests for the locale plan, alias payload, and the
  zh-CN/zh-Hant override case.
- Validate all translation filenames as canonical BCP47 locales.
@tofuliang

Copy link
Copy Markdown
Contributor Author

Simplified the implementation and removed Chinese-specific logic:

  • The runtime now uses the same locale preference list that Fyne's active localizer uses (go-locale.GetLocales()), ensuring the registered aliases always match the bundle that Fyne actually selects.
  • Removed the hardcoded zh-Hans/zh-Hant switch; the solution now generalizes to all languages including Portuguese, German, and explicit non-default scripts like uz-Cyrl-UZ.
  • Compact tests replaced the large subprocess scenarios with focused unit tests for the locale plan, alias payload, and the critical zh-CN/zh-Hant override case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants