Merge branch 'develop' into feature/jdk-24

This commit is contained in:
Armin Schrenk
2025-05-21 16:08:38 +02:00
29 changed files with 558 additions and 30 deletions

View File

@@ -2,16 +2,25 @@ name: Build macOS .dmg for x64
#######################################
# STOP! DO NOT EDIT THIS FILE!
#
#
# It is a copy of mac-dmg.yml with tiny adjustements (mainly lines 42 to 47)
# It was made necessary, since Github does not offer free macos intel runners for macos 15 and above.
# This workflow can only be triggered by a release.
#
#
#######################################
on:
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: 'Version'
required: false
notarize:
description: 'Notarize'
required: true
default: false
type: boolean
env:
JAVA_DIST: 'temurin'

View File

@@ -83,6 +83,15 @@
</content_rating>
<releases>
<release date="2025-05-15" version="1.16.2">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.16.2</url>
</release>
<release date="2025-04-30" version="1.16.1">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.16.1</url>
</release>
<release date="2025-04-29" version="1.16.0">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.16.0</url>
</release>
<release date="2025-04-09" version="1.15.3">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.15.3</url>
</release>

17
pom.xml
View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>cryptomator</artifactId>
<version>1.16.0-SNAPSHOT</version>
<version>1.17.0-SNAPSHOT</version>
<name>Cryptomator Desktop App</name>
<organization>
@@ -33,10 +33,10 @@
<nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents</nonModularGroupIds>
<!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>2.9.0-beta2</cryptomator.cryptofs.version>
<cryptomator.cryptofs.version>2.9.0</cryptomator.cryptofs.version>
<cryptomator.integrations.version>1.5.1</cryptomator.integrations.version>
<cryptomator.integrations.win.version>1.3.0</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.3.0</cryptomator.integrations.mac.version>
<cryptomator.integrations.mac.version>1.3.2</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.5.3</cryptomator.integrations.linux.version>
<cryptomator.fuse.version>5.0.5</cryptomator.fuse.version>
<cryptomator.webdav.version>2.0.10</cryptomator.webdav.version>
@@ -75,6 +75,17 @@
<surefire.jacoco.args></surefire.jacoco.args>
</properties>
<!-- TODO: Remove once webdav version 2.0.11 is released -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>webdav-nio-adapter-servlet</artifactId>
<version>1.2.9</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Cryptomator Libs -->
<dependency>

View File

@@ -59,6 +59,7 @@ open module org.cryptomator.desktop {
uses org.cryptomator.common.locationpresets.LocationPresetsProvider;
uses SSLContextProvider;
uses org.cryptomator.event.NotificationHandler;
provides TrayMenuController with AwtTrayMenuController;
provides Configurator with LogbackConfiguratorFactory;

View File

@@ -0,0 +1,160 @@
package org.cryptomator.common;
import org.cryptomator.cryptofs.event.BrokenDirFileEvent;
import org.cryptomator.cryptofs.event.BrokenFileNodeEvent;
import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent;
import org.cryptomator.cryptofs.event.ConflictResolvedEvent;
import org.cryptomator.cryptofs.event.DecryptionFailedEvent;
import org.cryptomator.cryptofs.event.FilesystemEvent;
import org.cryptomator.event.VaultEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import javafx.beans.InvalidationListener;
import javafx.collections.FXCollections;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableMap;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Comparator;
import java.util.Map;
import java.util.Set;
/**
* Map containing {@link VaultEvent}s.
* The map is keyed by the ciphertext path of the affected resource _and_ the {@link FilesystemEvent}s class in order to group same events
* <p>
* Use {@link EventMap#put(VaultEvent)} to add an element and {@link EventMap#remove(VaultEvent)} to remove it.
* <p>
* The map is size restricted to {@value MAX_SIZE} elements. If a _new_ element (i.e. not already present) is added, the least recently added is removed.
*/
@Singleton
public class EventMap implements ObservableMap<EventMap.EventKey, VaultEvent> {
private static final int MAX_SIZE = 300;
public record EventKey(Path ciphertextPath, Class<? extends FilesystemEvent> c) {}
private final ObservableMap<EventMap.EventKey, VaultEvent> delegate;
@Inject
public EventMap() {
delegate = FXCollections.observableHashMap();
}
@Override
public void addListener(MapChangeListener<? super EventKey, ? super VaultEvent> mapChangeListener) {
delegate.addListener(mapChangeListener);
}
@Override
public void removeListener(MapChangeListener<? super EventKey, ? super VaultEvent> mapChangeListener) {
delegate.removeListener(mapChangeListener);
}
@Override
public int size() {
return delegate.size();
}
@Override
public boolean isEmpty() {
return delegate.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return delegate.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return delegate.containsValue(value);
}
@Override
public VaultEvent get(Object key) {
return delegate.get(key);
}
@Override
public @Nullable VaultEvent put(EventKey key, VaultEvent value) {
return delegate.put(key, value);
}
@Override
public VaultEvent remove(Object key) {
return delegate.remove(key);
}
@Override
public void putAll(@NotNull Map<? extends EventKey, ? extends VaultEvent> m) {
delegate.putAll(m);
}
@Override
public void clear() {
delegate.clear();
}
@Override
public @NotNull Set<EventKey> keySet() {
return delegate.keySet();
}
@Override
public @NotNull Collection<VaultEvent> values() {
return delegate.values();
}
@Override
public @NotNull Set<Entry<EventKey, VaultEvent>> entrySet() {
return delegate.entrySet();
}
@Override
public void addListener(InvalidationListener invalidationListener) {
delegate.addListener(invalidationListener);
}
@Override
public void removeListener(InvalidationListener invalidationListener) {
delegate.removeListener(invalidationListener);
}
public synchronized void put(VaultEvent e) {
//compute key
var key = computeKey(e.actualEvent());
//if-else
var nullOrEntry = delegate.get(key);
if (nullOrEntry == null) {
if (size() == MAX_SIZE) {
delegate.entrySet().stream() //
.min(Comparator.comparing(entry -> entry.getValue().actualEvent().getTimestamp())) //
.ifPresent(oldestEntry -> delegate.remove(oldestEntry.getKey()));
}
delegate.put(key, e);
} else {
delegate.put(key, nullOrEntry.incrementCount(e.actualEvent()));
}
}
public synchronized VaultEvent remove(VaultEvent similar) {
//compute key
var key = computeKey(similar.actualEvent());
return this.remove(key);
}
private EventKey computeKey(FilesystemEvent e) {
var p = switch (e) {
case DecryptionFailedEvent(_, Path ciphertextPath, _) -> ciphertextPath;
case ConflictResolvedEvent(_, _, _, _, Path resolvedCiphertext) -> resolvedCiphertext;
case ConflictResolutionFailedEvent(_, _, Path conflictingCiphertext, _) -> conflictingCiphertext;
case BrokenDirFileEvent(_, Path ciphertext) -> ciphertext;
case BrokenFileNodeEvent(_, _, Path ciphertext) -> ciphertext;
};
return new EventKey(p, e.getClass());
}
}

View File

@@ -23,6 +23,7 @@ import org.cryptomator.cryptofs.event.FilesystemEvent;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.MasterkeyLoader;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import org.cryptomator.event.VaultEvent;
import org.cryptomator.integrations.mount.MountFailedException;
import org.cryptomator.integrations.mount.Mountpoint;
import org.cryptomator.integrations.mount.UnmountFailedException;
@@ -34,6 +35,7 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;

View File

@@ -9,6 +9,7 @@
package org.cryptomator.common.vaults;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Constants;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
@@ -35,6 +36,7 @@ import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION;
@Singleton
public class VaultListManager {
@@ -115,13 +117,15 @@ public class VaultListManager {
private Vault create(VaultSettings vaultSettings) {
var wrapper = new VaultConfigCache(vaultSettings);
try {
if (Objects.isNull(vaultSettings.lastKnownKeyLoader.get())) {
var keyIdScheme = wrapper.get().getKeyId().getScheme();
vaultSettings.lastKnownKeyLoader.set(keyIdScheme);
}
var vaultState = determineVaultState(vaultSettings.path.get());
if (vaultState == LOCKED) { //for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state
wrapper.reloadConfig();
if (Objects.isNull(vaultSettings.lastKnownKeyLoader.get())) {
var keyIdScheme = wrapper.get().getKeyId().getScheme();
vaultSettings.lastKnownKeyLoader.set(keyIdScheme);
}
} else if (vaultState == NEEDS_MIGRATION) {
vaultSettings.lastKnownKeyLoader.set(Constants.DEFAULT_KEY_ID.toString());
}
return vaultComponentFactory.create(vaultSettings, wrapper, vaultState, null).vault();
} catch (IOException e) {

View File

@@ -0,0 +1,14 @@
package org.cryptomator.event;
public sealed interface Answer permits Answer.DoNothing, Answer.DoSomething {
record DoNothing() implements Answer {}
record DoSomething(Runnable action) implements Answer {
void run() {
action.run();
}
}
}

View File

@@ -0,0 +1,15 @@
package org.cryptomator.event;
import org.cryptomator.integrations.common.IntegrationsLoader;
import java.util.ServiceLoader;
import java.util.stream.Stream;
public interface NotificationHandler {
Answer handle(VaultEvent e);
static Stream<NotificationHandler> loadAll() {
return IntegrationsLoader.loadAll(ServiceLoader.load(NotificationHandler.class), NotificationHandler.class);
}
}

View File

@@ -0,0 +1,27 @@
package org.cryptomator.event;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.event.FilesystemEvent;
import java.time.Instant;
public record VaultEvent(Vault v, FilesystemEvent actualEvent, int count) implements Comparable<VaultEvent> {
public VaultEvent(Vault v, FilesystemEvent actualEvent) {
this(v, actualEvent, 1);
}
@Override
public int compareTo(VaultEvent other) {
var timeResult = actualEvent.getTimestamp().compareTo(other.actualEvent().getTimestamp());
if(timeResult != 0) {
return timeResult;
} else {
return this.equals(other) ? 0 : this.actualEvent.getClass().getName().compareTo(other.actualEvent.getClass().getName());
}
}
public VaultEvent incrementCount(FilesystemEvent update) {
return new VaultEvent(v, update, count+1);
}
}

View File

@@ -0,0 +1,185 @@
package org.cryptomator.networking;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.KeyStoreSpi;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.concurrent.atomic.AtomicInteger;
public class CombinedKeyStoreSpi extends KeyStoreSpi {
private final KeyStore primary;
private final KeyStore fallback;
public static CombinedKeyStoreSpi create(KeyStore primary, KeyStore fallback) {
checkIfLoaded(primary);
checkIfLoaded(fallback);
return new CombinedKeyStoreSpi(primary, fallback);
}
private static void checkIfLoaded(KeyStore s) {
try {
s.aliases();
} catch (KeyStoreException e) {
throw new IllegalArgumentException("Keystore %s is not loaded.".formatted(s.getType()));
}
}
private CombinedKeyStoreSpi(KeyStore primary, KeyStore fallback) {
this.primary = primary;
this.fallback = fallback;
}
@Override
public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException {
try {
Key key = primary.getKey(alias, password);
if (key == null) {
key = fallback.getKey(alias, password);
}
return key;
} catch (KeyStoreException e) {
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
}
}
@Override
public Certificate[] engineGetCertificateChain(String alias) {
try {
Certificate[] chain = primary.getCertificateChain(alias);
if (chain == null) {
chain = fallback.getCertificateChain(alias);
}
return chain;
} catch (KeyStoreException e) {
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
}
}
@Override
public Certificate engineGetCertificate(String alias) {
try {
Certificate cert = primary.getCertificate(alias);
if (cert == null) {
cert = fallback.getCertificate(alias);
}
return cert;
} catch (KeyStoreException e) {
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
}
}
@Override
public Date engineGetCreationDate(String alias) {
try {
Date date = primary.getCreationDate(alias);
if (date == null) {
date = fallback.getCreationDate(alias);
}
return date;
} catch (KeyStoreException e) {
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
}
}
@Override
public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) throws KeyStoreException {
throw new UnsupportedOperationException("Read-only KeyStore");
}
@Override
public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) throws KeyStoreException {
throw new UnsupportedOperationException("Read-only KeyStore");
}
@Override
public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException {
throw new UnsupportedOperationException("Read-only KeyStore");
}
@Override
public void engineDeleteEntry(String alias) throws KeyStoreException {
throw new UnsupportedOperationException("Read-only KeyStore");
}
@Override
public Enumeration<String> engineAliases() {
var aliases = new LinkedHashSet<String>();
try {
primary.aliases().asIterator().forEachRemaining(aliases::add);
fallback.aliases().asIterator().forEachRemaining(aliases::add);
return Collections.enumeration(aliases);
} catch (KeyStoreException e) {
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
}
}
@Override
public boolean engineContainsAlias(String alias) {
try {
return primary.containsAlias(alias) || fallback.containsAlias(alias);
} catch (KeyStoreException e) {
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
}
}
@Override
public int engineSize() {
var aliases = engineAliases();
var i = new AtomicInteger(0);
aliases.asIterator().forEachRemaining(_ -> i.incrementAndGet());
return i.get();
}
@Override
public boolean engineIsKeyEntry(String alias) {
try {
return primary.isKeyEntry(alias) || fallback.isKeyEntry(alias);
} catch (KeyStoreException e) {
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
}
}
@Override
public boolean engineIsCertificateEntry(String alias) {
try {
return primary.isCertificateEntry(alias) || fallback.isCertificateEntry(alias);
} catch (KeyStoreException e) {
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
}
}
@Override
public String engineGetCertificateAlias(Certificate cert) {
try {
String alias = primary.getCertificateAlias(cert);
if (alias == null) {
alias = fallback.getCertificateAlias(cert);
}
return alias;
} catch (KeyStoreException e) {
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
}
}
@Override
public void engineStore(OutputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException {
throw new UnsupportedOperationException("Read-only KeyStore");
}
@Override
public void engineLoad(InputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException {
// Nothing to do; the real keystores are already loaded.
}
}

View File

@@ -6,6 +6,7 @@ import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.cert.CertificateException;
/**
@@ -16,6 +17,16 @@ public class SSLContextWithMacKeychain extends SSLContextDifferentTrustStoreBase
@Override
KeyStore getTruststore() throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException {
return KeyStore.getInstance("KeychainStore-ROOT");
var userKeyStore = KeyStore.getInstance("KeychainStore");
var systemRootKeyStore = KeyStore.getInstance("KeychainStore-ROOT");
userKeyStore.load(null);
systemRootKeyStore.load(null);
try {
CombinedKeyStoreSpi spi = CombinedKeyStoreSpi.create(userKeyStore, systemRootKeyStore);
Provider dummyProvider = new Provider("CombinedKeyStoreProvider", "1.0", "Provides a combined, read-only KeyStore") {};
return new KeyStore(spi, dummyProvider, "CombinedKeyStoreProvider") {};
} catch (IllegalArgumentException e) {
throw new KeyStoreException(e);
}
}
}

View File

@@ -20,8 +20,8 @@ public interface ErrorComponent {
default Stage show() {
Stage stage = window();
stage.setScene(scene());
stage.setMinWidth(420);
stage.setMinHeight(300);
stage.setMinWidth(450);
stage.setMinHeight(450);
stage.show();
return stage;
}

View File

@@ -0,0 +1,14 @@
package org.cryptomator.ui.eventview;
import org.cryptomator.ui.common.FxController;
import javax.inject.Inject;
@EventViewScoped
public class UpdateEventViewController implements FxController {
@Inject
public UpdateEventViewController() {
}
}

View File

@@ -74,7 +74,6 @@
<Tooltip text="%main.vaultDetail.decryptName.tooltip"/>
</tooltip>
</Button>
<Region HBox.hgrow="ALWAYS"/>
<Button text="%main.vaultDetail.stats" minWidth="120" onAction="#showVaultStatistics" contentDisplay="BOTTOM" prefHeight="72">

View File

@@ -14,17 +14,15 @@
spacing="12"
alignment="CENTER_LEFT">
<!-- Remark Check the containing list view for a fixed cell size before editing height properties -->
<children>
<VBox alignment="CENTER" minWidth="20">
<FontAwesome5IconView fx:id="vaultStateView" glyph="${controller.glyph}" HBox.hgrow="NEVER" glyphSize="16"/>
</VBox>
<VBox spacing="4" HBox.hgrow="ALWAYS">
<Label styleClass="header-label" text="${controller.vault.displayName}"/>
<Label styleClass="detail-label" text="${controller.vault.displayablePath}" textOverrun="CENTER_ELLIPSIS" visible="${!controller.compactMode}" managed="${!controller.compactMode}">
<tooltip>
<Tooltip text="${controller.vault.displayablePath}"/>
</tooltip>
</Label>
</VBox>
</children>
<VBox alignment="CENTER" minWidth="20">
<FontAwesome5IconView fx:id="vaultStateView" glyph="${controller.glyph}" HBox.hgrow="NEVER" glyphSize="16"/>
</VBox>
<VBox spacing="4" HBox.hgrow="ALWAYS">
<Label styleClass="header-label" text="${controller.vault.displayName}"/>
<Label styleClass="detail-label" text="${controller.vault.displayablePath}" textOverrun="CENTER_ELLIPSIS" visible="${!controller.compactMode}" managed="${!controller.compactMode}">
<tooltip>
<Tooltip text="${controller.vault.displayablePath}"/>
</tooltip>
</Label>
</VBox>
</HBox>

View File

@@ -599,6 +599,7 @@ decryptNames.dropZone.error.generic=Error al descifrar nombre de archivos
# Event View
eventView.title=Eventos
eventView.filter.allVaults=Todos
eventView.clearListButton.tooltip=Borrar lista
## event list entries
eventView.entry.vaultLocked.description=Desbloquear "%s" para más detalles
eventView.entry.conflictResolved.message=Conflicto resuelto

View File

@@ -3,7 +3,7 @@
# Generics
generic.action.dismiss=لغو
## Button
generic.button.apply=درخواست
generic.button.apply=اعمال
generic.button.back=بازگشت
generic.button.cancel=انصراف
generic.button.change=تغییر

View File

@@ -424,7 +424,9 @@ main.vaultDetail.stats=Statistiques du volume chiffré
main.vaultDetail.locateEncryptedFileBtn=Localiser le fichier chiffré
main.vaultDetail.locateEncryptedFileBtn.tooltip=Choisissez un fichier dans votre coffre pour localiser sa version chiffrée
main.vaultDetail.encryptedPathsCopied=Chemins d'accès copiés dans le presse-papier !
main.vaultDetail.locateEncrypted.filePickerTitle=Sélectionner le fichier dans le coffre
main.vaultDetail.decryptName.buttonLabel=Déchiffrer le nom d'un fichier
main.vaultDetail.decryptName.tooltip=Choisir un fichier de coffre chiffré pour déchiffrer son nom
### Missing
main.vaultDetail.missing.info=Cryptomator n'a pas pu trouver de volume chiffré dans ce chemin d'accès.
main.vaultDetail.missing.recheck=Revérifier
@@ -596,6 +598,7 @@ decryptNames.dropZone.error.generic=Impossible de déchiffrer les noms de fichie
# Event View
eventView.title=Événements
eventView.filter.allVaults=Tous
eventView.clearListButton.tooltip=Effacer la liste
## event list entries
eventView.entry.vaultLocked.description=Déverrouillez "%s" pour plus de détails
eventView.entry.conflictResolved.message=Conflit résolu

View File

@@ -49,6 +49,7 @@ addvaultwizard.new.nameInstruction=בחירת שם עבור הכספת
addvaultwizard.new.namePrompt=שם הכספת
### Location
addvaultwizard.new.locationInstruction=היכן Cryptomator צריך לשמור את הקבצים המוצפנים של הכספת שלך?
addvaultwizard.new.locationLoading=בודק מערכת קבצים מקומית עבור ספריות ברירת מחדל לאחסון ענן…
addvaultwizard.new.locationLabel=מיקום אחסון
addvaultwizard.new.locationPrompt=
addvaultwizard.new.directoryPickerLabel=מיקום מותאם אישית

View File

@@ -599,6 +599,7 @@ decryptNames.dropZone.error.generic=Decifratura nomi file non riuscita
# Event View
eventView.title=Eventi
eventView.filter.allVaults=Tutti
eventView.clearListButton.tooltip=Cancella elenco
## event list entries
eventView.entry.vaultLocked.description=Sblocca "%s" per i dettagli
eventView.entry.conflictResolved.message=Conflitto risolto

View File

@@ -599,6 +599,7 @@ decryptNames.dropZone.error.generic=Neizdevās atšifrēt datņu nosaukumus
# Event View
eventView.title=Notikumi
eventView.filter.allVaults=Viss
eventView.clearListButton.tooltip=Notīrīt sarakstu
## event list entries
eventView.entry.vaultLocked.description=Atslēgt "%s", lai redzētu informāciju
eventView.entry.conflictResolved.message=Atrisināta nesaderība

View File

@@ -599,6 +599,7 @@ decryptNames.dropZone.error.generic=Kan bestandsnamen niet decoderen
# Event View
eventView.title=Activiteiten
eventView.filter.allVaults=Alle
eventView.clearListButton.tooltip=Wis lijst
## event list entries
eventView.entry.vaultLocked.description=Ontgrendel "%s" voor details
eventView.entry.conflictResolved.message=Opgelost conflict

View File

@@ -599,6 +599,7 @@ decryptNames.dropZone.error.generic=Falha ao desencriptar nomes de ficheiros
# Event View
eventView.title=Eventos
eventView.filter.allVaults=Todos
eventView.clearListButton.tooltip=Limpar lista
## event list entries
eventView.entry.vaultLocked.description=Desbloquear "%s" para detalhes
eventView.entry.conflictResolved.message=Conflito resolvido

View File

@@ -599,6 +599,7 @@ decryptNames.dropZone.error.generic=Falha ao descriptografar nomes de arquivos
# Event View
eventView.title=Eventos
eventView.filter.allVaults=Todos
eventView.clearListButton.tooltip=Limpar lista
## event list entries
eventView.entry.vaultLocked.description=Desbloquear "%s" para detalhes
eventView.entry.conflictResolved.message=Conflito resolvido

View File

@@ -599,6 +599,7 @@ decryptNames.dropZone.error.generic=Не удалось расшифровать
# Event View
eventView.title=События
eventView.filter.allVaults=Все
eventView.clearListButton.tooltip=Очистить список
## event list entries
eventView.entry.vaultLocked.description=Разблокируйте "%s" для деталей
eventView.entry.conflictResolved.message=Решённый конфликт

View File

@@ -577,6 +577,7 @@ decryptNames.dropZone.error.generic=Nepodarilo sa dešifrovať názvy súborov
# Event View
eventView.title=Udalosti
eventView.filter.allVaults=Všetko
eventView.clearListButton.tooltip=Vymazať zoznam
## event list entries
eventView.entry.vaultLocked.description=Podrobnosti získate odomknutím „%s“
eventView.entry.conflictResolved.message=Vyriešený konflikt

View File

@@ -394,6 +394,7 @@ main.vaultlist.contextMenu.vaultoptions=Visa inställningar för valv
main.vaultlist.contextMenu.reveal=Visa enhet
main.vaultlist.addVaultBtn.menuItemNew=Skapa nytt valv...
main.vaultlist.addVaultBtn.menuItemExisting=Öppna befintligt valv...
main.vaultlist.showEventsButton.tooltip=Öppna händelsevy
##Notificaition
main.notification.updateAvailable=Uppdatering tillgänglig.
main.notification.support=Stöd Cryptomator.
@@ -422,6 +423,9 @@ main.vaultDetail.stats=Valv Statistik
main.vaultDetail.locateEncryptedFileBtn=Leta upp krypterad fil
main.vaultDetail.locateEncryptedFileBtn.tooltip=Välj en fil från ditt valv för att hitta dess krypterade motsvarighet
main.vaultDetail.encryptedPathsCopied=Sökvägar kopierade till klippbordet!
main.vaultDetail.locateEncrypted.filePickerTitle=Välj fil inuti valvet
main.vaultDetail.decryptName.buttonLabel=Dekryptera filnamn
main.vaultDetail.decryptName.tooltip=Välj en krypterad valvfil för att dekryptera dess namn
### Missing
main.vaultDetail.missing.info=Cryptomator kunde inte hitta någt valv i denna sökväg.
main.vaultDetail.missing.recheck=Kontrollera igen
@@ -578,7 +582,40 @@ shareVault.hub.instruction.2=2. Ge åtkomst till gruppmedlemmen i Cryptomatornav
shareVault.hub.openHub=Öppna kryptomatornav
# Decrypt File Names
decryptNames.title=Dekryptera filnamn
decryptNames.filePicker.title=Välj krypterad fil
decryptNames.filePicker.extensionDescription=Cryptomator krypterad fil
decryptNames.copyTable.tooltip=Kopiera tabell
decryptNames.clearTable.tooltip=Rensa tabell
decryptNames.copyHint=Kopiera cellinnehåll med %s
decryptNames.dropZone.message=Släpp filer eller klicka för att välja
decryptNames.dropZone.error.vaultInternalFiles=Interna valvfiler utan dekrypterbart namn valdes
decryptNames.dropZone.error.foreignFiles=Filer tillhör inte valvet "%s"
decryptNames.dropZone.error.noDirIdBackup=Katalog med valda filer innehåller inte dirId.c9r fil
decryptNames.dropZone.error.generic=Det gick inte att dekryptera filnamn
# Event View
eventView.title=Händelser
eventView.filter.allVaults=Samtliga
eventView.clearListButton.tooltip=Rensa listan
## event list entries
eventView.entry.vaultLocked.description=Lås upp "%s" för detaljer
eventView.entry.conflictResolved.message=Löst konflikt
eventView.entry.conflictResolved.showDecrypted=Visa dekrypterad fil
eventView.entry.conflictResolved.copyDecrypted=Kopiera dekrypterad sökväg
eventView.entry.conflict.message=Konfliktlösning misslyckades
eventView.entry.conflict.showDecrypted=Visa dekrypterad, originalfil
eventView.entry.conflict.copyDecrypted=Kopiera dekrypterad, ursprunglig sökväg
eventView.entry.conflict.showEncrypted=Visa krypterad fil som inte kunde synkroniseras
eventView.entry.conflict.copyEncrypted=Kopiera krypterad sökväg som inte kunde synkroniseras
eventView.entry.decryptionFailed.message=Dekryptering misslyckades
eventView.entry.decryptionFailed.showEncrypted=Visa krypterad fil
eventView.entry.decryptionFailed.copyEncrypted=Kopiera krypterad sökväg
eventView.entry.brokenDirFile.message=Trasig kataloglänk
eventView.entry.brokenDirFile.showEncrypted=Visa trasig, krypterad länk
eventView.entry.brokenDirFile.copyEncrypted=Kopiera sökväg för trasig länk
eventView.entry.brokenFileNode.message=Trasig filsystemsnod
eventView.entry.brokenFileNode.showEncrypted=Visa trasig krypterad nod
eventView.entry.brokenFileNode.copyEncrypted=Kopiera sökväg för trasig, krypterad nod
eventView.entry.brokenFileNode.copyDecrypted=Kopiera dekrypterad sökväg

View File

@@ -94,7 +94,7 @@ addvault.new.readme.accessLocation.2=這是您加密檔案庫的存取位置。
addvault.new.readme.accessLocation.3=所有被加進這個磁區的檔案都將被 Cryptomator 加密。你可以把它當做磁碟或資料夾使用。這裡式顯示出解密後內容,您的檔案總是以被加密的狀態儲存在磁碟中。
addvault.new.readme.accessLocation.4=您可以放心移除這個檔案。
## Existing
addvaultwizard.existing.title=添加現有加密檔案庫
addvaultwizard.existing.title=開啟現有加密檔案庫
addvaultwizard.existing.instruction=請選擇現有加密檔案庫中名為「vault.cryptomator」的檔案。如果只有一個名為「masterkey.cryptomator」的檔案則選擇該檔案。
addvaultwizard.existing.chooseBtn=選取…
addvaultwizard.existing.filePickerTitle=選取加密檔案庫的檔案
@@ -127,7 +127,7 @@ unlock.unlockBtn=解鎖
## Select
unlock.chooseMasterkey.message=未找到主金鑰文件
unlock.chooseMasterkey.description=無法在其預期位置找到加密檔案庫「%s」的主密鑰檔案。請手動選擇密鑰文件。
unlock.chooseMasterkey.filePickerTitle=选择主金鑰文件
unlock.chooseMasterkey.filePickerTitle=選擇主金鑰檔案
unlock.chooseMasterkey.filePickerMimeDesc=Cryptomator 主密鑰
## Success
unlock.success.message=解鎖成功
@@ -177,6 +177,7 @@ hub.registerFailed.description.generic=註冊過程發生錯誤。更多細節
hub.registerFailed.description.deviceAlreadyExists=其他使用者已在此裝置上註冊。請切換至該使用者帳戶或使用其他裝置進行註冊。
### Unauthorized
hub.unauthorized.message=拒絕存取
hub.unauthorized.description=您沒有被授權開啟這個加密檔案庫。請聯絡檔案庫擁有者取得權限。
### Requires Account Initialization
hub.requireAccountInit.message=需進一步操作
hub.requireAccountInit.description.0=請完成您的
@@ -282,6 +283,7 @@ preferences.title=偏好
## General
preferences.general=一般
preferences.general.startHidden=啟動 Cryptomator 時隱藏視窗
preferences.general.autoCloseVaults=當離開應用程式的時候直接鎖定檔案庫而不詢問
preferences.general.debugLogging=啟用除錯日誌
preferences.general.debugDirectory=顯示日誌檔
preferences.general.autoStart=系統啟動時同時啟動 Cryptomator
@@ -393,6 +395,7 @@ main.vaultlist.contextMenu.vaultoptions=顯示加密檔案庫選項
main.vaultlist.contextMenu.reveal=顯示磁碟
main.vaultlist.addVaultBtn.menuItemNew=新建加密檔案庫...
main.vaultlist.addVaultBtn.menuItemExisting=開啟現有的加密檔案庫...
main.vaultlist.showEventsButton.tooltip=打開事件檢視
##Notificaition
main.notification.updateAvailable=有可用更新
main.notification.support=贊助 Cryptomator.
@@ -421,6 +424,9 @@ main.vaultDetail.stats=加密檔案庫統計
main.vaultDetail.locateEncryptedFileBtn=顯示加密檔案路徑
main.vaultDetail.locateEncryptedFileBtn.tooltip=選擇要顯示對應加密檔案路徑的加密檔案庫檔案
main.vaultDetail.encryptedPathsCopied=路徑已複製到剪貼簿
main.vaultDetail.locateEncrypted.filePickerTitle=從加密檔案庫中選擇檔案
main.vaultDetail.decryptName.buttonLabel=解密檔案名稱
main.vaultDetail.decryptName.tooltip=選擇加密的檔案庫檔案以解密其名稱
### Missing
main.vaultDetail.missing.info=Cryptomator 無法在指定位置找到加密檔案庫。
main.vaultDetail.missing.recheck=重新檢查
@@ -577,7 +583,21 @@ shareVault.hub.instruction.2=2. 在Cryptomator Hub中允許團隊成員對加密
shareVault.hub.openHub=打開 Cryptomator Hub
# Decrypt File Names
decryptNames.filePicker.title=選擇已加密的檔案
decryptNames.filePicker.extensionDescription=Cryptomator 加密檔案
decryptNames.copyTable.tooltip=複製表格
decryptNames.clearTable.tooltip=清除表格
decryptNames.dropZone.error.foreignFiles=檔案不屬於加密檔案庫「%s」
decryptNames.dropZone.error.generic=解密檔案名稱失敗
# Event View
eventView.title=事件
eventView.filter.allVaults=全部
## event list entries
eventView.entry.decryptionFailed.message=解密失敗
eventView.entry.decryptionFailed.showEncrypted=顯示加密的檔案
eventView.entry.decryptionFailed.copyEncrypted=複製加密路徑
eventView.entry.brokenDirFile.message=損壞的目錄連結
eventView.entry.brokenDirFile.showEncrypted=顯示損壞的加密路徑
eventView.entry.brokenDirFile.copyEncrypted=複製損壞的路徑連結