Application specific preferences saving now enabled.
This commit is contained in:
1 parent
f88e5b81a2
commit
60abcc37d9
23 files changed
+1004
-248
No files matched your search
@@ -53,6 +53,7 @@ type application struct {
|
||||
saver *settingsSaver
|
||||
speechPending bool
|
||||
speechResults chan speechChangeResult
|
||||
overrideLoads chan func()
|
||||
speechChoices chan preferences.Discovery
|
||||
prepareSpeech func(config.Speech) (speech.Transport, error)
|
||||
|
||||
@@ -99,6 +100,7 @@ func new_application(ctx context.Context, source Source, keyboards Keyboards, vo
|
||||
a.ui.previewSpelling = a.preview_spelling
|
||||
a.ui.discoverChoices, a.ui.saveSettings = options.SettingsChoices, a.save_menu_settings
|
||||
a.speechResults = make(chan speechChangeResult, 1)
|
||||
a.overrideLoads = make(chan func(), 1)
|
||||
a.speechChoices = make(chan preferences.Discovery, 1)
|
||||
a.prepareSpeech = prepare_speech
|
||||
a.quick.selectSpeech = func(target config.Speech, done func(error, bool)) {
|
||||
|
||||
@@ -5,11 +5,13 @@ package app
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
|
||||
"git.stormux.org/storm/fenrir/internal/applicationoverrides"
|
||||
"git.stormux.org/storm/fenrir/internal/applicationoverrides/networkmanager"
|
||||
"git.stormux.org/storm/fenrir/internal/applicationoverrides/ninjam"
|
||||
"git.stormux.org/storm/fenrir/internal/config"
|
||||
"git.stormux.org/storm/fenrir/internal/i18n"
|
||||
"git.stormux.org/storm/fenrir/internal/interaction"
|
||||
"git.stormux.org/storm/fenrir/internal/prompt"
|
||||
@@ -26,6 +28,9 @@ type overrideSnapshot struct {
|
||||
|
||||
type applicationOverride struct {
|
||||
id string
|
||||
path string
|
||||
base config.Settings
|
||||
profile *config.Document
|
||||
terminal int
|
||||
adapter applicationoverrides.Adapter
|
||||
underlying, temporary overrideSnapshot
|
||||
@@ -33,7 +38,59 @@ type applicationOverride struct {
|
||||
initialized, recognized, exitPending bool
|
||||
}
|
||||
|
||||
func (a *application) saves_unavailable() bool { return len(a.overrides) != 0 || a.overrideTransition }
|
||||
func (a *application) saves_unavailable() bool {
|
||||
return a.overrideTransition || a.overrideRestoreFailed || a.overrideFailed != nil || (a.overrideCurrent != nil && (!a.overrideCurrent.recognized || a.overrideCurrent.path == ""))
|
||||
}
|
||||
|
||||
func (a *application) settings_path() string {
|
||||
if a.overrideCurrent != nil {
|
||||
return a.overrideCurrent.path
|
||||
}
|
||||
return a.options.ConfigPath
|
||||
}
|
||||
|
||||
func (a *application) start_settings_save(path string, settings config.Settings) bool {
|
||||
if selected := a.overrideCurrent; selected != nil {
|
||||
base := selected.base
|
||||
return a.saver.start(a.ctx, settings, func() error {
|
||||
doc, err := config.Open_application_document(path, base)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return doc.Save(settings)
|
||||
})
|
||||
}
|
||||
return a.saver.save(a.ctx, path, settings)
|
||||
}
|
||||
|
||||
// Profile reads are bounded and run off the event loop, like settings writes.
|
||||
func (a *application) load_application_profile(selected *applicationOverride) {
|
||||
selected.base = config.Apply_application_settings(a.effective_settings(), a.settings_to_save())
|
||||
// Inherit the actual working selection, including startup fallback, rather
|
||||
// than unavailable saved voices or selections for a next-launch backend.
|
||||
copy_speech_selection(&selected.base.Presentation.Speech, a.settings.live.Presentation.Speech)
|
||||
selected.adapter.Configure(&selected.base)
|
||||
path, base := selected.path, selected.base
|
||||
a.overrideTransition = true
|
||||
go func() {
|
||||
doc, err := config.Open_application_document(path, base)
|
||||
select {
|
||||
case a.overrideLoads <- func() {
|
||||
a.overrideTransition = false
|
||||
if err != nil {
|
||||
selected.adapter.Close()
|
||||
delete(a.overrides, selected.terminal)
|
||||
a.voice.Say(i18n.Format("Could not load application settings: {0}", err.Error()))
|
||||
} else {
|
||||
selected.profile = doc
|
||||
selected.adapter.Reset()
|
||||
}
|
||||
a.overrideDirty = true
|
||||
}:
|
||||
case <-a.ctx.Done():
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (a *application) capture_override(terminal int) overrideSnapshot {
|
||||
settings := a.settings
|
||||
@@ -68,7 +125,7 @@ func (a *application) restore_override(done func()) bool {
|
||||
done()
|
||||
return true
|
||||
}
|
||||
if a.speechPending || a.overrideTransition || a.overrideRestoreFailed {
|
||||
if a.speechPending || a.saver.busy || a.overrideTransition || a.overrideRestoreFailed {
|
||||
return false
|
||||
}
|
||||
// A form opened under the application must never outlive that context:
|
||||
@@ -142,6 +199,9 @@ func (a *application) select_override(id string) {
|
||||
a.overrides = make(map[int]*applicationOverride)
|
||||
}
|
||||
selected := &applicationOverride{id: id, terminal: terminal, adapter: adapter}
|
||||
if a.options.ConfigPath != "" {
|
||||
selected.path = filepath.Join(filepath.Dir(a.options.ConfigPath), "applications", id+".toml")
|
||||
}
|
||||
selected.shell.Observe(a.lastSnapshot)
|
||||
a.overrides[terminal] = selected
|
||||
a.voice.Say(i18n.Format("{0} application override selected", i18n.T(adapter.Name())))
|
||||
@@ -151,7 +211,7 @@ func (a *application) select_override(id string) {
|
||||
// Called before reader.Update so exit detection and restored settings affect
|
||||
// the first shell snapshot, rather than interpreting it with the old adapter.
|
||||
func (a *application) observe_override(next screen.Snapshot) (applicationoverrides.Frame, bool) {
|
||||
if a.overrideTransition {
|
||||
if a.overrideTransition || (a.saver.busy && len(a.overrides) != 0) {
|
||||
return applicationoverrides.Frame{}, false
|
||||
}
|
||||
if a.overrideRestoreFailed {
|
||||
@@ -188,14 +248,42 @@ func (a *application) observe_override(next screen.Snapshot) (applicationoverrid
|
||||
return applicationoverrides.Frame{}, false
|
||||
}
|
||||
a.ui.close()
|
||||
if !selected.initialized && selected.profile == nil {
|
||||
a.load_application_profile(selected)
|
||||
return applicationoverrides.Frame{}, false
|
||||
}
|
||||
selected.underlying = a.capture_override(next.Terminal)
|
||||
if !selected.initialized {
|
||||
settings := a.effective_settings()
|
||||
selected.adapter.Configure(&settings)
|
||||
a.apply_remote_state(settings, false)
|
||||
a.configure_reader()
|
||||
a.engine.Clear_session_regions(next.Terminal)
|
||||
selected.initialized = true
|
||||
settings = selected.profile.Apply_application(settings)
|
||||
activate := func() {
|
||||
a.apply_remote_state(settings, false)
|
||||
a.settings.saveDraft, a.settings.resetBase = selected.profile.Settings, selected.profile.Settings
|
||||
a.settings.remoteFields = make(map[string]uint64)
|
||||
a.settings.remoteRevision = 0
|
||||
*a.quick = *new_quick_settings(settings.Presentation.Speech.Driver)
|
||||
a.configure_reader()
|
||||
a.engine.Clear_session_regions(next.Terminal)
|
||||
selected.initialized, selected.recognized = true, true
|
||||
a.overrideCurrent = selected
|
||||
}
|
||||
if !same_speech_selection(settings.Presentation.Speech, a.settings.live.Presentation.Speech) {
|
||||
a.overrideTransition = true
|
||||
a.change_speech(settings.Presentation.Speech, func(err error) {
|
||||
a.overrideTransition = false
|
||||
if err != nil {
|
||||
selected.adapter.Close()
|
||||
delete(a.overrides, selected.terminal)
|
||||
a.voice.Say(i18n.Format("Could not load application settings: {0}", err.Error()))
|
||||
return
|
||||
}
|
||||
activate()
|
||||
selected.adapter.Reset()
|
||||
})
|
||||
return applicationoverrides.Frame{}, false
|
||||
}
|
||||
activate()
|
||||
} else {
|
||||
// Numeric/runtime settings are synchronous. Speech selection is
|
||||
// restored separately before resuming a suspended adapter.
|
||||
|
||||
@@ -6,10 +6,12 @@ package app
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.stormux.org/storm/fenrir/internal/command"
|
||||
"git.stormux.org/storm/fenrir/internal/config"
|
||||
@@ -50,8 +52,8 @@ func TestOverrideRestoresSynthesizerAndKeepsSaveBlockedUntilCompletion(t *testin
|
||||
if response := request_selection(t, a, "SET speech#voice=Temporary;speech#module=fixture"); response != "" {
|
||||
t.Fatal(response)
|
||||
}
|
||||
if !reflect.DeepEqual(a.settings.saveDraft, baseline) {
|
||||
t.Fatal("temporary voice modified draft")
|
||||
if a.settings.saveDraft.Presentation.Speech.Voice != "Temporary" || !same_speech_selection(a.overrideCurrent.underlying.settings.saveDraft.Presentation.Speech, baseline.Presentation.Speech) {
|
||||
t.Fatal("application voice did not stay in its own draft")
|
||||
}
|
||||
if failed {
|
||||
a.prepareSpeech = func(config.Speech) (speech.Transport, error) { return nil, errors.New("fixture unavailable") }
|
||||
@@ -109,6 +111,19 @@ func override_read(t *testing.T, a *application, snapshot screen.Snapshot) {
|
||||
if err := a.read_screen(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Model the event loop's asynchronous profile-read completion, leaving
|
||||
// speech transitions to the tests which explicitly exercise them.
|
||||
if a.overrideTransition && !a.speechPending {
|
||||
select {
|
||||
case loaded := <-a.overrideLoads:
|
||||
loaded()
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("profile load stalled")
|
||||
}
|
||||
if err := a.read_screen(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplicationsMenuActivatesOnlyOnEnterAndReleasesCapture(t *testing.T) {
|
||||
@@ -183,7 +198,7 @@ func TestApplicationsMenuPrefixSearch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverrideCannotSaveOrContaminateDraftAndRestoresSession(t *testing.T) {
|
||||
func TestOverrideSavesOwnDraftAndRestoresSession(t *testing.T) {
|
||||
a, voice := persistence_application(t, true)
|
||||
a.handle_command(command.NextQuickValue) // Preserve a pre-existing unsaved adjustment.
|
||||
a.handle_command(command.AutoRead)
|
||||
@@ -202,20 +217,30 @@ func TestOverrideCannotSaveOrContaminateDraftAndRestoresSession(t *testing.T) {
|
||||
if response := a.apply_remote_settings("SET speech#rate=0.77;keyboard#word_echo=True"); response != "" {
|
||||
t.Fatal(response)
|
||||
}
|
||||
if !reflect.DeepEqual(a.settings.saveDraft, baseSettings.saveDraft) {
|
||||
t.Fatal("override modified save draft")
|
||||
if !reflect.DeepEqual(a.overrideCurrent.underlying.settings, baseSettings) {
|
||||
t.Fatal("ordinary draft changed")
|
||||
}
|
||||
profilePath := filepath.Join(filepath.Dir(a.options.ConfigPath), "applications", "ninjam.toml")
|
||||
a.save_settings()
|
||||
if a.saver.busy || voice.messages[len(voice.messages)-1] != "Save unavailable." {
|
||||
t.Fatal("shortcut save admitted")
|
||||
finish_persistence(t, a, false)
|
||||
if !strings.Contains(voice.messages[len(voice.messages)-1], "Ninjam application settings saved") {
|
||||
t.Fatal(voice.messages)
|
||||
}
|
||||
for _, text := range []string{"SETTING SAVE", "SETTING SAVEAS " + a.options.ConfigPath} {
|
||||
if got := runtime_request(a, text); got != "Save unavailable." {
|
||||
t.Fatal(text, got)
|
||||
}
|
||||
doc, err := config.Open_application_document(profilePath, a.overrideCurrent.base)
|
||||
if err != nil || doc.Settings.Presentation.Speech.Rate != a.settings.live.Presentation.Speech.Rate || !doc.Settings.Keyboard.WordEcho {
|
||||
t.Fatal("profile not saved", err)
|
||||
}
|
||||
reply := persistence_request(a, "SAVE")
|
||||
finish_persistence(t, a, false)
|
||||
persistence_reply(t, reply, false)
|
||||
if got := runtime_request(a, "SETTING SAVEAS "+a.options.ConfigPath); got != "SAVEAS is unavailable in application settings" {
|
||||
t.Fatal(got)
|
||||
}
|
||||
a.open_settings()
|
||||
receive_ui(t, a.ui)
|
||||
if !strings.Contains(voice.messages[len(voice.messages)-1], "Ninjam application settings") {
|
||||
t.Fatal(voice.messages)
|
||||
}
|
||||
for range 100 {
|
||||
if control, ok := a.ui.session.Focused_control(); ok && control.Action == "save" {
|
||||
break
|
||||
@@ -223,9 +248,7 @@ func TestOverrideCannotSaveOrContaminateDraftAndRestoresSession(t *testing.T) {
|
||||
a.ui.handle(interaction.Key{Name: "tab"})
|
||||
}
|
||||
a.ui.handle(interaction.Key{Name: "enter"})
|
||||
if !a.ui.active || a.saver.busy || voice.messages[len(voice.messages)-1] != "Save unavailable." {
|
||||
t.Fatal("menu save admitted", voice.messages)
|
||||
}
|
||||
finish_persistence(t, a, false)
|
||||
a.ui.close()
|
||||
a.select_override("default")
|
||||
if !reflect.DeepEqual(a.settings, baseSettings) || a.quick.revision != baseQuick || !reflect.DeepEqual(a.engine.Capture_session(), baseRuntime) {
|
||||
@@ -355,3 +378,132 @@ func TestActivationRejectsPendingOperationsAndRestartStartsWithoutOverride(t *te
|
||||
t.Fatal("restart retained override", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplicationProfileReloadAndDefaultInheritance(t *testing.T) {
|
||||
for _, terminalMode := range []bool{false, true} {
|
||||
a, _ := persistence_application(t, terminalMode)
|
||||
override_read(t, a, override_screen(1, "ninjam"))
|
||||
a.select_override("ninjam")
|
||||
override_read(t, a, override_screen(1, "ninjam"))
|
||||
a.handle_command(command.NextQuickValue)
|
||||
savedRate := a.settings.live.Presentation.Speech.Rate
|
||||
a.save_settings()
|
||||
// A terminal switch must wait for the admitted application write.
|
||||
override_read(t, a, override_screen(3, "other terminal"))
|
||||
if a.overrideCurrent == nil {
|
||||
t.Fatal("save ownership lost before completion")
|
||||
}
|
||||
finish_persistence(t, a, false)
|
||||
override_read(t, a, override_screen(3, "other terminal"))
|
||||
if a.overrideCurrent != nil || a.settings.live.Presentation.Speech.Rate == savedRate {
|
||||
t.Fatal("application leaked across terminal")
|
||||
}
|
||||
// Change an unrelated ordinary setting, then discard/reselect the adapter.
|
||||
if text := a.apply_remote_settings("SET speech#pitch=0.6"); text != "" {
|
||||
t.Fatal(text)
|
||||
}
|
||||
pitch := a.settings.live.Presentation.Speech.Pitch
|
||||
override_read(t, a, override_screen(1, "ninjam"))
|
||||
a.select_override("default")
|
||||
a.select_override("ninjam")
|
||||
override_read(t, a, override_screen(1, "ninjam"))
|
||||
if a.settings.live.Presentation.Speech.Rate != savedRate || a.settings.live.Presentation.Speech.Pitch != pitch {
|
||||
t.Fatal("saved rate or new inherited pitch missing", a.settings.live.Presentation.Speech)
|
||||
}
|
||||
if text := a.apply_remote_settings("SET speech#rate=0.1"); text != "" {
|
||||
t.Fatal(text)
|
||||
}
|
||||
if text := a.reset_remote_settings(); text != "" {
|
||||
t.Fatal(text)
|
||||
}
|
||||
if a.settings.live.Presentation.Speech.Rate != savedRate {
|
||||
t.Fatal("RESET did not restore profile")
|
||||
}
|
||||
before := a.settings
|
||||
if text := a.apply_remote_settings("SET speech#rate=0.2;screen#ignore_screen=1"); text == "" || !reflect.DeepEqual(a.settings, before) {
|
||||
t.Fatal("global remote change was not rejected atomically")
|
||||
}
|
||||
override_read(t, a, override_screen(1, "[Username@host directory] $"))
|
||||
if a.overrideCurrent != nil || a.settings.live.Presentation.Speech.Rate == savedRate {
|
||||
t.Fatal("exit did not restore default")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplicationProfileVoiceLoadAndFailureLeaveDefaultIntact(t *testing.T) {
|
||||
for _, failure := range []string{"", "invalid", "unavailable"} {
|
||||
a, _, path := selection_application(t, true)
|
||||
profilePath := filepath.Join(filepath.Dir(path), "applications", "ninjam.toml")
|
||||
if err := os.MkdirAll(filepath.Dir(profilePath), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
data := "[presentation.speech]\nvoice = 'Application voice'\nrate = 12\n"
|
||||
if failure == "invalid" {
|
||||
data = "[presentation.speech]\nrate = 999\n"
|
||||
}
|
||||
if err := os.WriteFile(profilePath, []byte(data), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a.prepareSpeech = func(config.Speech) (speech.Transport, error) {
|
||||
if failure == "unavailable" {
|
||||
return nil, errors.New("voice unavailable")
|
||||
}
|
||||
return &preparedVoice{}, nil
|
||||
}
|
||||
baseline := a.settings
|
||||
override_read(t, a, override_screen(1, "ninjam"))
|
||||
a.select_override("ninjam")
|
||||
override_read(t, a, override_screen(1, "ninjam"))
|
||||
if a.speechPending {
|
||||
finish_selection(t, a)
|
||||
}
|
||||
if failure != "" {
|
||||
if a.overrideCurrent != nil || !reflect.DeepEqual(a.settings, baseline) || a.saves_unavailable() {
|
||||
t.Fatal("failed profile changed ordinary settings")
|
||||
}
|
||||
continue
|
||||
}
|
||||
override_read(t, a, override_screen(1, "ninjam"))
|
||||
if a.settings.live.Presentation.Speech.Voice != "Application voice" || a.settings.live.Presentation.Speech.Rate != 12 {
|
||||
t.Fatal("profile voice not applied")
|
||||
}
|
||||
a.select_override("default")
|
||||
finish_selection(t, a)
|
||||
if !reflect.DeepEqual(a.settings, baseline) {
|
||||
t.Fatal("default voice not restored")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplicationMenuPreservesSessionTogglesAndLaunchChoices(t *testing.T) {
|
||||
a, _ := persistence_application(t, true)
|
||||
a.settings.saveDraft.Keyboard.Layout = "laptop"
|
||||
a.settings.saveDraft.Presentation.Speech.Driver = "dectalk"
|
||||
a.settings.saveDraft.Presentation.Speech.Voice = "Next launch voice"
|
||||
override_read(t, a, override_screen(1, "ninjam"))
|
||||
a.select_override("ninjam")
|
||||
override_read(t, a, override_screen(1, "ninjam"))
|
||||
a.handle_command(command.Mute)
|
||||
a.handle_command(command.AutoRead)
|
||||
a.open_settings()
|
||||
receive_ui(t, a.ui)
|
||||
for range 100 {
|
||||
if control, ok := a.ui.session.Focused_control(); ok && control.Action == "save" {
|
||||
break
|
||||
}
|
||||
a.ui.handle(interaction.Key{Name: "tab"})
|
||||
}
|
||||
a.ui.handle(interaction.Key{Name: "enter"})
|
||||
finish_persistence(t, a, false)
|
||||
if !a.muted || a.engine.Automatic_reading() || a.settings.live.Keyboard.Layout != "desktop" || a.settings.live.Presentation.Speech.Driver != "speech-dispatcher" || a.settings.live.Presentation.Speech.Voice != "" {
|
||||
t.Fatal("menu changed session toggles or launch-only choices")
|
||||
}
|
||||
data, err := os.ReadFile(a.overrideCurrent.path)
|
||||
if err != nil || strings.Contains(string(data), "enabled") || strings.Contains(string(data), "automatic") || strings.Contains(string(data), "driver") || strings.Contains(string(data), "layout") || strings.Contains(string(data), "voice") {
|
||||
t.Fatal("menu saved inherited/session/launch settings", string(data), err)
|
||||
}
|
||||
a.select_override("default")
|
||||
if a.settings.saveDraft.Keyboard.Layout != "laptop" || a.settings.saveDraft.Presentation.Speech.Driver != "dectalk" {
|
||||
t.Fatal("ordinary next-launch choices lost")
|
||||
}
|
||||
}
|
||||
@@ -121,9 +121,7 @@ func (a *application) handle_command(id command.ID) {
|
||||
case command.ProgressMonitor:
|
||||
a.quick.mark_changed("presentation.progress.enabled")
|
||||
a.settings.live.Presentation.Progress.Enabled = !a.settings.live.Presentation.Progress.Enabled
|
||||
if a.overrideCurrent == nil {
|
||||
a.settings.saveDraft.Presentation.Progress = a.settings.live.Presentation.Progress
|
||||
}
|
||||
a.settings.saveDraft.Presentation.Progress = a.settings.live.Presentation.Progress
|
||||
a.engine.Configure_progress(a.settings.live.Presentation.Progress.Enabled)
|
||||
if a.settings.live.Presentation.Progress.Enabled {
|
||||
a.voice.Say(i18n.T("Progress monitoring enabled"))
|
||||
@@ -176,9 +174,7 @@ func (a *application) review_command(id command.ID) {
|
||||
}
|
||||
switch id {
|
||||
case command.ToggleTracking:
|
||||
if a.overrideCurrent == nil {
|
||||
a.settings.saveDraft.Reading.Mode = a.engine.Tracking_mode()
|
||||
}
|
||||
a.settings.saveDraft.Reading.Mode = a.engine.Tracking_mode()
|
||||
}
|
||||
if spelling := result.Spelling; spelling != nil && !a.temporarySilence {
|
||||
a.voice.say_spelling(*spelling)
|
||||
|
||||
@@ -114,6 +114,8 @@ func (a *application) loop(ticks <-chan time.Time) error {
|
||||
select {
|
||||
case discovery := <-a.speechChoices:
|
||||
a.quick.discovery = discovery
|
||||
case loaded := <-a.overrideLoads:
|
||||
loaded()
|
||||
case result := <-a.speechResults:
|
||||
a.speech_result(result)
|
||||
if len(a.overrides) != 0 || a.overrideCurrent != nil {
|
||||
@@ -126,6 +128,7 @@ func (a *application) loop(ticks <-chan time.Time) error {
|
||||
a.spellcheck_result(result)
|
||||
case err := <-a.saver.results:
|
||||
a.settings_save_result(err)
|
||||
a.overrideDirty = true
|
||||
a.quitting = a.quitAfterSave
|
||||
case result := <-scriptResults:
|
||||
if err := a.poll_input(false); err != nil {
|
||||
|
||||
@@ -27,7 +27,15 @@ import (
|
||||
)
|
||||
|
||||
func (ui *internalUI) open_settings(settings config.Settings, path string, liveQuick ...config.Settings) {
|
||||
generation, err := ui.begin(preferences.MenuTitle)
|
||||
ui.open_settings_context(settings, path, nil, preferences.MenuTitle, "", liveQuick...)
|
||||
}
|
||||
|
||||
func (ui *internalUI) open_application_settings(settings config.Settings, path string, base config.Settings, name string) {
|
||||
ui.open_settings_context(settings, path, &base, i18n.Format("{0} application settings", name), i18n.Format("{0} application settings saved.", name))
|
||||
}
|
||||
|
||||
func (ui *internalUI) open_settings_context(settings config.Settings, path string, applicationBase *config.Settings, title string, savedMessage string, liveQuick ...config.Settings) {
|
||||
generation, err := ui.begin(title)
|
||||
if err != nil {
|
||||
ui.failed("Could not open Fenrir Menu: {0}", err)
|
||||
return
|
||||
@@ -47,7 +55,16 @@ func (ui *internalUI) open_settings(settings config.Settings, path string, liveQ
|
||||
previewChanged := ui.changed
|
||||
speechOverride := ui.speechOverride
|
||||
go func() {
|
||||
doc, err := config.Open_document(path, settings)
|
||||
var doc *config.Document
|
||||
var err error
|
||||
if applicationBase != nil {
|
||||
doc, err = config.Open_application_document(path, *applicationBase)
|
||||
if err == nil {
|
||||
doc.Settings = settings
|
||||
}
|
||||
} else {
|
||||
doc, err = config.Open_document(path, settings)
|
||||
}
|
||||
if err != nil {
|
||||
ui.post(uiUpdate{generation: generation, close: true, speech: i18n.Format("Could not open Fenrir Menu: {0}", err.Error())})
|
||||
return
|
||||
@@ -62,11 +79,20 @@ func (ui *internalUI) open_settings(settings config.Settings, path string, liveQ
|
||||
if ui.discoverChoices != nil {
|
||||
discovery = ui.discoverChoices(doc.Settings)
|
||||
}
|
||||
session, err := preferences.New_session(doc.Settings, doc.Order, discovery.Choices(doc.Settings))
|
||||
var session *interaction.Session
|
||||
if applicationBase != nil {
|
||||
session, err = preferences.New_application_session(doc.Settings, doc.Order, title, discovery.Choices(doc.Settings))
|
||||
} else {
|
||||
session, err = preferences.New_session(doc.Settings, doc.Order, discovery.Choices(doc.Settings))
|
||||
}
|
||||
if err != nil {
|
||||
ui.post(uiUpdate{generation: generation, close: true, speech: i18n.Format("Could not open Fenrir Menu: {0}", err.Error())})
|
||||
return
|
||||
}
|
||||
readSettings := preferences.Settings
|
||||
if applicationBase != nil {
|
||||
readSettings = preferences.Application_settings
|
||||
}
|
||||
previous := doc.Settings
|
||||
catalogs := map[string]preferences.Discovery{previous.Presentation.Speech.Driver: discovery}
|
||||
pending := map[string]bool{}
|
||||
@@ -77,7 +103,7 @@ func (ui *internalUI) open_settings(settings config.Settings, path string, liveQ
|
||||
previewChanged(session)
|
||||
}
|
||||
if ui.cancelTest != nil {
|
||||
draft, err := preferences.Settings(session, doc.Settings)
|
||||
draft, err := readSettings(session, doc.Settings)
|
||||
if err != nil || draft.Presentation.Speech != tested {
|
||||
ui.stop_test()
|
||||
}
|
||||
@@ -121,7 +147,7 @@ func (ui *internalUI) open_settings(settings config.Settings, path string, liveQ
|
||||
var action func(string, *interaction.Session)
|
||||
action = func(name string, session *interaction.Session) {
|
||||
if name == "test-synthesizer" {
|
||||
draft, err := preferences.Settings(session, doc.Settings)
|
||||
draft, err := readSettings(session, doc.Settings)
|
||||
if err != nil {
|
||||
ui.test_failed(err)
|
||||
return
|
||||
@@ -169,7 +195,7 @@ func (ui *internalUI) open_settings(settings config.Settings, path string, liveQ
|
||||
if name != "save" {
|
||||
return
|
||||
}
|
||||
draft, err := preferences.Settings(session, doc.Settings)
|
||||
draft, err := readSettings(session, doc.Settings)
|
||||
if err != nil {
|
||||
ui.failed("Could not save settings: {0}", err)
|
||||
return
|
||||
@@ -184,6 +210,9 @@ func (ui *internalUI) open_settings(settings config.Settings, path string, liveQ
|
||||
ui.onClosed = nil // Successful save commits the form preview.
|
||||
}
|
||||
ui.update(uiUpdate{generation: generation, close: true})
|
||||
if applicationBase != nil {
|
||||
ui.voice.Say(savedMessage)
|
||||
}
|
||||
}) {
|
||||
ui.session = nil
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"git.stormux.org/storm/fenrir/internal/config"
|
||||
"git.stormux.org/storm/fenrir/internal/control"
|
||||
"git.stormux.org/storm/fenrir/internal/i18n"
|
||||
)
|
||||
|
||||
// These operations run only after the shared credential and settings gates.
|
||||
@@ -27,6 +28,9 @@ func (a *application) remote_settings_request(text string, request control.Reque
|
||||
return "Settings are busy; try again after the current operation", false
|
||||
}
|
||||
if verb == "SET" {
|
||||
if err := a.check_application_remote(text); err != nil {
|
||||
return err.Error(), false
|
||||
}
|
||||
updated, err := remote_settings(a.effective_settings(), text)
|
||||
if err != nil {
|
||||
return err.Error(), false
|
||||
@@ -77,8 +81,11 @@ func (a *application) remote_settings_request(text string, request control.Reque
|
||||
}
|
||||
return a.reset_remote_settings(), false
|
||||
}
|
||||
path := a.options.ConfigPath
|
||||
path := a.settings_path()
|
||||
if verb == "SAVEAS" {
|
||||
if a.overrideCurrent != nil {
|
||||
return "SAVEAS is unavailable in application settings", false
|
||||
}
|
||||
// A root TTY reader also admits its active console user for ordinary
|
||||
// commands. That must not grant arbitrary root-owned file writes.
|
||||
if request.UID != 0 && request.UID != os.Geteuid() {
|
||||
@@ -89,7 +96,7 @@ func (a *application) remote_settings_request(text string, request control.Reque
|
||||
if path == "" || strings.ContainsAny(path, "\x00\r\n") {
|
||||
return "A valid settings destination is required", false
|
||||
}
|
||||
if !a.saver.save(a.ctx, path, a.settings_to_save()) {
|
||||
if !a.start_settings_save(path, a.settings_to_save()) {
|
||||
return "Settings are busy; try again after the current operation", false
|
||||
}
|
||||
a.saver.revision = a.settings.remoteRevision
|
||||
@@ -119,6 +126,9 @@ func (a *application) settings_save_result(err error) {
|
||||
response = "Could not save settings: " + err.Error()
|
||||
}
|
||||
remote_reply(reply, response)
|
||||
} else if a.overrideCurrent != nil && err == nil {
|
||||
a.saver.busy = false
|
||||
a.voice.Say(i18n.Format("{0} application settings saved.", i18n.T(a.overrideCurrent.adapter.Name())))
|
||||
} else {
|
||||
a.saver.result(err, a.voice)
|
||||
}
|
||||
@@ -149,10 +159,6 @@ func (a *application) reset_remote_settings() string {
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
if a.overrideCurrent != nil {
|
||||
a.apply_remote_state(updated, false)
|
||||
return ""
|
||||
}
|
||||
a.settings.saveDraft = persisted
|
||||
_, speechOverridden := a.settings.remoteFields["speech#enabled"]
|
||||
a.apply_remote_state(updated, speechOverridden)
|
||||
@@ -203,3 +209,18 @@ func copy_remote_settings(target *config.Settings, source config.Settings, field
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Screen routing is global and has no application-profile counterpart.
|
||||
func (a *application) check_application_remote(text string) error {
|
||||
if a.overrideCurrent == nil {
|
||||
return nil
|
||||
}
|
||||
_, parameters, _ := strings.Cut(text, " ")
|
||||
for _, assignment := range remote_assignments(parameters) {
|
||||
path, _, _ := strings.Cut(assignment, "=")
|
||||
if strings.EqualFold(strings.TrimSpace(path), "screen#ignore_screen") {
|
||||
return fmt.Errorf("screen#ignore_screen is unavailable in application settings")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -139,6 +139,10 @@ func (a *application) open_settings() {
|
||||
selected := a.settings.live.Presentation.Speech
|
||||
a.ui.speechOverride = &selected
|
||||
}
|
||||
if selected := a.overrideCurrent; selected != nil {
|
||||
a.ui.open_application_settings(a.settings_to_save(), selected.path, selected.base, i18n.T(selected.adapter.Name()))
|
||||
return
|
||||
}
|
||||
if a.quick.changed {
|
||||
a.ui.open_settings(a.settings.live, a.options.ConfigPath, a.settings.live)
|
||||
} else {
|
||||
@@ -155,7 +159,7 @@ func (a *application) save_settings() {
|
||||
a.voice.Say(i18n.T("Failed to save settings."))
|
||||
return
|
||||
}
|
||||
if a.saver.save(a.ctx, a.options.ConfigPath, a.settings_to_save()) {
|
||||
if a.start_settings_save(a.settings_path(), a.settings_to_save()) {
|
||||
a.saver.revision = a.settings.remoteRevision
|
||||
}
|
||||
}
|
||||
@@ -172,6 +176,7 @@ func (a *application) save_menu_settings(doc *config.Document, draft config.Sett
|
||||
a.ui.failed("Could not save settings: {0}", fmt.Errorf("speech selection in progress"))
|
||||
return false
|
||||
}
|
||||
previousDraft := a.settings.saveDraft
|
||||
speechApplied := a.settings.speechAvailable && draft.Presentation.Speech.Driver == a.settings.live.Presentation.Speech.Driver && !same_speech_selection(draft.Presentation.Speech, a.settings.saveDraft.Presentation.Speech)
|
||||
if !a.saver.start(a.ctx, draft, func() error {
|
||||
var prepared speech.Transport
|
||||
@@ -220,7 +225,24 @@ func (a *application) save_menu_settings(doc *config.Document, draft config.Sett
|
||||
if a.quick.fields["presentation.progress.enabled"] > quickRevision {
|
||||
merged.Presentation.Progress = a.settings.live.Presentation.Progress
|
||||
}
|
||||
a.apply_saved_settings(merged, speechApplied && err == nil)
|
||||
if a.overrideCurrent != nil {
|
||||
live := config.Apply_application_settings(a.effective_settings(), merged)
|
||||
if merged.Presentation.Speech.Enabled == previousDraft.Presentation.Speech.Enabled {
|
||||
live.Presentation.Speech.Enabled = !a.muted
|
||||
}
|
||||
if merged.Reading.Automatic == previousDraft.Reading.Automatic {
|
||||
live.Reading.Automatic = a.engine.Automatic_reading()
|
||||
}
|
||||
if !speechApplied || err != nil {
|
||||
copy_speech_selection(&live.Presentation.Speech, a.settings.live.Presentation.Speech)
|
||||
}
|
||||
a.apply_remote_state(live, false)
|
||||
a.configure_reader()
|
||||
a.settings.saveDraft = merged
|
||||
a.quick.selectionChanged = false
|
||||
} else {
|
||||
a.apply_saved_settings(merged, speechApplied && err == nil)
|
||||
}
|
||||
a.settings.resetBase = draft // RESET reflects disk, not later overrides.
|
||||
a.quick.changed = newerQuick
|
||||
}
|
||||
@@ -249,6 +271,9 @@ func (a *application) effective_settings() config.Settings {
|
||||
// Remote assignments are validated against effective session state, then
|
||||
// separately overlaid on the save draft so unrelated next-launch choices survive.
|
||||
func (a *application) apply_remote_settings(text string) string {
|
||||
if err := a.check_application_remote(text); err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
updated, err := remote_settings(a.effective_settings(), text)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
@@ -256,10 +281,6 @@ func (a *application) apply_remote_settings(text string) string {
|
||||
if updated.Presentation.Speech.Enabled && !a.settings.speechAvailable {
|
||||
return "Speech was disabled at startup; enable it in settings and relaunch"
|
||||
}
|
||||
if a.overrideCurrent != nil {
|
||||
a.apply_remote_state(updated, false)
|
||||
return ""
|
||||
}
|
||||
// Apply only the requested assignments to the persistence
|
||||
// draft, retaining unrelated next-launch menu settings.
|
||||
draftBase := a.settings.saveDraft
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
)
|
||||
|
||||
// Application_setting admits only settings which can be applied and restored
|
||||
// without changing devices, routing, storage, or other process-wide services.
|
||||
func Application_setting(path string) bool {
|
||||
switch path {
|
||||
case "presentation.speech.enabled", "presentation.speech.rate", "presentation.speech.pitch", "presentation.speech.volume", "presentation.speech.module", "presentation.speech.language", "presentation.speech.voice",
|
||||
"presentation.sound.enabled", "presentation.sound.volume", "presentation.progress.enabled",
|
||||
"presentation.time_and_date.time_format", "presentation.time_and_date.date_format",
|
||||
"keyboard.character_echo", "keyboard.word_echo", "keyboard.echo_on_capslock":
|
||||
return true
|
||||
case "reading.automatic", "reading.cursor_echo", "reading.mode", "reading.punctuation_level",
|
||||
"reading.raise_pitch_for_capitals", "reading.beep_for_capitals", "reading.say_cap_for_capitals",
|
||||
"reading.indentation", "reading.attributes", "reading.attribute_format", "reading.spellcheck",
|
||||
"reading.spellcheck_read_word", "reading.spellcheck_say_misspelled", "reading.spellcheck_sound",
|
||||
"reading.emoticons", "reading.repeated_symbols", "reading.repeated_symbols_min":
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Open_application_document overlays a sparse, current-schema profile on the
|
||||
// application's inherited settings. Missing files inherit everything.
|
||||
func Open_application_document(path string, base Settings) (*Document, error) {
|
||||
doc := &Document{Settings: base, path: path, applicationBase: &base, applicationFields: make(map[string]bool)}
|
||||
data, err := read_document(path)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return doc, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var fields map[string]any
|
||||
if err := toml.Unmarshal(data, &fields); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var collect func(map[string]any, string) error
|
||||
collect = func(fields map[string]any, prefix string) error {
|
||||
for name, value := range fields {
|
||||
path := prefix + name
|
||||
if table, ok := value.(map[string]any); ok {
|
||||
if err := collect(table, path+"."); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !Application_setting(path) {
|
||||
return fmt.Errorf("setting %s is not supported in application profiles", path)
|
||||
}
|
||||
doc.applicationFields[path] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := collect(fields, ""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := toml.NewDecoder(bytes.NewReader(data)).DisallowUnknownFields().Decode(&doc.Settings); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := doc.Settings.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
doc.original, doc.existed = data, true
|
||||
return doc, nil
|
||||
}
|
||||
|
||||
// Apply_application preserves inherited session state for every omitted field.
|
||||
func (d *Document) Apply_application(base Settings) Settings {
|
||||
for path := range d.applicationFields {
|
||||
setting_value(reflect.ValueOf(&base).Elem(), path).Set(setting_value(reflect.ValueOf(d.Settings), path))
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
func (d *Document) save_application(settings Settings) error {
|
||||
fields := make(map[string]any)
|
||||
selectionChanged := false
|
||||
for _, path := range []string{"presentation.speech.module", "presentation.speech.language", "presentation.speech.voice"} {
|
||||
selectionChanged = selectionChanged || d.applicationFields[path] || !reflect.DeepEqual(setting_value(reflect.ValueOf(settings), path).Interface(), setting_value(reflect.ValueOf(*d.applicationBase), path).Interface())
|
||||
}
|
||||
var visit func(reflect.Value, reflect.Value, string)
|
||||
visit = func(value, base reflect.Value, prefix string) {
|
||||
for index := range value.NumField() {
|
||||
path := prefix + value.Type().Field(index).Tag.Get("toml")
|
||||
child, inherited := value.Field(index), base.Field(index)
|
||||
if child.Kind() == reflect.Struct {
|
||||
visit(child, inherited, path+".")
|
||||
continue
|
||||
}
|
||||
speechSelection := selectionChanged && (path == "presentation.speech.module" || path == "presentation.speech.language" || path == "presentation.speech.voice")
|
||||
if !Application_setting(path) || (!speechSelection && !d.applicationFields[path] && reflect.DeepEqual(child.Interface(), inherited.Interface())) {
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(path, ".")
|
||||
table := fields
|
||||
for _, part := range parts[:len(parts)-1] {
|
||||
if table[part] == nil {
|
||||
table[part] = make(map[string]any)
|
||||
}
|
||||
table = table[part].(map[string]any)
|
||||
}
|
||||
table[parts[len(parts)-1]] = child.Interface()
|
||||
}
|
||||
}
|
||||
visit(reflect.ValueOf(settings), reflect.ValueOf(*d.applicationBase), "")
|
||||
data, err := toml.Marshal(fields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return save_data(d.path, data)
|
||||
}
|
||||
|
||||
// Apply_application_settings copies the supported live settings only. Inherited
|
||||
// next-launch choices must never replace the running backend or layout.
|
||||
func Apply_application_settings(base Settings, source Settings) Settings {
|
||||
for _, definition := range Definitions() {
|
||||
if Application_setting(definition.Path) {
|
||||
if err := definition.Set(&base, definition.Value(source)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
base.Presentation.Progress = source.Presentation.Progress
|
||||
return base
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestApplicationDocumentSparseInheritanceAndExplicitValues(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "applications", "ninjam.toml")
|
||||
base := Defaults()
|
||||
doc, err := Open_application_document(path, base)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
saved := base
|
||||
saved.Presentation.Speech.Rate = 12
|
||||
saved.Keyboard.WordEcho = true
|
||||
if err := doc.Save(saved); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
data, _ := os.ReadFile(path)
|
||||
if strings.Contains(string(data), "driver") || strings.Contains(string(data), "automatic") || strings.Contains(string(data), "version") {
|
||||
t.Fatal("inherited values copied", string(data))
|
||||
}
|
||||
info, _ := os.Stat(path)
|
||||
if info.Mode().Perm() != 0600 {
|
||||
t.Fatal(info.Mode())
|
||||
}
|
||||
// A new ordinary voice is inherited; explicitly saved rate/echo survive.
|
||||
base.Presentation.Speech.Voice = "New default"
|
||||
base.Presentation.Speech.Rate = 12
|
||||
doc, err = Open_application_document(path, base)
|
||||
if err != nil || doc.Settings.Presentation.Speech.Voice != "New default" || !doc.Settings.Keyboard.WordEcho {
|
||||
t.Fatal(doc, err)
|
||||
}
|
||||
if err := doc.Save(doc.Settings); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
base.Presentation.Speech.Rate = 60
|
||||
doc, err = Open_application_document(path, base)
|
||||
if err != nil || doc.Settings.Presentation.Speech.Rate != 12 {
|
||||
t.Fatal("explicit value equal to previous base lost", doc, err)
|
||||
}
|
||||
session := base
|
||||
session.Presentation.Speech.Enabled = false
|
||||
session.Reading.Automatic = false
|
||||
effective := doc.Apply_application(session)
|
||||
if effective.Presentation.Speech.Enabled || effective.Reading.Automatic || effective.Presentation.Speech.Rate != 12 {
|
||||
t.Fatal("session state overwritten", effective)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplicationDocumentRejectsInvalidProfilesAndExternalChanges(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "profile.toml")
|
||||
for _, data := range []string{
|
||||
"[presentation.speech]\nrate = 101\n",
|
||||
"[presentation.speech]\nrate = 'fast'\n",
|
||||
"[presentation.speech]\ndriver = 'dectalk'\n",
|
||||
"[keyboard]\nlayout = 'laptop'\n",
|
||||
"[screen]\nignored_vts = [1]\n",
|
||||
"[reading]\nunknown = true\n",
|
||||
"[reading]\nmode = 'invalid'\n",
|
||||
"[broken",
|
||||
} {
|
||||
if err := os.WriteFile(path, []byte(data), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := Open_application_document(path, Defaults()); err == nil {
|
||||
t.Fatal("invalid profile accepted", data)
|
||||
}
|
||||
}
|
||||
if err := os.WriteFile(path, []byte("[presentation.speech]\nrate = 10\n"), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
doc, err := Open_application_document(path, Defaults())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
changed := []byte("[presentation.speech]\nrate = 20\n")
|
||||
if err := os.WriteFile(path, changed, 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := doc.Save(doc.Settings); err == nil {
|
||||
t.Fatal("external edit overwritten")
|
||||
}
|
||||
data, _ := os.ReadFile(path)
|
||||
if string(data) != string(changed) {
|
||||
t.Fatal(string(data))
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplicationVoiceSelectionSavedTogether(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "profile.toml")
|
||||
base := Defaults()
|
||||
base.Presentation.Speech.Module = "original-module"
|
||||
base.Presentation.Speech.Language = "en"
|
||||
doc, err := Open_application_document(path, base)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
chosen := base
|
||||
chosen.Presentation.Speech.Voice = "Application voice"
|
||||
if err := doc.Save(chosen); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
base.Presentation.Speech.Module = "different-module"
|
||||
base.Presentation.Speech.Language = "fr"
|
||||
doc, err = Open_application_document(path, base)
|
||||
if err != nil || doc.Settings.Presentation.Speech.Module != "original-module" || doc.Settings.Presentation.Speech.Language != "en" || doc.Settings.Presentation.Speech.Voice != "Application voice" {
|
||||
t.Fatal("coupled voice selection changed", doc, err)
|
||||
}
|
||||
}
|
||||
@@ -34,11 +34,13 @@ import (
|
||||
// Document retains file identity and table order for an isolated edit. Version
|
||||
// is metadata, not a UI tab. Omitted tables follow in schema order.
|
||||
type Document struct {
|
||||
Settings Settings
|
||||
Order []string
|
||||
path string
|
||||
original []byte
|
||||
existed bool
|
||||
Settings Settings
|
||||
Order []string
|
||||
path string
|
||||
original []byte
|
||||
existed bool
|
||||
applicationBase *Settings
|
||||
applicationFields map[string]bool
|
||||
}
|
||||
|
||||
func read_document(path string) ([]byte, error) {
|
||||
@@ -156,6 +158,9 @@ func (d *Document) Save(settings Settings) error {
|
||||
if err := settings.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if d.applicationBase != nil {
|
||||
return d.save_application(settings)
|
||||
}
|
||||
// The same schema drives persistence and the form, including nested groups.
|
||||
var output bytes.Buffer
|
||||
fmt.Fprintf(&output, "version = %d\n", settings.Version)
|
||||
|
||||
@@ -67,11 +67,11 @@ msgstr "Wütend"
|
||||
msgid "Apple"
|
||||
msgstr "Apfel"
|
||||
|
||||
#: internal/app/application_overrides.go:237
|
||||
#: internal/app/application_overrides.go:325
|
||||
msgid "Application"
|
||||
msgstr "Anwendung"
|
||||
|
||||
#: internal/app/application_overrides.go:236
|
||||
#: internal/app/application_overrides.go:324
|
||||
msgid "Applications"
|
||||
msgstr "Anwendungen"
|
||||
|
||||
@@ -163,8 +163,8 @@ msgstr "Schmetterling"
|
||||
msgid "Canada"
|
||||
msgstr "Kanada"
|
||||
|
||||
#: internal/app/application_overrides.go:238
|
||||
#: internal/preferences/settings.go:79
|
||||
#: internal/app/application_overrides.go:326
|
||||
#: internal/preferences/settings.go:93
|
||||
msgid "Cancel"
|
||||
msgstr "Abbrechen"
|
||||
|
||||
@@ -256,7 +256,7 @@ msgstr "Live-Bereich 9 löschen"
|
||||
msgid "Clipboard"
|
||||
msgstr "Zwischenablage"
|
||||
|
||||
#: internal/app/application.go:142 internal/app/application.go:145
|
||||
#: internal/app/application.go:144 internal/app/application.go:147
|
||||
#: internal/app/clipboard.go:218 internal/app/clipboard.go:374
|
||||
msgid "Clipboard import failed"
|
||||
msgstr "Import aus der Zwischenablage fehlgeschlagen"
|
||||
@@ -289,8 +289,13 @@ msgstr "Cool"
|
||||
msgid "Could not check spelling: {0}"
|
||||
msgstr "Rechtschreibung konnte nicht geprüft werden: {0}"
|
||||
|
||||
#: internal/app/application_overrides.go:241
|
||||
#: internal/app/application_overrides.go:245
|
||||
#: internal/app/application_overrides.go:278
|
||||
#: internal/app/application_overrides.go:83
|
||||
msgid "Could not load application settings: {0}"
|
||||
msgstr "Anwendungseinstellungen konnten nicht geladen werden: {0}"
|
||||
|
||||
#: internal/app/application_overrides.go:329
|
||||
#: internal/app/application_overrides.go:333
|
||||
msgid "Could not open Applications: {0}"
|
||||
msgstr "Anwendungen konnten nicht geöffnet werden: {0}"
|
||||
|
||||
@@ -298,8 +303,8 @@ msgstr "Anwendungen konnten nicht geöffnet werden: {0}"
|
||||
msgid "Could not open Emoji Menu: {0}"
|
||||
msgstr "Emoji-Menü konnte nicht geöffnet werden: {0}"
|
||||
|
||||
#: internal/app/fenrir_menu.go:32 internal/app/fenrir_menu.go:52
|
||||
#: internal/app/fenrir_menu.go:67 internal/app/runtime_settings.go:133
|
||||
#: internal/app/fenrir_menu.go:40 internal/app/fenrir_menu.go:69
|
||||
#: internal/app/fenrir_menu.go:89 internal/app/runtime_settings.go:133
|
||||
msgid "Could not open Fenrir Menu: {0}"
|
||||
msgstr "Fenrir-Menü konnte nicht geöffnet werden: {0}"
|
||||
|
||||
@@ -307,8 +312,8 @@ msgstr "Fenrir-Menü konnte nicht geöffnet werden: {0}"
|
||||
msgid "Could not open Spellcheck Menu: {0}"
|
||||
msgstr "Rechtschreibmenü konnte nicht geöffnet werden: {0}"
|
||||
|
||||
#: internal/app/application_overrides.go:104
|
||||
#: internal/app/application_overrides.go:208
|
||||
#: internal/app/application_overrides.go:161
|
||||
#: internal/app/application_overrides.go:296
|
||||
msgid "Could not restore application settings: {0}"
|
||||
msgstr "Anwendungseinstellungen konnten nicht wiederhergestellt werden: {0}"
|
||||
|
||||
@@ -316,8 +321,8 @@ msgstr "Anwendungseinstellungen konnten nicht wiederhergestellt werden: {0}"
|
||||
msgid "Could not save exceptions: {0}"
|
||||
msgstr "Ausnahmen konnten nicht gespeichert werden: {0}"
|
||||
|
||||
#: internal/app/fenrir_menu.go:174 internal/app/fenrir_menu.go:180
|
||||
#: internal/app/runtime_settings.go:172 internal/app/runtime_settings.go:199
|
||||
#: internal/app/fenrir_menu.go:200 internal/app/fenrir_menu.go:206
|
||||
#: internal/app/runtime_settings.go:176 internal/app/runtime_settings.go:204
|
||||
msgid "Could not save settings: {0}"
|
||||
msgstr "Einstellungen konnten nicht gespeichert werden: {0}"
|
||||
|
||||
@@ -337,12 +342,12 @@ msgstr "Dolch"
|
||||
msgid "Date format"
|
||||
msgstr "Datumsformat"
|
||||
|
||||
#: internal/app/application_overrides.go:230
|
||||
#: internal/app/application_overrides.go:318
|
||||
msgid "Default"
|
||||
msgstr "Standard"
|
||||
|
||||
#: internal/app/application_overrides.go:129
|
||||
#: internal/app/application_overrides.go:182
|
||||
#: internal/app/application_overrides.go:186
|
||||
#: internal/app/application_overrides.go:242
|
||||
msgid "Default settings restored"
|
||||
msgstr "Standardeinstellungen wiederhergestellt"
|
||||
|
||||
@@ -445,7 +450,7 @@ msgstr ""
|
||||
msgid "Face with symbols"
|
||||
msgstr "Gesicht mit Symbolen"
|
||||
|
||||
#: internal/app/quick_settings.go:295 internal/app/runtime_settings.go:155
|
||||
#: internal/app/quick_settings.go:295 internal/app/runtime_settings.go:159
|
||||
msgid "Failed to save settings."
|
||||
msgstr "Einstellungen konnten nicht gespeichert werden."
|
||||
|
||||
@@ -489,7 +494,7 @@ msgstr "Gefaltete Hände"
|
||||
msgid "Food"
|
||||
msgstr "Essen"
|
||||
|
||||
#: internal/app/commands.go:145
|
||||
#: internal/app/commands.go:143
|
||||
msgid "Forward next keypress"
|
||||
msgstr "Nächste Taste durchreichen"
|
||||
|
||||
@@ -726,7 +731,7 @@ msgstr "Übelkeit"
|
||||
msgid "Netherlands"
|
||||
msgstr "Niederlande"
|
||||
|
||||
#: internal/app/application_overrides.go:230
|
||||
#: internal/app/application_overrides.go:318
|
||||
msgid "NetworkManager"
|
||||
msgstr "NetworkManager"
|
||||
|
||||
@@ -734,7 +739,7 @@ msgstr "NetworkManager"
|
||||
msgid "New channel"
|
||||
msgstr "Neuer Kanal"
|
||||
|
||||
#: internal/app/application_overrides.go:230
|
||||
#: internal/app/application_overrides.go:318
|
||||
msgid "Ninjam"
|
||||
msgstr "Ninjam"
|
||||
|
||||
@@ -787,7 +792,7 @@ msgstr "Nicht im Lesemodus"
|
||||
msgid "Not in window Mode"
|
||||
msgstr "Nicht im Fenstermodus"
|
||||
|
||||
#: internal/preferences/settings.go:79
|
||||
#: internal/preferences/settings.go:93
|
||||
msgid "OK"
|
||||
msgstr "OK"
|
||||
|
||||
@@ -850,11 +855,11 @@ msgstr ""
|
||||
"Gibt den aktuell ausgewählten Text aus, der in die Zwischenablage kopiert "
|
||||
"wird"
|
||||
|
||||
#: internal/app/commands.go:131
|
||||
#: internal/app/commands.go:129
|
||||
msgid "Progress monitoring disabled"
|
||||
msgstr "Fortschrittsüberwachung deaktiviert"
|
||||
|
||||
#: internal/app/commands.go:129
|
||||
#: internal/app/commands.go:127
|
||||
msgid "Progress monitoring enabled"
|
||||
msgstr "Fortschrittsüberwachung aktiviert"
|
||||
|
||||
@@ -1006,7 +1011,7 @@ msgstr "Traurig"
|
||||
msgid "Santa"
|
||||
msgstr "Weihnachtsmann"
|
||||
|
||||
#: internal/app/runtime_settings.go:151 internal/app/runtime_settings.go:168
|
||||
#: internal/app/runtime_settings.go:155 internal/app/runtime_settings.go:172
|
||||
msgid "Save unavailable."
|
||||
msgstr "Speichern nicht verfügbar."
|
||||
|
||||
@@ -1030,7 +1035,7 @@ msgstr "„Falsch geschrieben“ ansagen"
|
||||
msgid "Screaming"
|
||||
msgstr "Schreiend"
|
||||
|
||||
#: internal/app/commands.go:154 internal/app/scripts.go
|
||||
#: internal/app/commands.go:152 internal/app/scripts.go
|
||||
msgid "Script failed"
|
||||
msgstr "Skript fehlgeschlagen"
|
||||
|
||||
@@ -1078,8 +1083,8 @@ msgstr "Live-Bereich aus einer oder zwei Markierungen festlegen"
|
||||
msgid "Set window begin and end marks"
|
||||
msgstr "Setze Fensteranfangs- und -endmarken"
|
||||
|
||||
#: internal/app/application_overrides.go:114
|
||||
#: internal/app/application_overrides.go:227 internal/app/commands.go:84
|
||||
#: internal/app/application_overrides.go:171
|
||||
#: internal/app/application_overrides.go:315 internal/app/commands.go:84
|
||||
msgid "Settings are busy"
|
||||
msgstr "Einstellungen sind beschäftigt"
|
||||
|
||||
@@ -1227,11 +1232,11 @@ msgstr "Symbole"
|
||||
msgid "Taco"
|
||||
msgstr "Taco"
|
||||
|
||||
#: internal/preferences/settings.go:72
|
||||
#: internal/preferences/settings.go:83
|
||||
msgid "Test synthesizer"
|
||||
msgstr "Sprachsynthesizer testen"
|
||||
|
||||
#: internal/app/fenrir_menu.go:139
|
||||
#: internal/app/fenrir_menu.go:165
|
||||
msgid "Testing voice"
|
||||
msgstr "Stimme wird getestet"
|
||||
|
||||
@@ -2187,7 +2192,7 @@ msgstr "Tonhöhe"
|
||||
msgid "speech rate"
|
||||
msgstr "Sprechgeschwindigkeit"
|
||||
|
||||
#: internal/app/commands.go:140
|
||||
#: internal/app/commands.go:138
|
||||
msgid "speech temporary disabled"
|
||||
msgstr "Sprachausgabe vorübergehend ausgeschaltet"
|
||||
|
||||
@@ -2282,10 +2287,18 @@ msgstr "Zwinkern"
|
||||
msgid "{0} added to dictionary"
|
||||
msgstr "{0} zum Wörterbuch hinzugefügt"
|
||||
|
||||
#: internal/app/application_overrides.go:147
|
||||
#: internal/app/application_overrides.go:207
|
||||
msgid "{0} application override selected"
|
||||
msgstr "Anwendungseinstellungen für {0} ausgewählt"
|
||||
|
||||
#: internal/app/fenrir_menu.go:34
|
||||
msgid "{0} application settings"
|
||||
msgstr "Anwendungseinstellungen für {0}"
|
||||
|
||||
#: internal/app/fenrir_menu.go:34 internal/app/remote_persistence.go:131
|
||||
msgid "{0} application settings saved."
|
||||
msgstr "Anwendungseinstellungen für {0} gespeichert."
|
||||
|
||||
#: internal/interaction/presentation.go:53
|
||||
msgid "{0} button"
|
||||
msgstr "{0} Schaltfläche"
|
||||
|
||||
@@ -67,11 +67,11 @@ msgstr "Enfadado"
|
||||
msgid "Apple"
|
||||
msgstr "Manzana"
|
||||
|
||||
#: internal/app/application_overrides.go:237
|
||||
#: internal/app/application_overrides.go:325
|
||||
msgid "Application"
|
||||
msgstr "Aplicación"
|
||||
|
||||
#: internal/app/application_overrides.go:236
|
||||
#: internal/app/application_overrides.go:324
|
||||
msgid "Applications"
|
||||
msgstr "Aplicaciones"
|
||||
|
||||
@@ -163,8 +163,8 @@ msgstr "Mariposa"
|
||||
msgid "Canada"
|
||||
msgstr "Canadá"
|
||||
|
||||
#: internal/app/application_overrides.go:238
|
||||
#: internal/preferences/settings.go:79
|
||||
#: internal/app/application_overrides.go:326
|
||||
#: internal/preferences/settings.go:93
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
|
||||
@@ -256,7 +256,7 @@ msgstr "Borrar la región dinámica 9"
|
||||
msgid "Clipboard"
|
||||
msgstr "Portapapeles"
|
||||
|
||||
#: internal/app/application.go:142 internal/app/application.go:145
|
||||
#: internal/app/application.go:144 internal/app/application.go:147
|
||||
#: internal/app/clipboard.go:218 internal/app/clipboard.go:374
|
||||
msgid "Clipboard import failed"
|
||||
msgstr "Error al importar el portapapeles"
|
||||
@@ -289,8 +289,13 @@ msgstr "Genial"
|
||||
msgid "Could not check spelling: {0}"
|
||||
msgstr "No se pudo revisar la ortografía: {0}"
|
||||
|
||||
#: internal/app/application_overrides.go:241
|
||||
#: internal/app/application_overrides.go:245
|
||||
#: internal/app/application_overrides.go:278
|
||||
#: internal/app/application_overrides.go:83
|
||||
msgid "Could not load application settings: {0}"
|
||||
msgstr "No se pudo cargar la configuración de la aplicación: {0}"
|
||||
|
||||
#: internal/app/application_overrides.go:329
|
||||
#: internal/app/application_overrides.go:333
|
||||
msgid "Could not open Applications: {0}"
|
||||
msgstr "No se pudo abrir Aplicaciones: {0}"
|
||||
|
||||
@@ -298,8 +303,8 @@ msgstr "No se pudo abrir Aplicaciones: {0}"
|
||||
msgid "Could not open Emoji Menu: {0}"
|
||||
msgstr "No se pudo abrir el menú de emojis: {0}"
|
||||
|
||||
#: internal/app/fenrir_menu.go:32 internal/app/fenrir_menu.go:52
|
||||
#: internal/app/fenrir_menu.go:67 internal/app/runtime_settings.go:133
|
||||
#: internal/app/fenrir_menu.go:40 internal/app/fenrir_menu.go:69
|
||||
#: internal/app/fenrir_menu.go:89 internal/app/runtime_settings.go:133
|
||||
msgid "Could not open Fenrir Menu: {0}"
|
||||
msgstr "No se pudo abrir el menú de Fenrir: {0}"
|
||||
|
||||
@@ -307,8 +312,8 @@ msgstr "No se pudo abrir el menú de Fenrir: {0}"
|
||||
msgid "Could not open Spellcheck Menu: {0}"
|
||||
msgstr "No se pudo abrir el menú de ortografía: {0}"
|
||||
|
||||
#: internal/app/application_overrides.go:104
|
||||
#: internal/app/application_overrides.go:208
|
||||
#: internal/app/application_overrides.go:161
|
||||
#: internal/app/application_overrides.go:296
|
||||
msgid "Could not restore application settings: {0}"
|
||||
msgstr "No se pudo restaurar la configuración de la aplicación: {0}"
|
||||
|
||||
@@ -316,8 +321,8 @@ msgstr "No se pudo restaurar la configuración de la aplicación: {0}"
|
||||
msgid "Could not save exceptions: {0}"
|
||||
msgstr "No se pudieron guardar las excepciones: {0}"
|
||||
|
||||
#: internal/app/fenrir_menu.go:174 internal/app/fenrir_menu.go:180
|
||||
#: internal/app/runtime_settings.go:172 internal/app/runtime_settings.go:199
|
||||
#: internal/app/fenrir_menu.go:200 internal/app/fenrir_menu.go:206
|
||||
#: internal/app/runtime_settings.go:176 internal/app/runtime_settings.go:204
|
||||
msgid "Could not save settings: {0}"
|
||||
msgstr "No se pudo guardar la configuración: {0}"
|
||||
|
||||
@@ -337,12 +342,12 @@ msgstr "Daga"
|
||||
msgid "Date format"
|
||||
msgstr "Formato de fecha"
|
||||
|
||||
#: internal/app/application_overrides.go:230
|
||||
#: internal/app/application_overrides.go:318
|
||||
msgid "Default"
|
||||
msgstr "Predeterminado"
|
||||
|
||||
#: internal/app/application_overrides.go:129
|
||||
#: internal/app/application_overrides.go:182
|
||||
#: internal/app/application_overrides.go:186
|
||||
#: internal/app/application_overrides.go:242
|
||||
msgid "Default settings restored"
|
||||
msgstr "Configuración predeterminada restaurada"
|
||||
|
||||
@@ -446,7 +451,7 @@ msgstr ""
|
||||
msgid "Face with symbols"
|
||||
msgstr "Cara con símbolos"
|
||||
|
||||
#: internal/app/quick_settings.go:295 internal/app/runtime_settings.go:155
|
||||
#: internal/app/quick_settings.go:295 internal/app/runtime_settings.go:159
|
||||
msgid "Failed to save settings."
|
||||
msgstr "No se pudo guardar la configuración."
|
||||
|
||||
@@ -490,7 +495,7 @@ msgstr "Manos juntas"
|
||||
msgid "Food"
|
||||
msgstr "Comida"
|
||||
|
||||
#: internal/app/commands.go:145
|
||||
#: internal/app/commands.go:143
|
||||
msgid "Forward next keypress"
|
||||
msgstr "Enviar la siguiente tecla"
|
||||
|
||||
@@ -734,7 +739,7 @@ msgstr "Náuseas"
|
||||
msgid "Netherlands"
|
||||
msgstr "Países Bajos"
|
||||
|
||||
#: internal/app/application_overrides.go:230
|
||||
#: internal/app/application_overrides.go:318
|
||||
msgid "NetworkManager"
|
||||
msgstr "NetworkManager"
|
||||
|
||||
@@ -742,7 +747,7 @@ msgstr "NetworkManager"
|
||||
msgid "New channel"
|
||||
msgstr "Nuevo canal"
|
||||
|
||||
#: internal/app/application_overrides.go:230
|
||||
#: internal/app/application_overrides.go:318
|
||||
msgid "Ninjam"
|
||||
msgstr "Ninjam"
|
||||
|
||||
@@ -796,7 +801,7 @@ msgstr "Fuera del modo de revisión"
|
||||
msgid "Not in window Mode"
|
||||
msgstr "No está en modo ventana"
|
||||
|
||||
#: internal/preferences/settings.go:79
|
||||
#: internal/preferences/settings.go:93
|
||||
msgid "OK"
|
||||
msgstr "Aceptar"
|
||||
|
||||
@@ -857,11 +862,11 @@ msgid ""
|
||||
"Presents the currently selected text that will be copied to the clipboard"
|
||||
msgstr "Presenta el texto seleccionado que se copiará al portapapeles"
|
||||
|
||||
#: internal/app/commands.go:131
|
||||
#: internal/app/commands.go:129
|
||||
msgid "Progress monitoring disabled"
|
||||
msgstr "Seguimiento del progreso desactivado"
|
||||
|
||||
#: internal/app/commands.go:129
|
||||
#: internal/app/commands.go:127
|
||||
msgid "Progress monitoring enabled"
|
||||
msgstr "Seguimiento del progreso activado"
|
||||
|
||||
@@ -1013,7 +1018,7 @@ msgstr "Triste"
|
||||
msgid "Santa"
|
||||
msgstr "Papá Noel"
|
||||
|
||||
#: internal/app/runtime_settings.go:151 internal/app/runtime_settings.go:168
|
||||
#: internal/app/runtime_settings.go:155 internal/app/runtime_settings.go:172
|
||||
msgid "Save unavailable."
|
||||
msgstr "Guardado no disponible."
|
||||
|
||||
@@ -1037,7 +1042,7 @@ msgstr "Decir «mal escrita»"
|
||||
msgid "Screaming"
|
||||
msgstr "Gritando"
|
||||
|
||||
#: internal/app/commands.go:154 internal/app/scripts.go
|
||||
#: internal/app/commands.go:152 internal/app/scripts.go
|
||||
msgid "Script failed"
|
||||
msgstr "El script falló"
|
||||
|
||||
@@ -1085,8 +1090,8 @@ msgstr "Definir una región dinámica a partir de una o dos marcas"
|
||||
msgid "Set window begin and end marks"
|
||||
msgstr "Establece una marca de principio y otra para el final de la ventana"
|
||||
|
||||
#: internal/app/application_overrides.go:114
|
||||
#: internal/app/application_overrides.go:227 internal/app/commands.go:84
|
||||
#: internal/app/application_overrides.go:171
|
||||
#: internal/app/application_overrides.go:315 internal/app/commands.go:84
|
||||
msgid "Settings are busy"
|
||||
msgstr "La configuración está ocupada"
|
||||
|
||||
@@ -1234,11 +1239,11 @@ msgstr "Símbolos"
|
||||
msgid "Taco"
|
||||
msgstr "Taco"
|
||||
|
||||
#: internal/preferences/settings.go:72
|
||||
#: internal/preferences/settings.go:83
|
||||
msgid "Test synthesizer"
|
||||
msgstr "Probar sintetizador"
|
||||
|
||||
#: internal/app/fenrir_menu.go:139
|
||||
#: internal/app/fenrir_menu.go:165
|
||||
msgid "Testing voice"
|
||||
msgstr "Probando la voz"
|
||||
|
||||
@@ -2193,7 +2198,7 @@ msgstr "tono de voz"
|
||||
msgid "speech rate"
|
||||
msgstr "velocidad de voz"
|
||||
|
||||
#: internal/app/commands.go:140
|
||||
#: internal/app/commands.go:138
|
||||
msgid "speech temporary disabled"
|
||||
msgstr "Habla desactivada temporalmente"
|
||||
|
||||
@@ -2288,10 +2293,18 @@ msgstr "guiño"
|
||||
msgid "{0} added to dictionary"
|
||||
msgstr "{0} añadido al diccionario"
|
||||
|
||||
#: internal/app/application_overrides.go:147
|
||||
#: internal/app/application_overrides.go:207
|
||||
msgid "{0} application override selected"
|
||||
msgstr "Ajustes de aplicación seleccionados para {0}"
|
||||
|
||||
#: internal/app/fenrir_menu.go:34
|
||||
msgid "{0} application settings"
|
||||
msgstr "Configuración de la aplicación {0}"
|
||||
|
||||
#: internal/app/fenrir_menu.go:34 internal/app/remote_persistence.go:131
|
||||
msgid "{0} application settings saved."
|
||||
msgstr "Configuración de la aplicación {0} guardada."
|
||||
|
||||
#: internal/interaction/presentation.go:53
|
||||
msgid "{0} button"
|
||||
msgstr "{0} botón"
|
||||
|
||||
@@ -57,11 +57,11 @@ msgstr ""
|
||||
msgid "Apple"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/application_overrides.go:237
|
||||
#: internal/app/application_overrides.go:325
|
||||
msgid "Application"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/application_overrides.go:236
|
||||
#: internal/app/application_overrides.go:324
|
||||
msgid "Applications"
|
||||
msgstr ""
|
||||
|
||||
@@ -153,7 +153,7 @@ msgstr ""
|
||||
msgid "Canada"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/application_overrides.go:238 internal/preferences/settings.go:79
|
||||
#: internal/app/application_overrides.go:326 internal/preferences/settings.go:93
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
@@ -245,7 +245,7 @@ msgstr ""
|
||||
msgid "Clipboard"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/application.go:142 internal/app/application.go:145 internal/app/clipboard.go:218 internal/app/clipboard.go:374
|
||||
#: internal/app/application.go:144 internal/app/application.go:147 internal/app/clipboard.go:218 internal/app/clipboard.go:374
|
||||
msgid "Clipboard import failed"
|
||||
msgstr ""
|
||||
|
||||
@@ -277,7 +277,11 @@ msgstr ""
|
||||
msgid "Could not check spelling: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/application_overrides.go:241 internal/app/application_overrides.go:245
|
||||
#: internal/app/application_overrides.go:278 internal/app/application_overrides.go:83
|
||||
msgid "Could not load application settings: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/application_overrides.go:329 internal/app/application_overrides.go:333
|
||||
msgid "Could not open Applications: {0}"
|
||||
msgstr ""
|
||||
|
||||
@@ -285,7 +289,7 @@ msgstr ""
|
||||
msgid "Could not open Emoji Menu: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/fenrir_menu.go:32 internal/app/fenrir_menu.go:52 internal/app/fenrir_menu.go:67 internal/app/runtime_settings.go:133
|
||||
#: internal/app/fenrir_menu.go:40 internal/app/fenrir_menu.go:69 internal/app/fenrir_menu.go:89 internal/app/runtime_settings.go:133
|
||||
msgid "Could not open Fenrir Menu: {0}"
|
||||
msgstr ""
|
||||
|
||||
@@ -293,7 +297,7 @@ msgstr ""
|
||||
msgid "Could not open Spellcheck Menu: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/application_overrides.go:104 internal/app/application_overrides.go:208
|
||||
#: internal/app/application_overrides.go:161 internal/app/application_overrides.go:296
|
||||
msgid "Could not restore application settings: {0}"
|
||||
msgstr ""
|
||||
|
||||
@@ -301,7 +305,7 @@ msgstr ""
|
||||
msgid "Could not save exceptions: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/fenrir_menu.go:174 internal/app/fenrir_menu.go:180 internal/app/runtime_settings.go:172 internal/app/runtime_settings.go:199
|
||||
#: internal/app/fenrir_menu.go:200 internal/app/fenrir_menu.go:206 internal/app/runtime_settings.go:176 internal/app/runtime_settings.go:204
|
||||
msgid "Could not save settings: {0}"
|
||||
msgstr ""
|
||||
|
||||
@@ -321,11 +325,11 @@ msgstr ""
|
||||
msgid "Date format"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/application_overrides.go:230
|
||||
#: internal/app/application_overrides.go:318
|
||||
msgid "Default"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/application_overrides.go:129 internal/app/application_overrides.go:182
|
||||
#: internal/app/application_overrides.go:186 internal/app/application_overrides.go:242
|
||||
msgid "Default settings restored"
|
||||
msgstr ""
|
||||
|
||||
@@ -417,7 +421,7 @@ msgstr ""
|
||||
msgid "Face with symbols"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/quick_settings.go:295 internal/app/runtime_settings.go:155
|
||||
#: internal/app/quick_settings.go:295 internal/app/runtime_settings.go:159
|
||||
msgid "Failed to save settings."
|
||||
msgstr ""
|
||||
|
||||
@@ -461,7 +465,7 @@ msgstr ""
|
||||
msgid "Food"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/commands.go:145
|
||||
#: internal/app/commands.go:143
|
||||
msgid "Forward next keypress"
|
||||
msgstr ""
|
||||
|
||||
@@ -697,7 +701,7 @@ msgstr ""
|
||||
msgid "Netherlands"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/application_overrides.go:230
|
||||
#: internal/app/application_overrides.go:318
|
||||
msgid "NetworkManager"
|
||||
msgstr ""
|
||||
|
||||
@@ -705,7 +709,7 @@ msgstr ""
|
||||
msgid "New channel"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/application_overrides.go:230
|
||||
#: internal/app/application_overrides.go:318
|
||||
msgid "Ninjam"
|
||||
msgstr ""
|
||||
|
||||
@@ -753,7 +757,7 @@ msgstr ""
|
||||
msgid "Not in window Mode"
|
||||
msgstr ""
|
||||
|
||||
#: internal/preferences/settings.go:79
|
||||
#: internal/preferences/settings.go:93
|
||||
msgid "OK"
|
||||
msgstr ""
|
||||
|
||||
@@ -813,11 +817,11 @@ msgstr ""
|
||||
msgid "Presents the currently selected text that will be copied to the clipboard"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/commands.go:131
|
||||
#: internal/app/commands.go:129
|
||||
msgid "Progress monitoring disabled"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/commands.go:129
|
||||
#: internal/app/commands.go:127
|
||||
msgid "Progress monitoring enabled"
|
||||
msgstr ""
|
||||
|
||||
@@ -969,7 +973,7 @@ msgstr ""
|
||||
msgid "Santa"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/runtime_settings.go:151 internal/app/runtime_settings.go:168
|
||||
#: internal/app/runtime_settings.go:155 internal/app/runtime_settings.go:172
|
||||
msgid "Save unavailable."
|
||||
msgstr ""
|
||||
|
||||
@@ -993,7 +997,7 @@ msgstr ""
|
||||
msgid "Screaming"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/commands.go:154 internal/app/scripts.go
|
||||
#: internal/app/commands.go:152 internal/app/scripts.go
|
||||
msgid "Script failed"
|
||||
msgstr ""
|
||||
|
||||
@@ -1041,7 +1045,7 @@ msgstr ""
|
||||
msgid "Set window begin and end marks"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/application_overrides.go:114 internal/app/application_overrides.go:227 internal/app/commands.go:84
|
||||
#: internal/app/application_overrides.go:171 internal/app/application_overrides.go:315 internal/app/commands.go:84
|
||||
msgid "Settings are busy"
|
||||
msgstr ""
|
||||
|
||||
@@ -1189,11 +1193,11 @@ msgstr ""
|
||||
msgid "Taco"
|
||||
msgstr ""
|
||||
|
||||
#: internal/preferences/settings.go:72
|
||||
#: internal/preferences/settings.go:83
|
||||
msgid "Test synthesizer"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/fenrir_menu.go:139
|
||||
#: internal/app/fenrir_menu.go:165
|
||||
msgid "Testing voice"
|
||||
msgstr ""
|
||||
|
||||
@@ -2085,7 +2089,7 @@ msgstr ""
|
||||
msgid "speech rate"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/commands.go:140
|
||||
#: internal/app/commands.go:138
|
||||
msgid "speech temporary disabled"
|
||||
msgstr ""
|
||||
|
||||
@@ -2177,10 +2181,18 @@ msgstr ""
|
||||
msgid "{0} added to dictionary"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/application_overrides.go:147
|
||||
#: internal/app/application_overrides.go:207
|
||||
msgid "{0} application override selected"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/fenrir_menu.go:34
|
||||
msgid "{0} application settings"
|
||||
msgstr ""
|
||||
|
||||
#: internal/app/fenrir_menu.go:34 internal/app/remote_persistence.go:131
|
||||
msgid "{0} application settings saved."
|
||||
msgstr ""
|
||||
|
||||
#: internal/interaction/presentation.go:53
|
||||
msgid "{0} button"
|
||||
msgstr ""
|
||||
|
||||
@@ -68,11 +68,11 @@ msgstr "Złość"
|
||||
msgid "Apple"
|
||||
msgstr "Jabłko"
|
||||
|
||||
#: internal/app/application_overrides.go:237
|
||||
#: internal/app/application_overrides.go:325
|
||||
msgid "Application"
|
||||
msgstr "Aplikacja"
|
||||
|
||||
#: internal/app/application_overrides.go:236
|
||||
#: internal/app/application_overrides.go:324
|
||||
msgid "Applications"
|
||||
msgstr "Aplikacje"
|
||||
|
||||
@@ -164,8 +164,8 @@ msgstr "Motyl"
|
||||
msgid "Canada"
|
||||
msgstr "Kanada"
|
||||
|
||||
#: internal/app/application_overrides.go:238
|
||||
#: internal/preferences/settings.go:79
|
||||
#: internal/app/application_overrides.go:326
|
||||
#: internal/preferences/settings.go:93
|
||||
msgid "Cancel"
|
||||
msgstr "Anuluj"
|
||||
|
||||
@@ -257,7 +257,7 @@ msgstr "Wyczyść obszar dynamiczny 9"
|
||||
msgid "Clipboard"
|
||||
msgstr "Schowek"
|
||||
|
||||
#: internal/app/application.go:142 internal/app/application.go:145
|
||||
#: internal/app/application.go:144 internal/app/application.go:147
|
||||
#: internal/app/clipboard.go:218 internal/app/clipboard.go:374
|
||||
msgid "Clipboard import failed"
|
||||
msgstr "Importowanie schowka nie powiodło się"
|
||||
@@ -290,8 +290,13 @@ msgstr "Wyluzowanie"
|
||||
msgid "Could not check spelling: {0}"
|
||||
msgstr "Nie można sprawdzić pisowni: {0}"
|
||||
|
||||
#: internal/app/application_overrides.go:241
|
||||
#: internal/app/application_overrides.go:245
|
||||
#: internal/app/application_overrides.go:278
|
||||
#: internal/app/application_overrides.go:83
|
||||
msgid "Could not load application settings: {0}"
|
||||
msgstr "Nie można wczytać ustawień aplikacji: {0}"
|
||||
|
||||
#: internal/app/application_overrides.go:329
|
||||
#: internal/app/application_overrides.go:333
|
||||
msgid "Could not open Applications: {0}"
|
||||
msgstr "Nie można otworzyć menu aplikacji: {0}"
|
||||
|
||||
@@ -299,8 +304,8 @@ msgstr "Nie można otworzyć menu aplikacji: {0}"
|
||||
msgid "Could not open Emoji Menu: {0}"
|
||||
msgstr "Nie można otworzyć menu emoji: {0}"
|
||||
|
||||
#: internal/app/fenrir_menu.go:32 internal/app/fenrir_menu.go:52
|
||||
#: internal/app/fenrir_menu.go:67 internal/app/runtime_settings.go:133
|
||||
#: internal/app/fenrir_menu.go:40 internal/app/fenrir_menu.go:69
|
||||
#: internal/app/fenrir_menu.go:89 internal/app/runtime_settings.go:133
|
||||
msgid "Could not open Fenrir Menu: {0}"
|
||||
msgstr "Nie można otworzyć menu Fenrira: {0}"
|
||||
|
||||
@@ -308,8 +313,8 @@ msgstr "Nie można otworzyć menu Fenrira: {0}"
|
||||
msgid "Could not open Spellcheck Menu: {0}"
|
||||
msgstr "Nie można otworzyć menu sprawdzania pisowni: {0}"
|
||||
|
||||
#: internal/app/application_overrides.go:104
|
||||
#: internal/app/application_overrides.go:208
|
||||
#: internal/app/application_overrides.go:161
|
||||
#: internal/app/application_overrides.go:296
|
||||
msgid "Could not restore application settings: {0}"
|
||||
msgstr "Nie można przywrócić ustawień aplikacji: {0}"
|
||||
|
||||
@@ -317,8 +322,8 @@ msgstr "Nie można przywrócić ustawień aplikacji: {0}"
|
||||
msgid "Could not save exceptions: {0}"
|
||||
msgstr "Nie można zapisać wyjątków: {0}"
|
||||
|
||||
#: internal/app/fenrir_menu.go:174 internal/app/fenrir_menu.go:180
|
||||
#: internal/app/runtime_settings.go:172 internal/app/runtime_settings.go:199
|
||||
#: internal/app/fenrir_menu.go:200 internal/app/fenrir_menu.go:206
|
||||
#: internal/app/runtime_settings.go:176 internal/app/runtime_settings.go:204
|
||||
msgid "Could not save settings: {0}"
|
||||
msgstr "Nie można zapisać ustawień: {0}"
|
||||
|
||||
@@ -338,12 +343,12 @@ msgstr "Sztylet"
|
||||
msgid "Date format"
|
||||
msgstr "Format daty"
|
||||
|
||||
#: internal/app/application_overrides.go:230
|
||||
#: internal/app/application_overrides.go:318
|
||||
msgid "Default"
|
||||
msgstr "Domyślne"
|
||||
|
||||
#: internal/app/application_overrides.go:129
|
||||
#: internal/app/application_overrides.go:182
|
||||
#: internal/app/application_overrides.go:186
|
||||
#: internal/app/application_overrides.go:242
|
||||
msgid "Default settings restored"
|
||||
msgstr "Przywrócono ustawienia domyślne"
|
||||
|
||||
@@ -446,7 +451,7 @@ msgstr ""
|
||||
msgid "Face with symbols"
|
||||
msgstr "Twarz z symbolami"
|
||||
|
||||
#: internal/app/quick_settings.go:295 internal/app/runtime_settings.go:155
|
||||
#: internal/app/quick_settings.go:295 internal/app/runtime_settings.go:159
|
||||
msgid "Failed to save settings."
|
||||
msgstr "Nie udało się zapisać ustawień."
|
||||
|
||||
@@ -490,7 +495,7 @@ msgstr "Złożone dłonie"
|
||||
msgid "Food"
|
||||
msgstr "Jedzenie"
|
||||
|
||||
#: internal/app/commands.go:145
|
||||
#: internal/app/commands.go:143
|
||||
msgid "Forward next keypress"
|
||||
msgstr "Przepuść następny klawisz"
|
||||
|
||||
@@ -730,7 +735,7 @@ msgstr "Nudności"
|
||||
msgid "Netherlands"
|
||||
msgstr "Holandia"
|
||||
|
||||
#: internal/app/application_overrides.go:230
|
||||
#: internal/app/application_overrides.go:318
|
||||
msgid "NetworkManager"
|
||||
msgstr "NetworkManager"
|
||||
|
||||
@@ -738,7 +743,7 @@ msgstr "NetworkManager"
|
||||
msgid "New channel"
|
||||
msgstr "Nowy kanał"
|
||||
|
||||
#: internal/app/application_overrides.go:230
|
||||
#: internal/app/application_overrides.go:318
|
||||
msgid "Ninjam"
|
||||
msgstr "Ninjam"
|
||||
|
||||
@@ -791,7 +796,7 @@ msgstr "Poza trybem przeglądu"
|
||||
msgid "Not in window Mode"
|
||||
msgstr "Nie znajdujesz się w trybie okna."
|
||||
|
||||
#: internal/preferences/settings.go:79
|
||||
#: internal/preferences/settings.go:93
|
||||
msgid "OK"
|
||||
msgstr "OK"
|
||||
|
||||
@@ -852,11 +857,11 @@ msgid ""
|
||||
"Presents the currently selected text that will be copied to the clipboard"
|
||||
msgstr "Odczytuje zaznaczony tekst, który zostanie skopiowany do schowka"
|
||||
|
||||
#: internal/app/commands.go:131
|
||||
#: internal/app/commands.go:129
|
||||
msgid "Progress monitoring disabled"
|
||||
msgstr "Monitorowanie postępu wyłączone"
|
||||
|
||||
#: internal/app/commands.go:129
|
||||
#: internal/app/commands.go:127
|
||||
msgid "Progress monitoring enabled"
|
||||
msgstr "Monitorowanie postępu włączone"
|
||||
|
||||
@@ -1008,7 +1013,7 @@ msgstr "Smutek"
|
||||
msgid "Santa"
|
||||
msgstr "Święty Mikołaj"
|
||||
|
||||
#: internal/app/runtime_settings.go:151 internal/app/runtime_settings.go:168
|
||||
#: internal/app/runtime_settings.go:155 internal/app/runtime_settings.go:172
|
||||
msgid "Save unavailable."
|
||||
msgstr "Zapisywanie niedostępne."
|
||||
|
||||
@@ -1032,7 +1037,7 @@ msgstr "Mów „błędna pisownia”"
|
||||
msgid "Screaming"
|
||||
msgstr "Krzyk"
|
||||
|
||||
#: internal/app/commands.go:154 internal/app/scripts.go
|
||||
#: internal/app/commands.go:152 internal/app/scripts.go
|
||||
msgid "Script failed"
|
||||
msgstr "Skrypt nie powiódł się"
|
||||
|
||||
@@ -1080,8 +1085,8 @@ msgstr "Ustaw obszar dynamiczny za pomocą jednego lub dwóch znaczników"
|
||||
msgid "Set window begin and end marks"
|
||||
msgstr "Ustaw początek i koniec okna."
|
||||
|
||||
#: internal/app/application_overrides.go:114
|
||||
#: internal/app/application_overrides.go:227 internal/app/commands.go:84
|
||||
#: internal/app/application_overrides.go:171
|
||||
#: internal/app/application_overrides.go:315 internal/app/commands.go:84
|
||||
msgid "Settings are busy"
|
||||
msgstr "Ustawienia są zajęte"
|
||||
|
||||
@@ -1229,11 +1234,11 @@ msgstr "Symbole"
|
||||
msgid "Taco"
|
||||
msgstr "Taco"
|
||||
|
||||
#: internal/preferences/settings.go:72
|
||||
#: internal/preferences/settings.go:83
|
||||
msgid "Test synthesizer"
|
||||
msgstr "Testuj syntezator"
|
||||
|
||||
#: internal/app/fenrir_menu.go:139
|
||||
#: internal/app/fenrir_menu.go:165
|
||||
msgid "Testing voice"
|
||||
msgstr "Testowanie głosu"
|
||||
|
||||
@@ -2185,7 +2190,7 @@ msgstr "wysokość głosu"
|
||||
msgid "speech rate"
|
||||
msgstr "szybkość mowy"
|
||||
|
||||
#: internal/app/commands.go:140
|
||||
#: internal/app/commands.go:138
|
||||
msgid "speech temporary disabled"
|
||||
msgstr "Mowa tymczasowo wyłączona."
|
||||
|
||||
@@ -2280,10 +2285,18 @@ msgstr "mrugnięcie"
|
||||
msgid "{0} added to dictionary"
|
||||
msgstr "Dodano {0} do słownika"
|
||||
|
||||
#: internal/app/application_overrides.go:147
|
||||
#: internal/app/application_overrides.go:207
|
||||
msgid "{0} application override selected"
|
||||
msgstr "Wybrano ustawienia aplikacji {0}"
|
||||
|
||||
#: internal/app/fenrir_menu.go:34
|
||||
msgid "{0} application settings"
|
||||
msgstr "Ustawienia aplikacji {0}"
|
||||
|
||||
#: internal/app/fenrir_menu.go:34 internal/app/remote_persistence.go:131
|
||||
msgid "{0} application settings saved."
|
||||
msgstr "Ustawienia aplikacji {0} zapisane."
|
||||
|
||||
#: internal/interaction/presentation.go:53
|
||||
msgid "{0} button"
|
||||
msgstr "{0} przycisk"
|
||||
|
||||
@@ -66,11 +66,11 @@ msgstr "Zangado"
|
||||
msgid "Apple"
|
||||
msgstr "Maçã"
|
||||
|
||||
#: internal/app/application_overrides.go:237
|
||||
#: internal/app/application_overrides.go:325
|
||||
msgid "Application"
|
||||
msgstr "Aplicativo"
|
||||
|
||||
#: internal/app/application_overrides.go:236
|
||||
#: internal/app/application_overrides.go:324
|
||||
msgid "Applications"
|
||||
msgstr "Aplicativos"
|
||||
|
||||
@@ -162,8 +162,8 @@ msgstr "Borboleta"
|
||||
msgid "Canada"
|
||||
msgstr "Canadá"
|
||||
|
||||
#: internal/app/application_overrides.go:238
|
||||
#: internal/preferences/settings.go:79
|
||||
#: internal/app/application_overrides.go:326
|
||||
#: internal/preferences/settings.go:93
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
|
||||
@@ -255,7 +255,7 @@ msgstr "Limpar região dinâmica 9"
|
||||
msgid "Clipboard"
|
||||
msgstr "Área de transferência"
|
||||
|
||||
#: internal/app/application.go:142 internal/app/application.go:145
|
||||
#: internal/app/application.go:144 internal/app/application.go:147
|
||||
#: internal/app/clipboard.go:218 internal/app/clipboard.go:374
|
||||
msgid "Clipboard import failed"
|
||||
msgstr "Falha ao importar a área de transferência"
|
||||
@@ -288,8 +288,13 @@ msgstr "Legal"
|
||||
msgid "Could not check spelling: {0}"
|
||||
msgstr "Não foi possível verificar a ortografia: {0}"
|
||||
|
||||
#: internal/app/application_overrides.go:241
|
||||
#: internal/app/application_overrides.go:245
|
||||
#: internal/app/application_overrides.go:278
|
||||
#: internal/app/application_overrides.go:83
|
||||
msgid "Could not load application settings: {0}"
|
||||
msgstr "Não foi possível carregar as configurações do aplicativo: {0}"
|
||||
|
||||
#: internal/app/application_overrides.go:329
|
||||
#: internal/app/application_overrides.go:333
|
||||
msgid "Could not open Applications: {0}"
|
||||
msgstr "Não foi possível abrir Aplicativos: {0}"
|
||||
|
||||
@@ -297,8 +302,8 @@ msgstr "Não foi possível abrir Aplicativos: {0}"
|
||||
msgid "Could not open Emoji Menu: {0}"
|
||||
msgstr "Não foi possível abrir o menu de emojis: {0}"
|
||||
|
||||
#: internal/app/fenrir_menu.go:32 internal/app/fenrir_menu.go:52
|
||||
#: internal/app/fenrir_menu.go:67 internal/app/runtime_settings.go:133
|
||||
#: internal/app/fenrir_menu.go:40 internal/app/fenrir_menu.go:69
|
||||
#: internal/app/fenrir_menu.go:89 internal/app/runtime_settings.go:133
|
||||
msgid "Could not open Fenrir Menu: {0}"
|
||||
msgstr "Não foi possível abrir o menu do Fenrir: {0}"
|
||||
|
||||
@@ -306,8 +311,8 @@ msgstr "Não foi possível abrir o menu do Fenrir: {0}"
|
||||
msgid "Could not open Spellcheck Menu: {0}"
|
||||
msgstr "Não foi possível abrir o menu de ortografia: {0}"
|
||||
|
||||
#: internal/app/application_overrides.go:104
|
||||
#: internal/app/application_overrides.go:208
|
||||
#: internal/app/application_overrides.go:161
|
||||
#: internal/app/application_overrides.go:296
|
||||
msgid "Could not restore application settings: {0}"
|
||||
msgstr "Não foi possível restaurar as configurações do aplicativo: {0}"
|
||||
|
||||
@@ -315,8 +320,8 @@ msgstr "Não foi possível restaurar as configurações do aplicativo: {0}"
|
||||
msgid "Could not save exceptions: {0}"
|
||||
msgstr "Não foi possível salvar as exceções: {0}"
|
||||
|
||||
#: internal/app/fenrir_menu.go:174 internal/app/fenrir_menu.go:180
|
||||
#: internal/app/runtime_settings.go:172 internal/app/runtime_settings.go:199
|
||||
#: internal/app/fenrir_menu.go:200 internal/app/fenrir_menu.go:206
|
||||
#: internal/app/runtime_settings.go:176 internal/app/runtime_settings.go:204
|
||||
msgid "Could not save settings: {0}"
|
||||
msgstr "Não foi possível salvar as configurações: {0}"
|
||||
|
||||
@@ -336,12 +341,12 @@ msgstr "Adaga"
|
||||
msgid "Date format"
|
||||
msgstr "Formato da data"
|
||||
|
||||
#: internal/app/application_overrides.go:230
|
||||
#: internal/app/application_overrides.go:318
|
||||
msgid "Default"
|
||||
msgstr "Padrão"
|
||||
|
||||
#: internal/app/application_overrides.go:129
|
||||
#: internal/app/application_overrides.go:182
|
||||
#: internal/app/application_overrides.go:186
|
||||
#: internal/app/application_overrides.go:242
|
||||
msgid "Default settings restored"
|
||||
msgstr "Configurações padrão restauradas"
|
||||
|
||||
@@ -446,7 +451,7 @@ msgstr ""
|
||||
msgid "Face with symbols"
|
||||
msgstr "Rosto com símbolos"
|
||||
|
||||
#: internal/app/quick_settings.go:295 internal/app/runtime_settings.go:155
|
||||
#: internal/app/quick_settings.go:295 internal/app/runtime_settings.go:159
|
||||
msgid "Failed to save settings."
|
||||
msgstr "Falha ao salvar as configurações."
|
||||
|
||||
@@ -490,7 +495,7 @@ msgstr "Mãos juntas"
|
||||
msgid "Food"
|
||||
msgstr "Comida"
|
||||
|
||||
#: internal/app/commands.go:145
|
||||
#: internal/app/commands.go:143
|
||||
msgid "Forward next keypress"
|
||||
msgstr "Avançar próxima tecla pressionada"
|
||||
|
||||
@@ -732,7 +737,7 @@ msgstr "Enjoado"
|
||||
msgid "Netherlands"
|
||||
msgstr "Países Baixos"
|
||||
|
||||
#: internal/app/application_overrides.go:230
|
||||
#: internal/app/application_overrides.go:318
|
||||
msgid "NetworkManager"
|
||||
msgstr "NetworkManager"
|
||||
|
||||
@@ -740,7 +745,7 @@ msgstr "NetworkManager"
|
||||
msgid "New channel"
|
||||
msgstr "Novo canal"
|
||||
|
||||
#: internal/app/application_overrides.go:230
|
||||
#: internal/app/application_overrides.go:318
|
||||
msgid "Ninjam"
|
||||
msgstr "Ninjam"
|
||||
|
||||
@@ -794,7 +799,7 @@ msgstr "Fora do modo de revisão"
|
||||
msgid "Not in window Mode"
|
||||
msgstr "Não no Modo de Janela"
|
||||
|
||||
#: internal/preferences/settings.go:79
|
||||
#: internal/preferences/settings.go:93
|
||||
msgid "OK"
|
||||
msgstr "OK"
|
||||
|
||||
@@ -856,11 +861,11 @@ msgid ""
|
||||
msgstr ""
|
||||
"Apresenta o texto selecionado que será copiado para a área de transferência"
|
||||
|
||||
#: internal/app/commands.go:131
|
||||
#: internal/app/commands.go:129
|
||||
msgid "Progress monitoring disabled"
|
||||
msgstr "Monitoramento de progresso desativado"
|
||||
|
||||
#: internal/app/commands.go:129
|
||||
#: internal/app/commands.go:127
|
||||
msgid "Progress monitoring enabled"
|
||||
msgstr "Monitoramento de progresso ativado"
|
||||
|
||||
@@ -1012,7 +1017,7 @@ msgstr "Triste"
|
||||
msgid "Santa"
|
||||
msgstr "Papai Noel"
|
||||
|
||||
#: internal/app/runtime_settings.go:151 internal/app/runtime_settings.go:168
|
||||
#: internal/app/runtime_settings.go:155 internal/app/runtime_settings.go:172
|
||||
msgid "Save unavailable."
|
||||
msgstr "Salvamento indisponível."
|
||||
|
||||
@@ -1036,7 +1041,7 @@ msgstr "Dizer “erro ortográfico”"
|
||||
msgid "Screaming"
|
||||
msgstr "Gritando"
|
||||
|
||||
#: internal/app/commands.go:154 internal/app/scripts.go
|
||||
#: internal/app/commands.go:152 internal/app/scripts.go
|
||||
msgid "Script failed"
|
||||
msgstr "O script falhou"
|
||||
|
||||
@@ -1084,8 +1089,8 @@ msgstr "Definir uma região dinâmica a partir de uma ou duas marcas"
|
||||
msgid "Set window begin and end marks"
|
||||
msgstr "Definir marcas de início e fim da janela"
|
||||
|
||||
#: internal/app/application_overrides.go:114
|
||||
#: internal/app/application_overrides.go:227 internal/app/commands.go:84
|
||||
#: internal/app/application_overrides.go:171
|
||||
#: internal/app/application_overrides.go:315 internal/app/commands.go:84
|
||||
msgid "Settings are busy"
|
||||
msgstr "As configurações estão ocupadas"
|
||||
|
||||
@@ -1233,11 +1238,11 @@ msgstr "Símbolos"
|
||||
msgid "Taco"
|
||||
msgstr "Taco"
|
||||
|
||||
#: internal/preferences/settings.go:72
|
||||
#: internal/preferences/settings.go:83
|
||||
msgid "Test synthesizer"
|
||||
msgstr "Testar sintetizador"
|
||||
|
||||
#: internal/app/fenrir_menu.go:139
|
||||
#: internal/app/fenrir_menu.go:165
|
||||
msgid "Testing voice"
|
||||
msgstr "Testando a voz"
|
||||
|
||||
@@ -2198,7 +2203,7 @@ msgstr "tom da fala"
|
||||
msgid "speech rate"
|
||||
msgstr "velocidade da fala"
|
||||
|
||||
#: internal/app/commands.go:140
|
||||
#: internal/app/commands.go:138
|
||||
msgid "speech temporary disabled"
|
||||
msgstr "fala temporariamente desativada"
|
||||
|
||||
@@ -2293,10 +2298,18 @@ msgstr "piscadela"
|
||||
msgid "{0} added to dictionary"
|
||||
msgstr "{0} adicionado ao dicionário"
|
||||
|
||||
#: internal/app/application_overrides.go:147
|
||||
#: internal/app/application_overrides.go:207
|
||||
msgid "{0} application override selected"
|
||||
msgstr "Configurações do aplicativo {0} selecionadas"
|
||||
|
||||
#: internal/app/fenrir_menu.go:34
|
||||
msgid "{0} application settings"
|
||||
msgstr "Configurações do aplicativo {0}"
|
||||
|
||||
#: internal/app/fenrir_menu.go:34 internal/app/remote_persistence.go:131
|
||||
msgid "{0} application settings saved."
|
||||
msgstr "Configurações do aplicativo {0} salvas."
|
||||
|
||||
#: internal/interaction/presentation.go:53
|
||||
msgid "{0} button"
|
||||
msgstr "{0} botão"
|
||||
|
||||
@@ -65,11 +65,11 @@ msgstr "Злость"
|
||||
msgid "Apple"
|
||||
msgstr "Яблоко"
|
||||
|
||||
#: internal/app/application_overrides.go:237
|
||||
#: internal/app/application_overrides.go:325
|
||||
msgid "Application"
|
||||
msgstr "Приложение"
|
||||
|
||||
#: internal/app/application_overrides.go:236
|
||||
#: internal/app/application_overrides.go:324
|
||||
msgid "Applications"
|
||||
msgstr "Приложения"
|
||||
|
||||
@@ -161,8 +161,8 @@ msgstr "Бабочка"
|
||||
msgid "Canada"
|
||||
msgstr "Канада"
|
||||
|
||||
#: internal/app/application_overrides.go:238
|
||||
#: internal/preferences/settings.go:79
|
||||
#: internal/app/application_overrides.go:326
|
||||
#: internal/preferences/settings.go:93
|
||||
msgid "Cancel"
|
||||
msgstr "Отмена"
|
||||
|
||||
@@ -254,7 +254,7 @@ msgstr "Очистить динамическую область 9"
|
||||
msgid "Clipboard"
|
||||
msgstr "Буфер обмена"
|
||||
|
||||
#: internal/app/application.go:142 internal/app/application.go:145
|
||||
#: internal/app/application.go:144 internal/app/application.go:147
|
||||
#: internal/app/clipboard.go:218 internal/app/clipboard.go:374
|
||||
msgid "Clipboard import failed"
|
||||
msgstr "Не удалось импортировать буфер обмена"
|
||||
@@ -287,8 +287,13 @@ msgstr "Круто"
|
||||
msgid "Could not check spelling: {0}"
|
||||
msgstr "Не удалось проверить правописание: {0}"
|
||||
|
||||
#: internal/app/application_overrides.go:241
|
||||
#: internal/app/application_overrides.go:245
|
||||
#: internal/app/application_overrides.go:278
|
||||
#: internal/app/application_overrides.go:83
|
||||
msgid "Could not load application settings: {0}"
|
||||
msgstr "Не удалось загрузить настройки приложения: {0}"
|
||||
|
||||
#: internal/app/application_overrides.go:329
|
||||
#: internal/app/application_overrides.go:333
|
||||
msgid "Could not open Applications: {0}"
|
||||
msgstr "Не удалось открыть меню приложений: {0}"
|
||||
|
||||
@@ -296,8 +301,8 @@ msgstr "Не удалось открыть меню приложений: {0}"
|
||||
msgid "Could not open Emoji Menu: {0}"
|
||||
msgstr "Не удалось открыть меню эмодзи: {0}"
|
||||
|
||||
#: internal/app/fenrir_menu.go:32 internal/app/fenrir_menu.go:52
|
||||
#: internal/app/fenrir_menu.go:67 internal/app/runtime_settings.go:133
|
||||
#: internal/app/fenrir_menu.go:40 internal/app/fenrir_menu.go:69
|
||||
#: internal/app/fenrir_menu.go:89 internal/app/runtime_settings.go:133
|
||||
msgid "Could not open Fenrir Menu: {0}"
|
||||
msgstr "Не удалось открыть меню Fenrir: {0}"
|
||||
|
||||
@@ -305,8 +310,8 @@ msgstr "Не удалось открыть меню Fenrir: {0}"
|
||||
msgid "Could not open Spellcheck Menu: {0}"
|
||||
msgstr "Не удалось открыть меню проверки правописания: {0}"
|
||||
|
||||
#: internal/app/application_overrides.go:104
|
||||
#: internal/app/application_overrides.go:208
|
||||
#: internal/app/application_overrides.go:161
|
||||
#: internal/app/application_overrides.go:296
|
||||
msgid "Could not restore application settings: {0}"
|
||||
msgstr "Не удалось восстановить настройки приложения: {0}"
|
||||
|
||||
@@ -314,8 +319,8 @@ msgstr "Не удалось восстановить настройки прил
|
||||
msgid "Could not save exceptions: {0}"
|
||||
msgstr "Не удалось сохранить исключения: {0}"
|
||||
|
||||
#: internal/app/fenrir_menu.go:174 internal/app/fenrir_menu.go:180
|
||||
#: internal/app/runtime_settings.go:172 internal/app/runtime_settings.go:199
|
||||
#: internal/app/fenrir_menu.go:200 internal/app/fenrir_menu.go:206
|
||||
#: internal/app/runtime_settings.go:176 internal/app/runtime_settings.go:204
|
||||
msgid "Could not save settings: {0}"
|
||||
msgstr "Не удалось сохранить настройки: {0}"
|
||||
|
||||
@@ -335,12 +340,12 @@ msgstr "Кинжал"
|
||||
msgid "Date format"
|
||||
msgstr "Формат даты"
|
||||
|
||||
#: internal/app/application_overrides.go:230
|
||||
#: internal/app/application_overrides.go:318
|
||||
msgid "Default"
|
||||
msgstr "По умолчанию"
|
||||
|
||||
#: internal/app/application_overrides.go:129
|
||||
#: internal/app/application_overrides.go:182
|
||||
#: internal/app/application_overrides.go:186
|
||||
#: internal/app/application_overrides.go:242
|
||||
msgid "Default settings restored"
|
||||
msgstr "Настройки по умолчанию восстановлены"
|
||||
|
||||
@@ -442,7 +447,7 @@ msgstr "Экспорт текущего буфера обмена fenrir в бу
|
||||
msgid "Face with symbols"
|
||||
msgstr "Лицо с символами"
|
||||
|
||||
#: internal/app/quick_settings.go:295 internal/app/runtime_settings.go:155
|
||||
#: internal/app/quick_settings.go:295 internal/app/runtime_settings.go:159
|
||||
msgid "Failed to save settings."
|
||||
msgstr "Не удалось сохранить настройки."
|
||||
|
||||
@@ -486,7 +491,7 @@ msgstr "Сложенные ладони"
|
||||
msgid "Food"
|
||||
msgstr "Еда"
|
||||
|
||||
#: internal/app/commands.go:145
|
||||
#: internal/app/commands.go:143
|
||||
msgid "Forward next keypress"
|
||||
msgstr "Переслать следующую клавишу."
|
||||
|
||||
@@ -725,7 +730,7 @@ msgstr "Тошнота"
|
||||
msgid "Netherlands"
|
||||
msgstr "Нидерланды"
|
||||
|
||||
#: internal/app/application_overrides.go:230
|
||||
#: internal/app/application_overrides.go:318
|
||||
msgid "NetworkManager"
|
||||
msgstr "NetworkManager"
|
||||
|
||||
@@ -733,7 +738,7 @@ msgstr "NetworkManager"
|
||||
msgid "New channel"
|
||||
msgstr "Новый канал"
|
||||
|
||||
#: internal/app/application_overrides.go:230
|
||||
#: internal/app/application_overrides.go:318
|
||||
msgid "Ninjam"
|
||||
msgstr "Ninjam"
|
||||
|
||||
@@ -786,7 +791,7 @@ msgstr "Нет в режиме просмотра."
|
||||
msgid "Not in window Mode"
|
||||
msgstr "Не в режиме окна."
|
||||
|
||||
#: internal/preferences/settings.go:79
|
||||
#: internal/preferences/settings.go:93
|
||||
msgid "OK"
|
||||
msgstr "ОК"
|
||||
|
||||
@@ -848,11 +853,11 @@ msgid ""
|
||||
msgstr ""
|
||||
"Сообщает текущий выбранный текст, который будет скопирован в буфер обмена."
|
||||
|
||||
#: internal/app/commands.go:131
|
||||
#: internal/app/commands.go:129
|
||||
msgid "Progress monitoring disabled"
|
||||
msgstr "Отслеживание хода выполнения отключено"
|
||||
|
||||
#: internal/app/commands.go:129
|
||||
#: internal/app/commands.go:127
|
||||
msgid "Progress monitoring enabled"
|
||||
msgstr "Отслеживание хода выполнения включено"
|
||||
|
||||
@@ -1004,7 +1009,7 @@ msgstr "Грусть"
|
||||
msgid "Santa"
|
||||
msgstr "Дед Мороз"
|
||||
|
||||
#: internal/app/runtime_settings.go:151 internal/app/runtime_settings.go:168
|
||||
#: internal/app/runtime_settings.go:155 internal/app/runtime_settings.go:172
|
||||
msgid "Save unavailable."
|
||||
msgstr "Сохранение недоступно."
|
||||
|
||||
@@ -1028,7 +1033,7 @@ msgstr "Говорить «ошибка»"
|
||||
msgid "Screaming"
|
||||
msgstr "Крик"
|
||||
|
||||
#: internal/app/commands.go:154 internal/app/scripts.go
|
||||
#: internal/app/commands.go:152 internal/app/scripts.go
|
||||
msgid "Script failed"
|
||||
msgstr "Ошибка сценария"
|
||||
|
||||
@@ -1076,8 +1081,8 @@ msgstr "Задать динамическую область по одной и
|
||||
msgid "Set window begin and end marks"
|
||||
msgstr "Установка начала и конца окна"
|
||||
|
||||
#: internal/app/application_overrides.go:114
|
||||
#: internal/app/application_overrides.go:227 internal/app/commands.go:84
|
||||
#: internal/app/application_overrides.go:171
|
||||
#: internal/app/application_overrides.go:315 internal/app/commands.go:84
|
||||
msgid "Settings are busy"
|
||||
msgstr "Настройки заняты"
|
||||
|
||||
@@ -1225,11 +1230,11 @@ msgstr "Символы"
|
||||
msgid "Taco"
|
||||
msgstr "Тако"
|
||||
|
||||
#: internal/preferences/settings.go:72
|
||||
#: internal/preferences/settings.go:83
|
||||
msgid "Test synthesizer"
|
||||
msgstr "Проверить синтезатор"
|
||||
|
||||
#: internal/app/fenrir_menu.go:139
|
||||
#: internal/app/fenrir_menu.go:165
|
||||
msgid "Testing voice"
|
||||
msgstr "Проверка голоса"
|
||||
|
||||
@@ -2182,7 +2187,7 @@ msgstr "высота голоса"
|
||||
msgid "speech rate"
|
||||
msgstr "скорость речи"
|
||||
|
||||
#: internal/app/commands.go:140
|
||||
#: internal/app/commands.go:138
|
||||
msgid "speech temporary disabled"
|
||||
msgstr "Речь временно отключена"
|
||||
|
||||
@@ -2277,10 +2282,18 @@ msgstr "подмигивание"
|
||||
msgid "{0} added to dictionary"
|
||||
msgstr "{0} добавлено в словарь"
|
||||
|
||||
#: internal/app/application_overrides.go:147
|
||||
#: internal/app/application_overrides.go:207
|
||||
msgid "{0} application override selected"
|
||||
msgstr "Выбраны настройки приложения {0}"
|
||||
|
||||
#: internal/app/fenrir_menu.go:34
|
||||
msgid "{0} application settings"
|
||||
msgstr "Настройки приложения {0}"
|
||||
|
||||
#: internal/app/fenrir_menu.go:34 internal/app/remote_persistence.go:131
|
||||
msgid "{0} application settings saved."
|
||||
msgstr "Настройки приложения {0} сохранены."
|
||||
|
||||
#: internal/interaction/presentation.go:53
|
||||
msgid "{0} button"
|
||||
msgstr "{0}, кнопка"
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package preferences
|
||||
|
||||
import (
|
||||
"git.stormux.org/storm/fenrir/internal/config"
|
||||
"git.stormux.org/storm/fenrir/internal/interaction"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestApplicationSettingsOnlyExposeRestorableControls(t *testing.T) {
|
||||
base := config.Defaults()
|
||||
session, err := New_application_session(base, nil, "Ninjam application settings")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
values := session.Values()
|
||||
for _, path := range []string{"presentation.speech.driver", "presentation.sound.theme", "keyboard.layout", "keyboard.devices", "reading.punctuation_profile", "clipboard.persistent"} {
|
||||
if _, exists := values[path]; exists {
|
||||
t.Fatal("startup/global control exposed", path)
|
||||
}
|
||||
}
|
||||
if _, exists := values["presentation.speech.voice"]; !exists {
|
||||
t.Fatal("voice control missing")
|
||||
}
|
||||
session.Handle(interaction.Key{Name: "tab"})
|
||||
session.Handle(interaction.Key{Name: "tab"})
|
||||
session.Handle(interaction.Key{Name: "up"})
|
||||
saved, err := Application_settings(session, base)
|
||||
if err != nil || saved.Presentation.Speech.Rate != base.Presentation.Speech.Rate+1 || saved.Keyboard.Layout != base.Keyboard.Layout {
|
||||
t.Fatal(saved, err)
|
||||
}
|
||||
}
|
||||
@@ -119,6 +119,9 @@ func (d Discovery) Refresh_speech(session *interaction.Session, previous *config
|
||||
"presentation.speech.voice": &next.Presentation.Speech.Voice,
|
||||
} {
|
||||
control, ok := values[path]
|
||||
if !ok && path == "presentation.speech.driver" {
|
||||
continue
|
||||
}
|
||||
if !ok || control.Selected < 0 || control.Selected >= len(control.Choices) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -38,10 +38,18 @@ type Choices map[string][]config.Choice
|
||||
const MenuTitle = "Fenrir Menu"
|
||||
|
||||
func New_session(settings config.Settings, order []string, discovered ...Choices) (*interaction.Session, error) {
|
||||
return new_session(settings, order, MenuTitle, false, discovered...)
|
||||
}
|
||||
|
||||
func New_application_session(settings config.Settings, order []string, title string, discovered ...Choices) (*interaction.Session, error) {
|
||||
return new_session(settings, order, title, true, discovered...)
|
||||
}
|
||||
|
||||
func new_session(settings config.Settings, order []string, title string, application bool, discovered ...Choices) (*interaction.Session, error) {
|
||||
if err := settings.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
form := interaction.Form{Type: interaction.TabForm, Title: MenuTitle}
|
||||
form := interaction.Form{Type: interaction.TabForm, Title: title}
|
||||
for _, section := range config.Ordered_sections(config.Sections(), order) {
|
||||
if section.Label == "" {
|
||||
continue
|
||||
@@ -50,6 +58,9 @@ func New_session(settings config.Settings, order []string, discovered ...Choices
|
||||
var append_section func(config.Section, string) error
|
||||
append_section = func(section config.Section, group string) error {
|
||||
for _, definition := range section.Settings {
|
||||
if application && !config.Application_setting(definition.Path) {
|
||||
continue
|
||||
}
|
||||
control, err := make_control(definition, settings, discovered)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -76,6 +87,9 @@ func New_session(settings config.Settings, order []string, discovered ...Choices
|
||||
if err := append_section(section, ""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(tab.Controls) == 0 {
|
||||
continue
|
||||
}
|
||||
tab.Controls = append(tab.Controls, interaction.Control{ID: "ok", Label: "OK", Kind: interaction.Button, Action: "save"}, interaction.Control{ID: "cancel", Label: "Cancel", Kind: interaction.Button, Action: "cancel"})
|
||||
form.Tabs = append(form.Tabs, tab)
|
||||
}
|
||||
@@ -137,15 +151,25 @@ func make_control(definition config.Definition, settings config.Settings, discov
|
||||
}
|
||||
|
||||
func Settings(session *interaction.Session, base config.Settings) (config.Settings, error) {
|
||||
return settings_values(session, base, false)
|
||||
}
|
||||
|
||||
func Application_settings(session *interaction.Session, base config.Settings) (config.Settings, error) {
|
||||
return settings_values(session, base, true)
|
||||
}
|
||||
|
||||
func settings_values(session *interaction.Session, base config.Settings, application bool) (config.Settings, error) {
|
||||
values := session.Values()
|
||||
// Visibility follows the form's selected driver, including unsaved switches.
|
||||
driver := values["presentation.speech.driver"]
|
||||
if driver.Selected < 0 || driver.Selected >= len(driver.Choices) {
|
||||
driver, hasDriver := values["presentation.speech.driver"]
|
||||
if (!application && !hasDriver) || (hasDriver && (driver.Selected < 0 || driver.Selected >= len(driver.Choices))) {
|
||||
return config.Settings{}, fmt.Errorf("invalid speech driver selection")
|
||||
}
|
||||
base.Presentation.Speech.Driver = driver.Choices[driver.Selected].ID
|
||||
if hasDriver {
|
||||
base.Presentation.Speech.Driver = driver.Choices[driver.Selected].ID
|
||||
}
|
||||
for _, definition := range config.Definitions() {
|
||||
if !definition.Available(base) {
|
||||
if !definition.Available(base) || (application && !config.Application_setting(definition.Path)) {
|
||||
continue
|
||||
}
|
||||
control, ok := values[definition.Path]
|
||||
|
||||
Reference in new issue
Block a user