~exprez135/cryptomator-libre

570286c7df14b5ef6473f264b6bb685147f5ce50 — Sebastian Stenzel 3 years ago aef33dc + 11e3ee8 1.5.5
Merge branch 'release/1.5.5'
98 files changed, 868 insertions(+), 305 deletions(-)

M main/buildkit/pom.xml
M main/commons/pom.xml
M main/keychain/pom.xml
D main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccess.java
M main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccessStrategy.java
A main/keychain/src/main/java/org/cryptomator/keychain/KeychainManager.java
M main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java
M main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceKeychainAccess.java
M main/keychain/src/main/java/org/cryptomator/keychain/MacSystemKeychainAccess.java
M main/keychain/src/main/java/org/cryptomator/keychain/WindowsProtectedKeychainAccess.java
A main/keychain/src/test/java/org/cryptomator/keychain/KeychainManagerTest.java
D main/keychain/src/test/java/org/cryptomator/keychain/KeychainModuleTest.java
D main/keychain/src/test/java/org/cryptomator/keychain/TestKeychainComponent.java
D main/keychain/src/test/java/org/cryptomator/keychain/TestKeychainModule.java
M main/launcher/pom.xml
M main/pom.xml
M main/ui/pom.xml
M main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultSuccessController.java
M main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java
M main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java
M main/ui/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java
M main/ui/src/main/java/org/cryptomator/ui/common/VaultService.java
M main/ui/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java
M main/ui/src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordController.java
M main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java
M main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java
M main/ui/src/main/java/org/cryptomator/ui/launcher/UiLauncher.java
M main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java
M main/ui/src/main/java/org/cryptomator/ui/mainwindow/WelcomeController.java
M main/ui/src/main/java/org/cryptomator/ui/migration/MigrationRunController.java
M main/ui/src/main/java/org/cryptomator/ui/migration/MigrationSuccessController.java
M main/ui/src/main/java/org/cryptomator/ui/traymenu/TrayImageFactory.java
M main/ui/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java
M main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockComponent.java
M main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockController.java
M main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java
M main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockSuccessController.java
M main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java
M main/ui/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java
M main/ui/src/main/java/org/cryptomator/ui/wrongfilealert/WrongFileAlertController.java
M main/ui/src/main/resources/fxml/addvault_new_location.fxml
M main/ui/src/main/resources/fxml/addvault_welcome.fxml
M main/ui/src/main/resources/fxml/preferences_about.fxml
M main/ui/src/main/resources/fxml/unlock.fxml
M main/ui/src/main/resources/fxml/vault_detail.fxml
M main/ui/src/main/resources/fxml/vault_detail_locked.fxml
M main/ui/src/main/resources/fxml/vault_detail_missing.fxml
M main/ui/src/main/resources/fxml/vault_detail_needsmigration.fxml
M main/ui/src/main/resources/fxml/vault_detail_unlocked.fxml
M main/ui/src/main/resources/fxml/vault_detail_welcome.fxml
M main/ui/src/main/resources/fxml/vault_options_masterkey.fxml
M main/ui/src/main/resources/fxml/vault_options_mount.fxml
M main/ui/src/main/resources/i18n/strings.properties
M main/ui/src/main/resources/i18n/strings_ar.properties
M main/ui/src/main/resources/i18n/strings_ca.properties
M main/ui/src/main/resources/i18n/strings_cs.properties
M main/ui/src/main/resources/i18n/strings_de.properties
M main/ui/src/main/resources/i18n/strings_es.properties
M main/ui/src/main/resources/i18n/strings_fr.properties
M main/ui/src/main/resources/i18n/strings_hi.properties
M main/ui/src/main/resources/i18n/strings_it.properties
M main/ui/src/main/resources/i18n/strings_ja.properties
M main/ui/src/main/resources/i18n/strings_ko.properties
M main/ui/src/main/resources/i18n/strings_lv.properties
M main/ui/src/main/resources/i18n/strings_nb.properties
M main/ui/src/main/resources/i18n/strings_nl.properties
M main/ui/src/main/resources/i18n/strings_nn.properties
A main/ui/src/main/resources/i18n/strings_pl.properties
M main/ui/src/main/resources/i18n/strings_pt_BR.properties
M main/ui/src/main/resources/i18n/strings_ru.properties
M main/ui/src/main/resources/i18n/strings_sk.properties
M main/ui/src/main/resources/i18n/strings_sv.properties
M main/ui/src/main/resources/i18n/strings_tr.properties
M main/ui/src/main/resources/i18n/strings_zh.properties
M main/ui/src/main/resources/i18n/strings_zh_TW.properties
A main/ui/src/main/resources/img/bot/arm-l.png
A main/ui/src/main/resources/img/bot/arm-l@2x.png
A main/ui/src/main/resources/img/bot/arm-r.png
A main/ui/src/main/resources/img/bot/arm-r@2x.png
A main/ui/src/main/resources/img/bot/body.png
A main/ui/src/main/resources/img/bot/body@2x.png
R main/ui/src/main/resources/{bot.png => img/bot/bot.png}
R main/ui/src/main/resources/{bot@2x.png => img/bot/bot@2x.png}
A main/ui/src/main/resources/img/bot/face.png
A main/ui/src/main/resources/img/bot/face@2x.png
A main/ui/src/main/resources/img/bot/legs.png
A main/ui/src/main/resources/img/bot/legs@2x.png
R main/ui/src/main/resources/{select-masterkey-mac.png => img/select-masterkey-mac.png}
R main/ui/src/main/resources/{select-masterkey-win.png => img/select-masterkey-win.png}
R main/ui/src/main/resources/{tray_icon.png => img/tray_icon.png}
R main/ui/src/main/resources/{tray_icon_mac_black.png => img/tray_icon_mac_black.png}
R main/ui/src/main/resources/{tray_icon_mac_black@2x.png => img/tray_icon_mac_black@2x.png}
R main/ui/src/main/resources/{tray_icon_mac_white.png => img/tray_icon_mac_white.png}
R main/ui/src/main/resources/{tray_icon_mac_white@2x.png => img/tray_icon_mac_white@2x.png}
R main/ui/src/main/resources/{vault-volume-mac.png => img/vault-volume-mac.png}
R main/ui/src/main/resources/{vault-volume-win.png => img/vault-volume-win.png}
R main/ui/src/main/resources/{window_icon_32.png => img/window_icon_32.png}
R main/ui/src/main/resources/{window_icon_512.png => img/window_icon_512.png}
M main/buildkit/pom.xml => main/buildkit/pom.xml +1 -1
@@ 4,7 4,7 @@
	<parent>
		<groupId>org.cryptomator</groupId>
		<artifactId>main</artifactId>
		<version>1.5.4</version>
		<version>1.5.5</version>
	</parent>
	<artifactId>buildkit</artifactId>
	<packaging>pom</packaging>

M main/commons/pom.xml => main/commons/pom.xml +1 -1
@@ 4,7 4,7 @@
	<parent>
		<groupId>org.cryptomator</groupId>
		<artifactId>main</artifactId>
		<version>1.5.4</version>
		<version>1.5.5</version>
	</parent>
	<artifactId>commons</artifactId>
	<name>Cryptomator Commons</name>

M main/keychain/pom.xml => main/keychain/pom.xml +14 -3
@@ 4,7 4,7 @@
	<parent>
		<groupId>org.cryptomator</groupId>
		<artifactId>main</artifactId>
		<version>1.5.4</version>
		<version>1.5.5</version>
	</parent>
	<artifactId>keychain</artifactId>
	<name>System Keychain Access</name>


@@ 15,16 15,27 @@
			<artifactId>commons</artifactId>
		</dependency>

		<!-- JavaFx -->
		<dependency>
			<groupId>org.openjfx</groupId>
			<artifactId>javafx-base</artifactId>
		</dependency>
		<dependency>
			<groupId>org.openjfx</groupId>
			<artifactId>javafx-graphics</artifactId>
		</dependency>
		
		<!-- Apache -->
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
		</dependency>

		<!-- Google -->
		<dependency>
			<groupId>com.google.code.gson</groupId>
			<artifactId>gson</artifactId>
		</dependency>

		<!-- Google -->
		<dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava</artifactId>

D main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccess.java => main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccess.java +0 -38
@@ 1,38 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.keychain;

public interface KeychainAccess {

	/**
	 * Associates a passphrase with a given key.
	 * 
	 * @param key Key used to retrieve the passphrase via {@link #loadPassphrase(String)}.
	 * @param passphrase The secret to store in this keychain.
	 */
	void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException;

	/**
	 * @param key Unique key previously used while {@link #storePassphrase(String, CharSequence) storing a passphrase}.
	 * @return The stored passphrase for the given key or <code>null</code> if no value for the given key could be found.
	 */
	char[] loadPassphrase(String key) throws KeychainAccessException;

	/**
	 * Deletes a passphrase with a given key.
	 * 
	 * @param key Unique key previously used while {@link #storePassphrase(String, CharSequence) storing a passphrase}.
	 */
	void deletePassphrase(String key) throws KeychainAccessException;

	/**
	 * Updates a passphrase with a given key.
	 *
	 * @param key Unique key previously used while {@link #storePassphrase(String, CharSequence) storing a passphrase}.
	 * @param passphrase The secret to be updated in this keychain.
	 */
	void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException;
}

M main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccessStrategy.java => main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccessStrategy.java +30 -1
@@ 5,7 5,36 @@
 *******************************************************************************/
package org.cryptomator.keychain;

interface KeychainAccessStrategy extends KeychainAccess {
interface KeychainAccessStrategy {

	/**
	 * Associates a passphrase with a given key.
	 *
	 * @param key Key used to retrieve the passphrase via {@link #loadPassphrase(String)}.
	 * @param passphrase The secret to store in this keychain.
	 */
	void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException;

	/**
	 * @param key Unique key previously used while {@link #storePassphrase(String, CharSequence) storing a passphrase}.
	 * @return The stored passphrase for the given key or <code>null</code> if no value for the given key could be found.
	 */
	char[] loadPassphrase(String key) throws KeychainAccessException;

	/**
	 * Deletes a passphrase with a given key.
	 *
	 * @param key Unique key previously used while {@link #storePassphrase(String, CharSequence) storing a passphrase}.
	 */
	void deletePassphrase(String key) throws KeychainAccessException;

	/**
	 * Updates a passphrase with a given key. Noop, if there is no item for the given key.
	 *
	 * @param key Unique key previously used while {@link #storePassphrase(String, CharSequence) storing a passphrase}.
	 * @param passphrase The secret to be updated in this keychain.
	 */
	void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException;

	/**
	 * @return <code>true</code> if this KeychainAccessStrategy works on the current machine.

A main/keychain/src/main/java/org/cryptomator/keychain/KeychainManager.java => main/keychain/src/main/java/org/cryptomator/keychain/KeychainManager.java +117 -0
@@ 0,0 1,117 @@
package org.cryptomator.keychain;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;

public class KeychainManager implements KeychainAccessStrategy {

	private static final Logger LOG = LoggerFactory.getLogger(KeychainManager.class);

	private final KeychainAccessStrategy keychain;
	private LoadingCache<String, BooleanProperty> passphraseStoredProperties;

	KeychainManager(KeychainAccessStrategy keychain) {
		assert keychain.isSupported();
		this.keychain = keychain;
		this.passphraseStoredProperties = CacheBuilder.newBuilder() //
				.weakValues() //
				.build(CacheLoader.from(this::createStoredPassphraseProperty));
	}

	@Override
	public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
		keychain.storePassphrase(key, passphrase);
		setPassphraseStored(key, true);
	}

	@Override
	public char[] loadPassphrase(String key) throws KeychainAccessException {
		char[] passphrase = keychain.loadPassphrase(key);
		setPassphraseStored(key, passphrase != null);
		return passphrase;
	}

	@Override
	public void deletePassphrase(String key) throws KeychainAccessException {
		keychain.deletePassphrase(key);
		setPassphraseStored(key, false);
	}

	@Override
	public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
		keychain.changePassphrase(key, passphrase);
		setPassphraseStored(key, true);
	}

	@Override
	public boolean isSupported() {
		return true;
	}

	/**
	 * Checks if the keychain knows a passphrase for the given key.
	 * <p>
	 * Expensive operation. If possible, use {@link #getPassphraseStoredProperty(String)} instead.
	 *
	 * @param key The key to look up
	 * @return <code>true</code> if a password for <code>key</code> is stored.
	 * @throws KeychainAccessException
	 */
	public boolean isPassphraseStored(String key) throws KeychainAccessException {
		char[] storedPw = null;
		try {
			storedPw = keychain.loadPassphrase(key);
			return storedPw != null;
		} finally {
			if (storedPw != null) {
				Arrays.fill(storedPw, ' ');
			}
		}
	}
	
	private void setPassphraseStored(String key, boolean value) {
		BooleanProperty property = passphraseStoredProperties.getIfPresent(key);
		if (property != null) {
			if (Platform.isFxApplicationThread()) {
				property.set(value);
			} else {
				LOG.warn("");
				Platform.runLater(() -> property.set(value));
			}
		}
	}

	/**
	 * Returns an observable property for use in the UI that tells whether a passphrase is stored for the given key.
	 * <p>
	 * Assuming that this process is the only process modifying Cryptomator-related items in the system keychain, this
	 * property stays in memory in an attempt to avoid unnecessary calls to the system keychain. Note that due to this
	 * fact the value stored in the returned property is not 100% reliable. Code defensively!
	 *
	 * @param key The key to look up
	 * @return An observable property which is <code>true</code> when it almost certain that a password for <code>key</code> is stored.
	 * @see #isPassphraseStored(String) 
	 */
	public ReadOnlyBooleanProperty getPassphraseStoredProperty(String key) {
		return passphraseStoredProperties.getUnchecked(key);
	}

	private BooleanProperty createStoredPassphraseProperty(String key) {
		try {
			LOG.warn("LOAD"); // TODO remove
			return new SimpleBooleanProperty(isPassphraseStored(key));
		} catch (KeychainAccessException e) {
			return new SimpleBooleanProperty(false);
		}
	}

}

M main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java => main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java +21 -9
@@ 5,10 5,10 @@
 *******************************************************************************/
package org.cryptomator.keychain;

import com.google.common.collect.Sets;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ElementsIntoSet;
import dagger.multibindings.IntoSet;
import org.cryptomator.common.JniModule;

import javax.inject.Singleton;


@@ 16,18 16,30 @@ import java.util.Optional;
import java.util.Set;

@Module(includes = {JniModule.class})
public class KeychainModule {
public abstract class KeychainModule {

	@Binds
	@IntoSet
	abstract KeychainAccessStrategy bindMacSystemKeychainAccess(MacSystemKeychainAccess keychainAccessStrategy);

	@Binds
	@IntoSet
	abstract KeychainAccessStrategy bindWindowsProtectedKeychainAccess(WindowsProtectedKeychainAccess keychainAccessStrategy);
	
	@Binds
	@IntoSet
	abstract KeychainAccessStrategy bindLinuxSecretServiceKeychainAccess(LinuxSecretServiceKeychainAccess keychainAccessStrategy);

	@Provides
	@ElementsIntoSet
	Set<KeychainAccessStrategy> provideKeychainAccessStrategies(MacSystemKeychainAccess macKeychain, WindowsProtectedKeychainAccess winKeychain, LinuxSecretServiceKeychainAccess linKeychain) {
		return Sets.newHashSet(macKeychain, winKeychain, linKeychain);
	@Singleton
	static Optional<KeychainAccessStrategy> provideSupportedKeychain(Set<KeychainAccessStrategy> keychainAccessStrategies) {
		return keychainAccessStrategies.stream().filter(KeychainAccessStrategy::isSupported).findFirst();
	}

	
	@Provides
	@Singleton
	public Optional<KeychainAccess> provideSupportedKeychain(Set<KeychainAccessStrategy> keychainAccessStrategies) {
		return keychainAccessStrategies.stream().filter(KeychainAccessStrategy::isSupported).map(KeychainAccess.class::cast).findFirst();
	public static Optional<KeychainManager> provideKeychainManager(Optional<KeychainAccessStrategy> keychainAccess) {
		return keychainAccess.map(KeychainManager::new);
	}

}

M main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceKeychainAccess.java => main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceKeychainAccess.java +2 -0
@@ 3,11 3,13 @@ package org.cryptomator.keychain;
import org.apache.commons.lang3.SystemUtils;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Optional;

/**
 * A facade to LinuxSecretServiceKeychainAccessImpl that doesn't depend on libraries that are unavailable on Mac and Windows.
 */
@Singleton
public class LinuxSecretServiceKeychainAccess implements KeychainAccessStrategy {

	// the actual implementation is hidden in this delegate object which is loaded via reflection,

M main/keychain/src/main/java/org/cryptomator/keychain/MacSystemKeychainAccess.java => main/keychain/src/main/java/org/cryptomator/keychain/MacSystemKeychainAccess.java +6 -2
@@ 8,11 8,13 @@ package org.cryptomator.keychain;
import java.util.Optional;

import javax.inject.Inject;
import javax.inject.Singleton;

import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.jni.MacFunctions;
import org.cryptomator.jni.MacKeychainAccess;

@Singleton
class MacSystemKeychainAccess implements KeychainAccessStrategy {

	private final Optional<MacFunctions> macFunctions;


@@ 47,8 49,10 @@ class MacSystemKeychainAccess implements KeychainAccessStrategy {
	}

	@Override
	public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
		storePassphrase(key, passphrase);
	public void changePassphrase(String key, CharSequence passphrase) {
		if (keychain().deletePassword(key)) {
			keychain().storePassword(key, passphrase);
		}
	}

}

M main/keychain/src/main/java/org/cryptomator/keychain/WindowsProtectedKeychainAccess.java => main/keychain/src/main/java/org/cryptomator/keychain/WindowsProtectedKeychainAccess.java +7 -2
@@ 25,6 25,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;


@@ 50,6 51,7 @@ import java.util.stream.Collectors;

import static java.nio.charset.StandardCharsets.UTF_8;

@Singleton
class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {

	private static final Logger LOG = LoggerFactory.getLogger(WindowsProtectedKeychainAccess.class);


@@ 113,8 115,11 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
	}

	@Override
	public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
		storePassphrase(key, passphrase);
	public void changePassphrase(String key, CharSequence passphrase) {
		loadKeychainEntriesIfNeeded();
		if (keychainEntries.remove(key) != null) {
			storePassphrase(key, passphrase);
		}
	}

	@Override

A main/keychain/src/test/java/org/cryptomator/keychain/KeychainManagerTest.java => main/keychain/src/test/java/org/cryptomator/keychain/KeychainManagerTest.java +55 -0
@@ 0,0 1,55 @@
package org.cryptomator.keychain;


import javafx.application.Platform;
import javafx.beans.property.ReadOnlyBooleanProperty;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;


class KeychainManagerTest {
	
	@Test
	public void testStoreAndLoad() throws KeychainAccessException {
		KeychainManager keychainManager = new KeychainManager(new MapKeychainAccess());
		keychainManager.storePassphrase("test", "asd");
		Assertions.assertArrayEquals("asd".toCharArray(), keychainManager.loadPassphrase("test"));
	}
	
	@Nested
	public static class WhenObservingProperties {

		@BeforeAll
		public static void startup() throws InterruptedException {
			CountDownLatch latch = new CountDownLatch(1);
			Platform.startup(latch::countDown);
			latch.await(5, TimeUnit.SECONDS);
		}
		
		@Test
		public void testPropertyChangesWhenStoringPassword() throws KeychainAccessException, InterruptedException {
			KeychainManager keychainManager = new KeychainManager(new MapKeychainAccess());
			ReadOnlyBooleanProperty property = keychainManager.getPassphraseStoredProperty("test");
			Assertions.assertEquals(false, property.get());
			
			keychainManager.storePassphrase("test", "bar");
			
			AtomicBoolean result = new AtomicBoolean(false);
			CountDownLatch latch = new CountDownLatch(1);
			Platform.runLater(() -> {
				result.set(property.get());
				latch.countDown();
			});
			latch.await(1, TimeUnit.SECONDS);
			Assertions.assertEquals(true, result.get());
		}
		
	}

}
\ No newline at end of file

D main/keychain/src/test/java/org/cryptomator/keychain/KeychainModuleTest.java => main/keychain/src/test/java/org/cryptomator/keychain/KeychainModuleTest.java +0 -24
@@ 1,24 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.keychain;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.Optional;

public class KeychainModuleTest {

	@Test
	public void testGetKeychain() throws KeychainAccessException {
		Optional<KeychainAccess> keychainAccess = DaggerTestKeychainComponent.builder().keychainModule(new TestKeychainModule()).build().keychainAccess();
		Assertions.assertTrue(keychainAccess.isPresent());
		Assertions.assertTrue(keychainAccess.get() instanceof MapKeychainAccess);
		keychainAccess.get().storePassphrase("test", "asd");
		Assertions.assertArrayEquals("asd".toCharArray(), keychainAccess.get().loadPassphrase("test"));
	}

}

D main/keychain/src/test/java/org/cryptomator/keychain/TestKeychainComponent.java => main/keychain/src/test/java/org/cryptomator/keychain/TestKeychainComponent.java +0 -19
@@ 1,19 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.keychain;

import dagger.Component;

import javax.inject.Singleton;
import java.util.Optional;

@Singleton
@Component(modules = KeychainModule.class)
interface TestKeychainComponent {

	Optional<KeychainAccess> keychainAccess();

}

D main/keychain/src/test/java/org/cryptomator/keychain/TestKeychainModule.java => main/keychain/src/test/java/org/cryptomator/keychain/TestKeychainModule.java +0 -17
@@ 1,17 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.keychain;

import java.util.Set;

public class TestKeychainModule extends KeychainModule {

	@Override
	Set<KeychainAccessStrategy> provideKeychainAccessStrategies(MacSystemKeychainAccess macKeychain, WindowsProtectedKeychainAccess winKeychain, LinuxSecretServiceKeychainAccess linKeychain) {
		return Set.of(new MapKeychainAccess());
	}

}

M main/launcher/pom.xml => main/launcher/pom.xml +1 -1
@@ 4,7 4,7 @@
	<parent>
		<groupId>org.cryptomator</groupId>
		<artifactId>main</artifactId>
		<version>1.5.4</version>
		<version>1.5.5</version>
	</parent>
	<artifactId>launcher</artifactId>
	<name>Cryptomator Launcher</name>

M main/pom.xml => main/pom.xml +2 -2
@@ 3,7 3,7 @@
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.cryptomator</groupId>
	<artifactId>main</artifactId>
	<version>1.5.4</version>
	<version>1.5.5</version>
	<packaging>pom</packaging>
	<name>Cryptomator</name>



@@ 24,7 24,7 @@
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

		<!-- cryptomator dependencies -->
		<cryptomator.cryptofs.version>1.9.10</cryptomator.cryptofs.version>
		<cryptomator.cryptofs.version>1.9.11</cryptomator.cryptofs.version>
		<cryptomator.jni.version>2.2.2</cryptomator.jni.version>
		<cryptomator.fuse.version>1.2.3</cryptomator.fuse.version>
		<cryptomator.dokany.version>1.1.15</cryptomator.dokany.version>

M main/ui/pom.xml => main/ui/pom.xml +1 -1
@@ 4,7 4,7 @@
	<parent>
		<groupId>org.cryptomator</groupId>
		<artifactId>main</artifactId>
		<version>1.5.4</version>
		<version>1.5.5</version>
	</parent>
	<artifactId>ui</artifactId>
	<name>Cryptomator GUI</name>

M main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultSuccessController.java => main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultSuccessController.java +2 -1
@@ 9,6 9,7 @@ import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplication;

import javax.inject.Inject;
import java.util.Optional;

@AddVaultWizardScoped
public class AddVaultSuccessController implements FxController {


@@ 27,7 28,7 @@ public class AddVaultSuccessController implements FxController {
	@FXML
	public void unlockAndClose() {
		close();
		fxApplication.startUnlockWorkflow(vault.get());
		fxApplication.startUnlockWorkflow(vault.get(), Optional.of(window));
	}

	@FXML

M main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java => main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java +1 -2
@@ 18,7 18,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.inject.Named;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;


@@ 57,7 56,7 @@ public class ChooseExistingVaultController implements FxController {

	@FXML
	public void initialize() {
		final String resource = SystemUtils.IS_OS_MAC ? "/select-masterkey-mac.png" : "/select-masterkey-win.png";
		final String resource = SystemUtils.IS_OS_MAC ? "/img/select-masterkey-mac.png" : "/img/select-masterkey-win.png";
		try (InputStream in = getClass().getResourceAsStream(resource)) {
			this.screenshot = new Image(in);
		} catch (IOException e) {

M main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java => main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java +13 -7
@@ 29,6 29,7 @@ import java.io.File;
import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ResourceBundle;


@@ 48,7 49,6 @@ public class CreateNewVaultLocationController implements FxController {
	private final StringProperty vaultName;
	private final ResourceBundle resourceBundle;
	private final BooleanBinding validVaultPath;
	private final BooleanBinding invalidVaultPath;
	private final BooleanProperty usePresetPath;
	private final StringProperty warningText;



@@ 71,7 71,6 @@ public class CreateNewVaultLocationController implements FxController {
		this.vaultName = vaultName;
		this.resourceBundle = resourceBundle;
		this.validVaultPath = Bindings.createBooleanBinding(this::isValidVaultPath, vaultPath);
		this.invalidVaultPath = validVaultPath.not();
		this.usePresetPath = new SimpleBooleanProperty();
		this.warningText = new SimpleStringProperty();
	}


@@ 125,6 124,9 @@ public class CreateNewVaultLocationController implements FxController {
		} catch (FileAlreadyExistsException e) {
			LOG.warn("Can not use already existing vault path {}", vaultPath.get());
			warningText.set(resourceBundle.getString("addvaultwizard.new.fileAlreadyExists"));
		} catch (NoSuchFileException e) {
			LOG.warn("At least one path component does not exist of path {}", vaultPath.get());
			warningText.set(resourceBundle.getString("addvaultwizard.new.locationDoesNotExist"));
		} catch (IOException e) {
			LOG.error("Failed to create and delete directory at chosen vault path.", e);
			errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();


@@ 135,7 137,11 @@ public class CreateNewVaultLocationController implements FxController {
	public void chooseCustomVaultPath() {
		DirectoryChooser directoryChooser = new DirectoryChooser();
		directoryChooser.setTitle(resourceBundle.getString("addvaultwizard.new.directoryPickerTitle"));
		directoryChooser.setInitialDirectory(customVaultPath.toFile());
		if (Files.exists(customVaultPath)) {
			directoryChooser.setInitialDirectory(customVaultPath.toFile());
		} else {
			directoryChooser.setInitialDirectory(DEFAULT_CUSTOM_VAULT_PATH.toFile());
		}
		final File file = directoryChooser.showDialog(window);
		if (file != null) {
			customVaultPath = file.toPath().toAbsolutePath();


@@ 153,12 159,12 @@ public class CreateNewVaultLocationController implements FxController {
		return vaultPath;
	}

	public BooleanBinding invalidVaultPathProperty() {
		return invalidVaultPath;
	public BooleanBinding validVaultPathProperty() {
		return validVaultPath;
	}

	public Boolean getInvalidVaultPath() {
		return invalidVaultPath.get();
	public Boolean getValidVaultPath() {
		return validVaultPath.get();
	}

	public LocationPresets getLocationPresets() {

M main/ui/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java => main/ui/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java +3 -3
@@ 10,8 10,8 @@ import javafx.stage.Stage;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.keychain.KeychainAccess;
import org.cryptomator.keychain.KeychainAccessException;
import org.cryptomator.keychain.KeychainManager;
import org.cryptomator.ui.common.Animations;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;


@@ 36,14 36,14 @@ public class ChangePasswordController implements FxController {
	private final Vault vault;
	private final ObjectProperty<CharSequence> newPassword;
	private final ErrorComponent.Builder errorComponent;
	private final Optional<KeychainAccess> keychain;
	private final Optional<KeychainManager> keychain;

	public NiceSecurePasswordField oldPasswordField;
	public CheckBox finalConfirmationCheckbox;
	public Button finishButton;

	@Inject
	public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, @Named("newPassword") ObjectProperty<CharSequence> newPassword, ErrorComponent.Builder errorComponent, Optional<KeychainAccess> keychain) {
	public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, @Named("newPassword") ObjectProperty<CharSequence> newPassword, ErrorComponent.Builder errorComponent, Optional<KeychainManager> keychain) {
		this.window = window;
		this.vault = vault;
		this.newPassword = newPassword;

M main/ui/src/main/java/org/cryptomator/ui/common/VaultService.java => main/ui/src/main/java/org/cryptomator/ui/common/VaultService.java +3 -6
@@ 4,16 4,13 @@ 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.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.keychain.KeychainAccess;
import org.cryptomator.keychain.KeychainManager;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import java.nio.CharBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;


@@ 28,10 25,10 @@ public class VaultService {
	private static final Logger LOG = LoggerFactory.getLogger(VaultService.class);

	private final ExecutorService executorService;
	private final Optional<KeychainAccess> keychain;
	private final Optional<KeychainManager> keychain;

	@Inject
	public VaultService(ExecutorService executorService, Optional<KeychainAccess> keychain) {
	public VaultService(ExecutorService executorService, Optional<KeychainManager> keychain) {
		this.executorService = executorService;
		this.keychain = keychain;
	}

M main/ui/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java => main/ui/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java +1 -0
@@ 26,6 26,7 @@ public enum FontAwesome5Icon {
	INFO_CIRCLE("\uF05A"), //
	KEY("\uF084"), //
	LINK("\uF0C1"), //
	UNLINK("\uf127"),
	LOCK("\uF023"), //
	LOCK_OPEN("\uF3C1"), //
	MAGIC("\uF0D0"), //

M main/ui/src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordController.java => main/ui/src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordController.java +6 -6
@@ 4,8 4,8 @@ import javafx.beans.property.BooleanProperty;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.keychain.KeychainAccess;
import org.cryptomator.keychain.KeychainAccessException;
import org.cryptomator.keychain.KeychainManager;
import org.cryptomator.ui.common.FxController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


@@ 20,14 20,14 @@ public class ForgetPasswordController implements FxController {

	private final Stage window;
	private final Vault vault;
	private final Optional<KeychainAccess> keychainAccess;
	private final Optional<KeychainManager> keychain;
	private final BooleanProperty confirmedResult;

	@Inject
	public ForgetPasswordController(@ForgetPasswordWindow Stage window, @ForgetPasswordWindow Vault vault, Optional<KeychainAccess> keychainAccess, @ForgetPasswordWindow BooleanProperty confirmedResult) {
	public ForgetPasswordController(@ForgetPasswordWindow Stage window, @ForgetPasswordWindow Vault vault, Optional<KeychainManager> keychain, @ForgetPasswordWindow BooleanProperty confirmedResult) {
		this.window = window;
		this.vault = vault;
		this.keychainAccess = keychainAccess;
		this.keychain = keychain;
		this.confirmedResult = confirmedResult;
	}



@@ 38,9 38,9 @@ public class ForgetPasswordController implements FxController {

	@FXML
	public void finish() {
		if (keychainAccess.isPresent()) {
		if (keychain.isPresent()) {
			try {
				keychainAccess.get().deletePassphrase(vault.getId());
				keychain.get().deletePassphrase(vault.getId());
				LOG.debug("Forgot password for vault {}.", vault.getDisplayableName());
				confirmedResult.setValue(true);
			} catch (KeychainAccessException e) {

M main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java => main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java +2 -2
@@ 96,9 96,9 @@ public class FxApplication extends Application {
		});
	}

	public void startUnlockWorkflow(Vault vault) {
	public void startUnlockWorkflow(Vault vault, Optional<Stage> owner) {
		Platform.runLater(() -> {
			unlockWindowBuilderProvider.get().vault(vault).build().startUnlockWorkflow();
			unlockWindowBuilderProvider.get().vault(vault).owner(owner).build().startUnlockWorkflow();
			LOG.debug("Showing UnlockWindow for {}", vault.getDisplayableName());
		});
	}

M main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java => main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java +2 -5
@@ 9,14 9,11 @@ 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.collections.FXCollections;
import javafx.collections.ObservableSet;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.StageFactory;
import org.cryptomator.ui.mainwindow.MainWindowComponent;


@@ 49,8 46,8 @@ abstract class FxApplicationModule {
		}
		try {
			return List.of( //
					createImageFromResource("/window_icon_32.png"), //
					createImageFromResource("/window_icon_512.png") //
					createImageFromResource("/img/window_icon_32.png"), //
					createImageFromResource("/img/window_icon_512.png") //
			);
		} catch (IOException e) {
			throw new UncheckedIOException("Failed to load embedded resource.", e);

M main/ui/src/main/java/org/cryptomator/ui/launcher/UiLauncher.java => main/ui/src/main/java/org/cryptomator/ui/launcher/UiLauncher.java +8 -4
@@ 62,11 62,11 @@ public class UiLauncher {
		Desktop.getDesktop().addAppEventListener((AppReopenedListener) e -> showMainWindowAsync(hasTrayIcon));

		// auto unlock
		Collection<Vault> vaultsWithAutoUnlockEnabled = vaults.filtered(v -> v.getVaultSettings().unlockAfterStartup().get());
		if (!vaultsWithAutoUnlockEnabled.isEmpty()) {
		Collection<Vault> vaultsToAutoUnlock = vaults.filtered(this::shouldAttemptAutoUnlock);
		if (!vaultsToAutoUnlock.isEmpty()) {
			fxApplicationStarter.get(hasTrayIcon).thenAccept(app -> {
				for (Vault vault : vaultsWithAutoUnlockEnabled){
					app.startUnlockWorkflow(vault);
				for (Vault vault : vaultsToAutoUnlock) {
					app.startUnlockWorkflow(vault, Optional.empty());
				}
			});
		}


@@ 74,6 74,10 @@ public class UiLauncher {
		launchEventHandler.startHandlingLaunchEvents(hasTrayIcon);
	}

	private boolean shouldAttemptAutoUnlock(Vault vault) {
		return vault.isLocked() && vault.getVaultSettings().unlockAfterStartup().get();
	}

	private void showMainWindowAsync(boolean hasTrayIcon) {
		fxApplicationStarter.get(hasTrayIcon).thenAccept(FxApplication::showMainWindow);
	}

M main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java => main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java +29 -3
@@ 1,14 1,20 @@
package org.cryptomator.ui.mainwindow;

import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.keychain.KeychainManager;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplication;
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
import org.fxmisc.easybind.EasyBind;

import javax.inject.Inject;
import java.util.Optional;

@MainWindowScoped
public class VaultDetailLockedController implements FxController {


@@ 16,24 22,34 @@ public class VaultDetailLockedController implements FxController {
	private final ReadOnlyObjectProperty<Vault> vault;
	private final FxApplication application;
	private final VaultOptionsComponent.Builder vaultOptionsWindow;
	private final Optional<KeychainManager> keychainManagerOptional;
	private final Stage mainWindow;
	private final BooleanExpression passwordSaved;

	@Inject
	VaultDetailLockedController(ObjectProperty<Vault> vault, FxApplication application, VaultOptionsComponent.Builder vaultOptionsWindow) {
	VaultDetailLockedController(ObjectProperty<Vault> vault, FxApplication application, VaultOptionsComponent.Builder vaultOptionsWindow, Optional<KeychainManager> keychainManagerOptional, @MainWindow Stage mainWindow) {
		this.vault = vault;
		this.application = application;
		this.vaultOptionsWindow = vaultOptionsWindow;
		this.keychainManagerOptional = keychainManagerOptional;
		this.mainWindow = mainWindow;
		if (keychainManagerOptional.isPresent()) {
			this.passwordSaved = BooleanExpression.booleanExpression(EasyBind.select(vault).selectObject(v -> keychainManagerOptional.get().getPassphraseStoredProperty(v.getId())));
		} else {
			this.passwordSaved = new SimpleBooleanProperty(false);
		}
	}

	@FXML
	public void unlock() {
		application.startUnlockWorkflow(vault.get());
		application.startUnlockWorkflow(vault.get(), Optional.of(mainWindow));
	}

	@FXML
	public void showVaultOptions() {
		vaultOptionsWindow.vault(vault.get()).build().showVaultOptionsWindow();
	}
	

	/* Getter/Setter */

	public ReadOnlyObjectProperty<Vault> vaultProperty() {


@@ 43,4 59,14 @@ public class VaultDetailLockedController implements FxController {
	public Vault getVault() {
		return vault.get();
	}

	public BooleanExpression passwordSavedProperty() {
		return passwordSaved;
	}

	public boolean isPasswordSaved() {
		if (keychainManagerOptional.isPresent() && vault.get() != null) {
			return keychainManagerOptional.get().getPassphraseStoredProperty(vault.get().getId()).get();
		} else return false;
	}
}

M main/ui/src/main/java/org/cryptomator/ui/mainwindow/WelcomeController.java => main/ui/src/main/java/org/cryptomator/ui/mainwindow/WelcomeController.java +1 -0
@@ 42,4 42,5 @@ public class WelcomeController implements FxController {
	public boolean isNoVaultPresent() {
		return noVaultPresent.get();
	}
	
}

M main/ui/src/main/java/org/cryptomator/ui/migration/MigrationRunController.java => main/ui/src/main/java/org/cryptomator/ui/migration/MigrationRunController.java +7 -7
@@ 22,8 22,8 @@ import org.cryptomator.cryptofs.migration.Migrators;
import org.cryptomator.cryptofs.migration.api.MigrationContinuationListener;
import org.cryptomator.cryptofs.migration.api.MigrationProgressListener;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.keychain.KeychainAccess;
import org.cryptomator.keychain.KeychainAccessException;
import org.cryptomator.keychain.KeychainManager;
import org.cryptomator.ui.common.Animations;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;


@@ 55,7 55,7 @@ public class MigrationRunController implements FxController {
	private final Vault vault;
	private final ExecutorService executor;
	private final ScheduledExecutorService scheduler;
	private final Optional<KeychainAccess> keychainAccess;
	private final Optional<KeychainManager> keychain;
	private final ObjectProperty<FileSystemCapabilityChecker.Capability> missingCapability;
	private final ErrorComponent.Builder errorComponent;
	private final Lazy<Scene> startScene;


@@ 69,13 69,13 @@ public class MigrationRunController implements FxController {
	public NiceSecurePasswordField passwordField;

	@Inject
	public MigrationRunController(@MigrationWindow Stage window, @MigrationWindow Vault vault, ExecutorService executor, ScheduledExecutorService scheduler, Optional<KeychainAccess> keychainAccess, @Named("capabilityErrorCause") ObjectProperty<FileSystemCapabilityChecker.Capability> missingCapability, @FxmlScene(FxmlFile.MIGRATION_START) Lazy<Scene> startScene, @FxmlScene(FxmlFile.MIGRATION_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.MIGRATION_CAPABILITY_ERROR) Lazy<Scene> capabilityErrorScene, @FxmlScene(FxmlFile.MIGRATION_IMPOSSIBLE) Lazy<Scene> impossibleScene, ErrorComponent.Builder errorComponent) {
	public MigrationRunController(@MigrationWindow Stage window, @MigrationWindow Vault vault, ExecutorService executor, ScheduledExecutorService scheduler, Optional<KeychainManager> keychain, @Named("capabilityErrorCause") ObjectProperty<FileSystemCapabilityChecker.Capability> missingCapability, @FxmlScene(FxmlFile.MIGRATION_START) Lazy<Scene> startScene, @FxmlScene(FxmlFile.MIGRATION_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.MIGRATION_CAPABILITY_ERROR) Lazy<Scene> capabilityErrorScene, @FxmlScene(FxmlFile.MIGRATION_IMPOSSIBLE) Lazy<Scene> impossibleScene, ErrorComponent.Builder errorComponent) {

		this.window = window;
		this.vault = vault;
		this.executor = executor;
		this.scheduler = scheduler;
		this.keychainAccess = keychainAccess;
		this.keychain = keychain;
		this.missingCapability = missingCapability;
		this.errorComponent = errorComponent;
		this.startScene = startScene;


@@ 88,7 88,7 @@ public class MigrationRunController implements FxController {
	}

	public void initialize() {
		if (keychainAccess.isPresent()) {
		if (keychain.isPresent()) {
			loadStoredPassword();
		}
		migrationButtonDisabled.bind(vault.stateProperty().isNotEqualTo(VaultState.NEEDS_MIGRATION).or(passwordField.textProperty().isEmpty()));


@@ 167,10 167,10 @@ public class MigrationRunController implements FxController {
	}

	private void loadStoredPassword() {
		assert keychainAccess.isPresent();
		assert keychain.isPresent();
		char[] storedPw = null;
		try {
			storedPw = keychainAccess.get().loadPassphrase(vault.getId());
			storedPw = keychain.get().loadPassphrase(vault.getId());
			if (storedPw != null) {
				passwordField.setPassword(storedPw);
				passwordField.selectRange(storedPw.length, storedPw.length);

M main/ui/src/main/java/org/cryptomator/ui/migration/MigrationSuccessController.java => main/ui/src/main/java/org/cryptomator/ui/migration/MigrationSuccessController.java +6 -2
@@ 5,8 5,10 @@ import javafx.stage.Stage;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplication;
import org.cryptomator.ui.mainwindow.MainWindow;

import javax.inject.Inject;
import java.util.Optional;

@MigrationScoped
public class MigrationSuccessController implements FxController {


@@ 14,18 16,20 @@ public class MigrationSuccessController implements FxController {
	private final FxApplication fxApplication;
	private final Stage window;
	private final Vault vault;
	private final Stage mainWindow;

	@Inject
	MigrationSuccessController(FxApplication fxApplication, @MigrationWindow Stage window, @MigrationWindow Vault vault) {
	MigrationSuccessController(FxApplication fxApplication, @MigrationWindow Stage window, @MigrationWindow Vault vault, @MainWindow Stage mainWindow) {
		this.fxApplication = fxApplication;
		this.window = window;
		this.vault = vault;
		this.mainWindow = mainWindow;
	}

	@FXML
	public void unlockAndClose() {
		close();
		fxApplication.startUnlockWorkflow(vault);
		fxApplication.startUnlockWorkflow(vault, Optional.of(mainWindow));
	}

	@FXML

M main/ui/src/main/java/org/cryptomator/ui/traymenu/TrayImageFactory.java => main/ui/src/main/java/org/cryptomator/ui/traymenu/TrayImageFactory.java +3 -3
@@ 30,13 30,13 @@ class TrayImageFactory {
				.map(MacApplicationUiAppearance::getCurrentInterfaceStyle) //
				.orElse(MacApplicationUiInterfaceStyle.LIGHT);
		return switch (interfaceStyle) {
			case DARK -> "/tray_icon_mac_white.png";
			case LIGHT -> "/tray_icon_mac_black.png";
			case DARK -> "/img/tray_icon_mac_white.png";
			case LIGHT -> "/img/tray_icon_mac_black.png";
		};
	}

	private String getWinOrLinuxResourceName() {
		return "/tray_icon.png";
		return "/img/tray_icon.png";
	}

}

M main/ui/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java => main/ui/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java +2 -1
@@ 15,6 15,7 @@ import java.awt.PopupMenu;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.EventObject;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.function.Consumer;



@@ 103,7 104,7 @@ class TrayMenuController {
	}

	private void unlockVault(Vault vault) {
		fxApplicationStarter.get(true).thenAccept(app -> app.startUnlockWorkflow(vault));
		fxApplicationStarter.get(true).thenAccept(app -> app.startUnlockWorkflow(vault, Optional.empty()));
	}

	private void lockVault(Vault vault) {

M main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockComponent.java => main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockComponent.java +7 -8
@@ 6,18 6,14 @@
package org.cryptomator.ui.unlock;

import dagger.BindsInstance;
import dagger.Lazy;
import dagger.Subcomponent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.common.vaults.Vault;

import java.util.concurrent.Executor;
import javax.inject.Named;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

@UnlockScoped
@Subcomponent(modules = {UnlockModule.class})


@@ 26,7 22,7 @@ public interface UnlockComponent {
	ExecutorService defaultExecutorService();

	UnlockWorkflow unlockWorkflow();
	

	default Future<Boolean> startUnlockWorkflow() {
		UnlockWorkflow workflow = unlockWorkflow();
		defaultExecutorService().submit(workflow);


@@ 35,10 31,13 @@ public interface UnlockComponent {

	@Subcomponent.Builder
	interface Builder {
		

		@BindsInstance
		Builder vault(@UnlockWindow Vault vault);

		@BindsInstance
		Builder owner(@Named("unlockWindowOwner") Optional<Stage> owner);

		UnlockComponent build();
	}


M main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockController.java => main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockController.java +80 -5
@@ 1,5 1,10 @@
package org.cryptomator.ui.unlock;

import javafx.animation.Animation;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.ObjectBinding;


@@ 9,9 14,13 @@ import javafx.beans.property.SimpleBooleanProperty;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ContentDisplay;
import javafx.scene.image.ImageView;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
import javafx.util.Duration;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.keychain.KeychainAccess;
import org.cryptomator.keychain.KeychainManager;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.UserInteractionLock;
import org.cryptomator.ui.controls.NiceSecurePasswordField;


@@ 38,15 47,22 @@ public class UnlockController implements FxController {
	private final Optional<char[]> savedPassword;
	private final UserInteractionLock<UnlockModule.PasswordEntry> passwordEntryLock;
	private final ForgetPasswordComponent.Builder forgetPassword;
	private final Optional<KeychainAccess> keychainAccess;
	private final Optional<KeychainManager> keychain;
	private final ObjectBinding<ContentDisplay> unlockButtonContentDisplay;
	private final BooleanBinding userInteractionDisabled;
	private final BooleanProperty unlockButtonDisabled;
	
	public NiceSecurePasswordField passwordField;
	public CheckBox savePasswordCheckbox;
	public ImageView face;
	public ImageView leftArm;
	public ImageView rightArm;
	public ImageView legs;
	public ImageView body;
	public Animation unlockAnimation;

	@Inject
	public UnlockController(@UnlockWindow Stage window, @UnlockWindow Vault vault, AtomicReference<char[]> password, @Named("savePassword") AtomicBoolean savePassword, @Named("savedPassword") Optional<char[]> savedPassword, UserInteractionLock<UnlockModule.PasswordEntry> passwordEntryLock, ForgetPasswordComponent.Builder forgetPassword, Optional<KeychainAccess> keychainAccess) {
	public UnlockController(@UnlockWindow Stage window, @UnlockWindow Vault vault, AtomicReference<char[]> password, @Named("savePassword") AtomicBoolean savePassword, @Named("savedPassword") Optional<char[]> savedPassword, UserInteractionLock<UnlockModule.PasswordEntry> passwordEntryLock, ForgetPasswordComponent.Builder forgetPassword, Optional<KeychainManager> keychain) {
		this.window = window;
		this.vault = vault;
		this.password = password;


@@ 54,20 70,61 @@ public class UnlockController implements FxController {
		this.savedPassword = savedPassword;
		this.passwordEntryLock = passwordEntryLock;
		this.forgetPassword = forgetPassword;
		this.keychainAccess = keychainAccess;
		this.keychain = keychain;
		this.unlockButtonContentDisplay = Bindings.createObjectBinding(this::getUnlockButtonContentDisplay, passwordEntryLock.awaitingInteraction());
		this.userInteractionDisabled = passwordEntryLock.awaitingInteraction().not();
		this.unlockButtonDisabled = new SimpleBooleanProperty();
		this.window.setOnCloseRequest(windowEvent -> cancel());
	}

	@FXML
	public void initialize() {
		savePasswordCheckbox.setSelected(savedPassword.isPresent());
		if (password.get() != null) {
			passwordField.setPassword(password.get());
		}
		unlockButtonDisabled.bind(userInteractionDisabled.or(passwordField.textProperty().isEmpty()));
		
		var leftArmTranslation = new Translate(24, 0);
		var leftArmRotation = new Rotate(60, 16, 30, 0);
		var leftArmRetracted = new KeyValue(leftArmTranslation.xProperty(), 24);
		var leftArmExtended = new KeyValue(leftArmTranslation.xProperty(), 0.0);
		var leftArmHorizontal = new KeyValue(leftArmRotation.angleProperty(), 60, Interpolator.EASE_OUT);
		var leftArmHanging = new KeyValue(leftArmRotation.angleProperty(), 0);
		leftArm.getTransforms().setAll(leftArmTranslation, leftArmRotation);

		var rightArmTranslation = new Translate(-24, 0);
		var rightArmRotation = new Rotate(60, 48, 30, 0);
		var rightArmRetracted = new KeyValue(rightArmTranslation.xProperty(), -24);
		var rightArmExtended = new KeyValue(rightArmTranslation.xProperty(), 0.0);
		var rightArmHorizontal = new KeyValue(rightArmRotation.angleProperty(), -60);
		var rightArmHanging = new KeyValue(rightArmRotation.angleProperty(), 0, Interpolator.EASE_OUT);
		rightArm.getTransforms().setAll(rightArmTranslation, rightArmRotation);

		var legsRetractedY = new KeyValue(legs.scaleYProperty(), 0);
		var legsExtendedY = new KeyValue(legs.scaleYProperty(), 1, Interpolator.EASE_OUT);
		var legsRetractedX = new KeyValue(legs.scaleXProperty(), 0);
		var legsExtendedX = new KeyValue(legs.scaleXProperty(), 1, Interpolator.EASE_OUT);
		legs.setScaleY(0);
		legs.setScaleX(0);

		var faceHidden = new KeyValue(face.opacityProperty(), 0.0);
		var faceVisible = new KeyValue(face.opacityProperty(), 1.0, Interpolator.LINEAR);
		face.setOpacity(0);

		unlockAnimation = new Timeline(
				new KeyFrame(Duration.ZERO, leftArmRetracted, leftArmHorizontal, rightArmRetracted, rightArmHorizontal, legsRetractedY, legsRetractedX, faceHidden),
				new KeyFrame(Duration.millis(200), leftArmExtended, leftArmHorizontal, rightArmRetracted, rightArmHorizontal),
				new KeyFrame(Duration.millis(400), leftArmExtended, leftArmHanging, rightArmExtended, rightArmHorizontal),
				new KeyFrame(Duration.millis(600), leftArmExtended, leftArmHanging, rightArmExtended, rightArmHanging),
				new KeyFrame(Duration.millis(800), legsExtendedY, legsExtendedX, faceHidden),
				new KeyFrame(Duration.millis(1000), faceVisible)
		);
		
		passwordEntryLock.awaitingInteraction().addListener(observable -> stopUnlockAnimation());
	}


	@FXML
	public void cancel() {
		LOG.debug("Unlock canceled by user.");


@@ 75,6 132,7 @@ public class UnlockController implements FxController {
		passwordEntryLock.interacted(UnlockModule.PasswordEntry.CANCELED);
	}


	@FXML
	public void unlock() {
		LOG.trace("UnlockController.unlock()");


@@ 88,6 146,23 @@ public class UnlockController implements FxController {
			Arrays.fill(oldPw, ' ');
		}
		passwordEntryLock.interacted(UnlockModule.PasswordEntry.PASSWORD_ENTERED);
		startUnlockAnimation();
	}
	
	private void startUnlockAnimation() {
		leftArm.setVisible(true);
		rightArm.setVisible(true);
		legs.setVisible(true);
		face.setVisible(true);
		unlockAnimation.playFromStart();
	}

	private void stopUnlockAnimation() {
		unlockAnimation.stop();
		leftArm.setVisible(false);
		rightArm.setVisible(false);
		legs.setVisible(false);
		face.setVisible(false);
	}

	/* Save Password */


@@ 131,6 206,6 @@ public class UnlockController implements FxController {
	}

	public boolean isKeychainAccessAvailable() {
		return keychainAccess.isPresent();
		return keychain.isPresent();
	}
}

M main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java => main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java +11 -12
@@ 5,12 5,11 @@ import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
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.keychain.KeychainAccess;
import org.cryptomator.keychain.KeychainAccessException;
import org.cryptomator.keychain.KeychainManager;
import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FXMLLoaderFactory;
import org.cryptomator.ui.common.FxController;


@@ 25,16 24,11 @@ import org.slf4j.LoggerFactory;

import javax.inject.Named;
import javax.inject.Provider;
import java.nio.CharBuffer;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Module(subcomponents = {ForgetPasswordComponent.class})
abstract class UnlockModule {


@@ 52,8 46,8 @@ abstract class UnlockModule {
	@Provides
	@Named("savedPassword")
	@UnlockScoped
	static Optional<char[]> provideStoredPassword(Optional<KeychainAccess> keychainAccess, @UnlockWindow Vault vault) {
		return keychainAccess.map(k -> {
	static Optional<char[]> provideStoredPassword(Optional<KeychainManager> keychain, @UnlockWindow Vault vault) {
		return keychain.map(k -> {
			try {
				return k.loadPassphrase(vault.getId());
			} catch (KeychainAccessException e) {


@@ 62,7 56,7 @@ abstract class UnlockModule {
			}
		});
	}
	

	@Provides
	@UnlockScoped
	static AtomicReference<char[]> providePassword(@Named("savedPassword") Optional<char[]> storedPassword) {


@@ 86,11 80,16 @@ abstract class UnlockModule {
	@Provides
	@UnlockWindow
	@UnlockScoped
	static Stage provideStage(StageFactory factory, @UnlockWindow Vault vault) {
	static Stage provideStage(StageFactory factory, @UnlockWindow Vault vault, @Named("unlockWindowOwner") Optional<Stage> owner) {
		Stage stage = factory.create();
		stage.setTitle(vault.getDisplayableName());
		stage.setResizable(false);
		stage.initModality(Modality.APPLICATION_MODAL);
		if (owner.isPresent()) {
			stage.initOwner(owner.get());
			stage.initModality(Modality.WINDOW_MODAL);
		} else {
			stage.initModality(Modality.APPLICATION_MODAL);
		}
		return stage;
	}


M main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockSuccessController.java => main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockSuccessController.java +1 -1
@@ 31,7 31,7 @@ public class UnlockSuccessController implements FxController {
	private final VaultService vaultService;
	private final ObjectProperty<ContentDisplay> revealButtonState;
	private final BooleanProperty revealButtonDisabled;
	

	public CheckBox rememberChoiceCheckbox;

	@Inject

M main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java => main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java +12 -4
@@ 5,13 5,14 @@ import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.Window;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.common.vaults.Volume;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.keychain.KeychainAccess;
import org.cryptomator.keychain.KeychainAccessException;
import org.cryptomator.keychain.KeychainManager;
import org.cryptomator.ui.common.Animations;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxmlFile;


@@ 51,14 52,14 @@ public class UnlockWorkflow extends Task<Boolean> {
	private final AtomicBoolean savePassword;
	private final Optional<char[]> savedPassword;
	private final UserInteractionLock<PasswordEntry> passwordEntryLock;
	private final Optional<KeychainAccess> keychain;
	private final Optional<KeychainManager> keychain;
	private final Lazy<Scene> unlockScene;
	private final Lazy<Scene> successScene;
	private final Lazy<Scene> invalidMountPointScene;
	private final ErrorComponent.Builder errorComponent;

	@Inject
	UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, AtomicReference<char[]> password, @Named("savePassword") AtomicBoolean savePassword, @Named("savedPassword") Optional<char[]> savedPassword, UserInteractionLock<PasswordEntry> passwordEntryLock, Optional<KeychainAccess> keychain, @FxmlScene(FxmlFile.UNLOCK) Lazy<Scene> unlockScene, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, ErrorComponent.Builder errorComponent) {
	UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, AtomicReference<char[]> password, @Named("savePassword") AtomicBoolean savePassword, @Named("savedPassword") Optional<char[]> savedPassword, UserInteractionLock<PasswordEntry> passwordEntryLock, Optional<KeychainManager> keychain, @FxmlScene(FxmlFile.UNLOCK) Lazy<Scene> unlockScene, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, ErrorComponent.Builder errorComponent) {
		this.window = window;
		this.vault = vault;
		this.vaultService = vaultService;


@@ 94,7 95,7 @@ public class UnlockWorkflow extends Task<Boolean> {
			wipePassword(savedPassword.orElse(null));
		}
	}
	

	private boolean attemptUnlock() throws InterruptedException, IOException, Volume.VolumeException {
		boolean proceed = password.get() != null || askForPassword(false) == PasswordEntry.PASSWORD_ENTERED;
		while (proceed) {


@@ 112,6 113,13 @@ public class UnlockWorkflow extends Task<Boolean> {
		Platform.runLater(() -> {
			window.setScene(unlockScene.get());
			window.show();
			Window owner = window.getOwner();
			if (owner != null) {
				window.setX(owner.getX() + (owner.getWidth() - window.getWidth()) / 2);
				window.setY(owner.getY() + (owner.getHeight() - window.getHeight()) / 2);
			} else {
				window.centerOnScreen();
			}
			if (animateShake) {
				Animations.createShakeWindowAnimation(window).play();
			}

M main/ui/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java => main/ui/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java +30 -1
@@ 1,13 1,19 @@
package org.cryptomator.ui.vaultoptions;

import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.keychain.KeychainAccessException;
import org.cryptomator.keychain.KeychainManager;
import org.cryptomator.ui.changepassword.ChangePasswordComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;

import javax.inject.Inject;
import java.util.Optional;

@VaultOptionsScoped
public class MasterkeyOptionsController implements FxController {


@@ 16,13 22,20 @@ public class MasterkeyOptionsController implements FxController {
	private final Stage window;
	private final ChangePasswordComponent.Builder changePasswordWindow;
	private final RecoveryKeyComponent.Builder recoveryKeyWindow;
	private final Optional<KeychainManager> keychainManagerOptional;
	private final BooleanExpression passwordSaved;


	@Inject
	MasterkeyOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow, RecoveryKeyComponent.Builder recoveryKeyWindow) {
	MasterkeyOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow, RecoveryKeyComponent.Builder recoveryKeyWindow, Optional<KeychainManager> keychainManagerOptional) {
		this.vault = vault;
		this.window = window;
		this.changePasswordWindow = changePasswordWindow;
		this.recoveryKeyWindow = recoveryKeyWindow;
		this.keychainManagerOptional = keychainManagerOptional;
		if (keychainManagerOptional.isPresent()) {
			this.passwordSaved = Bindings.createBooleanBinding(this::isPasswordSaved, keychainManagerOptional.get().getPassphraseStoredProperty(vault.getId()));
		} else this.passwordSaved = new SimpleBooleanProperty(false);
	}

	@FXML


@@ 39,4 52,20 @@ public class MasterkeyOptionsController implements FxController {
	public void showRecoverVaultDialogue() {
		recoveryKeyWindow.vault(vault).owner(window).build().showRecoveryKeyRecoverWindow();
	}

	@FXML
	public void removePasswordFromKeychain() throws KeychainAccessException {
		keychainManagerOptional.get().deletePassphrase(vault.getId());
		window.close();
	}

	public BooleanExpression passwordSavedProperty() {
		return passwordSaved;
	}

	public boolean isPasswordSaved() {
		if (keychainManagerOptional.isPresent() && vault != null) {
			return keychainManagerOptional.get().getPassphraseStoredProperty(vault.getId()).get();
		} else return false;
	}
}

M main/ui/src/main/java/org/cryptomator/ui/wrongfilealert/WrongFileAlertController.java => main/ui/src/main/java/org/cryptomator/ui/wrongfilealert/WrongFileAlertController.java +1 -1
@@ 30,7 30,7 @@ public class WrongFileAlertController implements FxController {

	@FXML
	public void initialize() {
		final String resource = SystemUtils.IS_OS_MAC ? "/vault-volume-mac.png" : "/vault-volume-win.png";
		final String resource = SystemUtils.IS_OS_MAC ? "/img/vault-volume-mac.png" : "/img/vault-volume-win.png";
		try (InputStream in = getClass().getResourceAsStream(resource)) {
			this.screenshot = new Image(in);
		} catch (IOException e) {

M main/ui/src/main/resources/fxml/addvault_new_location.fxml => main/ui/src/main/resources/fxml/addvault_new_location.fxml +1 -1
@@ 60,7 60,7 @@
		<ButtonBar buttonMinWidth="120" buttonOrder="B+X">
			<buttons>
				<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" onAction="#back"/>
				<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" onAction="#next" defaultButton="true" disable="${controller.invalidVaultPath}"/>
				<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" onAction="#next" defaultButton="true" disable="${!controller.validVaultPath}"/>
			</buttons>
		</ButtonBar>
	</children>

M main/ui/src/main/resources/fxml/addvault_welcome.fxml => main/ui/src/main/resources/fxml/addvault_welcome.fxml +1 -1
@@ 21,7 21,7 @@
		<Region VBox.vgrow="ALWAYS"/>

		<ImageView VBox.vgrow="ALWAYS" fitHeight="128" preserveRatio="true" smooth="true" cache="true">
			<Image url="/bot.png"/>
			<Image url="/img/bot/bot.png"/>
		</ImageView>

		<Region VBox.vgrow="ALWAYS"/>

M main/ui/src/main/resources/fxml/preferences_about.fxml => main/ui/src/main/resources/fxml/preferences_about.fxml +1 -1
@@ 18,7 18,7 @@
	<children>
		<HBox spacing="12" VBox.vgrow="NEVER">
			<ImageView VBox.vgrow="ALWAYS" fitHeight="64" preserveRatio="true" smooth="true" cache="true">
				<Image url="/bot.png"/>
				<Image url="/img/bot/bot.png"/>
			</ImageView>
			<VBox spacing="3" HBox.hgrow="ALWAYS" alignment="CENTER_LEFT">
				<FormattedLabel styleClass="label-large" format="Cryptomator %s" arg1="${controller.applicationVersion}"/>

M main/ui/src/main/resources/fxml/unlock.fxml => main/ui/src/main/resources/fxml/unlock.fxml +32 -5
@@ 5,6 5,10 @@
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.ProgressIndicator?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import org.cryptomator.ui.controls.FormattedLabel?>
<?import org.cryptomator.ui.controls.NiceSecurePasswordField?>


@@ 19,11 23,34 @@
		<Insets topRightBottomLeft="12"/>
	</padding>
	<children>
		<VBox spacing="6">
			<FormattedLabel format="%unlock.passwordPrompt" arg1="${controller.vault.displayableName}" wrapText="true"/>
			<NiceSecurePasswordField fx:id="passwordField" disable="${controller.userInteractionDisabled}"/>
			<CheckBox fx:id="savePasswordCheckbox" text="%unlock.savePassword" onAction="#didClickSavePasswordCheckbox" disable="${controller.userInteractionDisabled}" visible="${controller.keychainAccessAvailable}"/>
		</VBox>
		<HBox spacing="12" VBox.vgrow="ALWAYS">
			<StackPane alignment="CENTER" HBox.hgrow="NEVER">
				<ImageView VBox.vgrow="ALWAYS" fitWidth="64" preserveRatio="true" smooth="true" cache="true" fx:id="face" visible="false">
					<Image url="/img/bot/face.png"/>
				</ImageView>

				<ImageView VBox.vgrow="ALWAYS" fitWidth="64" preserveRatio="true" smooth="true" cache="true" fx:id="leftArm" visible="false">
					<Image url="/img/bot/arm-l.png"/>
				</ImageView>

				<ImageView VBox.vgrow="ALWAYS" fitWidth="64" preserveRatio="true" smooth="true" cache="true" fx:id="rightArm" visible="false">
					<Image url="/img/bot/arm-r.png"/>
				</ImageView>

				<ImageView VBox.vgrow="ALWAYS" fitWidth="64" preserveRatio="true" smooth="true" cache="true" fx:id="legs" visible="false">
					<Image url="/img/bot/legs.png"/>
				</ImageView>

				<ImageView VBox.vgrow="ALWAYS" fitWidth="64" preserveRatio="true" smooth="true" cache="true" fx:id="body">
					<Image url="/img/bot/body.png"/>
				</ImageView>
			</StackPane>
			<VBox spacing="6" HBox.hgrow="ALWAYS">
				<FormattedLabel format="%unlock.passwordPrompt" arg1="${controller.vault.displayableName}" wrapText="true"/>
				<NiceSecurePasswordField fx:id="passwordField" disable="${controller.userInteractionDisabled}"/>
				<CheckBox fx:id="savePasswordCheckbox" text="%unlock.savePassword" onAction="#didClickSavePasswordCheckbox" disable="${controller.userInteractionDisabled}" visible="${controller.keychainAccessAvailable}"/>
			</VBox>
		</HBox>

		<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
			<ButtonBar buttonMinWidth="120" buttonOrder="+CI">

M main/ui/src/main/resources/fxml/vault_detail.fxml => main/ui/src/main/resources/fxml/vault_detail.fxml +1 -1
@@ 14,7 14,7 @@
	  xmlns:fx="http://javafx.com/fxml"
	  fx:controller="org.cryptomator.ui.mainwindow.VaultDetailController"
	  minWidth="300"
	  spacing="36">
	  spacing="60">
	<padding>
		<Insets topRightBottomLeft="24"/>
	</padding>

M main/ui/src/main/resources/fxml/vault_detail_locked.fxml => main/ui/src/main/resources/fxml/vault_detail_locked.fxml +19 -5
@@ 1,8 1,10 @@
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<VBox xmlns="http://javafx.com/javafx"


@@ 10,11 12,15 @@
	  fx:controller="org.cryptomator.ui.mainwindow.VaultDetailLockedController"
	  alignment="TOP_CENTER"
	  spacing="9">
	<padding>
		<Insets topRightBottomLeft="24"/>
	</padding>
	<children>
		<Button styleClass="button-large" text="%main.vaultDetail.unlockBtn" minWidth="120" onAction="#unlock" defaultButton="${controller.vault.locked}">
		<Button styleClass="button-large" text="%main.vaultDetail.unlockBtn" minWidth="120" onAction="#unlock" defaultButton="${controller.vault.locked}" visible="${!controller.passwordSaved}"
				managed="${!controller.passwordSaved}">
			<graphic>
				<FontAwesome5IconView glyph="KEY" glyphSize="15"/>
			</graphic>
		</Button>
		<Button styleClass="button-large" text="%main.vaultDetail.unlockNowBtn" minWidth="120" onAction="#unlock" defaultButton="${controller.vault.locked}" visible="${controller.passwordSaved}"
				managed="${controller.passwordSaved}">
			<graphic>
				<FontAwesome5IconView glyph="KEY" glyphSize="15"/>
			</graphic>


@@ 24,5 30,13 @@
				<FontAwesome5IconView glyph="COG"/>
			</graphic>
		</Hyperlink>
		<Region VBox.vgrow="ALWAYS"/>
		<HBox alignment="CENTER_RIGHT" spacing="6">
			<Label styleClass="label-small,label-muted" text="%main.vaultDetail.passwordSavedInKeychain" visible="${controller.passwordSaved}">
				<graphic>
					<FontAwesome5IconView styleClass="glyph-icon-muted" glyph="LOCK"/>
				</graphic>
			</Label>
		</HBox>
	</children>
</VBox>

M main/ui/src/main/resources/fxml/vault_detail_missing.fxml => main/ui/src/main/resources/fxml/vault_detail_missing.fxml +0 -3
@@ 9,9 9,6 @@
	  fx:controller="org.cryptomator.ui.mainwindow.VaultDetailMissingVaultController"
	  alignment="TOP_CENTER"
	  spacing="9">
	<padding>
		<Insets topRightBottomLeft="24"/>
	</padding>
	<children>
		<StackPane alignment="CENTER">
			<Circle styleClass="glyph-icon-primary" radius="48"/>

M main/ui/src/main/resources/fxml/vault_detail_needsmigration.fxml => main/ui/src/main/resources/fxml/vault_detail_needsmigration.fxml +1 -5
@@ 1,16 1,12 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.control.Label?>
<VBox xmlns="http://javafx.com/javafx"
	  xmlns:fx="http://javafx.com/fxml"
	  fx:controller="org.cryptomator.ui.mainwindow.VaultDetailNeedsMigrationController"
	  alignment="TOP_CENTER"
	  spacing="9">
	<padding>
		<Insets topRightBottomLeft="24"/>
	</padding>
	<children>
		<Label text="%main.vaultDetail.migratePrompt" wrapText="true"/>
		<Button styleClass="button-large" text="%main.vaultDetail.migrateButton" minWidth="120" onAction="#showVaultMigrator">

M main/ui/src/main/resources/fxml/vault_detail_unlocked.fxml => main/ui/src/main/resources/fxml/vault_detail_unlocked.fxml +4 -4
@@ 36,13 36,13 @@
	<Region VBox.vgrow="ALWAYS"/>

	<HBox alignment="CENTER_RIGHT" spacing="6">
		<Label styleClass="label-small" text="%main.vaultDetail.bytesPerSecondRead"/>
		<ThrougputLabel styleClass="label-small" alignment="CENTER_RIGHT" minWidth="60" idleFormat="%main.vaultDetail.throughput.idle" kibsFormat="%main.vaultDetail.throughput.kbps"
		<Label styleClass="label-small,label-muted" text="%main.vaultDetail.bytesPerSecondRead"/>
		<ThrougputLabel styleClass="label-small,label-muted" alignment="CENTER_RIGHT" minWidth="60" idleFormat="%main.vaultDetail.throughput.idle" kibsFormat="%main.vaultDetail.throughput.kbps"
						mibsFormat="%main.vaultDetail.throughput.mbps" bytesPerSecond="${controller.vault.stats.bytesPerSecondRead}"/>
	</HBox>
	<HBox alignment="CENTER_RIGHT" spacing="6">
		<Label styleClass="label-small" text="%main.vaultDetail.bytesPerSecondWritten"/>
		<ThrougputLabel styleClass="label-small" alignment="CENTER_RIGHT" minWidth="60" idleFormat="%main.vaultDetail.throughput.idle" kibsFormat="%main.vaultDetail.throughput.kbps"
		<Label styleClass="label-small,label-muted" text="%main.vaultDetail.bytesPerSecondWritten"/>
		<ThrougputLabel styleClass="label-small,label-muted" alignment="CENTER_RIGHT" minWidth="60" idleFormat="%main.vaultDetail.throughput.idle" kibsFormat="%main.vaultDetail.throughput.kbps"
						mibsFormat="%main.vaultDetail.throughput.mbps" bytesPerSecond="${controller.vault.stats.bytesPerSecondWritten}"/>
	</HBox>
</VBox>
\ No newline at end of file

M main/ui/src/main/resources/fxml/vault_detail_welcome.fxml => main/ui/src/main/resources/fxml/vault_detail_welcome.fxml +1 -1
@@ 11,7 11,7 @@
	  spacing="24">
	<children>
		<ImageView VBox.vgrow="ALWAYS" fitHeight="128" preserveRatio="true" smooth="true" cache="true">
			<Image url="/bot.png"/>
			<Image url="/img/bot/bot.png"/>
		</ImageView>

		<TextFlow styleClass="text-flow" prefWidth="-Infinity" visible="${controller.noVaultPresent}" managed="${controller.noVaultPresent}">

M main/ui/src/main/resources/fxml/vault_options_masterkey.fxml => main/ui/src/main/resources/fxml/vault_options_masterkey.fxml +17 -12
@@ 16,28 16,33 @@
		<Insets topRightBottomLeft="12"/>
	</padding>
	<children>
		<Button text="%vaultOptions.masterkey.changePasswordBtn" onAction="#changePassword" contentDisplay="LEFT">
			<graphic>
				<FontAwesome5IconView glyph="KEY"/>
			</graphic>
		</Button>
		
		<VBox spacing="6" alignment="CENTER">
			<Button text="%vaultOptions.masterkey.changePasswordBtn" onAction="#changePassword" contentDisplay="LEFT" maxWidth="Infinity">
				<graphic>
					<FontAwesome5IconView glyph="KEY"/>
				</graphic>
			</Button>
			<Button text="%vaultOptions.masterkey.forgetSavedPasswordBtn" onAction="#removePasswordFromKeychain" contentDisplay="LEFT" maxWidth="Infinity" visible="${controller.passwordSaved}"
					managed="${controller.passwordSaved}">
				<graphic>
					<FontAwesome5IconView glyph="UNLINK"/>
				</graphic>
			</Button>
		</VBox>
		<Region VBox.vgrow="ALWAYS"/>
		
		<Label maxWidth="-Infinity" text="%vaultOptions.masterkey.recoveryKeyExpanation" wrapText="true"/>
		<HBox spacing="6" alignment="CENTER">
			<Button text="%vaultOptions.masterkey.showRecoveryKeyBtn" onAction="#showRecoveryKey" contentDisplay="LEFT">
		<VBox spacing="6" alignment="CENTER">
			<Button text="%vaultOptions.masterkey.showRecoveryKeyBtn" onAction="#showRecoveryKey" contentDisplay="LEFT" maxWidth="Infinity">
				<graphic>
					<FontAwesome5IconView glyph="EYE"/>
				</graphic>
			</Button>
			<Button text="%vaultOptions.masterkey.recoverPasswordBtn" onAction="#showRecoverVaultDialogue" contentDisplay="LEFT">
			<Button text="%vaultOptions.masterkey.recoverPasswordBtn" onAction="#showRecoverVaultDialogue" contentDisplay="LEFT" maxWidth="Infinity">
				<graphic>
					<FontAwesome5IconView glyph="SYNC"/>
				</graphic>
			</Button>
		</HBox>

		</VBox>
		<Region VBox.vgrow="ALWAYS"/>
	</children>
</VBox>

M main/ui/src/main/resources/fxml/vault_options_mount.fxml => main/ui/src/main/resources/fxml/vault_options_mount.fxml +3 -3
@@ 30,7 30,7 @@

		<CheckBox fx:id="readOnlyCheckbox" text="%vaultOptions.mount.readonly"/>

		<CheckBox fx:id="customMountFlagsCheckbox" text="%vaultOptions.mount.customMountFlags" onAction="#toggleUseCustomMountFlags"/>
		<CheckBox fx:id="customMountFlagsCheckbox" text="%vaultOptions.mount.customMountFlags" onAction="#toggleUseCustomMountFlags" visible="${!controller.webDavAndWindows}" managed="${!controller.webDavAndWindows}"/>

		<TextField fx:id="mountFlags" HBox.hgrow="ALWAYS" maxWidth="Infinity">
			<VBox.margin>


@@ 48,7 48,7 @@
			<RadioButton toggleGroup="${mountPoint}" fx:id="mountPointWinDriveLetter" text="%vaultOptions.mount.mountPoint.driveLetter"/>
			<ChoiceBox fx:id="driveLetterSelection" disable="${!mountPointWinDriveLetter.selected}"/>
		</HBox>
		<HBox spacing="6" alignment="CENTER_LEFT">
		<HBox spacing="6" alignment="CENTER_LEFT" visible="${!controller.webDavAndWindows}" managed="${!controller.webDavAndWindows}">
			<RadioButton toggleGroup="${mountPoint}" fx:id="mountPointCustomDir" text="%vaultOptions.mount.mountPoint.custom"/>
			<Button text="%vaultOptions.mount.mountPoint.directoryPickerButton" onAction="#chooseCustomMountPoint" contentDisplay="LEFT" disable="${!mountPointCustomDir.selected}">
				<graphic>


@@ 56,7 56,7 @@
				</graphic>
			</Button>
		</HBox>
		<TextField text="${controller.customMountPath}" visible="${mountPointCustomDir.selected}" maxWidth="Infinity" disable="true">
		<TextField text="${controller.customMountPath}" visible="${mountPointCustomDir.selected}" maxWidth="Infinity" disable="true" managed="${!controller.webDavAndWindows}">
			<VBox.margin>
				<Insets left="24"/>
			</VBox.margin>

M main/ui/src/main/resources/i18n/strings.properties => main/ui/src/main/resources/i18n/strings.properties +5 -1
@@ 43,6 43,7 @@ addvaultwizard.new.directoryPickerLabel=Custom Location
addvaultwizard.new.directoryPickerButton=Choose…
addvaultwizard.new.directoryPickerTitle=Select Directory
addvaultwizard.new.fileAlreadyExists=Vault can not be created at this path because some object already exists.
addvaultwizard.new.locationDoesNotExist=Vault can not be created at this path because at least one path component does not exist.
addvaultwizard.new.invalidName=Invalid vault name. Please consider a regular directory name.
### Password
addvaultwizard.new.createVaultBtn=Create Vault


@@ 176,8 177,10 @@ main.vaultlist.addVaultBtn=Add Vault
main.vaultDetail.welcomeOnboarding=Thanks for choosing Cryptomator to protect your files. If you need any assistance, check out our getting started guides:
### Locked
main.vaultDetail.lockedStatus=LOCKED
main.vaultDetail.unlockBtn=Unlock
main.vaultDetail.unlockBtn=Unlock…
main.vaultDetail.unlockNowBtn=Unlock Now
main.vaultDetail.optionsBtn=Vault Options
main.vaultDetail.passwordSavedInKeychain=Password saved
### Unlocked
main.vaultDetail.unlockedStatus=UNLOCKED
main.vaultDetail.accessLocation=Your vault's contents are accessible here:


@@ 227,6 230,7 @@ vaultOptions.mount.mountPoint.directoryPickerTitle=Pick an empty directory
## Master Key
vaultOptions.masterkey=Password
vaultOptions.masterkey.changePasswordBtn=Change Password
vaultOptions.masterkey.forgetSavedPasswordBtn=Forget Saved Password
vaultOptions.masterkey.recoveryKeyExpanation=A recovery key is your only means to restore access to a vault if you lose your password.
vaultOptions.masterkey.showRecoveryKeyBtn=Display Recovery Key
vaultOptions.masterkey.recoverPasswordBtn=Recover Password

M main/ui/src/main/resources/i18n/strings_ar.properties => main/ui/src/main/resources/i18n/strings_ar.properties +6 -1
@@ 42,6 42,7 @@ addvaultwizard.new.directoryPickerLabel=موقع مخصص
addvaultwizard.new.directoryPickerButton=اختر…
addvaultwizard.new.directoryPickerTitle=اختر القاموس
addvaultwizard.new.fileAlreadyExists=لا يمكن إنشاء مخزن على هذا المسار لأن بعض الملفات موجودة مسبقا.
addvaultwizard.new.locationDoesNotExist=لا يمكن إنشاء المخزن على هذا المسار لأن جزء من هذا المسار غير موجود.
addvaultwizard.new.invalidName=اسم المخزن غير صالح. يرجى النظر في اسم الدليل المعتاد.
### Password
addvaultwizard.new.createVaultBtn=انشئ حافظة


@@ 134,6 135,7 @@ preferences.general.theme.dark=مظلم (أسود)
preferences.general.unlockThemes=تفعيل الوضع المظلم
preferences.general.startHidden=إخفاء النافذة عند بدء تشغيل Cryptomator
preferences.general.debugLogging=تمكين سجلات التصحيح
preferences.general.debugDirectory=عرض ملفات السجل
preferences.general.autoStart=تشغيل Cryptomator عند بدء تشغيل النظام
preferences.general.interfaceOrientation=اتجاه الواجهة
preferences.general.interfaceOrientation.ltr=من اليسار إلى اليمين


@@ 174,8 176,10 @@ main.vaultlist.addVaultBtn=أضِف مخزنًا
main.vaultDetail.welcomeOnboarding=شكرا لاختيار Cryptomator لحماية ملفاتك. إذا كنت بحاجة إلى أية مساعدة، تحقق من دليل وتعليمات الإستخدام:
### Locked
main.vaultDetail.lockedStatus=مغلق
main.vaultDetail.unlockBtn=افتح
main.vaultDetail.unlockBtn=فتح…
main.vaultDetail.unlockNowBtn=فتح القفل الان
main.vaultDetail.optionsBtn=خيارات المخزن
main.vaultDetail.passwordSavedInKeychain=تم حفظ كلمة المرور
### Unlocked
main.vaultDetail.unlockedStatus=مفتوح
main.vaultDetail.accessLocation=يمكن الوصول إلى محتويات مخزنك هنا:


@@ 225,6 229,7 @@ vaultOptions.mount.mountPoint.directoryPickerTitle=اختر مجلد فارغ
## Master Key
vaultOptions.masterkey=كلمة المرور
vaultOptions.masterkey.changePasswordBtn=تغيير كلمة المرور
vaultOptions.masterkey.forgetSavedPasswordBtn=نسيان كلمة المرور المحفوظة
vaultOptions.masterkey.recoveryKeyExpanation=مفتاح الاسترداد هو وسيلتك الوحيدة لاستعادة الوصول إلى مخزنك إذا فقدت كلمة المرور.
vaultOptions.masterkey.showRecoveryKeyBtn=عرض مفتاح الاسترداد
vaultOptions.masterkey.recoverPasswordBtn=استرجاع كلمة المرور

M main/ui/src/main/resources/i18n/strings_ca.properties => main/ui/src/main/resources/i18n/strings_ca.properties +1 -1
@@ 175,7 175,7 @@ main.vaultlist.addVaultBtn=Afegir una caixa forta
main.vaultDetail.welcomeOnboarding=Gràcies per escollir Cryptomator per protegir els vostres fitxers. Si vos cal ajuda, llegiu les nostres guies per donar els Primers passos:
### Locked
main.vaultDetail.lockedStatus=BLOQUEJADA
main.vaultDetail.unlockBtn=Desbloqueja
main.vaultDetail.unlockNowBtn=Desbloqueja ara
main.vaultDetail.optionsBtn=Opcions de la caixa forta
### Unlocked
main.vaultDetail.unlockedStatus=DESBLOQUEJADA

M main/ui/src/main/resources/i18n/strings_cs.properties => main/ui/src/main/resources/i18n/strings_cs.properties +6 -2
@@ 42,6 42,7 @@ addvaultwizard.new.directoryPickerLabel=Vlastní umístění
addvaultwizard.new.directoryPickerButton=Vybrat...
addvaultwizard.new.directoryPickerTitle=Vyberte adresář
addvaultwizard.new.fileAlreadyExists=Trezor nemůže být vytvořen na tomto umístění, protože stejný objekt již existuje.
addvaultwizard.new.locationDoesNotExist=Trezor nemůže být vytvořen v tomto umístění, protože přinejmenším jedna položka z daného umístění neexistuje.
addvaultwizard.new.invalidName=Neplatné jméno trezoru. Prosím změňte jméno adresáře.
### Password
addvaultwizard.new.createVaultBtn=Vytvořit trezor


@@ 134,7 135,7 @@ preferences.general.theme.dark=Tmavý
preferences.general.unlockThemes=Odemknout tmavý režim
preferences.general.startHidden=Skrýt okno Cryptomatoru při spuštění
preferences.general.debugLogging=Ladicí režim
preferences.general.debugDirectory=Ukázat logovací soubory
preferences.general.debugDirectory=Ukázat soubory se záznamy událostí (log)
preferences.general.autoStart=Spustit Cryptomator při spuštění systému
preferences.general.interfaceOrientation=Orientace prostředí
preferences.general.interfaceOrientation.ltr=Zleva doprava


@@ 175,8 176,10 @@ main.vaultlist.addVaultBtn=Přidat trezor
main.vaultDetail.welcomeOnboarding=Děkujeme, že jste si vybrali Cryptomator pro ochranu vašich souborů. Pokud potřebujete pomoc, podívejte se na naše návody:
### Locked
main.vaultDetail.lockedStatus=UZAMKNUTO
main.vaultDetail.unlockBtn=Odemknout
main.vaultDetail.unlockBtn=Odemknout…
main.vaultDetail.unlockNowBtn=Odemknout nyní
main.vaultDetail.optionsBtn=Možnosti trezoru
main.vaultDetail.passwordSavedInKeychain=Heslo uloženo
### Unlocked
main.vaultDetail.unlockedStatus=ODEMKNUTO
main.vaultDetail.accessLocation=Obsah vašeho trezoru je dostupný:


@@ 226,6 229,7 @@ vaultOptions.mount.mountPoint.directoryPickerTitle=Vyberte prázdný adresář
## Master Key
vaultOptions.masterkey=Heslo
vaultOptions.masterkey.changePasswordBtn=Změnit heslo
vaultOptions.masterkey.forgetSavedPasswordBtn=Zapomenout uložené heslo
vaultOptions.masterkey.recoveryKeyExpanation=Obnovovací klíč je váš jediný způsob, jak obnovit přístup k trezoru, pokud ztratíte své heslo.
vaultOptions.masterkey.showRecoveryKeyBtn=Zobrazit klíč k obnově
vaultOptions.masterkey.recoverPasswordBtn=Obnovit heslo

M main/ui/src/main/resources/i18n/strings_de.properties => main/ui/src/main/resources/i18n/strings_de.properties +6 -2
@@ 42,6 42,7 @@ addvaultwizard.new.directoryPickerLabel=Eigener Ort
addvaultwizard.new.directoryPickerButton=Durchsuchen …
addvaultwizard.new.directoryPickerTitle=Verzeichnis auswählen
addvaultwizard.new.fileAlreadyExists=Der Tresor konnte nicht erstellt werden, da der Speicherort bereits belegt ist.
addvaultwizard.new.locationDoesNotExist=Der Tresor kann mit diesem Pfad nicht erstellt werden, da mindestens ein Pfadbestandteil nicht existiert.
addvaultwizard.new.invalidName=Ungültiger Tresorname. Tresore müssen wie Verzeichnisse benannt werden.
### Password
addvaultwizard.new.createVaultBtn=Tresor erstellen


@@ 79,7 80,7 @@ removeVault.information=Dein Tresor wird nicht gelöscht, sondern lediglich aus 
removeVault.confirmBtn=Tresor entfernen

# Change Password
changepassword.title=Password ändern
changepassword.title=Passwort ändern
changepassword.enterOldPassword=Gib dein aktuelles Passwort für „%s“ ein
changepassword.finalConfirmation=Mir ist bewusst, dass ich bei Verlust meines Passworts nicht mehr auf meine Daten zugreifen kann



@@ 175,8 176,10 @@ main.vaultlist.addVaultBtn=Tresor hinzufügen
main.vaultDetail.welcomeOnboarding=Danke, dass du zum Schutz deiner Dateien Cryptomator gewählt hast. Falls du Hilfe brauchst, schau dir unsere Anleitungen an:
### Locked
main.vaultDetail.lockedStatus=GESPERRT
main.vaultDetail.unlockBtn=Entsperren
main.vaultDetail.unlockBtn=Entsperren …
main.vaultDetail.unlockNowBtn=Jetzt entsperren
main.vaultDetail.optionsBtn=Tresoroptionen
main.vaultDetail.passwordSavedInKeychain=Passwort gespeichert
### Unlocked
main.vaultDetail.unlockedStatus=ENTSPERRT
main.vaultDetail.accessLocation=Dein Tresorinhalt ist hier erreichbar:


@@ 226,6 229,7 @@ vaultOptions.mount.mountPoint.directoryPickerTitle=Wähle ein leeres Verzeichnis
## Master Key
vaultOptions.masterkey=Passwort
vaultOptions.masterkey.changePasswordBtn=Password ändern
vaultOptions.masterkey.forgetSavedPasswordBtn=Gespeichertes Passwort vergessen
vaultOptions.masterkey.recoveryKeyExpanation=Bei Verlust deines Passworts ist ein Wiederherstellungsschlüssel deine einzige Möglichkeit, den Zugriff auf einen Tresor wiederherzustellen.
vaultOptions.masterkey.showRecoveryKeyBtn=Wiederherstellungsschlüssel anzeigen
vaultOptions.masterkey.recoverPasswordBtn=Passwort wiederherstellen

M main/ui/src/main/resources/i18n/strings_es.properties => main/ui/src/main/resources/i18n/strings_es.properties +6 -1
@@ 42,6 42,7 @@ addvaultwizard.new.directoryPickerLabel=Ubicación personalizada
addvaultwizard.new.directoryPickerButton=Elegir…
addvaultwizard.new.directoryPickerTitle=Seleccionar directorio
addvaultwizard.new.fileAlreadyExists=La bóveda no puede crearse en esta ruta porque ya existe un objeto.
addvaultwizard.new.locationDoesNotExist=La bóveda no puede ser creada en esta ruta porque al menos un componente no existe.
addvaultwizard.new.invalidName=Nombre de bóveda inválido. Considere un nombre de directorio regular.
### Password
addvaultwizard.new.createVaultBtn=Crear bóveda


@@ 134,6 135,7 @@ preferences.general.theme.dark=Oscuro
preferences.general.unlockThemes=Desbloquear el modo oscuro
preferences.general.startHidden=Ocultar ventana al iniciar Cryptomator
preferences.general.debugLogging=Habilitar registro de depuración
preferences.general.debugDirectory=Revelar archivos de registro
preferences.general.autoStart=Cargar Cryptomator al iniciar el sistema
preferences.general.interfaceOrientation=Orientación de la interfaz
preferences.general.interfaceOrientation.ltr=Izquierda a derecha


@@ 174,8 176,10 @@ main.vaultlist.addVaultBtn=Añadir bóveda
main.vaultDetail.welcomeOnboarding=Gracias por elegir Cryptomator para proteger los archivos. En caso de necesitar ayuda, revisar nuestras guías:
### Locked
main.vaultDetail.lockedStatus=BLOQUEADO
main.vaultDetail.unlockBtn=Desbloquear
main.vaultDetail.unlockBtn=Desbloquear…
main.vaultDetail.unlockNowBtn=Desbloquear ahora
main.vaultDetail.optionsBtn=Opciones de la bóveda
main.vaultDetail.passwordSavedInKeychain=Contraseña guardada
### Unlocked
main.vaultDetail.unlockedStatus=DESBLOQUEADO
main.vaultDetail.accessLocation=El contenido de la bóveda es accesible aquí:


@@ 225,6 229,7 @@ vaultOptions.mount.mountPoint.directoryPickerTitle=Elegir un directorio vacío
## Master Key
vaultOptions.masterkey=Contraseña
vaultOptions.masterkey.changePasswordBtn=Cambiar contraseña
vaultOptions.masterkey.forgetSavedPasswordBtn=Olvidar contraseña guardada
vaultOptions.masterkey.recoveryKeyExpanation=Una clave de recuperación es el único medio para restaurar el acceso a una bóveda si pierde su contraseña.
vaultOptions.masterkey.showRecoveryKeyBtn=Mostrar clave de recuperación
vaultOptions.masterkey.recoverPasswordBtn=Recuperar contraseña

M main/ui/src/main/resources/i18n/strings_fr.properties => main/ui/src/main/resources/i18n/strings_fr.properties +6 -2
@@ 42,6 42,7 @@ addvaultwizard.new.directoryPickerLabel=Emplacement personnalisé
addvaultwizard.new.directoryPickerButton=Choisir...
addvaultwizard.new.directoryPickerTitle=Choisissez le répertoire
addvaultwizard.new.fileAlreadyExists=Le coffre ne peut pas être créé sur ce chemin car un objet existe déjà.
addvaultwizard.new.locationDoesNotExist=Le coffre ne peut pas être créé à cet endroit car au moins un répertoire du chemin n'existe pas.
addvaultwizard.new.invalidName=Nom de coffre invalide. Préférez un nom de répertoire habituel.
### Password
addvaultwizard.new.createVaultBtn=Créer un coffre


@@ 134,7 135,7 @@ preferences.general.theme.dark=Sombre
preferences.general.unlockThemes=Débloquer le mode nuit
preferences.general.startHidden=Démarrer Cryptomator en mode caché
preferences.general.debugLogging=Activer les logs debug
preferences.general.debugDirectory=Afficher le relevé
preferences.general.debugDirectory=Afficher le journal
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


@@ 175,8 176,10 @@ main.vaultlist.addVaultBtn=Ajouter un coffre
main.vaultDetail.welcomeOnboarding=Merci d'avoir choisi Cryptomateur pour protéger vos fichiers. Si vous avez besoin d'aide, consultez nos guides de démarrage :
### Locked
main.vaultDetail.lockedStatus=VERROUILLÉ
main.vaultDetail.unlockBtn=Déverrouiller
main.vaultDetail.unlockBtn=Déverrouiller…
main.vaultDetail.unlockNowBtn=Déverouiller
main.vaultDetail.optionsBtn=Option du coffre
main.vaultDetail.passwordSavedInKeychain=Mot de passe enregistré
### Unlocked
main.vaultDetail.unlockedStatus=DÉVERROUILLÉ
main.vaultDetail.accessLocation=Le contenu de votre coffre est accessible ici :


@@ 226,6 229,7 @@ vaultOptions.mount.mountPoint.directoryPickerTitle=Choisir un répertoire vide
## Master Key
vaultOptions.masterkey=Mot de passe
vaultOptions.masterkey.changePasswordBtn=Modifier le mot de passe
vaultOptions.masterkey.forgetSavedPasswordBtn=Oublier le mot de passe enregistré
vaultOptions.masterkey.recoveryKeyExpanation=Une clé de récupération est votre seul moyen de rétablir l'accès à un coffre-fort, si vous perdez votre mot de passe.
vaultOptions.masterkey.showRecoveryKeyBtn=Afficher la clé de récupération
vaultOptions.masterkey.recoverPasswordBtn=Récupération du mot de passe

M main/ui/src/main/resources/i18n/strings_hi.properties => main/ui/src/main/resources/i18n/strings_hi.properties +0 -1
@@ 81,7 81,6 @@ main.vaultlist.addVaultBtn=वाउल्ट डालें
## Vault Detail
### Welcome
### Locked
main.vaultDetail.unlockBtn=अनलॉक करें
main.vaultDetail.optionsBtn=वॉल्ट के विकल्प
### Unlocked
main.vaultDetail.accessLocation=आपके वॉल्ट की चीजें यहाँ एक्सेस कर सकतें हैं:

M main/ui/src/main/resources/i18n/strings_it.properties => main/ui/src/main/resources/i18n/strings_it.properties +5 -1
@@ 42,6 42,7 @@ addvaultwizard.new.directoryPickerLabel=Posizione personalizzata
addvaultwizard.new.directoryPickerButton=Scegli…
addvaultwizard.new.directoryPickerTitle=Seleziona cartella
addvaultwizard.new.fileAlreadyExists=La cassaforte non può essere creata in questo percorso perché un oggetto esiste già.
addvaultwizard.new.locationDoesNotExist=La cassaforte non può essere creata in questo percorso perché almeno un componente di percorso non esiste.
addvaultwizard.new.invalidName=Nome della cassaforte non valido. Si prega di considerare un nome di directory regolare.
### Password
addvaultwizard.new.createVaultBtn=Crea Cassaforte


@@ 175,8 176,10 @@ main.vaultlist.addVaultBtn=Aggiungi Cassaforte
main.vaultDetail.welcomeOnboarding=Grazie per aver scelto Cryptomator per proteggere i tuoi file. Se hai bisogno di assistenza, dai un'occhiata alle guide per iniziare:
### Locked
main.vaultDetail.lockedStatus=BLOCCATO
main.vaultDetail.unlockBtn=Sblocca
main.vaultDetail.unlockBtn=Sblocca…
main.vaultDetail.unlockNowBtn=Sblocca adesso
main.vaultDetail.optionsBtn=Opzioni Cassaforte
main.vaultDetail.passwordSavedInKeychain=Password salvata
### Unlocked
main.vaultDetail.unlockedStatus=SBLOCCATO
main.vaultDetail.accessLocation=I contenuti della tua cassaforte sono accessibili qui:


@@ 226,6 229,7 @@ vaultOptions.mount.mountPoint.directoryPickerTitle=Scegli una directory vuota
## Master Key
vaultOptions.masterkey=Password
vaultOptions.masterkey.changePasswordBtn=Modifica password
vaultOptions.masterkey.forgetSavedPasswordBtn=Dimentica Password Salvata
vaultOptions.masterkey.recoveryKeyExpanation=Una chiave di recupero è il tuo unico mezzo per ripristinare l'accesso a una cassaforte se perdi la tua password.
vaultOptions.masterkey.showRecoveryKeyBtn=Visualizza la chiave di recupero
vaultOptions.masterkey.recoverPasswordBtn=Recupera password

M main/ui/src/main/resources/i18n/strings_ja.properties => main/ui/src/main/resources/i18n/strings_ja.properties +5 -1
@@ 42,6 42,7 @@ addvaultwizard.new.directoryPickerLabel=ユーザー設定
addvaultwizard.new.directoryPickerButton=選択...
addvaultwizard.new.directoryPickerTitle=ディレクトリを選択
addvaultwizard.new.fileAlreadyExists=いくつかのオブジェクトが既に存在しているため、このパスに金庫を作成することはできません。
addvaultwizard.new.locationDoesNotExist=このパスには階層がないため金庫を作成することができません。
addvaultwizard.new.invalidName=無効な金庫の名前です。一般的なディレクトリの名前を検討してください。
### Password
addvaultwizard.new.createVaultBtn=金庫を作成


@@ 175,8 176,10 @@ main.vaultlist.addVaultBtn=金庫を追加
main.vaultDetail.welcomeOnboarding=ファイル保護するために Cryptomator を選んでいただきありがとうございます。ヘルプが必要であれば、スタートガイドをご覧ください:
### Locked
main.vaultDetail.lockedStatus=施錠済み
main.vaultDetail.unlockBtn=解錠
main.vaultDetail.unlockBtn=解錠...
main.vaultDetail.unlockNowBtn=今すぐ解錠
main.vaultDetail.optionsBtn=金庫のオプション
main.vaultDetail.passwordSavedInKeychain=パスワードを保存しました
### Unlocked
main.vaultDetail.unlockedStatus=解錠済み
main.vaultDetail.accessLocation=アクセス可能な金庫のコンテンツ:


@@ 226,6 229,7 @@ vaultOptions.mount.mountPoint.directoryPickerTitle=空のディレクトリを
## Master Key
vaultOptions.masterkey=パスワード
vaultOptions.masterkey.changePasswordBtn=パスワードの変更
vaultOptions.masterkey.forgetSavedPasswordBtn=保存したパスワードを削除する
vaultOptions.masterkey.recoveryKeyExpanation=回復キーはパスワードを忘れてしまった場合でも、金庫へのアクセスを回復する唯一の手段です。
vaultOptions.masterkey.showRecoveryKeyBtn=回復キーを表示
vaultOptions.masterkey.recoverPasswordBtn=パスワードの回復

M main/ui/src/main/resources/i18n/strings_ko.properties => main/ui/src/main/resources/i18n/strings_ko.properties +4 -1
@@ 134,6 134,7 @@ preferences.general.theme.dark=어둡게
preferences.general.unlockThemes=다크모드 해제
preferences.general.startHidden=Cryptomator를 시작할 때 창 숨김
preferences.general.debugLogging=디버그 로그기록을 사용하도록 설정
preferences.general.debugDirectory=Log 파일 표시
preferences.general.autoStart=시스템 시작 시 Cryptomator 실행
preferences.general.interfaceOrientation=인터페이스 방향
preferences.general.interfaceOrientation.ltr=왼쪽에서 오른쪽으로


@@ 174,8 175,10 @@ main.vaultlist.addVaultBtn=Vault 추가
main.vaultDetail.welcomeOnboarding=파일을 보호하기 위해 Cryptomator를 선택해주셔서 감사합니다. 만약 다른 도움이 필요하시면, 시작안내서를 참조하시기 바랍니다.
### Locked
main.vaultDetail.lockedStatus=잠김
main.vaultDetail.unlockBtn=잠금해제
main.vaultDetail.unlockBtn=잠금해제...
main.vaultDetail.unlockNowBtn=지금 잠금해제
main.vaultDetail.optionsBtn=Vault 옵션
main.vaultDetail.passwordSavedInKeychain=비밀번호 저장됨
### Unlocked
main.vaultDetail.unlockedStatus=잠금해제됨
main.vaultDetail.accessLocation=이 Vault의 내용은 다음의 경로에서 접근할 수 있습니다:

M main/ui/src/main/resources/i18n/strings_lv.properties => main/ui/src/main/resources/i18n/strings_lv.properties +1 -1
@@ 169,7 169,7 @@ main.vaultlist.addVaultBtn=Pievienot glabātuvi
main.vaultDetail.welcomeOnboarding=Paldies, ka izvēlējāties Cryptomator lai aizsargātu jūsu datus. Ja jums nepieciešama palīdzība, iepazīstieties ar mūsu darba sākšanas ceļvežiem:
### Locked
main.vaultDetail.lockedStatus=AIZSLĒGTS
main.vaultDetail.unlockBtn=Atslēgt
main.vaultDetail.unlockNowBtn=Atslēgt tagad
main.vaultDetail.optionsBtn=Glabātuves opcijas
### Unlocked
main.vaultDetail.unlockedStatus=ATSLĒGTS

M main/ui/src/main/resources/i18n/strings_nb.properties => main/ui/src/main/resources/i18n/strings_nb.properties +2 -1
@@ 134,6 134,7 @@ preferences.general.theme.dark=Mørk
preferences.general.unlockThemes=Lås opp mørk modus
preferences.general.startHidden=Skjul vinduet når du starter Cryptomator
preferences.general.debugLogging=Aktiver loggføring av feilsøk
preferences.general.debugDirectory=Vis loggfiler
preferences.general.autoStart=Start Cryptomator ved systemstart
preferences.general.interfaceOrientation=Grensesnittorientering
preferences.general.interfaceOrientation.ltr=Fra venstre til høyre


@@ 174,7 175,7 @@ main.vaultlist.addVaultBtn=Legg til hvelv
main.vaultDetail.welcomeOnboarding=Takk for at du valgte Cryptomator for å beskytte filene dine. Hvis du trenger hjelp, sjekk ut våre guider for å komme i gang:
### Locked
main.vaultDetail.lockedStatus=LÅST
main.vaultDetail.unlockBtn=Lås opp
main.vaultDetail.unlockNowBtn=Lås opp nå
main.vaultDetail.optionsBtn=Alternativer for hvelvet
### Unlocked
main.vaultDetail.unlockedStatus=ULÅST

M main/ui/src/main/resources/i18n/strings_nl.properties => main/ui/src/main/resources/i18n/strings_nl.properties +1 -1
@@ 165,7 165,7 @@ main.vaultlist.addVaultBtn=Kluis toevoegen
main.vaultDetail.welcomeOnboarding=Bedankt dat u Cryptomator heeft gekozen om uw bestanden te beschermen. Voor assistentie verwijzen we u naar de starthandleidingen:
### Locked
main.vaultDetail.lockedStatus=VERGRENDELD
main.vaultDetail.unlockBtn=Ontgrendel
main.vaultDetail.unlockNowBtn=Nu Ontgrendelen
main.vaultDetail.optionsBtn=Kluis-instellingen
### Unlocked
main.vaultDetail.unlockedStatus=ONTGRENDELD

M main/ui/src/main/resources/i18n/strings_nn.properties => main/ui/src/main/resources/i18n/strings_nn.properties +2 -1
@@ 134,6 134,7 @@ preferences.general.theme.dark=Mørk
preferences.general.unlockThemes=Lås opp mørk modus
preferences.general.startHidden=Skjul vindauget når du startar Cryptomator
preferences.general.debugLogging=Aktivar protokollføring av feilsøk
preferences.general.debugDirectory=Vis loggfiler
preferences.general.autoStart=Start Cryptomator ved systemstart
preferences.general.interfaceOrientation=Grensesnittorientering
preferences.general.interfaceOrientation.ltr=Frå venstre til høgre


@@ 174,7 175,7 @@ main.vaultlist.addVaultBtn=Legg til kvelv
main.vaultDetail.welcomeOnboarding=Takk for at du valde Cryptomator for å verna filene dine. Viss du treng hjelp, sjekk ut guidane våre for å komma i gang:
### Locked
main.vaultDetail.lockedStatus=LÅST
main.vaultDetail.unlockBtn=Låse opp
main.vaultDetail.unlockNowBtn=Lås opp no
main.vaultDetail.optionsBtn=Alternativ for kvelven
### Unlocked
main.vaultDetail.unlockedStatus=ULÅST

A main/ui/src/main/resources/i18n/strings_pl.properties => main/ui/src/main/resources/i18n/strings_pl.properties +174 -0
@@ 0,0 1,174 @@
# Locale Specific CSS files such as CJK, RTL,...

# Generics
## Button
generic.button.apply=Zastosuj
generic.button.back=Wstecz
generic.button.cancel=Anuluj
generic.button.change=Zmień
generic.button.close=Zamknij
generic.button.copy=Skopiuj
generic.button.copied=Skopiowane!
generic.button.done=Gotowe
generic.button.next=Dalej
generic.button.print=Drukuj
## Error
generic.error.title=Wystąpił nieoczekiwany błąd
generic.error.instruction=To nie powinno było się zdarzyć. Proszę zgłosić poniższy tekst z błędem i zamieścić opis kroków, które doprowadziły do tego błędu.

# Tray Menu
traymenu.showMainWindow=Wyświetl
traymenu.showPreferencesWindow=Ustawienia
traymenu.lockAllVaults=Blokuj wszystko
traymenu.quitApplication=Wyjście
traymenu.vault.unlock=Odblokuj
traymenu.vault.lock=Blokuj
traymenu.vault.reveal=Pokaż

# Add Vault Wizard
addvaultwizard.title=Dodaj sejf
## Welcome
addvaultwizard.welcome.newButton=Utwórz nowy sejf
addvaultwizard.welcome.existingButton=Otwórz istniejący sejf
## New
### Name
addvaultwizard.new.nameInstruction=Wybierz nazwę sejfu
addvaultwizard.new.namePrompt=Nazwa sejfu
### Location
addvaultwizard.new.locationInstruction=Gdzie Cryptomator powinien przechowywać zaszyfrowane pliki twojego sejfu?
addvaultwizard.new.locationLabel=Miejsce zapisu
addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Własna lokalizacja
addvaultwizard.new.directoryPickerButton=Wybierz…
addvaultwizard.new.directoryPickerTitle=Wybierz katalog
addvaultwizard.new.fileAlreadyExists=Sejf nie może być utworzony w tym miejscu, ponieważ jakiś obiekt już istnieje.
addvaultwizard.new.locationDoesNotExist=Sejf nie może być utworzony w tej ścieżce, ponieważ co najmniej jeden katalog nie istnieje.
addvaultwizard.new.invalidName=Nieprawidłowa nazwa sejfu. Proszę spróbować prawidłową nazwę dla katalogu.
### Password
addvaultwizard.new.createVaultBtn=Utwórz sejf
addvaultwizard.new.generateRecoveryKeyChoice=Nie będziesz mógł uzyskać dostępu do swoich danych bez hasła. Czy chciałbyś klucz odzyskiwania w przypadku utraty hasła?
addvaultwizard.new.generateRecoveryKeyChoice.yes=Tak poproszę, przezorny zawsze ubezpieczony
addvaultwizard.new.generateRecoveryKeyChoice.no=Nie dziękuję, nie zapomnę hasła
### Information
addvault.new.readme.storageLocation.fileName=IMPORTANT.rtf
addvault.new.readme.storageLocation.1=⚠ PLIKI SEJFU ⚠
addvault.new.readme.storageLocation.2=To jest miejsce, w którym przechowywane są pliki Twojego sejfu.
addvault.new.readme.storageLocation.3=NIE WOLNO
addvault.new.readme.storageLocation.4=• zmieniać plików w tym katalogu lub
addvault.new.readme.storageLocation.5=• kopiować do tego katalogu jakichkolwiek plików do szyfrowania.
addvault.new.readme.storageLocation.6=Jeśli chcesz zaszyfrować pliki i wyświetlić zawartość sejfu, wykonaj następujące czynności:
addvault.new.readme.storageLocation.7=1. Dodaj ten sejf do programu Cryptomator.
addvault.new.readme.storageLocation.8=2. Odblokuj sejf w programie Cryptomator.
addvault.new.readme.storageLocation.9=3. Otwórz miejsce z odszyfrowanymi plikami, klikając przycisk "Pokaż".
addvault.new.readme.storageLocation.10=Jeśli potrzebujesz pomocy, sprawdź dokumentację: %s
addvault.new.readme.accessLocation.fileName=WELCOME.rtf
addvault.new.readme.accessLocation.1=🔐 ZASZYFROWANY DYSK 🔐
addvault.new.readme.accessLocation.2=Tu są przechowywane pliki Twojego sejfu.
addvault.new.readme.accessLocation.3=Wszystkie pliki dodane tutaj zostaną zaszyfrowane przez Cryptomator. Możesz tu wykonywać operacje jak na każdym innym dysku czy katalogu. To jest jedynie odszyfrowany podgląd Twoich plików, wszystkie pozostają cały czas zaszyfrowane na Twoim dysku twardym.
addvault.new.readme.accessLocation.4=Jeśli chcesz możesz spokojnie usunąć ten plik.
## Existing
addvaultwizard.existing.instruction=Wybierz plik "masterkey.cryptomator" w swoim istniejącym sejfie.
addvaultwizard.existing.chooseBtn=Wybierz…
addvaultwizard.existing.filePickerTitle=Wybierz plik Masterkey
## Success
addvaultwizard.success.nextStepsInstructions=Dodano sejf "%s".\nMusisz odblokować ten sejf, aby uzyskać dostęp lub dodać zawartość. Możesz go również odblokować kiedy indziej.
addvaultwizard.success.unlockNow=Odblokuj teraz

# Remove Vault
removeVault.title=Usuń sejf
removeVault.information=To tylko sprawi, że Cryptomator nie będzie widział tego sejfu. Możesz dodać go ponownie później. Żadne zaszyfrowane pliki nie zostaną usunięte z dysku twardego.
removeVault.confirmBtn=Usuń sejf

# Change Password
changepassword.title=Zmiana Hasła
changepassword.enterOldPassword=Wprowadź aktualne hasło dla "%s"
changepassword.finalConfirmation=Jestem świadomy tego, że nie będę mógł uzyskać dostępu do moich danych, jeśli zapomnę hasła

# Forget Password
forgetPassword.title=Zapomnij hasło
forgetPassword.information=To spowoduje usunięcie zapisanego hasła z systemowego menadżera haseł.
forgetPassword.confirmBtn=Zapomnij hasło

# Unlock
unlock.title=Odblokuj sejf
unlock.passwordPrompt=Wprowadź hasło dla "%s":
unlock.savePassword=Zapamiętaj hasło
unlock.unlockBtn=Odblokuj
## Success
unlock.success.message="%s" został odblokowany! Twój sejf jest teraz dostępny.
unlock.success.rememberChoice=Zapamiętaj wybór i nie pokazuj ponownie tego okna
unlock.success.revealBtn=Otwórz sejf
## Invalid Mount Point
unlock.error.invalidMountPoint=Punkt montowania nie jest pustym katalogiem: %s

# Migration
migration.title=Aktualizuj sejf
## Start
migration.start.prompt=Twój sejf "%s" musi zostać zaktualizowany do nowszego formatu. Przed kolejnym krokiem upewnij się, że sejf nie wymaga synchronizowania.
migration.start.confirm=Tak, mój sejf jest w pełni zsynchronizowany
## Run
migration.run.enterPassword=Wprowadź hasło dla "%s"
migration.run.startMigrationBtn=Aktualizuj sejf
migration.run.progressHint=To może chwilę potrwać…
## Sucess
migration.success.nextStepsInstructions=Aktualizacja "%s" zakończona pomyślnie. Możesz już odblokować swój sejf.
migration.success.unlockNow=Odblokuj teraz
## Missing file system capabilities
migration.error.missingFileSystemCapabilities.title=Nieobsługiwany system plików
migration.error.missingFileSystemCapabilities.description=Migracja nie została rozpoczęta, ponieważ Twój sejf znajduje się na niewspieranym systemie plików.
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Ten system plików nie obsługuje długich nazw plików.
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Ten system plików nie obsługuje długich ścieżek.
migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=Ten system plików nie pozwala na odczyt.
migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=Ten system plików nie pozwala na zapis.
## Impossible
migration.impossible.heading=Nie można zmigrować sejfu
migration.impossible.reason=Sejf nie może być automatycznie zmigrowany, ponieważ jego lokalizacja lub punkt dostępu nie są kompatybilne.
migration.impossible.moreInfo=Sejf nadal może być otwarty starszą wersją. Aby uzyskać informacje dotyczące ręcznej migracji sejfu wejdź na

# Preferences
preferences.title=Ustawienia
## General
preferences.general=Ogólne
preferences.general.theme=Interfejs
preferences.general.theme.light=Jasny
preferences.general.theme.dark=Ciemny
preferences.general.unlockThemes=Odblokuj tryb ciemny
preferences.general.startHidden=Ukryj okno podczas uruchamiania programu Cryptomator
preferences.general.debugLogging=Włącz logowanie w trybie debug
## Volume
## Updates
## Donation Key
## About

# Main Window
main.closeBtn.tooltip=Zamknij
main.preferencesBtn.tooltip=Ustawienia
## Drag 'n' Drop
## Vault List
main.vaultlist.contextMenu.remove=Usuń sejf
main.vaultlist.addVaultBtn=Dodaj sejf
## Vault Detail
### Welcome
### Locked
main.vaultDetail.unlockNowBtn=Odblokuj teraz
### Unlocked
main.vaultDetail.lockBtn=Blokuj
### Missing
### Needs Migration
main.vaultDetail.migrateButton=Aktualizuj sejf

# Wrong File Alert

# Vault Options
## General
vaultOptions.general=Ogólne
## Mount
vaultOptions.mount.mountPoint.directoryPickerButton=Wybierz…
## Master Key
vaultOptions.masterkey.changePasswordBtn=Zmiana Hasła

# Recovery Key

# New Password

# Quit

M main/ui/src/main/resources/i18n/strings_pt_BR.properties => main/ui/src/main/resources/i18n/strings_pt_BR.properties +2 -1
@@ 134,6 134,7 @@ preferences.general.theme.dark=Escuro
preferences.general.unlockThemes=Desbloquear o modo escuro
preferences.general.startHidden=Ocultar janela ao iniciar o Cryptomator
preferences.general.debugLogging=Ativar log de debug
preferences.general.debugDirectory=Mostrar arquivos de log
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


@@ 174,7 175,7 @@ main.vaultlist.addVaultBtn=Adicionar Cofre
main.vaultDetail.welcomeOnboarding=Obrigado por escolher o Cryptomator para proteger seus arquivos. Se precisar de ajuda, confira nossos guias de introdução:
### Locked
main.vaultDetail.lockedStatus=BLOQUEADO
main.vaultDetail.unlockBtn=Desbloquear
main.vaultDetail.unlockNowBtn=Desbloquear Agora
main.vaultDetail.optionsBtn=Opções de Cofre
### Unlocked
main.vaultDetail.unlockedStatus=DESBLOQUEADO

M main/ui/src/main/resources/i18n/strings_ru.properties => main/ui/src/main/resources/i18n/strings_ru.properties +33 -29
@@ 13,8 13,8 @@ generic.button.done=Готово
generic.button.next=Далее
generic.button.print=Распечатать
## Error
generic.error.title=Произошла непредвиденная ошибка
generic.error.instruction=Этого не должно было произойти. Пожалуйста, создайте отчет об ошибке ниже и опишите, какие шаги к ней привели.
generic.error.title=Неизвестная ошибка
generic.error.instruction=Этого не должно было произойти. Создайте отчёт об ошибке ниже и опишите, какие шаги к ней привели.

# Tray Menu
traymenu.showMainWindow=Показать


@@ 29,33 29,34 @@ traymenu.vault.reveal=Показать
addvaultwizard.title=Добавить хранилище
## Welcome
addvaultwizard.welcome.newButton=Создать хранилище
addvaultwizard.welcome.existingButton=Открыть существующее хранилище
addvaultwizard.welcome.existingButton=Открыть имеющееся хранилище
## New
### Name
addvaultwizard.new.nameInstruction=Выберите имя для хранилища
addvaultwizard.new.namePrompt=Название хранилища
addvaultwizard.new.namePrompt=Имя хранилища
### Location
addvaultwizard.new.locationInstruction=Где Cryptomator должен хранить зашифрованные файлы вашего хранилища?
addvaultwizard.new.locationLabel=Расположение хранилища
addvaultwizard.new.locationLabel=Место хранения
addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Своё расположение
addvaultwizard.new.directoryPickerButton=Выбрать…
addvaultwizard.new.directoryPickerTitle=Выберите каталог
addvaultwizard.new.directoryPickerTitle=Выберите папку
addvaultwizard.new.fileAlreadyExists=Хранилище не может быть создано по этому пути, потому что некоторые объекты уже существуют.
addvaultwizard.new.invalidName=Недопустимое название хранилища. Пожалуйста, выберите корректное название каталога.
addvaultwizard.new.locationDoesNotExist=Хранилище не может быть создано по этому пути, потому что по крайней мере один компонент пути не существует.
addvaultwizard.new.invalidName=Неверное имя хранилища. Укажите корректное имя папки.
### Password
addvaultwizard.new.createVaultBtn=Создать хранилище
addvaultwizard.new.generateRecoveryKeyChoice=Вы не сможете получить доступ к своим данным без пароля. Хотите создать ключ для восстановления на случай потери пароля?
addvaultwizard.new.generateRecoveryKeyChoice.yes=Да, пожалуй, лучше так, чем потом сожалеть
addvaultwizard.new.generateRecoveryKeyChoice.yes=Да, лучше предостеречься, чем потом жалеть
addvaultwizard.new.generateRecoveryKeyChoice.no=Нет, спасибо, я не потеряю свой пароль
### Information
addvault.new.readme.storageLocation.fileName=ВАЖНО.rtf
addvault.new.readme.storageLocation.1=⚠️  ФАЙЛЫ ХРАНИЛИЩА  ⚠️
addvault.new.readme.storageLocation.2=Это место расположения вашего хранилища.
addvault.new.readme.storageLocation.2=Это место, где находится ваше хранилище.
addvault.new.readme.storageLocation.3=НЕТ
addvault.new.readme.storageLocation.4=• изменяйте любые файлы в этой папке или
addvault.new.readme.storageLocation.5=• добавляйте в эту папку любые файлы для шифрования.
addvault.new.readme.storageLocation.6=Если вы хотите зашифровать файлы и просмотреть содержимое хранилища, сделайте следующее:
addvault.new.readme.storageLocation.6=Чтобы зашифровать файлы и просмотреть содержимое хранилища, сделайте следующее:
addvault.new.readme.storageLocation.7=1. Добавьте это хранилище в Cryptomator.
addvault.new.readme.storageLocation.8=2. Разблокируйте хранилище в Cryptomator.
addvault.new.readme.storageLocation.9=3. Откройте место доступа, нажав кнопку "Показать".


@@ 63,10 64,10 @@ addvault.new.readme.storageLocation.10=Если вам нужна помощь, 
addvault.new.readme.accessLocation.fileName=ПРИВЕТСТВИЕ.rtf
addvault.new.readme.accessLocation.1=🔐️  ЗАШИФРОВАННЫЙ ТОМ  🔐️
addvault.new.readme.accessLocation.2=Это место доступа к вашему хранилищу.
addvault.new.readme.accessLocation.3=Любые файлы, добавленные в этот том, будут зашифрованы Cryptomator. Вы можете работать с ним, как с любым другим диском/папкой. Здесь отображается только расшифрованное содержимое тома, ваши файлы остаются зашифрованными на жестком диске постоянно.
addvault.new.readme.accessLocation.3=Любые файлы, добавленные в этот том, будут зашифрованы Cryptomator. Вы можете работать с ним как с любым другим диском/папкой. Здесь отображается только расшифрованное содержимое тома, ваши файлы остаются зашифрованными на жёстком диске постоянно.
addvault.new.readme.accessLocation.4=Этот файл можно удалить.
## Existing
addvaultwizard.existing.instruction=Выберите файл "masterkey.cryptomator" от существующего хранилища.
addvaultwizard.existing.instruction=Выберите файл "masterkey.cryptomator" от имеющегося хранилища.
addvaultwizard.existing.chooseBtn=Выбрать…
addvaultwizard.existing.filePickerTitle=Выберите файл MasterKey
## Success


@@ 75,7 76,7 @@ addvaultwizard.success.unlockNow=Разблокировать

# Remove Vault
removeVault.title=Удалить хранилище
removeVault.information=Cryptomator просто забудет это хранилище. Вы можете добавить его снова позже. Зашифрованные файлы не будут удалены с жесткого диска.
removeVault.information=Cryptomator просто забудет это хранилище. Позже вы можете добавить его снова. Зашифрованные файлы не будут удалены с жёсткого диска.
removeVault.confirmBtn=Удалить хранилище

# Change Password


@@ 103,18 104,18 @@ unlock.error.invalidMountPoint=Точка монтирования - не пус
# Migration
migration.title=Обновить хранилище
## Start
migration.start.prompt=Ваше хранилище "%s" должно быть обновлено до более нового формата. Перед тем, как продолжить, убедитесь в отсутствии отложенной синхронизации, которая может повлиять на хранилище.
migration.start.prompt=Хранилище "%s" нужно преобразовать в более новый формат. Прежде чем продолжить, убедитесь, что нет отложенной синхронизации, которая может повлиять на хранилище.
migration.start.confirm=Да, моё хранилище полностью синхронизировано
## Run
migration.run.enterPassword=Введите пароль для "%s"
migration.run.startMigrationBtn=Перенести хранилище
migration.run.progressHint=Это может занять некоторое время…
## Sucess
migration.success.nextStepsInstructions="%s" перенесено успешно.\nТеперь вы можете разблокировать хранилище.
migration.success.nextStepsInstructions=Перенос "%s" успешно выполнен.\nТеперь можно разблокировать хранилище.
migration.success.unlockNow=Разблокировать
## Missing file system capabilities
migration.error.missingFileSystemCapabilities.title=Неподдерживаемая файловая система
migration.error.missingFileSystemCapabilities.description=Миграция не была запущена, так как ваше хранилище расположено в неподходящей файловой системе.
migration.error.missingFileSystemCapabilities.description=Миграция не была запущена, так как хранилище находится в неподходящей файловой системе.
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Файловая система не поддерживает длинные имена файлов.
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Файловая система не поддерживает длинные пути.
migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=Файловая система не разрешает чтение.


@@ 133,7 134,7 @@ preferences.general.theme.light=Светлая
preferences.general.theme.dark=Тёмная
preferences.general.unlockThemes=Разблокировать темный режим
preferences.general.startHidden=Скрывать окно при запуске Cryptomator
preferences.general.debugLogging=Включить ведение журнала отладки
preferences.general.debugLogging=Вести журнал отладки
preferences.general.debugDirectory=Показать файлы журнала
preferences.general.autoStart=Запускать Cryptomator при старте системы
preferences.general.interfaceOrientation=Ориентация интерфейса


@@ 148,7 149,7 @@ preferences.volume.webdav.scheme=Схема WebDAV
preferences.updates=Обновления
preferences.updates.currentVersion=Текущая версия: %s
preferences.updates.autoUpdateCheck=Автоматически проверять наличие обновлений
preferences.updates.checkNowBtn=Проверить сейчас
preferences.updates.checkNowBtn=Проверить
preferences.updates.updateAvailable=Доступно обновление до версии %s.
## Donation Key
preferences.donationKey=Пожертвование


@@ 167,7 168,7 @@ main.donationKeyMissing.tooltip=Мы будем рады финансовой п
main.dropZone.dropVault=Добавить это хранилище
main.dropZone.unknownDragboardContent=Если вы хотите добавить хранилище, перетащите его в это окно
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Нажмите здесь для добавления хранилища
main.vaultlist.emptyList.onboardingInstruction=Нажмите здесь, чтобы добавить хранилище
main.vaultlist.contextMenu.remove=Удалить хранилище
main.vaultlist.addVaultBtn=Добавить хранилище
## Vault Detail


@@ 175,11 176,13 @@ main.vaultlist.addVaultBtn=Добавить хранилище
main.vaultDetail.welcomeOnboarding=Спасибо, что выбрали Cryptomator для защиты ваших файлов. Если вам нужна помощь, ознакомьтесь с нашими руководствами по началу работы:
### Locked
main.vaultDetail.lockedStatus=ЗАБЛОКИРОВАНО
main.vaultDetail.unlockBtn=Разблокировать
main.vaultDetail.unlockBtn=Разблокировка…
main.vaultDetail.unlockNowBtn=Разблокировать
main.vaultDetail.optionsBtn=Параметры хранилища
main.vaultDetail.passwordSavedInKeychain=Пароль сохранен
### Unlocked
main.vaultDetail.unlockedStatus=РАЗБЛОКИРОВАНО
main.vaultDetail.accessLocation=Содержимое вашего хранилища доступно здесь:
main.vaultDetail.accessLocation=Содержимое хранилища доступно здесь:
main.vaultDetail.revealBtn=Показать диск
main.vaultDetail.lockBtn=Заблокировать
main.vaultDetail.bytesPerSecondRead=чтение:


@@ 196,7 199,7 @@ main.vaultDetail.migratePrompt=Чтобы получить доступ к хр
# Wrong File Alert
wrongFileAlert.title=Как шифровать файлы
wrongFileAlert.header.title=Вы пытались зашифровать эти файлы?
wrongFileAlert.header.lead=Для этого Cryptomator создает том в системном файловом менеджере.
wrongFileAlert.header.lead=Для этого Cryptomator создаёт том в системном диспетчере файлов.
wrongFileAlert.instruction.0=Чтобы зашифровать файлы, выполните следующее:
wrongFileAlert.instruction.1=1. Разблокируйте ваше хранилище.
wrongFileAlert.instruction.2=2. Нажмите кнопку "Показать", чтобы открыть том в диспетчере файлов.


@@ 215,27 218,28 @@ vaultOptions.general.actionAfterUnlock.ask=Спрашивать
vaultOptions.mount=Монтирование
vaultOptions.mount.readonly=Только чтение
vaultOptions.mount.driveName=Имя диска
vaultOptions.mount.customMountFlags=Пользовательские флаги монтирования
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=Выберите пустой каталог
vaultOptions.mount.mountPoint.directoryPickerTitle=Выберите пустую папку
## Master Key
vaultOptions.masterkey=Пароль
vaultOptions.masterkey.changePasswordBtn=Изменить пароль
vaultOptions.masterkey.recoveryKeyExpanation=Ключ восстановления - это единственный способ восстановить доступ к хранилищу в случае утери пароля.
vaultOptions.masterkey.forgetSavedPasswordBtn=Забыть сохраненный пароль
vaultOptions.masterkey.recoveryKeyExpanation=Ключ восстановления - это единственный способ восстановить доступ к хранилищу при утере пароля.
vaultOptions.masterkey.showRecoveryKeyBtn=Показать ключ восстановления
vaultOptions.masterkey.recoverPasswordBtn=Восстановить пароль

# Recovery Key
recoveryKey.title=Ключ восстановления
recoveryKey.enterPassword.prompt=Введите пароль для отображения ключа восстановления для "%s":
recoveryKey.display.message=Следующий ключ восстановления можно использовать для восстановления доступа к "%s":
recoveryKey.display.StorageHints=Храните его в надежном месте, например:\n • в менеджере паролей\n • на флэш-накопителе USB\n • распечатайте на бумаге
recoveryKey.recover.prompt=Введите ваш ключ восстановления для "%s":
recoveryKey.enterPassword.prompt=Введите пароль, чтобы показать ключ для "%s":
recoveryKey.display.message=Ключ для восстановления доступа к "%s":
recoveryKey.display.StorageHints=Храните его в надёжном месте, например:\n • в диспетчере паролей\n • на флеш-накопителе USB\n • распечатанным на бумаге
recoveryKey.recover.prompt=Введите ключ восстановления для "%s":
recoveryKey.recover.validKey=Это действительный ключ восстановления
recoveryKey.printout.heading=Ключ восстановления Cryptomator\n"%s"\n


M main/ui/src/main/resources/i18n/strings_sk.properties => main/ui/src/main/resources/i18n/strings_sk.properties +5 -1
@@ 41,6 41,7 @@ addvaultwizard.new.directoryPickerLabel=Vlastné umiestnenie
addvaultwizard.new.directoryPickerButton=Vybrať…
addvaultwizard.new.directoryPickerTitle=Vybrať adresár
addvaultwizard.new.fileAlreadyExists=Na tejto ceste nie je možné vytvoriť trezor, pretože niektorý objekt už existuje.
addvaultwizard.new.locationDoesNotExist=Peňaženka sa nedá vytvoriť na danej ceste pretože ani jedna cesta neexistuje.
addvaultwizard.new.invalidName=Neplatný názov trezoru. Zvážte bežný názov adresára.
### Password
addvaultwizard.new.createVaultBtn=Vytvoriť trezor


@@ 122,7 123,9 @@ main.vaultlist.addVaultBtn=Pridať trezor
## Vault Detail
### Welcome
### Locked
main.vaultDetail.unlockBtn=Odomknúť
main.vaultDetail.unlockBtn=Odomknúť…
main.vaultDetail.unlockNowBtn=Odomknúť teraz
main.vaultDetail.passwordSavedInKeychain=Heslo uložené
### Unlocked
main.vaultDetail.lockBtn=Uzamknúť
### Missing


@@ 136,6 139,7 @@ main.vaultDetail.lockBtn=Uzamknúť
vaultOptions.mount.mountPoint.directoryPickerButton=Vybrať…
## Master Key
vaultOptions.masterkey.changePasswordBtn=Zmeniť heslo
vaultOptions.masterkey.forgetSavedPasswordBtn=Zabudnúť uložené heslo

# Recovery Key


M main/ui/src/main/resources/i18n/strings_sv.properties => main/ui/src/main/resources/i18n/strings_sv.properties +5 -1
@@ 134,6 134,7 @@ preferences.general.theme.dark=Mörkt
preferences.general.unlockThemes=Lås upp mörkt läge
preferences.general.startHidden=Dölj fönster när Cryptomator startar
preferences.general.debugLogging=Aktivera loggning för felsökning
preferences.general.debugDirectory=Visa loggfiler
preferences.general.autoStart=Starta Cryptomator vid systemstart
preferences.general.interfaceOrientation=Gränssnittsjustering
preferences.general.interfaceOrientation.ltr=Vänster till höger


@@ 174,8 175,10 @@ main.vaultlist.addVaultBtn=Lägg till valv
main.vaultDetail.welcomeOnboarding=Tack för att du väljer Cryptomator för att skydda dina filer. Om du behöver hjälp, kolla in våra kom-gång guider:
### Locked
main.vaultDetail.lockedStatus=LÅST
main.vaultDetail.unlockBtn=Lås upp
main.vaultDetail.unlockBtn=Lås upp…
main.vaultDetail.unlockNowBtn=Lås upp nu
main.vaultDetail.optionsBtn=Valv-inställningar
main.vaultDetail.passwordSavedInKeychain=Lösenord sparat
### Unlocked
main.vaultDetail.unlockedStatus=UPPLÅST
main.vaultDetail.accessLocation=Ditt valv's innehåll kan nås härifrån:


@@ 225,6 228,7 @@ vaultOptions.mount.mountPoint.directoryPickerTitle=Välj en tom katalog
## Master Key
vaultOptions.masterkey=Lösenord
vaultOptions.masterkey.changePasswordBtn=Ändra lösenord
vaultOptions.masterkey.forgetSavedPasswordBtn=Ta bort sparat lösenord
vaultOptions.masterkey.recoveryKeyExpanation=En återställningsnyckel är ditt enda sätt att återställa åtkomst till ett valv, om du förlorar ditt lösenord.
vaultOptions.masterkey.showRecoveryKeyBtn=Visa återsällningsnyckel
vaultOptions.masterkey.recoverPasswordBtn=Återställ lösenord

M main/ui/src/main/resources/i18n/strings_tr.properties => main/ui/src/main/resources/i18n/strings_tr.properties +6 -1
@@ 42,6 42,7 @@ addvaultwizard.new.directoryPickerLabel=Özel Konum
addvaultwizard.new.directoryPickerButton=Seç…
addvaultwizard.new.directoryPickerTitle=Dizin Seç
addvaultwizard.new.fileAlreadyExists=Bazı nesneler varolduğu için kasa bu yolda oluşturulamıyor.
addvaultwizard.new.locationDoesNotExist=En az bir yol bileşeni varolmadığı için kasa bu yolda oluşturulamıyor.
addvaultwizard.new.invalidName=Geçersiz kasa adı. Lütfen normal bir dizin adı kullanın.
### Password
addvaultwizard.new.createVaultBtn=Kasa Oluştur


@@ 134,6 135,7 @@ preferences.general.theme.dark=Koyu
preferences.general.unlockThemes=Koyu modun kilidini aç
preferences.general.startHidden=Cryptomator'ı başlatırken pencereyi gizle
preferences.general.debugLogging=Hata ayıklama günlüğünü etkinleştir
preferences.general.debugDirectory=Kayıt dosyalarını göster
preferences.general.autoStart=Cryptomator'u sistem başlangıcında çalıştır
preferences.general.interfaceOrientation=Arayüz Yönü
preferences.general.interfaceOrientation.ltr=Sola Yaslı


@@ 174,8 176,10 @@ main.vaultlist.addVaultBtn=Kasa Ekle
main.vaultDetail.welcomeOnboarding=Dosyalarınızı korumak için Cryptomator'u seçtiğiniz için teşekkür ederiz. Yardıma ihtiyacınız olursa başlangıç kılavuzlarımıza bakın:
### Locked
main.vaultDetail.lockedStatus=KİLİTLİ
main.vaultDetail.unlockBtn=Kilidi Aç
main.vaultDetail.unlockBtn=Kilit aç…
main.vaultDetail.unlockNowBtn=Kilidi Şimdi Aç
main.vaultDetail.optionsBtn=Kasa Ayarları
main.vaultDetail.passwordSavedInKeychain=Şifre kaydedildi
### Unlocked
main.vaultDetail.unlockedStatus=KİLİDİ AÇIK
main.vaultDetail.accessLocation=Kasa içeriğinize buradan erişilebilir:


@@ 225,6 229,7 @@ vaultOptions.mount.mountPoint.directoryPickerTitle=Boş bir dizin seçin
## Master Key
vaultOptions.masterkey=Şifre
vaultOptions.masterkey.changePasswordBtn=Şifreyi Değiştir
vaultOptions.masterkey.forgetSavedPasswordBtn=Kaydedilen Şifreyi Unut
vaultOptions.masterkey.recoveryKeyExpanation=Bir kurtarma anahtarı şifrenizi unuttuğunuz takdirde kasanıza ulaşmanın tek yoludur.
vaultOptions.masterkey.showRecoveryKeyBtn=Kurtarma Anahtarını Göster
vaultOptions.masterkey.recoverPasswordBtn=Parolanızı Kurtarın

M main/ui/src/main/resources/i18n/strings_zh.properties => main/ui/src/main/resources/i18n/strings_zh.properties +10 -5
@@ 29,7 29,7 @@ traymenu.vault.reveal=显示
addvaultwizard.title=添加保险库
## Welcome
addvaultwizard.welcome.newButton=创建新的保险库
addvaultwizard.welcome.existingButton=打开已有的保险库
addvaultwizard.welcome.existingButton=打开现有的保险库
## New
### Name
addvaultwizard.new.nameInstruction=为保险库创建一个名称


@@ 42,6 42,7 @@ addvaultwizard.new.directoryPickerLabel=自定义位置
addvaultwizard.new.directoryPickerButton=选择...
addvaultwizard.new.directoryPickerTitle=选择目录
addvaultwizard.new.fileAlreadyExists=无法在此路径上创建保险库,因为某些对象已经存在。
addvaultwizard.new.locationDoesNotExist=无法在此路径上创建保险库,因为至少有一个路径组件不存在
addvaultwizard.new.invalidName=无效的保险库名称,请考虑一个常规的目录名称
### Password
addvaultwizard.new.createVaultBtn=创建保险库


@@ 66,11 67,11 @@ addvault.new.readme.accessLocation.2=这是你的保险库的访问路径
addvault.new.readme.accessLocation.3=任何添加到此卷的文件都将被Cryptomator加密。你可以像在一般磁盘/文件夹上那样操作它。 这只是对其内容的解密查看,你的文件在硬盘上会一直保持加密
addvault.new.readme.accessLocation.4=随时可以删除此文件。
## Existing
addvaultwizard.existing.instruction=选择您现有密码库中的"masterkey.cryptomator"文件。
addvaultwizard.existing.instruction=请选择您现有保险库中的 "masterkey.cryptomator" 文件
addvaultwizard.existing.chooseBtn=选择...
addvaultwizard.existing.filePickerTitle=选择 Masterkey 文件
## Success
addvaultwizard.success.nextStepsInstructions=添加了保险库"%s"\n您需要先解锁此保险库才能访问或添加内容。 或者您可以在稍后任何时间点解锁它。
addvaultwizard.success.nextStepsInstructions=已添加保险库  "%s"\n您需要先解锁此保险库才能访问或添加内容。或者您可以在稍后任何时候再解锁它
addvaultwizard.success.unlockNow=立即解锁

# Remove Vault


@@ 94,7 95,7 @@ unlock.passwordPrompt=输入 "%s" 的密码
unlock.savePassword=保存密码
unlock.unlockBtn=解锁
## Success
unlock.success.message=已成功解锁 "%s" !您的保险库现在可以访问。
unlock.success.message=已成功解锁 "%s"! 您现在可以访问该保险库
unlock.success.rememberChoice=记住该选择,不要再显示
unlock.success.revealBtn=显示保险库
## Invalid Mount Point


@@ 134,6 135,7 @@ preferences.general.theme.dark=暗色
preferences.general.unlockThemes=解锁暗黑模式
preferences.general.startHidden=最小化启动 Cryptomator 到系统托盘
preferences.general.debugLogging=启用调试日志
preferences.general.debugDirectory=显示日志文件
preferences.general.autoStart=开机运行 Cryptomator
preferences.general.interfaceOrientation=界面方向
preferences.general.interfaceOrientation.ltr=从左到右


@@ 174,8 176,10 @@ main.vaultlist.addVaultBtn=添加保险库
main.vaultDetail.welcomeOnboarding=感谢您使用 Cryptomator 来保护您的文件。如果您需要任何帮助,请查看正显示的快速开始指南:
### Locked
main.vaultDetail.lockedStatus=已锁定
main.vaultDetail.unlockBtn=解锁
main.vaultDetail.unlockBtn=解锁…
main.vaultDetail.unlockNowBtn=立即解锁
main.vaultDetail.optionsBtn=保险库选项
main.vaultDetail.passwordSavedInKeychain=密码已保存
### Unlocked
main.vaultDetail.unlockedStatus=已解锁
main.vaultDetail.accessLocation=您的保险库内容在此处访问:


@@ 225,6 229,7 @@ vaultOptions.mount.mountPoint.directoryPickerTitle=选择一个空目录
## Master Key
vaultOptions.masterkey=密码
vaultOptions.masterkey.changePasswordBtn=更改密码
vaultOptions.masterkey.forgetSavedPasswordBtn=忘记保存的密码
vaultOptions.masterkey.recoveryKeyExpanation=忘记密码时,恢复密钥是你恢复保险库访问权限的唯一方式!!!
vaultOptions.masterkey.showRecoveryKeyBtn=显示恢复密钥
vaultOptions.masterkey.recoverPasswordBtn=恢复密码

M main/ui/src/main/resources/i18n/strings_zh_TW.properties => main/ui/src/main/resources/i18n/strings_zh_TW.properties +7 -2
@@ 41,7 41,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=自訂位置
addvaultwizard.new.directoryPickerButton=選取
addvaultwizard.new.directoryPickerTitle=選取資料夾
addvaultwizard.new.fileAlreadyExists=在這個路徑無法建立加密檔案庫因為其中已包含其他檔案。
addvaultwizard.new.fileAlreadyExists=無法在這個路徑建立加密檔案庫,因為其中已有其他檔案。
addvaultwizard.new.locationDoesNotExist=無法在這個路徑建立加密檔案庫,因為部份路徑不存在。
addvaultwizard.new.invalidName=無效的加密檔案庫名稱。請考慮一般資料夾會使用的名稱。
### Password
addvaultwizard.new.createVaultBtn=新建加密檔案庫


@@ 134,6 135,7 @@ preferences.general.theme.dark=暗色
preferences.general.unlockThemes=解鎖暗色模式
preferences.general.startHidden=啟動 Cryptomator 時隱藏視窗
preferences.general.debugLogging=啟用除錯日誌
preferences.general.debugDirectory=顯示日誌檔
preferences.general.autoStart=系統啟動時同時啟動 Cryptomator
preferences.general.interfaceOrientation=界面排版方向
preferences.general.interfaceOrientation.ltr=由左至右


@@ 174,8 176,10 @@ main.vaultlist.addVaultBtn=新增加密檔案庫
main.vaultDetail.welcomeOnboarding=感謝您選用 Cryptomator 保護您的檔案。如果您需要任何協助,請參照我們的使用指南:
### Locked
main.vaultDetail.lockedStatus=已鎖定
main.vaultDetail.unlockBtn=解鎖
main.vaultDetail.unlockBtn=解鎖…
main.vaultDetail.unlockNowBtn=立即解鎖
main.vaultDetail.optionsBtn=加密檔案庫選項
main.vaultDetail.passwordSavedInKeychain=密碼已儲存
### Unlocked
main.vaultDetail.unlockedStatus=已解鎖
main.vaultDetail.accessLocation=您可以從這裡取得您加密檔案庫的內容


@@ 225,6 229,7 @@ vaultOptions.mount.mountPoint.directoryPickerTitle=選取一個空的資料夾
## Master Key
vaultOptions.masterkey=密碼
vaultOptions.masterkey.changePasswordBtn=變更密碼
vaultOptions.masterkey.forgetSavedPasswordBtn=清除已存密碼
vaultOptions.masterkey.recoveryKeyExpanation=復原金鑰僅用於在您遺失密碼時恢復存取能力。
vaultOptions.masterkey.showRecoveryKeyBtn=顯示復原金鑰
vaultOptions.masterkey.recoverPasswordBtn=重置密碼

A main/ui/src/main/resources/img/bot/arm-l.png => main/ui/src/main/resources/img/bot/arm-l.png +0 -0
A main/ui/src/main/resources/img/bot/arm-l@2x.png => main/ui/src/main/resources/img/bot/arm-l@2x.png +0 -0
A main/ui/src/main/resources/img/bot/arm-r.png => main/ui/src/main/resources/img/bot/arm-r.png +0 -0
A main/ui/src/main/resources/img/bot/arm-r@2x.png => main/ui/src/main/resources/img/bot/arm-r@2x.png +0 -0
A main/ui/src/main/resources/img/bot/body.png => main/ui/src/main/resources/img/bot/body.png +0 -0
A main/ui/src/main/resources/img/bot/body@2x.png => main/ui/src/main/resources/img/bot/body@2x.png +0 -0
R main/ui/src/main/resources/bot.png => main/ui/src/main/resources/img/bot/bot.png +0 -0
R main/ui/src/main/resources/bot@2x.png => main/ui/src/main/resources/img/bot/bot@2x.png +0 -0
A main/ui/src/main/resources/img/bot/face.png => main/ui/src/main/resources/img/bot/face.png +0 -0
A main/ui/src/main/resources/img/bot/face@2x.png => main/ui/src/main/resources/img/bot/face@2x.png +0 -0
A main/ui/src/main/resources/img/bot/legs.png => main/ui/src/main/resources/img/bot/legs.png +0 -0
A main/ui/src/main/resources/img/bot/legs@2x.png => main/ui/src/main/resources/img/bot/legs@2x.png +0 -0
R main/ui/src/main/resources/select-masterkey-mac.png => main/ui/src/main/resources/img/select-masterkey-mac.png +0 -0
R main/ui/src/main/resources/select-masterkey-win.png => main/ui/src/main/resources/img/select-masterkey-win.png +0 -0
R main/ui/src/main/resources/tray_icon.png => main/ui/src/main/resources/img/tray_icon.png +0 -0
R main/ui/src/main/resources/tray_icon_mac_black.png => main/ui/src/main/resources/img/tray_icon_mac_black.png +0 -0
R main/ui/src/main/resources/tray_icon_mac_black@2x.png => main/ui/src/main/resources/img/tray_icon_mac_black@2x.png +0 -0
R main/ui/src/main/resources/tray_icon_mac_white.png => main/ui/src/main/resources/img/tray_icon_mac_white.png +0 -0
R main/ui/src/main/resources/tray_icon_mac_white@2x.png => main/ui/src/main/resources/img/tray_icon_mac_white@2x.png +0 -0
R main/ui/src/main/resources/vault-volume-mac.png => main/ui/src/main/resources/img/vault-volume-mac.png +0 -0
R main/ui/src/main/resources/vault-volume-win.png => main/ui/src/main/resources/img/vault-volume-win.png +0 -0
R main/ui/src/main/resources/window_icon_32.png => main/ui/src/main/resources/img/window_icon_32.png +0 -0
R main/ui/src/main/resources/window_icon_512.png => main/ui/src/main/resources/img/window_icon_512.png +0 -0