From d9e23b9ebfa58df8ee6c12327036ca128818f458 Mon Sep 17 00:00:00 2001 From: crschnick Date: Mon, 15 Apr 2024 04:40:50 +0000 Subject: [PATCH] Various fixes --- .../xpipe/app/browser/file/BrowserAlerts.java | 10 +-- .../app/comp/store/StoreCreationComp.java | 4 +- app/src/main/java/io/xpipe/app/core/App.java | 2 +- .../ContextualFileReferenceChoiceComp.java | 2 +- .../java/io/xpipe/app/prefs/AppPrefs.java | 16 ++-- .../xpipe/app/prefs/ExternalEditorType.java | 2 +- .../app/prefs/ExternalPasswordManager.java | 80 +++++++++++++++++++ .../app/prefs/PasswordManagerCategory.java | 29 ++++++- .../io/xpipe/app/util/DataStoreFormatter.java | 15 +++- .../io/xpipe/app/util/LicenseProvider.java | 4 +- .../java/io/xpipe/app/util/PlatformState.java | 3 +- .../java/io/xpipe/app/util/RdpConfig.java | 4 + .../java/io/xpipe/app/util/ScanAlert.java | 1 + .../app/util/SecretRetrievalStrategy.java | 12 ++- .../util/SecretRetrievalStrategyHelper.java | 3 +- .../xpipe/app/resources/style/header-bars.css | 15 ---- .../xpipe/app/resources/style/popup-menu.css | 15 ++++ .../io/xpipe/core/process/ShellControl.java | 7 +- lang/app/strings/fixed_en.properties | 1 + lang/app/strings/translations_de.properties | 11 ++- lang/app/strings/translations_en.properties | 10 ++- lang/app/strings/translations_es.properties | 5 ++ lang/app/strings/translations_fr.properties | 5 ++ lang/app/strings/translations_it.properties | 5 ++ lang/app/strings/translations_ja.properties | 5 ++ lang/app/strings/translations_nl.properties | 5 ++ lang/app/strings/translations_pt.properties | 5 ++ lang/app/strings/translations_ru.properties | 5 ++ lang/app/strings/translations_tr.properties | 5 ++ lang/app/strings/translations_zh.properties | 5 ++ 30 files changed, 246 insertions(+), 45 deletions(-) create mode 100644 app/src/main/java/io/xpipe/app/prefs/ExternalPasswordManager.java diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserAlerts.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserAlerts.java index f7b359ec..413f777f 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserAlerts.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserAlerts.java @@ -16,14 +16,14 @@ public class BrowserAlerts { public static FileConflictChoice showFileConflictAlert(String file, boolean multiple) { var map = new LinkedHashMap(); - map.put(new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE), FileConflictChoice.CANCEL); + map.put(new ButtonType(AppI18n.get("cancel"), ButtonBar.ButtonData.CANCEL_CLOSE), FileConflictChoice.CANCEL); if (multiple) { - map.put(new ButtonType("Skip", ButtonBar.ButtonData.OTHER), FileConflictChoice.SKIP); - map.put(new ButtonType("Skip All", ButtonBar.ButtonData.OTHER), FileConflictChoice.SKIP_ALL); + map.put(new ButtonType(AppI18n.get("skip"), ButtonBar.ButtonData.OTHER), FileConflictChoice.SKIP); + map.put(new ButtonType(AppI18n.get("skipAll"), ButtonBar.ButtonData.OTHER), FileConflictChoice.SKIP_ALL); } - map.put(new ButtonType("Replace", ButtonBar.ButtonData.OTHER), FileConflictChoice.REPLACE); + map.put(new ButtonType(AppI18n.get("replace"), ButtonBar.ButtonData.OTHER), FileConflictChoice.REPLACE); if (multiple) { - map.put(new ButtonType("Replace All", ButtonBar.ButtonData.OTHER), FileConflictChoice.REPLACE_ALL); + map.put(new ButtonType(AppI18n.get("replaceAll"), ButtonBar.ButtonData.OTHER), FileConflictChoice.REPLACE_ALL); } return AppWindowHelper.showBlockingAlert(alert -> { alert.setTitle(AppI18n.get("fileConflictAlertTitle")); diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreCreationComp.java b/app/src/main/java/io/xpipe/app/comp/store/StoreCreationComp.java index 9a32e93f..19004d7a 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreCreationComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreCreationComp.java @@ -212,8 +212,8 @@ public class StoreCreationComp extends DialogComp { .setContent(AppWindowHelper.alertContentText(AppI18n.get("confirmInvalidStoreContent"))); alert.setAlertType(Alert.AlertType.CONFIRMATION); alert.getButtonTypes().clear(); - alert.getButtonTypes().add(new ButtonType("Retry", ButtonBar.ButtonData.CANCEL_CLOSE)); - alert.getButtonTypes().add(new ButtonType("Skip", ButtonBar.ButtonData.OK_DONE)); + alert.getButtonTypes().add(new ButtonType(AppI18n.get("retry"), ButtonBar.ButtonData.CANCEL_CLOSE)); + alert.getButtonTypes().add(new ButtonType(AppI18n.get("skip"), ButtonBar.ButtonData.OK_DONE)); }) .map(b -> b.getButtonData().isDefaultButton()) .orElse(false); diff --git a/app/src/main/java/io/xpipe/app/core/App.java b/app/src/main/java/io/xpipe/app/core/App.java index df6873f9..855ac5f2 100644 --- a/app/src/main/java/io/xpipe/app/core/App.java +++ b/app/src/main/java/io/xpipe/app/core/App.java @@ -65,7 +65,7 @@ public class App extends Application { "XPipe %s (%s)", t.getValue(), AppProperties.get().getVersion()); var prefix = AppProperties.get().isStaging() ? "[Public Test Build, Not a proper release] " : ""; var suffix = u.getValue() != null - ? AppI18n.get("updateReadyTitle", u.getValue().getVersion()) + ? " " + AppI18n.get("updateReadyTitle", u.getValue().getVersion()) : ""; return prefix + base + suffix; }, diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/ContextualFileReferenceChoiceComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/ContextualFileReferenceChoiceComp.java index 1921ea75..d65e3d30 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/ContextualFileReferenceChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/ContextualFileReferenceChoiceComp.java @@ -84,7 +84,7 @@ public class ContextualFileReferenceChoiceComp extends Comp> var gitShareButton = new ButtonComp(null, new FontIcon("mdi2g-git"), () -> { if (!AppPrefs.get().enableGitStorage().get()) { AppLayoutModel.get().selectSettings(); - AppPrefs.get().selectCategory(3); + AppPrefs.get().selectCategory("synchronization"); return; } diff --git a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java index 0dedd5a3..b54e05ad 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java @@ -139,15 +139,15 @@ public class AppPrefs { new AboutCategory(), new SystemCategory(), new AppearanceCategory(), + new SyncCategory(), + new VaultCategory(), + new PasswordManagerCategory(), + new SecurityCategory(), new TerminalCategory(), new EditorCategory(), new RdpCategory(), - new SyncCategory(), - new VaultCategory(), new SshCategory(), new LocalShellCategory(), - new SecurityCategory(), - new PasswordManagerCategory(), new TroubleshootCategory(), new DeveloperCategory()); var selected = AppCache.get("selectedPrefsCategory", Integer.class, () -> 0); @@ -505,10 +505,12 @@ public class AppPrefs { } } - public void selectCategory(int selected) { + public void selectCategory(String id) { AppLayoutModel.get().selectSettings(); - var index = selected >= 0 && selected < categories.size() ? selected : 0; - selectedCategory.setValue(categories.get(index)); + var found = categories.stream().filter(appPrefsCategory -> appPrefsCategory.getId().equals(id)).findFirst(); + found.ifPresent(appPrefsCategory -> { + selectedCategory.setValue(appPrefsCategory); + }); } public String passwordManagerString(String key) { diff --git a/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java b/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java index 6c1827b8..777e797f 100644 --- a/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java +++ b/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java @@ -241,7 +241,7 @@ public interface ExternalEditorType extends PrefsChoiceValue { public WindowsType(String id, String executable, boolean detach) { super(id, executable); - this.detach = true; + this.detach = detach; } @Override diff --git a/app/src/main/java/io/xpipe/app/prefs/ExternalPasswordManager.java b/app/src/main/java/io/xpipe/app/prefs/ExternalPasswordManager.java new file mode 100644 index 00000000..3e30ed96 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/prefs/ExternalPasswordManager.java @@ -0,0 +1,80 @@ +package io.xpipe.app.prefs; + +import io.xpipe.app.ext.PrefsChoiceValue; +import io.xpipe.core.process.OsType; + +import java.util.List; +import java.util.stream.Stream; + +public interface ExternalPasswordManager extends PrefsChoiceValue { + + String getTemplate(); + + static ExternalPasswordManager BITWARDEN = new ExternalPasswordManager() { + @Override + public String getTemplate() { + return "bw get password $KEY --nointeraction --raw"; + } + + @Override + public String getId() { + return "bitwarden"; + } + }; + + static ExternalPasswordManager ONEPASSWORD = new ExternalPasswordManager() { + @Override + public String getTemplate() { + return "op read $KEY --force"; + } + + @Override + public String getId() { + return "1Password"; + } + }; + + static ExternalPasswordManager DASHLANE = new ExternalPasswordManager() { + @Override + public String getTemplate() { + return "dcli password --output console $KEY"; + } + + @Override + public String getId() { + return "Dashlane"; + } + }; + + + static ExternalPasswordManager LASTPASS = new ExternalPasswordManager() { + @Override + public String getTemplate() { + return "lpass show --password $KEY"; + } + + @Override + public String getId() { + return "LastPass"; + } + }; + + static ExternalPasswordManager MACOS_KEYCHAIN = new ExternalPasswordManager() { + @Override + public String getTemplate() { + return "security find-generic-password -w -l $KEY"; + } + + @Override + public String getId() { + return "macOS keychain"; + } + + @Override + public boolean isSelectable() { + return OsType.getLocal() == OsType.MACOS; + } + }; + + static List ALL = Stream.of(ONEPASSWORD, BITWARDEN, DASHLANE, LASTPASS, MACOS_KEYCHAIN).filter(externalPasswordManager -> externalPasswordManager.isSelectable()).toList(); +} diff --git a/app/src/main/java/io/xpipe/app/prefs/PasswordManagerCategory.java b/app/src/main/java/io/xpipe/app/prefs/PasswordManagerCategory.java index 9015318d..30cc22bd 100644 --- a/app/src/main/java/io/xpipe/app/prefs/PasswordManagerCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/PasswordManagerCategory.java @@ -2,6 +2,7 @@ package io.xpipe.app.prefs; import atlantafx.base.theme.Styles; import io.xpipe.app.comp.base.ButtonComp; +import io.xpipe.app.core.AppI18n; import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.impl.HorizontalComp; import io.xpipe.app.fxcomps.impl.TextFieldComp; @@ -14,6 +15,8 @@ import io.xpipe.core.store.LocalStore; import javafx.beans.property.SimpleStringProperty; import javafx.geometry.Insets; import javafx.geometry.Pos; +import javafx.scene.control.MenuButton; +import javafx.scene.control.MenuItem; import org.kordamp.ikonli.javafx.FontIcon; import java.util.List; @@ -25,6 +28,22 @@ public class PasswordManagerCategory extends AppPrefsCategory { return "passwordManager"; } + private Comp createTemplateChoice() { + return Comp.of(() -> { + var cb = new MenuButton(); + cb.textProperty().bind(AppI18n.observable("templates")); + ExternalPasswordManager.ALL.forEach(externalPasswordManager -> { + var m = new MenuItem(externalPasswordManager.toTranslatedString().getValue()); + m.setOnAction(event -> { + AppPrefs.get().passwordManagerCommand.set(externalPasswordManager.getTemplate()); + event.consume(); + }); + cb.getItems().add(m); + }); + return cb; + }); + } + @Override protected Comp create() { var prefs = AppPrefs.get(); @@ -48,6 +67,13 @@ public class PasswordManagerCategory extends AppPrefsCategory { }); }; + var c = new TextFieldComp(prefs.passwordManagerCommand, true).apply(struc -> struc.get().setPromptText("mypassmgr get $KEY")).minWidth(350); + var visit = createTemplateChoice(); + var choice = new HorizontalComp(List.of(c, visit)).apply(struc -> { + struc.get().setAlignment(Pos.CENTER_LEFT); + struc.get().setSpacing(10); + }); + var testPasswordManager = new HorizontalComp(List.of( new TextFieldComp(testPasswordManagerValue) .apply(struc -> struc.get().setPromptText("Enter password key")) @@ -63,8 +89,7 @@ public class PasswordManagerCategory extends AppPrefsCategory { .addTitle("passwordManager") .sub(new OptionsBuilder() .nameAndDescription("passwordManagerCommand") - .addComp(new TextFieldComp(prefs.passwordManagerCommand, true) - .apply(struc -> struc.get().setPromptText("mypassmgr get $KEY"))) + .addComp(choice) .nameAndDescription("passwordManagerCommandTest") .addComp(testPasswordManager)) .buildComp(); diff --git a/app/src/main/java/io/xpipe/app/util/DataStoreFormatter.java b/app/src/main/java/io/xpipe/app/util/DataStoreFormatter.java index 8d2496dd..fbbcfdfc 100644 --- a/app/src/main/java/io/xpipe/app/util/DataStoreFormatter.java +++ b/app/src/main/java/io/xpipe/app/util/DataStoreFormatter.java @@ -13,6 +13,17 @@ import java.util.function.IntFunction; public class DataStoreFormatter { + public static String formattedOsName(String osName) { + osName = osName.replaceAll("^Microsoft ", ""); + + var proRequired = !LicenseProvider.get().checkOsName(osName); + if (!proRequired) { + return osName; + } + + return "[Pro] " + osName; + } + public static ObservableValue shellInformation(StoreEntryWrapper w) { return BindingsHelper.map(w.getPersistentState(), o -> { if (o instanceof ShellStoreState s) { @@ -23,11 +34,11 @@ public class DataStoreFormatter { if (s.getShellDialect() != null && !s.getShellDialect().getDumbMode().supportsAnyPossibleInteraction()) { return s.getOsName() != null - ? s.getOsName() + ? formattedOsName(s.getOsName()) : s.getShellDialect().getDisplayName(); } - return s.isRunning() ? s.getOsName() : "Connection failed"; + return s.isRunning() ? formattedOsName(s.getOsName()) : "Connection failed"; } return "?"; diff --git a/app/src/main/java/io/xpipe/app/util/LicenseProvider.java b/app/src/main/java/io/xpipe/app/util/LicenseProvider.java index c0e28b8a..bb1bc20d 100644 --- a/app/src/main/java/io/xpipe/app/util/LicenseProvider.java +++ b/app/src/main/java/io/xpipe/app/util/LicenseProvider.java @@ -21,7 +21,9 @@ public abstract class LicenseProvider { public abstract LicensedFeature getFeature(String id); - public abstract void checkShellControl(String s); + public abstract boolean checkOsName(String name); + + public abstract void checkOsNameOrThrow(String s); public abstract void showLicenseAlert(LicenseRequiredException ex); diff --git a/app/src/main/java/io/xpipe/app/util/PlatformState.java b/app/src/main/java/io/xpipe/app/util/PlatformState.java index b6a21e84..401c6df2 100644 --- a/app/src/main/java/io/xpipe/app/util/PlatformState.java +++ b/app/src/main/java/io/xpipe/app/util/PlatformState.java @@ -1,6 +1,7 @@ package io.xpipe.app.util; import io.xpipe.app.core.check.AppSystemFontCheck; +import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.TrackEvent; import io.xpipe.app.prefs.AppPrefs; import io.xpipe.core.process.OsType; @@ -83,7 +84,7 @@ public enum PlatformState { : h.getMessage(); TrackEvent.warn(h.getMessage()); PlatformState.setCurrent(PlatformState.EXITED); - return Optional.of(new HeadlessException(msg)); + return Optional.of(ErrorEvent.expected(new HeadlessException(msg))); } catch (Throwable t) { TrackEvent.warn(t.getMessage()); PlatformState.setCurrent(PlatformState.EXITED); diff --git a/app/src/main/java/io/xpipe/app/util/RdpConfig.java b/app/src/main/java/io/xpipe/app/util/RdpConfig.java index e89b68ce..a6e1cc65 100644 --- a/app/src/main/java/io/xpipe/app/util/RdpConfig.java +++ b/app/src/main/java/io/xpipe/app/util/RdpConfig.java @@ -62,5 +62,9 @@ public class RdpConfig { public static class TypedValue { String type; String value; + + public static TypedValue string(String value) { + return new TypedValue("s", value); + } } } diff --git a/app/src/main/java/io/xpipe/app/util/ScanAlert.java b/app/src/main/java/io/xpipe/app/util/ScanAlert.java index 4f4e53cd..49864627 100644 --- a/app/src/main/java/io/xpipe/app/util/ScanAlert.java +++ b/app/src/main/java/io/xpipe/app/util/ScanAlert.java @@ -183,6 +183,7 @@ public class ScanAlert { } shellControl = newValue.getStore().control(); + shellControl.withoutLicenseCheck(); shellControl.start(); var a = applicable.apply(entry.get().get(), shellControl); diff --git a/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategy.java b/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategy.java index 448cbbeb..02db6adc 100644 --- a/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategy.java +++ b/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategy.java @@ -127,13 +127,23 @@ public interface SecretRetrievalStrategy { return new SecretQueryResult(null, true); } + String r; try (var cc = new LocalStore().control().command(cmd).start()) { - return new SecretQueryResult(InPlaceSecretValue.of(cc.readStdoutOrThrow()), false); + r = cc.readStdoutOrThrow(); } catch (Exception ex) { ErrorEvent.fromThrowable("Unable to retrieve password with command " + cmd, ex) .handle(); return new SecretQueryResult(null, true); } + + if (r.lines().count() > 1 || r.isBlank()) { + throw ErrorEvent.expected(new IllegalArgumentException("Received not exactly one output line:\n" + r + "\n\n" + + "XPipe requires your password manager command to output only the raw password." + + " If the output includes any formatting, messages, or your password key either matched multiple entries or none," + + " you will have to change the command and/or password key.")); + } + + return new SecretQueryResult(InPlaceSecretValue.of(r), false); } @Override diff --git a/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategyHelper.java b/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategyHelper.java index 96a659bf..5c8a20a8 100644 --- a/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategyHelper.java +++ b/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategyHelper.java @@ -46,7 +46,7 @@ public class SecretRetrievalStrategyHelper { .apply(struc -> struc.get().setPromptText("Password key")) .hgrow(), new ButtonComp(null, new FontIcon("mdomz-settings"), () -> { - AppPrefs.get().selectCategory(9); + AppPrefs.get().selectCategory("passwordManager"); App.getApp().getStage().requestFocus(); }) .grow(false, true))) @@ -54,6 +54,7 @@ public class SecretRetrievalStrategyHelper { return new OptionsBuilder() .name("passwordKey") .addComp(content, keyProperty) + .nonNull() .bind( () -> { return new SecretRetrievalStrategy.PasswordManager(keyProperty.getValue()); diff --git a/app/src/main/resources/io/xpipe/app/resources/style/header-bars.css b/app/src/main/resources/io/xpipe/app/resources/style/header-bars.css index a40eb5d4..05154ec4 100644 --- a/app/src/main/resources/io/xpipe/app/resources/style/header-bars.css +++ b/app/src/main/resources/io/xpipe/app/resources/style/header-bars.css @@ -74,21 +74,6 @@ -fx-border-width: 4; } -.store-header-bar .menu-button .context-menu > * > * { - -fx-padding: 5px 10px 5px 10px; -} - -.store-header-bar .menu-button .context-menu > * { - -fx-padding: 0; -} - -.store-header-bar .menu-button .context-menu { - -fx-padding: 3px; - -fx-background-radius: 4px; - -fx-border-radius: 4px; - -fx-border-color: -color-neutral-muted; -} - .store-creation-bar, .store-sort-bar, .store-category-bar { -fx-background-radius: 0 4px 4px 0; -fx-border-radius: 0 4px 4px 0; diff --git a/app/src/main/resources/io/xpipe/app/resources/style/popup-menu.css b/app/src/main/resources/io/xpipe/app/resources/style/popup-menu.css index 722e067a..fc045503 100644 --- a/app/src/main/resources/io/xpipe/app/resources/style/popup-menu.css +++ b/app/src/main/resources/io/xpipe/app/resources/style/popup-menu.css @@ -1,3 +1,18 @@ +.menu-button .context-menu > * > * { + -fx-padding: 5px 10px 5px 10px; +} + +.menu-button .context-menu > * { + -fx-padding: 0; +} + +.menu-button .context-menu { + -fx-padding: 3px; + -fx-background-radius: 4px; + -fx-border-radius: 4px; + -fx-border-color: -color-neutral-muted; +} + .context-menu > * > * { -fx-padding: 3px 10px 3px 10px; -fx-background-radius: 1px; diff --git a/core/src/main/java/io/xpipe/core/process/ShellControl.java b/core/src/main/java/io/xpipe/core/process/ShellControl.java index 958c52dd..92e2b511 100644 --- a/core/src/main/java/io/xpipe/core/process/ShellControl.java +++ b/core/src/main/java/io/xpipe/core/process/ShellControl.java @@ -67,7 +67,12 @@ public interface ShellControl extends ProcessControl { } default ShellControl withShellStateFail(StatefulDataStore store) { - return onStartupFail(shellControl -> { + return onStartupFail(t -> { + // Ugly + if (t.getClass().getSimpleName().equals("LicenseRequiredException")) { + return; + } + var s = store.getState(); s.setRunning(false); store.setState(s); diff --git a/lang/app/strings/fixed_en.properties b/lang/app/strings/fixed_en.properties index ac98cef8..24ee529e 100644 --- a/lang/app/strings/fixed_en.properties +++ b/lang/app/strings/fixed_en.properties @@ -57,3 +57,4 @@ github=GitHub mstsc=Microsoft Terminal Services Client (MSTSC) remmina=Remmina microsoftRemoteDesktopApp=Microsoft Remote Desktop.app +bitwarden=Bitwarden diff --git a/lang/app/strings/translations_de.properties b/lang/app/strings/translations_de.properties index 09a6356e..3888e1e0 100644 --- a/lang/app/strings/translations_de.properties +++ b/lang/app/strings/translations_de.properties @@ -326,7 +326,7 @@ system=System application=Anwendung storage=Speicherung runOnStartup=Beim Starten ausführen -closeBehaviour=Verhalten schließen +closeBehaviour=Exit-Verhalten closeBehaviourDescription=Legt fest, wie XPipe beim Schließen des Hauptfensters vorgehen soll. language=Sprache languageDescription=Die zu verwendende Anzeigesprache.\n\nBeachte, dass die automatischen Übersetzungen als Grundlage dienen und von den Mitwirkenden manuell korrigiert und verbessert werden. Du kannst die Übersetzungsarbeit auch unterstützen, indem du Übersetzungsverbesserungen auf GitHub einreichst. @@ -366,7 +366,8 @@ developerModeDescription=Wenn diese Option aktiviert ist, hast du Zugriff auf ei editor=Editor custom=Benutzerdefiniert passwordManagerCommand=Passwortmanager-Befehl -passwordManagerCommandDescription=Der Befehl, der ausgeführt werden soll, um Passwörter abzurufen. Der Platzhalterstring $KEY wird beim Aufruf durch den zitierten Passwortschlüssel ersetzt. Dies sollte deinen Passwortmanager CLI aufrufen, um das Passwort auf stdout auszugeben, z. B. mypassmgr get $KEY.\n\nDu kannst den Schlüssel dann so einstellen, dass er immer dann abgefragt wird, wenn du eine Verbindung aufbaust, die ein Passwort erfordert. +#custom +passwordManagerCommandDescription=Der Befehl, der ausgeführt werden soll, um Passwörter abzurufen. Der Platzhalterstring $KEY wird beim Aufruf durch den Passwortschlüssel mit Anführungszeichen ersetzt. Dies sollte deinen Passwortmanager CLI aufrufen, um das Passwort auf stdout auszugeben, z. B. mypassmgr get $KEY.\n\nDu kannst den Schlüssel dann so einstellen, dass er immer dann abgefragt wird, wenn du eine Verbindung aufbaust, die ein Passwort erfordert. passwordManagerCommandTest=Passwort-Manager testen passwordManagerCommandTestDescription=Du kannst hier testen, ob die Ausgabe korrekt aussieht, wenn du einen Passwortmanager-Befehl eingerichtet hast. Der Befehl sollte nur das Passwort selbst auf stdout ausgeben, keine andere Formatierung sollte in der Ausgabe enthalten sein. preferEditorTabs=Lieber neue Tabs öffnen @@ -434,3 +435,9 @@ modified=Geändert isOnlySupported=wird nur mit einer professionellen Lizenz unterstützt areOnlySupported=werden nur mit einer professionellen Lizenz unterstützt updateReadyTitle=Update auf $VERSION$ bereit +#custom +templates=Vorlagen +retry=Wiederholen +retryAll=Alle Versuche wiederholen +replace=Ersetzen +replaceAll=Ersetze alles diff --git a/lang/app/strings/translations_en.properties b/lang/app/strings/translations_en.properties index 183b35ee..2e29a88c 100644 --- a/lang/app/strings/translations_en.properties +++ b/lang/app/strings/translations_en.properties @@ -328,8 +328,8 @@ system=System application=Application storage=Storage runOnStartup=Run on startup -#context: setting -closeBehaviour=Close behaviour +#context: title +closeBehaviour=Exit behaviour closeBehaviourDescription=Controls how XPipe should proceed upon closing its main window. language=Language languageDescription=The display language to use.\n\nNote that the translations use automatically generated translations as a base and are manually fixed and improved by contributors. You can also help the translation effort by submitting translation fixes on GitHub. @@ -439,3 +439,9 @@ modified=Modified isOnlySupported=is only supported with a professional license areOnlySupported=are only supported with a professional license updateReadyTitle=Update to $VERSION$ ready +#context: digital template +templates=Templates +retry=Retry +retryAll=Retry all +replace=Replace +replaceAll=Replace all diff --git a/lang/app/strings/translations_es.properties b/lang/app/strings/translations_es.properties index cc75dbbc..4cf15552 100644 --- a/lang/app/strings/translations_es.properties +++ b/lang/app/strings/translations_es.properties @@ -424,3 +424,8 @@ modified=Modificado isOnlySupported=sólo es compatible con una licencia profesional areOnlySupported=sólo son compatibles con una licencia profesional updateReadyTitle=Actualiza a $VERSION$ ready +templates=Plantillas +retry=Reintentar +retryAll=Reintentar todo +replace=Sustituye +replaceAll=Sustituir todo diff --git a/lang/app/strings/translations_fr.properties b/lang/app/strings/translations_fr.properties index b1ee2eb1..ac645962 100644 --- a/lang/app/strings/translations_fr.properties +++ b/lang/app/strings/translations_fr.properties @@ -424,3 +424,8 @@ modified=Modifié isOnlySupported=n'est pris en charge qu'avec une licence professionnelle areOnlySupported=ne sont pris en charge qu'avec une licence professionnelle updateReadyTitle=Mise à jour de $VERSION$ ready +templates=Modèles +retry=Réessayer +retryAll=Réessayer tout +replace=Remplacer +replaceAll=Remplacer tout diff --git a/lang/app/strings/translations_it.properties b/lang/app/strings/translations_it.properties index 9a08d8a1..d6d58598 100644 --- a/lang/app/strings/translations_it.properties +++ b/lang/app/strings/translations_it.properties @@ -424,3 +424,8 @@ modified=Modificato isOnlySupported=è supportato solo con una licenza professionale areOnlySupported=sono supportati solo con una licenza professionale updateReadyTitle=Aggiornamento a $VERSION$ ready +templates=Modelli +retry=Riprova +retryAll=Riprova tutti +replace=Sostituire +replaceAll=Sostituisci tutto diff --git a/lang/app/strings/translations_ja.properties b/lang/app/strings/translations_ja.properties index 7e341d19..160933c6 100644 --- a/lang/app/strings/translations_ja.properties +++ b/lang/app/strings/translations_ja.properties @@ -424,3 +424,8 @@ modified=変更された isOnlySupported=プロフェッショナルライセンスでのみサポートされる areOnlySupported=プロフェッショナルライセンスでのみサポートされる updateReadyTitle=$VERSION$ に更新 +templates=テンプレート +retry=リトライ +retryAll=すべて再試行する +replace=置き換える +replaceAll=すべて置き換える diff --git a/lang/app/strings/translations_nl.properties b/lang/app/strings/translations_nl.properties index 5543c107..c41fb31c 100644 --- a/lang/app/strings/translations_nl.properties +++ b/lang/app/strings/translations_nl.properties @@ -424,3 +424,8 @@ modified=Gewijzigd isOnlySupported=wordt alleen ondersteund met een professionele licentie areOnlySupported=worden alleen ondersteund met een professionele licentie updateReadyTitle=Bijwerken naar $VERSION$ klaar +templates=Sjablonen +retry=Opnieuw proberen +retryAll=Alles opnieuw proberen +replace=Vervangen +replaceAll=Alles vervangen diff --git a/lang/app/strings/translations_pt.properties b/lang/app/strings/translations_pt.properties index 3a3d9fff..3411aea1 100644 --- a/lang/app/strings/translations_pt.properties +++ b/lang/app/strings/translations_pt.properties @@ -424,3 +424,8 @@ modified=Modificado isOnlySupported=só é suportado com uma licença profissional areOnlySupported=só são suportados com uma licença profissional updateReadyTitle=Actualiza para $VERSION$ ready +templates=Modelos +retry=Repetir +retryAll=Repetir tudo +replace=Substitui +replaceAll=Substitui tudo diff --git a/lang/app/strings/translations_ru.properties b/lang/app/strings/translations_ru.properties index fb17a616..8b791e1d 100644 --- a/lang/app/strings/translations_ru.properties +++ b/lang/app/strings/translations_ru.properties @@ -424,3 +424,8 @@ modified=Изменено isOnlySupported=поддерживается только при наличии профессиональной лицензии areOnlySupported=поддерживаются только с профессиональной лицензией updateReadyTitle=Обновление на $VERSION$ готово +templates=Шаблоны +retry=Retry +retryAll=Повторите все попытки +replace=Замените +replaceAll=Заменить все diff --git a/lang/app/strings/translations_tr.properties b/lang/app/strings/translations_tr.properties index f35f7f72..59259650 100644 --- a/lang/app/strings/translations_tr.properties +++ b/lang/app/strings/translations_tr.properties @@ -424,3 +424,8 @@ modified=Değiştirilmiş isOnlySupported=yalnızca profesyonel lisans ile desteklenir areOnlySupported=yalnızca profesyonel lisans ile desteklenir updateReadyTitle=$VERSION$ için güncelleme hazır +templates=Şablonlar +retry=Yeniden Dene +retryAll=Tümünü yeniden dene +replace=Değiştirin +replaceAll=Tümünü değiştirin diff --git a/lang/app/strings/translations_zh.properties b/lang/app/strings/translations_zh.properties index 23075f2c..045eb97a 100644 --- a/lang/app/strings/translations_zh.properties +++ b/lang/app/strings/translations_zh.properties @@ -424,3 +424,8 @@ modified=已修改 isOnlySupported=只有专业许可证才支持 areOnlySupported=只有专业许可证才支持 updateReadyTitle=更新至$VERSION$ ready +templates=模板 +retry=重试 +retryAll=全部重试 +replace=替换 +replaceAll=全部替换