diff --git a/.github/stale.yml b/.github/stale.yml
index 58ad1e2ad..61494684a 100644
--- a/.github/stale.yml
+++ b/.github/stale.yml
@@ -1,12 +1,14 @@
# Number of days of inactivity before an issue becomes stale
-daysUntilStale: 90
+daysUntilStale: 180
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 30
# Issues with these labels will never be considered stale
exemptLabels:
- type:security-issue # never close automatically
+ - type:feature-request # never close automatically
- state:awaiting-response # handled by different bot
- state:blocked
+ - state:confirmed
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: true
# Label to use when marking an issue as stale
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index cfda7c67b..1119f53ee 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -31,10 +31,10 @@
-
-
+
+
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
index 0e7df1ed1..c3807468d 100644
--- a/.idea/encodings.xml
+++ b/.idea/encodings.xml
@@ -1,11 +1,13 @@
-
+
-
-
-
-
-
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/main/buildkit/pom.xml b/main/buildkit/pom.xml
index 11ad987bd..c08e82d9f 100644
--- a/main/buildkit/pom.xml
+++ b/main/buildkit/pom.xml
@@ -4,7 +4,7 @@
org.cryptomator
main
- 1.5.0-beta1
+ 1.5.0-beta2
buildkit
pom
diff --git a/main/commons/pom.xml b/main/commons/pom.xml
index 1f9b121ec..5a00af424 100644
--- a/main/commons/pom.xml
+++ b/main/commons/pom.xml
@@ -4,7 +4,7 @@
org.cryptomator
main
- 1.5.0-beta1
+ 1.5.0-beta2
commons
Cryptomator Commons
@@ -48,6 +48,12 @@
easybind
+
+
+ com.auth0
+ java-jwt
+
+
com.google.guava
diff --git a/main/commons/src/main/java/org/cryptomator/common/CommonsModule.java b/main/commons/src/main/java/org/cryptomator/common/CommonsModule.java
index 5d86ca6ce..795dc6dcd 100644
--- a/main/commons/src/main/java/org/cryptomator/common/CommonsModule.java
+++ b/main/commons/src/main/java/org/cryptomator/common/CommonsModule.java
@@ -28,13 +28,23 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Consumer;
@Module(subcomponents = {VaultComponent.class})
public abstract class CommonsModule {
private static final int NUM_SCHEDULER_THREADS = 4;
+ @Provides
+ @Singleton
+ @Named("licensePublicKey")
+ static String provideLicensePublicKey() {
+ // in PEM format without the dash-escaped begin/end lines
+ return "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQB7NfnqiZbg2KTmoflmZ71PbXru7oW" //
+ + "fmnV2yv3eDjlDfGruBrqz9TtXBZV/eYWt31xu1osIqaT12lKBvZ511aaAkIBeOEV" //
+ + "gwcBIlJr6kUw7NKzeJt7r2rrsOyQoOG2nWc/Of/NBqA3mIZRHk5Aq1YupFdD26QE" //
+ + "r0DzRyj4ixPIt38CQB8=";
+ }
+
@Provides
@Singleton
@Named("SemVer")
@@ -56,7 +66,7 @@ public abstract class CommonsModule {
@Provides
@Singleton
- static ScheduledExecutorService provideScheduledExecutorService(@Named("shutdownTaskScheduler") Consumer shutdownTaskScheduler) {
+ static ScheduledExecutorService provideScheduledExecutorService(ShutdownHook shutdownHook) {
final AtomicInteger threadNumber = new AtomicInteger(1);
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(NUM_SCHEDULER_THREADS, r -> {
Thread t = new Thread(r);
@@ -64,7 +74,7 @@ public abstract class CommonsModule {
t.setDaemon(true);
return t;
});
- shutdownTaskScheduler.accept(executorService::shutdown);
+ shutdownHook.runOnShutdown(executorService::shutdown);
return executorService;
}
diff --git a/main/commons/src/main/java/org/cryptomator/common/Environment.java b/main/commons/src/main/java/org/cryptomator/common/Environment.java
index 52e23ff07..8dfb56ef2 100644
--- a/main/commons/src/main/java/org/cryptomator/common/Environment.java
+++ b/main/commons/src/main/java/org/cryptomator/common/Environment.java
@@ -25,6 +25,7 @@ public class Environment {
private static final Path RELATIVE_HOME_DIR = Paths.get("~");
private static final Path ABSOLUTE_HOME_DIR = Paths.get(USER_HOME);
private static final char PATH_LIST_SEP = ':';
+ private static final int DEFAULT_MIN_PW_LENGTH = 8;
@Inject
public Environment() {
@@ -37,6 +38,7 @@ public class Environment {
LOG.debug("cryptomator.keychainPath: {}", System.getProperty("cryptomator.keychainPath"));
LOG.debug("cryptomator.logDir: {}", System.getProperty("cryptomator.logDir"));
LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir"));
+ LOG.debug("cryptomator.minPwLength: {}", System.getProperty("cryptomator.minPwLength"));
}
public boolean useCustomLogbackConfig() {
@@ -63,6 +65,19 @@ public class Environment {
return getPath("cryptomator.mountPointsDir").map(this::replaceHomeDir);
}
+ public int getMinPwLength() {
+ return getInt("cryptomator.minPwLength", DEFAULT_MIN_PW_LENGTH);
+ }
+
+ private int getInt(String propertyName, int defaultValue) {
+ String value = System.getProperty(propertyName);
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) { // includes "null" values
+ return defaultValue;
+ }
+ }
+
private Optional getPath(String propertyName) {
String value = System.getProperty(propertyName);
return Optional.ofNullable(value).map(Paths::get);
diff --git a/main/commons/src/main/java/org/cryptomator/common/LicenseChecker.java b/main/commons/src/main/java/org/cryptomator/common/LicenseChecker.java
new file mode 100644
index 000000000..93f16f755
--- /dev/null
+++ b/main/commons/src/main/java/org/cryptomator/common/LicenseChecker.java
@@ -0,0 +1,56 @@
+package org.cryptomator.common;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.exceptions.JWTVerificationException;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.auth0.jwt.interfaces.JWTVerifier;
+import com.google.common.io.BaseEncoding;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Optional;
+
+@Singleton
+class LicenseChecker {
+
+ private final JWTVerifier verifier;
+
+ @Inject
+ public LicenseChecker(@Named("licensePublicKey") String pemEncodedPublicKey) {
+ Algorithm algorithm = Algorithm.ECDSA512(decodePublicKey(pemEncodedPublicKey), null);
+ this.verifier = JWT.require(algorithm).build();
+ }
+
+ private static ECPublicKey decodePublicKey(String pemEncodedPublicKey) {
+ try {
+ byte[] keyBytes = BaseEncoding.base64().decode(pemEncodedPublicKey);
+ PublicKey key = KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(keyBytes));
+ if (key instanceof ECPublicKey) {
+ return (ECPublicKey) key;
+ } else {
+ throw new IllegalStateException("Key not an EC public key.");
+ }
+ } catch (InvalidKeySpecException e) {
+ throw new IllegalArgumentException("Invalid license public key", e);
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ public Optional check(String licenseKey) {
+ try {
+ return Optional.of(verifier.verify(licenseKey));
+ } catch (JWTVerificationException exception) {
+ return Optional.empty();
+ }
+ }
+
+}
diff --git a/main/commons/src/main/java/org/cryptomator/common/LicenseHolder.java b/main/commons/src/main/java/org/cryptomator/common/LicenseHolder.java
new file mode 100644
index 000000000..70d439afe
--- /dev/null
+++ b/main/commons/src/main/java/org/cryptomator/common/LicenseHolder.java
@@ -0,0 +1,79 @@
+package org.cryptomator.common;
+
+import com.auth0.jwt.interfaces.DecodedJWT;
+import javafx.beans.binding.Bindings;
+import javafx.beans.binding.BooleanBinding;
+import javafx.beans.binding.StringBinding;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import org.cryptomator.common.settings.Settings;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.util.Optional;
+
+@Singleton
+public class LicenseHolder {
+
+ private final Settings settings;
+ private final LicenseChecker licenseChecker;
+ private final ObjectProperty validJwtClaims;
+ private final StringBinding licenseSubject;
+ private final BooleanBinding validLicenseProperty;
+
+ @Inject
+ public LicenseHolder(LicenseChecker licenseChecker, Settings settings) {
+ this.settings = settings;
+ this.licenseChecker = licenseChecker;
+ this.validJwtClaims = new SimpleObjectProperty<>();
+ this.licenseSubject = Bindings.createStringBinding(this::getLicenseSubject, validJwtClaims);
+ this.validLicenseProperty = validJwtClaims.isNotNull();
+
+ Optional claims = licenseChecker.check(settings.licenseKey().get());
+ validJwtClaims.set(claims.orElse(null));
+ }
+
+ public boolean validateAndStoreLicense(String licenseKey) {
+ Optional claims = licenseChecker.check(licenseKey);
+ validJwtClaims.set(claims.orElse(null));
+ if (claims.isPresent()) {
+ settings.licenseKey().set(licenseKey);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /* Observable Properties */
+
+ public Optional getLicenseKey() {
+ DecodedJWT claims = validJwtClaims.get();
+ if (claims != null) {
+ return Optional.of(claims.getToken());
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ public StringBinding licenseSubjectProperty() {
+ return licenseSubject;
+ }
+
+ public String getLicenseSubject() {
+ DecodedJWT claims = validJwtClaims.get();
+ if (claims != null) {
+ return claims.getSubject();
+ } else {
+ return null;
+ }
+ }
+
+ public BooleanBinding validLicenseProperty() {
+ return validLicenseProperty;
+ }
+
+ public boolean isValidLicense() {
+ return validLicenseProperty.get();
+ }
+
+}
diff --git a/main/commons/src/main/java/org/cryptomator/common/Optionals.java b/main/commons/src/main/java/org/cryptomator/common/Optionals.java
deleted file mode 100644
index 4ccea7d8b..000000000
--- a/main/commons/src/main/java/org/cryptomator/common/Optionals.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the accompanying LICENSE file.
- *******************************************************************************/
-package org.cryptomator.common;
-
-import java.util.Optional;
-import java.util.function.Function;
-
-public final class Optionals {
-
- private Optionals() {
- }
-
- /**
- * Returns a function that is equivalent to the input function but immediately gets the value of the returned optional when invoked.
- *
- * @param the type of the input to the function
- * @param the type of the result of the function
- * @param function An {@code Optional}-bearing input function {@code Function>}
- * @return A {@code Function}, that may throw a NoSuchElementException, if the original function returns an empty optional.
- */
- public static Function unwrap(Function> function) {
- return t -> function.apply(t).get();
- }
-
-}
diff --git a/main/commons/src/main/java/org/cryptomator/common/ShutdownHook.java b/main/commons/src/main/java/org/cryptomator/common/ShutdownHook.java
new file mode 100644
index 000000000..0fcb9d676
--- /dev/null
+++ b/main/commons/src/main/java/org/cryptomator/common/ShutdownHook.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the accompanying LICENSE file.
+ *******************************************************************************/
+package org.cryptomator.common;
+
+import com.google.common.util.concurrent.Runnables;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.util.Queue;
+import java.util.concurrent.PriorityBlockingQueue;
+
+@Singleton
+public class ShutdownHook extends Thread {
+
+ private static final int PRIO_VERY_LAST = Integer.MIN_VALUE;
+ public static final int PRIO_LAST = PRIO_VERY_LAST + 1;
+ public static final int PRIO_DEFAULT = 0;
+ public static final int PRIO_FIRST = Integer.MAX_VALUE;
+ private static final Logger LOG = LoggerFactory.getLogger(ShutdownHook.class);
+ private static final OrderedTask POISON = new OrderedTask(PRIO_VERY_LAST, Runnables.doNothing());
+ private final Queue tasks = new PriorityBlockingQueue<>();
+
+ @Inject
+ ShutdownHook() {
+ super(null, null, "ShutdownTasks", 0);
+ Runtime.getRuntime().addShutdownHook(this);
+ LOG.debug("Registered shutdown hook.");
+ }
+
+ @Override
+ public void run() {
+ LOG.debug("Running graceful shutdown tasks...");
+ tasks.add(POISON);
+ Runnable task;
+ while ((task = tasks.remove()) != POISON) {
+ try {
+ task.run();
+ } catch (RuntimeException e) {
+ LOG.error("Exception while shutting down.", e);
+ }
+ }
+ }
+
+ /**
+ * Schedules a task to be run during shutdown with default order
+ *
+ * @param task The task to be scheduled
+ */
+ public void runOnShutdown(Runnable task) {
+ runOnShutdown(PRIO_DEFAULT, task);
+ }
+
+ /**
+ * Schedules a task to be run with the given priority
+ *
+ * @param priority Tasks with high priority will be run before task with lower priority
+ * @param task The task to be scheduled
+ */
+ public void runOnShutdown(int priority, Runnable task) {
+ tasks.add(new OrderedTask(priority, task));
+ }
+
+ private static class OrderedTask implements Comparable, Runnable {
+
+ private final int priority;
+ private final Runnable task;
+
+ public OrderedTask(int priority, Runnable task) {
+ this.priority = priority;
+ this.task = task;
+ }
+
+ @Override
+ public int compareTo(OrderedTask other) {
+ // overflow-safe signum impl:
+ if (this.priority > other.priority) {
+ return -1; // higher prio -> this before other
+ } else if (this.priority < other.priority) {
+ return +1; // lower prio -> other before this
+ } else {
+ return 0; // same prio
+ }
+ }
+
+ @Override
+ public void run() {
+ task.run();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java b/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java
index ff5613f1e..16d16b01d 100644
--- a/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java
+++ b/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java
@@ -15,6 +15,8 @@ import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.NodeOrientation;
@@ -35,6 +37,7 @@ public class Settings {
public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = System.getProperty("os.name").toLowerCase().contains("windows") ? VolumeImpl.DOKANY : VolumeImpl.FUSE;
public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
public static final NodeOrientation DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT;
+ private static final String DEFAULT_LICENSE_KEY = "";
private final ObservableList directories = FXCollections.observableArrayList(VaultSettings::observables);
private final BooleanProperty askedForUpdateCheck = new SimpleBooleanProperty(DEFAULT_ASKED_FOR_UPDATE_CHECK);
@@ -47,6 +50,7 @@ public class Settings {
private final ObjectProperty preferredVolumeImpl = new SimpleObjectProperty<>(DEFAULT_PREFERRED_VOLUME_IMPL);
private final ObjectProperty theme = new SimpleObjectProperty<>(DEFAULT_THEME);
private final ObjectProperty userInterfaceOrientation = new SimpleObjectProperty<>(DEFAULT_USER_INTERFACE_ORIENTATION);
+ private final StringProperty licenseKey = new SimpleStringProperty(DEFAULT_LICENSE_KEY);
private Consumer saveCmd;
@@ -65,6 +69,7 @@ public class Settings {
preferredVolumeImpl.addListener(this::somethingChanged);
theme.addListener(this::somethingChanged);
userInterfaceOrientation.addListener(this::somethingChanged);
+ licenseKey.addListener(this::somethingChanged);
}
void setSaveCmd(Consumer saveCmd) {
@@ -126,4 +131,8 @@ public class Settings {
public ObjectProperty userInterfaceOrientation() {
return userInterfaceOrientation;
}
+
+ public StringProperty licenseKey() {
+ return licenseKey;
+ }
}
diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java b/main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java
index 874994cf8..33afb7306 100644
--- a/main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java
+++ b/main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java
@@ -38,6 +38,7 @@ public class SettingsJsonAdapter extends TypeAdapter {
out.name("preferredVolumeImpl").value(value.preferredVolumeImpl().get().name());
out.name("theme").value(value.theme().get().name());
out.name("uiOrientation").value(value.userInterfaceOrientation().get().name());
+ out.name("licenseKey").value(value.licenseKey().get());
out.endObject();
}
@@ -90,6 +91,9 @@ public class SettingsJsonAdapter extends TypeAdapter {
case "uiOrientation":
settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
break;
+ case "licenseKey":
+ settings.licenseKey().set(in.nextString());
+ break;
default:
LOG.warn("Unsupported vault setting found in JSON: " + name);
in.skipValue();
diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/VaultListManager.java b/main/commons/src/main/java/org/cryptomator/common/vaults/VaultListManager.java
index 3bb3496b8..e950fd4e2 100644
--- a/main/commons/src/main/java/org/cryptomator/common/vaults/VaultListManager.java
+++ b/main/commons/src/main/java/org/cryptomator/common/vaults/VaultListManager.java
@@ -18,6 +18,7 @@ import org.cryptomator.cryptofs.migration.Migrators;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException;
+import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.Collection;
@@ -67,7 +68,13 @@ public class VaultListManager {
}
private Optional get(Path vaultPath) {
- return vaultList.stream().filter(v -> v.getPath().equals(vaultPath)).findAny();
+ return vaultList.stream().filter(v -> {
+ try {
+ return Files.isSameFile(vaultPath, v.getPath());
+ } catch (IOException e) {
+ return false;
+ }
+ }).findAny();
}
private Vault create(VaultSettings vaultSettings) {
@@ -76,7 +83,7 @@ public class VaultListManager {
return comp.vault();
}
- private VaultState determineVaultState(Path pathToVault) {
+ public static VaultState determineVaultState(Path pathToVault) {
try {
if (!CryptoFileSystemProvider.containsVault(pathToVault, MASTERKEY_FILENAME)) {
return VaultState.MISSING;
diff --git a/main/commons/src/test/java/org/cryptomator/common/LicenseCheckerTest.java b/main/commons/src/test/java/org/cryptomator/common/LicenseCheckerTest.java
new file mode 100644
index 000000000..5ae8f0fb9
--- /dev/null
+++ b/main/commons/src/test/java/org/cryptomator/common/LicenseCheckerTest.java
@@ -0,0 +1,62 @@
+package org.cryptomator.common;
+
+import com.auth0.jwt.interfaces.DecodedJWT;
+import org.cryptomator.common.LicenseChecker;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Optional;
+
+class LicenseCheckerTest {
+
+ private static final String PUBLIC_KEY = "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ" //
+ + "PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47" //
+ + "6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM" //
+ + "Al8G7CqwoJOsW7Kddns=";
+
+ private LicenseChecker licenseChecker;
+
+ @BeforeEach
+ public void setup() {
+ licenseChecker = new LicenseChecker(PUBLIC_KEY);
+ }
+
+ @Test
+ public void testCheckValidLicense() {
+ String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
+
+ Optional decoded = licenseChecker.check(license);
+
+ Assertions.assertTrue(decoded.isPresent());
+ Assertions.assertEquals("cryptobot@example.com", decoded.get().getSubject());
+ }
+
+ @Test
+ public void testCheckInvalidLicenseHeader() {
+ String license = "EyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
+
+ Optional decoded = licenseChecker.check(license);
+
+ Assertions.assertFalse(decoded.isPresent());
+ }
+
+ @Test
+ public void testCheckInvalidLicensePayload() {
+ String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.EyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
+
+ Optional decoded = licenseChecker.check(license);
+
+ Assertions.assertFalse(decoded.isPresent());
+ }
+
+ @Test
+ public void testCheckInvalidLicenseSignature() {
+ String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.aQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
+
+ Optional decoded = licenseChecker.check(license);
+
+ Assertions.assertFalse(decoded.isPresent());
+ }
+
+}
\ No newline at end of file
diff --git a/main/keychain/pom.xml b/main/keychain/pom.xml
index 441e75af8..568dfca7f 100644
--- a/main/keychain/pom.xml
+++ b/main/keychain/pom.xml
@@ -4,7 +4,7 @@
org.cryptomator
main
- 1.5.0-beta1
+ 1.5.0-beta2
keychain
System Keychain Access
diff --git a/main/launcher/pom.xml b/main/launcher/pom.xml
index a56cd33c6..9338d9943 100644
--- a/main/launcher/pom.xml
+++ b/main/launcher/pom.xml
@@ -4,7 +4,7 @@
org.cryptomator
main
- 1.5.0-beta1
+ 1.5.0-beta2
launcher
Cryptomator Launcher
diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/CleanShutdownPerformer.java b/main/launcher/src/main/java/org/cryptomator/launcher/CleanShutdownPerformer.java
deleted file mode 100644
index 717fb86a8..000000000
--- a/main/launcher/src/main/java/org/cryptomator/launcher/CleanShutdownPerformer.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the accompanying LICENSE file.
- *******************************************************************************/
-package org.cryptomator.launcher;
-
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-@Singleton
-class CleanShutdownPerformer extends Thread {
-
- private static final Logger LOG = LoggerFactory.getLogger(CleanShutdownPerformer.class);
- private final ConcurrentMap tasks = new ConcurrentHashMap<>();
-
- @Inject
- CleanShutdownPerformer() {
- super(null, null, "ShutdownTasks", 0);
- }
-
- @Override
- public void run() {
- LOG.debug("Running graceful shutdown tasks...");
- tasks.keySet().forEach(r -> {
- try {
- r.run();
- } catch (RuntimeException e) {
- LOG.error("Exception while shutting down.", e);
- }
- });
- tasks.clear();
- }
-
- void scheduleShutdownTask(Runnable task) {
- tasks.put(task, Boolean.TRUE);
- }
-
- void registerShutdownHook() {
- Runtime.getRuntime().addShutdownHook(this);
- }
-}
\ No newline at end of file
diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/Cryptomator.java b/main/launcher/src/main/java/org/cryptomator/launcher/Cryptomator.java
index a4d861c3e..04eb9448d 100644
--- a/main/launcher/src/main/java/org/cryptomator/launcher/Cryptomator.java
+++ b/main/launcher/src/main/java/org/cryptomator/launcher/Cryptomator.java
@@ -32,17 +32,15 @@ public class Cryptomator {
private final IpcFactory ipcFactory;
private final Optional applicationVersion;
private final CountDownLatch shutdownLatch;
- private final CleanShutdownPerformer shutdownPerformer;
private final UiLauncher uiLauncher;
@Inject
- Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, IpcFactory ipcFactory, @Named("applicationVersion") Optional applicationVersion, @Named("shutdownLatch") CountDownLatch shutdownLatch, CleanShutdownPerformer shutdownPerformer, UiLauncher uiLauncher) {
+ Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, IpcFactory ipcFactory, @Named("applicationVersion") Optional applicationVersion, @Named("shutdownLatch") CountDownLatch shutdownLatch, UiLauncher uiLauncher) {
this.logConfig = logConfig;
this.debugMode = debugMode;
this.ipcFactory = ipcFactory;
this.applicationVersion = applicationVersion;
this.shutdownLatch = shutdownLatch;
- this.shutdownPerformer = shutdownPerformer;
this.uiLauncher = uiLauncher;
}
@@ -90,7 +88,6 @@ public class Cryptomator {
*/
private int runGuiApplication() {
try {
- shutdownPerformer.registerShutdownHook();
uiLauncher.launch();
shutdownLatch.await();
LOG.info("UI shut down");
diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/CryptomatorModule.java b/main/launcher/src/main/java/org/cryptomator/launcher/CryptomatorModule.java
index 268d69002..6b37e29b6 100644
--- a/main/launcher/src/main/java/org/cryptomator/launcher/CryptomatorModule.java
+++ b/main/launcher/src/main/java/org/cryptomator/launcher/CryptomatorModule.java
@@ -7,18 +7,10 @@ import javax.inject.Named;
import javax.inject.Singleton;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
-import java.util.function.Consumer;
@Module
class CryptomatorModule {
-
- @Provides
- @Singleton
- @Named("shutdownTaskScheduler")
- Consumer provideShutdownTaskScheduler(CleanShutdownPerformer shutdownPerformer) {
- return shutdownPerformer::scheduleShutdownTask;
- }
-
+
@Provides
@Singleton
@Named("shutdownLatch")
diff --git a/main/launcher/src/main/java/org/cryptomator/logging/LoggerConfiguration.java b/main/launcher/src/main/java/org/cryptomator/logging/LoggerConfiguration.java
index d1916abee..cb3bf6ff1 100644
--- a/main/launcher/src/main/java/org/cryptomator/logging/LoggerConfiguration.java
+++ b/main/launcher/src/main/java/org/cryptomator/logging/LoggerConfiguration.java
@@ -5,9 +5,8 @@ import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
-import ch.qos.logback.core.hook.DelayingShutdownHook;
-import ch.qos.logback.core.util.Duration;
import org.cryptomator.common.Environment;
+import org.cryptomator.common.ShutdownHook;
import javax.inject.Inject;
import javax.inject.Named;
@@ -16,26 +15,27 @@ import java.util.Map;
@Singleton
public class LoggerConfiguration {
-
- private static final double SHUTDOWN_DELAY_MS = 100;
-
+
private final LoggerContext context;
private final Environment environment;
private final Appender stdout;
private final Appender upgrade;
private final Appender file;
+ private final ShutdownHook shutdownHook;
@Inject
LoggerConfiguration(LoggerContext context, //
Environment environment, //
@Named("stdoutAppender") Appender stdout, //
@Named("upgradeAppender") Appender upgrade, //
- @Named("fileAppender") Appender file) {
+ @Named("fileAppender") Appender file, //
+ ShutdownHook shutdownHook) {
this.context = context;
this.environment = environment;
this.stdout = stdout;
this.upgrade = upgrade;
this.file = file;
+ this.shutdownHook = shutdownHook;
}
public void init() {
@@ -62,9 +62,7 @@ public class LoggerConfiguration {
upgrades.setAdditive(false);
// add shutdown hook
- DelayingShutdownHook shutdownHook = new DelayingShutdownHook();
- shutdownHook.setContext(context);
- shutdownHook.setDelay(Duration.buildByMilliseconds(SHUTDOWN_DELAY_MS));
+ shutdownHook.runOnShutdown(ShutdownHook.PRIO_LAST, context::stop);
}
}
diff --git a/main/pom.xml b/main/pom.xml
index 026092229..386bf7b7b 100644
--- a/main/pom.xml
+++ b/main/pom.xml
@@ -3,7 +3,7 @@
4.0.0
org.cryptomator
main
- 1.5.0-beta1
+ 1.5.0-beta2
pom
Cryptomator
@@ -23,26 +23,25 @@
UTF-8
-
+
1.9.0-rc2
- 2.2.1
- 1.2.1
- 1.1.11
+ 2.2.2
+ 1.2.2
+ 1.1.12
1.0.10
+
13.0.1
-
3.9
-
+ 3.8.3
1.0.3
-
28.1-jre
2.25.2
2.8.6
-
1.7.29
1.2.3
+
5.5.2
3.1.0
2.2
@@ -156,6 +155,13 @@
commons-lang3
${commons-lang3.version}
+
+
+
+ com.auth0
+ java-jwt
+ ${jwt.version}
+
diff --git a/main/ui/pom.xml b/main/ui/pom.xml
index e2c17e375..afe40303b 100644
--- a/main/ui/pom.xml
+++ b/main/ui/pom.xml
@@ -4,7 +4,7 @@
org.cryptomator
main
- 1.5.0-beta1
+ 1.5.0-beta2
ui
Cryptomator GUI
@@ -37,7 +37,7 @@
org.fxmisc.easybind
easybind
-
+
@@ -65,7 +65,7 @@
com.nulab-inc
zxcvbn
- 1.2.7
+ 1.3.0
diff --git a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultFailureExisitingController.java b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultFailureExistingController.java
similarity index 80%
rename from main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultFailureExisitingController.java
rename to main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultFailureExistingController.java
index 201f53345..1bb9eef40 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultFailureExisitingController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultFailureExistingController.java
@@ -15,14 +15,14 @@ import javax.inject.Inject;
import java.nio.file.Path;
@AddVaultWizardScoped
-public class AddVaultFailureExisitingController implements FxController {
+public class AddVaultFailureExistingController implements FxController {
private final Stage window;
private final Lazy previousScene;
private final StringBinding vaultName;
@Inject
- AddVaultFailureExisitingController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_EXISTING) Lazy previousScene, ObjectProperty pathOfFailedVault){
+ AddVaultFailureExistingController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_EXISTING) Lazy previousScene, ObjectProperty pathOfFailedVault){
this.window = window;
this.previousScene = previousScene;
this.vaultName = Bindings.createStringBinding(() -> pathOfFailedVault.get().getFileName().toString(),pathOfFailedVault);
diff --git a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultModule.java b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultModule.java
index 4eaa1c207..aafa97f5e 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultModule.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultModule.java
@@ -19,7 +19,10 @@ import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
+import org.cryptomator.ui.common.NewPasswordController;
+import org.cryptomator.ui.common.PasswordStrengthUtil;
import org.cryptomator.ui.mainwindow.MainWindow;
+import org.cryptomator.ui.recoverykey.RecoveryKeyDisplayController;
import javax.inject.Named;
import javax.inject.Provider;
@@ -31,6 +34,13 @@ import java.util.ResourceBundle;
@Module
public abstract class AddVaultModule {
+ @Provides
+ @AddVaultWizardScoped
+ @Named("newPassword")
+ static ObjectProperty provideNewPasswordProperty() {
+ return new SimpleObjectProperty<>("");
+ }
+
@Provides
@AddVaultWizardWindow
@AddVaultWizardScoped
@@ -150,8 +160,8 @@ public abstract class AddVaultModule {
@Binds
@IntoMap
- @FxControllerKey(AddVaultFailureExisitingController.class)
- abstract FxController bindAddVaultFailureExistingController(AddVaultFailureExisitingController controller);
+ @FxControllerKey(AddVaultFailureExistingController.class)
+ abstract FxController bindAddVaultFailureExistingController(AddVaultFailureExistingController controller);
@Binds
@IntoMap
@@ -168,13 +178,27 @@ public abstract class AddVaultModule {
@FxControllerKey(CreateNewVaultPasswordController.class)
abstract FxController bindCreateNewVaultPasswordController(CreateNewVaultPasswordController controller);
- @Binds
+ @Provides
@IntoMap
- @FxControllerKey(AddVaultSuccessController.class)
- abstract FxController bindAddVaultSuccessController(AddVaultSuccessController controller);
+ @FxControllerKey(NewPasswordController.class)
+ static FxController provideNewPasswordController(ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater, @Named("newPassword") ObjectProperty password) {
+ return new NewPasswordController(resourceBundle, strengthRater, password);
+ }
@Binds
@IntoMap
@FxControllerKey(CreateNewVaultRecoveryKeyController.class)
abstract FxController bindCreateNewVaultRecoveryKeyController(CreateNewVaultRecoveryKeyController controller);
+
+ @Provides
+ @IntoMap
+ @FxControllerKey(RecoveryKeyDisplayController.class)
+ static FxController provideRecoveryKeyDisplayController(@AddVaultWizardWindow Stage window, @Named("vaultName") StringProperty vaultName, @Named("recoveryKey") StringProperty recoveryKey) {
+ return new RecoveryKeyDisplayController(window, vaultName.get(), recoveryKey.get());
+ }
+
+ @Binds
+ @IntoMap
+ @FxControllerKey(AddVaultSuccessController.class)
+ abstract FxController bindAddVaultSuccessController(AddVaultSuccessController controller);
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java
index e15d195bf..e0c5f9bec 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java
@@ -52,8 +52,10 @@ public class CreateNewVaultLocationController implements FxController {
private Path customVaultPath = DEFAULT_CUSTOM_VAULT_PATH;
public ToggleGroup predefinedLocationToggler;
+ public RadioButton iclouddriveRadioButton;
public RadioButton dropboxRadioButton;
public RadioButton gdriveRadioButton;
+ public RadioButton onedriveRadioButton;
public RadioButton customRadioButton;
@Inject
@@ -91,10 +93,14 @@ public class CreateNewVaultLocationController implements FxController {
}
private void togglePredefinedLocation(@SuppressWarnings("unused") ObservableValue extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
- if (dropboxRadioButton.equals(newValue)) {
+ if (iclouddriveRadioButton.equals(newValue)) {
+ vaultPath.set(locationPresets.getIclouddriveLocation().resolve(vaultName.get()));
+ } else if (dropboxRadioButton.equals(newValue)) {
vaultPath.set(locationPresets.getDropboxLocation().resolve(vaultName.get()));
} else if (gdriveRadioButton.equals(newValue)) {
vaultPath.set(locationPresets.getGdriveLocation().resolve(vaultName.get()));
+ } else if (onedriveRadioButton.equals(newValue)) {
+ vaultPath.set(locationPresets.getOnedriveLocation().resolve(vaultName.get()));
} else if (customRadioButton.equals(newValue)) {
vaultPath.set(customVaultPath.resolve(vaultName.get()));
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java
index ab2fe27a0..4ddf54c6a 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java
@@ -5,16 +5,14 @@ import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.BooleanProperty;
-import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
-import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
-import javafx.scene.control.Label;
-import javafx.scene.layout.HBox;
+import javafx.scene.control.Toggle;
+import javafx.scene.control.ToggleGroup;
import javafx.stage.Stage;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
@@ -24,11 +22,7 @@ import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.Tasks;
-import org.cryptomator.ui.controls.FontAwesome5IconView;
-import org.cryptomator.ui.controls.NiceSecurePasswordField;
-import org.cryptomator.ui.common.PasswordStrengthUtil;
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
-import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -58,6 +52,7 @@ public class CreateNewVaultPasswordController implements FxController {
private final Stage window;
private final Lazy chooseLocationScene;
private final Lazy recoveryKeyScene;
+ private final Lazy successScene;
private final ExecutorService executor;
private final RecoveryKeyFactory recoveryKeyFactory;
private final StringProperty vaultNameProperty;
@@ -66,26 +61,22 @@ public class CreateNewVaultPasswordController implements FxController {
private final StringProperty recoveryKeyProperty;
private final VaultListManager vaultListManager;
private final ResourceBundle resourceBundle;
- private final PasswordStrengthUtil strengthRater;
+ private final ObjectProperty password;
private final ReadmeGenerator readmeGenerator;
- private final IntegerProperty passwordStrength;
private final BooleanProperty processing;
private final BooleanProperty readyToCreateVault;
private final ObjectBinding createVaultButtonState;
- public NiceSecurePasswordField passwordField;
- public NiceSecurePasswordField reenterField;
- public Label passwordStrengthLabel;
- public HBox passwordMatchBox;
- public FontAwesome5IconView checkmark;
- public FontAwesome5IconView cross;
- public Label passwordMatchLabel;
+ public ToggleGroup recoveryKeyChoice;
+ public Toggle showRecoveryKey;
+ public Toggle skipRecoveryKey;
@Inject
- CreateNewVaultPasswordController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy chooseLocationScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY) Lazy recoveryKeyScene, ExecutorService executor, RecoveryKeyFactory recoveryKeyFactory, @Named("vaultName") StringProperty vaultName, ObjectProperty vaultPath, @AddVaultWizardWindow ObjectProperty vault, @Named("recoveryKey") StringProperty recoveryKey, VaultListManager vaultListManager, ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater, ReadmeGenerator readmeGenerator) {
+ CreateNewVaultPasswordController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy chooseLocationScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY) Lazy recoveryKeyScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy successScene, ExecutorService executor, RecoveryKeyFactory recoveryKeyFactory, @Named("vaultName") StringProperty vaultName, ObjectProperty vaultPath, @AddVaultWizardWindow ObjectProperty vault, @Named("recoveryKey") StringProperty recoveryKey, VaultListManager vaultListManager, ResourceBundle resourceBundle, @Named("newPassword") ObjectProperty password, ReadmeGenerator readmeGenerator) {
this.window = window;
this.chooseLocationScene = chooseLocationScene;
this.recoveryKeyScene = recoveryKeyScene;
+ this.successScene = successScene;
this.executor = executor;
this.recoveryKeyFactory = recoveryKeyFactory;
this.vaultNameProperty = vaultName;
@@ -94,9 +85,8 @@ public class CreateNewVaultPasswordController implements FxController {
this.recoveryKeyProperty = recoveryKey;
this.vaultListManager = vaultListManager;
this.resourceBundle = resourceBundle;
- this.strengthRater = strengthRater;
+ this.password = password;
this.readmeGenerator = readmeGenerator;
- this.passwordStrength = new SimpleIntegerProperty(-1);
this.processing = new SimpleBooleanProperty();
this.readyToCreateVault = new SimpleBooleanProperty();
this.createVaultButtonState = Bindings.createObjectBinding(this::getCreateVaultButtonState, processing);
@@ -104,22 +94,8 @@ public class CreateNewVaultPasswordController implements FxController {
@FXML
public void initialize() {
- //binds the actual strength value to the rating of the password util
- passwordStrength.bind(Bindings.createIntegerBinding(() -> strengthRater.computeRate(passwordField.getCharacters().toString()), passwordField.textProperty()));
- //binding indicating if the passwords not match
- BooleanBinding passwordsMatch = Bindings.createBooleanBinding(() -> CharSequence.compare(passwordField.getCharacters(), reenterField.getCharacters()) == 0, passwordField.textProperty(), reenterField.textProperty());
- BooleanBinding reenterFieldNotEmpty = reenterField.textProperty().isNotEmpty();
- readyToCreateVault.bind(reenterFieldNotEmpty.and(passwordsMatch).and(processing.not()));
- //make match indicator invisible when passwords do not match or one is empty
- passwordMatchBox.visibleProperty().bind(reenterFieldNotEmpty);
- checkmark.visibleProperty().bind(passwordsMatch.and(reenterFieldNotEmpty));
- checkmark.managedProperty().bind(checkmark.visibleProperty());
- cross.visibleProperty().bind(passwordsMatch.not().and(reenterFieldNotEmpty));
- cross.managedProperty().bind(cross.visibleProperty());
- passwordMatchLabel.textProperty().bind(Bindings.when(passwordsMatch.and(reenterFieldNotEmpty)).then(resourceBundle.getString("addvaultwizard.new.passwordsMatch")).otherwise(resourceBundle.getString("addvaultwizard.new.passwordsDoNotMatch")));
-
- //bindsings for the password strength indicator
- passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
+ BooleanBinding isValidNewPassword = Bindings.createBooleanBinding(() -> password.get() != null && password.get().length() > 0, password);
+ readyToCreateVault.bind(isValidNewPassword.and(recoveryKeyChoice.selectedToggleProperty().isNotNull()).and(processing.not()));
}
@FXML
@@ -130,7 +106,7 @@ public class CreateNewVaultPasswordController implements FxController {
@FXML
public void next() {
Path pathToVault = vaultPathProperty.get();
-
+
try {
Files.createDirectory(pathToVault);
} catch (FileAlreadyExistsException e) {
@@ -141,12 +117,41 @@ public class CreateNewVaultPasswordController implements FxController {
LOG.error("", e);
}
+ if (showRecoveryKey.equals(recoveryKeyChoice.getSelectedToggle())) {
+ showRecoveryKeyScene();
+ } else if (skipRecoveryKey.equals(recoveryKeyChoice.getSelectedToggle())) {
+ showSuccessScene();
+ } else {
+ throw new IllegalStateException("Unexpected toggle state");
+ }
+ }
+
+ private void showRecoveryKeyScene() {
+ Path pathToVault = vaultPathProperty.get();
processing.set(true);
Tasks.create(() -> {
- initializeVault(pathToVault, passwordField.getCharacters());
- return recoveryKeyFactory.createRecoveryKey(pathToVault, passwordField.getCharacters());
+ initializeVault(pathToVault, password.get());
+ return recoveryKeyFactory.createRecoveryKey(pathToVault, password.get());
}).onSuccess(recoveryKey -> {
- initializationSucceeded(pathToVault, recoveryKey);
+ initializationSucceeded(pathToVault);
+ recoveryKeyProperty.set(recoveryKey);
+ window.setScene(recoveryKeyScene.get());
+ }).onError(IOException.class, e -> {
+ // TODO show generic error screen
+ LOG.error("", e);
+ }).andFinally(() -> {
+ processing.set(false);
+ }).runOnce(executor);
+ }
+
+ private void showSuccessScene() {
+ Path pathToVault = vaultPathProperty.get();
+ processing.set(true);
+ Tasks.create(() -> {
+ initializeVault(pathToVault, password.get());
+ }).onSuccess(() -> {
+ initializationSucceeded(pathToVault);
+ window.setScene(successScene.get());
}).onError(IOException.class, e -> {
// TODO show generic error screen
LOG.error("", e);
@@ -175,13 +180,11 @@ public class CreateNewVaultPasswordController implements FxController {
}
LOG.info("Created vault at {}", path);
}
-
- private void initializationSucceeded(Path pathToVault, String recoveryKey) {
+
+ private void initializationSucceeded(Path pathToVault) {
try {
Vault newVault = vaultListManager.add(pathToVault);
vaultProperty.set(newVault);
- recoveryKeyProperty.set(recoveryKey);
- window.setScene(recoveryKeyScene.get());
} catch (NoSuchFileException e) {
throw new UncheckedIOException(e);
}
@@ -197,14 +200,6 @@ public class CreateNewVaultPasswordController implements FxController {
return vaultNameProperty;
}
- public IntegerProperty passwordStrengthProperty() {
- return passwordStrength;
- }
-
- public int getPasswordStrength() {
- return passwordStrength.get();
- }
-
public BooleanProperty readyToCreateVaultProperty() {
return readyToCreateVault;
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultRecoveryKeyController.java b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultRecoveryKeyController.java
index 4c38f1250..44f186774 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultRecoveryKeyController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultRecoveryKeyController.java
@@ -1,7 +1,6 @@
package org.cryptomator.ui.addvaultwizard;
import dagger.Lazy;
-import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.stage.Stage;
@@ -10,33 +9,20 @@ import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import javax.inject.Inject;
-import javax.inject.Named;
public class CreateNewVaultRecoveryKeyController implements FxController {
private final Stage window;
private final Lazy successScene;
- private final StringProperty recoveryKeyProperty;
@Inject
- CreateNewVaultRecoveryKeyController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy successScene, @Named("recoveryKey")StringProperty recoveryKey) {
+ CreateNewVaultRecoveryKeyController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy successScene) {
this.window = window;
this.successScene = successScene;
- this.recoveryKeyProperty = recoveryKey;
}
@FXML
public void next() {
window.setScene(successScene.get());
}
-
- /* Getter/Setter */
-
- public String getRecoveryKey() {
- return recoveryKeyProperty.get();
- }
-
- public StringProperty recoveryKeyProperty() {
- return recoveryKeyProperty;
- }
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/LocationPresets.java b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/LocationPresets.java
index a8a889acf..b3b6a4b25 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/LocationPresets.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/LocationPresets.java
@@ -13,20 +13,30 @@ import java.nio.file.Paths;
public class LocationPresets {
private static final String USER_HOME = System.getProperty("user.home");
+ private static final String[] ICLOUDDRIVE_LOCATIONS = {"~/Library/Mobile Documents/iCloud~com~setolabs~Cryptomator/Documents"};
private static final String[] DROPBOX_LOCATIONS = {"~/Dropbox"};
private static final String[] GDRIVE_LOCATIONS = {"~/Google Drive"};
+ private static final String[] ONEDRIVE_LOCATIONS = {"~/OneDrive"};
+ private final ReadOnlyObjectProperty iclouddriveLocation;
private final ReadOnlyObjectProperty dropboxLocation;
private final ReadOnlyObjectProperty gdriveLocation;
+ private final ReadOnlyObjectProperty onedriveLocation;
+ private final BooleanBinding foundIclouddrive;
private final BooleanBinding foundDropbox;
private final BooleanBinding foundGdrive;
+ private final BooleanBinding foundOnedrive;
@Inject
public LocationPresets() {
+ this.iclouddriveLocation = new SimpleObjectProperty<>(existingWritablePath(ICLOUDDRIVE_LOCATIONS));
this.dropboxLocation = new SimpleObjectProperty<>(existingWritablePath(DROPBOX_LOCATIONS));
this.gdriveLocation = new SimpleObjectProperty<>(existingWritablePath(GDRIVE_LOCATIONS));
+ this.onedriveLocation = new SimpleObjectProperty<>(existingWritablePath(ONEDRIVE_LOCATIONS));
+ this.foundIclouddrive = iclouddriveLocation.isNotNull();
this.foundDropbox = dropboxLocation.isNotNull();
this.foundGdrive = gdriveLocation.isNotNull();
+ this.foundOnedrive = onedriveLocation.isNotNull();
}
private static Path existingWritablePath(String... candidates) {
@@ -49,6 +59,22 @@ public class LocationPresets {
/* Observables */
+ public ReadOnlyObjectProperty iclouddriveLocationProperty() {
+ return iclouddriveLocation;
+ }
+
+ public Path getIclouddriveLocation() {
+ return iclouddriveLocation.get();
+ }
+
+ public BooleanBinding foundIclouddriveProperty() {
+ return foundIclouddrive;
+ }
+
+ public boolean isFoundIclouddrive() {
+ return foundIclouddrive.get();
+ }
+
public ReadOnlyObjectProperty dropboxLocationProperty() {
return dropboxLocation;
}
@@ -73,7 +99,7 @@ public class LocationPresets {
return gdriveLocation.get();
}
- public BooleanBinding froundGdriveProperty() {
+ public BooleanBinding foundGdriveProperty() {
return foundGdrive;
}
@@ -81,4 +107,20 @@ public class LocationPresets {
return foundGdrive.get();
}
+ public ReadOnlyObjectProperty onedriveLocationProperty() {
+ return onedriveLocation;
+ }
+
+ public Path getOnedriveLocation() {
+ return onedriveLocation.get();
+ }
+
+ public BooleanBinding foundOnedriveProperty() {
+ return foundOnedrive;
+ }
+
+ public boolean isFoundOnedrive() {
+ return foundOnedrive.get();
+ }
+
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java b/main/ui/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java
index 30fcaa5d2..d711f9069 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java
@@ -3,6 +3,7 @@ package org.cryptomator.ui.changepassword;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
@@ -22,6 +23,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
+import javax.inject.Named;
import java.io.IOException;
import java.util.ResourceBundle;
@@ -33,47 +35,24 @@ public class ChangePasswordController implements FxController {
private final Stage window;
private final Vault vault;
- private final ResourceBundle resourceBundle;
- private final PasswordStrengthUtil strengthRater;
- private final IntegerProperty passwordStrength;
+ private final ObjectProperty newPassword;
public NiceSecurePasswordField oldPasswordField;
- public NiceSecurePasswordField newPasswordField;
- public NiceSecurePasswordField reenterPasswordField;
- public Label passwordStrengthLabel;
- public HBox passwordMatchBox;
- public FontAwesome5IconView checkmark;
- public FontAwesome5IconView cross;
- public Label passwordMatchLabel;
public CheckBox finalConfirmationCheckbox;
public Button finishButton;
@Inject
- public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater) {
+ public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, @Named("newPassword") ObjectProperty newPassword) {
this.window = window;
this.vault = vault;
- this.resourceBundle = resourceBundle;
- this.strengthRater = strengthRater;
- this.passwordStrength = new SimpleIntegerProperty(-1);
+ this.newPassword = newPassword;
}
@FXML
public void initialize() {
- //binds the actual strength value to the rating of the password util
- passwordStrength.bind(Bindings.createIntegerBinding(() -> strengthRater.computeRate(newPasswordField.getCharacters().toString()), newPasswordField.textProperty()));
- //binding indicating if the passwords not match
- BooleanBinding passwordsMatch = Bindings.createBooleanBinding(() -> CharSequence.compare(newPasswordField.getCharacters(), reenterPasswordField.getCharacters()) == 0, newPasswordField.textProperty(), reenterPasswordField.textProperty());
- BooleanBinding reenterFieldNotEmpty = reenterPasswordField.textProperty().isNotEmpty();
- //disable the finish button when passwords do not match or one is empty
- finishButton.disableProperty().bind(reenterFieldNotEmpty.not().or(passwordsMatch.not()).or(finalConfirmationCheckbox.selectedProperty().not()));
- //make match indicator invisible when passwords do not match or one is empty
- passwordMatchBox.visibleProperty().bind(reenterFieldNotEmpty);
- checkmark.visibleProperty().bind(passwordsMatch.and(reenterFieldNotEmpty));
- checkmark.managedProperty().bind(checkmark.visibleProperty());
- cross.visibleProperty().bind(passwordsMatch.not().and(reenterFieldNotEmpty));
- cross.managedProperty().bind(cross.visibleProperty());
- passwordMatchLabel.textProperty().bind(Bindings.when(passwordsMatch.and(reenterFieldNotEmpty)).then(resourceBundle.getString("changepassword.passwordsMatch")).otherwise(resourceBundle.getString("changepassword.passwordsDoNotMatch")));
- passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
+ BooleanBinding hasNotConfirmedCheckbox = finalConfirmationCheckbox.selectedProperty().not();
+ BooleanBinding isInvalidNewPassword = Bindings.createBooleanBinding(() -> newPassword.get() == null || newPassword.get().length() == 0, newPassword);
+ finishButton.disableProperty().bind(hasNotConfirmedCheckbox.or(isInvalidNewPassword));
}
@FXML
@@ -84,15 +63,15 @@ public class ChangePasswordController implements FxController {
@FXML
public void finish() {
try {
- CryptoFileSystemProvider.changePassphrase(vault.getPath(), MASTERKEY_FILENAME, oldPasswordField.getCharacters(), newPasswordField.getCharacters());
+ CryptoFileSystemProvider.changePassphrase(vault.getPath(), MASTERKEY_FILENAME, oldPasswordField.getCharacters(), newPassword.get());
LOG.info("Successful changed password for {}", vault.getDisplayableName());
window.close();
} catch (IOException e) {
- //TODO
+ // TODO show generic error screen
LOG.error("IO error occured during password change. Unable to perform operation.", e);
e.printStackTrace();
} catch (InvalidPassphraseException e) {
- //TODO
+ // TODO shake
LOG.info("Wrong old password.");
}
}
@@ -102,12 +81,5 @@ public class ChangePasswordController implements FxController {
public Vault getVault() {
return vault;
}
-
- public IntegerProperty passwordStrengthProperty() {
- return passwordStrength;
- }
-
- public int getPasswordStrength() {
- return passwordStrength.get();
- }
+
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordModule.java b/main/ui/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordModule.java
index 7a2262aee..fcef4a7ec 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordModule.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordModule.java
@@ -4,6 +4,8 @@ import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Modality;
@@ -14,15 +16,25 @@ import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
+import org.cryptomator.ui.common.NewPasswordController;
+import org.cryptomator.ui.common.PasswordStrengthUtil;
import javax.inject.Named;
import javax.inject.Provider;
+import java.nio.CharBuffer;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
@Module
abstract class ChangePasswordModule {
+
+ @Provides
+ @ChangePasswordScoped
+ @Named("newPassword")
+ static ObjectProperty provideNewPasswordProperty() {
+ return new SimpleObjectProperty<>("");
+ }
@Provides
@ChangePasswordWindow
@@ -58,5 +70,12 @@ abstract class ChangePasswordModule {
@IntoMap
@FxControllerKey(ChangePasswordController.class)
abstract FxController bindUnlockController(ChangePasswordController controller);
+
+ @Provides
+ @IntoMap
+ @FxControllerKey(NewPasswordController.class)
+ static FxController provideNewPasswordController(ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater, @Named("newPassword") ObjectProperty password) {
+ return new NewPasswordController(resourceBundle, strengthRater, password);
+ }
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/common/FxmlFile.java b/main/ui/src/main/java/org/cryptomator/ui/common/FxmlFile.java
index 553e70bef..e6f449ccb 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/common/FxmlFile.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/common/FxmlFile.java
@@ -18,9 +18,11 @@ public enum FxmlFile {
PREFERENCES("/fxml/preferences.fxml"), //
QUIT("/fxml/quit.fxml"), //
RECOVERYKEY_CREATE("/fxml/recoverykey_create.fxml"), //
- RECOVERYKEY_DISPLAY("/fxml/recoverykey_display.fxml"), //
+ RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
REMOVE_VAULT("/fxml/remove_vault.fxml"), //
UNLOCK("/fxml/unlock.fxml"),
+ UNLOCK_GENERIC_ERROR("/fxml/unlock_generic_error.fxml"), //
+ UNLOCK_INVALID_MOUNT_POINT("/fxml/unlock_invalid_mount_point.fxml"), //
UNLOCK_SUCCESS("/fxml/unlock_success.fxml"), //
VAULT_OPTIONS("/fxml/vault_options.fxml"), //
WRONGFILEALERT("/fxml/wrongfilealert.fxml");
diff --git a/main/ui/src/main/java/org/cryptomator/ui/common/NewPasswordController.java b/main/ui/src/main/java/org/cryptomator/ui/common/NewPasswordController.java
new file mode 100644
index 000000000..2d0c424eb
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/common/NewPasswordController.java
@@ -0,0 +1,74 @@
+package org.cryptomator.ui.common;
+
+import javafx.beans.Observable;
+import javafx.beans.binding.Bindings;
+import javafx.beans.binding.BooleanBinding;
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleIntegerProperty;
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import org.cryptomator.ui.controls.FontAwesome5IconView;
+import org.cryptomator.ui.controls.NiceSecurePasswordField;
+import org.fxmisc.easybind.EasyBind;
+
+import java.util.ResourceBundle;
+
+public class NewPasswordController implements FxController {
+
+ private final ResourceBundle resourceBundle;
+ private final PasswordStrengthUtil strengthRater;
+ private final ObjectProperty password;
+ private final IntegerProperty passwordStrength = new SimpleIntegerProperty(-1);
+
+ public NiceSecurePasswordField passwordField;
+ public NiceSecurePasswordField reenterField;
+ public Label passwordStrengthLabel;
+ public Label passwordMatchLabel;
+ public FontAwesome5IconView checkmark;
+ public FontAwesome5IconView cross;
+
+ public NewPasswordController(ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater, ObjectProperty password) {
+ this.resourceBundle = resourceBundle;
+ this.strengthRater = strengthRater;
+ this.password = password;
+ }
+
+ @FXML
+ public void initialize() {
+ BooleanBinding passwordsMatch = Bindings.createBooleanBinding(this::hasSamePasswordInBothFields, passwordField.textProperty(), reenterField.textProperty());
+ BooleanBinding reenterFieldNotEmpty = reenterField.textProperty().isNotEmpty();
+ passwordStrength.bind(Bindings.createIntegerBinding(() -> strengthRater.computeRate(passwordField.getCharacters()), passwordField.textProperty()));
+ passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
+
+ passwordMatchLabel.visibleProperty().bind(reenterFieldNotEmpty);
+ passwordMatchLabel.graphicProperty().bind(Bindings.when(passwordsMatch.and(reenterFieldNotEmpty)).then(checkmark).otherwise(cross));
+ passwordMatchLabel.textProperty().bind(Bindings.when(passwordsMatch.and(reenterFieldNotEmpty)).then(resourceBundle.getString("newPassword.passwordsMatch")).otherwise(resourceBundle.getString("newPassword.passwordsDoNotMatch")));
+
+ passwordField.textProperty().addListener(this::passwordsDidChange);
+ reenterField.textProperty().addListener(this::passwordsDidChange);
+ }
+
+ private void passwordsDidChange(@SuppressWarnings("unused") Observable observable) {
+ if (hasSamePasswordInBothFields() && strengthRater.fulfillsMinimumRequirements(passwordField.getCharacters())) {
+ password.set(passwordField.getCharacters());
+ } else {
+ password.set("");
+ }
+ }
+
+ private boolean hasSamePasswordInBothFields() {
+ return CharSequence.compare(passwordField.getCharacters(), reenterField.getCharacters()) == 0;
+ }
+
+ /* Getter/Setter */
+
+ public IntegerProperty passwordStrengthProperty() {
+ return passwordStrength;
+ }
+
+ public int getPasswordStrength() {
+ return passwordStrength.get();
+ }
+
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/common/PasswordStrengthUtil.java b/main/ui/src/main/java/org/cryptomator/ui/common/PasswordStrengthUtil.java
index 76385c372..0224118cd 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/common/PasswordStrengthUtil.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/common/PasswordStrengthUtil.java
@@ -8,12 +8,11 @@
*******************************************************************************/
package org.cryptomator.ui.common;
-import com.google.common.base.Strings;
import com.nulabinc.zxcvbn.Zxcvbn;
+import org.cryptomator.common.Environment;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import javax.inject.Inject;
-import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
@@ -22,30 +21,37 @@ public class PasswordStrengthUtil {
private static final int PW_TRUNC_LEN = 100; // truncate very long passwords, since zxcvbn memory and runtime depends vastly on the length
private static final String RESSOURCE_PREFIX = "passwordStrength.messageLabel.";
+ private static final List SANITIZED_INPUTS = List.of("cryptomator");
- private final Zxcvbn zxcvbn;
- private final List sanitizedInputs;
private final ResourceBundle resourceBundle;
+ private final int minPwLength;
+ private final Zxcvbn zxcvbn;
@Inject
- public PasswordStrengthUtil(ResourceBundle resourceBundle) {
+ public PasswordStrengthUtil(ResourceBundle resourceBundle, Environment environment) {
this.resourceBundle = resourceBundle;
+ this.minPwLength = environment.getMinPwLength();
this.zxcvbn = new Zxcvbn();
- this.sanitizedInputs = List.of("cryptomator");
}
- public int computeRate(String password) {
- if (Strings.isNullOrEmpty(password)) {
+ public boolean fulfillsMinimumRequirements(CharSequence password) {
+ return password.length() >= minPwLength;
+ }
+
+ public int computeRate(CharSequence password) {
+ if (password == null || password.length() < minPwLength) {
return -1;
} else {
int numCharsToRate = Math.min(PW_TRUNC_LEN, password.length());
- return zxcvbn.measure(password.substring(0, numCharsToRate), sanitizedInputs).getScore();
+ return zxcvbn.measure(password.subSequence(0, numCharsToRate), SANITIZED_INPUTS).getScore();
}
}
public String getStrengthDescription(Number score) {
- if (resourceBundle.containsKey(RESSOURCE_PREFIX + score.intValue())) {
- return resourceBundle.getString("passwordStrength.messageLabel." + score.intValue());
+ if (score.intValue() == -1) {
+ return String.format(resourceBundle.getString(RESSOURCE_PREFIX + "tooShort"), minPwLength);
+ } else if (resourceBundle.containsKey(RESSOURCE_PREFIX + score.intValue())) {
+ return resourceBundle.getString(RESSOURCE_PREFIX + score.intValue());
} else {
return "";
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/common/StackTraceController.java b/main/ui/src/main/java/org/cryptomator/ui/common/StackTraceController.java
new file mode 100644
index 000000000..accab1b89
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/common/StackTraceController.java
@@ -0,0 +1,28 @@
+package org.cryptomator.ui.common;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+
+public class StackTraceController implements FxController {
+
+ private final String stackTrace;
+
+ public StackTraceController(Exception cause) {
+ this.stackTrace = provideStackTrace(cause);
+ }
+
+ static String provideStackTrace(Exception cause) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ cause.printStackTrace(new PrintStream(baos));
+ return baos.toString(StandardCharsets.UTF_8);
+ }
+
+ /* Getter/Setter */
+
+ public String getStackTrace() {
+ return stackTrace;
+ }
+
+
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/common/VaultService.java b/main/ui/src/main/java/org/cryptomator/ui/common/VaultService.java
new file mode 100644
index 000000000..ec2030fd2
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/common/VaultService.java
@@ -0,0 +1,190 @@
+package org.cryptomator.ui.common;
+
+import javafx.concurrent.Task;
+import org.cryptomator.common.vaults.Vault;
+import org.cryptomator.common.vaults.VaultState;
+import org.cryptomator.common.vaults.Volume;
+import org.cryptomator.ui.fxapp.FxApplicationScoped;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+@FxApplicationScoped
+public class VaultService {
+
+ private static final Logger LOG = LoggerFactory.getLogger(VaultService.class);
+
+ private final ExecutorService executorService;
+
+ @Inject
+ public VaultService(ExecutorService executorService) {
+ this.executorService = executorService;
+ }
+
+ public void reveal(Vault vault) {
+ executorService.execute(createRevealTask(vault));
+ }
+
+ /**
+ * Creates but doesn't start a reveal task.
+ *
+ * @param vault The vault to reveal
+ */
+ public Task createRevealTask(Vault vault) {
+ Task task = new RevealVaultTask(vault);
+ task.setOnSucceeded(evt -> LOG.info("Revealed {}", vault.getDisplayableName()));
+ task.setOnFailed(evt -> LOG.error("Failed to reveal " + vault.getDisplayableName(), evt.getSource().getException()));
+ return task;
+ }
+
+ /**
+ * Locks a vault in a background thread.
+ *
+ * @param vault The vault to lock
+ * @param forced Whether to attempt a forced lock
+ */
+ public void lock(Vault vault, boolean forced) {
+ executorService.execute(createLockTask(vault, forced));
+ }
+
+ /**
+ * Creates but doesn't start a lock task.
+ *
+ * @param vault The vault to lock
+ * @param forced Whether to attempt a forced lock
+ */
+ public Task createLockTask(Vault vault, boolean forced) {
+ Task task = new LockVaultTask(vault, forced);
+ task.setOnSucceeded(evt -> LOG.info("Locked {}", vault.getDisplayableName()));
+ task.setOnFailed(evt -> LOG.error("Failed to lock " + vault.getDisplayableName(), evt.getSource().getException()));
+ return task;
+ }
+
+ /**
+ * Locks all given vaults in a background thread.
+ *
+ * @param vaults The vaults to lock
+ * @param forced Whether to attempt a forced lock
+ */
+ public void lockAll(Collection vaults, boolean forced) {
+ executorService.execute(createLockAllTask(vaults, forced));
+ }
+
+ /**
+ * Creates but doesn't start a lock-all task.
+ *
+ * @param vaults The list of vaults to be locked
+ * @param forced Whether to attempt a forced lock
+ * @return Meta-Task that waits until all vaults are locked or fails after the first failure of a subtask
+ */
+ public Task> createLockAllTask(Collection vaults, boolean forced) {
+ List> lockTasks = vaults.stream().map(v -> new LockVaultTask(v, forced)).collect(Collectors.toUnmodifiableList());
+ lockTasks.forEach(executorService::execute);
+ Task> task = new WaitForTasksTask(lockTasks);
+ String vaultNames = vaults.stream().map(Vault::getDisplayableName).collect(Collectors.joining(", "));
+ task.setOnSucceeded(evt -> LOG.info("Locked {}", vaultNames));
+ task.setOnFailed(evt -> LOG.error("Failed to lock vaults " + vaultNames, evt.getSource().getException()));
+ return task;
+ }
+
+ private static class RevealVaultTask extends Task {
+
+ private final Vault vault;
+
+ /**
+ * @param vault The vault to lock
+ */
+ public RevealVaultTask(Vault vault) {
+ this.vault = vault;
+ }
+
+ @Override
+ protected Vault call() throws Volume.VolumeException {
+ vault.reveal();
+ return vault;
+ }
+ }
+
+ /**
+ * A task that waits for completion of multiple other tasks
+ */
+ private static class WaitForTasksTask extends Task> {
+
+ private final Collection> startedTasks;
+
+ public WaitForTasksTask(Collection> tasks) {
+ this.startedTasks = List.copyOf(tasks);
+ }
+
+ @Override
+ protected Collection call() throws Exception {
+ Iterator> remainingTasks = startedTasks.iterator();
+ Collection completed = new ArrayList<>();
+ try {
+ // wait for all tasks:
+ while (remainingTasks.hasNext()) {
+ Vault lockedVault = remainingTasks.next().get();
+ completed.add(lockedVault);
+ }
+ } catch (ExecutionException e) {
+ // cancel all remaining:
+ while (remainingTasks.hasNext()) {
+ remainingTasks.next().cancel(true);
+ }
+ throw e;
+ }
+ return List.copyOf(completed);
+ }
+ }
+
+ /**
+ * A task that locks a vault
+ */
+ private static class LockVaultTask extends Task {
+
+ private final Vault vault;
+ private final boolean forced;
+
+ /**
+ * @param vault The vault to lock
+ * @param forced Whether to attempt a forced lock
+ */
+ public LockVaultTask(Vault vault, boolean forced) {
+ this.vault = vault;
+ this.forced = forced;
+ }
+
+ @Override
+ protected Vault call() throws Volume.VolumeException {
+ vault.lock(forced);
+ return vault;
+ }
+
+ @Override
+ protected void scheduled() {
+ vault.setState(VaultState.PROCESSING);
+ }
+
+ @Override
+ protected void succeeded() {
+ vault.setState(VaultState.LOCKED);
+ }
+
+ @Override
+ protected void failed() {
+ vault.setState(VaultState.UNLOCKED);
+ }
+
+ }
+
+
+
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java b/main/ui/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java
index f5b62ae2b..742d29438 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java
@@ -9,23 +9,29 @@ public enum FontAwesome5Icon {
CHECK("\uF00C"), //
COG("\uF013"), //
COGS("\uF085"), //
+ COPY("\uF0C5"), //
EXCLAMATION("\uF12A"),
+ EXCLAMATION_CIRCLE("\uF06A"), //
EXCLAMATION_TRIANGLE("\uF071"), //
EYE("\uF06E"), //
EYE_SLASH("\uF070"), //
FILE_IMPORT("\uF56F"), //
FOLDER_OPEN("\uF07C"), //
+ HAND_HOLDING_HEART("\uF4BE"), //
+ HEART("\uF004"), //
HDD("\uF0A0"), //
KEY("\uF084"), //
+ LINK("\uF0C1"), //
LOCK_ALT("\uF30D"), //
LOCK_OPEN_ALT("\uF3C2"), //
- MINUS("\uF068"), //
PLUS("\uF067"), //
+ PRINT("\uF02F"), //
QUESTION("\uF128"), //
SPARKLES("\uF890"), //
SPINNER("\uF110"), //
SYNC("\uF021"), //
TIMES("\uF00D"), //
+ USER_CROWN("\uF6A4"), //
WRENCH("\uF0AD"), //
;
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java b/main/ui/src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java
index 3a85fbad6..0a50f5322 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java
@@ -89,7 +89,7 @@ public class NiceSecurePasswordField extends StackPane {
}
public void swipe() {
- passwordField.swipe();;
+ passwordField.swipe();
}
public void selectAll() {
diff --git a/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java b/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java
index f52a9b2ed..aca5650ea 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java
@@ -9,6 +9,7 @@ import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import javafx.stage.Stage;
+import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.UiTheme;
import org.cryptomator.common.vaults.Vault;
@@ -16,8 +17,10 @@ import org.cryptomator.jni.JniException;
import org.cryptomator.jni.MacApplicationUiAppearance;
import org.cryptomator.jni.MacApplicationUiState;
import org.cryptomator.jni.MacFunctions;
+import org.cryptomator.ui.common.VaultService;
import org.cryptomator.ui.mainwindow.MainWindowComponent;
import org.cryptomator.ui.preferences.PreferencesComponent;
+import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.quit.QuitComponent;
import org.cryptomator.ui.unlock.UnlockComponent;
import org.slf4j.Logger;
@@ -38,17 +41,21 @@ public class FxApplication extends Application {
private final UnlockComponent.Builder unlockWindowBuilder;
private final QuitComponent.Builder quitWindowBuilder;
private final Optional macFunctions;
+ private final VaultService vaultService;
+ private final LicenseHolder licenseHolder;
private final ObservableSet visibleStages = FXCollections.observableSet();
private final BooleanBinding hasVisibleStages = Bindings.isNotEmpty(visibleStages);
@Inject
- FxApplication(Settings settings, Lazy mainWindow, Lazy preferencesWindow, UnlockComponent.Builder unlockWindowBuilder, QuitComponent.Builder quitWindowBuilder, Optional macFunctions) {
+ FxApplication(Settings settings, Lazy mainWindow, Lazy preferencesWindow, UnlockComponent.Builder unlockWindowBuilder, QuitComponent.Builder quitWindowBuilder, Optional macFunctions, VaultService vaultService, LicenseHolder licenseHolder) {
this.settings = settings;
this.mainWindow = mainWindow;
this.preferencesWindow = preferencesWindow;
this.unlockWindowBuilder = unlockWindowBuilder;
this.quitWindowBuilder = quitWindowBuilder;
this.macFunctions = macFunctions;
+ this.vaultService = vaultService;
+ this.licenseHolder = licenseHolder;
}
public void start() {
@@ -79,9 +86,9 @@ public class FxApplication extends Application {
}
}
- public void showPreferencesWindow() {
+ public void showPreferencesWindow(SelectedPreferencesTab selectedTab) {
Platform.runLater(() -> {
- Stage stage = preferencesWindow.get().showPreferencesWindow();
+ Stage stage = preferencesWindow.get().showPreferencesWindow(selectedTab);
addVisibleStage(stage);
LOG.debug("Showing Preferences");
});
@@ -111,11 +118,16 @@ public class FxApplication extends Application {
});
}
+ public VaultService getVaultService() {
+ return vaultService;
+ }
+
private void themeChanged(@SuppressWarnings("unused") ObservableValue extends UiTheme> observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) {
loadSelectedStyleSheet(newValue);
}
- private void loadSelectedStyleSheet(UiTheme theme) {
+ private void loadSelectedStyleSheet(UiTheme desiredTheme) {
+ UiTheme theme = licenseHolder.isValidLicense() ? desiredTheme : UiTheme.LIGHT;
switch (theme) {
// case CUSTOM:
// // TODO
diff --git a/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java b/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java
index 43fa9e749..a51470489 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java
@@ -5,8 +5,10 @@
*******************************************************************************/
package org.cryptomator.ui.fxapp;
+import dagger.Binds;
import dagger.Module;
import dagger.Provides;
+import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.image.Image;
@@ -44,6 +46,9 @@ abstract class FxApplicationModule {
return Optional.empty();
}
}
+
+ @Binds
+ abstract Application bindApplication(FxApplication application);
@Provides
static MainWindowComponent provideMainWindowComponent(MainWindowComponent.Builder builder) {
diff --git a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowController.java b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowController.java
index f0acc0992..14d9350da 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowController.java
@@ -1,30 +1,24 @@
package org.cryptomator.ui.mainwindow;
-import javafx.beans.binding.BooleanBinding;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.SimpleBooleanProperty;
import javafx.fxml.FXML;
+import javafx.scene.input.DragEvent;
import javafx.scene.input.TransferMode;
-import javafx.scene.layout.HBox;
-import javafx.scene.layout.Pane;
-import javafx.scene.layout.Region;
-import javafx.scene.layout.VBox;
-import javafx.stage.Stage;
-import org.cryptomator.common.vaults.Vault;
+import javafx.scene.layout.StackPane;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.ui.common.FxController;
-import org.cryptomator.ui.fxapp.FxApplication;
-import org.cryptomator.ui.fxapp.UpdateChecker;
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
-import javax.inject.Named;
import java.io.File;
+import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
-import java.util.Collection;
+import java.util.Set;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
@MainWindowScoped
public class MainWindowController implements FxController {
@@ -32,27 +26,14 @@ public class MainWindowController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(MainWindowController.class);
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes
- private final Stage window;
- private final FxApplication application;
- private final boolean minimizeToSysTray;
- private final UpdateChecker updateChecker;
- private final BooleanBinding updateAvailable;
private final VaultListManager vaultListManager;
private final WrongFileAlertComponent.Builder wrongFileAlert;
- public HBox titleBar;
- public VBox root;
- public Pane dragAndDropIndicator;
- public Region resizer;
- private double xOffset;
- private double yOffset;
+ private final BooleanProperty draggingOver = new SimpleBooleanProperty();
+ private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
+ public StackPane root;
@Inject
- public MainWindowController(@MainWindow Stage window, FxApplication application, @Named("trayMenuSupported") boolean minimizeToSysTray, UpdateChecker updateChecker, VaultListManager vaultListManager, WrongFileAlertComponent.Builder wrongFileAlert) {
- this.window = window;
- this.application = application;
- this.minimizeToSysTray = minimizeToSysTray;
- this.updateChecker = updateChecker;
- this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
+ public MainWindowController(VaultListManager vaultListManager, WrongFileAlertComponent.Builder wrongFileAlert) {
this.vaultListManager = vaultListManager;
this.wrongFileAlert = wrongFileAlert;
}
@@ -60,77 +41,70 @@ public class MainWindowController implements FxController {
@FXML
public void initialize() {
LOG.debug("init MainWindowController");
- titleBar.setOnMousePressed(event -> {
- xOffset = event.getSceneX();
- yOffset = event.getSceneY();
- });
- titleBar.setOnMouseDragged(event -> {
- window.setX(event.getScreenX() - xOffset);
- window.setY(event.getScreenY() - yOffset);
- });
- resizer.setOnMouseDragged(event -> {
- // we know for a fact that window is borderless. i.e. the scene starts at 0/0 of the window.
- window.setWidth(event.getSceneX());
- window.setHeight(event.getSceneY());
- });
- updateChecker.automaticallyCheckForUpdatesIfEnabled();
- dragAndDropIndicator.setVisible(false);
- root.setOnDragOver(event -> {
- if (event.getGestureSource() != root && event.getDragboard().hasFiles()) {
- /* allow for both copying and moving, whatever user chooses */
- event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
- dragAndDropIndicator.setVisible(true);
- }
- event.consume();
- });
- root.setOnDragExited(event -> dragAndDropIndicator.setVisible(false));
- root.setOnDragDropped(event -> {
- if (event.getGestureSource() != root && event.getDragboard().hasFiles()) {
- /* allow for both copying and moving, whatever user chooses */
- event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
- Collection vaultPaths = event.getDragboard().getFiles().stream().map(File::toPath).flatMap(this::addVault).collect(Collectors.toSet());
- if (vaultPaths.isEmpty()) {
- wrongFileAlert.build().showWrongFileAlertWindow();
- }
- }
- event.consume();
- });
+ root.setOnDragEntered(this::handleDragEvent);
+ root.setOnDragOver(this::handleDragEvent);
+ root.setOnDragDropped(this::handleDragEvent);
+ root.setOnDragExited(this::handleDragEvent);
}
- private Stream addVault(Path pathToVault) {
+ private void handleDragEvent(DragEvent event) {
+ if (DragEvent.DRAG_ENTERED.equals(event.getEventType()) && event.getGestureSource() == null) {
+ draggingOver.set(true);
+ } else if (DragEvent.DRAG_OVER.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
+ event.acceptTransferModes(TransferMode.ANY);
+ draggingVaultOver.set(event.getDragboard().getFiles().stream().map(File::toPath).anyMatch(this::containsVault));
+ } else if (DragEvent.DRAG_DROPPED.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
+ Set vaultPaths = event.getDragboard().getFiles().stream().map(File::toPath).filter(this::containsVault).collect(Collectors.toSet());
+ if (vaultPaths.isEmpty()) {
+ wrongFileAlert.build().showWrongFileAlertWindow();
+ } else {
+ vaultPaths.forEach(this::addVault);
+ }
+ event.setDropCompleted(!vaultPaths.isEmpty());
+ event.consume();
+ } else if (DragEvent.DRAG_EXITED.equals(event.getEventType())) {
+ draggingOver.set(false);
+ draggingVaultOver.set(false);
+ }
+ }
+
+ private boolean containsVault(Path path) {
+ if (path.getFileName().toString().equals(MASTERKEY_FILENAME)) {
+ return true;
+ } else if (Files.isDirectory(path) && Files.exists(path.resolve(MASTERKEY_FILENAME))) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private void addVault(Path pathToVault) {
try {
if (pathToVault.getFileName().toString().equals(MASTERKEY_FILENAME)) {
- return Stream.of(vaultListManager.add(pathToVault.getParent()));
+ vaultListManager.add(pathToVault.getParent());
} else {
- return Stream.of(vaultListManager.add(pathToVault));
+ vaultListManager.add(pathToVault);
}
} catch (NoSuchFileException e) {
LOG.debug("Not a vault: {}", pathToVault);
}
- return Stream.empty();
- }
-
- @FXML
- public void close() {
- if (minimizeToSysTray) {
- window.close();
- } else {
- window.setIconified(true);
- }
- }
-
- @FXML
- public void showPreferences() {
- application.showPreferencesWindow();
}
/* Getter/Setter */
- public BooleanBinding updateAvailableProperty() {
- return updateAvailable;
+ public BooleanProperty draggingOverProperty() {
+ return draggingOver;
}
- public boolean isUpdateAvailable() {
- return updateAvailable.get();
+ public boolean isDraggingOver() {
+ return draggingOver.get();
+ }
+
+ public BooleanProperty draggingVaultOverProperty() {
+ return draggingVaultOver;
+ }
+
+ public boolean isDraggingVaultOver() {
+ return draggingVaultOver.get();
}
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java
index 5ef76c5e0..8fb9dd1a4 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java
@@ -68,6 +68,16 @@ abstract class MainWindowModule {
@FxControllerKey(MainWindowController.class)
abstract FxController bindMainWindowController(MainWindowController controller);
+ @Binds
+ @IntoMap
+ @FxControllerKey(MainWindowTitleController.class)
+ abstract FxController bindMainWindowTitleController(MainWindowTitleController controller);
+
+ @Binds
+ @IntoMap
+ @FxControllerKey(ResizeController.class)
+ abstract FxController bindResizeController(ResizeController controller);
+
@Binds
@IntoMap
@FxControllerKey(VaultListController.class)
diff --git a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowSceneFactory.java b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowSceneFactory.java
index b86a56e5f..dbb9b4cbf 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowSceneFactory.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowSceneFactory.java
@@ -17,22 +17,22 @@ public class MainWindowSceneFactory extends DefaultSceneFactory {
protected static final KeyCodeCombination SHORTCUT_N = new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN);
- private final Lazy mainWindowController;
+ private final Lazy mainWindowTitleController;
private final Lazy vaultListController;
@Inject
- public MainWindowSceneFactory(Settings settings, Lazy mainWindowController, Lazy vaultListController) {
+ public MainWindowSceneFactory(Settings settings, Lazy mainWindowTitleController, Lazy vaultListController) {
super(settings);
- this.mainWindowController = mainWindowController;
+ this.mainWindowTitleController = mainWindowTitleController;
this.vaultListController = vaultListController;
}
@Override
protected void setupDefaultAccelerators(Scene scene, Stage stage) {
if (SystemUtils.IS_OS_WINDOWS) {
- scene.getAccelerators().put(ALT_F4, mainWindowController.get()::close);
+ scene.getAccelerators().put(ALT_F4, mainWindowTitleController.get()::close);
} else {
- scene.getAccelerators().put(SHORTCUT_W, mainWindowController.get()::close);
+ scene.getAccelerators().put(SHORTCUT_W, mainWindowTitleController.get()::close);
}
scene.getAccelerators().put(SHORTCUT_N, vaultListController.get()::didClickAddVault);
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java
new file mode 100644
index 000000000..49bd905f5
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java
@@ -0,0 +1,95 @@
+package org.cryptomator.ui.mainwindow;
+
+import javafx.beans.binding.BooleanBinding;
+import javafx.fxml.FXML;
+import javafx.scene.layout.HBox;
+import javafx.stage.Stage;
+import org.cryptomator.common.LicenseHolder;
+import org.cryptomator.common.vaults.VaultListManager;
+import org.cryptomator.ui.common.FxController;
+import org.cryptomator.ui.fxapp.FxApplication;
+import org.cryptomator.ui.fxapp.UpdateChecker;
+import org.cryptomator.ui.preferences.SelectedPreferencesTab;
+import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+@MainWindowScoped
+public class MainWindowTitleController implements FxController {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MainWindowTitleController.class);
+
+ public HBox titleBar;
+
+ private final Stage window;
+ private final FxApplication application;
+ private final boolean minimizeToSysTray;
+ private final UpdateChecker updateChecker;
+ private final BooleanBinding updateAvailable;
+ private final LicenseHolder licenseHolder;
+
+ private double xOffset;
+ private double yOffset;
+
+ @Inject
+ MainWindowTitleController(@MainWindow Stage window, FxApplication application, @Named("trayMenuSupported") boolean minimizeToSysTray, UpdateChecker updateChecker, LicenseHolder licenseHolder) {
+ this.window = window;
+ this.application = application;
+ this.minimizeToSysTray = minimizeToSysTray;
+ this.updateChecker = updateChecker;
+ this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
+ this.licenseHolder = licenseHolder;
+ }
+
+ @FXML
+ public void initialize() {
+ LOG.debug("init MainWindowTitleController");
+ updateChecker.automaticallyCheckForUpdatesIfEnabled();
+ titleBar.setOnMousePressed(event -> {
+ xOffset = event.getSceneX();
+ yOffset = event.getSceneY();
+ });
+ titleBar.setOnMouseDragged(event -> {
+ window.setX(event.getScreenX() - xOffset);
+ window.setY(event.getScreenY() - yOffset);
+ });
+ }
+
+ @FXML
+ public void close() {
+ if (minimizeToSysTray) {
+ window.close();
+ } else {
+ window.setIconified(true);
+ }
+ }
+
+ @FXML
+ public void showPreferences() {
+ application.showPreferencesWindow(SelectedPreferencesTab.ANY);
+ }
+
+ @FXML
+ public void showDonationKeyPreferences() {
+ application.showPreferencesWindow(SelectedPreferencesTab.DONATION_KEY);
+ }
+
+ /* Getter/Setter */
+
+ public LicenseHolder getLicenseHolder() {
+ return licenseHolder;
+ }
+
+ public BooleanBinding updateAvailableProperty() {
+ return updateAvailable;
+ }
+
+ public boolean isUpdateAvailable() {
+ return updateAvailable.get();
+ }
+
+
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/ResizeController.java b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/ResizeController.java
new file mode 100644
index 000000000..fbf67aeb2
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/ResizeController.java
@@ -0,0 +1,102 @@
+package org.cryptomator.ui.mainwindow;
+
+import javafx.fxml.FXML;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.Region;
+import javafx.stage.Stage;
+import org.cryptomator.ui.common.FxController;
+
+import javax.inject.Inject;
+
+@MainWindow
+public class ResizeController implements FxController {
+
+ private final Stage window;
+
+ public Region tlResizer;
+ public Region trResizer;
+ public Region blResizer;
+ public Region brResizer;
+
+ private double origX, origY, origW, origH;
+
+ @Inject
+ ResizeController(@MainWindow Stage window) {
+ this.window = window;
+ // TODO inject settings and save current position and size
+ }
+
+ @FXML
+ public void initialize() {
+ tlResizer.setOnMousePressed(this::startResize);
+ trResizer.setOnMousePressed(this::startResize);
+ blResizer.setOnMousePressed(this::startResize);
+ brResizer.setOnMousePressed(this::startResize);
+ tlResizer.setOnMouseDragged(this::resizeTopLeft);
+ trResizer.setOnMouseDragged(this::resizeTopRight);
+ blResizer.setOnMouseDragged(this::resizeBottomLeft);
+ brResizer.setOnMouseDragged(this::resizeBottomRight);
+ }
+
+ private void startResize(MouseEvent evt) {
+ origX = window.getX();
+ origY = window.getY();
+ origW = window.getWidth();
+ origH = window.getHeight();
+ }
+
+ private void resizeTopLeft(MouseEvent evt) {
+ resizeTop(evt);
+ resizeLeft(evt);
+ }
+
+ private void resizeTopRight(MouseEvent evt) {
+ resizeTop(evt);
+ resizeRight(evt);
+ }
+
+ private void resizeBottomLeft(MouseEvent evt) {
+ resizeBottom(evt);
+ resizeLeft(evt);
+ }
+
+ private void resizeBottomRight(MouseEvent evt) {
+ resizeBottom(evt);
+ resizeRight(evt);
+ }
+
+ private void resizeTop(MouseEvent evt) {
+ double newY = evt.getScreenY();
+ double dy = newY - origY;
+ double newH = origH - dy;
+ if (newH < window.getMaxHeight() && newH > window.getMinHeight()) {
+ window.setY(newY);
+ window.setHeight(newH);
+ }
+ }
+
+ private void resizeLeft(MouseEvent evt) {
+ double newX = evt.getScreenX();
+ double dx = newX - origX;
+ double newW = origW - dx;
+ if (newW < window.getMaxWidth() && newW > window.getMinWidth()) {
+ window.setX(newX);
+ window.setWidth(newW);
+ }
+ }
+
+ private void resizeBottom(MouseEvent evt) {
+ double newH = evt.getSceneY();
+ if (newH < window.getMaxHeight() && newH > window.getMinHeight()) {
+ window.setHeight(newH);
+ }
+ }
+
+ private void resizeRight(MouseEvent evt) {
+ double newW = evt.getSceneX();
+ if (newW < window.getMaxWidth() && newW > window.getMinWidth()) {
+ window.setWidth(newW);
+ }
+ }
+
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnlockedController.java b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnlockedController.java
index f2d801679..32f9bc9b0 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnlockedController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnlockedController.java
@@ -4,53 +4,32 @@ import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.fxml.FXML;
import org.cryptomator.common.vaults.Vault;
-import org.cryptomator.common.vaults.VaultState;
-import org.cryptomator.common.vaults.Volume;
import org.cryptomator.ui.common.FxController;
-import org.cryptomator.ui.common.Tasks;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.cryptomator.ui.common.VaultService;
import javax.inject.Inject;
-import java.util.concurrent.ExecutorService;
@MainWindowScoped
public class VaultDetailUnlockedController implements FxController {
- private static final Logger LOG = LoggerFactory.getLogger(VaultDetailUnlockedController.class);
-
private final ReadOnlyObjectProperty vault;
- private final ExecutorService executor;
+ private final VaultService vaultService;
@Inject
- public VaultDetailUnlockedController(ObjectProperty vault, ExecutorService executor) {
+ public VaultDetailUnlockedController(ObjectProperty vault, VaultService vaultService) {
this.vault = vault;
- this.executor = executor;
+ this.vaultService = vaultService;
}
@FXML
public void revealAccessLocation() {
- try {
- vault.get().reveal();
- } catch (Volume.VolumeException e) {
- LOG.error("Failed to reveal vault.", e);
- }
+ vaultService.reveal(vault.get());
}
@FXML
public void lock() {
- Vault v = vault.get();
- v.setState(VaultState.PROCESSING);
- Tasks.create(() -> {
- v.lock(false);
- }).onSuccess(() -> {
- LOG.trace("Regular unmount succeeded.");
- v.setState(VaultState.LOCKED);
- }).onError(Exception.class, e -> {
- v.setState(VaultState.UNLOCKED);
- LOG.error("Regular unmount failed.", e);
- // TODO
- }).runOnce(executor);
+ vaultService.lock(vault.get(), false);
+ // TODO count lock attempts, and allow forced lock
}
/* Getter/Setter */
diff --git a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java
index 82fe7c7c7..0da7063ce 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java
@@ -1,14 +1,18 @@
package org.cryptomator.ui.mainwindow;
+import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ObjectProperty;
+import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.ListView;
import javafx.stage.Stage;
import org.cryptomator.common.vaults.Vault;
+import org.cryptomator.common.vaults.VaultListManager;
+import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.removevault.RemoveVaultComponent;
@@ -23,7 +27,6 @@ public class VaultListController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(VaultListController.class);
- private final Stage window;
private final ObservableList vaults;
private final ObjectProperty selectedVault;
private final VaultListCellFactory cellFactory;
@@ -34,8 +37,7 @@ public class VaultListController implements FxController {
public ListView vaultList;
@Inject
- VaultListController(@MainWindow Stage window, ObservableList vaults, ObjectProperty selectedVault, VaultListCellFactory cellFactory, AddVaultWizardComponent.Builder addVaultWizard, RemoveVaultComponent.Builder removeVault) {
- this.window = window;
+ VaultListController(ObservableList vaults, ObjectProperty selectedVault, VaultListCellFactory cellFactory, AddVaultWizardComponent.Builder addVaultWizard, RemoveVaultComponent.Builder removeVault) {
this.vaults = vaults;
this.selectedVault = selectedVault;
this.cellFactory = cellFactory;
@@ -43,6 +45,7 @@ public class VaultListController implements FxController {
this.removeVault = removeVault;
this.noVaultSelected = selectedVault.isNull();
this.emptyVaultList = Bindings.isEmpty(vaults);
+ selectedVault.addListener(this::selectedVaultDidChange);
}
public void initialize() {
@@ -59,6 +62,23 @@ public class VaultListController implements FxController {
});
}
+ private void selectedVaultDidChange(@SuppressWarnings("unused") ObservableValue extends Vault> observableValue, @SuppressWarnings("unused") Vault oldValue, Vault newValue) {
+ VaultState reportedState = newValue.getState();
+ switch (reportedState) {
+ case LOCKED:
+ case NEEDS_MIGRATION:
+ case MISSING:
+ VaultState determinedState = VaultListManager.determineVaultState(newValue.getPath());
+ newValue.setState(determinedState);
+ break;
+ case ERROR:
+ case UNLOCKED:
+ case PROCESSING:
+ default:
+ // no-op
+ }
+ }
+
@FXML
public void didClickAddVault() {
addVaultWizard.build().showAddVaultWizard();
diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/AutoStartMacStrategy.java b/main/ui/src/main/java/org/cryptomator/ui/preferences/AutoStartMacStrategy.java
new file mode 100644
index 000000000..0bc469a77
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/preferences/AutoStartMacStrategy.java
@@ -0,0 +1,43 @@
+package org.cryptomator.ui.preferences;
+
+import org.cryptomator.jni.MacFunctions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+class AutoStartMacStrategy implements AutoStartStrategy {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AutoStartMacStrategy.class);
+
+ private final MacFunctions macFunctions;
+
+ public AutoStartMacStrategy(MacFunctions macFunctions) {
+ this.macFunctions = macFunctions;
+ }
+
+ @Override
+ public CompletionStage isAutoStartEnabled() {
+ boolean enabled = macFunctions.launchServices().isLoginItemEnabled();
+ return CompletableFuture.completedFuture(enabled);
+ }
+
+ @Override
+ public void enableAutoStart() throws TogglingAutoStartFailedException {
+ if (macFunctions.launchServices().enableLoginItem()) {
+ LOG.debug("Added login item.");
+ } else {
+ throw new TogglingAutoStartFailedException("Failed to add login item.");
+ }
+ }
+
+ @Override
+ public void disableAutoStart() throws TogglingAutoStartFailedException {
+ if (macFunctions.launchServices().disableLoginItem()) {
+ LOG.debug("Removed login item.");
+ } else {
+ throw new TogglingAutoStartFailedException("Failed to remove login item.");
+ }
+ }
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/AutoStartModule.java b/main/ui/src/main/java/org/cryptomator/ui/preferences/AutoStartModule.java
new file mode 100644
index 000000000..d006d8681
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/preferences/AutoStartModule.java
@@ -0,0 +1,26 @@
+package org.cryptomator.ui.preferences;
+
+import dagger.Module;
+import dagger.Provides;
+import org.apache.commons.lang3.SystemUtils;
+import org.cryptomator.jni.MacFunctions;
+
+import java.util.Optional;
+
+@Module
+abstract class AutoStartModule {
+
+ @Provides
+ @PreferencesScoped
+ public static Optional provideAutoStartStrategy(Optional macFunctions) {
+ if (SystemUtils.IS_OS_MAC_OSX && macFunctions.isPresent()) {
+ return Optional.of(new AutoStartMacStrategy(macFunctions.get()));
+ } else if (SystemUtils.IS_OS_WINDOWS) {
+ Optional exeName = ProcessHandle.current().info().command();
+ return exeName.map(AutoStartWinStrategy::new);
+ } else {
+ return Optional.empty();
+ }
+ }
+
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/AutoStartStrategy.java b/main/ui/src/main/java/org/cryptomator/ui/preferences/AutoStartStrategy.java
new file mode 100644
index 000000000..99b21b4cd
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/preferences/AutoStartStrategy.java
@@ -0,0 +1,24 @@
+package org.cryptomator.ui.preferences;
+
+import java.util.concurrent.CompletionStage;
+
+public interface AutoStartStrategy {
+
+ CompletionStage isAutoStartEnabled();
+
+ void enableAutoStart() throws TogglingAutoStartFailedException;
+
+ void disableAutoStart() throws TogglingAutoStartFailedException;
+
+ class TogglingAutoStartFailedException extends Exception {
+
+ public TogglingAutoStartFailedException(String message) {
+ super(message);
+ }
+
+ public TogglingAutoStartFailedException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ }
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/AutoStartWinStrategy.java b/main/ui/src/main/java/org/cryptomator/ui/preferences/AutoStartWinStrategy.java
new file mode 100644
index 000000000..f4a8b0578
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/preferences/AutoStartWinStrategy.java
@@ -0,0 +1,91 @@
+package org.cryptomator.ui.preferences;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+class AutoStartWinStrategy implements AutoStartStrategy {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AutoStartWinStrategy.class);
+ private static final String HKCU_AUTOSTART_KEY = "\"HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\"";
+ private static final String AUTOSTART_VALUE = "Cryptomator";
+ private final String exePath;
+
+ public AutoStartWinStrategy(String exePath) {
+ this.exePath = exePath;
+ }
+
+ @Override
+ public CompletionStage isAutoStartEnabled() {
+ ProcessBuilder regQuery = new ProcessBuilder("reg", "query", HKCU_AUTOSTART_KEY, //
+ "/v", AUTOSTART_VALUE);
+ try {
+ Process proc = regQuery.start();
+ return proc.onExit().thenApply(p -> p.exitValue() == 0);
+ } catch (IOException e) {
+ LOG.warn("Failed to query {} from registry key {}", AUTOSTART_VALUE, HKCU_AUTOSTART_KEY);
+ return CompletableFuture.completedFuture(false);
+ }
+ }
+
+ @Override
+ public void enableAutoStart() throws TogglingAutoStartFailedException {
+ ProcessBuilder regAdd = new ProcessBuilder("reg", "add", HKCU_AUTOSTART_KEY, //
+ "/v", AUTOSTART_VALUE, //
+ "/t", "REG_SZ", //
+ "/d", "\"" + exePath + "\"", //
+ "/f");
+ String command = regAdd.command().stream().collect(Collectors.joining(" "));
+ try {
+ Process proc = regAdd.start();
+ boolean finishedInTime = waitForProcess(proc, 5, TimeUnit.SECONDS);
+ if (finishedInTime) {
+ LOG.debug("Added {} to registry key {}.", AUTOSTART_VALUE, HKCU_AUTOSTART_KEY);
+ } else {
+ throw new TogglingAutoStartFailedException("Adding registry value failed.");
+ }
+ } catch (IOException e) {
+ throw new TogglingAutoStartFailedException("Adding registry value failed. " + command, e);
+ }
+ }
+
+ @Override
+ public void disableAutoStart() throws TogglingAutoStartFailedException {
+ ProcessBuilder regRemove = new ProcessBuilder("reg", "delete", HKCU_AUTOSTART_KEY, //
+ "/v", AUTOSTART_VALUE, //
+ "/f");
+ String command = regRemove.command().stream().collect(Collectors.joining(" "));
+ try {
+ Process proc = regRemove.start();
+ boolean finishedInTime = waitForProcess(proc, 5, TimeUnit.SECONDS);
+ if (finishedInTime) {
+ LOG.debug("Removed {} from registry key {}.", AUTOSTART_VALUE, HKCU_AUTOSTART_KEY);
+ } else {
+ throw new TogglingAutoStartFailedException("Removing registry value failed.");
+ }
+ } catch (IOException e) {
+ throw new TogglingAutoStartFailedException("Removing registry value failed. " + command, e);
+ }
+ }
+
+ private static boolean waitForProcess(Process proc, int timeout, TimeUnit timeUnit) {
+ boolean finishedInTime = false;
+ try {
+ finishedInTime = proc.waitFor(timeout, timeUnit);
+ } catch (InterruptedException e) {
+ LOG.error("Timeout while reading registry", e);
+ Thread.currentThread().interrupt();
+ } finally {
+ if (!finishedInTime) {
+ proc.destroyForcibly();
+ }
+ }
+ return finishedInTime;
+ }
+
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/DonationKeyPreferencesController.java b/main/ui/src/main/java/org/cryptomator/ui/preferences/DonationKeyPreferencesController.java
new file mode 100644
index 000000000..476887b26
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/preferences/DonationKeyPreferencesController.java
@@ -0,0 +1,45 @@
+package org.cryptomator.ui.preferences;
+
+import javafx.application.Application;
+import javafx.beans.value.ObservableValue;
+import javafx.fxml.FXML;
+import javafx.scene.control.TextArea;
+import org.cryptomator.common.LicenseHolder;
+import org.cryptomator.ui.common.FxController;
+
+import javax.inject.Inject;
+
+@PreferencesScoped
+public class DonationKeyPreferencesController implements FxController {
+
+ private static final String DONATION_URI = "https://store.cryptomator.org/desktop";
+
+ private final Application application;
+ private final LicenseHolder licenseHolder;
+ public TextArea donationKeyField;
+
+ @Inject
+ DonationKeyPreferencesController(Application application, LicenseHolder licenseHolder) {
+ this.application = application;
+ this.licenseHolder = licenseHolder;
+ }
+
+ @FXML
+ public void initialize() {
+ donationKeyField.setText(licenseHolder.getLicenseKey().orElse(null));
+ donationKeyField.textProperty().addListener(this::registrationKeyChanged);
+ }
+
+ private void registrationKeyChanged(@SuppressWarnings("unused") ObservableValue extends String> observable, @SuppressWarnings("unused") String oldValue, String newValue) {
+ licenseHolder.validateAndStoreLicense(newValue);
+ }
+
+ @FXML
+ public void getDonationKey() {
+ application.getHostServices().showDocument(DONATION_URI);
+ }
+
+ public LicenseHolder getLicenseHolder() {
+ return licenseHolder;
+ }
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java b/main/ui/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java
index bbccb0ad7..ae18dbd3f 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java
@@ -1,6 +1,9 @@
package org.cryptomator.ui.preferences;
+import javafx.application.Platform;
import javafx.beans.value.ObservableValue;
+import javafx.concurrent.Task;
+import javafx.fxml.FXML;
import javafx.geometry.NodeOrientation;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox;
@@ -8,6 +11,7 @@ import javafx.scene.control.RadioButton;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.util.StringConverter;
+import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.UiTheme;
import org.cryptomator.ui.common.FxController;
@@ -15,25 +19,38 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
+import javax.inject.Named;
+import java.util.Optional;
+import java.util.concurrent.ExecutorService;
@PreferencesScoped
public class GeneralPreferencesController implements FxController {
-
+
private static final Logger LOG = LoggerFactory.getLogger(GeneralPreferencesController.class);
private final Settings settings;
+ private final boolean trayMenuSupported;
+ private final Optional autoStartStrategy;
+ private final LicenseHolder licenseHolder;
+ private final ExecutorService executor;
public ChoiceBox themeChoiceBox;
public CheckBox startHiddenCheckbox;
public CheckBox debugModeCheckbox;
+ public CheckBox autoStartCheckbox;
public ToggleGroup nodeOrientation;
public RadioButton nodeOrientationLtr;
public RadioButton nodeOrientationRtl;
@Inject
- GeneralPreferencesController(Settings settings) {
+ GeneralPreferencesController(Settings settings, @Named("trayMenuSupported") boolean trayMenuSupported, Optional autoStartStrategy, LicenseHolder licenseHolder, ExecutorService executor) {
this.settings = settings;
+ this.trayMenuSupported = trayMenuSupported;
+ this.autoStartStrategy = autoStartStrategy;
+ this.licenseHolder = licenseHolder;
+ this.executor = executor;
}
+ @FXML
public void initialize() {
themeChoiceBox.getItems().addAll(UiTheme.values());
themeChoiceBox.valueProperty().bindBidirectional(settings.theme());
@@ -43,9 +60,23 @@ public class GeneralPreferencesController implements FxController {
debugModeCheckbox.selectedProperty().bindBidirectional(settings.debugMode());
- nodeOrientation.selectedToggleProperty().addListener(this::toggleNodeOrientation);
+ autoStartStrategy.ifPresent(autoStart -> {
+ autoStart.isAutoStartEnabled().thenAccept(enabled -> {
+ Platform.runLater(() -> autoStartCheckbox.setSelected(enabled));
+ });
+ });
+
nodeOrientationLtr.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.LEFT_TO_RIGHT);
nodeOrientationRtl.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.RIGHT_TO_LEFT);
+ nodeOrientation.selectedToggleProperty().addListener(this::toggleNodeOrientation);
+ }
+
+ public boolean isTrayMenuSupported() {
+ return this.trayMenuSupported;
+ }
+
+ public boolean isAutoStartSupported() {
+ return autoStartStrategy.isPresent();
}
private void toggleNodeOrientation(@SuppressWarnings("unused") ObservableValue extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
@@ -58,6 +89,20 @@ public class GeneralPreferencesController implements FxController {
}
}
+ @FXML
+ public void toggleAutoStart() {
+ autoStartStrategy.ifPresent(autoStart -> {
+ boolean enableAutoStart = autoStartCheckbox.isSelected();
+ Task toggleTask = new ToggleAutoStartTask(autoStart, enableAutoStart);
+ toggleTask.setOnFailed(evt -> autoStartCheckbox.setSelected(!enableAutoStart)); // restore previous state
+ executor.execute(toggleTask);
+ });
+ }
+
+ public LicenseHolder getLicenseHolder() {
+ return licenseHolder;
+ }
+
/* Helper classes */
private static class UiThemeConverter extends StringConverter {
@@ -73,4 +118,25 @@ public class GeneralPreferencesController implements FxController {
}
}
+ private static class ToggleAutoStartTask extends Task {
+
+ private final AutoStartStrategy autoStart;
+ private final boolean enable;
+
+ public ToggleAutoStartTask(AutoStartStrategy autoStart, boolean enable) {
+ this.autoStart = autoStart;
+ this.enable = enable;
+ }
+
+ @Override
+ protected Void call() throws Exception {
+ if (enable) {
+ autoStart.enableAutoStart();
+ } else {
+ autoStart.disableAutoStart();
+ }
+ return null;
+ }
+ }
+
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesComponent.java b/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesComponent.java
index 49408d639..320b8c63f 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesComponent.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesComponent.java
@@ -7,9 +7,9 @@ package org.cryptomator.ui.preferences;
import dagger.Lazy;
import dagger.Subcomponent;
+import javafx.beans.property.ObjectProperty;
import javafx.scene.Scene;
import javafx.stage.Stage;
-import org.cryptomator.ui.common.FXMLLoaderFactory;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
@@ -23,7 +23,10 @@ public interface PreferencesComponent {
@FxmlScene(FxmlFile.PREFERENCES)
Lazy scene();
- default Stage showPreferencesWindow() {
+ ObjectProperty selectedTabProperty();
+
+ default Stage showPreferencesWindow(SelectedPreferencesTab selectedTab) {
+ selectedTabProperty().set(selectedTab);
Stage stage = window();
stage.setScene(scene().get());
stage.show();
diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesController.java b/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesController.java
index e63505e4b..718f9d443 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesController.java
@@ -1,6 +1,9 @@
package org.cryptomator.ui.preferences;
+import javafx.beans.Observable;
import javafx.beans.binding.BooleanBinding;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
@@ -15,26 +18,50 @@ import javax.inject.Inject;
public class PreferencesController implements FxController {
private final Stage window;
+ private final ObjectProperty selectedTabProperty;
private final BooleanBinding updateAvailable;
public TabPane tabPane;
public Tab generalTab;
+ public Tab volumeTab;
public Tab updatesTab;
+ public Tab donationKeyTab;
@Inject
- public PreferencesController(@PreferencesWindow Stage window, UpdateChecker updateChecker) {
+ public PreferencesController(@PreferencesWindow Stage window, ObjectProperty selectedTabProperty, UpdateChecker updateChecker) {
this.window = window;
+ this.selectedTabProperty = selectedTabProperty;
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
}
@FXML
public void initialize() {
window.setOnShowing(this::windowWillAppear);
+ selectedTabProperty.addListener(observable -> this.selectChosenTab());
}
- private void windowWillAppear(@SuppressWarnings("unused") WindowEvent windowEvent) {
- if (updateAvailable.get()) {
- tabPane.getSelectionModel().select(updatesTab);
+ private void selectChosenTab() {
+ Tab toBeSelected = getTabToSelect(selectedTabProperty.get());
+ tabPane.getSelectionModel().select(toBeSelected);
+ }
+
+ private Tab getTabToSelect(SelectedPreferencesTab selectedTab) {
+ switch (selectedTab) {
+ case UPDATES:
+ return updatesTab;
+ case VOLUME:
+ return volumeTab;
+ case DONATION_KEY:
+ return donationKeyTab;
+ case GENERAL:
+ return generalTab;
+ case ANY:
+ default:
+ return updateAvailable.get() ? updatesTab : generalTab;
}
}
+ private void windowWillAppear(@SuppressWarnings("unused") WindowEvent windowEvent) {
+ selectChosenTab();
+ }
+
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java b/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java
index ae4233cb8..8b76364ef 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java
@@ -4,6 +4,8 @@ import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;
@@ -20,9 +22,15 @@ import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
-@Module
+@Module(includes = {AutoStartModule.class})
abstract class PreferencesModule {
+ @Provides
+ @PreferencesScoped
+ static ObjectProperty provideSelectedTabProperty() {
+ return new SimpleObjectProperty<>(SelectedPreferencesTab.ANY);
+ }
+
@Provides
@PreferencesWindow
@PreferencesScoped
@@ -70,4 +78,9 @@ abstract class PreferencesModule {
@FxControllerKey(VolumePreferencesController.class)
abstract FxController bindVolumePreferencesController(VolumePreferencesController controller);
+ @Binds
+ @IntoMap
+ @FxControllerKey(DonationKeyPreferencesController.class)
+ abstract FxController bindDonationKeyPreferencesController(DonationKeyPreferencesController controller);
+
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/SelectedPreferencesTab.java b/main/ui/src/main/java/org/cryptomator/ui/preferences/SelectedPreferencesTab.java
new file mode 100644
index 000000000..74f305c6e
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/preferences/SelectedPreferencesTab.java
@@ -0,0 +1,28 @@
+package org.cryptomator.ui.preferences;
+
+public enum SelectedPreferencesTab {
+ /**
+ * Let the controller decide which tab to show.
+ */
+ ANY,
+
+ /**
+ * Show general tab
+ */
+ GENERAL,
+
+ /**
+ * Show volume tab
+ */
+ VOLUME,
+
+ /**
+ * Show updates tab
+ */
+ UPDATES,
+
+ /**
+ * Show donation key tab
+ */
+ DONATION_KEY,
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/quit/QuitController.java b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitController.java
index 897bce32c..66dd13ade 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/quit/QuitController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitController.java
@@ -1,26 +1,22 @@
package org.cryptomator.ui.quit;
-import javafx.application.Platform;
import javafx.collections.ObservableList;
-import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.stage.Stage;
import org.cryptomator.common.vaults.Vault;
-import org.cryptomator.common.vaults.VaultState;
-import org.cryptomator.common.vaults.Volume;
import org.cryptomator.ui.common.FxController;
+import org.cryptomator.ui.common.VaultService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.awt.desktop.QuitResponse;
-import java.util.Iterator;
-import java.util.List;
-import java.util.concurrent.Executor;
+import java.util.Collection;
import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
@QuitScoped
public class QuitController implements FxController {
@@ -30,15 +26,17 @@ public class QuitController implements FxController {
private final Stage window;
private final QuitResponse response;
private final ObservableList unlockedVaults;
- private final ExecutorService executor;
+ private final ExecutorService executorService;
+ private final VaultService vaultService;
public Button lockAndQuitButton;
@Inject
- QuitController(@QuitWindow Stage window, QuitResponse response, ObservableList vaults, ExecutorService executor) {
+ QuitController(@QuitWindow Stage window, QuitResponse response, ObservableList vaults, ExecutorService executorService, VaultService vaultService) {
this.window = window;
this.response = response;
this.unlockedVaults = vaults.filtered(Vault::isUnlocked);
- this.executor = executor;
+ this.executorService = executorService;
+ this.vaultService = vaultService;
}
@FXML
@@ -52,84 +50,24 @@ public class QuitController implements FxController {
public void lockAndQuit() {
lockAndQuitButton.setDisable(true);
lockAndQuitButton.setContentDisplay(ContentDisplay.LEFT);
-
- Iterator toBeLocked = List.copyOf(unlockedVaults).iterator();
- ScheduledService lockAllService = new LockAllVaultsService(executor, toBeLocked);
- lockAllService.setOnSucceeded(evt -> {
- if (!toBeLocked.hasNext()) {
+
+ Task> lockAllTask = vaultService.createLockAllTask(unlockedVaults, false);
+ lockAllTask.setOnSucceeded(evt -> {
+ LOG.info("Locked {}", lockAllTask.getValue().stream().map(Vault::getDisplayableName).collect(Collectors.joining(", ")));
+ if (unlockedVaults.isEmpty()) {
window.close();
response.performQuit();
}
});
- lockAllService.setOnFailed(evt -> {
+ lockAllTask.setOnFailed(evt -> {
+ LOG.warn("Locking failed", lockAllTask.getException());
lockAndQuitButton.setDisable(false);
lockAndQuitButton.setContentDisplay(ContentDisplay.TEXT_ONLY);
// TODO: show force lock or force quit scene (and DO NOT cancelQuit() here!)
// see https://github.com/cryptomator/cryptomator/blob/1.4.16/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java#L151-L163
response.cancelQuit();
});
- lockAllService.start();
+ executorService.execute(lockAllTask);
}
- /**
- * @param vault The vault to lock
- * @return Task that tries to lock the given vault gracefully.
- */
- private Task createGracefulLockTask(Vault vault) {
- Task task = new Task() {
- @Override
- protected Void call() throws Volume.VolumeException {
- vault.lock(false);
- LOG.info("Locked {}", vault.getDisplayableName());
- return null;
- }
- };
- task.setOnScheduled(evt -> {
- vault.setState(VaultState.PROCESSING);
- });
- task.setOnSucceeded(evt -> {
- vault.setState(VaultState.LOCKED);
- });
- task.setOnFailed(evt -> {
- LOG.warn("Failed to lock vault", vault);
- });
- return task;
- }
-
- /**
- * @return Task that succeeds immediately
- */
- private Task createNoopTask() {
- return new Task<>() {
- @Override
- protected Void call() {
- return null;
- }
- };
- }
-
- private class LockAllVaultsService extends ScheduledService {
-
- private final Iterator vaultsToLock;
-
- public LockAllVaultsService(Executor executor, Iterator vaultsToLock) {
- this.vaultsToLock = vaultsToLock;
- setExecutor(executor);
- setRestartOnFailure(false);
- }
-
- @Override
- protected Task createTask() {
- assert Platform.isFxApplicationThread();
- if (vaultsToLock.hasNext()) {
- return createGracefulLockTask(vaultsToLock.next());
- } else {
- // This should be unreachable code, since vaultsToLock is only accessed on the FX App Thread.
- // But if quitting the application takes longer for any reason, this service should shut down properly
- reset();
- return createNoopTask();
- }
- }
- }
-
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java b/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java
index 2ca6ebb69..945256f9e 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java
@@ -40,7 +40,7 @@ public class RecoveryKeyCreationController implements FxController {
public NiceSecurePasswordField passwordField;
@Inject
- public RecoveryKeyCreationController(@RecoveryKeyWindow Stage window, @FxmlScene(FxmlFile.RECOVERYKEY_DISPLAY) Lazy successScene, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey) {
+ public RecoveryKeyCreationController(@RecoveryKeyWindow Stage window, @FxmlScene(FxmlFile.RECOVERYKEY_SUCCESS) Lazy successScene, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey) {
this.window = window;
this.successScene = successScene;
this.vault = vault;
diff --git a/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyDisplayController.java b/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyDisplayController.java
index 2d96d786f..2b61ddf85 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyDisplayController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyDisplayController.java
@@ -1,26 +1,73 @@
package org.cryptomator.ui.recoverykey;
-import javafx.beans.property.ReadOnlyStringProperty;
-import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
+import javafx.print.PageLayout;
+import javafx.print.Printer;
+import javafx.print.PrinterJob;
+import javafx.scene.input.Clipboard;
+import javafx.scene.input.ClipboardContent;
+import javafx.scene.text.Font;
+import javafx.scene.text.FontSmoothingType;
+import javafx.scene.text.FontWeight;
+import javafx.scene.text.Text;
+import javafx.scene.text.TextFlow;
import javafx.stage.Stage;
-import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
-import javax.inject.Inject;
-
-@RecoveryKeyScoped
public class RecoveryKeyDisplayController implements FxController {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyDisplayController.class);
private final Stage window;
- private final Vault vault;
- private final StringProperty recoveryKeyProperty;
-
- @Inject
- public RecoveryKeyDisplayController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow StringProperty recoveryKey) {
+ private final String vaultName;
+ private final String recoveryKey;
+
+ public RecoveryKeyDisplayController(Stage window, String vaultName, String recoveryKey) {
this.window = window;
- this.vault = vault;
- this.recoveryKeyProperty = recoveryKey;
+ this.vaultName = vaultName;
+ this.recoveryKey = recoveryKey;
+ }
+
+ @FXML
+ public void printRecoveryKey() {
+ // TODO localize
+
+ PrinterJob job = PrinterJob.createPrinterJob();
+ if (job != null && job.showPrintDialog(window)) {
+ PageLayout pageLayout = job.getJobSettings().getPageLayout();
+
+ Text heading = new Text("Cryptomator Recovery Key\n" + vaultName + "\n");
+ heading.setFont(Font.font("serif", FontWeight.BOLD, 20));
+ heading.setFontSmoothingType(FontSmoothingType.LCD);
+
+ Text key = new Text(recoveryKey);
+ key.setFont(Font.font("serif", FontWeight.NORMAL, 16));
+ key.setFontSmoothingType(FontSmoothingType.GRAY);
+
+ TextFlow textFlow = new TextFlow();
+ textFlow.setPrefSize(pageLayout.getPrintableWidth(), pageLayout.getPrintableHeight());
+ textFlow.getChildren().addAll(heading, key);
+ textFlow.setLineSpacing(6);
+
+ if (job.printPage(textFlow)) {
+ LOG.info("Recovery key printed.");
+ job.endJob();
+ } else {
+ LOG.warn("Printing recovery key failed.");
+ }
+ } else {
+ LOG.info("Printing recovery key canceled by user.");
+ }
+ }
+
+ @FXML
+ public void copyRecoveryKey() {
+ ClipboardContent clipboardContent = new ClipboardContent();
+ clipboardContent.putString(recoveryKey);
+ Clipboard.getSystemClipboard().setContent(clipboardContent);
+ LOG.info("Recovery key copied to clipboard.");
}
@FXML
@@ -30,15 +77,15 @@ public class RecoveryKeyDisplayController implements FxController {
/* Getter/Setter */
- public Vault getVault() {
- return vault;
- }
-
- public ReadOnlyStringProperty recoveryKeyProperty() {
- return recoveryKeyProperty;
+ public boolean isPrinterSupported() {
+ return Printer.getDefaultPrinter() != null;
}
public String getRecoveryKey() {
- return recoveryKeyProperty.get();
+ return recoveryKey;
+ }
+
+ public String getVaultName() {
+ return vaultName;
}
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java b/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java
index 261b50682..80f89c749 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java
@@ -10,6 +10,7 @@ import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Modality;
import javafx.stage.Stage;
+import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FXMLLoaderFactory;
import org.cryptomator.ui.common.FxController;
@@ -63,10 +64,10 @@ abstract class RecoveryKeyModule {
}
@Provides
- @FxmlScene(FxmlFile.RECOVERYKEY_DISPLAY)
+ @FxmlScene(FxmlFile.RECOVERYKEY_SUCCESS)
@RecoveryKeyScoped
- static Scene provideRecoveryKeyDisplayScene(@RecoveryKeyWindow FXMLLoaderFactory fxmlLoaders) {
- return fxmlLoaders.createScene("/fxml/recoverykey_display.fxml");
+ static Scene provideRecoveryKeySuccessScene(@RecoveryKeyWindow FXMLLoaderFactory fxmlLoaders) {
+ return fxmlLoaders.createScene("/fxml/recoverykey_success.fxml");
}
// ------------------
@@ -76,9 +77,16 @@ abstract class RecoveryKeyModule {
@FxControllerKey(RecoveryKeyCreationController.class)
abstract FxController bindRecoveryKeyCreationController(RecoveryKeyCreationController controller);
- @Binds
+ @Provides
@IntoMap
@FxControllerKey(RecoveryKeyDisplayController.class)
- abstract FxController bindRecoveryKeyDisplayController(RecoveryKeyDisplayController controller);
+ static FxController provideRecoveryKeyDisplayController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow StringProperty recoveryKey) {
+ return new RecoveryKeyDisplayController(window, vault.getDisplayableName(), recoveryKey.get());
+ }
+
+ @Binds
+ @IntoMap
+ @FxControllerKey(RecoveryKeySuccessController.class)
+ abstract FxController bindRecoveryKeySuccessController(RecoveryKeySuccessController controller);
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeySuccessController.java b/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeySuccessController.java
new file mode 100644
index 000000000..ae9e2e70f
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeySuccessController.java
@@ -0,0 +1,24 @@
+package org.cryptomator.ui.recoverykey;
+
+import javafx.fxml.FXML;
+import javafx.stage.Stage;
+import org.cryptomator.ui.common.FxController;
+
+import javax.inject.Inject;
+
+@RecoveryKeyScoped
+public class RecoveryKeySuccessController implements FxController {
+
+ private final Stage window;
+
+ @Inject
+ public RecoveryKeySuccessController(@RecoveryKeyWindow Stage window) {
+ this.window = window;
+ }
+
+ @FXML
+ public void close() {
+ window.close();
+ }
+
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java b/main/ui/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java
index c95a02905..bef3c441c 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java
@@ -3,11 +3,14 @@ package org.cryptomator.ui.traymenu;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.collections.ObservableList;
+import org.cryptomator.common.ShutdownHook;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
+import org.cryptomator.common.vaults.Volume;
import org.cryptomator.ui.fxapp.FxApplication;
import org.cryptomator.ui.launcher.FxApplicationStarter;
+import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -37,17 +40,17 @@ class TrayMenuController {
private final ResourceBundle resourceBundle;
private final FxApplicationStarter fxApplicationStarter;
private final CountDownLatch shutdownLatch;
- private final Settings settings;
+ private final ShutdownHook shutdownHook;
private final ObservableList vaults;
private final PopupMenu menu;
private final AtomicBoolean allowSuddenTermination;
@Inject
- TrayMenuController(ResourceBundle resourceBundle, FxApplicationStarter fxApplicationStarter, @Named("shutdownLatch") CountDownLatch shutdownLatch, Settings settings, ObservableList vaults) {
+ TrayMenuController(ResourceBundle resourceBundle, FxApplicationStarter fxApplicationStarter, @Named("shutdownLatch") CountDownLatch shutdownLatch, ShutdownHook shutdownHook, ObservableList vaults) {
this.resourceBundle = resourceBundle;
this.fxApplicationStarter = fxApplicationStarter;
this.shutdownLatch = shutdownLatch;
- this.settings = settings;
+ this.shutdownHook = shutdownHook;
this.vaults = vaults;
this.menu = new PopupMenu();
this.allowSuddenTermination = new AtomicBoolean(true);
@@ -71,6 +74,7 @@ class TrayMenuController {
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_QUIT_HANDLER)) {
Desktop.getDesktop().setQuitHandler(this::handleQuitRequest);
}
+ shutdownHook.runOnShutdown(this::forceUnmountRemainingVaults);
// allow sudden termination
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_SUDDEN_TERMINATION)) {
@@ -112,6 +116,11 @@ class TrayMenuController {
}
menu.addSeparator();
+ MenuItem lockAllItem = new MenuItem(resourceBundle.getString("traymenu.lockAllVaults"));
+ lockAllItem.addActionListener(this::lockAllVaults);
+ lockAllItem.setEnabled(!vaults.filtered(Vault::isUnlocked).isEmpty());
+ menu.add(lockAllItem);
+
MenuItem quitApplicationItem = new MenuItem(resourceBundle.getString("traymenu.quitApplication"));
quitApplicationItem.addActionListener(this::quitApplication);
menu.add(quitApplicationItem);
@@ -126,11 +135,11 @@ class TrayMenuController {
submenu.add(unlockItem);
} else if (vault.isUnlocked()) {
MenuItem lockItem = new MenuItem(resourceBundle.getString("traymenu.vault.lock"));
- lockItem.setEnabled(false); // TODO add action listener
+ lockItem.addActionListener(createActionListenerForVault(vault, this::lockVault));
submenu.add(lockItem);
MenuItem revealItem = new MenuItem(resourceBundle.getString("traymenu.vault.reveal"));
- revealItem.setEnabled(false); // TODO add action listener
+ revealItem.addActionListener(createActionListenerForVault(vault, this::revealVault));
submenu.add(revealItem);
}
@@ -145,12 +154,24 @@ class TrayMenuController {
fxApplicationStarter.get(true).thenAccept(app -> app.showUnlockWindow(vault));
}
+ private void lockVault(Vault vault) {
+ fxApplicationStarter.get(true).thenAccept(app -> app.getVaultService().lock(vault, false));
+ }
+
+ private void lockAllVaults(ActionEvent actionEvent) {
+ fxApplicationStarter.get(true).thenAccept(app -> app.getVaultService().lockAll(vaults.filtered(Vault::isUnlocked), false));
+ }
+
+ private void revealVault(Vault vault) {
+ fxApplicationStarter.get(true).thenAccept(app -> app.getVaultService().reveal(vault));
+ }
+
void showMainWindow(@SuppressWarnings("unused") ActionEvent actionEvent) {
fxApplicationStarter.get(true).thenAccept(app -> app.showMainWindow());
}
private void showPreferencesWindow(@SuppressWarnings("unused") EventObject actionEvent) {
- fxApplicationStarter.get(true).thenAccept(FxApplication::showPreferencesWindow);
+ fxApplicationStarter.get(true).thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ANY));
}
private void handleQuitRequest(EventObject e, QuitResponse response) {
@@ -174,4 +195,16 @@ class TrayMenuController {
}
});
}
+
+ private void forceUnmountRemainingVaults() {
+ for (Vault vault : vaults) {
+ if (vault.isUnlocked()) {
+ try {
+ vault.lock(true);
+ } catch (Volume.VolumeException e) {
+ LOG.error("Failed to unmount vault " + vault.getPath(), e);
+ }
+ }
+ }
+ }
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockController.java b/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockController.java
index 7c1221aaf..837dc827f 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockController.java
@@ -7,6 +7,7 @@ import javafx.animation.Timeline;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.WritableValue;
@@ -32,6 +33,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
+import javax.inject.Named;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.NotDirectoryException;
import java.util.Arrays;
@@ -49,19 +51,25 @@ public class UnlockController implements FxController {
private final ObjectBinding unlockButtonState;
private final Optional keychainAccess;
private final Lazy successScene;
+ private final Lazy invalidMountPointScene;
+ private final Lazy genericErrorScene;
+ private final ObjectProperty genericErrorCause;
private final ForgetPasswordComponent.Builder forgetPassword;
private final BooleanProperty unlockButtonDisabled;
public NiceSecurePasswordField passwordField;
public CheckBox savePassword;
@Inject
- public UnlockController(@UnlockWindow Stage window, @UnlockWindow Vault vault, ExecutorService executor, Optional keychainAccess, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy successScene, ForgetPasswordComponent.Builder forgetPassword) {
+ public UnlockController(@UnlockWindow Stage window, @UnlockWindow Vault vault, ExecutorService executor, Optional keychainAccess, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy invalidMountPointScene, @FxmlScene(FxmlFile.UNLOCK_GENERIC_ERROR) Lazy genericErrorScene, @Named("genericErrorCause") ObjectProperty genericErrorCause, ForgetPasswordComponent.Builder forgetPassword) {
this.window = window;
this.vault = vault;
this.executor = executor;
this.unlockButtonState = Bindings.createObjectBinding(this::getUnlockButtonState, vault.stateProperty());
this.keychainAccess = keychainAccess;
this.successScene = successScene;
+ this.invalidMountPointScene = invalidMountPointScene;
+ this.genericErrorScene = genericErrorScene;
+ this.genericErrorCause = genericErrorCause;
this.forgetPassword = forgetPassword;
this.unlockButtonDisabled = new SimpleBooleanProperty();
}
@@ -100,17 +108,16 @@ public class UnlockController implements FxController {
shakeWindow();
passwordField.selectAll();
passwordField.requestFocus();
- }).onError(UnsupportedVaultFormatException.class, e -> {
- // TODO
}).onError(NotDirectoryException.class, e -> {
LOG.error("Unlock failed. Mount point not a directory: {}", e.getMessage());
- // TODO
+ window.setScene(invalidMountPointScene.get());
}).onError(DirectoryNotEmptyException.class, e -> {
LOG.error("Unlock failed. Mount point not empty: {}", e.getMessage());
- // TODO
+ window.setScene(invalidMountPointScene.get());
}).onError(Exception.class, e -> { // including RuntimeExceptions
LOG.error("Unlock failed for technical reasons.", e);
- // TODO
+ genericErrorCause.set(e);
+ window.setScene(genericErrorScene.get());
}).andFinally(() -> {
if (!vault.isUnlocked()) {
vault.setState(VaultState.LOCKED);
diff --git a/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockGenericErrorController.java b/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockGenericErrorController.java
new file mode 100644
index 000000000..faa357969
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockGenericErrorController.java
@@ -0,0 +1,29 @@
+package org.cryptomator.ui.unlock;
+
+import dagger.Lazy;
+import javafx.fxml.FXML;
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+import org.cryptomator.ui.common.FxController;
+import org.cryptomator.ui.common.FxmlFile;
+import org.cryptomator.ui.common.FxmlScene;
+
+import javax.inject.Inject;
+
+@UnlockScoped
+public class UnlockGenericErrorController implements FxController {
+
+ private final Stage window;
+ private final Lazy unlockScene;
+
+ @Inject
+ UnlockGenericErrorController(@UnlockWindow Stage window, @FxmlScene(FxmlFile.UNLOCK) Lazy unlockScene) {
+ this.window = window;
+ this.unlockScene = unlockScene;
+ }
+
+ @FXML
+ public void back() {
+ window.setScene(unlockScene.get());
+ }
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockInvalidMountPointController.java b/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockInvalidMountPointController.java
new file mode 100644
index 000000000..d73c96c07
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockInvalidMountPointController.java
@@ -0,0 +1,39 @@
+package org.cryptomator.ui.unlock;
+
+import dagger.Lazy;
+import javafx.fxml.FXML;
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+import org.cryptomator.common.vaults.Vault;
+import org.cryptomator.ui.common.FxController;
+import org.cryptomator.ui.common.FxmlFile;
+import org.cryptomator.ui.common.FxmlScene;
+
+import javax.inject.Inject;
+
+@UnlockScoped
+public class UnlockInvalidMountPointController implements FxController {
+
+ private final Stage window;
+ private final Lazy unlockScene;
+ private final Vault vault;
+
+ @Inject
+ UnlockInvalidMountPointController(@UnlockWindow Stage window, @FxmlScene(FxmlFile.UNLOCK) Lazy unlockScene, @UnlockWindow Vault vault) {
+ this.window = window;
+ this.unlockScene = unlockScene;
+ this.vault = vault;
+ }
+
+ @FXML
+ public void back() {
+ window.setScene(unlockScene.get());
+ }
+
+ /* Getter/Setter */
+
+ public String getMountPoint() {
+ return vault.getVaultSettings().getIndividualMountPath().orElse("AUTO");
+ }
+
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java b/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java
index 488f7cfa8..69488010f 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java
@@ -4,6 +4,8 @@ import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Modality;
@@ -14,6 +16,7 @@ import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
+import org.cryptomator.ui.common.StackTraceController;
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
import javax.inject.Named;
@@ -44,6 +47,13 @@ abstract class UnlockModule {
return stage;
}
+ @Provides
+ @Named("genericErrorCause")
+ @UnlockScoped
+ static ObjectProperty provideGenericErrorCause() {
+ return new SimpleObjectProperty<>();
+ }
+
@Provides
@FxmlScene(FxmlFile.UNLOCK)
@UnlockScoped
@@ -58,6 +68,21 @@ abstract class UnlockModule {
return fxmlLoaders.createScene("/fxml/unlock_success.fxml");
}
+ @Provides
+ @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT)
+ @UnlockScoped
+ static Scene provideInvalidMountPointScene(@UnlockWindow FXMLLoaderFactory fxmlLoaders) {
+ return fxmlLoaders.createScene("/fxml/unlock_invalid_mount_point.fxml");
+ }
+
+
+ @Provides
+ @FxmlScene(FxmlFile.UNLOCK_GENERIC_ERROR)
+ @UnlockScoped
+ static Scene provideGenericErrorScene(@UnlockWindow FXMLLoaderFactory fxmlLoaders) {
+ return fxmlLoaders.createScene("/fxml/unlock_generic_error.fxml");
+ }
+
// ------------------
@@ -71,5 +96,22 @@ abstract class UnlockModule {
@FxControllerKey(UnlockSuccessController.class)
abstract FxController bindUnlockSuccessController(UnlockSuccessController controller);
+ @Binds
+ @IntoMap
+ @FxControllerKey(UnlockInvalidMountPointController.class)
+ abstract FxController bindUnlockInvalidMountPointController(UnlockInvalidMountPointController controller);
+
+ @Binds
+ @IntoMap
+ @FxControllerKey(UnlockGenericErrorController.class)
+ abstract FxController bindUnlockGenericErrorController(UnlockGenericErrorController controller);
+
+ @Provides
+ @IntoMap
+ @FxControllerKey(StackTraceController.class)
+ static FxController provideStackTraceController(@Named("genericErrorCause") ObjectProperty errorCause) {
+ return new StackTraceController(errorCause.get());
+ }
+
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockSuccessController.java b/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockSuccessController.java
index 44e1c0010..cf61ab2e7 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockSuccessController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockSuccessController.java
@@ -5,13 +5,13 @@ import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
+import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.control.ContentDisplay;
import javafx.stage.Stage;
import org.cryptomator.common.vaults.Vault;
-import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.ui.common.FxController;
-import org.cryptomator.ui.common.Tasks;
+import org.cryptomator.ui.common.VaultService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -26,14 +26,16 @@ public class UnlockSuccessController implements FxController {
private final Stage window;
private final Vault vault;
private final ExecutorService executor;
+ private final VaultService vaultService;
private final ObjectProperty revealButtonState;
private final BooleanProperty revealButtonDisabled;
@Inject
- public UnlockSuccessController(@UnlockWindow Stage window, @UnlockWindow Vault vault, ExecutorService executor) {
+ public UnlockSuccessController(@UnlockWindow Stage window, @UnlockWindow Vault vault, ExecutorService executor, VaultService vaultService) {
this.window = window;
this.vault = vault;
this.executor = executor;
+ this.vaultService = vaultService;
this.revealButtonState = new SimpleObjectProperty<>(ContentDisplay.TEXT_ONLY);
this.revealButtonDisabled = new SimpleBooleanProperty();
}
@@ -49,17 +51,19 @@ public class UnlockSuccessController implements FxController {
LOG.trace("UnlockSuccessController.revealAndClose()");
revealButtonState.set(ContentDisplay.LEFT);
revealButtonDisabled.set(true);
- Tasks.create(() -> {
- vault.reveal();
- }).onSuccess(() -> {
- window.close();
- }).onError(InvalidPassphraseException.class, e -> {
- // TODO
- LOG.warn("Reveal failed.", e);
- }).andFinally(() -> {
+
+ Task revealTask = vaultService.createRevealTask(vault);
+ revealTask.setOnSucceeded(evt -> {
revealButtonState.set(ContentDisplay.TEXT_ONLY);
revealButtonDisabled.set(false);
- }).runOnce(executor);
+ window.close();
+ });
+ revealTask.setOnFailed(evt -> {
+ LOG.warn("Reveal failed.", revealTask.getException());
+ revealButtonState.set(ContentDisplay.TEXT_ONLY);
+ revealButtonDisabled.set(false);
+ });
+ executor.execute(revealTask);
}
/* Getter/Setter */
diff --git a/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java b/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java
index 6efa3e407..9ed5ba2ea 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java
@@ -84,12 +84,7 @@ public class MountOptionsController implements FxController {
driveLetterSelection.getItems().addAll(windowsDriveLetters.getAllDriveLetters());
driveLetterSelection.setConverter(new WinDriveLetterLabelConverter(windowsDriveLetters));
driveLetterSelection.setValue(vault.getVaultSettings().winDriveLetter().get());
- vault.getVaultSettings().usesIndividualMountPath().bind(mountPoint.selectedToggleProperty().isEqualTo(mountPointCustomDir));
- vault.getVaultSettings().winDriveLetter().bind( //
- Bindings.when(mountPoint.selectedToggleProperty().isEqualTo(mountPointWinDriveLetter)) //
- .then(driveLetterSelection.getSelectionModel().selectedItemProperty()) //
- .otherwise((String) null) //
- );
+
if (vault.getVaultSettings().usesIndividualMountPath().get()) {
mountPoint.selectToggle(mountPointCustomDir);
} else if (!Strings.isNullOrEmpty(vault.getVaultSettings().winDriveLetter().get())) {
@@ -97,6 +92,13 @@ public class MountOptionsController implements FxController {
} else {
mountPoint.selectToggle(mountPointAuto);
}
+
+ vault.getVaultSettings().usesIndividualMountPath().bind(mountPoint.selectedToggleProperty().isEqualTo(mountPointCustomDir));
+ vault.getVaultSettings().winDriveLetter().bind( //
+ Bindings.when(mountPoint.selectedToggleProperty().isEqualTo(mountPointWinDriveLetter)) //
+ .then(driveLetterSelection.getSelectionModel().selectedItemProperty()) //
+ .otherwise((String) null) //
+ );
}
@FXML
@@ -116,7 +118,7 @@ public class MountOptionsController implements FxController {
@FXML
private void chooseCustomMountPoint() {
DirectoryChooser directoryChooser = new DirectoryChooser();
- directoryChooser.setTitle(resourceBundle.getString("vaultOptions.mount.winDirChooser"));
+ directoryChooser.setTitle(resourceBundle.getString("vaultOptions.mount.mountPoint.directoryPickerTitle"));
try {
directoryChooser.setInitialDirectory(Path.of(System.getProperty("user.home")).toFile());
} catch (Exception e) {
diff --git a/main/ui/src/main/resources/css/dark_theme.css b/main/ui/src/main/resources/css/dark_theme.css
index c277fc09f..7ad3a1da5 100644
--- a/main/ui/src/main/resources/css/dark_theme.css
+++ b/main/ui/src/main/resources/css/dark_theme.css
@@ -78,6 +78,7 @@
SCROLL_BAR_THUMB_NORMAL: GRAY_3;
SCROLL_BAR_THUMB_HOVER: GRAY_4;
INDICATOR_BG: RED_5;
+ DRAG_N_DROP_INDICATOR_BG: GRAY_3;
PROGRESS_INDICATOR_BEGIN: GRAY_7;
PROGRESS_INDICATOR_END: GRAY_5;
PROGRESS_BAR_BG: GRAY_2;
@@ -116,6 +117,10 @@
-fx-font-size: 0.8em;
}
+.label-extra-small {
+ -fx-font-size: 0.64em;
+}
+
.text-flow > * {
-fx-fill: TEXT_FILL;
}
@@ -179,20 +184,6 @@
-fx-fill: CONTROL_WHITE_BG_ARMED;
}
-.main-window .resizer {
- -fx-background-color: linear-gradient(to bottom right,
- transparent 50%,
- CONTROL_BORDER_NORMAL 51%,
- CONTROL_BORDER_NORMAL 60%,
- transparent 61%,
- transparent 70%,
- CONTROL_BORDER_NORMAL 71%,
- CONTROL_BORDER_NORMAL 80%,
- transparent 81%
- );
- -fx-cursor: nw_resize;
-}
-
.main-window .update-indicator {
-fx-background-color: PRIMARY_BG, white, INDICATOR_BG;
-fx-background-insets: 0, 1px, 2px;
@@ -201,6 +192,16 @@
-fx-translate-y: 1px;
}
+.main-window .drag-n-drop-indicator {
+ -fx-border-color: DRAG_N_DROP_INDICATOR_BG;
+ -fx-border-width: 3px;
+}
+
+.main-window .drag-n-drop-indicator .drag-n-drop-header {
+ -fx-background-color: DRAG_N_DROP_INDICATOR_BG;
+ -fx-padding: 3px;
+}
+
/*******************************************************************************
* *
* TabPane *
@@ -212,34 +213,36 @@
}
.tab-pane > .tab-header-area {
- -fx-padding: 6px 12px 0 12px;
- -fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG;
- -fx-background-insets: 0, 0 0 1px 0;
+ -fx-padding: 0 12px;
+ -fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
+ -fx-background-insets: 0, 0 0 3px 0;
}
-.tab-pane > .tab-header-area > .headers-region > .tab {
- -fx-background-color: CONTROL_BORDER_NORMAL, MAIN_BG;
- -fx-background-insets: 0 0 1px 0, 1px;
- -fx-background-radius: 4px 4px 0 0;
- -fx-padding: 0.2em 1em 0.2em 1em;
+.tab-pane .tab {
+ -fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
+ -fx-background-insets: 0, 0 0 3px 0;
+ -fx-padding: 6px 12px;
}
-.tab-pane > .tab-header-area > .headers-region > .tab:selected {
- -fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG;
- -fx-background-insets: 0, 1px 1px 0 1px;
+.tab-pane .tab:selected {
+ -fx-background-color: PRIMARY_BG, CONTROL_PRIMARY_LIGHT_BG_NORMAL;
}
-.tab-pane > .tab-header-area > .headers-region > .tab > .tab-container > .tab-label {
- -fx-text-fill: TEXT_FILL;
+.tab-pane .tab .tab-label {
+ -fx-text-fill: SECONDARY_BG;
-fx-alignment: CENTER;
}
-.tab-pane > .tab-header-area > .headers-region > .tab .glyph-icon {
+.tab-pane .tab .glyph-icon {
-fx-fill: SECONDARY_BG;
}
-.tab-pane > .tab-header-area > .headers-region > .tab:selected .glyph-icon {
- -fx-fill: TEXT_FILL;
+.tab-pane .tab:selected .glyph-icon {
+ -fx-fill: PRIMARY_BG;
+}
+
+.tab-pane .tab:selected .tab-label {
+ -fx-text-fill: TEXT_FILL_PRIMARY;
}
/*******************************************************************************
@@ -672,7 +675,6 @@
******************************************************************************/
.choice-box {
- -fx-text-fill: TEXT_FILL;
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
-fx-background-insets: 0, 1px;
-fx-background-radius: 4px;
@@ -683,6 +685,18 @@
-fx-background-color: CONTROL_BORDER_FOCUSED, CONTROL_BG_NORMAL;
}
+.choice-box:disabled {
+ -fx-background-color: CONTROL_BORDER_DISABLED, CONTROL_BG_DISABLED;
+}
+
+.choice-box > .label {
+ -fx-text-fill: TEXT_FILL;
+}
+
+.choice-box:disabled > .label {
+ -fx-text-fill: TEXT_FILL_SECONDARY;
+}
+
.choice-box > .open-button {
-fx-padding: 0 0 0 0.3em;
}
@@ -694,6 +708,10 @@
-fx-shape: "M 0 0 h 7 l -3.5 4 z";
}
+.choice-box:disabled > .open-button > .arrow {
+ -fx-background-color: transparent, TEXT_FILL_SECONDARY;
+}
+
.choice-box .context-menu {
-fx-translate-x: -1.4em;
}
diff --git a/main/ui/src/main/resources/css/light_theme.css b/main/ui/src/main/resources/css/light_theme.css
index a4ad762ed..a09d3a908 100644
--- a/main/ui/src/main/resources/css/light_theme.css
+++ b/main/ui/src/main/resources/css/light_theme.css
@@ -78,6 +78,7 @@
SCROLL_BAR_THUMB_NORMAL: GRAY_7;
SCROLL_BAR_THUMB_HOVER: GRAY_6;
INDICATOR_BG: RED_5;
+ DRAG_N_DROP_INDICATOR_BG: GRAY_5;
PROGRESS_INDICATOR_BEGIN: GRAY_2;
PROGRESS_INDICATOR_END: GRAY_4;
PROGRESS_BAR_BG: GRAY_8;
@@ -116,6 +117,10 @@
-fx-font-size: 0.8em;
}
+.label-extra-small {
+ -fx-font-size: 0.64em;
+}
+
.text-flow > * {
-fx-fill: TEXT_FILL;
}
@@ -179,20 +184,6 @@
-fx-fill: CONTROL_WHITE_BG_ARMED;
}
-.main-window .resizer {
- -fx-background-color: linear-gradient(to bottom right,
- transparent 50%,
- CONTROL_BORDER_NORMAL 51%,
- CONTROL_BORDER_NORMAL 60%,
- transparent 61%,
- transparent 70%,
- CONTROL_BORDER_NORMAL 71%,
- CONTROL_BORDER_NORMAL 80%,
- transparent 81%
- );
- -fx-cursor: nw_resize;
-}
-
.main-window .update-indicator {
-fx-background-color: PRIMARY_BG, white, INDICATOR_BG;
-fx-background-insets: 0, 1px, 2px;
@@ -201,6 +192,16 @@
-fx-translate-y: 1px;
}
+.main-window .drag-n-drop-indicator {
+ -fx-border-color: DRAG_N_DROP_INDICATOR_BG;
+ -fx-border-width: 3px;
+}
+
+.main-window .drag-n-drop-indicator .drag-n-drop-header {
+ -fx-background-color: DRAG_N_DROP_INDICATOR_BG;
+ -fx-padding: 3px;
+}
+
/*******************************************************************************
* *
* TabPane *
@@ -212,34 +213,36 @@
}
.tab-pane > .tab-header-area {
- -fx-padding: 6px 12px 0 12px;
- -fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG;
- -fx-background-insets: 0, 0 0 1px 0;
+ -fx-padding: 0 12px;
+ -fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
+ -fx-background-insets: 0, 0 0 3px 0;
}
-.tab-pane > .tab-header-area > .headers-region > .tab {
- -fx-background-color: CONTROL_BORDER_NORMAL, MAIN_BG;
- -fx-background-insets: 0 0 1px 0, 1px;
- -fx-background-radius: 4px 4px 0 0;
- -fx-padding: 0.2em 1em 0.2em 1em;
+.tab-pane .tab {
+ -fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
+ -fx-background-insets: 0, 0 0 3px 0;
+ -fx-padding: 6px 12px;
}
-.tab-pane > .tab-header-area > .headers-region > .tab:selected {
- -fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG;
- -fx-background-insets: 0, 1px 1px 0 1px;
+.tab-pane .tab:selected {
+ -fx-background-color: PRIMARY_BG, CONTROL_PRIMARY_LIGHT_BG_NORMAL;
}
-.tab-pane > .tab-header-area > .headers-region > .tab > .tab-container > .tab-label {
- -fx-text-fill: TEXT_FILL;
+.tab-pane .tab .tab-label {
+ -fx-text-fill: SECONDARY_BG;
-fx-alignment: CENTER;
}
-.tab-pane > .tab-header-area > .headers-region > .tab .glyph-icon {
+.tab-pane .tab .glyph-icon {
-fx-fill: SECONDARY_BG;
}
-.tab-pane > .tab-header-area > .headers-region > .tab:selected .glyph-icon {
- -fx-fill: TEXT_FILL;
+.tab-pane .tab:selected .glyph-icon {
+ -fx-fill: PRIMARY_BG;
+}
+
+.tab-pane .tab:selected .tab-label {
+ -fx-text-fill: TEXT_FILL_PRIMARY;
}
/*******************************************************************************
@@ -672,7 +675,6 @@
******************************************************************************/
.choice-box {
- -fx-text-fill: TEXT_FILL;
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
-fx-background-insets: 0, 1px;
-fx-background-radius: 4px;
@@ -683,6 +685,18 @@
-fx-background-color: CONTROL_BORDER_FOCUSED, CONTROL_BG_NORMAL;
}
+.choice-box:disabled {
+ -fx-background-color: CONTROL_BORDER_DISABLED, CONTROL_BG_DISABLED;
+}
+
+.choice-box > .label {
+ -fx-text-fill: TEXT_FILL;
+}
+
+.choice-box:disabled > .label {
+ -fx-text-fill: TEXT_FILL_SECONDARY;
+}
+
.choice-box > .open-button {
-fx-padding: 0 0 0 0.3em;
}
@@ -694,6 +708,10 @@
-fx-shape: "M 0 0 h 7 l -3.5 4 z";
}
+.choice-box:disabled > .open-button > .arrow {
+ -fx-background-color: transparent, TEXT_FILL_SECONDARY;
+}
+
.choice-box .context-menu {
-fx-translate-x: -1.4em;
}
diff --git a/main/ui/src/main/resources/fxml/addvault_existing.fxml b/main/ui/src/main/resources/fxml/addvault_existing.fxml
index 4b989e291..e9c64f88a 100644
--- a/main/ui/src/main/resources/fxml/addvault_existing.fxml
+++ b/main/ui/src/main/resources/fxml/addvault_existing.fxml
@@ -11,8 +11,8 @@
diff --git a/main/ui/src/main/resources/fxml/addvault_existing_error.fxml b/main/ui/src/main/resources/fxml/addvault_existing_error.fxml
index c145b6283..244f57602 100644
--- a/main/ui/src/main/resources/fxml/addvault_existing_error.fxml
+++ b/main/ui/src/main/resources/fxml/addvault_existing_error.fxml
@@ -11,9 +11,9 @@
diff --git a/main/ui/src/main/resources/fxml/addvault_new_location.fxml b/main/ui/src/main/resources/fxml/addvault_new_location.fxml
index dfb1addbf..420a0f960 100644
--- a/main/ui/src/main/resources/fxml/addvault_new_location.fxml
+++ b/main/ui/src/main/resources/fxml/addvault_new_location.fxml
@@ -14,8 +14,8 @@
@@ -29,8 +29,10 @@
+
+
diff --git a/main/ui/src/main/resources/fxml/vault_options.fxml b/main/ui/src/main/resources/fxml/vault_options.fxml
index 20910a66e..81c19cde9 100644
--- a/main/ui/src/main/resources/fxml/vault_options.fxml
+++ b/main/ui/src/main/resources/fxml/vault_options.fxml
@@ -8,6 +8,7 @@
fx:id="tabPane"
fx:controller="org.cryptomator.ui.vaultoptions.VaultOptionsController"
minWidth="400"
+ tabMinWidth="60"
tabClosingPolicy="UNAVAILABLE"
tabDragPolicy="FIXED">
diff --git a/main/ui/src/main/resources/fxml/vault_options_mount.fxml b/main/ui/src/main/resources/fxml/vault_options_mount.fxml
index 3508dc218..b645d3da2 100644
--- a/main/ui/src/main/resources/fxml/vault_options_mount.fxml
+++ b/main/ui/src/main/resources/fxml/vault_options_mount.fxml
@@ -10,7 +10,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/main/ui/src/main/resources/i18n/strings.properties b/main/ui/src/main/resources/i18n/strings.properties
index 2d7a4d21c..6a8ff8ef3 100644
--- a/main/ui/src/main/resources/i18n/strings.properties
+++ b/main/ui/src/main/resources/i18n/strings.properties
@@ -7,12 +7,18 @@ generic.button.apply=Apply
generic.button.back=Back
generic.button.cancel=Cancel
generic.button.change=Change
+generic.button.copy=Copy
generic.button.done=Done
generic.button.next=Next
+generic.button.print=Print
+## Error
+generic.error.title=An unexpected error occured
+generic.error.instruction=This should not have happened. Please report the error text below and include a description of what steps did lead to this error.
# Tray Menu
traymenu.showMainWindow=Show
traymenu.showPreferencesWindow=Preferences
+traymenu.lockAllVaults=Lock All
traymenu.quitApplication=Quit
traymenu.vault.unlock=Unlock
traymenu.vault.lock=Lock
@@ -34,18 +40,14 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Custom Location
addvaultwizard.new.directoryPickerButton=Choose…
addvaultwizard.new.directoryPickerTitle=Select Directory
-addvaultwizard.new.enterPassword=Enter a password for the vault
addvaultwizard.new.fileAlreadyExists=Vault can not be created at this path because some object already exists.
addvaultwizard.new.invalidName=Invalid vault name. Please consider a regular directory name.
addvaultwizard.new.ioException=Selected directory failed a basic test. Make sure to have the appropriate access rights and nothing interferes with Cryptomator.
### Password
-addvaultwizard.new.reenterPassword=Confirm the password
-addvaultwizard.new.passwordsMatch=Passwords match!
-addvaultwizard.new.passwordsDoNotMatch=Passwords do not match
addvaultwizard.new.createVaultBtn=Create Vault
-### Recovery Key
-addvaultwizard.new.recoveryKeyInstruction=This is your recovery key. Keep it safe, it is your only chance to recover your data if you lose your password.
-addvaultwizard.new.recoveryKeySavedCheckbox=Yes, I've made a secure backup of this recovery key
+addvaultwizard.new.generateRecoveryKeyChoice=You won't be able to access your data without your password. Do you want a recovery key for the case you lose your password?
+addvaultwizard.new.generateRecoveryKeyChoice.yes=Yes please, better safe than sorry
+addvaultwizard.new.generateRecoveryKeyChoice.no=No thanks, I will not lose my password
### Information
addvault.new.readme.storageLocation.fileName=WHAT IS THIS DIRECTORY.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ VAULT FILES ⚠️
@@ -73,10 +75,6 @@ removeVault.confirmBtn=Remove Vault
# Change Password
changepassword.title=Change Password
changepassword.enterOldPassword=Enter the current password for "%s"
-changepassword.enterNewPassword=Enter a new password for your vault
-changepassword.reenterNewPassword=Confirm the new password
-changepassword.passwordsMatch=Passwords match!
-changepassword.passwordsDoNotMatch=Passwords do not match
changepassword.finalConfirmation=I understand that I will not be able to recover my data if I forget my password
# Forget Password
@@ -92,6 +90,8 @@ unlock.unlockBtn=Unlock
## Success
unlock.success.message=Unlocked "%s" successfully! Your vault is now accessible.
unlock.success.revealBtn=Reveal Vault
+## Invalid Mount Point
+unlock.error.invalidMountPoint=Mount point is not an empty directory: %s
# Migration
migration.title=Upgrade Vault
@@ -112,6 +112,10 @@ preferences.general=General
preferences.general.theme=Look & Feel
preferences.general.startHidden=Hide window when starting Cryptomator
preferences.general.debugLogging=Enable debug logging
+preferences.general.autoStart=Launch Cryptomator on system start
+preferences.general.interfaceOrientation=Interface Orientation
+preferences.general.interfaceOrientation.ltr=Left to Right
+preferences.general.interfaceOrientation.rtl=Right to Left
## Volume
preferences.volume=Virtual Drive
preferences.volume.type=Volume Type
@@ -123,10 +127,19 @@ preferences.updates.currentVersion=Current Version: %s
preferences.updates.autoUpdateCheck=Check for updates automatically
preferences.updates.checkNowBtn=Check Now
preferences.updates.updateAvailable=Update to version %s available.
+## Donation Key
+preferences.donationKey=Donation
+preferences.donationKey.registeredFor=Registered for %s
+preferences.donationKey.noDonationKey=No valid donation key found. It's like a license key but for awesome people using free software. ;-)
+preferences.donationKey.getDonationKey=Get a donation key
# Main Window
main.closeBtn.tooltip=Close
main.preferencesBtn.tooltip=Preferences
+main.donationKeyMissing.tooltip=Please consider donating
+## Drag 'n' Drop
+main.dropZone.dropVault=Add this vault
+main.dropZone.unknownDragboardContent=If you want to add a vault, drag it to this window
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Click here to add a vault
main.vaultlist.contextMenu.remove=Remove Vault
@@ -166,14 +179,25 @@ vaultOptions.mount.readonly=Read-Only
vaultOptions.mount.driveName=Drive Name
vaultOptions.mount.customMountFlags=Custom Mount Flags
vaultOptions.mount.winDriveLetterOccupied=occupied
-vaultOptions.mount.winDirChooser=Pick an empty directory
+vaultOptions.mount.mountPoint=Mount Point
+vaultOptions.mount.mountPoint.auto=Automatically pick a suitable location
+vaultOptions.mount.mountPoint.driveLetter=Use assigned drive letter
+vaultOptions.mount.mountPoint.custom=Custom path
+vaultOptions.mount.mountPoint.directoryPickerButton=Choose…
+vaultOptions.mount.mountPoint.directoryPickerTitle=Pick an empty directory
# Recovery Key
recoveryKey.title=Recovery Key
recoveryKey.enterPassword.prompt=Enter your password to show the recovery key for "%s":
-recoveryKey.display.message=Recovery Key for "%s":
+recoveryKey.display.message=The following recovery key can be used to restore access to "%s":
+recoveryKey.display.StorageHints=Keep it somewhere very secure, e.g.:\n • Store it using a password manager\n • Save it on a USB flash drive\n • Print it on paper
-# Misc
+# New Password
+newPassword.promptText=Enter a new password
+newPassword.reenterPassword=Confirm the new password
+newPassword.passwordsMatch=Passwords match!
+newPassword.passwordsDoNotMatch=Passwords do not match
+passwordStrength.messageLabel.tooShort=Use at least %d characters
passwordStrength.messageLabel.0=Very weak
passwordStrength.messageLabel.1=Weak
passwordStrength.messageLabel.2=Fair
diff --git a/main/ui/src/main/resources/i18n/strings_ar.properties b/main/ui/src/main/resources/i18n/strings_ar.properties
index 2299365d9..a455cd526 100644
--- a/main/ui/src/main/resources/i18n/strings_ar.properties
+++ b/main/ui/src/main/resources/i18n/strings_ar.properties
@@ -8,6 +8,7 @@ generic.button.cancel=الغاء
generic.button.change=تغيير
generic.button.done=تم
generic.button.next=التالي
+## Error
# Tray Menu
traymenu.showMainWindow=اظهار
@@ -33,15 +34,10 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=موقع مخصص
addvaultwizard.new.directoryPickerButton=اختر…
addvaultwizard.new.directoryPickerTitle=اختر القاموس
-addvaultwizard.new.enterPassword=أدخل كلمة المرور لهذا الحساب
addvaultwizard.new.fileAlreadyExists=لا يمكن إنشاء مخزن على هذا المسار لأن بعض الملفات موجودة مسبقا.
addvaultwizard.new.invalidName=اسم المخزن غير صالح. يرجى النظر في اسم الدليل المعتاد.
### Password
-addvaultwizard.new.reenterPassword=تأكيد كلمة المرور
-addvaultwizard.new.passwordsMatch=كلمات المرور متطابقة!
-addvaultwizard.new.passwordsDoNotMatch=كلمات المرور غير متطابقة
addvaultwizard.new.createVaultBtn=انشئ حافظة
-### Recovery Key
### Information
addvault.new.readme.storageLocation.4=لو انك تحتاج مساعدة, جرب "%s".
## Existing
@@ -52,10 +48,6 @@ addvaultwizard.existing.chooseBtn=اختر…
removeVault.confirmBtn=احذف الحافظة
# Change Password
-changepassword.enterNewPassword=ادخل كلمة المرور الجديدة لحافظتك
-changepassword.reenterNewPassword=تأكيد كلمة المرور الجديدة
-changepassword.passwordsMatch=كلمات المرور متطابقة!
-changepassword.passwordsDoNotMatch=كلمات المرور غير متطابقة
# Forget Password
forgetPassword.title=نسيت كلمة المرور
@@ -67,6 +59,7 @@ unlock.savePassword=احفظ كلمة المرور
unlock.unlockBtn=افتح
## Success
unlock.success.revealBtn=افتح الحافظة
+## Invalid Mount Point
# Migration
migration.title=ترقية الحافظة
@@ -87,9 +80,11 @@ preferences.volume.webdav.port=منفذ WebDav
preferences.updates=تحديثات
preferences.updates.autoUpdateCheck=تحقق من التحديثات اوتوماتيكيا
preferences.updates.checkNowBtn=تحقق الان
+## Donation Key
# Main Window
main.preferencesBtn.tooltip=تفضيلات
+## Drag 'n' Drop
## Vault List
main.vaultlist.addVaultBtn=أضِف مخزنًا
## Vault Detail
@@ -108,9 +103,10 @@ main.vaultDetail.migrateButton=ترقية الحافظة
## General
vaultOptions.general=عام
## Mount
+vaultOptions.mount.mountPoint.directoryPickerButton=اختر…
# Recovery Key
-# Misc
+# New Password
# Quit
diff --git a/main/ui/src/main/resources/i18n/strings_cs.properties b/main/ui/src/main/resources/i18n/strings_cs.properties
index bf93e13e1..5ee4a53c6 100644
--- a/main/ui/src/main/resources/i18n/strings_cs.properties
+++ b/main/ui/src/main/resources/i18n/strings_cs.properties
@@ -6,8 +6,11 @@ generic.button.apply=Použít
generic.button.back=Zpět
generic.button.cancel=Zrušit
generic.button.change=Změnit
+generic.button.copy=Kopírovat
generic.button.done=Hotovo
generic.button.next=Další
+generic.button.print=Vytisknout
+## Error
# Tray Menu
traymenu.showMainWindow=Zobrazit
@@ -33,16 +36,12 @@ addvaultwizard.new.locationPrompt=...
addvaultwizard.new.directoryPickerLabel=Vlastní umístění
addvaultwizard.new.directoryPickerButton=Vybrat...
addvaultwizard.new.directoryPickerTitle=Vyberte adresář
-addvaultwizard.new.enterPassword=Zadejte heslo pro trezor
addvaultwizard.new.fileAlreadyExists=Trezor nemůže být vytvořen na tomto umístění, protože stejný objekt již existuje.
addvaultwizard.new.invalidName=Neplatné jméno trezoru. Prosím změňte jméno adresáře.
### Password
-addvaultwizard.new.reenterPassword=Potvrďte heslo
-addvaultwizard.new.passwordsMatch=Hesla se shodují!
-addvaultwizard.new.passwordsDoNotMatch=Hesla se neshodují
addvaultwizard.new.createVaultBtn=Vytvořit trezor
-### Recovery Key
-addvaultwizard.new.recoveryKeyInstruction=Toto je tvůj obnovovací klíč. Uchovej jej v bezpečí, je to jediná šance, jak obnovit data, pokud ztratíš heslo.
+addvaultwizard.new.generateRecoveryKeyChoice.yes=Ano prosím, jistota je jistota
+addvaultwizard.new.generateRecoveryKeyChoice.no=Ne, díky, neztratím své heslo
### Information
addvault.new.readme.storageLocation.fileName=CO JE TOTO ZA ADRESÁŘ.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ OBSAH TREZORU ⚠️
@@ -69,10 +68,6 @@ removeVault.confirmBtn=Odstranit trezor
# Change Password
changepassword.title=Změnit heslo
changepassword.enterOldPassword=Zadejte současné heslo pro "%s"
-changepassword.enterNewPassword=Zadejte nové heslo pro váš trezor
-changepassword.reenterNewPassword=Potvrď nové heslo
-changepassword.passwordsMatch=Hesla se shodují!
-changepassword.passwordsDoNotMatch=Hesla se neshodují
changepassword.finalConfirmation=UPOZORNĚNÍ: Pokud heslo zapomenete/ztratíte, nenávratně příjdete o přístup ke svému obsahu!
# Forget Password
@@ -88,6 +83,7 @@ unlock.unlockBtn=Odemknout
## Success
unlock.success.message=Trezor "%s" byl úspěšně odemčen a nyní je dostupný.
unlock.success.revealBtn=Zobrazit trezor
+## Invalid Mount Point
# Migration
migration.title=Upgrade trezoru
@@ -119,10 +115,18 @@ preferences.updates.currentVersion=Aktuální verze: %s
preferences.updates.autoUpdateCheck=Automaticky kontrolovat aktualizace
preferences.updates.checkNowBtn=Zkontrolovat nyní
preferences.updates.updateAvailable=Dostupná nová verze %s.
+## Donation Key
+preferences.donationKey=Příspěvek
+preferences.donationKey.registeredFor=Registrováno na: %s
+preferences.donationKey.getDonationKey=Získat darovací klíč
# Main Window
main.closeBtn.tooltip=Zavřít
main.preferencesBtn.tooltip=Nastavení
+main.donationKeyMissing.tooltip=Prosím, zvažte darování
+## Drag 'n' Drop
+main.dropZone.dropVault=Přidat tento trezor
+main.dropZone.unknownDragboardContent=Pokud chcete přidat trezor, přetáhněte jej do tohoto okna
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Klikněte sem pro přidání nového trezoru
main.vaultlist.contextMenu.remove=Odstranit trezor
@@ -144,6 +148,7 @@ main.vaultDetail.throughput.kbps=%.1f kiB/s
main.vaultDetail.throughput.mbps=%.1f MiB/s
### Needs Migration
main.vaultDetail.migrateButton=Upgrade trezoru
+main.vaultDetail.migratePrompt=Váš trezor musí být aktualizován na nový formát, než k němu budete mít přístup
# Wrong File Alert
wrongFileAlert.title=neznámý soubor
@@ -161,14 +166,20 @@ vaultOptions.mount.readonly=Pouze pro čtení
vaultOptions.mount.driveName=Jméno jednotky
vaultOptions.mount.customMountFlags=Vlastní parametry
vaultOptions.mount.winDriveLetterOccupied=obsazené
-vaultOptions.mount.winDirChooser=Vyberte prázdný adresář
+vaultOptions.mount.mountPoint=Přípojný bod
+vaultOptions.mount.mountPoint.auto=Automaticky vybrat vhodné místo
+vaultOptions.mount.mountPoint.driveLetter=Použít přiřazené písmeno
+vaultOptions.mount.mountPoint.custom=Vlastní cesta
+vaultOptions.mount.mountPoint.directoryPickerButton=Vybrat...
+vaultOptions.mount.mountPoint.directoryPickerTitle=Vyberte prázdný adresář
# Recovery Key
recoveryKey.title=Klíč k obnově
recoveryKey.enterPassword.prompt=Zadejte své heslo pro zobrazení obnovovacího klíče pro "%s":
-recoveryKey.display.message=Klíč k obnově "%s":
+recoveryKey.display.message=Následující obnovovací klíč může být použit k obnovení přístupu k "%s":
+recoveryKey.display.StorageHints=Uchovejte ho někde velmi bezpečně, např.\n • Uložit pomocí správce hesel\n • Uložit na USB flash disk\n • Vytisknout na papír
-# Misc
+# New Password
passwordStrength.messageLabel.0=velmi slabé
passwordStrength.messageLabel.1=slabé
passwordStrength.messageLabel.2=dostatečné
diff --git a/main/ui/src/main/resources/i18n/strings_de.properties b/main/ui/src/main/resources/i18n/strings_de.properties
index 2b8607471..3791235a3 100644
--- a/main/ui/src/main/resources/i18n/strings_de.properties
+++ b/main/ui/src/main/resources/i18n/strings_de.properties
@@ -6,12 +6,18 @@ generic.button.apply=Übernehmen
generic.button.back=Zurück
generic.button.cancel=Abbrechen
generic.button.change=Ändern
+generic.button.copy=Kopieren
generic.button.done=Fertig
generic.button.next=Weiter
+generic.button.print=Drucken
+## Error
+generic.error.title=Ein unerwarteter Fehler ist aufgetreten
+generic.error.instruction=Dies hätte nicht passieren dürfen. Bitte melde den Fehlertext unten und füge eine Beschreibung hinzu, welche Schritte zu diesem Fehler geführt haben.
# Tray Menu
traymenu.showMainWindow=Anzeigen
traymenu.showPreferencesWindow=Einstellungen
+traymenu.lockAllVaults=Alle sperren
traymenu.quitApplication=Beenden
traymenu.vault.unlock=Entsperren
traymenu.vault.lock=Sperren
@@ -32,32 +38,28 @@ addvaultwizard.new.locationLabel=Speicherort
addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Eigener Ort
addvaultwizard.new.directoryPickerButton=Durchsuchen …
-addvaultwizard.new.directoryPickerTitle=Wähle ein Verzeichnis
-addvaultwizard.new.enterPassword=Vergib ein Passwort für den Tresor
+addvaultwizard.new.directoryPickerTitle=Verzeichnis auswählen
addvaultwizard.new.fileAlreadyExists=Der Tresor konnte nicht erstellt werden, da der Speicherort bereits belegt ist.
addvaultwizard.new.invalidName=Ungültiger Tresorname. Tresore müssen wie Verzeichnisse benannt werden.
addvaultwizard.new.ioException=Auf das ausgewählte Verzeichnis kann nicht zugegriffen werden. Prüfe die Zugriffsrechte und stelle sicher, dass Cryptomator nicht von einer anderen Anwendung blockiert wird.
### Password
-addvaultwizard.new.reenterPassword=Bestätige das Passwort
-addvaultwizard.new.passwordsMatch=Passwörter stimmen überein!
-addvaultwizard.new.passwordsDoNotMatch=Passwörter stimmen nicht überein
addvaultwizard.new.createVaultBtn=Tresor erstellen
-### Recovery Key
-addvaultwizard.new.recoveryKeyInstruction=Dies ist dein Wiederherstellungsschlüssel. Bewahre ihn sicher auf. Ohne ihn gibt es keine Möglichkeit deine Daten wiederherzustellen, wenn du dein Kennwort verloren hast.
-addvaultwizard.new.recoveryKeySavedCheckbox=Ja, ich habe eine Datensicherung dieses Wiederherstellungsschlüssels durchgeführt
+addvaultwizard.new.generateRecoveryKeyChoice=Ohne dieses Passwort kannst du auf deine Daten nicht mehr zugreifen. Möchtest du für den Fall eines Passwortverlusts einen Wiederherstellungsschlüssel erstellen?
+addvaultwizard.new.generateRecoveryKeyChoice.yes=Ja bitte, sicher ist sicher
+addvaultwizard.new.generateRecoveryKeyChoice.no=Nein danke, ich werde mein Passwort nicht verlieren
### Information
addvault.new.readme.storageLocation.fileName=WAS BEDEUTET DIESER ORDNER.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ TRESORDATEIEN ⚠️
addvault.new.readme.storageLocation.2=Dies ist der Speicherort deines Tresors. Ändere {\\b KEINE} Dateien in diesem Verzeichnis.
addvault.new.readme.storageLocation.3=Wenn du Dateien mit Cryptomator verschlüsseln möchtest, entsperre den Tresor und verwende das zur Verfügung gestellte Laufwerk.
addvault.new.readme.storageLocation.4=Falls du Hilfe brauchst, versuche %s.
-addvault.new.readme.accessLocation.fileName=WILLKOMMEN ZUM TRESOR.rtf
+addvault.new.readme.accessLocation.fileName=WILLKOMMEN ZU DEINEM TRESOR.rtf
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ VERSCHLÜSSELTES LAUFWERK 🔐️
-addvault.new.readme.accessLocation.2=Dies ist dein Tresorlaufwerk. Alle zu diesem Laufwerk hinzugefügten Dateien werden mit Cryptomator verschlüsselt. Um zu einem späteren Zeitpunkt auf dieses Laufwerk zuzugreifen, entsperre es einfach wieder aus der Cryptomator-Anwendung heraus.
+addvault.new.readme.accessLocation.2=Dies ist dein Tresorlaufwerk. Alle zu diesem Laufwerk hinzugefügten Dateien werden von Cryptomator verschlüsselt. Um zu einem späteren Zeitpunkt auf dieses Laufwerk zuzugreifen, entsperre es einfach wieder aus der Cryptomator-Anwendung heraus.
addvault.new.readme.accessLocation.3=Diese Datei kannst du löschen.
## Existing
addvaultwizard.existing.instruction=Wähle die Datei „masterkey.cryptomator“ deines vorhandenen Tresors aus.
-addvaultwizard.existing.chooseBtn=Durchsuchen…
+addvaultwizard.existing.chooseBtn=Durchsuchen …
addvaultwizard.existing.filePickerTitle=Masterkey-Datei auswählen
addvaultwizard.existing.error=Der Tresor „%s“ ist kein gültiger Tresor und kann nicht hinzugefügt werden. Weitere Informationen findest du im Diagnoseprotokoll.
## Success
@@ -71,11 +73,7 @@ removeVault.confirmBtn=Tresor entfernen
# Change Password
changepassword.title=Password ändern
-changepassword.enterOldPassword=Gib dein derzeitiges Passwort für "%s" ein
-changepassword.enterNewPassword=Gib ein neues Tresorpasswort ein
-changepassword.reenterNewPassword=Bestätige das neue Passwort
-changepassword.passwordsMatch=Passwörter stimmen überein!
-changepassword.passwordsDoNotMatch=Passwörter stimmen nicht überein
+changepassword.enterOldPassword=Gib dein aktuelles Passwort für „%s“ ein
changepassword.finalConfirmation=Mir ist bewusst, dass ich den Tresor ohne das Passwort NICHT mehr öffnen kann
# Forget Password
@@ -85,12 +83,14 @@ forgetPassword.confirmBtn=Passwort vergessen
# Unlock
unlock.title=Tresor entsperren
-unlock.passwordPrompt=Gib das Passwort für "%s" ein:
+unlock.passwordPrompt=Gib das Passwort für „%s“ ein:
unlock.savePassword=Passwort speichern
unlock.unlockBtn=Entsperren
## Success
-unlock.success.message="%s" erfolgreich entsperrt!
+unlock.success.message=„%s“ erfolgreich entsperrt! Nun kannst du auf deinen Tresor zugreifen.
unlock.success.revealBtn=Tresor anzeigen
+## Invalid Mount Point
+unlock.error.invalidMountPoint=Einhängepunkt ist kein leeres Verzeichnis: %s
# Migration
migration.title=Tresor aktualisieren
@@ -111,6 +111,10 @@ preferences.general=Allgemein
preferences.general.theme=Erscheinungsbild
preferences.general.startHidden=Cryptomator im Hintergrund starten
preferences.general.debugLogging=Diagnoseprotokoll aktivieren
+preferences.general.autoStart=Cryptomator beim Systemstart starten
+preferences.general.interfaceOrientation=Oberflächenausrichtung
+preferences.general.interfaceOrientation.ltr=Von links nach rechts
+preferences.general.interfaceOrientation.rtl=Von rechts nach links
## Volume
preferences.volume=Tresorlaufwerk
preferences.volume.type=Laufwerkstyp
@@ -122,10 +126,19 @@ preferences.updates.currentVersion=Aktuelle Version: %s
preferences.updates.autoUpdateCheck=Automatisch nach Updates suchen
preferences.updates.checkNowBtn=Jetzt suchen
preferences.updates.updateAvailable=Update auf Version %s verfügbar.
+## Donation Key
+preferences.donationKey=Spende
+preferences.donationKey.registeredFor=Registriert für %s
+preferences.donationKey.noDonationKey=Kein gültiger Spendenschlüssel gefunden. Er ist wie ein Lizenzschlüssel, aber für tolle Leute, die freie Software verwenden. ;-)
+preferences.donationKey.getDonationKey=Einen Spendenschlüssel erhalten
# Main Window
main.closeBtn.tooltip=Schließen
main.preferencesBtn.tooltip=Einstellungen
+main.donationKeyMissing.tooltip=Bitte denke über eine Spende nach
+## Drag 'n' Drop
+main.dropZone.dropVault=Diesen Tresor hinzufügen
+main.dropZone.unknownDragboardContent=Wenn Sie einen Tresor hinzufügen möchten, ziehen Sie ihn in dieses Fenster
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Klicke hier, um einen Tresor hinzuzufügen
main.vaultlist.contextMenu.remove=Tresor entfernen
@@ -152,7 +165,7 @@ main.vaultDetail.migratePrompt=Dein Tresor muss auf ein neues Format aktualisier
# Wrong File Alert
wrongFileAlert.title=Unbekannte Datei
wrongFileAlert.btn=OK
-wrongFileAlert.information=Du hast versucht, eine Datei oder einen Ordner hinzuzufügen, der scheinbar kein Cryptomator-Tresor ist. Um Dateien zu verschlüsseln, erstelle und entsperre einen Tresor.
+wrongFileAlert.information=Du hast versucht, eine Datei oder einen Ordner hinzuzufügen, der anscheinend kein Cryptomator-Tresor ist. Zum Verschlüsseln deiner Daten musst du zuerst einen Tresor erstellen und diesen dann entsperren.
# Vault Options
## General
@@ -165,14 +178,25 @@ vaultOptions.mount.readonly=Schreibgeschützt
vaultOptions.mount.driveName=Laufwerksname
vaultOptions.mount.customMountFlags=Benutzerdefinierte Mount-Flags
vaultOptions.mount.winDriveLetterOccupied=belegt
-vaultOptions.mount.winDirChooser=Wähle ein leeres Verzeichnis
+vaultOptions.mount.mountPoint=Einhängepunkt
+vaultOptions.mount.mountPoint.auto=Automatisch einen passenden Ort auswählen
+vaultOptions.mount.mountPoint.driveLetter=Zugewiesenen Laufwerksbuchstaben verwenden
+vaultOptions.mount.mountPoint.custom=Eigener Pfad
+vaultOptions.mount.mountPoint.directoryPickerButton=Durchsuchen …
+vaultOptions.mount.mountPoint.directoryPickerTitle=Wähle ein leeres Verzeichnis
# Recovery Key
recoveryKey.title=Wiederherstellungsschlüssel
-recoveryKey.enterPassword.prompt=Gib dein Kennwort ein, um den Wiederherstellungsschlüssel für „%s“ anzuzeigen:
-recoveryKey.display.message=Wiederherstellungsschlüssel für „%s“:
+recoveryKey.enterPassword.prompt=Gib dein Passwort ein, um den Wiederherstellungsschlüssel für „%s“ anzuzeigen:
+recoveryKey.display.message=Mit folgendem Wiederherstellungsschlüssel kannst du den Zugriff auf „%s“ wiederherstellen:
+recoveryKey.display.StorageHints=Bewahre ihn möglichst sicher auf, z. B.\n • in einem Passwortmanager\n • auf einem USB-Speicherstick\n • auf Papier ausgedruckt
-# Misc
+# New Password
+newPassword.promptText=Gib ein neues Passwort ein
+newPassword.reenterPassword=Bestätige das neue Passwort
+newPassword.passwordsMatch=Passwörter stimmen überein!
+newPassword.passwordsDoNotMatch=Passwörter stimmen nicht überein
+passwordStrength.messageLabel.tooShort=Verwende mindestens %d Zeichen
passwordStrength.messageLabel.0=Sehr schwach
passwordStrength.messageLabel.1=Schwach
passwordStrength.messageLabel.2=Mittel
diff --git a/main/ui/src/main/resources/i18n/strings_el.properties b/main/ui/src/main/resources/i18n/strings_el.properties
index 6f0caec0b..fd4979118 100644
--- a/main/ui/src/main/resources/i18n/strings_el.properties
+++ b/main/ui/src/main/resources/i18n/strings_el.properties
@@ -2,6 +2,7 @@
# Generics
## Button
+## Error
# Tray Menu
@@ -11,7 +12,6 @@
### Name
### Location
### Password
-### Recovery Key
### Information
## Existing
## Success
@@ -24,6 +24,7 @@
# Unlock
## Success
+## Invalid Mount Point
# Migration
## Start
@@ -34,8 +35,10 @@
## General
## Volume
## Updates
+## Donation Key
# Main Window
+## Drag 'n' Drop
## Vault List
## Vault Detail
### Locked
@@ -50,6 +53,6 @@
# Recovery Key
-# Misc
+# New Password
# Quit
diff --git a/main/ui/src/main/resources/i18n/strings_es.properties b/main/ui/src/main/resources/i18n/strings_es.properties
index ba2d10cd9..eb4a375ce 100644
--- a/main/ui/src/main/resources/i18n/strings_es.properties
+++ b/main/ui/src/main/resources/i18n/strings_es.properties
@@ -6,12 +6,18 @@ generic.button.apply=Aplicar
generic.button.back=Volver
generic.button.cancel=Cancelar
generic.button.change=Modificar
+generic.button.copy=Copiar
generic.button.done=Hecho
generic.button.next=Continuar
+generic.button.print=Imprimir
+## Error
+generic.error.title=Ocurrió un error inesperado
+generic.error.instruction=Esto no debió suceder. Notifique el error de abajo e incluya una descripción de los pasos que llevaron a este error.
# Tray Menu
traymenu.showMainWindow=Mostrar
traymenu.showPreferencesWindow=Preferencias
+traymenu.lockAllVaults=Bloquear todo
traymenu.quitApplication=Salir
traymenu.vault.unlock=Desbloquear
traymenu.vault.lock=Bloquear
@@ -33,18 +39,14 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Ubicación personalizada
addvaultwizard.new.directoryPickerButton=Elegir…
addvaultwizard.new.directoryPickerTitle=Seleccionar directorio
-addvaultwizard.new.enterPassword=Ingresar una contraseña para la bóveda
addvaultwizard.new.fileAlreadyExists=La bóveda no puede crearse en esta ruta porque ya existe un objeto.
addvaultwizard.new.invalidName=Nombre de bóveda inválido. Considere un nombre de directorio regular.
addvaultwizard.new.ioException=El directorio seleccionado falló una prueba básica. Asegúrese de tener los derechos de acceso apropiados y que nada interfiere con Cryptomator.
### Password
-addvaultwizard.new.reenterPassword=Confirmar la contraseña
-addvaultwizard.new.passwordsMatch=¡Las contraseñas coinciden!
-addvaultwizard.new.passwordsDoNotMatch=Las contraseñas no coinciden
addvaultwizard.new.createVaultBtn=Crear bóveda
-### Recovery Key
-addvaultwizard.new.recoveryKeyInstruction=Esta es la clave de recuperación. Se debe mantener segura, es la única oportunidad de recuperar los datos si se pierde la contraseña.
-addvaultwizard.new.recoveryKeySavedCheckbox=Sí, he hecho una copia de seguridad de esta clave de recuperación
+addvaultwizard.new.generateRecoveryKeyChoice=No podrá acceder a sus datos sin su contraseña. ¿Desea una clave de recuperación en caso de que pierda su contraseña?
+addvaultwizard.new.generateRecoveryKeyChoice.yes=Sí, por favor
+addvaultwizard.new.generateRecoveryKeyChoice.no=No, gracias
### Information
addvault.new.readme.storageLocation.fileName=QUÉ ES ESTE DIRECTORIO.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ ARCHIVOS DE LA BÓVEDA ⚠️
@@ -72,10 +74,6 @@ removeVault.confirmBtn=Eliminar bóveda
# Change Password
changepassword.title=Cambiar contraseña
changepassword.enterOldPassword=Ingresar la contraseña actual para "%s"
-changepassword.enterNewPassword=Ingresar una contraseña nueva para la bóveda
-changepassword.reenterNewPassword=Confirmar la contraseña nueva
-changepassword.passwordsMatch=¡Las contraseñas coinciden!
-changepassword.passwordsDoNotMatch=Las contraseñas no coinciden
changepassword.finalConfirmation=Entiendo que no podré recuperar mis datos si olvido mi contraseña
# Forget Password
@@ -91,6 +89,8 @@ unlock.unlockBtn=Desbloquear
## Success
unlock.success.message=¡"%s" se desbloqueó con éxito! La bóveda ya es accesible.
unlock.success.revealBtn=Revelar bóveda
+## Invalid Mount Point
+unlock.error.invalidMountPoint=El punto de montaje no es un directorio vacío: %s
# Migration
migration.title=Mejorar bóveda
@@ -111,6 +111,10 @@ preferences.general=General
preferences.general.theme=Apariencia
preferences.general.startHidden=Ocultar ventana al iniciar Cryptomator
preferences.general.debugLogging=Habilitar registro de depuración
+preferences.general.autoStart=Cargar Cryptomator al iniciar el sistema
+preferences.general.interfaceOrientation=Orientación de la interfaz
+preferences.general.interfaceOrientation.ltr=Izquierda a derecha
+preferences.general.interfaceOrientation.rtl=Derecha a izquierda
## Volume
preferences.volume=Unidad virtual
preferences.volume.type=Tipo de volumen
@@ -122,10 +126,19 @@ preferences.updates.currentVersion=Versión actual: %s
preferences.updates.autoUpdateCheck=Buscar actualizaciones automáticamente
preferences.updates.checkNowBtn=Buscar ahora
preferences.updates.updateAvailable=Actualización a la versión %s disponible.
+## Donation Key
+preferences.donationKey=Donación
+preferences.donationKey.registeredFor=Registrado para %s
+preferences.donationKey.noDonationKey=No se encontró una clave de donación válida. Es como una clave de licencia pero para personas geniales que usan software libre.
+preferences.donationKey.getDonationKey=Obtener clave de donación
# Main Window
main.closeBtn.tooltip=Cerrar
main.preferencesBtn.tooltip=Preferencias
+main.donationKeyMissing.tooltip=Por favor, considera donar
+## Drag 'n' Drop
+main.dropZone.dropVault=Añadir esta bóveda
+main.dropZone.unknownDragboardContent=Si desea añadir una bóveda, arrástrela a esta ventana
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Hacer clic aquí para añadir una bóveda
main.vaultlist.contextMenu.remove=Eliminar bóveda
@@ -165,14 +178,25 @@ vaultOptions.mount.readonly=Sólo lectura
vaultOptions.mount.driveName=Nombre de la unidad
vaultOptions.mount.customMountFlags=Opciones de montaje personalizadas
vaultOptions.mount.winDriveLetterOccupied=ocupado
-vaultOptions.mount.winDirChooser=Elija un directorio vacío
+vaultOptions.mount.mountPoint=Punto de montaje
+vaultOptions.mount.mountPoint.auto=Escoger automáticamente una ubicación adecuada
+vaultOptions.mount.mountPoint.driveLetter=Usar letra de unidad asignada
+vaultOptions.mount.mountPoint.custom=Ruta personalizada
+vaultOptions.mount.mountPoint.directoryPickerButton=Elegir…
+vaultOptions.mount.mountPoint.directoryPickerTitle=Elegir un directorio vacío
# Recovery Key
recoveryKey.title=Clave de recuperación
recoveryKey.enterPassword.prompt=Ingresar la contraseña para mostrar la clave de recuperación para "%s":
-recoveryKey.display.message=Clave de recuperación para "%s":
+recoveryKey.display.message=La siguiente clave de recuperación puede usarse para restaurar el acceso a "%s":
+recoveryKey.display.StorageHints=Manténgala en algún lugar seguro, p.ej.:\n • Almacenarla en un administrador de contraseñas\n • Guardarla en una llave USB\n • Imprimirla en un papel
-# Misc
+# New Password
+newPassword.promptText=Ingrese una contraseña nueva
+newPassword.reenterPassword=Confirme la contraseña nueva
+newPassword.passwordsMatch=¡Las contraseñas coinciden!
+newPassword.passwordsDoNotMatch=Las contraseñas no coinciden
+passwordStrength.messageLabel.tooShort=Usar al menos %d caracteres
passwordStrength.messageLabel.0=Muy débil
passwordStrength.messageLabel.1=Débil
passwordStrength.messageLabel.2=Aceptable
diff --git a/main/ui/src/main/resources/i18n/strings_fr.properties b/main/ui/src/main/resources/i18n/strings_fr.properties
index b0df2d887..f68eb7853 100644
--- a/main/ui/src/main/resources/i18n/strings_fr.properties
+++ b/main/ui/src/main/resources/i18n/strings_fr.properties
@@ -6,12 +6,18 @@ generic.button.apply=Appliquer
generic.button.back=Précédent
generic.button.cancel=Annuler
generic.button.change=Modifier
+generic.button.copy=Copier
generic.button.done=Terminé
generic.button.next=Suivant
+generic.button.print=Imprimer
+## Error
+generic.error.title=Une erreur inattendue est survenue
+generic.error.instruction=Cela n'aurait pas dû se produire. Veuillez reporter le texte d'erreur ci-dessous et inclure une description des étapes qui ont conduit à cette erreur.
# Tray Menu
traymenu.showMainWindow=Montrer
traymenu.showPreferencesWindow=Préférences
+traymenu.lockAllVaults=Tout Verrouiller
traymenu.quitApplication=Quitter
traymenu.vault.unlock=Déverrouiller
traymenu.vault.lock=Verrouiller
@@ -33,18 +39,14 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Emplacement personnalisé
addvaultwizard.new.directoryPickerButton=Choisir...
addvaultwizard.new.directoryPickerTitle=Choisissez le répertoire
-addvaultwizard.new.enterPassword=Saisissez un mot de passe pour le coffre
addvaultwizard.new.fileAlreadyExists=Le coffre ne peut pas être créé sur ce chemin car un objet existe déjà.
addvaultwizard.new.invalidName=Nom de coffre invalide. Préférez un nom de répertoire habituel.
addvaultwizard.new.ioException=Le répertoire sélectionné a échoué un test de base. Assurez-vous d'avoir les droits d'accès appropriés et que rien n'interfère avec Cryptomator.
### Password
-addvaultwizard.new.reenterPassword=Confirmez le mot de passe
-addvaultwizard.new.passwordsMatch=Mots de passe identiques !
-addvaultwizard.new.passwordsDoNotMatch=Les mots de passe ne correspondent pas
addvaultwizard.new.createVaultBtn=Créer un coffre
-### Recovery Key
-addvaultwizard.new.recoveryKeyInstruction=C'est votre clé de récupération. Gardez-la en sécurité, c'est votre seule chance de récupérer vos données si vous perdez votre mot de passe.
-addvaultwizard.new.recoveryKeySavedCheckbox=Oui, j'ai fait une sauvegarde sécurisée de cette clé de récupération
+addvaultwizard.new.generateRecoveryKeyChoice=Sans votre mot de passe, il sera d'impossible d'accéder à vos données. Souhaitez-vous créer une clé de secours en cas d'oubli du mot passe ?
+addvaultwizard.new.generateRecoveryKeyChoice.yes=Oui, mieux vaut prévenir que guérir
+addvaultwizard.new.generateRecoveryKeyChoice.no=Non merci, je n'oublierai pas mon mot de passe
### Information
addvault.new.readme.storageLocation.fileName=QUEL EST CE RÉPERTOIRE.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ FICHIERS DU COFFRE ⚠️
@@ -72,10 +74,6 @@ removeVault.confirmBtn=Supprimer le coffre
# Change Password
changepassword.title=Modifier le mot de passe
changepassword.enterOldPassword=Entrez le mot de passe actuel pour "%s"
-changepassword.enterNewPassword=Entrez un nouveau mot de passe pour votre coffre
-changepassword.reenterNewPassword=Confirmez le nouveau mot de passe
-changepassword.passwordsMatch=Mots de passe identiques !
-changepassword.passwordsDoNotMatch=Les mots de passe ne correspondent pas
changepassword.finalConfirmation=Je comprends que je ne pourrai pas récupérer mes données si j'oublie mon mot de passe
# Forget Password
@@ -91,6 +89,8 @@ unlock.unlockBtn=Déverrouiller
## Success
unlock.success.message=“%s” déverrouillé ! Le contenu de votre coffre est maintenant accessible.
unlock.success.revealBtn=Révéler le coffre
+## Invalid Mount Point
+unlock.error.invalidMountPoint=Le point de montage n'est pas un répertoire vide : %s
# Migration
migration.title=Mettre à jour le coffre
@@ -111,6 +111,10 @@ preferences.general=Général
preferences.general.theme=Apparence
preferences.general.startHidden=Démarrer Cryptomator en mode caché
preferences.general.debugLogging=Activer les logs debug
+preferences.general.autoStart=Lancer Cryptomator au démarrage du système
+preferences.general.interfaceOrientation=Orientation de l'interface
+preferences.general.interfaceOrientation.ltr=De gauche à droite
+preferences.general.interfaceOrientation.rtl=De droite à gauche
## Volume
preferences.volume=Disque virtuel
preferences.volume.type=Type de volume
@@ -122,10 +126,19 @@ preferences.updates.currentVersion=Version actuelle : “%s”
preferences.updates.autoUpdateCheck=Vérifier automatiquement l’existence de mise à jour
preferences.updates.checkNowBtn=Vérifier maintenant
preferences.updates.updateAvailable=Mise à jour “%s” disponible.
+## Donation Key
+preferences.donationKey=Faire un Don
+preferences.donationKey.registeredFor=Licence enregistrée à %s
+preferences.donationKey.noDonationKey=Aucune clé de don valide trouvée. C'est comme une clé de licence mais pour des personnes géniales utilisant un logiciel libre ;-)
+preferences.donationKey.getDonationKey=Obtenir une clé de don
# Main Window
main.closeBtn.tooltip=Fermer
main.preferencesBtn.tooltip=Préférences
+main.donationKeyMissing.tooltip=Merci d'envisager un don
+## Drag 'n' Drop
+main.dropZone.dropVault=Ajouter ce coffre
+main.dropZone.unknownDragboardContent=Si vous voulez ajouter un coffre, faites-le glisser dans cette fenêtre
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Cliquez ici pour ajouter un coffre
main.vaultlist.contextMenu.remove=Supprimer le coffre
@@ -165,14 +178,25 @@ vaultOptions.mount.readonly=Lecture seule
vaultOptions.mount.driveName=Nom du lecteur
vaultOptions.mount.customMountFlags=Drapeau de montage personnalisé
vaultOptions.mount.winDriveLetterOccupied=occupé
-vaultOptions.mount.winDirChooser=Choisir un répertoire vide
+vaultOptions.mount.mountPoint=Point de montage
+vaultOptions.mount.mountPoint.auto=Choisir automatiquement un emplacement approprié
+vaultOptions.mount.mountPoint.driveLetter=Utiliser la lettre du lecteur assignée
+vaultOptions.mount.mountPoint.custom=Chemin personnalisé
+vaultOptions.mount.mountPoint.directoryPickerButton=Choisir...
+vaultOptions.mount.mountPoint.directoryPickerTitle=Choisir un répertoire vide
# Recovery Key
recoveryKey.title=Clé de récupération
recoveryKey.enterPassword.prompt=Entrez votre mot de passe pour afficher la clé de récupération pour "%s":
-recoveryKey.display.message=Clé de récupération pour "%s":
+recoveryKey.display.message=La clé de récupération suivante peut être utilisée pour restaurer l'accès à "%s " :
+recoveryKey.display.StorageHints=Gardez-le quelque part de façon très sûr, par ex. :\n • Stockez le en utilisant un gestionnaire de mot de passe\n • Enregistrez-le sur une clé USB\n • Imprimez-le sur papier
-# Misc
+# New Password
+newPassword.promptText=Entrer un nouveau mot de passe
+newPassword.reenterPassword=Confirmez le nouveau mot de passe
+newPassword.passwordsMatch=Mots de passe identiques !
+newPassword.passwordsDoNotMatch=Les mots de passe ne sont pas identiques
+passwordStrength.messageLabel.tooShort=Utilisez un minimum de %d caractères
passwordStrength.messageLabel.0=Très faible
passwordStrength.messageLabel.1=Faible
passwordStrength.messageLabel.2=Satisfaisant
diff --git a/main/ui/src/main/resources/i18n/strings_hr.properties b/main/ui/src/main/resources/i18n/strings_hr.properties
index 6f0caec0b..fd4979118 100644
--- a/main/ui/src/main/resources/i18n/strings_hr.properties
+++ b/main/ui/src/main/resources/i18n/strings_hr.properties
@@ -2,6 +2,7 @@
# Generics
## Button
+## Error
# Tray Menu
@@ -11,7 +12,6 @@
### Name
### Location
### Password
-### Recovery Key
### Information
## Existing
## Success
@@ -24,6 +24,7 @@
# Unlock
## Success
+## Invalid Mount Point
# Migration
## Start
@@ -34,8 +35,10 @@
## General
## Volume
## Updates
+## Donation Key
# Main Window
+## Drag 'n' Drop
## Vault List
## Vault Detail
### Locked
@@ -50,6 +53,6 @@
# Recovery Key
-# Misc
+# New Password
# Quit
diff --git a/main/ui/src/main/resources/i18n/strings_it.properties b/main/ui/src/main/resources/i18n/strings_it.properties
index dc461826a..8d3101450 100644
--- a/main/ui/src/main/resources/i18n/strings_it.properties
+++ b/main/ui/src/main/resources/i18n/strings_it.properties
@@ -6,12 +6,18 @@ generic.button.apply=Applica
generic.button.back=Indietro
generic.button.cancel=Annulla
generic.button.change=Modifica
+generic.button.copy=Copia
generic.button.done=Fatto
generic.button.next=Avanti
+generic.button.print=Stampa
+## Error
+generic.error.title=Si è verificato un errore inatteso
+generic.error.instruction=Questo non dovrebbe essere accaduto. Si prega di segnalare il testo dell'errore qui sotto e includere una descrizione delle fasi che hanno portato a questo errore.
# Tray Menu
traymenu.showMainWindow=Visualizza
traymenu.showPreferencesWindow=Impostazioni
+traymenu.lockAllVaults=Blocca Tutto
traymenu.quitApplication=Esci
traymenu.vault.unlock=Sblocca
traymenu.vault.lock=Blocca
@@ -33,18 +39,14 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Posizione personalizzata
addvaultwizard.new.directoryPickerButton=Scegli…
addvaultwizard.new.directoryPickerTitle=Seleziona cartella
-addvaultwizard.new.enterPassword=Inserisci una password per la cassaforte
addvaultwizard.new.fileAlreadyExists=La cassaforte non può essere creata in questo percorso perché un oggetto esiste già.
addvaultwizard.new.invalidName=Nome della cassaforte non valido. Si prega di considerare un nome di directory regolare.
addvaultwizard.new.ioException=La cartella selezionata non ha superato un test di base. Assicurati di avere i diritti di accesso adeguati e che nulla interferisca con Cryptomator.
### Password
-addvaultwizard.new.reenterPassword=Conferma Password
-addvaultwizard.new.passwordsMatch=Le password corrispondono!
-addvaultwizard.new.passwordsDoNotMatch=Le password non corrispondono
addvaultwizard.new.createVaultBtn=Crea Cassaforte
-### Recovery Key
-addvaultwizard.new.recoveryKeyInstruction=Questa è la tua chiave di recupero. Tienilo al sicuro, è la tua unica possibilità di recuperare i tuoi dati se perdi la password.
-addvaultwizard.new.recoveryKeySavedCheckbox=Sì, ho fatto un backup sicuro di questa chiave di recupero
+addvaultwizard.new.generateRecoveryKeyChoice=Non sarai in grado di accedere ai tuoi dati senza password. Vuoi una chiave di recupero nel caso in cui perdi la password?
+addvaultwizard.new.generateRecoveryKeyChoice.yes=Si, per favore, è meglio essere al sicuro
+addvaultwizard.new.generateRecoveryKeyChoice.no=No grazie, non perderò la mia password
### Information
addvault.new.readme.storageLocation.fileName=CHE COSA È QUESTA DIRECTORY.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ FILE CASSAFORTE ⚠️
@@ -72,10 +74,6 @@ removeVault.confirmBtn=Rimuovi Cassaforte
# Change Password
changepassword.title=Modifica password
changepassword.enterOldPassword=Inserisci la password attuale per "%s"
-changepassword.enterNewPassword=Inserisci una nuova password per la tua cassaforte
-changepassword.reenterNewPassword=Conferma la nuova password
-changepassword.passwordsMatch=Le password corrispondono!
-changepassword.passwordsDoNotMatch=Le password non corrispondono
changepassword.finalConfirmation=Ho capito che non potrò recuperare i miei dati se dimentico la mia password
# Forget Password
@@ -91,6 +89,8 @@ unlock.unlockBtn=Sblocca
## Success
unlock.success.message=Sbloccato "%s" con successo! La tua cassaforte è ora accessibile.
unlock.success.revealBtn=Rivela Cassaforte
+## Invalid Mount Point
+unlock.error.invalidMountPoint=Il punto di mount non è una cartella vuota: %s
# Migration
migration.title=Aggiorna Cassaforte
@@ -111,6 +111,9 @@ preferences.general=Generali
preferences.general.theme=Aspetto
preferences.general.startHidden=Nascondi la finestra all'avvio di Cryptomator
preferences.general.debugLogging=Abilita i registri di debug
+preferences.general.interfaceOrientation=Orientamento Interfaccia
+preferences.general.interfaceOrientation.ltr=Da Sinistra a Destra
+preferences.general.interfaceOrientation.rtl=Da Destra a Sinistra
## Volume
preferences.volume=Drive virtuale
preferences.volume.type=Tipo volume
@@ -122,10 +125,19 @@ preferences.updates.currentVersion=Versione attuale %s
preferences.updates.autoUpdateCheck=Controlla automaticamente la presenza di aggiornamenti
preferences.updates.checkNowBtn=Controlla adesso
preferences.updates.updateAvailable=Disponibile aggiornamento alla versione %s.
+## Donation Key
+preferences.donationKey=Donazione
+preferences.donationKey.registeredFor=Registrato per %s
+preferences.donationKey.noDonationKey=Nessuna chiave di donazione valida. È come una chiave di licenza ma per persone fantastiche che usano software gratuito. ;-)
+preferences.donationKey.getDonationKey=Ottieni una chiave di donazione
# Main Window
main.closeBtn.tooltip=Chiudi
main.preferencesBtn.tooltip=Impostazioni
+main.donationKeyMissing.tooltip=Per favore considera una donazione
+## Drag 'n' Drop
+main.dropZone.dropVault=Aggiungi questa cassaforte
+main.dropZone.unknownDragboardContent=Se vuoi aggiungere una cassaforte, trascinala in questa finestra
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Clicca qui per aggiungere una cassaforte
main.vaultlist.contextMenu.remove=Rimuovi Cassaforte
@@ -165,14 +177,25 @@ vaultOptions.mount.readonly=Sola lettura
vaultOptions.mount.driveName=Nome drive
vaultOptions.mount.customMountFlags=Opzioni personalizzate
vaultOptions.mount.winDriveLetterOccupied=occupato
-vaultOptions.mount.winDirChooser=Scegli una directory vuota
+vaultOptions.mount.mountPoint=Punto di mount
+vaultOptions.mount.mountPoint.auto=Scegli automaticamente una posizione adatta
+vaultOptions.mount.mountPoint.driveLetter=Usa la lettera del drive assegnata
+vaultOptions.mount.mountPoint.custom=Percorso personalizzato
+vaultOptions.mount.mountPoint.directoryPickerButton=Scegli…
+vaultOptions.mount.mountPoint.directoryPickerTitle=Scegli una directory vuota
# Recovery Key
recoveryKey.title=Chiave di recupero
recoveryKey.enterPassword.prompt=Inserisci la password per visualizzare la chiave di recupero per "%s":
-recoveryKey.display.message=Chiave di recupero per "%s":
+recoveryKey.display.message=La seguente chiave di recupero può essere utilizzata per ripristinare l'accesso a %s":
+recoveryKey.display.StorageHints=Mantienilo da qualche parte molto sicuro, ad es.\n • Archivialo usando un gestore di password\n • Salvarlo su un'unità flash USB\n • Stamparlo su carta
-# Misc
+# New Password
+newPassword.promptText=Inserisci una nuova password
+newPassword.reenterPassword=Conferma la nuova password
+newPassword.passwordsMatch=Le password corrispondono!
+newPassword.passwordsDoNotMatch=Le password non corrispondono
+passwordStrength.messageLabel.tooShort=Usa almeno %d caratteri
passwordStrength.messageLabel.0=Molto debole
passwordStrength.messageLabel.1=Debole
passwordStrength.messageLabel.2=Sufficiente
diff --git a/main/ui/src/main/resources/i18n/strings_ja.properties b/main/ui/src/main/resources/i18n/strings_ja.properties
index 9cf714cfd..f32a2c4c0 100644
--- a/main/ui/src/main/resources/i18n/strings_ja.properties
+++ b/main/ui/src/main/resources/i18n/strings_ja.properties
@@ -6,12 +6,18 @@ generic.button.apply=適用
generic.button.back=戻る
generic.button.cancel=キャンセル
generic.button.change=変更
+generic.button.copy=コピー
generic.button.done=完了
generic.button.next=次へ
+generic.button.print=印刷
+## Error
+generic.error.title=予期しないエラーが発生しました
+generic.error.instruction=予期しないことが発生しました。下部のエラーテキストとエラーに至った経緯の説明を報告していただけると幸いです。
# Tray Menu
traymenu.showMainWindow=表示
traymenu.showPreferencesWindow=設定
+traymenu.lockAllVaults=すべて施錠
traymenu.quitApplication=終了
traymenu.vault.unlock=解錠
traymenu.vault.lock=施錠
@@ -33,27 +39,23 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=ユーザー設定
addvaultwizard.new.directoryPickerButton=選択...
addvaultwizard.new.directoryPickerTitle=ディレクトリを選択
-addvaultwizard.new.enterPassword=金庫のパスワードを入力してください
addvaultwizard.new.fileAlreadyExists=いくつかのオブジェクトが既に存在しているため、このパスに金庫を作成することはできません。
addvaultwizard.new.invalidName=無効な金庫の名前です。一般的なディレクトリの名前を検討してください。
-addvaultwizard.new.ioException=選択したディレクトリは、基本的なテストに失敗しました。適切なアクセス権限があることを確認し、Cryptomator に干渉しないようにしてください。
+addvaultwizard.new.ioException=選択したディレクトリは基本的なテストに失敗しました。適切なアクセス権限があることを確認し、Cryptomator に干渉しないようにしてください。
### Password
-addvaultwizard.new.reenterPassword=パスワードの確認
-addvaultwizard.new.passwordsMatch=パスワードが一致しました!
-addvaultwizard.new.passwordsDoNotMatch=パスワードが一致しません
addvaultwizard.new.createVaultBtn=金庫を作成
-### Recovery Key
-addvaultwizard.new.recoveryKeyInstruction=これは、あなたの回復キーです。大切に保管してください、パスワードを忘れてしまった場合でもデータを復元できる唯一の方法です。
-addvaultwizard.new.recoveryKeySavedCheckbox=はい、回復キーを大切に保管しました
+addvaultwizard.new.generateRecoveryKeyChoice=データにアクセスするにはパスワードが必須です。パスワードを紛失したときのためにリカバリーキーは必要ですか?
+addvaultwizard.new.generateRecoveryKeyChoice.yes=はい、後悔するより手段を用意します
+addvaultwizard.new.generateRecoveryKeyChoice.no=いいえ、パスワードをなくしません
### Information
addvault.new.readme.storageLocation.fileName=このディレクトリについて.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ 金庫ファイル ⚠️
-addvault.new.readme.storageLocation.2=ここは、金庫のストレージ先です。このディレクトリーのいずれに対しても変更を、{\\b 加えないでください} 。
+addvault.new.readme.storageLocation.2=ここは金庫のストレージ先です。このディレクトリーのいずれに対しても変更を、{\\b 加えないでください} 。
addvault.new.readme.storageLocation.3=Cryptomator を利用してファイルを暗号化するには、金庫を施錠して表示されるドライブを使用してください。
-addvault.new.readme.storageLocation.4=ヘルプが必要であれば、%s をお試しください。
+addvault.new.readme.storageLocation.4=ヘルプが必要であれば %s をお試しください。
addvault.new.readme.accessLocation.fileName=金庫へようこそ.rtf
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ 暗号ボリューム 🔐️
-addvault.new.readme.accessLocation.2=ここは、金庫のアクセス先です。Cryptomator により、このボリュームに追加したファイルが暗号化されます。これからは、Cryptomator アプリケーションから解錠してこのボリュームにアクセスしてください。
+addvault.new.readme.accessLocation.2=ここは、金庫のアクセス先です。Cryptomator によりこのボリュームに追加したファイルが暗号化されます。これからは Cryptomator アプリケーションから解錠してこのボリュームにアクセスしてください。
addvault.new.readme.accessLocation.3=このファイルはいつでも削除できます。
## Existing
addvaultwizard.existing.instruction=既存の金庫の "masterkey.cryptomator" ファイルを選択してください。
@@ -66,16 +68,12 @@ addvaultwizard.success.unlockNow=今すぐ解錠
# Remove Vault
removeVault.title=金庫を削除
-removeVault.information=この操作で、Cryptomator がこの金庫を認識しなくなります。あとで、再度追加することが可能です。暗号化されたファイルが、ドライブから削除されることはありません。
+removeVault.information=この操作で Cryptomator がこの金庫を認識しなくなります。あとで再度追加することが可能です。暗号化されたファイルがドライブから削除されることはありません。
removeVault.confirmBtn=金庫を削除
# Change Password
changepassword.title=パスワードの変更
changepassword.enterOldPassword="%s" の現在のパスワードを入力してください
-changepassword.enterNewPassword=金庫の新しいパスワードを入力してください
-changepassword.reenterNewPassword=新しいパスワードの確認
-changepassword.passwordsMatch=パスワードが一致しました!
-changepassword.passwordsDoNotMatch=パスワードが一致しません
changepassword.finalConfirmation=パスワードを忘れた場合に、データを復元できないことを理解しました
# Forget Password
@@ -91,6 +89,8 @@ unlock.unlockBtn=解錠
## Success
unlock.success.message="%s" の解錠に成功しました! 金庫にアクセス可能です。
unlock.success.revealBtn=金庫を表示
+## Invalid Mount Point
+unlock.error.invalidMountPoint=マウントポイントが空のディレクトリではありません: %s
# Migration
migration.title=金庫をアップグレード
@@ -109,8 +109,12 @@ preferences.title=設定
## General
preferences.general=基本設定
preferences.general.theme=外見 & 操作性
-preferences.general.startHidden=Cryptomator を開始したとき、ウィンドウを隠す
+preferences.general.startHidden=Cryptomator を開始したときウィンドウを隠す
preferences.general.debugLogging=ログを有効にする
+preferences.general.autoStart=システム開始時にCryptomatorを起動する
+preferences.general.interfaceOrientation=インターフェイスの向き
+preferences.general.interfaceOrientation.ltr=左から右
+preferences.general.interfaceOrientation.rtl=右から左
## Volume
preferences.volume=仮想ドライブ
preferences.volume.type=マウント方法
@@ -122,10 +126,19 @@ preferences.updates.currentVersion=現在のバージョン: %s
preferences.updates.autoUpdateCheck=自動的に更新を確認する
preferences.updates.checkNowBtn=今すぐ確認
preferences.updates.updateAvailable=利用可能なバージョン %s に更新します。
+## Donation Key
+preferences.donationKey=寄付
+preferences.donationKey.registeredFor=%s で登録されています
+preferences.donationKey.noDonationKey=有効な Donation Key が見つかりませんでした。ライセンスキーに似ていますがフリーソフトを使う寄付者向けのキーです。 ;-)
+preferences.donationKey.getDonationKey=Donation Key を入手する
# Main Window
main.closeBtn.tooltip=閉じる
main.preferencesBtn.tooltip=設定
+main.donationKeyMissing.tooltip=寄付をお願いします
+## Drag 'n' Drop
+main.dropZone.dropVault=この金庫を追加
+main.dropZone.unknownDragboardContent=このウィンドウにドラッグして、金庫を追加
## Vault List
main.vaultlist.emptyList.onboardingInstruction=ここをクリックして金庫を追加
main.vaultlist.contextMenu.remove=金庫を削除
@@ -152,7 +165,7 @@ main.vaultDetail.migratePrompt=金庫にアクセスする前に、 金庫を新
# Wrong File Alert
wrongFileAlert.title=不明なファイル
wrongFileAlert.btn=Ok
-wrongFileAlert.information=Cryptomator の金庫ではない、ファイルかフォルダーを追加しようとしました。データを暗号化するには、金庫を作成して施錠してください。
+wrongFileAlert.information=Cryptomator の金庫ではないファイルかフォルダーを追加しようとしました。データを暗号化するには金庫を作成して施錠してください。
# Vault Options
## General
@@ -165,14 +178,25 @@ vaultOptions.mount.readonly=読み取り専用
vaultOptions.mount.driveName=ドライブ名
vaultOptions.mount.customMountFlags=カスタム マウント フラグ
vaultOptions.mount.winDriveLetterOccupied=使用中
-vaultOptions.mount.winDirChooser=空のディレクトリを選択してください
+vaultOptions.mount.mountPoint=マウントポイント
+vaultOptions.mount.mountPoint.auto=自動的に適切な場所を選択する
+vaultOptions.mount.mountPoint.driveLetter=割り当てられるドライブ文字を使う
+vaultOptions.mount.mountPoint.custom=カスタムパス
+vaultOptions.mount.mountPoint.directoryPickerButton=選択...
+vaultOptions.mount.mountPoint.directoryPickerTitle=空のディレクトリを選択してください
# Recovery Key
recoveryKey.title=回復キー
-recoveryKey.enterPassword.prompt="%s" の回復キーを表示するために、パスワードを入力してください:
-recoveryKey.display.message="%s" の回復キー:
+recoveryKey.enterPassword.prompt="%s" の回復キーを表示するためのパスワードを入力してください:
+recoveryKey.display.message="%s" へのアクセス権限を復元するリカバリーキー:
+recoveryKey.display.StorageHints=とても安全な場所に保存してください、例えば:\n • パスワード管理ソフトに保存\n • USB フラッシュドライブに保存\n • 紙に印刷
-# Misc
+# New Password
+newPassword.promptText=新しいパスワードを入力してください
+newPassword.reenterPassword=新しいパスワードを確認
+newPassword.passwordsMatch=パスワードが一致しました!
+newPassword.passwordsDoNotMatch=パスワードが一致しません
+passwordStrength.messageLabel.tooShort=最低 %d 文字にしてください
passwordStrength.messageLabel.0=非常に弱い
passwordStrength.messageLabel.1=弱い
passwordStrength.messageLabel.2=普通
diff --git a/main/ui/src/main/resources/i18n/strings_ko.properties b/main/ui/src/main/resources/i18n/strings_ko.properties
index 619dae41e..33f48d067 100644
--- a/main/ui/src/main/resources/i18n/strings_ko.properties
+++ b/main/ui/src/main/resources/i18n/strings_ko.properties
@@ -6,12 +6,18 @@ generic.button.apply=적용
generic.button.back=뒤로
generic.button.cancel=취소
generic.button.change=변경
+generic.button.copy=복사
generic.button.done=완료
generic.button.next=다음
+generic.button.print=인쇄
+## Error
+generic.error.title=예기치 않은 오류가 발생되었습니다.
+generic.error.instruction=이러한 일은 일어나지 말아야 합니다. 아래의 텍스트와 이 오류를 발생시킨 단계에 대한 설명을 포함하여 제출하여 주십시요.
# Tray Menu
traymenu.showMainWindow=보기
traymenu.showPreferencesWindow=환경설정
+traymenu.lockAllVaults=모든 Vault 잠금
traymenu.quitApplication=종료
traymenu.vault.unlock=잠금해제
traymenu.vault.lock=잠금
@@ -33,18 +39,14 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=사용자 지정 위치
addvaultwizard.new.directoryPickerButton=선택
addvaultwizard.new.directoryPickerTitle=디렉터리 선택
-addvaultwizard.new.enterPassword=Vault의 비밀번호를 입력하십시요.
addvaultwizard.new.fileAlreadyExists=이미 다른 객체가 존재하고 있어 해당 경로에 Vault를 생성할 수 없습니다.
addvaultwizard.new.invalidName=유효하지 않은 Vault 이름입니다. 일반적인 디렉터리 이름으로 지정해주십시요.
addvaultwizard.new.ioException=선택된 디렉터리가 기본테스트에 실패하였습니다. Cryptomator가 적절한 권한을 가지고 있고 간섭을 받는 것은 없는지 확인하여 주십시요.
### Password
-addvaultwizard.new.reenterPassword=비밀번호 확인
-addvaultwizard.new.passwordsMatch=비밀번호 일치!
-addvaultwizard.new.passwordsDoNotMatch=비밀번호가 일치하지 않습니다.
addvaultwizard.new.createVaultBtn=Vault 생성
-### Recovery Key
-addvaultwizard.new.recoveryKeyInstruction=이것은 복구키입니다. 만일 비밀번호를 분실하셨을 때,데이터를 복구 할 수 있는 유일한 방법이므로, 안전하게 보관하시기 바랍니다.
-addvaultwizard.new.recoveryKeySavedCheckbox=예, 이 복구키를 안전하게 백업해두었습니다.
+addvaultwizard.new.generateRecoveryKeyChoice=비밀번호가 없으면 데이터에 접근할 수 없습니다. 비밀번호를 잊으신 경우 복구 키를 원하십니까?
+addvaultwizard.new.generateRecoveryKeyChoice.yes=네. 미안한 것 보단 안전함이 더 낫습니다.
+addvaultwizard.new.generateRecoveryKeyChoice.no=아니요, 나는 비밀번호를 잊지 않을겁니다.
### Information
addvault.new.readme.storageLocation.fileName=이 디렉터리에 대해.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ VAULT 파일 ⚠️
@@ -72,10 +74,6 @@ removeVault.confirmBtn=Vault 제거
# Change Password
changepassword.title=비밀번호 변경
changepassword.enterOldPassword="%s"의 비밀번호를 입력하여 주십시요.
-changepassword.enterNewPassword=Vault의 새 비밀번호를 입력하십시요.
-changepassword.reenterNewPassword=새 비밀번호 확인
-changepassword.passwordsMatch=비밀번호 일치!
-changepassword.passwordsDoNotMatch=비밀번호가 일치하지 않습니다.
changepassword.finalConfirmation=지정한 비밀번호를 잊어버리면 데이터를 복구할 수 없음을 알고있습니다.
# Forget Password
@@ -91,6 +89,8 @@ unlock.unlockBtn=잠금해제
## Success
unlock.success.message="%s"의 잠금해제가 성공적으로 수행되었습니다! 이제 이 Vault의 접근이 가능합니다.
unlock.success.revealBtn=Vault 표시
+## Invalid Mount Point
+unlock.error.invalidMountPoint=구성지점의 디렉터리가 비어있지 않습니다: %s
# Migration
migration.title=Vault 업그레이드
@@ -111,6 +111,10 @@ preferences.general=일반
preferences.general.theme=테마설정
preferences.general.startHidden=Cryptomator를 시작할 때 창 숨김
preferences.general.debugLogging=디버그 로그기록을 사용하도록 설정
+preferences.general.autoStart=시스템 시작 시 Cryptomator 실행
+preferences.general.interfaceOrientation=인터페이스 방향
+preferences.general.interfaceOrientation.ltr=왼쪽에서 오른쪽으로
+preferences.general.interfaceOrientation.rtl=오른쪽에서 왼쪽으로
## Volume
preferences.volume=가상 드라이브
preferences.volume.type=볼륨 유형
@@ -122,10 +126,19 @@ preferences.updates.currentVersion=현재 버전: %s
preferences.updates.autoUpdateCheck=자동으로 업데이트 확인
preferences.updates.checkNowBtn=지금 확인
preferences.updates.updateAvailable=버전 %s의 업데이트가 가능합니다.
+## Donation Key
+preferences.donationKey=기부하기
+preferences.donationKey.registeredFor=%s (으)로 등록되어 있습니다.
+preferences.donationKey.noDonationKey=유효한 도네이션(기부) 키를 찾지 못했습니다. 라이센스 키와 비슷하나, 무료 소프트웨어를 사용하는 멋진 사람들을 위한 것입니다. ;-)
+preferences.donationKey.getDonationKey=도네이션(기부) 키 얻기
# Main Window
main.closeBtn.tooltip=닫기
main.preferencesBtn.tooltip=환경설정
+main.donationKeyMissing.tooltip=기부를 부탁드립니다.
+## Drag 'n' Drop
+main.dropZone.dropVault=이 Vault를 추가
+main.dropZone.unknownDragboardContent=Vault를 추가하려면, 이 창에 드래그 하십시요.
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Vault를 추가하기 위해 이곳을 클릭합니다.
main.vaultlist.contextMenu.remove=Vault 제거
@@ -165,14 +178,25 @@ vaultOptions.mount.readonly=읽기 전용
vaultOptions.mount.driveName=드라이브 이름
vaultOptions.mount.customMountFlags=사용자 정의 매개변수 사용
vaultOptions.mount.winDriveLetterOccupied=사용됨
-vaultOptions.mount.winDirChooser=빈 디렉터리를 선택해 주십시요
+vaultOptions.mount.mountPoint=구성 지점
+vaultOptions.mount.mountPoint.auto=자동으로 적합한 위치를 선택
+vaultOptions.mount.mountPoint.driveLetter=드라이브 문자를 지정하여 사용
+vaultOptions.mount.mountPoint.custom=사용자 지정 경로
+vaultOptions.mount.mountPoint.directoryPickerButton=선택
+vaultOptions.mount.mountPoint.directoryPickerTitle=빈 디렉터리를 선택
# Recovery Key
recoveryKey.title=복구 키
recoveryKey.enterPassword.prompt="%s"의 복구 키를 표시하려면 비밀번호를 입력하여 주십시요.
-recoveryKey.display.message="%s"의 복구 키
+recoveryKey.display.message="%s" 데이터 접근을 복원하는데 사용 할 수 있는 복구 키 입니다:
+recoveryKey.display.StorageHints=매우 안전한곳에 보관하십시요. 예시:\n • 비밀번호 관리자를 사용하여 저장\n • USB 플래시 드라이브에 저장\n • 종이에 출력
-# Misc
+# New Password
+newPassword.promptText=새 비밀번호를 입력하십시요.
+newPassword.reenterPassword=새 비밀번호를 확인하여 주십시요.
+newPassword.passwordsMatch=비밀번호가 일치합니다!
+newPassword.passwordsDoNotMatch=비밀번호가 일치하지 않습니다.
+passwordStrength.messageLabel.tooShort=적어도 %d 자 이상 사용하여 주십시요.
passwordStrength.messageLabel.0=매우 취약함
passwordStrength.messageLabel.1=취약함
passwordStrength.messageLabel.2=보통
diff --git a/main/ui/src/main/resources/i18n/strings_nl.properties b/main/ui/src/main/resources/i18n/strings_nl.properties
index 4cba43945..0d66a7b96 100644
--- a/main/ui/src/main/resources/i18n/strings_nl.properties
+++ b/main/ui/src/main/resources/i18n/strings_nl.properties
@@ -6,12 +6,18 @@ generic.button.apply=Toepassen
generic.button.back=Terug
generic.button.cancel=Annuleren
generic.button.change=Wijzig
+generic.button.copy=Kopieer
generic.button.done=Klaar
generic.button.next=Volgende
+generic.button.print=Afdrukken
+## Error
+generic.error.title=Er is een onverwachte fout opgetreden
+generic.error.instruction=Dit had niet moeten gebeuren. Rapporteer de onderstaande fouttekst en voeg een beschrijving toe van de stappen die tot deze fout hebben geleid.
# Tray Menu
traymenu.showMainWindow=Toon
traymenu.showPreferencesWindow=Voorkeuren
+traymenu.lockAllVaults=Alles vergrendelen
traymenu.quitApplication=Afsluiten
traymenu.vault.unlock=Ontgrendel
traymenu.vault.lock=Vergrendel
@@ -33,18 +39,14 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Andere locatie
addvaultwizard.new.directoryPickerButton=Kies…
addvaultwizard.new.directoryPickerTitle=Selecteer map
-addvaultwizard.new.enterPassword=Voer een wachtwoord voor de kluis in
addvaultwizard.new.fileAlreadyExists=Er kan op deze locatie geen kluis aangemaakt worden, omdat er een bepaald object bestaat.
addvaultwizard.new.invalidName=Ongeldige kluisnaam. Overweeg een standaard mapnaam.
addvaultwizard.new.ioException=De geselecteerde map heeft een basistest niet gehaald. Zorg dat de map de juiste rechten heeft, en dat er niet conflicteert met Cryptomator.
### Password
-addvaultwizard.new.reenterPassword=Bevestig het wachtwoord
-addvaultwizard.new.passwordsMatch=Wachtwoorden komen overeen!
-addvaultwizard.new.passwordsDoNotMatch=Wachtwoorden komen niet overeen
addvaultwizard.new.createVaultBtn=Kluis aanmaken
-### Recovery Key
-addvaultwizard.new.recoveryKeyInstruction=Dit is uw herstelsleutel. Bewaar deze goed, het is de enige mogelijkheid om uw data te herstellen als u uw wachtwoord verliest.
-addvaultwizard.new.recoveryKeySavedCheckbox=Ja, ik heb een beveiligde back-up gemaakt van deze herstelsleutel
+addvaultwizard.new.generateRecoveryKeyChoice=Zonder wachtwoord hebt u geen toegang tot uw data. Wilt u een herstelsleutel voor het geval u uw wachtwoord verliest?
+addvaultwizard.new.generateRecoveryKeyChoice.yes=Ja graag, voorkomen is beter dan genezen
+addvaultwizard.new.generateRecoveryKeyChoice.no=Nee bedankt, ik verlies mijn wachtwoord niet
### Information
addvault.new.readme.storageLocation.fileName=WAT IS DEZE MAP.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ KLUIS BESTANDEN ⚠️
@@ -72,10 +74,6 @@ removeVault.confirmBtn=Verwijder Kluis
# Change Password
changepassword.title=Wijzig wachtwoord
changepassword.enterOldPassword=Voer het huidige wachtwoord voor "%s" in
-changepassword.enterNewPassword=Voer een nieuw wachtwoord voor de kluis in
-changepassword.reenterNewPassword=Bevestig het nieuwe wachtwoord
-changepassword.passwordsMatch=Wachtwoorden komen overeen!
-changepassword.passwordsDoNotMatch=Wachtwoorden komen niet overeen
changepassword.finalConfirmation=Ik begrijp dat ik mijn gegevens niet kan herstellen als ik mijn wachtwoord vergeet
# Forget Password
@@ -91,6 +89,8 @@ unlock.unlockBtn=Ontgrendel
## Success
unlock.success.message="%s" is met succes ontgrendeld! Uw kluis is nu toegankelijk.
unlock.success.revealBtn=Toon kluis
+## Invalid Mount Point
+unlock.error.invalidMountPoint=Koppelpunt is geen lege map: %s
# Migration
migration.title=Kluis upgraden
@@ -111,6 +111,10 @@ preferences.general=Algemeen
preferences.general.theme=Uiterlijk
preferences.general.startHidden=Verberg venster bij het opstarten van Cryptomator
preferences.general.debugLogging=Debug logging aanzetten
+preferences.general.autoStart=Start Cryptomator als het systeem opstart
+preferences.general.interfaceOrientation=Interface oriëntatie
+preferences.general.interfaceOrientation.ltr=Links naar rechts
+preferences.general.interfaceOrientation.rtl=Rechts naar links
## Volume
preferences.volume=Virtuele schijf
preferences.volume.type=Type volume
@@ -122,10 +126,19 @@ preferences.updates.currentVersion=Huidige Versie: "%s"
preferences.updates.autoUpdateCheck=Automatisch controleren op updates
preferences.updates.checkNowBtn=Controleer nu
preferences.updates.updateAvailable=Update naar versie "%s" beschikbaar.
+## Donation Key
+preferences.donationKey=Donatie
+preferences.donationKey.registeredFor=Geregistreerd voor %s
+preferences.donationKey.noDonationKey=Geen geldige donatiesleutel gevonden. Het is als een licentiesleutel, maar dan voor geweldige mensen die gratis software gebruiken. ;-)
+preferences.donationKey.getDonationKey=Verkrijg een donatiesleutel
# Main Window
main.closeBtn.tooltip=Sluiten
main.preferencesBtn.tooltip=Voorkeuren
+main.donationKeyMissing.tooltip=Overweeg alstublieft om een donatie te doen
+## Drag 'n' Drop
+main.dropZone.dropVault=Voeg deze kluis toe
+main.dropZone.unknownDragboardContent=Als u een kluis wilt toevoegen, sleep deze dan naar dit venster
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Klik hier om een kluis toe te voegen
main.vaultlist.contextMenu.remove=Verwijder Kluis
@@ -165,14 +178,25 @@ vaultOptions.mount.readonly=Alleen-Lezen
vaultOptions.mount.driveName=Schijfnaam
vaultOptions.mount.customMountFlags=Aangepaste Aankoppel Parameters
vaultOptions.mount.winDriveLetterOccupied=in gebruik
-vaultOptions.mount.winDirChooser=Kies een lege map
+vaultOptions.mount.mountPoint=Koppelpunt
+vaultOptions.mount.mountPoint.auto=Kies automatisch een geschikte locatie
+vaultOptions.mount.mountPoint.driveLetter=Gebruik de toegewezen schijfletter
+vaultOptions.mount.mountPoint.custom=Aangepast pad
+vaultOptions.mount.mountPoint.directoryPickerButton=Kies…
+vaultOptions.mount.mountPoint.directoryPickerTitle=Kies een lege map
# Recovery Key
recoveryKey.title=Herstelsleutel
recoveryKey.enterPassword.prompt=Voer uw wachtwoord in om de herstelsleutel voor "%s" te tonen:
-recoveryKey.display.message=Herstelsleutel voor "%s":
+recoveryKey.display.message=De volgende herstelsleutel kan worden gebruikt om "%s" te herstellen:
+recoveryKey.display.StorageHints=Bewaar het op een veilige plek, bv:\n • Bewaar het in een wachtwoordmanager\n • Sla het op op een USB-stick\n • Print het op papier
-# Misc
+# New Password
+newPassword.promptText=Voer een nieuw wachtwoord in
+newPassword.reenterPassword=Bevestig het nieuwe wachtwoord
+newPassword.passwordsMatch=Wachtwoorden komen overeen!
+newPassword.passwordsDoNotMatch=Wachtwoorden komen niet overeen
+passwordStrength.messageLabel.tooShort=Gebruik ten minste %d tekens
passwordStrength.messageLabel.0=Zeer zwak
passwordStrength.messageLabel.1=Zwak
passwordStrength.messageLabel.2=Redelijk
diff --git a/main/ui/src/main/resources/i18n/strings_pt.properties b/main/ui/src/main/resources/i18n/strings_pt.properties
index 6f0caec0b..fd4979118 100644
--- a/main/ui/src/main/resources/i18n/strings_pt.properties
+++ b/main/ui/src/main/resources/i18n/strings_pt.properties
@@ -2,6 +2,7 @@
# Generics
## Button
+## Error
# Tray Menu
@@ -11,7 +12,6 @@
### Name
### Location
### Password
-### Recovery Key
### Information
## Existing
## Success
@@ -24,6 +24,7 @@
# Unlock
## Success
+## Invalid Mount Point
# Migration
## Start
@@ -34,8 +35,10 @@
## General
## Volume
## Updates
+## Donation Key
# Main Window
+## Drag 'n' Drop
## Vault List
## Vault Detail
### Locked
@@ -50,6 +53,6 @@
# Recovery Key
-# Misc
+# New Password
# Quit
diff --git a/main/ui/src/main/resources/i18n/strings_pt_BR.properties b/main/ui/src/main/resources/i18n/strings_pt_BR.properties
index 44a7c93bc..155b2851b 100644
--- a/main/ui/src/main/resources/i18n/strings_pt_BR.properties
+++ b/main/ui/src/main/resources/i18n/strings_pt_BR.properties
@@ -6,12 +6,18 @@ generic.button.apply=Aplicar
generic.button.back=Voltar
generic.button.cancel=Cancelar
generic.button.change=Alterar
+generic.button.copy=Copiar
generic.button.done=Pronto
generic.button.next=Próximo
+generic.button.print=Imprimir
+## Error
+generic.error.title=Ocorreu um erro inesperado
+generic.error.instruction=Isso não deveria ter ocorrido. Por favor, reporte o texto do erro abaixo e inclua uma descrição dos passos que levaram a este erro.
# Tray Menu
traymenu.showMainWindow=Exibir
traymenu.showPreferencesWindow=Preferências
+traymenu.lockAllVaults=Bloquear todos
traymenu.quitApplication=Sair
traymenu.vault.unlock=Desbloquear
traymenu.vault.lock=Bloquear
@@ -33,17 +39,14 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Local Personalizado
addvaultwizard.new.directoryPickerButton=Escolher…
addvaultwizard.new.directoryPickerTitle=Selecionar Diretório
-addvaultwizard.new.enterPassword=Digite uma senha para o cofre
addvaultwizard.new.fileAlreadyExists=O cofre não pode ser criado neste local porque algum objeto já existe.
addvaultwizard.new.invalidName=Nome de cofre inválido. Por favor considere usar um nome de diretório comum.
+addvaultwizard.new.ioException=O diretório selecionado falhou em um teste básico. Certifique-se de ter os direitos de acesso apropriados e que nada interfira com o Cryptomator.
### Password
-addvaultwizard.new.reenterPassword=Confirme a senha
-addvaultwizard.new.passwordsMatch=As senhas são idênticas!
-addvaultwizard.new.passwordsDoNotMatch=As senhas são diferentes
addvaultwizard.new.createVaultBtn=Criar Cofre
-### Recovery Key
-addvaultwizard.new.recoveryKeyInstruction=Esta é a sua chave de recuperação. Mantenha-a segura, é sua única chance de recuperar seus dados se você perder sua senha.
-addvaultwizard.new.recoveryKeySavedCheckbox=Sim, fiz um backup seguro desta chave de recuperação
+addvaultwizard.new.generateRecoveryKeyChoice=Você não será capaz de acessar seus dados sem sua senha. Você quer uma chave de recuperação para o caso de perder sua senha?
+addvaultwizard.new.generateRecoveryKeyChoice.yes=Sim, por favor, melhor seguro do que arrependido
+addvaultwizard.new.generateRecoveryKeyChoice.no=Não, obrigado, eu não vou perder minha senha
### Information
addvault.new.readme.storageLocation.fileName=O QUE É ESTE DIRETÓRIO.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ ARQUIVOS DO COFRE ⚠️
@@ -71,10 +74,6 @@ removeVault.confirmBtn=Remover Cofre
# Change Password
changepassword.title=Alterar Senha
changepassword.enterOldPassword=Digite a senha atual para "%s"
-changepassword.enterNewPassword=Digite uma nova senha para o seu cofre
-changepassword.reenterNewPassword=Confirme a nova senha
-changepassword.passwordsMatch=As senhas são idênticas!
-changepassword.passwordsDoNotMatch=As senhas são diferentes
changepassword.finalConfirmation=Eu entendo que não poderei recuperar meus dados se esquecer minha senha
# Forget Password
@@ -90,6 +89,8 @@ unlock.unlockBtn=Desbloquear
## Success
unlock.success.message="%s" foi desbloqueado com sucesso! Seu cofre agora está acessível.
unlock.success.revealBtn=Revelar Cofre
+## Invalid Mount Point
+unlock.error.invalidMountPoint=O ponto de montagem não é um diretório vazio: %s
# Migration
migration.title=Atualizar Cofre
@@ -110,6 +111,10 @@ preferences.general=Geral
preferences.general.theme=Aparência
preferences.general.startHidden=Ocultar janela ao iniciar o Cryptomator
preferences.general.debugLogging=Ativar log de debug
+preferences.general.autoStart=Iniciar o Cryptomator durante inicialização do sistema
+preferences.general.interfaceOrientation=Orientação da interface
+preferences.general.interfaceOrientation.ltr=Da esquerda para a direita
+preferences.general.interfaceOrientation.rtl=Da direita para a esquerda
## Volume
preferences.volume=Volume Virtual
preferences.volume.type=Tipo de Volume
@@ -121,10 +126,19 @@ preferences.updates.currentVersion=Versão atual: %s
preferences.updates.autoUpdateCheck=Buscar atualizações automaticamente
preferences.updates.checkNowBtn=Buscar Agora
preferences.updates.updateAvailable=Atualizar para versão %s disponível.
+## Donation Key
+preferences.donationKey=Doação
+preferences.donationKey.registeredFor=Registrado para %s
+preferences.donationKey.noDonationKey=Nenhuma chave de doação válida encontrada. É como uma chave de licença, mas para pessoas incríveis usando software gratuito. ;-)
+preferences.donationKey.getDonationKey=Obtenha uma chave de doação
# Main Window
main.closeBtn.tooltip=Fechar
main.preferencesBtn.tooltip=Preferências
+main.donationKeyMissing.tooltip=Por favor, considere uma doação
+## Drag 'n' Drop
+main.dropZone.dropVault=Adicionar este cofre
+main.dropZone.unknownDragboardContent=Se você quer adicionar um cofre, arraste-o para esta janela
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Clique aqui para adicionar um cofre
main.vaultlist.contextMenu.remove=Remover Cofre
@@ -146,6 +160,7 @@ main.vaultDetail.throughput.kbps=%.1f kiB/s
main.vaultDetail.throughput.mbps=%.1f MiB/s
### Needs Migration
main.vaultDetail.migrateButton=Atualizar Cofre
+main.vaultDetail.migratePrompt=Seu cofre precisa ser atualizado para um novo formato antes de poder acessá-lo
# Wrong File Alert
wrongFileAlert.title=Arquivo Desconhecido
@@ -162,13 +177,26 @@ vaultOptions.mount=Montagem
vaultOptions.mount.readonly=Somente Leitura
vaultOptions.mount.driveName=Nome do Volume
vaultOptions.mount.customMountFlags=Flags Personalizadas de Montagem
+vaultOptions.mount.winDriveLetterOccupied=ocupado
+vaultOptions.mount.mountPoint=Ponto de Montagem
+vaultOptions.mount.mountPoint.auto=Escolher automaticamente um local adequado
+vaultOptions.mount.mountPoint.driveLetter=Usar letra de unidade atribuída
+vaultOptions.mount.mountPoint.custom=Caminho personalizado
+vaultOptions.mount.mountPoint.directoryPickerButton=Escolher…
+vaultOptions.mount.mountPoint.directoryPickerTitle=Escolha um diretório vazio
# Recovery Key
recoveryKey.title=Chave de recuperação
recoveryKey.enterPassword.prompt=Digite sua senha para mostrar a chave de recuperação de "%s":
-recoveryKey.display.message=Chave de recuperação de "%s":
+recoveryKey.display.message=A seguinte chave de recuperação pode ser usada para restaurar o acesso a "%s":
+recoveryKey.display.StorageHints=Mantenha-a em um lugar bem seguro, por exemplo:\n • Guarde num gerenciador de senhas\n • Grave num flash drive USB\n • Imprima, ou escreva, num papel
-# Misc
+# New Password
+newPassword.promptText=Digite a nova senha
+newPassword.reenterPassword=Confirme a nova senha
+newPassword.passwordsMatch=As senhas são idênticas!
+newPassword.passwordsDoNotMatch=As senhas não coincidem
+passwordStrength.messageLabel.tooShort=Use pelo menos %d caracteres
passwordStrength.messageLabel.0=Muito fraca
passwordStrength.messageLabel.1=Fraca
passwordStrength.messageLabel.2=Média
@@ -177,3 +205,4 @@ passwordStrength.messageLabel.4=Muito forte
# Quit
quit.prompt=Sair da aplicação? Existem cofres desbloqueados.
+quit.lockAndQuit=Bloquear e Sair
diff --git a/main/ui/src/main/resources/i18n/strings_ru.properties b/main/ui/src/main/resources/i18n/strings_ru.properties
index 7b393f8c1..0f591fe10 100644
--- a/main/ui/src/main/resources/i18n/strings_ru.properties
+++ b/main/ui/src/main/resources/i18n/strings_ru.properties
@@ -6,12 +6,18 @@ generic.button.apply=Применить
generic.button.back=Назад
generic.button.cancel=Отмена
generic.button.change=Изменить
+generic.button.copy=Копировать
generic.button.done=Готово
generic.button.next=Далее
+generic.button.print=Распечатать
+## Error
+generic.error.title=Произошла непредвиденная ошибка
+generic.error.instruction=Этого не должно было произойти. Пожалуйста, создайте отчет об ошибке ниже и опишите, какие шаги к ней привели.
# Tray Menu
traymenu.showMainWindow=Показать
traymenu.showPreferencesWindow=Настройки
+traymenu.lockAllVaults=Заблокировать всё
traymenu.quitApplication=Выход
traymenu.vault.unlock=Разблокировать
traymenu.vault.lock=Заблокировать
@@ -30,21 +36,17 @@ addvaultwizard.new.namePrompt=Имя хранилища
addvaultwizard.new.locationInstruction=Где Cryptomator должен хранить зашифрованные файлы вашего хранилища?
addvaultwizard.new.locationLabel=Место хранения
addvaultwizard.new.locationPrompt=…
-addvaultwizard.new.directoryPickerLabel=Своё место
+addvaultwizard.new.directoryPickerLabel=Пользовательское расположение
addvaultwizard.new.directoryPickerButton=Выбрать…
addvaultwizard.new.directoryPickerTitle=Выберите каталог
-addvaultwizard.new.enterPassword=Введите пароль для хранилища
addvaultwizard.new.fileAlreadyExists=Хранилище не может быть создано по этому пути, потому что некоторые объекты уже существуют.
addvaultwizard.new.invalidName=Неверное имя хранилища. Укажите корректное имя каталога.
addvaultwizard.new.ioException=Выбранный каталог не прошел начальную проверку. Убедитесь, что у вас есть права доступа и ничего не мешает Cryptomator.
### Password
-addvaultwizard.new.reenterPassword=Подтвердите пароль
-addvaultwizard.new.passwordsMatch=Пароли совпадают
-addvaultwizard.new.passwordsDoNotMatch=Пароли не совпадают
addvaultwizard.new.createVaultBtn=Создать хранилище
-### Recovery Key
-addvaultwizard.new.recoveryKeyInstruction=Это ключ восстановления. Держите его в надёжном месте - это единственный шанс восстановить данные, если вы потеряете пароль.
-addvaultwizard.new.recoveryKeySavedCheckbox=Да, у меня есть резервная копия этого ключа восстановления
+addvaultwizard.new.generateRecoveryKeyChoice=Вы не сможете получить доступ к своим данным без пароля. Хотите ли вы создать ключ для восстановления на случай потери пароля?
+addvaultwizard.new.generateRecoveryKeyChoice.yes=Да, пожалуй, лучше так, чем потом сожалеть
+addvaultwizard.new.generateRecoveryKeyChoice.no=Нет, спасибо, я не потеряю свой пароль
### Information
addvault.new.readme.storageLocation.fileName=ЧТО ЗА КАТАЛОГ.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ ФАЙЛЫ ХРАНИЛИЩ ⚠️
@@ -66,21 +68,17 @@ addvaultwizard.success.unlockNow=Разблокировать
# Remove Vault
removeVault.title=Удалить хранилище
-removeVault.information=Cryptomator просто забудет это хранилище. Позже вы можете добавить его снова. Зашифрованные файлы не будут удалены с жёсткого диска.
+removeVault.information=Cryptomator просто забудет это хранилище. Вы можете добавить его снова позже. Зашифрованные файлы не будут удалены с жесткого диска.
removeVault.confirmBtn=Удалить хранилище
# Change Password
changepassword.title=Изменить пароль
changepassword.enterOldPassword=Введите текущий пароль для "%s"
-changepassword.enterNewPassword=Введите новый пароль для хранилища
-changepassword.reenterNewPassword=Подтвердите новый пароль
-changepassword.passwordsMatch=Пароли совпадают
-changepassword.passwordsDoNotMatch=Пароли не совпадают
changepassword.finalConfirmation=Я понимаю, что не смогу восстановить свои данные, если забуду пароль
# Forget Password
forgetPassword.title=Не помню пароль
-forgetPassword.information=Сохранённый пароль от этого хранилища будет удалён из вашей связки ключей.
+forgetPassword.information=Это удалит сохраненный пароль от этого хранилища из вашего хранилища ключей.
forgetPassword.confirmBtn=Не помню пароль
# Unlock
@@ -91,12 +89,14 @@ unlock.unlockBtn=Разблокировать
## Success
unlock.success.message=Разблокировка "%s" успешно выполнена! Доступ в хранилище открыт.
unlock.success.revealBtn=Показать хранилище
+## Invalid Mount Point
+unlock.error.invalidMountPoint=Точка монтирования не является пустым каталогом: %s
# Migration
migration.title=Обновить хранилище
## Start
migration.start.prompt=Хранилище "%s" нужно обновить до более нового формата. Прежде чем продолжить, убедитесь, что нет отложенной синхронизации, которая может повлиять на хранилище.
-migration.start.confirm=Да, моё хранилище полностью синхронизировано
+migration.start.confirm=Да, мое хранилище полностью синхронизировано
## Run
migration.run.enterPassword=Введите пароль для "%s"
migration.run.startMigrationBtn=Перенести хранилище
@@ -111,6 +111,10 @@ preferences.general=Общие
preferences.general.theme=Оформление
preferences.general.startHidden=Скрывать окно при запуске Cryptomator
preferences.general.debugLogging=Вести журнал отладки
+preferences.general.autoStart=Запускать Cryptomator при старте системы
+preferences.general.interfaceOrientation=Ориентация интерфейса
+preferences.general.interfaceOrientation.ltr=Слева направо
+preferences.general.interfaceOrientation.rtl=Справа налево
## Volume
preferences.volume=Виртуальный диск
preferences.volume.type=Тип тома
@@ -122,10 +126,19 @@ preferences.updates.currentVersion=Текущая версия: %s
preferences.updates.autoUpdateCheck=Автоматически проверять наличие обновлений
preferences.updates.checkNowBtn=Проверить
preferences.updates.updateAvailable=Доступно обновление до версии %s.
+## Donation Key
+preferences.donationKey=Пожертвование
+preferences.donationKey.registeredFor=Зарегистрирован для %s
+preferences.donationKey.noDonationKey=Не найден ключ пожертвования. Он напоминает лицензионный ключ, но для удивительных людей, использующих свободное ПО ;-)
+preferences.donationKey.getDonationKey=Получить ключ пожертвования
# Main Window
main.closeBtn.tooltip=Закрыть
main.preferencesBtn.tooltip=Настройки
+main.donationKeyMissing.tooltip=Пожалуйста, обдумайте возможность поддержать приложение
+## Drag 'n' Drop
+main.dropZone.dropVault=Добавить это хранилище
+main.dropZone.unknownDragboardContent=Если вы хотите добавить хранилище, перетащите его в это окно
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Нажмите здесь, чтобы добавить хранилище
main.vaultlist.contextMenu.remove=Удалить хранилище
@@ -165,14 +178,25 @@ vaultOptions.mount.readonly=Только чтение
vaultOptions.mount.driveName=Имя диска
vaultOptions.mount.customMountFlags=Свои флаги монтирования
vaultOptions.mount.winDriveLetterOccupied=занято
-vaultOptions.mount.winDirChooser=Выберите пустой каталог
+vaultOptions.mount.mountPoint=Точка монтирования
+vaultOptions.mount.mountPoint.auto=Автоматически выбирать подходящую
+vaultOptions.mount.mountPoint.driveLetter=Использовать назначенную букву диска
+vaultOptions.mount.mountPoint.custom=Пользовательский путь
+vaultOptions.mount.mountPoint.directoryPickerButton=Выбрать…
+vaultOptions.mount.mountPoint.directoryPickerTitle=Выберите пустой каталог
# Recovery Key
recoveryKey.title=Ключ восстановления
recoveryKey.enterPassword.prompt=Введите пароль, чтобы показать ключ для "%s":
-recoveryKey.display.message=Ключ восстановления для "%s":
+recoveryKey.display.message=Следующий ключ восстановления можно использовать для восстановления доступа к "%s":
+recoveryKey.display.StorageHints=Храните его в надежном месте, например:\n • в менеджере паролей\n • на флэш-накопителе USB\n • распечатайте на бумаге
-# Misc
+# New Password
+newPassword.promptText=Введите новый пароль
+newPassword.reenterPassword=Подтвердите новый пароль
+newPassword.passwordsMatch=Пароли совпадают!
+newPassword.passwordsDoNotMatch=Пароли не совпадают
+passwordStrength.messageLabel.tooShort=Используйте не менее %d символов
passwordStrength.messageLabel.0=Очень слабый
passwordStrength.messageLabel.1=Слабый
passwordStrength.messageLabel.2=Хороший
diff --git a/main/ui/src/main/resources/i18n/strings_sv.properties b/main/ui/src/main/resources/i18n/strings_sv.properties
index 9a0d841c1..276e240fe 100644
--- a/main/ui/src/main/resources/i18n/strings_sv.properties
+++ b/main/ui/src/main/resources/i18n/strings_sv.properties
@@ -6,12 +6,18 @@ generic.button.apply=Verkställ
generic.button.back=Bakåt
generic.button.cancel=Avbryt
generic.button.change=Ändra
+generic.button.copy=Kopiera
generic.button.done=Klar
generic.button.next=Nästa
+generic.button.print=Skriv ut
+## Error
+generic.error.title=Ett oväntat fel uppstod
+generic.error.instruction=Det här borde inte ha hänt. Vänligen rapportera feltexten nedan och inkludera en beskrivning av vilka steg som ledde till detta fel.
# Tray Menu
traymenu.showMainWindow=Visa
traymenu.showPreferencesWindow=Inställningar
+traymenu.lockAllVaults=Lås Alla
traymenu.quitApplication=Avsluta
traymenu.vault.unlock=Lås upp
traymenu.vault.lock=Lås
@@ -33,17 +39,14 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Anpassad plats
addvaultwizard.new.directoryPickerButton=Välj…
addvaultwizard.new.directoryPickerTitle=Välj katalog
-addvaultwizard.new.enterPassword=Ange ett lösenord för valvet
-addvaultwizard.new.fileAlreadyExists=Valvet kan inte skapas på denna plats då vissa filer redan finns.
+addvaultwizard.new.fileAlreadyExists=Valvet kan inte skapas på denna plats då vissa filer redan existerar.
addvaultwizard.new.invalidName=Felaktigt namn på valvet. Vänligen ange ett vanligt katalognamn.
+addvaultwizard.new.ioException=En inledande kontroll av angiven katalog misslyckades. Kontrollera att du har tillräcklig åtkomst till den, och att inget stör Cryptomator.
### Password
-addvaultwizard.new.reenterPassword=Bekräfta lösenordet
-addvaultwizard.new.passwordsMatch=Lösenorden stämmer överens!
-addvaultwizard.new.passwordsDoNotMatch=Lösenorden stämmer inte överens
addvaultwizard.new.createVaultBtn=Skapa Valv
-### Recovery Key
-addvaultwizard.new.recoveryKeyInstruction=Detta är din återställningsnyckel. Förvara den väl, den är din enda chans att återställa din data om du tappar bort ditt lösenord.
-addvaultwizard.new.recoveryKeySavedCheckbox=Ja, jag har säkerhetskopierat återställningsnyckeln
+addvaultwizard.new.generateRecoveryKeyChoice=Du kommer inte ha tillgång till din data utan ditt lösenord. Vill du skapa en återställningsnyckel, ifall du tappar bort ditt lösenord?
+addvaultwizard.new.generateRecoveryKeyChoice.yes=Ja tack. För säkerhets skull
+addvaultwizard.new.generateRecoveryKeyChoice.no=Nej tack. Här slarvas inga lösenord bort
### Information
addvault.new.readme.storageLocation.fileName=VAD ÄR DETTA FÖR KATALOG.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ VALV-FILER ⚠️
@@ -71,10 +74,6 @@ removeVault.confirmBtn=Ta bort valv
# Change Password
changepassword.title=Ändra lösenord
changepassword.enterOldPassword=Ange nuvarande lösenord för "%s"
-changepassword.enterNewPassword=Ange ett nytt lösenord för ditt valv
-changepassword.reenterNewPassword=Bekräfta det nya lösenordet
-changepassword.passwordsMatch=Lösenorden stämmer överens!
-changepassword.passwordsDoNotMatch=Lösenorden stämmer inte överens
changepassword.finalConfirmation=Jag förstår att jag inte kommer kunna återställa mitt data om jag glömmer mitt lösenord
# Forget Password
@@ -90,6 +89,8 @@ unlock.unlockBtn=Lås upp
## Success
unlock.success.message="%s" upplåst! Ditt valv är nu åtkomligt.
unlock.success.revealBtn=Visa valv
+## Invalid Mount Point
+unlock.error.invalidMountPoint=Monteringspunkten är inte en tom katalog: %s
# Migration
migration.title=Uppgradera valv
@@ -110,6 +111,7 @@ preferences.general=Allmänt
preferences.general.theme=Utseende
preferences.general.startHidden=Dölj fönster när Cryptomator startar
preferences.general.debugLogging=Aktivera debug loggning
+preferences.general.autoStart=Starta Cryptomator vid systemstart
## Volume
preferences.volume=Virtuell enhet
preferences.volume.type=Volym-typ
@@ -121,10 +123,19 @@ preferences.updates.currentVersion=Nuvarande version: %s
preferences.updates.autoUpdateCheck=Kontrollera uppdateringar automatiskt
preferences.updates.checkNowBtn=Kontrollera nu
preferences.updates.updateAvailable=Uppdatering till version %s finns tillgänglig.
+## Donation Key
+preferences.donationKey=Donation
+preferences.donationKey.registeredFor=Registrerad till %s
+preferences.donationKey.noDonationKey=Ingen giltig donationsnyckel funnen. Det är som en licensnyckel fast för grymma personer som kör fri mjukvara ;-)
+preferences.donationKey.getDonationKey=Skaffa en donationsnyckel
# Main Window
main.closeBtn.tooltip=Stäng
main.preferencesBtn.tooltip=Inställningar
+main.donationKeyMissing.tooltip=Överväg gärna en donation
+## Drag 'n' Drop
+main.dropZone.dropVault=Lägg till detta valv
+main.dropZone.unknownDragboardContent=Om du vill lägga till ett valv, dra in det till detta fönster
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Klicka här för att lägga till valv
main.vaultlist.contextMenu.remove=Ta bort valv
@@ -164,14 +175,25 @@ vaultOptions.mount.readonly=Skrivskyddad
vaultOptions.mount.driveName=Enhetsnamn
vaultOptions.mount.customMountFlags=Egna monterings-flaggor
vaultOptions.mount.winDriveLetterOccupied=upptagen
-vaultOptions.mount.winDirChooser=Välj en tom katalog
+vaultOptions.mount.mountPoint=Monteringspunkt
+vaultOptions.mount.mountPoint.auto=Välj en lämplig plats automatiskt
+vaultOptions.mount.mountPoint.driveLetter=Använd tilldelad enhetsbokstav
+vaultOptions.mount.mountPoint.custom=Anpassad sökväg
+vaultOptions.mount.mountPoint.directoryPickerButton=Välj…
+vaultOptions.mount.mountPoint.directoryPickerTitle=Välj en tom katalog
# Recovery Key
recoveryKey.title=Återställningsnyckel
recoveryKey.enterPassword.prompt=Ange ditt lösenord för att visa återställningsnyckeln för "%s":
-recoveryKey.display.message=Återställningsnyckel för "%s":
+recoveryKey.display.message=Använd denna återställningsnyckel för att återställa åtkomst till "%s":
+recoveryKey.display.StorageHints=Spara den på en säker plats, t.ex:\n • I en lösenordshanterare\n • På ett USB-minne (förvara säkert) \n • Skriv ut på ett papper (förvara säkert)
-# Misc
+# New Password
+newPassword.promptText=Ange ett nytt lösenord
+newPassword.reenterPassword=Bekräfta nytt lösenord
+newPassword.passwordsMatch=Lösenorden överensstämmer!
+newPassword.passwordsDoNotMatch=Lösenorden överensstämmer inte
+passwordStrength.messageLabel.tooShort=Ange minst %d tecken
passwordStrength.messageLabel.0=Mycket svagt
passwordStrength.messageLabel.1=Svagt
passwordStrength.messageLabel.2=Skapligt
diff --git a/main/ui/src/main/resources/i18n/strings_tr.properties b/main/ui/src/main/resources/i18n/strings_tr.properties
index f3fce6a7e..476742c81 100644
--- a/main/ui/src/main/resources/i18n/strings_tr.properties
+++ b/main/ui/src/main/resources/i18n/strings_tr.properties
@@ -6,12 +6,18 @@ generic.button.apply=Uygula
generic.button.back=Geri
generic.button.cancel=İptal
generic.button.change=Değiştir
+generic.button.copy=Kopyala
generic.button.done=Bitti
generic.button.next=İleri
+generic.button.print=Yazdır
+## Error
+generic.error.title=Beklenmedik bir hata oluştu
+generic.error.instruction=Bu olmamalıydı. Lütfen aşağıdaki hata metnini ve bu hatayı aldığınızda hangi adımları uyguladığınızı rapor edin.
# Tray Menu
traymenu.showMainWindow=Göster
traymenu.showPreferencesWindow=Seçenekler
+traymenu.lockAllVaults=Hepsini Kilitle
traymenu.quitApplication=Çık
traymenu.vault.unlock=Kilidi Aç
traymenu.vault.lock=Kilitle
@@ -33,18 +39,14 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Özel Konum
addvaultwizard.new.directoryPickerButton=Seç…
addvaultwizard.new.directoryPickerTitle=Dizin Seç
-addvaultwizard.new.enterPassword=Kasa için bir şifre girin
addvaultwizard.new.fileAlreadyExists=Bazı nesneler varolduğu için kasa bu yolda oluşturulamıyor.
addvaultwizard.new.invalidName=Geçersiz kasa adı. Lütfen normal bir dizin adı kullanın.
addvaultwizard.new.ioException=Seçilen dizin temel bir testte başarısız oldu. Uygun erişim haklarına sahip olduğunuzdan ve hiçbir şeyin Cryptomator'u engellemediğinden emin olun.
### Password
-addvaultwizard.new.reenterPassword=Şifreyi onayla
-addvaultwizard.new.passwordsMatch=Şifreler eşleşti!
-addvaultwizard.new.passwordsDoNotMatch=Şifreler eşleşmiyor
addvaultwizard.new.createVaultBtn=Kasa Oluştur
-### Recovery Key
-addvaultwizard.new.recoveryKeyInstruction=111/5000\nBu senin kurtarma anahtarın. Güvende tutun, şifrenizi kaybederseniz verilerinizi kurtarmak için tek şansınız bu.
-addvaultwizard.new.recoveryKeySavedCheckbox=Evet, bu kurtarma anahtarının güvenli bir şekilde yedeğini aldım
+addvaultwizard.new.generateRecoveryKeyChoice=Şifreniz olmadan verilerinize erişemeyeceksiniz. Şifrenizi kaybetmeniz durumunda kullanabileceğiniz bir kurtarma anahtarı ister misiniz?
+addvaultwizard.new.generateRecoveryKeyChoice.yes=Evet lütfen, kafamı taşlara vurmaktan iyidir
+addvaultwizard.new.generateRecoveryKeyChoice.no=Hayır teşekkürler, şifremi kaybetmeyeceğim
### Information
addvault.new.readme.storageLocation.fileName=BU DİZİN NEDİR.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ KASA DOSYALARI ⚠️
@@ -72,10 +74,6 @@ removeVault.confirmBtn=Kasayı Sil
# Change Password
changepassword.title=Şifreyi Değiştir
changepassword.enterOldPassword="%s" için şuanki şifreyi gir
-changepassword.enterNewPassword=Kasanız için yeni bir şifre girin
-changepassword.reenterNewPassword=Yeni şifreyi onaylayın
-changepassword.passwordsMatch=Şifreler eşleşti!
-changepassword.passwordsDoNotMatch=Şifreler eşleşmiyor
changepassword.finalConfirmation=Şifremi unutursam verilerimi kurtaramayacağımın farkındayım
# Forget Password
@@ -91,6 +89,8 @@ unlock.unlockBtn=Kilidi Aç
## Success
unlock.success.message="%s" 'nin kilidi başarıyla açıldı! Kasanız şimdi erişilebilir durumda.
unlock.success.revealBtn=Kasayı Göster
+## Invalid Mount Point
+unlock.error.invalidMountPoint=Bağlantı noktası boş bir dizin değil: %s
# Migration
migration.title=Kasayı Güncelle
@@ -111,6 +111,10 @@ preferences.general=Genel
preferences.general.theme=Görünüş ve Davranış
preferences.general.startHidden=Cryptomator'ı başlatırken pencereyi gizle
preferences.general.debugLogging=Hata ayıklama günlüğünü etkinleştir
+preferences.general.autoStart=Cryptomator'u sistrm başlangıcında çalıştır
+preferences.general.interfaceOrientation=Arayüz Yönü
+preferences.general.interfaceOrientation.ltr=Sola Yaslı
+preferences.general.interfaceOrientation.rtl=Sağa Yaslı
## Volume
preferences.volume=Sanal Sürücü
preferences.volume.type=Birim Tipi
@@ -122,10 +126,19 @@ preferences.updates.currentVersion=Şuanki Sürüm: %s
preferences.updates.autoUpdateCheck=Güncellemeleri otomatik kontrol et
preferences.updates.checkNowBtn=Şimdi Kontrol Et
preferences.updates.updateAvailable=%s sürümüne güncelleme mevcut.
+## Donation Key
+preferences.donationKey=Bağış
+preferences.donationKey.registeredFor=%s için kaydedildi
+preferences.donationKey.noDonationKey=Geçerli bir bağış anahtarı bulunamadı. Bağış anahtarı dediğimiz şey, lisans anahtarı gibidir ama ücretsiz uygulama kullanan muhteşem insanlar içindir ;-)
+preferences.donationKey.getDonationKey=Bir bağış anahtarı al
# Main Window
main.closeBtn.tooltip=Kapat
main.preferencesBtn.tooltip=Seçenekler
+main.donationKeyMissing.tooltip=Bağış yapmayı düşünmez miydiniz?
+## Drag 'n' Drop
+main.dropZone.dropVault=Bu kasayı ekle
+main.dropZone.unknownDragboardContent=Bir kasa eklemek istiyorsanız, onu bu pencereye sürükleyin
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Kasa eklemek için buraya tıklayın
main.vaultlist.contextMenu.remove=Kasayı Sil
@@ -160,19 +173,30 @@ vaultOptions.general=Genel
vaultOptions.general.changePasswordBtn=Şifreyi Değiştir
vaultOptions.general.showRecoveryKeyBtn=Kurtarma Anahtarını Göster
## Mount
-vaultOptions.mount=Bağlantı
+vaultOptions.mount=Bağlanılıyor
vaultOptions.mount.readonly=Salt-Okunur
vaultOptions.mount.driveName=Sürücü Adı
vaultOptions.mount.customMountFlags=Özel Bağlantı Parametreleri
vaultOptions.mount.winDriveLetterOccupied=meşgul
-vaultOptions.mount.winDirChooser=Boş bir dizin seçin
+vaultOptions.mount.mountPoint=Bağlantı Noktası
+vaultOptions.mount.mountPoint.auto=Otomatik uygun yer bul
+vaultOptions.mount.mountPoint.driveLetter=Bir sürücü harfi kullan
+vaultOptions.mount.mountPoint.custom=Özel Dosya Yolu
+vaultOptions.mount.mountPoint.directoryPickerButton=Seç…
+vaultOptions.mount.mountPoint.directoryPickerTitle=Boş bir dizin seçin
# Recovery Key
recoveryKey.title=Kurtarma Anahtarı
recoveryKey.enterPassword.prompt="%s" için kurtarma anahtarını göstermek üzere şifrenizi girin:
-recoveryKey.display.message="%s" için Kurtarma Anahtarı:
+recoveryKey.display.message=Aşağıdaki kurtarma anahtarı "%s" kasasına erişimi kazanmak için kullanılabilir:
+recoveryKey.display.StorageHints=Bunu çok güvenli bir yerde saklayın, örneğin:\n • Şifre yöneticisi kullanarak depolayın\n • USB flash belleğe kaydedin\n • Bir sayfaya yazdırın
-# Misc
+# New Password
+newPassword.promptText=Yeni bir şifre girin
+newPassword.reenterPassword=Yeni şifreyi onaylayın
+newPassword.passwordsMatch=Şifreler eşleşti!
+newPassword.passwordsDoNotMatch=Şifreler eşleşmedi
+passwordStrength.messageLabel.tooShort=En az %d karakter kullanın
passwordStrength.messageLabel.0=Çok zayıf
passwordStrength.messageLabel.1=Zayıf
passwordStrength.messageLabel.2=Orta
diff --git a/main/ui/src/main/resources/i18n/strings_zh.properties b/main/ui/src/main/resources/i18n/strings_zh.properties
new file mode 100644
index 000000000..ee75d5cb7
--- /dev/null
+++ b/main/ui/src/main/resources/i18n/strings_zh.properties
@@ -0,0 +1,204 @@
+# Locale Specific CSS files such as CJK, RTL,...
+
+# Generics
+## Button
+generic.button.apply=应用
+generic.button.back=返回
+generic.button.cancel=取消
+generic.button.change=更改
+generic.button.copy=复制
+generic.button.done=完成
+generic.button.next=下一步
+generic.button.print=打印
+## Error
+generic.error.title=发生一个未知错误
+generic.error.instruction=这本不该发生的,请报告下面的错误文本,并包含导致该错误的操作步骤的描述信息。
+
+# Tray Menu
+traymenu.showMainWindow=显示
+traymenu.showPreferencesWindow=首选项
+traymenu.lockAllVaults=全部锁定
+traymenu.quitApplication=退出
+traymenu.vault.unlock=解锁
+traymenu.vault.lock=锁定
+traymenu.vault.reveal=显示
+
+# Add Vault Wizard
+addvaultwizard.title=添加保险库
+## Welcome
+addvaultwizard.welcome.newButton=创建新的保险库
+addvaultwizard.welcome.existingButton=打开已有的保险库
+## New
+### Name
+addvaultwizard.new.nameInstruction=为保险库创建一个名称
+addvaultwizard.new.namePrompt=保险库名称
+### Location
+addvaultwizard.new.locationInstruction=Cryptomator 应该在哪里存储您保险库的加密文件?
+addvaultwizard.new.locationLabel=存储位置
+addvaultwizard.new.locationPrompt=…
+addvaultwizard.new.directoryPickerLabel=自定义位置
+addvaultwizard.new.directoryPickerButton=选择...
+addvaultwizard.new.directoryPickerTitle=选择目录
+addvaultwizard.new.fileAlreadyExists=无法在此路径上创建保险库,因为某些对象已经存在。
+addvaultwizard.new.invalidName=无效的保险库名称,请考虑一个常规的目录名称
+addvaultwizard.new.ioException=选定的目录未能通过基本测试。请确保您拥有适当的访问权限,以及没有任何东西干扰Cryptomator.
+### Password
+addvaultwizard.new.createVaultBtn=创建保险库
+addvaultwizard.new.generateRecoveryKeyChoice=如果没有密码,您将无法访问您的数据。您想要一个恢复密钥来以防您丢失密码吗?
+addvaultwizard.new.generateRecoveryKeyChoice.yes=是的请,有备无患
+addvaultwizard.new.generateRecoveryKeyChoice.no=不,谢谢。我不会丢失密码的
+### Information
+addvault.new.readme.storageLocation.fileName=这是什么目录.rtf
+addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ 保险库文件 ⚠️
+addvault.new.readme.storageLocation.2=这是您的保险库的存储位置。{\\b 不要} 更改此目录中的任何文件。
+addvault.new.readme.storageLocation.3=如果您想要使用 Cryptomator 加密文件,解锁保险库并使用提供的驱动器。
+addvault.new.readme.storageLocation.4=如果您需要帮助,尝试 %s
+addvault.new.readme.accessLocation.fileName=欢迎来到您的保险库.rtf
+addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ 加密卷 🔐️
+addvault.new.readme.accessLocation.2=这是您的保险库的存取位置。添加到此卷的任何文件都将由Cryptomator加密。要在稍后时间点访问该卷, 只需从Cryptomator中再次解锁。
+addvault.new.readme.accessLocation.3=随时可以删除此文件。
+## Existing
+addvaultwizard.existing.instruction=选择您现有密码库中的"masterkey.cryptomator"文件。
+addvaultwizard.existing.chooseBtn=选择...
+addvaultwizard.existing.filePickerTitle=选择 Masterkey 文件
+addvaultwizard.existing.error=无法添加保险库"%s":不是一个有效的保险库,请查看日志以了解更多信息。
+## Success
+addvaultwizard.success.nextStepsInstructions=添加了保险库"%s"\n您需要先解锁此保险库才能访问或添加内容。 或者您可以在稍后任何时间点解锁它。
+addvaultwizard.success.unlockNow=立即解锁
+
+# Remove Vault
+removeVault.title=删除保险库
+removeVault.information=这将只会使 Cryptomator 忘记这个保险库,您可以稍后再添加它。任何加密的文件不会从您的硬盘中删除。
+removeVault.confirmBtn=删除保险库
+
+# Change Password
+changepassword.title=更改密码
+changepassword.enterOldPassword=输入 "%s" 的当前密码
+changepassword.finalConfirmation=我了解如果忘记了密码,我将无法恢复我的数据
+
+# Forget Password
+forgetPassword.title=忘记密码
+forgetPassword.information=这将从您的系统密钥链中删除此保险库保存的密码。
+forgetPassword.confirmBtn=忘记密码
+
+# Unlock
+unlock.title=解锁保险库
+unlock.passwordPrompt=输入 "%s" 的密码
+unlock.savePassword=保存密码
+unlock.unlockBtn=解锁
+## Success
+unlock.success.message=已成功解锁 "%s" !您的保险库现在可以访问。
+unlock.success.revealBtn=显示保险库
+## Invalid Mount Point
+unlock.error.invalidMountPoint=挂载点不是一个空目录: %s
+
+# Migration
+migration.title=升级保险库
+## Start
+migration.start.prompt=您的保险库 "%s" 需要更新到一个更新的格式。在继续操作之前,请确保没有影响此保险库的待定同步。
+migration.start.confirm=是的,我的保险库已完全同步
+## Run
+migration.run.enterPassword=输入 "%s" 的密码
+migration.run.startMigrationBtn=迁移保险库
+## Sucess
+migration.success.nextStepsInstructions=已成功迁移 "%s"\n您现在可以解锁您的保险库。
+migration.success.unlockNow=立即解锁
+
+# Preferences
+preferences.title=首选项
+## General
+preferences.general=常规
+preferences.general.theme=界面外观
+preferences.general.startHidden=启动Cryptomator时隐藏窗口
+preferences.general.debugLogging=启用调试日志
+## Volume
+preferences.volume=虚拟磁盘
+preferences.volume.type=卷类型
+preferences.volume.webdav.port=WebDAV 端口
+preferences.volume.webdav.scheme=WebDAV 方案
+## Updates
+preferences.updates=更新
+preferences.updates.currentVersion=当前版本:%s
+preferences.updates.autoUpdateCheck=自动检查更新
+preferences.updates.checkNowBtn=立即检查
+preferences.updates.updateAvailable=可更新到版本 %s
+## Donation Key
+preferences.donationKey=捐赠
+preferences.donationKey.registeredFor=已注册给 %s
+preferences.donationKey.noDonationKey=找不到有效的捐赠密钥,它相当于一个专门给使用免费软件的帅哥美女的许可证密钥。;-)
+preferences.donationKey.getDonationKey=获取一个捐赠密钥
+
+# Main Window
+main.closeBtn.tooltip=关闭
+main.preferencesBtn.tooltip=首选项
+main.donationKeyMissing.tooltip=请考虑捐赠
+## Drag 'n' Drop
+main.dropZone.dropVault=添加此保险库
+main.dropZone.unknownDragboardContent=如果您想要添加一个保险库,将其拖动到此窗口
+## Vault List
+main.vaultlist.emptyList.onboardingInstruction=点击此处添加一个保险库
+main.vaultlist.contextMenu.remove=删除保险库
+main.vaultlist.addVaultBtn=添加保险库
+## Vault Detail
+### Locked
+main.vaultDetail.lockedStatus=已锁定
+main.vaultDetail.unlockBtn=解锁
+main.vaultDetail.optionsBtn=保险库选项
+### Unlocked
+main.vaultDetail.unlockedStatus=已解锁
+main.vaultDetail.accessLocation=您的保险库内容在此处访问:
+main.vaultDetail.revealBtn=显示驱动器
+main.vaultDetail.lockBtn=锁定
+main.vaultDetail.bytesPerSecondRead=读:
+main.vaultDetail.bytesPerSecondWritten=写:
+main.vaultDetail.throughput.idle=空闲
+main.vaultDetail.throughput.kbps=%.1f kiB/s
+main.vaultDetail.throughput.mbps=%.1f MiB/s
+### Needs Migration
+main.vaultDetail.migrateButton=升级保险库
+main.vaultDetail.migratePrompt=您的保险库需要升级到新格式,然后才能访问
+
+# Wrong File Alert
+wrongFileAlert.title=未知文件
+wrongFileAlert.btn=好
+wrongFileAlert.information=您已尝试添加一个似乎不是Cryptomator保险库的文件或文件夹。要加密您的数据,请创建并解锁保险库。
+
+# Vault Options
+## General
+vaultOptions.general=常规
+vaultOptions.general.changePasswordBtn=更改密码
+vaultOptions.general.showRecoveryKeyBtn=显示恢复密钥
+## Mount
+vaultOptions.mount=挂载
+vaultOptions.mount.readonly=只读
+vaultOptions.mount.driveName=驱动器名称
+vaultOptions.mount.customMountFlags=自定义挂载标志
+vaultOptions.mount.winDriveLetterOccupied=已占用
+vaultOptions.mount.mountPoint=挂载点
+vaultOptions.mount.mountPoint.auto=自动选择一个合适的位置
+vaultOptions.mount.mountPoint.driveLetter=使用分配的驱动器字符
+vaultOptions.mount.mountPoint.custom=自定义路径
+vaultOptions.mount.mountPoint.directoryPickerButton=选择...
+vaultOptions.mount.mountPoint.directoryPickerTitle=选择一个空目录
+
+# Recovery Key
+recoveryKey.title=恢复密钥
+recoveryKey.enterPassword.prompt=输入您的密码以显示"%s"的恢复密钥:
+recoveryKey.display.message=下面的恢复密钥可用于恢复对"%s"的访问:
+recoveryKey.display.StorageHints=保存它到非常安全的某处,例如:\n • 使用密码管理器来保存\n • 将其保存在USB闪存盘\n • 打印在纸上
+
+# New Password
+newPassword.promptText=输入新密码
+newPassword.reenterPassword=确认新密码
+newPassword.passwordsMatch=密码匹配!
+newPassword.passwordsDoNotMatch=密码不匹配!
+passwordStrength.messageLabel.tooShort=请至少使用 %d 个字符
+passwordStrength.messageLabel.0=非常弱
+passwordStrength.messageLabel.1=弱
+passwordStrength.messageLabel.2=一般
+passwordStrength.messageLabel.3=强
+passwordStrength.messageLabel.4=非常强
+
+# Quit
+quit.prompt=退出程序?尚有解锁的保险库。
+quit.lockAndQuit=锁定并退出
diff --git a/main/ui/src/main/resources/i18n/strings_zh_TW.properties b/main/ui/src/main/resources/i18n/strings_zh_TW.properties
new file mode 100644
index 000000000..c368c9ebf
--- /dev/null
+++ b/main/ui/src/main/resources/i18n/strings_zh_TW.properties
@@ -0,0 +1,208 @@
+# Locale Specific CSS files such as CJK, RTL,...
+
+# Generics
+## Button
+generic.button.apply=套用
+generic.button.back=上一頁
+generic.button.cancel=取消
+generic.button.change=修改
+generic.button.copy=複製
+generic.button.done=完成
+generic.button.next=繼續
+generic.button.print=列印
+## Error
+generic.error.title=發生未知錯誤
+generic.error.instruction=這不應該發生。請回報下方的錯誤訊息,並附上造成這個錯誤的步驟。
+
+# Tray Menu
+traymenu.showMainWindow=顯示
+traymenu.showPreferencesWindow=偏好
+traymenu.lockAllVaults=全部鎖定
+traymenu.quitApplication=離開
+traymenu.vault.unlock=解鎖
+traymenu.vault.lock=鎖定
+traymenu.vault.reveal=顯示
+
+# Add Vault Wizard
+addvaultwizard.title=新增加密檔案庫
+## Welcome
+addvaultwizard.welcome.newButton=新建加密檔案庫
+addvaultwizard.welcome.existingButton=開啟現有加密檔案庫
+## New
+### Name
+addvaultwizard.new.nameInstruction=為加密檔案庫命名
+addvaultwizard.new.namePrompt=加密檔案庫名稱
+### Location
+addvaultwizard.new.locationInstruction=Cryptomator 應該將您加密後的檔案存放在哪裡?
+addvaultwizard.new.locationLabel=儲存位置
+addvaultwizard.new.locationPrompt=…
+addvaultwizard.new.directoryPickerLabel=自訂位置
+addvaultwizard.new.directoryPickerButton=選取
+addvaultwizard.new.directoryPickerTitle=選取資料夾
+addvaultwizard.new.fileAlreadyExists=在這個路徑無法建立加密檔案庫因為其中已包含其他檔案。
+addvaultwizard.new.invalidName=無效的加密檔案庫名稱。請考慮一般資料夾會使用的名稱。
+addvaultwizard.new.ioException=選取的資料夾無法通過基礎測試。請確認 Cryptomator 有存取權限以及不受干擾。
+### Password
+addvaultwizard.new.createVaultBtn=新建加密檔案庫
+addvaultwizard.new.generateRecoveryKeyChoice=若您遺失密碼將無法存取您的資料。您是否希望建立一組在您遺失密碼時可供復原的金鑰?
+addvaultwizard.new.generateRecoveryKeyChoice.yes=是,做好安全措施,以免後悔莫及。
+addvaultwizard.new.generateRecoveryKeyChoice.no=否,不用了,我不會弄丟密碼。
+### Information
+addvault.new.readme.storageLocation.fileName=這是什麼資料夾.rtf
+addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ 加密檔案庫使用的檔案 ⚠️
+addvault.new.readme.storageLocation.2=這是您加密檔案庫被儲存的路徑。{\\b 請勿} 更動資料夾內的所有檔案。
+addvault.new.readme.storageLocation.3=如果您希望使用 Cryptomator 加密您的檔案,解鎖加密檔案庫並使用解鎖後的磁碟。
+addvault.new.readme.storageLocation.4=如果您需要幫助,請試試%s。
+addvault.new.readme.accessLocation.fileName=歡迎來到您的加密檔案庫.rtf
+addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ 加密磁區 🔐️
+addvault.new.readme.accessLocation.2=這是用來存放需要被加密的檔案的位置。所有被加入這個磁區的檔案都會被 Cryptomator 加密。將來您若想存取這個磁區只需要到 Cryptomator 內進行解鎖即可。
+addvault.new.readme.accessLocation.3=您可以放心移除這個檔案。
+## Existing
+addvaultwizard.existing.instruction=請選取您現有加密檔案庫的 "masterkey.cryptomator" 檔案。
+addvaultwizard.existing.chooseBtn=選取
+addvaultwizard.existing.filePickerTitle=選擇主金鑰檔案
+addvaultwizard.existing.error=無法加入加密檔案庫 "%s":並不是一個合法加密檔案庫。請參照紀錄檔以了解更多資訊。
+## Success
+addvaultwizard.success.nextStepsInstructions=已加入加密檔案庫 "%s"。您需要將這個加密檔案庫解鎖以存取內容,或是您也可以之後再解鎖。
+addvaultwizard.success.unlockNow=立即解鎖
+
+# Remove Vault
+removeVault.title=移除加密檔案庫
+removeVault.information=這將會讓 Cryptomator 忘記這個加密檔案庫。您未來可以再重新加入。已加密的檔案將不會從您的硬碟中移除。
+removeVault.confirmBtn=移除加密檔案庫
+
+# Change Password
+changepassword.title=變更密碼
+changepassword.enterOldPassword=輸入 "%s" 目前的密碼
+changepassword.finalConfirmation=我明白如果忘記密碼將會無法復原我的資料
+
+# Forget Password
+forgetPassword.title=忘記密碼
+forgetPassword.information=這將會從系統鑰匙圈中移除這個加密檔案庫已存的密碼。
+forgetPassword.confirmBtn=忘記密碼
+
+# Unlock
+unlock.title=解鎖加密檔案庫
+unlock.passwordPrompt=輸入 "%s" 的密碼:
+unlock.savePassword=儲存密碼
+unlock.unlockBtn=解鎖
+## Success
+unlock.success.message=成功解鎖 "%s"!您現在可以存取您的加密檔案庫。
+unlock.success.revealBtn=顯示加密檔案庫
+## Invalid Mount Point
+unlock.error.invalidMountPoint=掛載點不是空資料夾:%s
+
+# Migration
+migration.title=升級加密檔案庫
+## Start
+migration.start.prompt=您的加密檔案庫 "%s" 需要升級成新的格式。在開始前請確保不會有檔案同步干擾更新過程。
+migration.start.confirm=是的,我的加密檔案庫已同步完成
+## Run
+migration.run.enterPassword=輸入 "%s" 的密碼
+migration.run.startMigrationBtn=升級加密檔案庫
+## Sucess
+migration.success.nextStepsInstructions=已成功升級 "%s"。\n您現在可以解鎖您的加密檔案庫了。
+migration.success.unlockNow=立即解鎖
+
+# Preferences
+preferences.title=偏好
+## General
+preferences.general=一般
+preferences.general.theme=外觀
+preferences.general.startHidden=啟動 Cryptomator 時隱藏視窗
+preferences.general.debugLogging=啟用除錯日誌
+preferences.general.autoStart=系統啟動時同時啟動 Cryptomator
+preferences.general.interfaceOrientation=界面排版方向
+preferences.general.interfaceOrientation.ltr=由左至右
+preferences.general.interfaceOrientation.rtl=由右至左
+## Volume
+preferences.volume=虛擬磁碟
+preferences.volume.type=磁區類型
+preferences.volume.webdav.port=WebDAV 埠號 (port)
+preferences.volume.webdav.scheme=WebDAV 協定 (scheme)
+## Updates
+preferences.updates=更新
+preferences.updates.currentVersion=目前版本:%s
+preferences.updates.autoUpdateCheck=勾選以啟用自動更新
+preferences.updates.checkNowBtn=立即檢查
+preferences.updates.updateAvailable=有版本 %s 可更新。
+## Donation Key
+preferences.donationKey=贊助
+preferences.donationKey.registeredFor=註冊給 %s
+preferences.donationKey.noDonationKey=未發現贊助金鑰。贊助金鑰就像授權金鑰,不過是給使用自由軟體的善心人士的。
+preferences.donationKey.getDonationKey=取得贊助金鑰
+
+# Main Window
+main.closeBtn.tooltip=關閉
+main.preferencesBtn.tooltip=偏好
+main.donationKeyMissing.tooltip=請考慮贊助
+## Drag 'n' Drop
+main.dropZone.dropVault=加入這個加密檔案庫
+main.dropZone.unknownDragboardContent=如果您想加入一個加密檔案庫,請將他拖到這個視窗裡
+## Vault List
+main.vaultlist.emptyList.onboardingInstruction=點擊此處以加入加密檔案庫
+main.vaultlist.contextMenu.remove=移除加密檔案庫
+main.vaultlist.addVaultBtn=新增加密檔案庫
+## Vault Detail
+### Locked
+main.vaultDetail.lockedStatus=已鎖定
+main.vaultDetail.unlockBtn=解鎖
+main.vaultDetail.optionsBtn=加密檔案庫選項
+### Unlocked
+main.vaultDetail.unlockedStatus=已解鎖
+main.vaultDetail.accessLocation=您可以從這裡取得您加密檔案庫的內容
+main.vaultDetail.revealBtn=顯示磁碟
+main.vaultDetail.lockBtn=鎖定
+main.vaultDetail.bytesPerSecondRead=讀取:
+main.vaultDetail.bytesPerSecondWritten=寫入:
+main.vaultDetail.throughput.idle=閒置
+main.vaultDetail.throughput.kbps=%.1f kiB/s
+main.vaultDetail.throughput.mbps=%.1f MiB/s
+### Needs Migration
+main.vaultDetail.migrateButton=升級加密檔案庫
+main.vaultDetail.migratePrompt=您必須先更新加密檔案庫才能存取內容
+
+# Wrong File Alert
+wrongFileAlert.title=未知的檔案
+wrongFileAlert.btn=確定
+wrongFileAlert.information=您試著加入一個似乎不是 Cryptomator 加密檔案庫的檔案或資料夾。若要加密您的資料,請新建並解鎖一個加密檔案庫。
+
+# Vault Options
+## General
+vaultOptions.general=一般
+vaultOptions.general.changePasswordBtn=變更密碼
+vaultOptions.general.showRecoveryKeyBtn=顯示復原金鑰
+## Mount
+vaultOptions.mount=掛載
+vaultOptions.mount.readonly=唯讀
+vaultOptions.mount.driveName=磁碟代號
+vaultOptions.mount.customMountFlags=自訂掛載參數
+vaultOptions.mount.winDriveLetterOccupied=已使用
+vaultOptions.mount.mountPoint=掛載點
+vaultOptions.mount.mountPoint.auto=自動選取一個合適的位置
+vaultOptions.mount.mountPoint.driveLetter=使用指定的磁碟代號
+vaultOptions.mount.mountPoint.custom=自訂路徑
+vaultOptions.mount.mountPoint.directoryPickerButton=選取
+vaultOptions.mount.mountPoint.directoryPickerTitle=選取一個空的資料夾
+
+# Recovery Key
+recoveryKey.title=復原金鑰
+recoveryKey.enterPassword.prompt=請輸入您的密碼以顯示 "%s" 的復原金鑰:
+recoveryKey.display.message=下方的復原金鑰可用來恢復 "%s" 的存取能力:
+recoveryKey.display.StorageHints=請把它保存在非常安全的地方,例如:\n • 使用密碼管理器保管\n • 存在 USB 隨身碟裡\n • 印在紙上
+
+# New Password
+newPassword.promptText=輸入新密碼
+newPassword.reenterPassword=確認新密碼
+newPassword.passwordsMatch=密碼相符
+newPassword.passwordsDoNotMatch=密碼不符
+passwordStrength.messageLabel.tooShort=密碼長度至少需 %d 個字元
+passwordStrength.messageLabel.0=非常弱
+passwordStrength.messageLabel.1=弱
+passwordStrength.messageLabel.2=一般
+passwordStrength.messageLabel.3=強
+passwordStrength.messageLabel.4=非常強
+
+# Quit
+quit.prompt=仍然有解鎖中的加密檔案庫,確定要離開?
+quit.lockAndQuit=鎖定並離開
diff --git a/main/ui/src/test/java/org/cryptomator/ui/common/PasswordStrengthUtilTest.java b/main/ui/src/test/java/org/cryptomator/ui/common/PasswordStrengthUtilTest.java
index 357defb17..fb2a61b18 100644
--- a/main/ui/src/test/java/org/cryptomator/ui/common/PasswordStrengthUtilTest.java
+++ b/main/ui/src/test/java/org/cryptomator/ui/common/PasswordStrengthUtilTest.java
@@ -1,6 +1,7 @@
package org.cryptomator.ui.common;
import com.google.common.base.Strings;
+import org.cryptomator.common.Environment;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -13,7 +14,7 @@ public class PasswordStrengthUtilTest {
@Test
public void testLongPasswords() {
- PasswordStrengthUtil util = new PasswordStrengthUtil(Mockito.mock(ResourceBundle.class));
+ PasswordStrengthUtil util = new PasswordStrengthUtil(Mockito.mock(ResourceBundle.class), Mockito.mock(Environment.class));
String longPw = Strings.repeat("x", 10_000);
Assertions.assertTimeout(Duration.ofSeconds(5), () -> {
util.computeRate(longPw);
@@ -21,9 +22,9 @@ public class PasswordStrengthUtilTest {
}
@Test
- @Disabled("waiting on upstream fix")
+ @Disabled("waiting on upstream fix") // https://github.com/nulab/zxcvbn4j/issues/54
public void testIssue979() {
- PasswordStrengthUtil util = new PasswordStrengthUtil(Mockito.mock(ResourceBundle.class));
+ PasswordStrengthUtil util = new PasswordStrengthUtil(Mockito.mock(ResourceBundle.class), Mockito.mock(Environment.class));
int result1 = util.computeRate("backed derrick buckling mountains glove client procedures desire destination sword hidden ram");
int result2 = util.computeRate("backed derrick buckling mountains glove client procedures desire destination sword hidden ram escalation");
Assertions.assertEquals(4, result1);