fix: localize native tray menu
This commit is contained in:
@@ -1,11 +1,16 @@
|
||||
// Package tray owns the native tray menu wiring for the desktop shell.
|
||||
package tray
|
||||
|
||||
import "sync"
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// MenuItem exposes click events from a native tray menu item.
|
||||
type MenuItem interface {
|
||||
Clicked() <-chan struct{}
|
||||
SetTitle(title string)
|
||||
SetTooltip(tooltip string)
|
||||
}
|
||||
|
||||
// Backend is the platform tray implementation.
|
||||
@@ -23,11 +28,63 @@ type Actions struct {
|
||||
Quit func()
|
||||
}
|
||||
|
||||
// Labels contains the user-visible text for one tray menu locale.
|
||||
type Labels struct {
|
||||
ShowTitle string
|
||||
ShowTooltip string
|
||||
QuitTitle string
|
||||
QuitTooltip string
|
||||
}
|
||||
|
||||
func englishLabels() Labels {
|
||||
return Labels{
|
||||
ShowTitle: "Show Verstak",
|
||||
ShowTooltip: "Show the Verstak window",
|
||||
QuitTitle: "Quit",
|
||||
QuitTooltip: "Quit Verstak",
|
||||
}
|
||||
}
|
||||
|
||||
func russianLabels() Labels {
|
||||
return Labels{
|
||||
ShowTitle: "Показать Верстак",
|
||||
ShowTooltip: "Показать окно Верстака",
|
||||
QuitTitle: "Выйти",
|
||||
QuitTooltip: "Завершить Верстак",
|
||||
}
|
||||
}
|
||||
|
||||
// LabelsForPreference resolves the saved language preference for native tray UI.
|
||||
// System locale values are intentionally passed in by the shell so this package
|
||||
// stays independent of process environment and is deterministic in tests.
|
||||
func LabelsForPreference(preference string, systemLocales ...string) Labels {
|
||||
if strings.EqualFold(strings.TrimSpace(preference), "ru") {
|
||||
return russianLabels()
|
||||
}
|
||||
if strings.EqualFold(strings.TrimSpace(preference), "system") {
|
||||
for _, locale := range systemLocales {
|
||||
locale = strings.ToLower(strings.TrimSpace(locale))
|
||||
if locale == "" {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(locale, "ru") {
|
||||
return russianLabels()
|
||||
}
|
||||
return englishLabels()
|
||||
}
|
||||
}
|
||||
return englishLabels()
|
||||
}
|
||||
|
||||
// Controller initializes one native tray and routes its menu actions.
|
||||
type Controller struct {
|
||||
backend Backend
|
||||
icon []byte
|
||||
start sync.Once
|
||||
mu sync.RWMutex
|
||||
labels Labels
|
||||
show MenuItem
|
||||
quit MenuItem
|
||||
}
|
||||
|
||||
// New creates a tray controller for one application process.
|
||||
@@ -35,9 +92,22 @@ func New(backend Backend, icon []byte) *Controller {
|
||||
return &Controller{
|
||||
backend: backend,
|
||||
icon: append([]byte(nil), icon...),
|
||||
labels: englishLabels(),
|
||||
}
|
||||
}
|
||||
|
||||
// SetLabels updates the current and future native tray menu labels.
|
||||
func (c *Controller) SetLabels(labels Labels) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
c.mu.Lock()
|
||||
c.labels = labels
|
||||
show, quit := c.show, c.quit
|
||||
c.mu.Unlock()
|
||||
applyLabels(show, quit, labels)
|
||||
}
|
||||
|
||||
// Start registers the native tray without taking over the Wails event loop.
|
||||
func (c *Controller) Start(actions Actions) {
|
||||
if c == nil || c.backend == nil {
|
||||
@@ -47,8 +117,16 @@ func (c *Controller) Start(actions Actions) {
|
||||
c.backend.Register(func() {
|
||||
c.backend.SetIcon(c.icon)
|
||||
c.backend.SetTooltip("Verstak")
|
||||
show := c.backend.AddMenuItem("Show Verstak", "Show the Verstak window")
|
||||
quit := c.backend.AddMenuItem("Quit", "Quit Verstak")
|
||||
c.mu.RLock()
|
||||
labels := c.labels
|
||||
c.mu.RUnlock()
|
||||
show := c.backend.AddMenuItem(labels.ShowTitle, labels.ShowTooltip)
|
||||
quit := c.backend.AddMenuItem(labels.QuitTitle, labels.QuitTooltip)
|
||||
c.mu.Lock()
|
||||
c.show, c.quit = show, quit
|
||||
labels = c.labels
|
||||
c.mu.Unlock()
|
||||
applyLabels(show, quit, labels)
|
||||
if actions.Show != nil && show != nil {
|
||||
go routeClicks(show.Clicked(), actions.Show)
|
||||
}
|
||||
@@ -59,6 +137,17 @@ func (c *Controller) Start(actions Actions) {
|
||||
})
|
||||
}
|
||||
|
||||
func applyLabels(show, quit MenuItem, labels Labels) {
|
||||
if show != nil {
|
||||
show.SetTitle(labels.ShowTitle)
|
||||
show.SetTooltip(labels.ShowTooltip)
|
||||
}
|
||||
if quit != nil {
|
||||
quit.SetTitle(labels.QuitTitle)
|
||||
quit.SetTooltip(labels.QuitTooltip)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop releases the native tray after Wails has begun application shutdown.
|
||||
func (c *Controller) Stop() {
|
||||
if c == nil || c.backend == nil {
|
||||
|
||||
@@ -7,12 +7,22 @@ import (
|
||||
|
||||
type fakeMenuItem struct {
|
||||
clicked chan struct{}
|
||||
title string
|
||||
tooltip string
|
||||
}
|
||||
|
||||
func (i *fakeMenuItem) Clicked() <-chan struct{} {
|
||||
return i.clicked
|
||||
}
|
||||
|
||||
func (i *fakeMenuItem) SetTitle(title string) {
|
||||
i.title = title
|
||||
}
|
||||
|
||||
func (i *fakeMenuItem) SetTooltip(tooltip string) {
|
||||
i.tooltip = tooltip
|
||||
}
|
||||
|
||||
type fakeBackend struct {
|
||||
icon []byte
|
||||
tooltip string
|
||||
@@ -35,7 +45,7 @@ func (b *fakeBackend) SetTooltip(tooltip string) {
|
||||
}
|
||||
|
||||
func (b *fakeBackend) AddMenuItem(title, _ string) MenuItem {
|
||||
item := &fakeMenuItem{clicked: make(chan struct{}, 1)}
|
||||
item := &fakeMenuItem{clicked: make(chan struct{}, 1), title: title}
|
||||
b.items[title] = item
|
||||
return item
|
||||
}
|
||||
@@ -89,3 +99,38 @@ func TestControllerStopsNativeTrayBackend(t *testing.T) {
|
||||
t.Fatalf("backend quit calls = %d, want 1", backend.quitCalls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestControllerUsesAndUpdatesLocalizedLabels(t *testing.T) {
|
||||
backend := &fakeBackend{items: make(map[string]*fakeMenuItem)}
|
||||
controller := New(backend, []byte{1})
|
||||
controller.SetLabels(LabelsForPreference("ru"))
|
||||
controller.Start(Actions{})
|
||||
|
||||
show := backend.items["Показать Верстак"]
|
||||
quit := backend.items["Выйти"]
|
||||
if show == nil || quit == nil {
|
||||
t.Fatalf("Russian tray menu = %#v", backend.items)
|
||||
}
|
||||
if show.tooltip != "Показать окно Верстака" || quit.tooltip != "Завершить Верстак" {
|
||||
t.Fatalf("Russian tray tooltips = show:%q quit:%q", show.tooltip, quit.tooltip)
|
||||
}
|
||||
|
||||
controller.SetLabels(LabelsForPreference("en"))
|
||||
if show.title != "Show Verstak" || quit.title != "Quit" {
|
||||
t.Fatalf("English tray menu after update = show:%q quit:%q", show.title, quit.title)
|
||||
}
|
||||
if show.tooltip != "Show the Verstak window" || quit.tooltip != "Quit Verstak" {
|
||||
t.Fatalf("English tray tooltips after update = show:%q quit:%q", show.tooltip, quit.tooltip)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLabelsForSystemRussianLocale(t *testing.T) {
|
||||
labels := LabelsForPreference("system", "", "ru_RU.UTF-8", "en_US.UTF-8")
|
||||
if labels.ShowTitle != "Показать Верстак" || labels.QuitTitle != "Выйти" {
|
||||
t.Fatalf("system Russian labels = %#v", labels)
|
||||
}
|
||||
labels = LabelsForPreference("system", "C.UTF-8", "", "ru_RU.UTF-8")
|
||||
if labels.ShowTitle != "Show Verstak" || labels.QuitTitle != "Quit" {
|
||||
t.Fatalf("higher-priority system locale must win, got %#v", labels)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,3 +39,15 @@ func (item systrayMenuItem) Clicked() <-chan struct{} {
|
||||
}
|
||||
return item.item.ClickedCh
|
||||
}
|
||||
|
||||
func (item systrayMenuItem) SetTitle(title string) {
|
||||
if item.item != nil {
|
||||
item.item.SetTitle(title)
|
||||
}
|
||||
}
|
||||
|
||||
func (item systrayMenuItem) SetTooltip(tooltip string) {
|
||||
if item.item != nil {
|
||||
item.item.SetTooltip(tooltip)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user