Files
netbird/client/ui/i18n/bundle_test.go
Maycon Santos 91acb8147c [management,client] 0.75.0 release with new desktop UI (#6473)
- **Wails v3 application** (`client/ui`) with a React + TypeScript + Tailwind frontend replacing the Fyne UI: main connection view, exit-node switcher, networks/peers browser with detail panels, profile management, settings (general, network, SSH, security, troubleshooting, appearance), debug-bundle creation, and a first-run welcome flow.
- **Internationalization**: go-i18n bundle with 9 locales (en, de, es, fr, hu, it, pt, ru, zh-CN) shared between the tray and the frontend.
- **New system tray** implementation with per-platform theme-aware icons, including a native XEmbed host for Linux (`xembed_tray_linux.c`) and a Linux theme watcher.
- **Session handling**: auth session watcher (`client/internal/auth/sessionwatch`), pending login flow, session-expiration dialog and tray notifications, and `netbird login` improvements.
- **Daemon API extensions** (`daemon.proto`): status stream subscription, event stream, networks/exit-node selection endpoints, and richer full status — with probe throttling on the daemon side to protect against UI-driven request storms.
- **UI preferences store** persisted per profile, autostart management via the daemon (single source of truth in HKCU on Windows).
- **Build system**: Taskfile-based builds per platform (macOS, Linux, Windows), Docker cross-compilation images, MSIX/NSIS/nfpm/AppImage packaging, and a new `frontend-ui` CI workflow.

Co-authored-by: Zoltan Papp <zoltan.pmail@gmail.com>
Co-authored-by: Eduard Gert <kontakt@eduardgert.de>
Co-authored-by: braginini <bangvalo@gmail.com>
Co-authored-by: Pascal Fischer <32096965+pascal-fischer@users.noreply.github.com>
Co-authored-by: riccardom <riccardomanfrin@gmail.com>
2026-07-06 13:47:16 +02:00

157 lines
5.4 KiB
Go

//go:build !android && !ios && !freebsd && !js
package i18n
import (
"testing"
"testing/fstest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// fakeLocales returns an in-memory FS that mirrors the real
// client/ui/i18n/locales layout (root-level _index.json plus
// <code>/common.json bundles). Used by every Bundle test so we don't
// depend on the embedded production bundles staying stable.
func fakeLocales() fstest.MapFS {
return fstest.MapFS{
"_index.json": {Data: []byte(`{
"languages": [
{"code": "en", "displayName": "English", "englishName": "English"},
{"code": "hu", "displayName": "Magyar", "englishName": "Hungarian"}
]
}`)},
"en/common.json": {Data: []byte(`{
"tray.menu.connect": {"message": "Connect", "description": "Tray menu item"},
"tray.menu.installVersion": {"message": "Install version {version}"},
"notify.update.body": {"message": "NetBird {version} is available."}
}`)},
"hu/common.json": {Data: []byte(`{
"tray.menu.connect": {"message": "Csatlakozás"},
"tray.menu.installVersion": {"message": "{version} telepítése"}
}`)},
}
}
func TestBundle_LoadsAllLanguages(t *testing.T) {
b, err := NewBundle(fakeLocales())
require.NoError(t, err)
langs := b.Languages()
require.Len(t, langs, 2)
codes := []LanguageCode{langs[0].Code, langs[1].Code}
assert.ElementsMatch(t, []LanguageCode{"en", "hu"}, codes, "Languages should list every bundle that loaded")
}
func TestBundle_TranslateLooksUpKey(t *testing.T) {
b, err := NewBundle(fakeLocales())
require.NoError(t, err)
assert.Equal(t, "Csatlakozás", b.Translate("hu", "tray.menu.connect"))
assert.Equal(t, "Connect", b.Translate("en", "tray.menu.connect"))
}
func TestBundle_TranslateSubstitutesPlaceholders(t *testing.T) {
b, err := NewBundle(fakeLocales())
require.NoError(t, err)
assert.Equal(t, "Install version 1.2.3",
b.Translate("en", "tray.menu.installVersion", "version", "1.2.3"),
"placeholders should substitute by name")
assert.Equal(t, "1.2.3 telepítése",
b.Translate("hu", "tray.menu.installVersion", "version", "1.2.3"))
}
func TestBundle_TranslateFallsBackToEnglish(t *testing.T) {
b, err := NewBundle(fakeLocales())
require.NoError(t, err)
// notify.update.body is missing from the hu bundle; English fallback
// applies so the user always sees a populated label rather than the
// raw key.
got := b.Translate("hu", "notify.update.body", "version", "9.9.9")
assert.Equal(t, "NetBird 9.9.9 is available.", got, "missing hu key should fall back to en bundle")
}
func TestBundle_TranslateUnknownKeyReturnsKey(t *testing.T) {
b, err := NewBundle(fakeLocales())
require.NoError(t, err)
assert.Equal(t, "tray.missing", b.Translate("en", "tray.missing"),
"unknown key should return the key itself for debugability")
}
func TestBundle_BundleForReturnsCopy(t *testing.T) {
b, err := NewBundle(fakeLocales())
require.NoError(t, err)
m, err := b.BundleFor("en")
require.NoError(t, err)
require.NotEmpty(t, m, "BundleFor should return populated map for known language")
m["tray.menu.connect"] = "Mutated"
assert.Equal(t, "Connect", b.Translate("en", "tray.menu.connect"),
"BundleFor must return a copy, not the live map")
}
func TestBundle_BundleForUnknownLanguage(t *testing.T) {
b, err := NewBundle(fakeLocales())
require.NoError(t, err)
_, err = b.BundleFor("xx")
assert.ErrorIs(t, err, ErrUnsupportedLanguage)
}
func TestBundle_HasLanguage(t *testing.T) {
b, err := NewBundle(fakeLocales())
require.NoError(t, err)
assert.True(t, b.HasLanguage("en"))
assert.True(t, b.HasLanguage("hu"))
assert.False(t, b.HasLanguage("de"))
}
func TestBundle_MissingDefaultBundleFails(t *testing.T) {
// Without an en bundle we have nothing to fall back to, so construction
// must hard-fail. Catches packaging accidents where someone drops the
// English locale.
fs := fstest.MapFS{
"_index.json": {Data: []byte(`{"languages":[{"code":"hu","displayName":"Magyar","englishName":"Hungarian"}]}`)},
"hu/common.json": {Data: []byte(`{"k":{"message":"v"}}`)},
}
_, err := NewBundle(fs)
require.Error(t, err)
assert.Contains(t, err.Error(), "default language")
}
func TestBundle_MissingBundleSkipsLanguage(t *testing.T) {
// A language declared in the index but missing its bundle file is
// dropped from Languages with a warning — adding a new language must
// be a two-step process (declare + ship), not declare-only.
fs := fstest.MapFS{
"_index.json": {Data: []byte(`{"languages":[
{"code":"en","displayName":"English","englishName":"English"},
{"code":"de","displayName":"Deutsch","englishName":"German"}
]}`)},
"en/common.json": {Data: []byte(`{"k":{"message":"v"}}`)},
}
b, err := NewBundle(fs)
require.NoError(t, err)
langs := b.Languages()
require.Len(t, langs, 1)
assert.Equal(t, LanguageCode("en"), langs[0].Code, "language without a bundle file must be dropped")
assert.False(t, b.HasLanguage("de"))
}
func TestBundle_OddPlaceholderArgsDoNotPanic(t *testing.T) {
b, err := NewBundle(fakeLocales())
require.NoError(t, err)
// Trailing dangling arg should be dropped, not panic — preserves UI
// stability when a caller passes an unpaired placeholder by mistake.
got := b.Translate("en", "tray.menu.installVersion", "version", "1.2.3", "extra")
assert.Equal(t, "Install version 1.2.3", got)
}