mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-18 14:49:57 +00:00
[client/ui] Introduce localisation (i18n + preferences) feature packages
Adds a tray + React translation pipeline driven by a single JSON locale tree (frontend/src/i18n/locales) embedded into the Go binary. The tray re-renders on language switch via a Localizer that subscribes to the preferences store. Layout: - client/ui/i18n: Bundle, LanguageCode, Language, errors, embedded-FS loader. Pure domain, no Wails/daemon deps. - client/ui/preferences: Store + UIPreferences for user-scope UI state, persisted under os.UserConfigDir()/netbird/ui-preferences.json with atomic writes and a subscribe/broadcast channel. - client/ui/services: thin Wails-binding facades (services.I18n, services.Preferences) so React sees ctx-first signatures. - client/ui/localizer.go: tray bridge that owns the active language, exposes T()/StatusLabel() and re-paints the menu on prefs change. - tray.go: every user-facing const replaced by translation keys via t.loc.T(...); menu rebuild + state replay on language switch. - main.go: //go:embed all:frontend/src/i18n/locales, wires Bundle -> Store -> Localizer -> Wails facades in order. Frontend API exposed via Wails bindings: I18n.Languages, I18n.Bundle, Preferences.Get, Preferences.SetLanguage, plus the netbird:preferences:changed event. Includes regenerated Wails TS bindings (peers/profileswitcher/etc. re-emitted as part of the build) and en/hu seed bundles.
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"context"
|
||||
"embed"
|
||||
"flag"
|
||||
"io/fs"
|
||||
"log"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -14,6 +15,8 @@ import (
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
"github.com/wailsapp/wails/v3/pkg/services/notifications"
|
||||
|
||||
"github.com/netbirdio/netbird/client/ui/i18n"
|
||||
"github.com/netbirdio/netbird/client/ui/preferences"
|
||||
"github.com/netbirdio/netbird/client/ui/services"
|
||||
"github.com/netbirdio/netbird/util"
|
||||
)
|
||||
@@ -21,6 +24,15 @@ import (
|
||||
//go:embed all:frontend/dist
|
||||
var assets embed.FS
|
||||
|
||||
// localesFS roots the i18n translation bundles. Embedded from the same
|
||||
// directory the React app imports, so a single JSON source drives both
|
||||
// the tray (Go) and the in-window UI (Vite imports the files directly).
|
||||
// The `all:` prefix is required so _index.json is included — //go:embed
|
||||
// silently drops files whose names start with "_" or "." otherwise.
|
||||
//
|
||||
//go:embed all:frontend/src/i18n/locales
|
||||
var localesRoot embed.FS
|
||||
|
||||
// stringList is a flag.Value that collects repeated string flags. The first
|
||||
// time the user passes -log-file the seeded default ("console") is dropped;
|
||||
// subsequent passes append. Lets the user replace or extend the log target
|
||||
@@ -48,6 +60,7 @@ func init() {
|
||||
application.RegisterEvent[services.SystemEvent](services.EventSystem)
|
||||
application.RegisterEvent[services.UpdateAvailable](services.EventUpdateAvailable)
|
||||
application.RegisterEvent[services.UpdateProgress](services.EventUpdateProgress)
|
||||
application.RegisterEvent[preferences.UIPreferences](preferences.EventPreferencesChanged)
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -115,6 +128,27 @@ func main() {
|
||||
notifier := notifications.New()
|
||||
profileSwitcher := services.NewProfileSwitcher(profiles, connection, peers)
|
||||
|
||||
// localesFS reroots the embedded tree at the locales directory itself
|
||||
// so the bundle sees _index.json and <lang>/common.json at the top
|
||||
// level (the //go:embed path is rooted at the package, not the leaf
|
||||
// dir).
|
||||
localesFS, err := fs.Sub(localesRoot, "frontend/src/i18n/locales")
|
||||
if err != nil {
|
||||
log.Fatalf("locate locales fs: %v", err)
|
||||
}
|
||||
// Build the domain layer first, then wrap it in the Wails-bound
|
||||
// services. The Bundle satisfies preferences.LanguageValidator so
|
||||
// SetLanguage rejects codes that have no shipped translation.
|
||||
bundle, err := i18n.NewBundle(localesFS)
|
||||
if err != nil {
|
||||
log.Fatalf("init i18n bundle: %v", err)
|
||||
}
|
||||
prefStore, err := preferences.NewStore(bundle, app.Event)
|
||||
if err != nil {
|
||||
log.Fatalf("init preferences store: %v", err)
|
||||
}
|
||||
localizer := NewLocalizer(bundle, prefStore)
|
||||
|
||||
app.RegisterService(application.NewService(connection))
|
||||
app.RegisterService(application.NewService(settings))
|
||||
app.RegisterService(application.NewService(services.NewNetworks(conn)))
|
||||
@@ -125,6 +159,8 @@ func main() {
|
||||
app.RegisterService(application.NewService(peers))
|
||||
app.RegisterService(application.NewService(notifier))
|
||||
app.RegisterService(application.NewService(profileSwitcher))
|
||||
app.RegisterService(application.NewService(services.NewI18n(bundle)))
|
||||
app.RegisterService(application.NewService(services.NewPreferences(prefStore)))
|
||||
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "NetBird",
|
||||
@@ -173,6 +209,7 @@ func main() {
|
||||
Notifier: notifier,
|
||||
Update: update,
|
||||
ProfileSwitcher: profileSwitcher,
|
||||
Localizer: localizer,
|
||||
})
|
||||
listenForShowSignal(context.Background(), tray)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user