Various fixes

This commit is contained in:
crschnick 2024-04-15 04:40:50 +00:00
parent 7bce1e8f3b
commit d9e23b9ebf
30 changed files with 246 additions and 45 deletions

View file

@ -16,14 +16,14 @@ public class BrowserAlerts {
public static FileConflictChoice showFileConflictAlert(String file, boolean multiple) { public static FileConflictChoice showFileConflictAlert(String file, boolean multiple) {
var map = new LinkedHashMap<ButtonType, FileConflictChoice>(); var map = new LinkedHashMap<ButtonType, FileConflictChoice>();
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) { if (multiple) {
map.put(new ButtonType("Skip", ButtonBar.ButtonData.OTHER), FileConflictChoice.SKIP); map.put(new ButtonType(AppI18n.get("skip"), ButtonBar.ButtonData.OTHER), FileConflictChoice.SKIP);
map.put(new ButtonType("Skip All", ButtonBar.ButtonData.OTHER), FileConflictChoice.SKIP_ALL); 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) { 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 -> { return AppWindowHelper.showBlockingAlert(alert -> {
alert.setTitle(AppI18n.get("fileConflictAlertTitle")); alert.setTitle(AppI18n.get("fileConflictAlertTitle"));

View file

@ -212,8 +212,8 @@ public class StoreCreationComp extends DialogComp {
.setContent(AppWindowHelper.alertContentText(AppI18n.get("confirmInvalidStoreContent"))); .setContent(AppWindowHelper.alertContentText(AppI18n.get("confirmInvalidStoreContent")));
alert.setAlertType(Alert.AlertType.CONFIRMATION); alert.setAlertType(Alert.AlertType.CONFIRMATION);
alert.getButtonTypes().clear(); alert.getButtonTypes().clear();
alert.getButtonTypes().add(new ButtonType("Retry", ButtonBar.ButtonData.CANCEL_CLOSE)); alert.getButtonTypes().add(new ButtonType(AppI18n.get("retry"), ButtonBar.ButtonData.CANCEL_CLOSE));
alert.getButtonTypes().add(new ButtonType("Skip", ButtonBar.ButtonData.OK_DONE)); alert.getButtonTypes().add(new ButtonType(AppI18n.get("skip"), ButtonBar.ButtonData.OK_DONE));
}) })
.map(b -> b.getButtonData().isDefaultButton()) .map(b -> b.getButtonData().isDefaultButton())
.orElse(false); .orElse(false);

View file

@ -65,7 +65,7 @@ public class App extends Application {
"XPipe %s (%s)", t.getValue(), AppProperties.get().getVersion()); "XPipe %s (%s)", t.getValue(), AppProperties.get().getVersion());
var prefix = AppProperties.get().isStaging() ? "[Public Test Build, Not a proper release] " : ""; var prefix = AppProperties.get().isStaging() ? "[Public Test Build, Not a proper release] " : "";
var suffix = u.getValue() != null var suffix = u.getValue() != null
? AppI18n.get("updateReadyTitle", u.getValue().getVersion()) ? " " + AppI18n.get("updateReadyTitle", u.getValue().getVersion())
: ""; : "";
return prefix + base + suffix; return prefix + base + suffix;
}, },

View file

@ -84,7 +84,7 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
var gitShareButton = new ButtonComp(null, new FontIcon("mdi2g-git"), () -> { var gitShareButton = new ButtonComp(null, new FontIcon("mdi2g-git"), () -> {
if (!AppPrefs.get().enableGitStorage().get()) { if (!AppPrefs.get().enableGitStorage().get()) {
AppLayoutModel.get().selectSettings(); AppLayoutModel.get().selectSettings();
AppPrefs.get().selectCategory(3); AppPrefs.get().selectCategory("synchronization");
return; return;
} }

View file

@ -139,15 +139,15 @@ public class AppPrefs {
new AboutCategory(), new AboutCategory(),
new SystemCategory(), new SystemCategory(),
new AppearanceCategory(), new AppearanceCategory(),
new SyncCategory(),
new VaultCategory(),
new PasswordManagerCategory(),
new SecurityCategory(),
new TerminalCategory(), new TerminalCategory(),
new EditorCategory(), new EditorCategory(),
new RdpCategory(), new RdpCategory(),
new SyncCategory(),
new VaultCategory(),
new SshCategory(), new SshCategory(),
new LocalShellCategory(), new LocalShellCategory(),
new SecurityCategory(),
new PasswordManagerCategory(),
new TroubleshootCategory(), new TroubleshootCategory(),
new DeveloperCategory()); new DeveloperCategory());
var selected = AppCache.get("selectedPrefsCategory", Integer.class, () -> 0); 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(); AppLayoutModel.get().selectSettings();
var index = selected >= 0 && selected < categories.size() ? selected : 0; var found = categories.stream().filter(appPrefsCategory -> appPrefsCategory.getId().equals(id)).findFirst();
selectedCategory.setValue(categories.get(index)); found.ifPresent(appPrefsCategory -> {
selectedCategory.setValue(appPrefsCategory);
});
} }
public String passwordManagerString(String key) { public String passwordManagerString(String key) {

View file

@ -241,7 +241,7 @@ public interface ExternalEditorType extends PrefsChoiceValue {
public WindowsType(String id, String executable, boolean detach) { public WindowsType(String id, String executable, boolean detach) {
super(id, executable); super(id, executable);
this.detach = true; this.detach = detach;
} }
@Override @Override

View file

@ -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<ExternalPasswordManager> ALL = Stream.of(ONEPASSWORD, BITWARDEN, DASHLANE, LASTPASS, MACOS_KEYCHAIN).filter(externalPasswordManager -> externalPasswordManager.isSelectable()).toList();
}

View file

@ -2,6 +2,7 @@ package io.xpipe.app.prefs;
import atlantafx.base.theme.Styles; import atlantafx.base.theme.Styles;
import io.xpipe.app.comp.base.ButtonComp; import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.impl.HorizontalComp; import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.impl.TextFieldComp; import io.xpipe.app.fxcomps.impl.TextFieldComp;
@ -14,6 +15,8 @@ import io.xpipe.core.store.LocalStore;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem;
import org.kordamp.ikonli.javafx.FontIcon; import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List; import java.util.List;
@ -25,6 +28,22 @@ public class PasswordManagerCategory extends AppPrefsCategory {
return "passwordManager"; 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 @Override
protected Comp<?> create() { protected Comp<?> create() {
var prefs = AppPrefs.get(); 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( var testPasswordManager = new HorizontalComp(List.of(
new TextFieldComp(testPasswordManagerValue) new TextFieldComp(testPasswordManagerValue)
.apply(struc -> struc.get().setPromptText("Enter password key")) .apply(struc -> struc.get().setPromptText("Enter password key"))
@ -63,8 +89,7 @@ public class PasswordManagerCategory extends AppPrefsCategory {
.addTitle("passwordManager") .addTitle("passwordManager")
.sub(new OptionsBuilder() .sub(new OptionsBuilder()
.nameAndDescription("passwordManagerCommand") .nameAndDescription("passwordManagerCommand")
.addComp(new TextFieldComp(prefs.passwordManagerCommand, true) .addComp(choice)
.apply(struc -> struc.get().setPromptText("mypassmgr get $KEY")))
.nameAndDescription("passwordManagerCommandTest") .nameAndDescription("passwordManagerCommandTest")
.addComp(testPasswordManager)) .addComp(testPasswordManager))
.buildComp(); .buildComp();

View file

@ -13,6 +13,17 @@ import java.util.function.IntFunction;
public class DataStoreFormatter { 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<String> shellInformation(StoreEntryWrapper w) { public static ObservableValue<String> shellInformation(StoreEntryWrapper w) {
return BindingsHelper.map(w.getPersistentState(), o -> { return BindingsHelper.map(w.getPersistentState(), o -> {
if (o instanceof ShellStoreState s) { if (o instanceof ShellStoreState s) {
@ -23,11 +34,11 @@ public class DataStoreFormatter {
if (s.getShellDialect() != null if (s.getShellDialect() != null
&& !s.getShellDialect().getDumbMode().supportsAnyPossibleInteraction()) { && !s.getShellDialect().getDumbMode().supportsAnyPossibleInteraction()) {
return s.getOsName() != null return s.getOsName() != null
? s.getOsName() ? formattedOsName(s.getOsName())
: s.getShellDialect().getDisplayName(); : s.getShellDialect().getDisplayName();
} }
return s.isRunning() ? s.getOsName() : "Connection failed"; return s.isRunning() ? formattedOsName(s.getOsName()) : "Connection failed";
} }
return "?"; return "?";

View file

@ -21,7 +21,9 @@ public abstract class LicenseProvider {
public abstract LicensedFeature getFeature(String id); 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); public abstract void showLicenseAlert(LicenseRequiredException ex);

View file

@ -1,6 +1,7 @@
package io.xpipe.app.util; package io.xpipe.app.util;
import io.xpipe.app.core.check.AppSystemFontCheck; import io.xpipe.app.core.check.AppSystemFontCheck;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent; import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
@ -83,7 +84,7 @@ public enum PlatformState {
: h.getMessage(); : h.getMessage();
TrackEvent.warn(h.getMessage()); TrackEvent.warn(h.getMessage());
PlatformState.setCurrent(PlatformState.EXITED); PlatformState.setCurrent(PlatformState.EXITED);
return Optional.of(new HeadlessException(msg)); return Optional.of(ErrorEvent.expected(new HeadlessException(msg)));
} catch (Throwable t) { } catch (Throwable t) {
TrackEvent.warn(t.getMessage()); TrackEvent.warn(t.getMessage());
PlatformState.setCurrent(PlatformState.EXITED); PlatformState.setCurrent(PlatformState.EXITED);

View file

@ -62,5 +62,9 @@ public class RdpConfig {
public static class TypedValue { public static class TypedValue {
String type; String type;
String value; String value;
public static TypedValue string(String value) {
return new TypedValue("s", value);
}
} }
} }

View file

@ -183,6 +183,7 @@ public class ScanAlert {
} }
shellControl = newValue.getStore().control(); shellControl = newValue.getStore().control();
shellControl.withoutLicenseCheck();
shellControl.start(); shellControl.start();
var a = applicable.apply(entry.get().get(), shellControl); var a = applicable.apply(entry.get().get(), shellControl);

View file

@ -127,13 +127,23 @@ public interface SecretRetrievalStrategy {
return new SecretQueryResult(null, true); return new SecretQueryResult(null, true);
} }
String r;
try (var cc = new LocalStore().control().command(cmd).start()) { try (var cc = new LocalStore().control().command(cmd).start()) {
return new SecretQueryResult(InPlaceSecretValue.of(cc.readStdoutOrThrow()), false); r = cc.readStdoutOrThrow();
} catch (Exception ex) { } catch (Exception ex) {
ErrorEvent.fromThrowable("Unable to retrieve password with command " + cmd, ex) ErrorEvent.fromThrowable("Unable to retrieve password with command " + cmd, ex)
.handle(); .handle();
return new SecretQueryResult(null, true); 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 @Override

View file

@ -46,7 +46,7 @@ public class SecretRetrievalStrategyHelper {
.apply(struc -> struc.get().setPromptText("Password key")) .apply(struc -> struc.get().setPromptText("Password key"))
.hgrow(), .hgrow(),
new ButtonComp(null, new FontIcon("mdomz-settings"), () -> { new ButtonComp(null, new FontIcon("mdomz-settings"), () -> {
AppPrefs.get().selectCategory(9); AppPrefs.get().selectCategory("passwordManager");
App.getApp().getStage().requestFocus(); App.getApp().getStage().requestFocus();
}) })
.grow(false, true))) .grow(false, true)))
@ -54,6 +54,7 @@ public class SecretRetrievalStrategyHelper {
return new OptionsBuilder() return new OptionsBuilder()
.name("passwordKey") .name("passwordKey")
.addComp(content, keyProperty) .addComp(content, keyProperty)
.nonNull()
.bind( .bind(
() -> { () -> {
return new SecretRetrievalStrategy.PasswordManager(keyProperty.getValue()); return new SecretRetrievalStrategy.PasswordManager(keyProperty.getValue());

View file

@ -74,21 +74,6 @@
-fx-border-width: 4; -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 { .store-creation-bar, .store-sort-bar, .store-category-bar {
-fx-background-radius: 0 4px 4px 0; -fx-background-radius: 0 4px 4px 0;
-fx-border-radius: 0 4px 4px 0; -fx-border-radius: 0 4px 4px 0;

View file

@ -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 > * > * { .context-menu > * > * {
-fx-padding: 3px 10px 3px 10px; -fx-padding: 3px 10px 3px 10px;
-fx-background-radius: 1px; -fx-background-radius: 1px;

View file

@ -67,7 +67,12 @@ public interface ShellControl extends ProcessControl {
} }
default <T extends ShellStoreState> ShellControl withShellStateFail(StatefulDataStore<T> store) { default <T extends ShellStoreState> ShellControl withShellStateFail(StatefulDataStore<T> store) {
return onStartupFail(shellControl -> { return onStartupFail(t -> {
// Ugly
if (t.getClass().getSimpleName().equals("LicenseRequiredException")) {
return;
}
var s = store.getState(); var s = store.getState();
s.setRunning(false); s.setRunning(false);
store.setState(s); store.setState(s);

View file

@ -57,3 +57,4 @@ github=GitHub
mstsc=Microsoft Terminal Services Client (MSTSC) mstsc=Microsoft Terminal Services Client (MSTSC)
remmina=Remmina remmina=Remmina
microsoftRemoteDesktopApp=Microsoft Remote Desktop.app microsoftRemoteDesktopApp=Microsoft Remote Desktop.app
bitwarden=Bitwarden

View file

@ -326,7 +326,7 @@ system=System
application=Anwendung application=Anwendung
storage=Speicherung storage=Speicherung
runOnStartup=Beim Starten ausführen runOnStartup=Beim Starten ausführen
closeBehaviour=Verhalten schließen closeBehaviour=Exit-Verhalten
closeBehaviourDescription=Legt fest, wie XPipe beim Schließen des Hauptfensters vorgehen soll. closeBehaviourDescription=Legt fest, wie XPipe beim Schließen des Hauptfensters vorgehen soll.
language=Sprache 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. 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 editor=Editor
custom=Benutzerdefiniert custom=Benutzerdefiniert
passwordManagerCommand=Passwortmanager-Befehl 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 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. 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 preferEditorTabs=Lieber neue Tabs öffnen
@ -434,3 +435,9 @@ modified=Geändert
isOnlySupported=wird nur mit einer professionellen Lizenz unterstützt isOnlySupported=wird nur mit einer professionellen Lizenz unterstützt
areOnlySupported=werden nur mit einer professionellen Lizenz unterstützt areOnlySupported=werden nur mit einer professionellen Lizenz unterstützt
updateReadyTitle=Update auf $VERSION$ bereit updateReadyTitle=Update auf $VERSION$ bereit
#custom
templates=Vorlagen
retry=Wiederholen
retryAll=Alle Versuche wiederholen
replace=Ersetzen
replaceAll=Ersetze alles

View file

@ -328,8 +328,8 @@ system=System
application=Application application=Application
storage=Storage storage=Storage
runOnStartup=Run on startup runOnStartup=Run on startup
#context: setting #context: title
closeBehaviour=Close behaviour closeBehaviour=Exit behaviour
closeBehaviourDescription=Controls how XPipe should proceed upon closing its main window. closeBehaviourDescription=Controls how XPipe should proceed upon closing its main window.
language=Language 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. 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 isOnlySupported=is only supported with a professional license
areOnlySupported=are only supported with a professional license areOnlySupported=are only supported with a professional license
updateReadyTitle=Update to $VERSION$ ready updateReadyTitle=Update to $VERSION$ ready
#context: digital template
templates=Templates
retry=Retry
retryAll=Retry all
replace=Replace
replaceAll=Replace all

View file

@ -424,3 +424,8 @@ modified=Modificado
isOnlySupported=sólo es compatible con una licencia profesional isOnlySupported=sólo es compatible con una licencia profesional
areOnlySupported=sólo son compatibles con una licencia profesional areOnlySupported=sólo son compatibles con una licencia profesional
updateReadyTitle=Actualiza a $VERSION$ ready updateReadyTitle=Actualiza a $VERSION$ ready
templates=Plantillas
retry=Reintentar
retryAll=Reintentar todo
replace=Sustituye
replaceAll=Sustituir todo

View file

@ -424,3 +424,8 @@ modified=Modifié
isOnlySupported=n'est pris en charge qu'avec une licence professionnelle isOnlySupported=n'est pris en charge qu'avec une licence professionnelle
areOnlySupported=ne sont 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 updateReadyTitle=Mise à jour de $VERSION$ ready
templates=Modèles
retry=Réessayer
retryAll=Réessayer tout
replace=Remplacer
replaceAll=Remplacer tout

View file

@ -424,3 +424,8 @@ modified=Modificato
isOnlySupported=è supportato solo con una licenza professionale isOnlySupported=è supportato solo con una licenza professionale
areOnlySupported=sono supportati solo con una licenza professionale areOnlySupported=sono supportati solo con una licenza professionale
updateReadyTitle=Aggiornamento a $VERSION$ ready updateReadyTitle=Aggiornamento a $VERSION$ ready
templates=Modelli
retry=Riprova
retryAll=Riprova tutti
replace=Sostituire
replaceAll=Sostituisci tutto

View file

@ -424,3 +424,8 @@ modified=変更された
isOnlySupported=プロフェッショナルライセンスでのみサポートされる isOnlySupported=プロフェッショナルライセンスでのみサポートされる
areOnlySupported=プロフェッショナルライセンスでのみサポートされる areOnlySupported=プロフェッショナルライセンスでのみサポートされる
updateReadyTitle=$VERSION$ に更新 updateReadyTitle=$VERSION$ に更新
templates=テンプレート
retry=リトライ
retryAll=すべて再試行する
replace=置き換える
replaceAll=すべて置き換える

View file

@ -424,3 +424,8 @@ modified=Gewijzigd
isOnlySupported=wordt alleen ondersteund met een professionele licentie isOnlySupported=wordt alleen ondersteund met een professionele licentie
areOnlySupported=worden alleen ondersteund met een professionele licentie areOnlySupported=worden alleen ondersteund met een professionele licentie
updateReadyTitle=Bijwerken naar $VERSION$ klaar updateReadyTitle=Bijwerken naar $VERSION$ klaar
templates=Sjablonen
retry=Opnieuw proberen
retryAll=Alles opnieuw proberen
replace=Vervangen
replaceAll=Alles vervangen

View file

@ -424,3 +424,8 @@ modified=Modificado
isOnlySupported=só é suportado com uma licença profissional isOnlySupported=só é suportado com uma licença profissional
areOnlySupported=só são suportados com uma licença profissional areOnlySupported=só são suportados com uma licença profissional
updateReadyTitle=Actualiza para $VERSION$ ready updateReadyTitle=Actualiza para $VERSION$ ready
templates=Modelos
retry=Repetir
retryAll=Repetir tudo
replace=Substitui
replaceAll=Substitui tudo

View file

@ -424,3 +424,8 @@ modified=Изменено
isOnlySupported=поддерживается только при наличии профессиональной лицензии isOnlySupported=поддерживается только при наличии профессиональной лицензии
areOnlySupported=поддерживаются только с профессиональной лицензией areOnlySupported=поддерживаются только с профессиональной лицензией
updateReadyTitle=Обновление на $VERSION$ готово updateReadyTitle=Обновление на $VERSION$ готово
templates=Шаблоны
retry=Retry
retryAll=Повторите все попытки
replace=Замените
replaceAll=Заменить все

View file

@ -424,3 +424,8 @@ modified=Değiştirilmiş
isOnlySupported=yalnızca profesyonel lisans ile desteklenir isOnlySupported=yalnızca profesyonel lisans ile desteklenir
areOnlySupported=yalnızca profesyonel lisans ile desteklenir areOnlySupported=yalnızca profesyonel lisans ile desteklenir
updateReadyTitle=$VERSION$ için güncelleme hazır 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

View file

@ -424,3 +424,8 @@ modified=已修改
isOnlySupported=只有专业许可证才支持 isOnlySupported=只有专业许可证才支持
areOnlySupported=只有专业许可证才支持 areOnlySupported=只有专业许可证才支持
updateReadyTitle=更新至$VERSION$ ready updateReadyTitle=更新至$VERSION$ ready
templates=模板
retry=重试
retryAll=全部重试
replace=替换
replaceAll=全部替换