Compare commits

..
Author SHA1 Message Date
Armin Schrenk d013497c48 cleanup 2023-05-09 09:23:20 +02:00
Armin Schrenk 8691eec42b enable directory listings in jar files 2023-05-08 21:21:43 +02:00
Armin Schrenk 0f9d572677 remove static language and read i18dir in constructor 2023-05-08 20:28:56 +02:00
Armin Schrenk baee23b6d4 Merge branch 'develop' into feature/dynamic-languages
# Conflicts:
#	src/main/java/org/cryptomator/launcher/SupportedLanguages.java
#	src/main/java/org/cryptomator/ui/preferences/InterfacePreferencesController.java
2023-05-08 19:56:38 +02:00
Armin Schrenk 7bfd0ed341 load supported languages on app start 2023-04-24 18:52:09 +02:00
57 changed files with 451 additions and 1487 deletions
+2 -2
View File
@@ -10,7 +10,7 @@ on:
required: false required: false
env: env:
JAVA_VERSION: 20 JAVA_VERSION: 19
jobs: jobs:
get-version: get-version:
@@ -54,7 +54,7 @@ jobs:
--verbose --verbose
--output runtime --output runtime
--module-path "${JAVA_HOME}/jmods" --module-path "${JAVA_HOME}/jmods"
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.zipfs
--strip-native-commands --strip-native-commands
--no-header-files --no-header-files
--no-man-pages --no-man-pages
+2 -3
View File
@@ -6,7 +6,7 @@ on:
types: [labeled] types: [labeled]
env: env:
JAVA_VERSION: 20 JAVA_VERSION: 19
defaults: defaults:
run: run:
@@ -53,9 +53,8 @@ jobs:
body: |- body: |-
:construction: Work in Progress :construction: Work in Progress
Please be patient, the builds are still [running](https://github.com/cryptomator/cryptomator/actions). New versions of Cryptomator can be found here in a few moments. Please be patient, the builds are still running. We will publish new versions of Cryptomator here in a few moments.
As usual, the GPG signatures can be checked using [our public key `5811 7AFA 1F85 B3EE C154 677D 615D 449F E6E6 A235`](https://gist.github.com/cryptobot/211111cf092037490275f39d408f461a). As usual, the GPG signatures can be checked using [our public key `5811 7AFA 1F85 B3EE C154 677D 615D 449F E6E6 A235`](https://gist.github.com/cryptobot/211111cf092037490275f39d408f461a).
--- ---
<!-- Don't forget to include the 💾 SHA-256 checksums of release artifacts: --> <!-- Don't forget to include the 💾 SHA-256 checksums of release artifacts: -->
+4 -4
View File
@@ -19,9 +19,9 @@ on:
type: boolean type: boolean
env: env:
JAVA_VERSION: 20 JAVA_VERSION: 19
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/20.0.1/openjfx-20.0.1_linux-x64_bin-jmods.zip' OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/19.0.2.1/openjfx-19.0.2.1_linux-x64_bin-jmods.zip'
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/20.0.1/openjfx-20.0.1_linux-aarch64_bin-jmods.zip' OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/19.0.2.1/openjfx-19.0.2.1_linux-aarch64_bin-jmods.zip'
jobs: jobs:
build: build:
@@ -45,7 +45,7 @@ jobs:
run: | run: |
sudo add-apt-repository ppa:coffeelibs/openjdk sudo add-apt-repository ppa:coffeelibs/openjdk
sudo apt-get update sudo apt-get update
sudo apt-get install debhelper devscripts dput coffeelibs-jdk-${{ env.JAVA_VERSION }} libgtk2.0-0 sudo apt-get install debhelper devscripts dput coffeelibs-jdk-19 libgtk2.0-0
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
+1 -1
View File
@@ -22,7 +22,7 @@ on:
value: ${{ jobs.determine-version.outputs.type }} value: ${{ jobs.determine-version.outputs.type }}
env: env:
JAVA_VERSION: 20 JAVA_VERSION: 19
JAVA_DIST: 'temurin' JAVA_DIST: 'temurin'
JAVA_CACHE: 'maven' JAVA_CACHE: 'maven'
+2 -2
View File
@@ -10,7 +10,7 @@ on:
required: false required: false
env: env:
JAVA_VERSION: 20 JAVA_VERSION: 19
jobs: jobs:
get-version: get-version:
@@ -70,7 +70,7 @@ jobs:
--verbose --verbose
--output runtime --output runtime
--module-path "${JAVA_HOME}/jmods" --module-path "${JAVA_HOME}/jmods"
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr,jdk.zipfs
--strip-native-commands --strip-native-commands
--no-header-files --no-header-files
--no-man-pages --no-man-pages
+1 -1
View File
@@ -4,7 +4,7 @@ on:
pull_request: pull_request:
env: env:
JAVA_VERSION: 20 JAVA_VERSION: 19
defaults: defaults:
run: run:
+1 -1
View File
@@ -7,7 +7,7 @@ on:
- 'hotfix/**' - 'hotfix/**'
env: env:
JAVA_VERSION: 20 JAVA_VERSION: 19
defaults: defaults:
run: run:
+5 -5
View File
@@ -14,11 +14,11 @@ on:
env: env:
JAVA_VERSION: 20 JAVA_VERSION: 19
JAVA_DIST: 'temurin' JAVA_DIST: 'temurin'
JAVA_CACHE: 'maven' JAVA_CACHE: 'maven'
JFX_JMODS_URL: 'https://download2.gluonhq.com/openjfx/20.0.1/openjfx-20.0.1_windows-x64_bin-jmods.zip' JFX_JMODS_URL: 'https://download2.gluonhq.com/openjfx/19.0.2.1/openjfx-19.0.2.1_windows-x64_bin-jmods.zip'
JFX_JMODS_HASH: 'D00767334C43B8832B5CF10267D34CA8F563D187C4655B73EB6020DD79C054B5' JFX_JMODS_HASH: 'B7CF2CAD2468842B3B78D99F6C0555771499A36FA1F1EE3DD1B9A4597F1FAB86'
defaults: defaults:
run: run:
@@ -51,7 +51,7 @@ jobs:
run: | run: |
curl --output jfxjmods.zip -L "${{ env.JFX_JMODS_URL }}" curl --output jfxjmods.zip -L "${{ env.JFX_JMODS_URL }}"
if(!(Get-FileHash -Path jfxjmods.zip -Algorithm SHA256).Hash.equals("${{ env.JFX_JMODS_HASH }}")) { if(!(Get-FileHash -Path jfxjmods.zip -Algorithm SHA256).Hash.equals("${{ env.JFX_JMODS_HASH }}")) {
throw "Wrong checksum of JMOD archive downloaded from ${{ env.JFX_JMODS_URL }}."; exit 1;
} }
Expand-Archive -Path jfxjmods.zip -DestinationPath jfxjmods Expand-Archive -Path jfxjmods.zip -DestinationPath jfxjmods
Get-ChildItem -Path jfxjmods -Recurse -Filter "*.jmod" | ForEach-Object { Move-Item -Path $_ -Destination $_.Directory.Parent} Get-ChildItem -Path jfxjmods -Recurse -Filter "*.jmod" | ForEach-Object { Move-Item -Path $_ -Destination $_.Directory.Parent}
@@ -83,7 +83,7 @@ jobs:
--verbose --verbose
--output runtime --output runtime
--module-path "jfxjmods;${JAVA_HOME}/jmods" --module-path "jfxjmods;${JAVA_HOME}/jmods"
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr,jdk.zipfs
--strip-native-commands --strip-native-commands
--no-header-files --no-header-files
--no-man-pages --no-man-pages
+1 -1
View File
@@ -8,7 +8,7 @@
</list> </list>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_20_PREVIEW" project-jdk-name="20" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_19" default="true" project-jdk-name="19" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
</project> </project>
+1 -1
View File
@@ -24,7 +24,7 @@ ${JAVA_HOME}/bin/jlink \
--verbose \ --verbose \
--output runtime \ --output runtime \
--module-path "${JAVA_HOME}/jmods" \ --module-path "${JAVA_HOME}/jmods" \
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr \ --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.zipfs \
--strip-native-commands \ --strip-native-commands \
--no-header-files \ --no-header-files \
--no-man-pages \ --no-man-pages \
@@ -66,8 +66,6 @@
</content_rating> </content_rating>
<releases> <releases>
<release date="2023-06-07" version="1.9.1"/>
<release date="2023-05-30" version="1.9.0"/>
<release date="2023-04-25" version="1.8.0"/> <release date="2023-04-25" version="1.8.0"/>
<release date="2023-04-07" version="1.7.5"/> <release date="2023-04-07" version="1.7.5"/>
<release date="2023-04-05" version="1.7.4"/> <release date="2023-04-05" version="1.7.4"/>
+1 -1
View File
@@ -2,7 +2,7 @@ Source: cryptomator
Maintainer: Cryptobot <releases@cryptomator.org> Maintainer: Cryptobot <releases@cryptomator.org>
Section: utils Section: utils
Priority: optional Priority: optional
Build-Depends: debhelper (>=10), coffeelibs-jdk-20, libgtk2.0-0, libgtk-3-0, libxxf86vm1, libgl1 Build-Depends: debhelper (>=10), coffeelibs-jdk-19, libgtk2.0-0, libgtk-3-0, libxxf86vm1, libgl1
Standards-Version: 4.5.0 Standards-Version: 4.5.0
Homepage: https://cryptomator.org Homepage: https://cryptomator.org
Vcs-Git: https://github.com/cryptomator/cryptomator.git Vcs-Git: https://github.com/cryptomator/cryptomator.git
+2 -2
View File
@@ -4,7 +4,7 @@
# Uncomment this to turn on verbose mode. # Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1 #export DH_VERBOSE=1
JAVA_HOME = /usr/lib/jvm/java-20-coffeelibs JAVA_HOME = /usr/lib/jvm/java-19-coffeelibs
DEB_BUILD_ARCH ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH) DEB_BUILD_ARCH ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH)
ifeq ($(DEB_BUILD_ARCH),amd64) ifeq ($(DEB_BUILD_ARCH),amd64)
JMODS_PATH = jmods/amd64:${JAVA_HOME}/jmods JMODS_PATH = jmods/amd64:${JAVA_HOME}/jmods
@@ -27,7 +27,7 @@ override_dh_auto_build:
$(JAVA_HOME)/bin/jlink \ $(JAVA_HOME)/bin/jlink \
--output runtime \ --output runtime \
--module-path "${JMODS_PATH}" \ --module-path "${JMODS_PATH}" \
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr \ --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.zipfs \
--strip-native-commands \ --strip-native-commands \
--no-header-files \ --no-header-files \
--no-man-pages \ --no-man-pages \
+1 -1
View File
@@ -46,7 +46,7 @@ cp ../../../target/${MAIN_JAR_GLOB} ../../../target/mods
${JAVA_HOME}/bin/jlink \ ${JAVA_HOME}/bin/jlink \
--output runtime \ --output runtime \
--module-path "${JAVA_HOME}/jmods" \ --module-path "${JAVA_HOME}/jmods" \
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \ --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr,jdk.zipfs \
--strip-native-commands \ --strip-native-commands \
--no-header-files \ --no-header-files \
--no-man-pages \ --no-man-pages \
+1 -1
View File
@@ -51,7 +51,7 @@ if ($clean -and (Test-Path -Path $runtimeImagePath)) {
--verbose ` --verbose `
--output runtime ` --output runtime `
--module-path "$Env:JAVA_HOME/jmods" ` --module-path "$Env:JAVA_HOME/jmods" `
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr ` --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr,jdk.zipfs `
--strip-native-commands ` --strip-native-commands `
--no-header-files ` --no-header-files `
--no-man-pages ` --no-man-pages `
+2 -10
View File
@@ -70,9 +70,6 @@
<CustomAction Id="JpDisallowDowngrade" Error="!(loc.DowngradeErrorMessage)" /> <CustomAction Id="JpDisallowDowngrade" Error="!(loc.DowngradeErrorMessage)" />
<?endif?> <?endif?>
<Binary Id="JpCaDll" SourceFile="wixhelper.dll"/>
<CustomAction Id="JpFindRelatedProducts" BinaryKey="JpCaDll" DllEntry="FindRelatedProductsEx" />
<?ifndef SkipCryptomatorLegacyCheck ?> <?ifndef SkipCryptomatorLegacyCheck ?>
<!-- Block installation if innosetup entry of Cryptomator is found --> <!-- Block installation if innosetup entry of Cryptomator is found -->
<Property Id="OLDEXEINSTALLER"> <Property Id="OLDEXEINSTALLER">
@@ -175,12 +172,11 @@
<?endif?> <?endif?>
<?ifndef JpAllowUpgrades ?> <?ifndef JpAllowUpgrades ?>
<Custom Action="JpDisallowUpgrade" After="JpFindRelatedProducts">JP_UPGRADABLE_FOUND</Custom> <Custom Action="JpDisallowUpgrade" After="FindRelatedProducts">JP_UPGRADABLE_FOUND</Custom>
<?endif?> <?endif?>
<?ifndef JpAllowDowngrades ?> <?ifndef JpAllowDowngrades ?>
<Custom Action="JpDisallowDowngrade" After="JpFindRelatedProducts">JP_DOWNGRADABLE_FOUND</Custom> <Custom Action="JpDisallowDowngrade" After="FindRelatedProducts">JP_DOWNGRADABLE_FOUND</Custom>
<?endif?> <?endif?>
<Custom Action="JpFindRelatedProducts" After="FindRelatedProducts"/>
<!-- Check and fail if Cryptomator is running --> <!-- Check and fail if Cryptomator is running -->
<Custom Action="WixCloseApplications" Before="InstallValidate"></Custom> <Custom Action="WixCloseApplications" Before="InstallValidate"></Custom>
@@ -192,10 +188,6 @@
<Custom Action="V170MigrateSettings" After="InstallFiles">NOT Installed OR REINSTALL</Custom> <Custom Action="V170MigrateSettings" After="InstallFiles">NOT Installed OR REINSTALL</Custom>
</InstallExecuteSequence> </InstallExecuteSequence>
<InstallUISequence>
<Custom Action="JpFindRelatedProducts" After="FindRelatedProducts"/>
</InstallUISequence>
<WixVariable Id="WixUIBannerBmp" Value="$(env.JP_WIXWIZARD_RESOURCES)\banner.bmp" /> <WixVariable Id="WixUIBannerBmp" Value="$(env.JP_WIXWIZARD_RESOURCES)\banner.bmp" />
<WixVariable Id="WixUIDialogBmp" Value="$(env.JP_WIXWIZARD_RESOURCES)\background.bmp" /> <WixVariable Id="WixUIDialogBmp" Value="$(env.JP_WIXWIZARD_RESOURCES)\background.bmp" />
</Product> </Product>
+15 -18
View File
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>cryptomator</artifactId> <artifactId>cryptomator</artifactId>
<version>1.9.1</version> <version>1.9.0-SNAPSHOT</version>
<name>Cryptomator Desktop App</name> <name>Cryptomator Desktop App</name>
<organization> <organization>
@@ -26,45 +26,45 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.jdk.version>20</project.jdk.version> <project.jdk.version>19</project.jdk.version>
<!-- Group IDs of jars that need to stay on the class path for now --> <!-- Group IDs of jars that need to stay on the class path for now -->
<!-- Once hypfvieh, swiesend, purejava and integrations-linux have module-info, remove them--> <!-- Once hypfvieh, swiesend, purejava and integrations-linux have module-info, remove them-->
<nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents,de.swiesend,org.purejava,com.github.hypfvieh</nonModularGroupIds> <nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents,de.swiesend,org.purejava,com.github.hypfvieh</nonModularGroupIds>
<!-- cryptomator dependencies --> <!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>2.6.5</cryptomator.cryptofs.version> <cryptomator.cryptofs.version>2.6.2</cryptomator.cryptofs.version>
<cryptomator.integrations.version>1.2.0</cryptomator.integrations.version> <cryptomator.integrations.version>1.2.0</cryptomator.integrations.version>
<cryptomator.integrations.win.version>1.2.0</cryptomator.integrations.win.version> <cryptomator.integrations.win.version>1.2.0</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.2.0</cryptomator.integrations.mac.version> <cryptomator.integrations.mac.version>1.2.0</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.2.1</cryptomator.integrations.linux.version> <cryptomator.integrations.linux.version>1.2.0</cryptomator.integrations.linux.version>
<cryptomator.fuse.version>3.0.0</cryptomator.fuse.version> <cryptomator.fuse.version>2.0.5</cryptomator.fuse.version>
<cryptomator.dokany.version>2.0.0</cryptomator.dokany.version> <cryptomator.dokany.version>2.0.0</cryptomator.dokany.version>
<cryptomator.webdav.version>2.0.3</cryptomator.webdav.version> <cryptomator.webdav.version>2.0.2</cryptomator.webdav.version>
<!-- 3rd party dependencies --> <!-- 3rd party dependencies -->
<commons-lang3.version>3.12.0</commons-lang3.version> <commons-lang3.version>3.12.0</commons-lang3.version>
<dagger.version>2.45</dagger.version> <dagger.version>2.45</dagger.version>
<easybind.version>2.2</easybind.version> <easybind.version>2.2</easybind.version>
<guava.version>32.0.0-jre</guava.version> <guava.version>31.1-jre</guava.version>
<gson.version>2.10.1</gson.version> <gson.version>2.10.1</gson.version>
<javafx.version>20.0.1</javafx.version> <javafx.version>19.0.2.1</javafx.version>
<jwt.version>4.4.0</jwt.version> <jwt.version>4.3.0</jwt.version>
<nimbus-jose.version>9.31</nimbus-jose.version> <nimbus-jose.version>9.31</nimbus-jose.version>
<logback.version>1.4.7</logback.version> <logback.version>1.4.5</logback.version>
<slf4j.version>2.0.7</slf4j.version> <slf4j.version>2.0.6</slf4j.version>
<tinyoauth2.version>0.5.1</tinyoauth2.version> <tinyoauth2.version>0.5.1</tinyoauth2.version>
<zxcvbn.version>1.7.0</zxcvbn.version> <zxcvbn.version>1.7.0</zxcvbn.version>
<!-- test dependencies --> <!-- test dependencies -->
<junit.jupiter.version>5.9.3</junit.jupiter.version> <junit.jupiter.version>5.9.2</junit.jupiter.version>
<mockito.version>5.3.1</mockito.version> <mockito.version>5.1.1</mockito.version>
<hamcrest.version>2.2</hamcrest.version> <hamcrest.version>2.2</hamcrest.version>
<!-- build-time dependencies --> <!-- build-time dependencies -->
<jetbrains.annotations.version>23.0.0</jetbrains.annotations.version> <jetbrains.annotations.version>23.0.0</jetbrains.annotations.version>
<dependency-check.version>8.1.2</dependency-check.version> <dependency-check.version>8.1.0</dependency-check.version>
<jacoco.version>0.8.9</jacoco.version> <jacoco.version>0.8.8</jacoco.version>
</properties> </properties>
<dependencies> <dependencies>
@@ -332,9 +332,6 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>--enable-preview</argLine>
</configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
-21
View File
@@ -1,16 +1,4 @@
import ch.qos.logback.classic.spi.Configurator; import ch.qos.logback.classic.spi.Configurator;
import org.cryptomator.common.locationpresets.DropboxLinuxLocationPresetsProvider;
import org.cryptomator.common.locationpresets.DropboxMacLocationPresetsProvider;
import org.cryptomator.common.locationpresets.DropboxWindowsLocationPresetsProvider;
import org.cryptomator.common.locationpresets.GoogleDriveLocationPresetsProvider;
import org.cryptomator.common.locationpresets.ICloudMacLocationPresetsProvider;
import org.cryptomator.common.locationpresets.ICloudWindowsLocationPresetsProvider;
import org.cryptomator.common.locationpresets.LocationPresetsProvider;
import org.cryptomator.common.locationpresets.MegaLocationPresetsProvider;
import org.cryptomator.common.locationpresets.OneDriveLinuxLocationPresetsProvider;
import org.cryptomator.common.locationpresets.OneDriveMacLocationPresetsProvider;
import org.cryptomator.common.locationpresets.OneDriveWindowsLocationPresetsProvider;
import org.cryptomator.common.locationpresets.PCloudLocationPresetsProvider;
import org.cryptomator.integrations.tray.TrayMenuController; import org.cryptomator.integrations.tray.TrayMenuController;
import org.cryptomator.logging.LogbackConfiguratorFactory; import org.cryptomator.logging.LogbackConfiguratorFactory;
import org.cryptomator.ui.traymenu.AwtTrayMenuController; import org.cryptomator.ui.traymenu.AwtTrayMenuController;
@@ -49,15 +37,6 @@ open module org.cryptomator.desktop {
/* TODO: filename-based modules: */ /* TODO: filename-based modules: */
requires static javax.inject; /* ugly dagger/guava crap */ requires static javax.inject; /* ugly dagger/guava crap */
uses org.cryptomator.common.locationpresets.LocationPresetsProvider;
provides TrayMenuController with AwtTrayMenuController; provides TrayMenuController with AwtTrayMenuController;
provides Configurator with LogbackConfiguratorFactory; provides Configurator with LogbackConfiguratorFactory;
provides LocationPresetsProvider with DropboxMacLocationPresetsProvider, //
DropboxWindowsLocationPresetsProvider, DropboxLinuxLocationPresetsProvider, //
ICloudMacLocationPresetsProvider, ICloudWindowsLocationPresetsProvider, //
GoogleDriveLocationPresetsProvider, //
PCloudLocationPresetsProvider, MegaLocationPresetsProvider, //
OneDriveLinuxLocationPresetsProvider, OneDriveWindowsLocationPresetsProvider, //
OneDriveMacLocationPresetsProvider;
} }
@@ -0,0 +1,64 @@
package org.cryptomator.common;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
/**
* Enum of common cloud providers and their default local storage location path.
*/
public enum LocationPreset {
DROPBOX("Dropbox", "~/Dropbox"),
ICLOUDDRIVE("iCloud Drive", "~/Library/Mobile Documents/com~apple~CloudDocs", "~/iCloudDrive"),
GDRIVE("Google Drive", "~/Google Drive/My Drive", "~/Google Drive"),
MEGA("MEGA", "~/MEGA"),
ONEDRIVE("OneDrive", "~/OneDrive"),
PCLOUD("pCloud", "~/pCloudDrive"),
LOCAL("local");
private final String name;
private final List<Path> candidates;
LocationPreset(String name, String... candidates) {
this.name = name;
this.candidates = Arrays.stream(candidates).map(UserHome::resolve).map(Path::of).toList();
}
/**
* Checks for this LocationPreset if any of the associated paths exist.
*
* @return the first existing path or null, if none exists.
*/
public Path existingPath() {
return candidates.stream().filter(Files::isDirectory).findFirst().orElse(null);
}
public String getDisplayName() {
return name;
}
@Override
public String toString() {
return getDisplayName();
}
//this contruct is needed, since static members are initialized after every enum member is initialized
//TODO: refactor this to normal class and use this also in different parts of the project
private static class UserHome {
private static final String USER_HOME = System.getProperty("user.home");
private static String resolve(String path) {
if (path.startsWith("~/")) {
return UserHome.USER_HOME + path.substring(1);
} else {
return path;
}
}
}
}
@@ -1,32 +0,0 @@
package org.cryptomator.common.locationpresets;
import org.cryptomator.integrations.common.OperatingSystem;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import static org.cryptomator.integrations.common.OperatingSystem.Value.LINUX;
@OperatingSystem(LINUX)
public final class DropboxLinuxLocationPresetsProvider implements LocationPresetsProvider {
private static final Path USER_HOME = LocationPresetsProvider.resolveLocation("~/.").toAbsolutePath();
private static final Predicate<String> PATTERN = Pattern.compile("Dropbox \\(.+\\)").asMatchPredicate();
@Override
public Stream<LocationPreset> getLocations() {
try (var dirStream = Files.list(USER_HOME)) {
var presets = dirStream.filter(p -> Files.isDirectory(p) && PATTERN.test(p.getFileName().toString())) //
.map(p -> new LocationPreset(p.getFileName().toString(), p)) //
.toList();
return presets.stream(); //workaround to ensure that the directory stream is always closed
} catch (IOException | UncheckedIOException e) { //UncheckedIOException thrown by the stream of Files.list()
return Stream.of();
}
}
}
@@ -1,35 +0,0 @@
package org.cryptomator.common.locationpresets;
import org.cryptomator.integrations.common.CheckAvailability;
import org.cryptomator.integrations.common.OperatingSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
import static org.cryptomator.integrations.common.OperatingSystem.Value.MAC;
@OperatingSystem(MAC)
@CheckAvailability
public final class DropboxMacLocationPresetsProvider implements LocationPresetsProvider {
private static final Path LOCATION = LocationPresetsProvider.resolveLocation("~/Library/CloudStorage/Dropbox");
private static final Path FALLBACK_LOCATION = LocationPresetsProvider.resolveLocation("~/Dropbox");
@CheckAvailability
public static boolean isPresent() {
return Files.isDirectory(LOCATION) || Files.isDirectory(FALLBACK_LOCATION);
}
@Override
public Stream<LocationPreset> getLocations() {
if(Files.isDirectory(LOCATION)) {
return Stream.of(new LocationPreset("Dropbox", LOCATION));
} else if(Files.isDirectory(FALLBACK_LOCATION)) {
return Stream.of(new LocationPreset("Dropbox", FALLBACK_LOCATION));
} else {
return Stream.of();
}
}
}
@@ -1,28 +0,0 @@
package org.cryptomator.common.locationpresets;
import org.cryptomator.integrations.common.CheckAvailability;
import org.cryptomator.integrations.common.OperatingSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
import static org.cryptomator.integrations.common.OperatingSystem.Value.WINDOWS;
@OperatingSystem(WINDOWS)
@CheckAvailability
public final class DropboxWindowsLocationPresetsProvider implements LocationPresetsProvider {
private static final Path LOCATION = LocationPresetsProvider.resolveLocation("~/Dropbox");
@CheckAvailability
public static boolean isPresent() {
return Files.isDirectory(LOCATION);
}
@Override
public Stream<LocationPreset> getLocations() {
return Stream.of(new LocationPreset("Dropbox", LOCATION));
}
}
@@ -1,37 +0,0 @@
package org.cryptomator.common.locationpresets;
import org.cryptomator.integrations.common.CheckAvailability;
import org.cryptomator.integrations.common.OperatingSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
import static org.cryptomator.integrations.common.OperatingSystem.Value.MAC;
import static org.cryptomator.integrations.common.OperatingSystem.Value.WINDOWS;
@OperatingSystem(WINDOWS)
@OperatingSystem(MAC)
@CheckAvailability
public final class GoogleDriveLocationPresetsProvider implements LocationPresetsProvider {
private static final Path LOCATION1 = LocationPresetsProvider.resolveLocation("~/GoogleDrive");
private static final Path LOCATION2 = LocationPresetsProvider.resolveLocation("~/GoogleDrive/My Drive");
@CheckAvailability
public static boolean isPresent() {
return Files.isDirectory(LOCATION1) || Files.isDirectory(LOCATION2);
}
@Override
public Stream<LocationPreset> getLocations() {
if(Files.isDirectory(LOCATION1)) {
return Stream.of(new LocationPreset("Google Drive", LOCATION1));
} else if(Files.isDirectory(LOCATION2)) {
return Stream.of(new LocationPreset("Google Drive", LOCATION2));
} else {
return Stream.of();
}
}
}
@@ -1,27 +0,0 @@
package org.cryptomator.common.locationpresets;
import org.cryptomator.integrations.common.CheckAvailability;
import org.cryptomator.integrations.common.OperatingSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
import static org.cryptomator.integrations.common.OperatingSystem.Value.MAC;
@OperatingSystem(MAC)
@CheckAvailability
public final class ICloudMacLocationPresetsProvider implements LocationPresetsProvider {
private static final Path LOCATION = LocationPresetsProvider.resolveLocation("~/Library/Mobile Documents/com~apple~CloudDocs");
@CheckAvailability
public static boolean isPresent() {
return Files.isDirectory(LOCATION);
}
@Override
public Stream<LocationPreset> getLocations() {
return Stream.of(new LocationPreset("iCloud Drive", LOCATION));
}
}
@@ -1,27 +0,0 @@
package org.cryptomator.common.locationpresets;
import org.cryptomator.integrations.common.CheckAvailability;
import org.cryptomator.integrations.common.OperatingSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
import static org.cryptomator.integrations.common.OperatingSystem.Value.WINDOWS;
@OperatingSystem(WINDOWS)
@CheckAvailability
public final class ICloudWindowsLocationPresetsProvider implements LocationPresetsProvider {
private static final Path LOCATION = LocationPresetsProvider.resolveLocation("~/iCloudDrive");
@CheckAvailability
public static boolean isPresent() {
return Files.isDirectory(LOCATION);
}
@Override
public Stream<LocationPreset> getLocations() {
return Stream.of(new LocationPreset("iCloud Drive", LOCATION));
}
}
@@ -1,9 +0,0 @@
package org.cryptomator.common.locationpresets;
import java.nio.file.Path;
public record LocationPreset(String name, Path path) {
}
@@ -1,97 +0,0 @@
package org.cryptomator.common.locationpresets;
import org.cryptomator.integrations.common.CheckAvailability;
import org.cryptomator.integrations.common.IntegrationsLoader;
import org.cryptomator.integrations.common.OperatingSystem;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.ServiceLoader;
import java.util.stream.Stream;
public interface LocationPresetsProvider {
Logger LOG = LoggerFactory.getLogger(LocationPresetsProvider.class);
String USER_HOME = System.getProperty("user.home");
/**
* Streams account-separated location presets found by this provider
* @return Stream of LocationPresets
*/
Stream<LocationPreset> getLocations();
static Path resolveLocation(String p) {
if (p.startsWith("~/")) {
return Path.of(USER_HOME, p.substring(2));
} else {
return Path.of(p);
}
}
//copied from org.cryptomator.integrations.common.IntegrationsLoader
//TODO: delete, once migrated to integrations-api
static <T> Stream<T> loadAll(Class<T> clazz) {
return ServiceLoader.load(clazz)
.stream()
.filter(LocationPresetsProvider::isSupportedOperatingSystem)
.filter(LocationPresetsProvider::passesStaticAvailabilityCheck)
.map(ServiceLoader.Provider::get)
.peek(impl -> logServiceIsAvailable(clazz, impl.getClass()));
}
private static boolean isSupportedOperatingSystem(ServiceLoader.Provider<?> provider) {
var annotations = provider.type().getAnnotationsByType(OperatingSystem.class);
return annotations.length == 0 || Arrays.stream(annotations).anyMatch(OperatingSystem.Value::isCurrent);
}
private static boolean passesStaticAvailabilityCheck(ServiceLoader.Provider<?> provider) {
return passesStaticAvailabilityCheck(provider.type());
}
static boolean passesStaticAvailabilityCheck(Class<?> type) {
return passesAvailabilityCheck(type, null);
}
private static void logServiceIsAvailable(Class<?> apiType, Class<?> implType) {
if (LOG.isDebugEnabled()) {
LOG.debug("{}: Implementation is available: {}", apiType.getSimpleName(), implType.getName());
}
}
private static <T> boolean passesAvailabilityCheck(Class<? extends T> type, @Nullable T instance) {
if (!type.isAnnotationPresent(CheckAvailability.class)) {
return true; // if type is not annotated, skip tests
}
if (!type.getModule().isExported(type.getPackageName(), IntegrationsLoader.class.getModule())) {
LOG.error("Can't run @CheckAvailability tests for class {}. Make sure to export {} to {}!", type.getName(), type.getPackageName(), IntegrationsLoader.class.getPackageName());
return false;
}
return Arrays.stream(type.getMethods())
.filter(m -> isAvailabilityCheck(m, instance == null))
.allMatch(m -> passesAvailabilityCheck(m, instance));
}
private static boolean passesAvailabilityCheck(Method m, @Nullable Object instance) {
assert Boolean.TYPE.equals(m.getReturnType());
try {
return (boolean) m.invoke(instance);
} catch (ReflectiveOperationException e) {
LOG.warn("Failed to invoke @CheckAvailability test {}#{}", m.getDeclaringClass(), m.getName(), e);
return false;
}
}
private static boolean isAvailabilityCheck(Method m, boolean isStatic) {
return m.isAnnotationPresent(CheckAvailability.class)
&& Boolean.TYPE.equals(m.getReturnType())
&& m.getParameterCount() == 0
&& Modifier.isStatic(m.getModifiers()) == isStatic;
}
}
@@ -1,29 +0,0 @@
package org.cryptomator.common.locationpresets;
import org.cryptomator.integrations.common.CheckAvailability;
import org.cryptomator.integrations.common.OperatingSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
import static org.cryptomator.integrations.common.OperatingSystem.Value.MAC;
import static org.cryptomator.integrations.common.OperatingSystem.Value.WINDOWS;
@OperatingSystem(WINDOWS)
@OperatingSystem(MAC)
@CheckAvailability
public final class MegaLocationPresetsProvider implements LocationPresetsProvider {
private static final Path LOCATION = LocationPresetsProvider.resolveLocation("~/MEGA");
@CheckAvailability
public static boolean isPresent() {
return Files.isDirectory(LOCATION);
}
@Override
public Stream<LocationPreset> getLocations() {
return Stream.of(new LocationPreset("MEGA", LOCATION));
}
}
@@ -1,28 +0,0 @@
package org.cryptomator.common.locationpresets;
import org.cryptomator.integrations.common.CheckAvailability;
import org.cryptomator.integrations.common.OperatingSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
import static org.cryptomator.integrations.common.OperatingSystem.Value.LINUX;
@OperatingSystem(LINUX)
@CheckAvailability
public final class OneDriveLinuxLocationPresetsProvider implements LocationPresetsProvider {
private static final Path LOCATION = LocationPresetsProvider.resolveLocation("~/OneDrive");
@CheckAvailability
public static boolean isPresent() {
return Files.isDirectory(LOCATION);
}
@Override
public Stream<LocationPreset> getLocations() {
return Stream.of(new LocationPreset("OneDrive", LOCATION));
}
}
@@ -1,44 +0,0 @@
package org.cryptomator.common.locationpresets;
import org.cryptomator.integrations.common.OperatingSystem;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static org.cryptomator.integrations.common.OperatingSystem.Value.MAC;
@OperatingSystem(MAC)
public final class OneDriveMacLocationPresetsProvider implements LocationPresetsProvider {
private static final Path FALLBACK_LOCATION = LocationPresetsProvider.resolveLocation("~/OneDrive");
private static final Path PARENT_LOCATION = LocationPresetsProvider.resolveLocation("~/Library/CloudStorage");
@Override
public Stream<LocationPreset> getLocations() {
var newLocations = getNewLocations().toList();
if (newLocations.size() >= 1) {
return newLocations.stream();
} else {
return getOldLocation();
}
}
private Stream<LocationPreset> getNewLocations() {
try (var dirStream = Files.newDirectoryStream(PARENT_LOCATION, "OneDrive*")) {
return StreamSupport.stream(dirStream.spliterator(), false) //
.filter(Files::isDirectory) //
.map(p -> new LocationPreset(String.join(" - ", p.getFileName().toString().split("-")), p));
} catch (IOException e) {
return Stream.of();
}
}
private Stream<LocationPreset> getOldLocation() {
return Stream.of(new LocationPreset("OneDrive", FALLBACK_LOCATION)).filter(preset -> Files.isDirectory(preset.path()));
}
}
@@ -1,108 +0,0 @@
package org.cryptomator.common.locationpresets;
import org.cryptomator.integrations.common.OperatingSystem;
import org.jetbrains.annotations.Blocking;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.cryptomator.integrations.common.OperatingSystem.Value.WINDOWS;
@OperatingSystem(WINDOWS)
public final class OneDriveWindowsLocationPresetsProvider implements LocationPresetsProvider {
private static final Logger LOG = LoggerFactory.getLogger(OneDriveWindowsLocationPresetsProvider.class);
private static final String REGSTR_TOKEN = "REG_SZ";
private static final String REG_ONEDRIVE_ACCOUNTS = "HKEY_CURRENT_USER\\Software\\Microsoft\\OneDrive\\Accounts\\";
@Override
public Stream<LocationPreset> getLocations() {
try {
var accountRegKeys = queryRegistry(REG_ONEDRIVE_ACCOUNTS, List.of(), l -> l.startsWith(REG_ONEDRIVE_ACCOUNTS)).toList();
var cloudLocations = new ArrayList<LocationPreset>();
for (var accountRegKey : accountRegKeys) {
var path = queryRegistry(accountRegKey, List.of("/v", "UserFolder"), l -> l.contains("UserFolder")).map(result -> result.substring(result.indexOf(REGSTR_TOKEN) + REGSTR_TOKEN.length()).trim()) //
.map(Path::of) //
.findFirst().orElseThrow();
var name = "OneDrive"; //we assume personal oneDrive account by default
if (!accountRegKey.endsWith("Personal")) {
name = queryRegistry(accountRegKey, List.of("/v", "DisplayName"), l -> l.contains("DisplayName")).map(result -> result.substring(result.indexOf(REGSTR_TOKEN) + REGSTR_TOKEN.length()).trim()) //
.map("OneDrive - "::concat) //
.findFirst().orElseThrow();
}
cloudLocations.add(new LocationPreset(name, path));
}
return cloudLocations.stream();
} catch (IOException | CommandFailedException | TimeoutException e) {
LOG.error("Unable to determine OneDrive location", e);
return Stream.of();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
LOG.error("Determination of OneDrive location interrupted", e);
return Stream.of();
}
}
private Stream<String> queryRegistry(String keyname, List<String> moreArgs, Predicate<String> outputFilter) throws InterruptedException, CommandFailedException, TimeoutException, IOException {
var args = new ArrayList<String>();
args.add("reg");
args.add("query");
args.add(keyname);
args.addAll(moreArgs);
ProcessBuilder command = new ProcessBuilder(args);
Process p = command.start();
waitForSuccess(p, 3, "`reg query`");
return p.inputReader(StandardCharsets.UTF_8).lines().filter(outputFilter);
}
/**
* Waits {@code timeoutSeconds} seconds for {@code process} to finish with exit code {@code 0}.
*
* @param process The process to wait for
* @param timeoutSeconds How long to wait (in seconds)
* @param cmdDescription A short description of the process used to generate log and exception messages
* @throws TimeoutException Thrown when the process doesn't finish in time
* @throws InterruptedException Thrown when the thread is interrupted while waiting for the process to finish
* @throws CommandFailedException Thrown when the process exit code is non-zero
*/
@Blocking
private static void waitForSuccess(Process process, int timeoutSeconds, String cmdDescription) throws TimeoutException, InterruptedException, CommandFailedException {
boolean exited = process.waitFor(timeoutSeconds, TimeUnit.SECONDS);
if (!exited) {
throw new TimeoutException(cmdDescription + " timed out after " + timeoutSeconds + "s");
}
if (process.exitValue() != 0) {
@SuppressWarnings("resource") var stdout = process.inputReader(StandardCharsets.UTF_8).lines().collect(Collectors.joining("\n"));
@SuppressWarnings("resource") var stderr = process.errorReader(StandardCharsets.UTF_8).lines().collect(Collectors.joining("\n"));
throw new CommandFailedException(cmdDescription, process.exitValue(), stdout, stderr);
}
}
private static class CommandFailedException extends Exception {
int exitCode;
String stdout;
String stderr;
private CommandFailedException(String cmdDescription, int exitCode, String stdout, String stderr) {
super(cmdDescription + " returned with non-zero exit code " + exitCode);
this.exitCode = exitCode;
this.stdout = stdout;
this.stderr = stderr;
}
}
}
@@ -1,30 +0,0 @@
package org.cryptomator.common.locationpresets;
import org.cryptomator.integrations.common.CheckAvailability;
import org.cryptomator.integrations.common.OperatingSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
import static org.cryptomator.integrations.common.OperatingSystem.Value.MAC;
import static org.cryptomator.integrations.common.OperatingSystem.Value.WINDOWS;
@OperatingSystem(WINDOWS)
@OperatingSystem(MAC)
@CheckAvailability
public final class PCloudLocationPresetsProvider implements LocationPresetsProvider {
private static final Path LOCATION = LocationPresetsProvider.resolveLocation("~/pCloudDrive");
@CheckAvailability
public static boolean isPresent() {
return Files.isDirectory(LOCATION);
}
@Override
public Stream<LocationPreset> getLocations() {
return Stream.of(new LocationPreset("pCloud", LOCATION));
}
}
@@ -9,8 +9,8 @@ import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel; import java.nio.channels.WritableByteChannel;
import java.util.function.Function; import java.util.function.Function;
//TODO can the enum be removed? // TODO make sealed, remove enum
sealed interface IpcMessage permits HandleLaunchArgsMessage, RevealRunningAppMessage { interface IpcMessage {
enum MessageType { enum MessageType {
REVEAL_RUNNING_APP(RevealRunningAppMessage::decode), REVEAL_RUNNING_APP(RevealRunningAppMessage::decode),
@@ -5,9 +5,10 @@ import java.util.List;
public interface IpcMessageListener { public interface IpcMessageListener {
default void handleMessage(IpcMessage message) { default void handleMessage(IpcMessage message) {
switch (message) { if (message instanceof RevealRunningAppMessage) {
case RevealRunningAppMessage m -> revealRunningApp(); // TODO: rename to _ with JEP 443 revealRunningApp();
case HandleLaunchArgsMessage m -> handleLaunchArgs(m.args()); } else if (message instanceof HandleLaunchArgsMessage m) {
handleLaunchArgs(m.args());
} }
} }
@@ -6,25 +6,27 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.Collator; import java.text.Collator;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.stream.StreamSupport;
@Singleton @Singleton
public class SupportedLanguages { public class SupportedLanguages {
private static final Logger LOG = LoggerFactory.getLogger(SupportedLanguages.class); private static final Logger LOG = LoggerFactory.getLogger(SupportedLanguages.class);
// these are BCP 47 language codes, not ISO. Note the "-" instead of the "_".
// "en" is not part of this list - it is always inserted at the top.
public static final List<String> LANGUAGE_TAGS = List.of("ar", "be", "bn", "bs", "ca", "cs", "da", "de", "el", "es", "fr", "gl", "he", //
"hi", "hr", "hu", "id", "it", "ja", "ko", "lv", "nb", "nl", "nn", "pa", "pl", "pt", "pt-BR", "ro", "ru", "sk", "sr", "sr-Latn", "sv", "sw", //
"ta", "th", "tr", "uk", "vi", "zh", "zh-HK", "zh-TW");
public static final String ENGLISH = "en"; public static final String ENGLISH = "en";
private final List<String> sortedLanguageTags; private final List<String> sortedLanguageTags;
private final Locale preferredLocale; private final Locale preferredLocale;
@Inject @Inject
@@ -33,13 +35,13 @@ public class SupportedLanguages {
preferredLocale = preferredLanguage == null ? Locale.getDefault() : Locale.forLanguageTag(preferredLanguage); preferredLocale = preferredLanguage == null ? Locale.getDefault() : Locale.forLanguageTag(preferredLanguage);
var collator = Collator.getInstance(preferredLocale); var collator = Collator.getInstance(preferredLocale);
collator.setStrength(Collator.PRIMARY); collator.setStrength(Collator.PRIMARY);
var sorted = new ArrayList<String>(); List<String> sortedTags = new ArrayList<>();
sorted.add(0, Settings.DEFAULT_LANGUAGE); sortedTags.add(0, Settings.DEFAULT_LANGUAGE);
sorted.add(1, ENGLISH); sortedTags.add(1, ENGLISH);
LANGUAGE_TAGS.stream() // getSupportedLanguageTags().stream() //
.sorted((a, b) -> collator.compare(Locale.forLanguageTag(a).getDisplayName(), Locale.forLanguageTag(b).getDisplayName())) // .sorted((a, b) -> collator.compare(Locale.forLanguageTag(a).getDisplayName(), Locale.forLanguageTag(b).getDisplayName())) //
.forEach(sorted::add); .forEach(sortedTags::add);
sortedLanguageTags = Collections.unmodifiableList(sorted); sortedLanguageTags = Collections.unmodifiableList(sortedTags);
} }
public void applyPreferred() { public void applyPreferred() {
@@ -51,4 +53,43 @@ public class SupportedLanguages {
return sortedLanguageTags; return sortedLanguageTags;
} }
/**
* Iterates over the /i18n directory and extracts from every localization file the BCP 47 code.
*
* @return list of supported BCP 47 language codes
*/
private static List<String> getSupportedLanguageTags() {
try {
var i18DirURI = SupportedLanguages.class.getResource("/i18n").toURI();
var uriScheme = i18DirURI.getScheme();
if (uriScheme.equals("jar")) {
final String[] array = i18DirURI.toString().split("!");
try (var jarFs = FileSystems.newFileSystem(URI.create(array[0]), Map.<String, String>of())) {
return streamDirectory(jarFs.getPath(array[1]));
}
} else if (uriScheme.equals("file")) {
return streamDirectory(Path.of(i18DirURI));
} else {
throw new IOException("Unsupported uri scheme: " + uriScheme);
}
} catch (URISyntaxException | IOException e) {
LOG.warn("Unable to determine additional supported languages.", e);
return List.of();
}
}
private static List<String> streamDirectory(Path i18Dir) throws IOException {
try (var dirStream = Files.newDirectoryStream(i18Dir, "strings_*.properties")) {
return StreamSupport.stream(dirStream.spliterator(), false) //
.map(SupportedLanguages::getBCP47CodeFromFileName) //
.toList();
}
}
private static String getBCP47CodeFromFileName(Path p) {
var fileName = p.getFileName().toString();
return fileName.substring("strings_".length(), fileName.indexOf(".properties")).replace('_', '-');
}
} }
@@ -1,9 +1,6 @@
package org.cryptomator.ui.addvaultwizard; package org.cryptomator.ui.addvaultwizard;
import dagger.Lazy; import dagger.Lazy;
import org.cryptomator.common.ObservableUtil;
import org.cryptomator.common.locationpresets.LocationPreset;
import org.cryptomator.common.locationpresets.LocationPresetsProvider;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.FxmlScene;
@@ -13,19 +10,22 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.RadioButton; import javafx.scene.control.RadioButton;
import javafx.scene.control.Toggle; import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup; import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.VBox;
import javafx.stage.DirectoryChooser; import javafx.stage.DirectoryChooser;
import javafx.stage.Stage; import javafx.stage.Stage;
import java.io.File; import java.io.File;
@@ -34,9 +34,6 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle; import java.util.ResourceBundle;
@AddVaultWizardScoped @AddVaultWizardScoped
@@ -49,72 +46,70 @@ public class CreateNewVaultLocationController implements FxController {
private final Stage window; private final Stage window;
private final Lazy<Scene> chooseNameScene; private final Lazy<Scene> chooseNameScene;
private final Lazy<Scene> choosePasswordScene; private final Lazy<Scene> choosePasswordScene;
private final List<RadioButton> locationPresetBtns; private final ObservedLocationPresets locationPresets;
private final ObjectProperty<Path> vaultPath; private final ObjectProperty<Path> vaultPath;
private final StringProperty vaultName; private final StringProperty vaultName;
private final ResourceBundle resourceBundle; private final ResourceBundle resourceBundle;
private final ObservableValue<VaultPathStatus> vaultPathStatus; private final BooleanBinding validVaultPath;
private final ObservableValue<Boolean> validVaultPath;
private final BooleanProperty usePresetPath; private final BooleanProperty usePresetPath;
private final StringProperty statusText;
private final ObjectProperty<Node> statusGraphic;
private Path customVaultPath = DEFAULT_CUSTOM_VAULT_PATH; private Path customVaultPath = DEFAULT_CUSTOM_VAULT_PATH;
//FXML //FXML
public ToggleGroup locationPresetsToggler; public ToggleGroup predefinedLocationToggler;
public VBox radioButtonVBox; public RadioButton iclouddriveRadioButton;
public RadioButton dropboxRadioButton;
public RadioButton gdriveRadioButton;
public RadioButton onedriveRadioButton;
public RadioButton megaRadioButton;
public RadioButton pcloudRadioButton;
public RadioButton customRadioButton; public RadioButton customRadioButton;
public Label locationStatusLabel; public Label vaultPathStatus;
public FontAwesome5IconView goodLocation; public FontAwesome5IconView goodLocation;
public FontAwesome5IconView badLocation; public FontAwesome5IconView badLocation;
@Inject @Inject
CreateNewVaultLocationController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy<Scene> chooseNameScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD) Lazy<Scene> choosePasswordScene, ObjectProperty<Path> vaultPath, @Named("vaultName") StringProperty vaultName, ResourceBundle resourceBundle) { CreateNewVaultLocationController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy<Scene> chooseNameScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD) Lazy<Scene> choosePasswordScene, ObservedLocationPresets locationPresets, ObjectProperty<Path> vaultPath, @Named("vaultName") StringProperty vaultName, ResourceBundle resourceBundle) {
this.window = window; this.window = window;
this.chooseNameScene = chooseNameScene; this.chooseNameScene = chooseNameScene;
this.choosePasswordScene = choosePasswordScene; this.choosePasswordScene = choosePasswordScene;
this.locationPresets = locationPresets;
this.vaultPath = vaultPath; this.vaultPath = vaultPath;
this.vaultName = vaultName; this.vaultName = vaultName;
this.resourceBundle = resourceBundle; this.resourceBundle = resourceBundle;
this.vaultPathStatus = ObservableUtil.mapWithDefault(vaultPath, this::validatePath, new VaultPathStatus(false, "error.message")); this.validVaultPath = Bindings.createBooleanBinding(this::validateVaultPathAndSetStatus, this.vaultPath);
this.validVaultPath = ObservableUtil.mapWithDefault(vaultPathStatus, VaultPathStatus::valid, false);
this.vaultPathStatus.addListener(this::updateStatusLabel);
this.usePresetPath = new SimpleBooleanProperty(); this.usePresetPath = new SimpleBooleanProperty();
this.locationPresetBtns = LocationPresetsProvider.loadAll(LocationPresetsProvider.class) // this.statusText = new SimpleStringProperty();
.flatMap(LocationPresetsProvider::getLocations) // this.statusGraphic = new SimpleObjectProperty<>();
.sorted(Comparator.comparing(LocationPreset::name)) //
.map(preset -> { //
var btn = new RadioButton(preset.name());
btn.setUserData(preset.path());
return btn;
}).toList();
} }
private VaultPathStatus validatePath(Path p) throws NullPointerException { private boolean validateVaultPathAndSetStatus() {
if (!Files.exists(p.getParent())) { final Path p = vaultPath.get();
return new VaultPathStatus(false, "addvaultwizard.new.locationDoesNotExist"); if (p == null) {
statusText.set("Error: Path is NULL.");
statusGraphic.set(badLocation);
return false;
} else if (!Files.exists(p.getParent())) {
statusText.set(resourceBundle.getString("addvaultwizard.new.locationDoesNotExist"));
statusGraphic.set(badLocation);
return false;
} else if (!isActuallyWritable(p.getParent())) { } else if (!isActuallyWritable(p.getParent())) {
return new VaultPathStatus(false, "addvaultwizard.new.locationIsNotWritable"); statusText.set(resourceBundle.getString("addvaultwizard.new.locationIsNotWritable"));
statusGraphic.set(badLocation);
return false;
} else if (!Files.notExists(p)) { } else if (!Files.notExists(p)) {
return new VaultPathStatus(false, "addvaultwizard.new.fileAlreadyExists"); statusText.set(resourceBundle.getString("addvaultwizard.new.fileAlreadyExists"));
statusGraphic.set(badLocation);
return false;
} else { } else {
return new VaultPathStatus(true, "addvaultwizard.new.locationIsOk"); statusText.set(resourceBundle.getString("addvaultwizard.new.locationIsOk"));
statusGraphic.set(goodLocation);
return true;
} }
} }
private void updateStatusLabel(ObservableValue<? extends VaultPathStatus> observable, VaultPathStatus oldValue, VaultPathStatus newValue) {
if (newValue.valid()) {
locationStatusLabel.setGraphic(goodLocation);
locationStatusLabel.getStyleClass().remove("label-red");
locationStatusLabel.getStyleClass().add("label-muted");
} else {
locationStatusLabel.setGraphic(badLocation);
locationStatusLabel.getStyleClass().remove("label-muted");
locationStatusLabel.getStyleClass().add("label-red");
}
this.locationStatusLabel.setText(resourceBundle.getString(newValue.localizationKey()));
}
private boolean isActuallyWritable(Path p) { private boolean isActuallyWritable(Path p) {
Path tmpFile = p.resolve(TEMP_FILE_FORMAT); Path tmpFile = p.resolve(TEMP_FILE_FORMAT);
try (var chan = Files.newByteChannel(tmpFile, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE, StandardOpenOption.DELETE_ON_CLOSE)) { try (var chan = Files.newByteChannel(tmpFile, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE, StandardOpenOption.DELETE_ON_CLOSE)) {
@@ -132,15 +127,26 @@ public class CreateNewVaultLocationController implements FxController {
@FXML @FXML
public void initialize() { public void initialize() {
radioButtonVBox.getChildren().addAll(1, locationPresetBtns); //first item is the list header predefinedLocationToggler.selectedToggleProperty().addListener(this::togglePredefinedLocation);
locationPresetsToggler.getToggles().addAll(locationPresetBtns); usePresetPath.bind(predefinedLocationToggler.selectedToggleProperty().isNotEqualTo(customRadioButton));
locationPresetsToggler.selectedToggleProperty().addListener(this::togglePredefinedLocation);
usePresetPath.bind(locationPresetsToggler.selectedToggleProperty().isNotEqualTo(customRadioButton));
} }
private void togglePredefinedLocation(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) { private void togglePredefinedLocation(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
var storagePath = Optional.ofNullable((Path) newValue.getUserData()).orElse(customVaultPath); if (iclouddriveRadioButton.equals(newValue)) {
vaultPath.set(storagePath.resolve(vaultName.get())); vaultPath.set(locationPresets.getIclouddriveLocation().resolve(vaultName.get()));
} else if (dropboxRadioButton.equals(newValue)) {
vaultPath.set(locationPresets.getDropboxLocation().resolve(vaultName.get()));
} else if (gdriveRadioButton.equals(newValue)) {
vaultPath.set(locationPresets.getGdriveLocation().resolve(vaultName.get()));
} else if (onedriveRadioButton.equals(newValue)) {
vaultPath.set(locationPresets.getOnedriveLocation().resolve(vaultName.get()));
} else if (megaRadioButton.equals(newValue)) {
vaultPath.set(locationPresets.getMegaLocation().resolve(vaultName.get()));
} else if (pcloudRadioButton.equals(newValue)) {
vaultPath.set(locationPresets.getPcloudLocation().resolve(vaultName.get()));
} else if (customRadioButton.equals(newValue)) {
vaultPath.set(customVaultPath.resolve(vaultName.get()));
}
} }
@FXML @FXML
@@ -150,8 +156,10 @@ public class CreateNewVaultLocationController implements FxController {
@FXML @FXML
public void next() { public void next() {
if (validVaultPath.getValue()) { if (validateVaultPathAndSetStatus()) {
window.setScene(choosePasswordScene.get()); window.setScene(choosePasswordScene.get());
} else {
validVaultPath.invalidate();
} }
} }
@@ -171,12 +179,6 @@ public class CreateNewVaultLocationController implements FxController {
} }
} }
/* Internal classes */
private record VaultPathStatus(boolean valid, String localizationKey) {
}
/* Getter/Setter */ /* Getter/Setter */
public Path getVaultPath() { public Path getVaultPath() {
@@ -187,28 +189,47 @@ public class CreateNewVaultLocationController implements FxController {
return vaultPath; return vaultPath;
} }
public ObservableValue<Boolean> validVaultPathProperty() { public BooleanBinding validVaultPathProperty() {
return validVaultPath; return validVaultPath;
} }
public boolean isValidVaultPath() { public Boolean getValidVaultPath() {
return validVaultPath.getValue(); return validVaultPath.get();
}
public ObservedLocationPresets getObservedLocationPresets() {
return locationPresets;
} }
public BooleanProperty usePresetPathProperty() { public BooleanProperty usePresetPathProperty() {
return usePresetPath; return usePresetPath;
} }
public boolean isUsePresetPath() { public boolean getUsePresetPath() {
return usePresetPath.get(); return usePresetPath.get();
} }
public BooleanBinding anyRadioButtonSelectedProperty() { public BooleanBinding anyRadioButtonSelectedProperty() {
return locationPresetsToggler.selectedToggleProperty().isNotNull(); return predefinedLocationToggler.selectedToggleProperty().isNotNull();
} }
public boolean isAnyRadioButtonSelected() { public boolean isAnyRadioButtonSelected() {
return anyRadioButtonSelectedProperty().get(); return anyRadioButtonSelectedProperty().get();
} }
public StringProperty statusTextProperty() {
return statusText;
}
public String getStatusText() {
return statusText.get();
}
public ObjectProperty<Node> statusGraphicProperty() {
return statusGraphic;
}
public Node getStatusGraphic() {
return statusGraphic.get();
}
} }
@@ -0,0 +1,141 @@
package org.cryptomator.ui.addvaultwizard;
import org.cryptomator.common.LocationPreset;
import javax.inject.Inject;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import java.nio.file.Path;
@AddVaultWizardScoped
public class ObservedLocationPresets {
private final ReadOnlyObjectProperty<Path> iclouddriveLocation;
private final ReadOnlyObjectProperty<Path> dropboxLocation;
private final ReadOnlyObjectProperty<Path> gdriveLocation;
private final ReadOnlyObjectProperty<Path> onedriveLocation;
private final ReadOnlyObjectProperty<Path> megaLocation;
private final ReadOnlyObjectProperty<Path> pcloudLocation;
private final BooleanBinding foundIclouddrive;
private final BooleanBinding foundDropbox;
private final BooleanBinding foundGdrive;
private final BooleanBinding foundOnedrive;
private final BooleanBinding foundMega;
private final BooleanBinding foundPcloud;
@Inject
public ObservedLocationPresets() {
this.iclouddriveLocation = new SimpleObjectProperty<>(LocationPreset.ICLOUDDRIVE.existingPath());
this.dropboxLocation = new SimpleObjectProperty<>(LocationPreset.DROPBOX.existingPath());
this.gdriveLocation = new SimpleObjectProperty<>(LocationPreset.GDRIVE.existingPath());
this.onedriveLocation = new SimpleObjectProperty<>(LocationPreset.ONEDRIVE.existingPath());
this.megaLocation = new SimpleObjectProperty<>(LocationPreset.MEGA.existingPath());
this.pcloudLocation = new SimpleObjectProperty<>(LocationPreset.PCLOUD.existingPath());
this.foundIclouddrive = iclouddriveLocation.isNotNull();
this.foundDropbox = dropboxLocation.isNotNull();
this.foundGdrive = gdriveLocation.isNotNull();
this.foundOnedrive = onedriveLocation.isNotNull();
this.foundMega = megaLocation.isNotNull();
this.foundPcloud = pcloudLocation.isNotNull();
}
/* Observables */
public ReadOnlyObjectProperty<Path> iclouddriveLocationProperty() {
return iclouddriveLocation;
}
public Path getIclouddriveLocation() {
return iclouddriveLocation.get();
}
public BooleanBinding foundIclouddriveProperty() {
return foundIclouddrive;
}
public boolean isFoundIclouddrive() {
return foundIclouddrive.get();
}
public ReadOnlyObjectProperty<Path> dropboxLocationProperty() {
return dropboxLocation;
}
public Path getDropboxLocation() {
return dropboxLocation.get();
}
public BooleanBinding foundDropboxProperty() {
return foundDropbox;
}
public boolean isFoundDropbox() {
return foundDropbox.get();
}
public ReadOnlyObjectProperty<Path> gdriveLocationProperty() {
return gdriveLocation;
}
public Path getGdriveLocation() {
return gdriveLocation.get();
}
public BooleanBinding foundGdriveProperty() {
return foundGdrive;
}
public boolean isFoundGdrive() {
return foundGdrive.get();
}
public ReadOnlyObjectProperty<Path> onedriveLocationProperty() {
return onedriveLocation;
}
public Path getOnedriveLocation() {
return onedriveLocation.get();
}
public BooleanBinding foundOnedriveProperty() {
return foundOnedrive;
}
public boolean isFoundOnedrive() {
return foundOnedrive.get();
}
public ReadOnlyObjectProperty<Path> megaLocationProperty() {
return megaLocation;
}
public Path getMegaLocation() {
return megaLocation.get();
}
public BooleanBinding foundMegaProperty() {
return foundMega;
}
public boolean isFoundMega() {
return foundMega.get();
}
public ReadOnlyObjectProperty<Path> pcloudLocationProperty() {
return pcloudLocation;
}
public Path getPcloudLocation() {
return pcloudLocation.get();
}
public BooleanBinding foundPcloudProperty() {
return foundPcloud;
}
public boolean isFoundPcloud() {
return foundPcloud.get();
}
}
@@ -1,85 +1,30 @@
package org.cryptomator.ui.fxapp; package org.cryptomator.ui.fxapp;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage; import java.util.concurrent.CompletionStage;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.stream.Stream;
@FxApplicationScoped @FxApplicationScoped
public class AutoUnlocker { public class AutoUnlocker {
private static final Logger LOG = LoggerFactory.getLogger(AutoUnlocker.class);
private final ObservableList<Vault> vaults; private final ObservableList<Vault> vaults;
private final FxApplicationWindows appWindows; private final FxApplicationWindows appWindows;
private final ScheduledExecutorService scheduler;
private ScheduledFuture<?> unlockMissingFuture;
private ScheduledFuture<?> timeoutFuture;
@Inject @Inject
public AutoUnlocker(ObservableList<Vault> vaults, FxApplicationWindows appWindows, ScheduledExecutorService scheduler) { public AutoUnlocker(ObservableList<Vault> vaults, FxApplicationWindows appWindows) {
this.vaults = vaults; this.vaults = vaults;
this.appWindows = appWindows; this.appWindows = appWindows;
this.scheduler = scheduler;
} }
public void tryUnlockForTimespan(int timespan, TimeUnit timeUnit) { public void unlock() {
// Unlock all available auto unlock vaults vaults.stream().filter(Vault::isLocked) //
Predicate<Vault> shouldAutoUnlock = v -> v.getVaultSettings().unlockAfterStartup().get(); .filter(v -> v.getVaultSettings().unlockAfterStartup().get()) //
unlockSequentially(vaults.stream().filter(shouldAutoUnlock)).thenRun(() -> startUnlockMissing(timespan, timeUnit)); .<CompletionStage<Void>>reduce(CompletableFuture.completedFuture(null), //
(unlockFlow, v) -> unlockFlow.handle((voit, ex) -> appWindows.startUnlockWorkflow(v, null)).thenCompose(stage -> stage), //we don't care here about the exception, logged elsewhere
(unlockChain1, unlockChain2) -> unlockChain1.handle((voit, ex) -> unlockChain2).thenCompose(stage -> stage));
} }
private CompletionStage<Void> unlockSequentially(Stream<Vault> vaultStream) {
// this is an attempt to run all the unlock workflows sequentially, i.e. start the next workflow only after completing/failing the previous workflow.
return vaultStream.filter(Vault::isLocked).reduce(CompletableFuture.completedFuture(null),
(prevUnlock, nextVault) -> prevUnlock.thenCompose(unused -> appWindows.startUnlockWorkflow(nextVault, null)),
(prevUnlock, nextUnlock) -> nextUnlock.exceptionally(e -> null) // we don't care here about the exception, logged elsewhere
);
}
private void startUnlockMissing(int timespan, TimeUnit timeUnit) {
// Start a temporary service if there are missing auto unlock vaults
if (getMissingAutoUnlockVaults().findAny().isPresent()) {
LOG.info("Found MISSING vaults, starting periodic check");
unlockMissingFuture = scheduler.scheduleWithFixedDelay(this::unlockMissing, 0, 1, TimeUnit.SECONDS);
timeoutFuture = scheduler.schedule(this::timeout, timespan, timeUnit);
}
}
private void unlockMissing() {
List<Vault> missingAutoUnlockVaults = getMissingAutoUnlockVaults().toList();
missingAutoUnlockVaults.forEach(VaultListManager::redetermineVaultState);
unlockSequentially(missingAutoUnlockVaults.stream()).thenRun(this::stopUnlockMissing);
}
private void stopUnlockMissing() {
// Stop checking if there are no more missing vaults
if (getMissingAutoUnlockVaults().findAny().isEmpty()) {
LOG.info("No more MISSING vaults, stopping periodic check");
unlockMissingFuture.cancel(false);
timeoutFuture.cancel(false);
}
}
private void timeout() {
LOG.info("MISSING vaults periodic check timed out");
unlockMissingFuture.cancel(false);
}
private Stream<Vault> getMissingAutoUnlockVaults() {
return vaults.stream()
.filter(Vault::isMissing)
.filter(v -> v.getVaultSettings().unlockAfterStartup().get());
}
} }
@@ -9,7 +9,6 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javafx.application.Platform; import javafx.application.Platform;
import java.util.concurrent.TimeUnit;
@FxApplicationScoped @FxApplicationScoped
public class FxApplication { public class FxApplication {
@@ -69,6 +68,7 @@ public class FxApplication {
}); });
launchEventHandler.startHandlingLaunchEvents(); launchEventHandler.startHandlingLaunchEvents();
autoUnlocker.tryUnlockForTimespan(2, TimeUnit.MINUTES); autoUnlocker.unlock();
} }
} }
@@ -12,7 +12,6 @@ import org.cryptomator.ui.preferences.PreferencesComponent;
import org.cryptomator.ui.preferences.SelectedPreferencesTab; import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.quit.QuitComponent; import org.cryptomator.ui.quit.QuitComponent;
import org.cryptomator.ui.unlock.UnlockComponent; import org.cryptomator.ui.unlock.UnlockComponent;
import org.cryptomator.ui.unlock.UnlockWorkflow;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -115,7 +114,7 @@ public class FxApplicationWindows {
LOG.debug("Start unlock workflow for {}", vault.getDisplayName()); LOG.debug("Start unlock workflow for {}", vault.getDisplayName());
return unlockWorkflowFactory.create(vault, owner).unlockWorkflow(); return unlockWorkflowFactory.create(vault, owner).unlockWorkflow();
}, Platform::runLater) // }, Platform::runLater) //
.thenAcceptAsync(UnlockWorkflow::run, executor) .thenCompose(unlockWorkflow -> CompletableFuture.runAsync(unlockWorkflow, executor)) //
.exceptionally(e -> { .exceptionally(e -> {
showErrorWindow(e, owner == null ? primaryStage : owner, null); showErrorWindow(e, owner == null ? primaryStage : owner, null);
return null; return null;
@@ -102,16 +102,14 @@ public class StartController implements FxController {
} }
private void loadingKeyFailed(Throwable e) { private void loadingKeyFailed(Throwable e) {
switch (e) { if (e instanceof UnlockCancelledException) {
case UnlockCancelledException uce -> {} //ok // ok
case VaultKeyInvalidException vkie -> { } else if (e instanceof VaultKeyInvalidException) {
LOG.error("Invalid key"); //TODO: specific error screen LOG.error("Invalid key"); //TODO: specific error screen
appWindows.showErrorWindow(e, window, null); appWindows.showErrorWindow(e, window, null);
} } else {
default -> { LOG.error("Failed to load key.", e);
LOG.error("Failed to load key.", e); appWindows.showErrorWindow(e, window, null);
appWindows.showErrorWindow(e, window, null);
}
} }
} }
@@ -84,19 +84,18 @@ public class AwtTrayMenuController implements TrayMenuController {
private void addChildren(Menu menu, List<TrayMenuItem> items) { private void addChildren(Menu menu, List<TrayMenuItem> items) {
for (var item : items) { for (var item : items) {
switch (item) { // TODO: use Pattern Matching for switch, once available
case ActionItem a -> { if (item instanceof ActionItem a) {
var menuItem = new MenuItem(a.title()); var menuItem = new MenuItem(a.title());
menuItem.addActionListener(evt -> a.action().run()); menuItem.addActionListener(evt -> a.action().run());
menuItem.setEnabled(a.enabled()); menuItem.setEnabled(a.enabled());
menu.add(menuItem); menu.add(menuItem);
} } else if (item instanceof SeparatorItem) {
case SeparatorItem s -> menu.addSeparator(); // TODO: rename to _ with JEP 443 menu.addSeparator();
case SubMenuItem s -> { } else if (item instanceof SubMenuItem s) {
var submenu = new Menu(s.title()); var submenu = new Menu(s.title());
addChildren(submenu, s.items()); addChildren(submenu, s.items());
menu.add(submenu); menu.add(submenu);
}
} }
} }
} }
@@ -38,11 +38,12 @@ public class UnlockInvalidMountPointController implements FxController {
@FXML @FXML
public void initialize() { public void initialize() {
var e = unlockException.get(); var e = unlockException.get();
var translationKey = switch (e) { String translationKey = "unlock.error.customPath.description.generic";
case MountPointNotSupportedException x -> "unlock.error.customPath.description.notSupported"; if (e instanceof MountPointNotSupportedException) {
case MountPointNotExistsException x -> "unlock.error.customPath.description.notExists"; translationKey = "unlock.error.customPath.description.notSupported";
default -> "unlock.error.customPath.description.generic"; } else if (e instanceof MountPointNotExistsException) {
}; translationKey = "unlock.error.customPath.description.notExists";
}
dialogDescription.setFormat(resourceBundle.getString(translationKey)); dialogDescription.setFormat(resourceBundle.getString(translationKey));
dialogDescription.setArg1(e.getMessage()); dialogDescription.setArg1(e.getMessage());
} }
@@ -19,7 +19,7 @@
spacing="12" spacing="12"
alignment="CENTER_LEFT"> alignment="CENTER_LEFT">
<fx:define> <fx:define>
<ToggleGroup fx:id="locationPresetsToggler"/> <ToggleGroup fx:id="predefinedLocationToggler"/>
<FontAwesome5IconView fx:id="badLocation" styleClass="glyph-icon-red" glyph="TIMES" /> <FontAwesome5IconView fx:id="badLocation" styleClass="glyph-icon-red" glyph="TIMES" />
<FontAwesome5IconView fx:id="goodLocation" styleClass="glyph-icon-primary" glyph="CHECK" /> <FontAwesome5IconView fx:id="goodLocation" styleClass="glyph-icon-primary" glyph="CHECK" />
</fx:define> </fx:define>
@@ -29,11 +29,16 @@
<children> <children>
<Region VBox.vgrow="ALWAYS"/> <Region VBox.vgrow="ALWAYS"/>
<VBox fx:id="radioButtonVBox" spacing="6"> <VBox spacing="6">
<Label wrapText="true" text="%addvaultwizard.new.locationInstruction"/> <Label wrapText="true" text="%addvaultwizard.new.locationInstruction"/>
<!-- PLACEHOLDER, more radio buttons are added programmatically via controller --> <RadioButton fx:id="iclouddriveRadioButton" toggleGroup="${predefinedLocationToggler}" text="iCloud Drive" visible="${controller.observedLocationPresets.foundIclouddrive}" managed="${controller.observedLocationPresets.foundIclouddrive}"/>
<RadioButton fx:id="dropboxRadioButton" toggleGroup="${predefinedLocationToggler}" text="Dropbox" visible="${controller.observedLocationPresets.foundDropbox}" managed="${controller.observedLocationPresets.foundDropbox}"/>
<RadioButton fx:id="gdriveRadioButton" toggleGroup="${predefinedLocationToggler}" text="Google Drive" visible="${controller.observedLocationPresets.foundGdrive}" managed="${controller.observedLocationPresets.foundGdrive}"/>
<RadioButton fx:id="onedriveRadioButton" toggleGroup="${predefinedLocationToggler}" text="OneDrive" visible="${controller.observedLocationPresets.foundOnedrive}" managed="${controller.observedLocationPresets.foundOnedrive}"/>
<RadioButton fx:id="megaRadioButton" toggleGroup="${predefinedLocationToggler}" text="MEGA" visible="${controller.observedLocationPresets.foundMega}" managed="${controller.observedLocationPresets.foundMega}"/>
<RadioButton fx:id="pcloudRadioButton" toggleGroup="${predefinedLocationToggler}" text="pCloud" visible="${controller.observedLocationPresets.foundPcloud}" managed="${controller.observedLocationPresets.foundPcloud}"/>
<HBox spacing="12" alignment="CENTER_LEFT"> <HBox spacing="12" alignment="CENTER_LEFT">
<RadioButton fx:id="customRadioButton" toggleGroup="${locationPresetsToggler}" text="%addvaultwizard.new.directoryPickerLabel"/> <RadioButton fx:id="customRadioButton" toggleGroup="${predefinedLocationToggler}" text="%addvaultwizard.new.directoryPickerLabel"/>
<Button contentDisplay="LEFT" text="%addvaultwizard.new.directoryPickerButton" onAction="#chooseCustomVaultPath" disable="${controller.usePresetPath}"> <Button contentDisplay="LEFT" text="%addvaultwizard.new.directoryPickerButton" onAction="#chooseCustomVaultPath" disable="${controller.usePresetPath}">
<graphic> <graphic>
<FontAwesome5IconView glyph="FOLDER_OPEN"/> <FontAwesome5IconView glyph="FOLDER_OPEN"/>
@@ -46,8 +51,8 @@
<VBox spacing="6"> <VBox spacing="6">
<Label text="%addvaultwizard.new.locationLabel" labelFor="$locationTextField"/> <Label text="%addvaultwizard.new.locationLabel" labelFor="$locationTextField"/>
<TextField fx:id="locationTextField" promptText="%addvaultwizard.new.locationPrompt" text="${controller.vaultPath}" editable="false" disable="${!controller.anyRadioButtonSelected}" HBox.hgrow="ALWAYS"/> <TextField promptText="%addvaultwizard.new.locationPrompt" text="${controller.vaultPath}" editable="false" disable="${!controller.anyRadioButtonSelected}" HBox.hgrow="ALWAYS"/>
<Label fx:id="locationStatusLabel" alignment="CENTER_RIGHT" wrapText="true" visible="${controller.anyRadioButtonSelected}" maxWidth="Infinity" graphicTextGap="6" /> <Label fx:id="vaultPathStatus" styleClass="label-muted" alignment="CENTER_RIGHT" wrapText="true" visible="${controller.anyRadioButtonSelected}" maxWidth="Infinity" graphicTextGap="6" text="${controller.statusText}" graphic="${controller.statusGraphic}" />
</VBox> </VBox>
<Region VBox.vgrow="ALWAYS"/> <Region VBox.vgrow="ALWAYS"/>
@@ -1,233 +0,0 @@
# Locale Specific CSS files such as CJK, RTL,...
# Generics
## Button
generic.button.apply=Прилагане
generic.button.back=Назад
generic.button.cancel=Отказ
generic.button.change=Променяне
generic.button.choose=Избиране…
generic.button.close=Затваряне
generic.button.copy=Копиране
generic.button.copied=Копирано!
generic.button.done=Готово
generic.button.next=Напред
generic.button.print=Отпечатване
# Error
error.message=Възникна грешка
error.description=Това е неочаквано за Криптоматор. Можете да потърсите съществуващи ревения за тази грешка. Или ако все още не е докладвана се чувствайте свободни да го направите.
error.hyperlink.lookup=Търсене на грешката
error.hyperlink.report=Докладване на грешката
error.technicalDetails=Подробности:
# Defaults
defaults.vault.vaultName=Хранилище
# Tray Menu
traymenu.showMainWindow=Показване
traymenu.showPreferencesWindow=Настройки
traymenu.lockAllVaults=Заключване на всички
traymenu.quitApplication=Изход
traymenu.vault.unlock=Отключване
traymenu.vault.lock=Заключване
traymenu.vault.reveal=Разкриване
# Add Vault Wizard
addvaultwizard.title=Добавяне на хранилище
## Welcome
addvaultwizard.welcome.newButton=Ново хранилище
addvaultwizard.welcome.existingButton=Отваряне на хранилище
## New
### Name
addvaultwizard.new.nameInstruction=Изберете име на хранилището
addvaultwizard.new.namePrompt=Наименование
### Location
addvaultwizard.new.locationInstruction=Къде Криптоматор ще държи шифрованите файлове на хранилището?
addvaultwizard.new.locationLabel=Местоположение на хранилището
addvaultwizard.new.locationPrompt=
addvaultwizard.new.directoryPickerLabel=Потребителско местоположение
addvaultwizard.new.directoryPickerButton=Избиране…
addvaultwizard.new.directoryPickerTitle=Избор на папка
addvaultwizard.new.fileAlreadyExists=Файл или папка с името на хранилището вече съществува
addvaultwizard.new.locationDoesNotExist=Папка на посоченото място не съществува или не може да бъде достъпена
addvaultwizard.new.locationIsNotWritable=Липсват права за писане на посоченото място
addvaultwizard.new.locationIsOk=Мястото е подходящо за хранилище
addvaultwizard.new.invalidName=Неприемливо име на хранилище
addvaultwizard.new.validName=Приемливо име на хранилище
addvaultwizard.new.validCharacters.message=Името на хранилището може да съдържа следните знаци:
addvaultwizard.new.validCharacters.chars=Букви (напр. a, ж или 수)
addvaultwizard.new.validCharacters.numbers=Числа
addvaultwizard.new.validCharacters.dashes=Тире (%s) или долна черта (%s)
### Password
addvaultwizard.new.createVaultBtn=Създаване
addvaultwizard.new.generateRecoveryKeyChoice=Без парола няма да имате достъп до данните си. Желаете ли да бъде създаден ключ за възстановяване, в случай че загубите паролата си?
addvaultwizard.new.generateRecoveryKeyChoice.yes=Да, нека имам за всеки случай
addvaultwizard.new.generateRecoveryKeyChoice.no=Не, няма да загубя паролата си
### Information
addvault.new.readme.storageLocation.1=⚠️ ФАЙЛОВЕ НА ХРАНИЛИЩЕ ⚠️
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.7=1. Добавете това хранилище към Криптоматор.
addvault.new.readme.storageLocation.8=2. Отключете хранилището в Криптоматор.
addvault.new.readme.storageLocation.9=3. Отворете местоположението на съдържанието чрез бутона „Разкриване“.
addvault.new.readme.storageLocation.10=Ако имате нужда от помощ, посетете документацията: %s
addvault.new.readme.accessLocation.1=🔐️ ШИФРОВАН ДЯЛ 🔐️
addvault.new.readme.accessLocation.2=Това е местоположението на съдържанието на хранилището.
addvault.new.readme.accessLocation.3=Файловете, в този дял са шифроване от Криптоматор. Можете да работите с тях както с всеки друг диск или папка. Това е само разшифрован вариант на съдуржанието. Файловете остават шифровани на твърдия диск през цялото време.
addvault.new.readme.accessLocation.4=При желание можете да премахнете този файл.
## Existing
addvaultwizard.existing.instruction=Изберете файла „vault.cryptomator“ от съществуващото хранилище. Но ако съществува файл „masterkey.cryptomator“, изберете него.
addvaultwizard.existing.chooseBtn=Избиране…
addvaultwizard.existing.filePickerTitle=Избор на файл на хранилището
addvaultwizard.existing.filePickerMimeDesc=Хранилище на Криптоматор
## Success
addvaultwizard.success.nextStepsInstructions=Хранилището „%s“ е добавено.\nЗа да го достъпите или да добавите съдържание в него трябва да го отключите. Можете да го направите сега или по всяко друго време.
addvaultwizard.success.unlockNow=Отключване сега
# Remove Vault
removeVault.title=Премахване на „%s“
removeVault.message=Премахване на хранилище?
removeVault.description=По този начин Криптоматор ще забрави за това хранилище. Можете да го добавите отново. Шифрованите файлове няма да бъдат премахнати от твърдия диск.
removeVault.confirmBtn=Премахване
# Change Password
changepassword.title=Промяна на парола
changepassword.enterOldPassword=Въведете текущата парола за %s
changepassword.finalConfirmation=Разбирам, че ще загубя достъпа до данните си ако забравя паролата
# Forget Password
forgetPassword.title=Забравяне на парола
forgetPassword.message=Забравяне на запазена парола?
forgetPassword.description=По този начин запазената парола за хранилището ще бъде премахната от ключодържателя на системата.
forgetPassword.confirmBtn=Забравяне на парола
# Unlock
unlock.title=Отключване на „%s“
unlock.passwordPrompt=Въведете паролата за „%s“:
unlock.savePassword=Запомняне на паролата
unlock.unlockBtn=Отключване
## Select
unlock.chooseMasterkey.message=Файлът на главния ключ не е намерен
unlock.chooseMasterkey.description=Криптоматор не може да намери файлът на главния ключ на хранилището „%s“. Изберете ръчно файла.
unlock.chooseMasterkey.filePickerTitle=Изберете файл на главен ключ
unlock.chooseMasterkey.filePickerMimeDesc=Гкавен ключ на Криптоматор
## Success
unlock.success.message=Отключено е успешно
unlock.success.description=Съдържанието на хранилището „%s“ е достъпно в точката му на монтиране.
unlock.success.revealBtn=Разкриване на диска
## Failure
## Hub
hub.noKeychain.openBtn=Към настройките
### Waiting
### Receive Key
### Register Device
### Registration Success
### Registration Failed
### Unauthorized
### License Exceeded
# Lock
## Force
## Failure
# Migration
## Start
## Run
## Success
migration.success.unlockNow=Отключване сега
## Missing file system capabilities
## Impossible
# Health Check
## Start
health.title=Проверка на състоянието на „%s“
health.intro.header=Проверка на състоянието
health.intro.text=Проверката на състоянието е набор от проверки и евентуално поправки за проблеми във вътрешната структура на хранилището. Имайте предвид, че:
## Start Failure
## Check Selection
## Detail view
health.check.detail.listFilters.label=Филтър
health.check.detail.fixAllSpecificBtn=Поправка всички от вида
health.check.exportBtn=Отчет
## Result view
health.result.severityFilter.all=Сериозност - Всички
health.result.severityFilter.good=Добре
health.result.severityFilter.info=Информация
health.result.severityFilter.warn=Предупреждение
health.result.severityFilter.crit=Криточно
## Fix Application
# Preferences
preferences.title=Настройки
## General
## Interface
preferences.interface.theme=Оформление
## Volume
## Updates
## Contribution
#<-- Add entries for donations and code/translation/documentation contribution -->
## About
# Vault Statistics
## Read
## Write
## Accesses
# Main Window
main.closeBtn.tooltip=Затваряне
main.preferencesBtn.tooltip=Настройки
## Vault List
main.vaultlist.contextMenu.lock=Заключване
main.vaultlist.contextMenu.unlock=Отключване…
main.vaultlist.contextMenu.unlockNow=Отключване сега
main.vaultlist.contextMenu.vaultoptions=Настройки на хранилището
main.vaultlist.contextMenu.reveal=Разкриване на диска
main.vaultlist.addVaultBtn=Добавяне на хранилище
## Vault Detail
### Welcome
### Locked
main.vaultDetail.unlockBtn=Отключване…
main.vaultDetail.unlockNowBtn=Отключване сега
### Unlocked
main.vaultDetail.unlockedStatus=ОТКЛЮЧЕНО
main.vaultDetail.revealBtn=Разкриване на диска
main.vaultDetail.lockBtn=Заключване
### Missing
### Needs Migration
### Error
# Wrong File Alert
# Vault Options
## General
vaultOptions.general.vaultName=Наименование
vaultOptions.general.actionAfterUnlock.reveal=Разкриване на диска
vaultOptions.general.actionAfterUnlock.ask=Запитване
## Mount
vaultOptions.mount.mountPoint.directoryPickerButton=Избиране…
## Master Key
vaultOptions.masterkey.changePasswordBtn=Промяна на парола
## Hub
# Recovery Key
## Display Recovery Key
## Reset Password
### Enter Recovery Key
### Reset Password
### Recovery Key Password Reset Success
# Convert Vault
# New Password
# Quit
# Forced Quit
@@ -172,8 +172,8 @@ migration.title=Tresor upgraden
## Start ## Start
migration.start.header=Tresor upgraden migration.start.header=Tresor upgraden
migration.start.text=Um deinen Tresor "%s" in dieser neuen Version von Cryptomator zu öffnen, muss der Tresor auf ein neueres Format aktualisiert werden. Bevor du dies tust, solltest du Folgendes wissen: migration.start.text=Um deinen Tresor "%s" in dieser neuen Version von Cryptomator zu öffnen, muss der Tresor auf ein neueres Format aktualisiert werden. Bevor du dies tust, solltest du Folgendes wissen:
migration.start.remarkUndone=Diese Aktualisierung kann nicht rückgängig gemacht werden. migration.start.remarkUndone=Dieser Vorgang kann nicht rückgängig gemacht werden.
migration.start.remarkVersions=Ältere Versionen von Cryptomator werden den aktualisierten Tresor nicht öffnen können. migration.start.remarkVersions=Ältere Versionen von Cryptomator können den aktualisierten Tresor nicht öffnen.
migration.start.remarkCanRun=Du musst sicherstellen, dass jedes Gerät, von dem aus du auf den Tresor zugreifst, diese Version von Cryptomator ausführen kann. migration.start.remarkCanRun=Du musst sicherstellen, dass jedes Gerät, von dem aus du auf den Tresor zugreifst, diese Version von Cryptomator ausführen kann.
migration.start.remarkSynced=Du musst sicherstellen, dass dein Tresor auf diesem Gerät und auf deinen anderen Geräten vollständig synchronisiert ist, bevor du ihn aktualisierst. migration.start.remarkSynced=Du musst sicherstellen, dass dein Tresor auf diesem Gerät und auf deinen anderen Geräten vollständig synchronisiert ist, bevor du ihn aktualisierst.
migration.start.confirm=Ich habe die oben genannten Informationen gelesen und verstanden migration.start.confirm=Ich habe die oben genannten Informationen gelesen und verstanden
@@ -225,7 +225,7 @@ health.check.detail.checkFinishedAndFound=Die Prüfung wurde abgeschlossen. Bitt
health.check.detail.checkFailed=Die Prüfung wurde wegen eines Fehlers abgebrochen. health.check.detail.checkFailed=Die Prüfung wurde wegen eines Fehlers abgebrochen.
health.check.detail.checkCancelled=Die Prüfung wurde abgebrochen. health.check.detail.checkCancelled=Die Prüfung wurde abgebrochen.
health.check.detail.listFilters.label=Filter health.check.detail.listFilters.label=Filter
health.check.detail.fixAllSpecificBtn=Behebe alle vom Typ health.check.detail.fixAllSpecificBtn=Alle dieses Typs reparieren
health.check.exportBtn=Bericht exportieren health.check.exportBtn=Bericht exportieren
## Result view ## Result view
health.result.severityFilter.all=Schweregrad - Alle health.result.severityFilter.all=Schweregrad - Alle
@@ -464,7 +464,7 @@ convertVault.title=Tresor konvertieren
convertVault.convert.convertBtn.before=Konvertieren convertVault.convert.convertBtn.before=Konvertieren
convertVault.convert.convertBtn.processing=Konvertierung läuft… convertVault.convert.convertBtn.processing=Konvertierung läuft…
convertVault.success.message=Konvertierung erfolgreich convertVault.success.message=Konvertierung erfolgreich
convertVault.hubToPassword.success.description=Du kannst nun den Tresor mit dem gewählten Passwort entsperren, ohne Hub Zugriff zu benötigen. convertVault.hubToPassword.success.description=Du kannst nun den Tresor mit dem gewählten Passwort entsperren, ohne auf den Hub zuzugreifen.
# New Password # New Password
newPassword.promptText=Gib ein neues Passwort ein newPassword.promptText=Gib ein neues Passwort ein
@@ -1,258 +0,0 @@
# Locale Specific CSS files such as CJK, RTL,...
# Generics
## Button
generic.button.apply=Vahvista
generic.button.back=Takaisin
generic.button.cancel=Peruuta
generic.button.change=Muuta
generic.button.choose=Valitse…
generic.button.close=Sulje
generic.button.copy=Kopioi
generic.button.copied=Kopioitu!
generic.button.done=Valmis
generic.button.next=Seuraava
generic.button.print=Tulosta
# Error
error.message=Tapahtui virhe
error.description=Cryptomator ei odottanut tämän tapahtuvan. Voit etsiä olemassa olevia ratkaisuja tähän virheeseen. Tai jos sitä ei ole vielä raportoitu, voit tehdä niin.
error.hyperlink.lookup=Etsi tämä virhe
error.hyperlink.report=Ilmoita ongelmasta
error.technicalDetails=Tiedot:
# Defaults
defaults.vault.vaultName=Vault
# Tray Menu
traymenu.showMainWindow=Näytä
traymenu.showPreferencesWindow=Asetukset
traymenu.lockAllVaults=Lukitse Kaikki
traymenu.quitApplication=Sulje
traymenu.vault.unlock=Avaa
traymenu.vault.lock=Lukitse
traymenu.vault.reveal=Paljasta
# Add Vault Wizard
addvaultwizard.title=Lisää Vault
## Welcome
addvaultwizard.welcome.newButton=Luo Uusi Vault
addvaultwizard.welcome.existingButton=Avaa Olemassaoleva Vault
## New
### Name
addvaultwizard.new.nameInstruction=Anna uusi nimi Vaultille
addvaultwizard.new.namePrompt=Vault Nimi
### Location
addvaultwizard.new.locationInstruction=Missä pitäisi Cryptomator tallentaa salattuja tiedostoja Vault?
addvaultwizard.new.locationLabel=Tallennustilan sijainti
addvaultwizard.new.locationPrompt=
addvaultwizard.new.directoryPickerLabel=Oma sijainti
addvaultwizard.new.directoryPickerButton=Valitse…
addvaultwizard.new.directoryPickerTitle=Valitse Hakemisto
addvaultwizard.new.fileAlreadyExists=Varoitus: samanniminen tiedosto tai Vault on jo olemassa!
addvaultwizard.new.locationDoesNotExist=Määritetyn polun hakemistoa ei ole olemassa tai sitä ei voi käyttää
addvaultwizard.new.locationIsNotWritable=Ei kirjoitusoikeuksia määritetyllä polulla
addvaultwizard.new.locationIsOk=Sopiva sijainti Vaultille
addvaultwizard.new.invalidName=Virheellinen vault nimi
addvaultwizard.new.validName=Kelvollinen vault nimi
addvaultwizard.new.validCharacters.message=Vaultin nimi voi sisältää seuraavat merkit:
addvaultwizard.new.validCharacters.chars=Sanamerkit (e.g. a, ж or 수)
addvaultwizard.new.validCharacters.numbers=Numerot
addvaultwizard.new.validCharacters.dashes=Hyphen (%s) tai alaviiva (%s)
### Password
addvaultwizard.new.createVaultBtn=Luo Uusi Vault
addvaultwizard.new.generateRecoveryKeyChoice=Et voi käyttää tietojasi ilman salasanaasi. Haluatko palautusavaimen siltä varalta, että menetät salasanasi?
addvaultwizard.new.generateRecoveryKeyChoice.yes=Kyllä kiitos, parempi olla varma kuin katua
addvaultwizard.new.generateRecoveryKeyChoice.no=Ei kiitos, en menetä salasanani
### Information
addvault.new.readme.storageLocation.fileName=IMPORTANT.rtf
addvault.new.readme.storageLocation.1=⚠️ VAULT TIEDOSTOT⚠️
addvault.new.readme.storageLocation.2=Tämä on vaultin tallennuspaikka.
addvault.new.readme.storageLocation.3=ÄLÄ
addvault.new.readme.storageLocation.4=• muuttaa mitä tahansa tiedostoja tämän hakemiston tai
addvault.new.readme.storageLocation.5=• liitä kaikki tiedostot salaukseen tähän kansioon.
addvault.new.readme.storageLocation.6=Jos haluat salata tiedostoja ja tarkastella Vaultin sisältöä, tee seuraavat toimet:
addvault.new.readme.storageLocation.7=1. Lisää tämä Vault Cryptomatoriin.
addvault.new.readme.storageLocation.8=2. Avaa Vault Cryptomatorissa.
addvault.new.readme.storageLocation.9=3. Avaa tallennuskansiosi klikkaamalla "Näytä" -painiketta.
addvault.new.readme.storageLocation.10=Mikäli tarvitset apua, konsultoi Cryptomatorin dokumentaatiota: %s
addvault.new.readme.accessLocation.fileName=TERVETULOA.rtf
addvault.new.readme.accessLocation.1=🔐️ SALATTU VOLYYMI 🔐️
addvault.new.readme.accessLocation.2=Tämä on salatun holvisi tallennuskansio.
addvault.new.readme.accessLocation.3=Cryptomator salaa kaikki tänne siirtämäsi tiedostot. Voit työskennellä tässä kansiossa aivan samalla tavalla kuin muutenkin. Tämä on ainoastaan näkymä jossa salaus on purettu. Todellisuudessa kaikki tiedostot pysyvät salattuina aina.
addvault.new.readme.accessLocation.4=Voit halutessasi poistaa tämän tiedoston. Poistaminen ei vaikuta Cryptomatorin toimintaan.
## Existing
addvaultwizard.existing.instruction=Valitse olemassa olevan Cryptomator holvin "vault.cryptomator" -tiedosto. Mikäli "vault.cryptomator" -tiedostoa ei ole, valitse "masterkey.cryptomator" -tiedosto.
addvaultwizard.existing.chooseBtn=Valitse…
addvaultwizard.existing.filePickerTitle=Valitse Holvi -tiedosto
addvaultwizard.existing.filePickerMimeDesc=Cryptomator Holvi
## Success
addvaultwizard.success.nextStepsInstructions=Holvi "%s" on lisätty.\nSinun täytyy avata kyseinen holvi tarkastellaksesi sen sisältöä. Vaihtoehtoisesti voit avata sen myöhemmin koska tahansa.
addvaultwizard.success.unlockNow=Avaa Nyt
# Remove Vault
removeVault.title=Poista "%s"
removeVault.message=Poistetaanko holvi?
removeVault.description=Tämä toiminto ainoastaan poistaa valitun holvin Cryptomatorin näkymästä. Salattuja tiedostoja tai holvia itsessään ei poisteta.
removeVault.confirmBtn=Poista Holvi
# Change Password
changepassword.title=Vaihda salasana
changepassword.enterOldPassword=Syötä "%s": n nykyinen salasana
changepassword.finalConfirmation=Ymmärrän, että tiedostojani ei voi palauttaa, mikäli unohdan salasanani.
# Forget Password
forgetPassword.title=Unohda Salasana
forgetPassword.message=Unohda tallennetut salasanat?
forgetPassword.description=Tämä poistaa valitun holvin tallennetun salasanan järjestelmäsi avainrenkaasta.
forgetPassword.confirmBtn=Unohda Salasana
# Unlock
unlock.title=Avaa "%s"
unlock.passwordPrompt=Syötä salasana "%s":lle
unlock.savePassword=Muista salasana
unlock.unlockBtn=Avaa
## Select
unlock.chooseMasterkey.message=Masterkey -tiedostoa ei löydy
unlock.chooseMasterkey.description=Cryptomator ei paikantanut masterkey -tiedostoa holville "%s". Ole hyvä ja valitse tiedosto manuaalisesti.
unlock.chooseMasterkey.filePickerTitle=Valitse Masterkey -tiedosto
unlock.chooseMasterkey.filePickerMimeDesc=Cryptomator Masterkey
## Success
unlock.success.message=Lukituksen purku onnistui
unlock.success.description="%s" holvin sisältö on nyt saatavilla sen hallinnointikohteessa.
unlock.success.rememberChoice=Muista valintani. Älä kysy uudelleen.
unlock.success.revealBtn=Paljasta Asema
## Failure
unlock.error.customPath.message=Holvia ei pystytty yhdistämään valittuun polkuun
unlock.error.customPath.description.notSupported=Mikäli haluat jatkaa mukautetun polun käyttöä, ole hyvä ja mene Asetuksiin ja valitse volyymityyppi mikä tukee sitä. Muussa tapauksessa avaa holvin asetukset ja valitse tuettu mukautettu polku.
unlock.error.customPath.description.notExists=Valitsemaasi mukautettua polkua ei ole olemassa. Voit joko luoda uuden polun tai muuttaa polkua holvisi asetuksissa.
unlock.error.customPath.description.generic=Olet valinnut mukautetun polun holvillesi, mutta sen kanssa ilmeni ongelma: %s
## Hub
hub.noKeychain.message=Laitteen avainta ei löytynyt
hub.noKeychain.description=Hub-holvien purkamiseksi tarvitaan laiteavain joka on suojattu avainrenkaalla. Jatkaaksesi, kytke “%s” päälle ja valitse avainrengas asetuksista.
hub.noKeychain.openBtn=Avaa asetukset
### Waiting
hub.auth.message=Odotetaan todennusta…
hub.auth.description=Pitäisi ohjata sinut automaattisesti uudelleen kirjautumissivulle.
hub.auth.loginLink=Uudelleenohjaus epäonnistui? Avaa tästä manuaalisesti.
### Receive Key
hub.receive.message=Odotetaan vastausta…
hub.receive.description=Cryptomator yhdistää Hub:iin. Ole hyvä ja odota.
### Register Device
hub.register.message=Laitteen nimi vaaditaan
hub.register.description=Et ole ilmeisesti ennen yhdistänyt Hub:iin tältä laitteelta. Jotta pääsyoikeus voidaan todentaa, sinun täytyy nimetä tämä laite.
hub.register.nameLabel=Laitteen Nimi
hub.register.occupiedMsg=Tämä nimi on jo käytössä
hub.register.registerBtn=Vahvista
### Registration Success
hub.registerSuccess.message=Laite nimetty onnistuneesti
hub.registerSuccess.description=Käyttääksesi holvia, holvin omistajan on valtuutettava laitteesi.
### Registration Failed
hub.registerFailed.message=Laitteen nimeäminen epäonnistui
hub.registerFailed.description=Nimeämisprosessissa tapahtui virhe. Löydät lisää tietoja lokitiedostoista.
### Unauthorized
hub.unauthorized.message=Pääsy estetty
hub.unauthorized.description=Laitteellasi ei ole pääsyvaltuutusta tähän holviin. Pyydä holvin omistajaa lisäämän valtuutus laitteellesi.
### License Exceeded
hub.invalidLicense.message=Hub-lisenssi ei ole voimassa
hub.invalidLicense.description=Cryptomator Hub:illasi ei ole voimassa olevaa lisenssiä. Ole hyvä ja ilmoita Hubin järjestelmänvalvojalle lisenssin päivittämiseksi tai sen uusimiseksi.
# Lock
## Force
lock.forced.message=Lukitus epäonnistui
lock.forced.description="%s":n lukitus epäonnistui, koska sinulla on avoimia tiedostoja tai niihin liittyviä päivityksiä vireillä. Voit halutessasi pakottaa lukituksen, mutta tämä saattaa johtaa tallentamattomien tiedostojen katoamiseen.
lock.forced.retryBtn=Yritä uudestaan
lock.forced.forceBtn=Pakota Lukitus
## Failure
lock.fail.message=Holvin lukitseminen epäonnistui
lock.fail.description="%s" holvia ei voitu lukita. Varmista, että avoinna olevat tiedostot tallennetaan turvalliseen paikkaan ja että tiedostot eivät ole avoinna tai käytössä. Lukitaksesi holvin, pakota Cryptomator -prosessin lopetus manuaalisesti.
# Migration
migration.title=Päivitä Holvi
## Start
migration.start.header=Päivitä Holvi
migration.start.text="%s" holvi tulee päivittää uuteen versioon, jotta voit avata sen Cryptomatorissa. Ennen kuin jatkat, varmista että ymmärrät seuraavan:
migration.start.remarkUndone=Päivitystä ei voi peruuttaa.
migration.start.remarkVersions=Cryptomatorin vanhemmat versiot eivät voi avata päivitettyä holvia.
migration.start.remarkCanRun=Sinun tulee varmistaa, että kaikki laitteet joilla haluat käyttää tätä holvia on päivitetty Cryptomatorin uusimpaan versioon.
migration.start.remarkSynced=Sinun tulee varmistaa, että holvisi on täysin ajantasalla tällä laitteella ja muilla laitteillasi, ennen kuin aloitat päivitysprosessin.
migration.start.confirm=Olen lukenut ja ymmärtänyt yllä ilmoitetut ohjeet
## Run
## Success
migration.success.unlockNow=Avaa Nyt
## Missing file system capabilities
## Impossible
# Health Check
## Start
## Start Failure
## Check Selection
## Detail view
## Result view
## Fix Application
# Preferences
preferences.title=Asetukset
## General
## Interface
## Volume
## Updates
## Contribution
#<-- Add entries for donations and code/translation/documentation contribution -->
## About
# Vault Statistics
## Read
## Write
## Accesses
# Main Window
main.closeBtn.tooltip=Sulje
main.preferencesBtn.tooltip=Asetukset
## Vault List
main.vaultlist.contextMenu.lock=Lukitse
main.vaultlist.contextMenu.unlockNow=Avaa Nyt
main.vaultlist.contextMenu.reveal=Paljasta Asema
main.vaultlist.addVaultBtn=Lisää Vault
## Vault Detail
### Welcome
### Locked
main.vaultDetail.unlockNowBtn=Avaa Nyt
### Unlocked
main.vaultDetail.revealBtn=Paljasta Asema
main.vaultDetail.lockBtn=Lukitse
### Missing
### Needs Migration
main.vaultDetail.migrateButton=Päivitä Holvi
### Error
# Wrong File Alert
# Vault Options
## General
vaultOptions.general.vaultName=Vault Nimi
vaultOptions.general.actionAfterUnlock.reveal=Paljasta Asema
## Mount
vaultOptions.mount.mountPoint.directoryPickerButton=Valitse…
## Master Key
vaultOptions.masterkey.changePasswordBtn=Vaihda salasana
## Hub
# Recovery Key
## Display Recovery Key
## Reset Password
### Enter Recovery Key
### Reset Password
### Recovery Key Password Reset Success
# Convert Vault
# New Password
# Quit
# Forced Quit
@@ -50,41 +50,26 @@ addvaultwizard.new.directoryPickerLabel=Custom Location
addvaultwizard.new.directoryPickerButton=Mamili… addvaultwizard.new.directoryPickerButton=Mamili…
addvaultwizard.new.directoryPickerTitle=Pumili ng Direktoryo addvaultwizard.new.directoryPickerTitle=Pumili ng Direktoryo
### Password ### Password
addvaultwizard.new.createVaultBtn=Gumawa ng bagong Vault
### Information ### Information
addvault.new.readme.storageLocation.2=Ito ang lokasyon ng iyong vault na imbakan.
addvault.new.readme.storageLocation.3=HUWAG
addvault.new.readme.storageLocation.6=Kung gusto mo i-encrypt ang mga files at makita nang nilalaman ng vault, gawin ang nakasabi:
addvault.new.readme.storageLocation.7=1. Idagdag ang vault na ito sa Cryptomator.
addvault.new.readme.accessLocation.2=Ito ang lokasyon ng iyong vault.
## Existing ## Existing
addvaultwizard.existing.chooseBtn=Mamili… addvaultwizard.existing.chooseBtn=Mamili…
## Success ## Success
# Remove Vault # Remove Vault
removeVault.title=Tanggalin %s
removeVault.message=Itangal ang vault?
removeVault.confirmBtn=Itangal ang vault
# Change Password # Change Password
# Forget Password # Forget Password
# Unlock # Unlock
unlock.savePassword=Maalala ang password
unlock.unlockBtn=I-unlock unlock.unlockBtn=I-unlock
## Select ## Select
unlock.chooseMasterkey.filePickerTitle=Piliin ang masterkey file
unlock.chooseMasterkey.filePickerMimeDesc=Cryptomator Masterkey
## Success ## Success
unlock.success.rememberChoice=Paalala ang pinili ko, huwag mag tanong ulit
## Failure ## Failure
## Hub ## Hub
### Waiting ### Waiting
### Receive Key ### Receive Key
### Register Device ### Register Device
hub.register.occupiedMsg=Ang pangalan ay nagamit na
hub.register.registerBtn=Kumpirmahin
### Registration Success ### Registration Success
### Registration Failed ### Registration Failed
### Unauthorized ### Unauthorized
+1 -86
View File
@@ -56,9 +56,6 @@ addvaultwizard.new.locationIsOk=Megfelelő hely a trezornak
addvaultwizard.new.invalidName=Érvénytelen vault név addvaultwizard.new.invalidName=Érvénytelen vault név
addvaultwizard.new.validName=Érvényes vault név addvaultwizard.new.validName=Érvényes vault név
addvaultwizard.new.validCharacters.message=A vault neve a következő karaktereket tartalmazhatja: addvaultwizard.new.validCharacters.message=A vault neve a következő karaktereket tartalmazhatja:
addvaultwizard.new.validCharacters.chars=Szókarakterek (pl. a, ж vagy 수)
addvaultwizard.new.validCharacters.numbers=Számok
addvaultwizard.new.validCharacters.dashes=Kötőjel (%s) vagy alulvonás (%s)
### Password ### Password
addvaultwizard.new.createVaultBtn=Új széf létrehozása addvaultwizard.new.createVaultBtn=Új széf létrehozása
addvaultwizard.new.generateRecoveryKeyChoice=Nem fog tudni hozzáférni az adataihoz a jelszó nélkül. Akar egy visszaállítási kulcsot arra az esetre, ha elveszíti a jelszavát? addvaultwizard.new.generateRecoveryKeyChoice=Nem fog tudni hozzáférni az adataihoz a jelszó nélkül. Akar egy visszaállítási kulcsot arra az esetre, ha elveszíti a jelszavát?
@@ -113,49 +110,28 @@ unlock.passwordPrompt=Írja be a jelszavát a következő széfhez "%s":
unlock.savePassword=Jelszó megjegyzése unlock.savePassword=Jelszó megjegyzése
unlock.unlockBtn=Feloldás unlock.unlockBtn=Feloldás
## Select ## Select
unlock.chooseMasterkey.message=Mesterkulcs fájl nem található
unlock.chooseMasterkey.description=Nem található a tároló kulcsfájlja a várt helyen. Kérjük válassza ki a kulcsfájlt manuálisan. unlock.chooseMasterkey.description=Nem található a tároló kulcsfájlja a várt helyen. Kérjük válassza ki a kulcsfájlt manuálisan.
unlock.chooseMasterkey.filePickerTitle=Mesterkulcs fájl kiválasztása unlock.chooseMasterkey.filePickerTitle=Mesterkulcs fájl kiválasztása
unlock.chooseMasterkey.filePickerMimeDesc=Cryptomator Mesterkulcs
## Success ## Success
unlock.success.message=Sikeres feloldás unlock.success.message=Sikeres feloldás
unlock.success.description="%s" sikreresen feloldásra került! Mostmár hozzáférhet a virtuális trezorhoz. unlock.success.description="%s" sikreresen feloldásra került! Mostmár hozzáférhet a virtuális trezorhoz.
unlock.success.rememberChoice=Jegyezze meg a választást és ne mutassa többet unlock.success.rememberChoice=Jegyezze meg a választást és ne mutassa többet
unlock.success.revealBtn=Széf megjelenítése unlock.success.revealBtn=Széf megjelenítése
## Failure ## Failure
unlock.error.customPath.message=Nem lehet csatolni a széfet az egyéni útvonalhoz
unlock.error.customPath.description.notSupported=Ha szeretné továbbra is az egyéni útvonalat használni, kérem, menjen a beállításokba és válasszon egy kötet típust, amely támogatja azt. Máskülönben, menjen a széf opciókhoz és válasszon egy támogatott csatoláspontot.
unlock.error.customPath.description.notExists=Az egyéni csatolás útvonal nem létezik. Hozza létre a helyi fájlrendszerében vagy változtassa meg a széf opciókban.
unlock.error.customPath.description.generic=Egyéni csatolási útvonalat választott ehhez a széfhez, de használatakor ez a hibaüzenet érkezett: %s
## Hub ## Hub
hub.noKeychain.message=Nem lehet az eszközkulcshoz hozzáférni
hub.noKeychain.description=Hogy feloldja a Hub széfeket, egy eszközkulcs szükséges, mely egy kulcslánccal van biztosítva. A folytatáshoz engedélyezze a következőt: “%s” és válasszon egy kulcsláncot a beállításokban.
hub.noKeychain.openBtn=Beállítások megnyitása hub.noKeychain.openBtn=Beállítások megnyitása
### Waiting ### Waiting
hub.auth.message=Várakozás a hitelesítésre…
hub.auth.description=Automatikusan átirányítjuk a bejelentkezési oldalra.
hub.auth.loginLink=Nem sikerült az átirányítás? Kattintson ide a megnyitáshoz.
### Receive Key ### Receive Key
hub.receive.message=Válasz feldolgozása… hub.receive.message=Válasz feldolgozása…
hub.receive.description=Cryptomator fogadja és feldolgozza a Hub válaszát. Kérem, várjon.
### Register Device ### Register Device
hub.register.message=Eszköznév szükséges
hub.register.description=Úgy tűnik, ez az első Hub-hozzáférés erről az eszközről. A hozzáférési jogosultság azonosításához el kell neveznie ezt az eszközt.
hub.register.nameLabel=Készülék neve hub.register.nameLabel=Készülék neve
hub.register.occupiedMsg=Ez a név már használatban van hub.register.occupiedMsg=Ez a név már használatban van
hub.register.registerBtn=Megerősítés hub.register.registerBtn=Megerősítés
### Registration Success ### Registration Success
hub.registerSuccess.message=Eszköz elnevezve
hub.registerSuccess.description=A széf hozzáféréséhez a széf tulajdonosának hitelesítenie kell az eszközét.
### Registration Failed ### Registration Failed
hub.registerFailed.message=Az eszköz elnevezése sikertelen volt hub.registerFailed.message=Az eszköz elnevezése sikertelen volt
hub.registerFailed.description=Hiba állt fel az elnevezési folyamatban. További részletekért tekintse meg az alkalmazásnaplót.
### Unauthorized ### Unauthorized
hub.unauthorized.message=Hozzáférés megtagadva
hub.unauthorized.description=Eszköze még nem kapott engedélyt ehhez a széfhez. Kérje a széf tulajdonosát, hogy engedélyezze a hozzáférést.
### License Exceeded ### License Exceeded
hub.invalidLicense.message=Érvénytelen Hub licenc
hub.invalidLicense.description=Az Ön Cryptomator Hub példánya érvénytelen licenccel rendelkezik. Kérem, értesítsen egy Hub rendszergazdát hogy frissítse vagy újítsa meg a licencet.
# Lock # Lock
## Force ## Force
@@ -171,11 +147,7 @@ lock.fail.description=A "%s" tárolót nem lehetett zárolni. Győződjön meg a
migration.title=Széf frissítése migration.title=Széf frissítése
## Start ## Start
migration.start.header=Széf frissítése migration.start.header=Széf frissítése
migration.start.text=Hogy megnyissa a "%s" nevű széfét a Cryptomator ezen új verziójában, a széfet frissíteni kell egy újabb formátumra. Mielőtt ezt megteszi, az alábbiakat érdemes figyelembe vennie:
migration.start.remarkUndone=Ezt a módosítást nem lehet visszafordítani. migration.start.remarkUndone=Ezt a módosítást nem lehet visszafordítani.
migration.start.remarkVersions=A Cryptomator régebbi verziói nem fogják tudni megnyitni a frissített széfet.
migration.start.remarkCanRun=Meg kell győződnie arról, hogy minden eszköz, amelyről eléri ezt a széfet képes futtatni a Cryptomator ezen verzióját.
migration.start.remarkSynced=Meg kell győződnie arról, hogy a széfe teljesen szinkronizálva van ezen és az összes többi eszközön a frissítés előtt.
migration.start.confirm=Elolvastam és megértettem a fenti információkat migration.start.confirm=Elolvastam és megértettem a fenti információkat
## Run ## Run
migration.run.enterPassword=Írja be a jelszót a következőhöz Enter the password for "%s" migration.run.enterPassword=Írja be a jelszót a következőhöz Enter the password for "%s"
@@ -233,16 +205,10 @@ health.result.severityFilter.good=Rendben
health.result.severityFilter.info=Infó health.result.severityFilter.info=Infó
health.result.severityFilter.warn=Figyelmeztetés health.result.severityFilter.warn=Figyelmeztetés
health.result.severityFilter.crit=Kritikus health.result.severityFilter.crit=Kritikus
health.result.severityTip.good=Súlyosság: Jó\nNormális séf struktúra.
health.result.severityTip.info=Súlyosság: Info\nA széf struktúrája ép, javítás ajánlott.
health.result.severityTip.warn=Súlyosság: Figyelmeztetés\nA széf struktúrája sérült, javítás erősen ajánlott.
health.result.severityTip.crit=Súlyosság: Kritikus\nA széf struktúrája sérült, adatvesztés áll fönt.
health.result.fixStateFilter.all=Javítás státusz - Mind
health.result.fixStateFilter.fixable=Javítható health.result.fixStateFilter.fixable=Javítható
health.result.fixStateFilter.notFixable=Nem javítható health.result.fixStateFilter.notFixable=Nem javítható
health.result.fixStateFilter.fixing=Javítás alatt… health.result.fixStateFilter.fixing=Javítás alatt…
health.result.fixStateFilter.fixed=Kijavított health.result.fixStateFilter.fixed=Kijavított
health.result.fixStateFilter.fixFailed=Sikertelen javítás
## Fix Application ## Fix Application
health.fix.fixBtn=Javítás health.fix.fixBtn=Javítás
health.fix.successTip=Javítás sikeres health.fix.successTip=Javítás sikeres
@@ -253,7 +219,6 @@ preferences.title=Beállítások
## General ## General
preferences.general=Általános preferences.general=Általános
preferences.general.startHidden=Az ablak elrejtése a Cryptomator indítása után preferences.general.startHidden=Az ablak elrejtése a Cryptomator indítása után
preferences.general.autoCloseVaults=A széfek automatikus lezárása az alkalmazás bezárásakor
preferences.general.debugLogging=Hibakeresési naplózás engedélyezése preferences.general.debugLogging=Hibakeresési naplózás engedélyezése
preferences.general.debugDirectory=Naplófájlok megjelenítése preferences.general.debugDirectory=Naplófájlok megjelenítése
preferences.general.autoStart=Cryptomator indítása a rendszerrel együtt preferences.general.autoStart=Cryptomator indítása a rendszerrel együtt
@@ -274,17 +239,7 @@ preferences.interface.showMinimizeButton=Kicsinyítés ikon megjelenítése
preferences.interface.showTrayIcon=Tálca ikon megjelenítése (újraindítás szükséges) preferences.interface.showTrayIcon=Tálca ikon megjelenítése (újraindítás szükséges)
## Volume ## Volume
preferences.volume=Virtuális meghajtó preferences.volume=Virtuális meghajtó
preferences.volume.type=Kötet Típusa
preferences.volume.type.automatic=Automatikus preferences.volume.type.automatic=Automatikus
preferences.volume.docsTooltip=További információért a kötet típusokról kattintson ide, hogy megnyissa a dokumentációt.
preferences.volume.fuseRestartRequired=A változtatások alkalmazásához újra kell indítania a Cryptomatort.
preferences.volume.tcp.port=TCP Port
preferences.volume.supportedFeatures=A kiválaszott kötet típus az alábbi funkciókat támogatja:
preferences.volume.feature.mountAuto=Automatikus csatlakozási pont választás
preferences.volume.feature.mountToDir=Egyéni mappa csatlakozási pontként
preferences.volume.feature.mountToDriveLetter=Meghajtó betűjel csatolási pontként
preferences.volume.feature.mountFlags=Egyéni csatolási opciók
preferences.volume.feature.readOnly=Csak olvasható csatolás
## Updates ## Updates
preferences.updates=Frissítések preferences.updates=Frissítések
preferences.updates.currentVersion=Jelenlegi verzió: %s preferences.updates.currentVersion=Jelenlegi verzió: %s
@@ -307,34 +262,26 @@ stats.title=Statisztika ehhez %s
stats.cacheHitRate=Gyorsítótár találati arány stats.cacheHitRate=Gyorsítótár találati arány
## Read ## Read
stats.read.throughput.idle=Olvasás: tétlen stats.read.throughput.idle=Olvasás: tétlen
stats.read.throughput.kibs=Olvasás: %.2f KiB/s
stats.read.throughput.mibs=Olvasás: %.2f MiB/s stats.read.throughput.mibs=Olvasás: %.2f MiB/s
stats.read.total.data.none=Olvasott adat: - stats.read.total.data.none=Olvasott adat: -
stats.read.total.data.kib=Adat beolvasva: %.1f KiB
stats.read.total.data.mib=Olvasott adat: %.1f MiB stats.read.total.data.mib=Olvasott adat: %.1f MiB
stats.read.total.data.gib=Olvasott adat: %.1f GiB stats.read.total.data.gib=Olvasott adat: %.1f GiB
stats.decr.total.data.none=Dekódolt adat: - stats.decr.total.data.none=Dekódolt adat: -
stats.decr.total.data.kib=Adat visszafejtve: %.1f KiB
stats.decr.total.data.mib=Dekódolt adat: %.1f MiB stats.decr.total.data.mib=Dekódolt adat: %.1f MiB
stats.decr.total.data.gib=Dekódolt adat: %.1f GiB stats.decr.total.data.gib=Dekódolt adat: %.1f GiB
stats.read.accessCount=Összes olvasás: %d stats.read.accessCount=Összes olvasás: %d
## Write ## Write
stats.write.throughput.idle=Írás: tétlen stats.write.throughput.idle=Írás: tétlen
stats.write.throughput.kibs=Írás: %.2f KiB/s
stats.write.throughput.mibs=Írás: %.2f MiB/s stats.write.throughput.mibs=Írás: %.2f MiB/s
stats.write.total.data.none=Írva: - stats.write.total.data.none=Írva: -
stats.write.total.data.kib=Írt adatok: %.1f KiB
stats.write.total.data.mib=Írott adat: %.1f MiB stats.write.total.data.mib=Írott adat: %.1f MiB
stats.write.total.data.gib=Írott adat: %.1f GiB stats.write.total.data.gib=Írott adat: %.1f GiB
stats.encr.total.data.none=Titkosított adat: - stats.encr.total.data.none=Titkosított adat: -
stats.encr.total.data.kib=Titkosított adatok: %.1f KiB
stats.encr.total.data.mib=Titkosított adat: %.1f MiB stats.encr.total.data.mib=Titkosított adat: %.1f MiB
stats.encr.total.data.gib=Titkosított adat: %.1f GiB stats.encr.total.data.gib=Titkosított adat: %.1f GiB
stats.write.accessCount=Összes írás: %d stats.write.accessCount=Összes írás: %d
## Accesses ## Accesses
stats.access.current=Hozzáférések: %d
stats.access.total=Összes hozzáférés: %d
# Main Window # Main Window
@@ -365,18 +312,12 @@ main.vaultDetail.passwordSavedInKeychain=Jelszó mentve
main.vaultDetail.unlockedStatus=FELOLDVA main.vaultDetail.unlockedStatus=FELOLDVA
main.vaultDetail.accessLocation=A széf tartalma itt érhető el: main.vaultDetail.accessLocation=A széf tartalma itt érhető el:
main.vaultDetail.revealBtn=Széf megjelenítése main.vaultDetail.revealBtn=Széf megjelenítése
main.vaultDetail.copyUri=URI másolása
main.vaultDetail.lockBtn=Zárolás main.vaultDetail.lockBtn=Zárolás
main.vaultDetail.bytesPerSecondRead=Olvasás: main.vaultDetail.bytesPerSecondRead=Olvasás:
main.vaultDetail.bytesPerSecondWritten=Írás: main.vaultDetail.bytesPerSecondWritten=Írás:
main.vaultDetail.throughput.idle=tétlen main.vaultDetail.throughput.idle=tétlen
main.vaultDetail.throughput.kbps=%.1f KiB/s
main.vaultDetail.throughput.mbps=%.1f MiB/s main.vaultDetail.throughput.mbps=%.1f MiB/s
main.vaultDetail.stats=Széf statisztika main.vaultDetail.stats=Széf statisztika
main.vaultDetail.locateEncryptedFileBtn=Titkosított fájl megkeresése
main.vaultDetail.locateEncryptedFileBtn.tooltip=Válasszon a széfből egy fájlt a titkosított megfelelőjének megkereséséhez
main.vaultDetail.encryptedPathsCopied=Az útvonal a vágólapra került!
main.vaultDetail.filePickerTitle=Fájl választása a széfben
### Missing ### Missing
main.vaultDetail.missing.info=A Cryptomator nem talált széfet ezen az útvonalon. main.vaultDetail.missing.info=A Cryptomator nem talált széfet ezen az útvonalon.
main.vaultDetail.missing.recheck=Ellenőrizze újra main.vaultDetail.missing.recheck=Ellenőrizze újra
@@ -415,56 +356,36 @@ vaultOptions.general.startHealthCheckBtn=Épség-ellenőrzés indítása
## Mount ## Mount
vaultOptions.mount=Felcsatolás vaultOptions.mount=Felcsatolás
vaultOptions.mount.info=Az opciók a kiválasztott kötet típustól függőek.
vaultOptions.mount.linkToPreferences=Virtuális meghajtó opciók megnyitása
vaultOptions.mount.readonly=Csak-olvasható vaultOptions.mount.readonly=Csak-olvasható
vaultOptions.mount.customMountFlags=Egyedi csatolási paraméterek vaultOptions.mount.customMountFlags=Egyedi csatolási paraméterek
vaultOptions.mount.winDriveLetterOccupied=foglalt vaultOptions.mount.winDriveLetterOccupied=foglalt
vaultOptions.mount.mountPoint=Csatolási pont vaultOptions.mount.mountPoint=Csatolási pont
vaultOptions.mount.mountPoint.auto=Válasszon egy megfelelő helyet automatikusan vaultOptions.mount.mountPoint.auto=Válasszon egy megfelelő helyet automatikusan
vaultOptions.mount.mountPoint.driveLetter=Használja a kiválasztott meghajtó betűjelét vaultOptions.mount.mountPoint.driveLetter=Használja a kiválasztott meghajtó betűjelét
vaultOptions.mount.mountPoint.custom=Egyéni könyvtár használata
vaultOptions.mount.mountPoint.directoryPickerButton=Kiválaszt… vaultOptions.mount.mountPoint.directoryPickerButton=Kiválaszt…
vaultOptions.mount.mountPoint.directoryPickerTitle=Válasszon könyvtárat
## Master Key ## Master Key
vaultOptions.masterkey=Jelszó vaultOptions.masterkey=Jelszó
vaultOptions.masterkey.changePasswordBtn=Jelszó megváltoztatása vaultOptions.masterkey.changePasswordBtn=Jelszó megváltoztatása
vaultOptions.masterkey.forgetSavedPasswordBtn=Mentett jelszó törlése vaultOptions.masterkey.forgetSavedPasswordBtn=Mentett jelszó törlése
vaultOptions.masterkey.recoveryKeyExplanation=A helyreállítási kulcs az egyetlen módja annak, hogy visszaállítsa a széfhez való hozzáférést, ha elveíti a jelszavát. vaultOptions.masterkey.recoveryKeyExplanation=A helyreállítási kulcs az egyetlen módja annak, hogy visszaállítsa a széfhez való hozzáférést, ha elveíti a jelszavát.
vaultOptions.masterkey.showRecoveryKeyBtn=Visszaállítási kulcs megjelenítése vaultOptions.masterkey.showRecoveryKeyBtn=Visszaállítási kulcs megjelenítése
vaultOptions.masterkey.recoverPasswordBtn=Jelszó visszaállítása
## Hub ## Hub
vaultOptions.hub=Helyreállítás
vaultOptions.hub.convertInfo=Ezen visszaállítási kulcs segítségével átváltoztathatja ezt a Hub széfet egy jelszó-alapú széffé vészhelyzet esetén.
vaultOptions.hub.convertBtn=Jelszó-alapú széffé alakítás
# Recovery Key # Recovery Key
## Display Recovery Key ## Display Recovery Key
recoveryKey.display.title=Visszaállítási kulcs mutatása
recoveryKey.create.message=Jelszó szükséges
recoveryKey.create.description=Írja be a jelszavát a "%s" visszaállítási kulcsának megjelenítéséhez: recoveryKey.create.description=Írja be a jelszavát a "%s" visszaállítási kulcsának megjelenítéséhez:
recoveryKey.display.description=A következő helyreállítási kulcs használható a "%s" hozzáférésének visszaállítására: recoveryKey.display.description=A következő helyreállítási kulcs használható a "%s" hozzáférésének visszaállítására:
recoveryKey.display.StorageHints=Tartsa nagyon biztonságos helyen. pl.:\n •Tárolja egy jelszókezelővel\n •Mentse el egy USB meghajtóra\n •Nyomtassa egy papírra recoveryKey.display.StorageHints=Tartsa nagyon biztonságos helyen. pl.:\n •Tárolja egy jelszókezelővel\n •Mentse el egy USB meghajtóra\n •Nyomtassa egy papírra
## Reset Password ## Reset Password
### Enter Recovery Key ### Enter Recovery Key
recoveryKey.recover.title=Jelszó visszaállítása
recoveryKey.recover.prompt=Írja be a visszaállítási kulcsot a következőhöz "%s":
recoveryKey.recover.correctKey=Ez a visszaállítási kulcs érvényes recoveryKey.recover.correctKey=Ez a visszaállítási kulcs érvényes
recoveryKey.recover.wrongKey=Ez a visszaállítási kulcs egy másik széfhez tartozik recoveryKey.recover.wrongKey=Ez a visszaállítási kulcs egy másik széfhez tartozik
recoveryKey.recover.invalidKey=Ez a visszaállítási kulcs nem érvényes recoveryKey.recover.invalidKey=Ez a visszaállítási kulcs nem érvényes
recoveryKey.printout.heading=Cryptomator visszaállítási kulcs\n"%s"\n recoveryKey.printout.heading=Cryptomator visszaállítási kulcs\n"%s"\n
### Reset Password ### Reset Password
recoveryKey.recover.resetBtn=Visszaállítás
### Recovery Key Password Reset Success ### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=A jelszó alaphelyzetbe állítása sikeresen megtörtént
recoveryKey.recover.resetSuccess.description=Feloldhatja a széfet az új jelszóval.
# Convert Vault # Convert Vault
convertVault.title=Széf átalakítása
convertVault.convert.convertBtn.before=Átalakítás
convertVault.convert.convertBtn.processing=Átalakítás…
convertVault.success.message=Sikeres átalakítás
convertVault.hubToPassword.success.description=Feloldhatja a széfet a kiválasztott jelszóval Hub hozzáférés nélkül.
# New Password # New Password
newPassword.promptText=Adjon meg egy új jelszót newPassword.promptText=Adjon meg egy új jelszót
@@ -479,12 +400,6 @@ passwordStrength.messageLabel.3=Erős
passwordStrength.messageLabel.4=Nagyon erős passwordStrength.messageLabel.4=Nagyon erős
# Quit # Quit
quit.title=Alkalmazás bezárása
quit.message=Vannak feloldott széfek
quit.description=Kérem, erősítse meg a kilépési szándékát. A Cryptomator lezárja az összes feloldott széfet az adatvesztés elkerülése érdekében.
quit.lockAndQuitBtn=Zárolás és kilépés quit.lockAndQuitBtn=Zárolás és kilépés
# Forced Quit # Forced Quit
quit.forced.message=Egyes széfeket nem lehetett lezárni
quit.forced.description=A széfek zárolását függőbben lévő műveletek vagy megnyitott fájlok blokkolták. Kényszerítheti a maradék széfek zárolását, de az I/O megszakítása nem mentett adatok elvesztéséhez vezethet.
quit.forced.forceAndQuitBtn=Kényszerítés és Kilépés
@@ -434,7 +434,6 @@ vaultOptions.masterkey.recoveryKeyExplanation=回復キーはパスワードを
vaultOptions.masterkey.showRecoveryKeyBtn=回復キーを表示 vaultOptions.masterkey.showRecoveryKeyBtn=回復キーを表示
vaultOptions.masterkey.recoverPasswordBtn=パスワードをリセット vaultOptions.masterkey.recoverPasswordBtn=パスワードをリセット
## Hub ## Hub
vaultOptions.hub=回復
# Recovery Key # Recovery Key
## Display Recovery Key ## Display Recovery Key
@@ -457,10 +456,6 @@ recoveryKey.recover.resetSuccess.message=パスワードをリセットしまし
recoveryKey.recover.resetSuccess.description=新しいパスワードで金庫の施錠ができます。 recoveryKey.recover.resetSuccess.description=新しいパスワードで金庫の施錠ができます。
# Convert Vault # Convert Vault
convertVault.title=金庫を変換
convertVault.convert.convertBtn.before=変換
convertVault.convert.convertBtn.processing=変換中…
convertVault.success.message=変換完了
# New Password # New Password
newPassword.promptText=新しいパスワードを入力してください newPassword.promptText=新しいパスワードを入力してください
@@ -168,7 +168,6 @@ lock.fail.description=O cofre "%s" não pode ser bloqueado. Certifique-se de que
migration.title=Atualizar Cofre migration.title=Atualizar Cofre
## Start ## Start
migration.start.header=Atualizar Cofre migration.start.header=Atualizar Cofre
migration.start.remarkUndone=Esta atualização não pode ser revertida.
migration.start.confirm=Li e entendi as informações acima migration.start.confirm=Li e entendi as informações acima
## Run ## Run
migration.run.enterPassword=Introduza a palavra-passe de "%s" migration.run.enterPassword=Introduza a palavra-passe de "%s"
@@ -223,11 +222,8 @@ health.result.severityFilter.good=Ótimo
health.result.severityFilter.info=Informações health.result.severityFilter.info=Informações
health.result.severityFilter.warn=Atenção health.result.severityFilter.warn=Atenção
health.result.severityFilter.crit=Crítico health.result.severityFilter.crit=Crítico
health.result.fixStateFilter.fixed=Corrigido
health.result.fixStateFilter.fixFailed=Falha na correção
## Fix Application ## Fix Application
health.fix.fixBtn=Corrigir health.fix.fixBtn=Corrigir
health.fix.successTip=Correção bem-sucedida
# Preferences # Preferences
preferences.title=Preferências preferences.title=Preferências
@@ -235,7 +231,6 @@ preferences.title=Preferências
preferences.general=Geral preferences.general=Geral
preferences.general.startHidden=Ocultar janela ao iniciar o Cryptomator preferences.general.startHidden=Ocultar janela ao iniciar o Cryptomator
preferences.general.autoCloseVaults=Bloquear cofres abertos automaticamente ao sair da aplicação preferences.general.autoCloseVaults=Bloquear cofres abertos automaticamente ao sair da aplicação
preferences.general.keychainBackend=Guardar palavras-passe com
## Interface ## Interface
preferences.interface=Interface preferences.interface=Interface
preferences.interface.theme=Aspecto e Ambiente preferences.interface.theme=Aspecto e Ambiente
@@ -251,9 +246,7 @@ preferences.interface.interfaceOrientation.rtl=Da direita para a esquerda
preferences.interface.showMinimizeButton=Mostrar botão de minimização preferences.interface.showMinimizeButton=Mostrar botão de minimização
## Volume ## Volume
preferences.volume=Unidade Virtual preferences.volume=Unidade Virtual
preferences.volume.type=Tipo de Volume
preferences.volume.type.automatic=Automático preferences.volume.type.automatic=Automático
preferences.volume.feature.readOnly=Volume apenas-leitura
## Updates ## Updates
preferences.updates=Atualizações preferences.updates=Atualizações
preferences.updates.currentVersion=Versão atual: %s preferences.updates.currentVersion=Versão atual: %s
@@ -262,10 +255,6 @@ preferences.updates.checkNowBtn=Verificar Agora
preferences.updates.updateAvailable=Atualização para a versão %s disponível. preferences.updates.updateAvailable=Atualização para a versão %s disponível.
## Contribution ## Contribution
preferences.contribute=Apoie-nos preferences.contribute=Apoie-nos
preferences.contribute.registeredFor=Certificado de apoiador registado para %s
preferences.contribute.noCertificate=Apoie o Cryptomator e receba um certificado de apoiador. É como uma chave de licença, mas para pessoas incríveis a usar um software gratuito. ;-)
preferences.contribute.getCertificate=Não o tem? Saiba como pode obtê-lo.
preferences.contribute.promptText=Insira o código do certificado de apoiador aqui
#<-- Add entries for donations and code/translation/documentation contribution --> #<-- Add entries for donations and code/translation/documentation contribution -->
## About ## About
@@ -154,8 +154,6 @@ hub.registerFailed.description=Ocorreu um erro no processo de nomeação do disp
hub.unauthorized.message=Acesso negado hub.unauthorized.message=Acesso negado
hub.unauthorized.description=Seu dispositivo ainda não foi autorizado a acessar este cofre. Peça ao proprietário ou a um administrador deste cofre para autorizá-lo. hub.unauthorized.description=Seu dispositivo ainda não foi autorizado a acessar este cofre. Peça ao proprietário ou a um administrador deste cofre para autorizá-lo.
### License Exceeded ### License Exceeded
hub.invalidLicense.message=Licença Invalida
hub.invalidLicense.description=Sua instância do Cryptomator Hub possui uma licença inválida. Por favor, informe um administrador do Hub para atualizar ou renovar a licença.
# Lock # Lock
## Force ## Force
@@ -274,10 +272,7 @@ preferences.interface.showMinimizeButton=Mostrar botão minimizar
preferences.interface.showTrayIcon=Mostrar ícone na barra do sistema (requer reinicialização) preferences.interface.showTrayIcon=Mostrar ícone na barra do sistema (requer reinicialização)
## Volume ## Volume
preferences.volume=Volume Virtual preferences.volume=Volume Virtual
preferences.volume.type=Tipo de Volume
preferences.volume.type.automatic=Automático preferences.volume.type.automatic=Automático
preferences.volume.docsTooltip=Abra a documentação para saber mais sobre os diferentes tipos de volumes.
preferences.volume.fuseRestartRequired=Para aplicar as mudanças, o Cryptomator precisa ser reiniciado.
preferences.volume.tcp.port=Porta TCP preferences.volume.tcp.port=Porta TCP
preferences.volume.supportedFeatures=O tipo de volume escolhido suporta os seguintes recursos: preferences.volume.supportedFeatures=O tipo de volume escolhido suporta os seguintes recursos:
preferences.volume.feature.mountAuto=Seleção automática de ponto de montagem preferences.volume.feature.mountAuto=Seleção automática de ponto de montagem
@@ -307,27 +302,21 @@ stats.title=Estatísticas para %s
stats.cacheHitRate=Taxa de Utilização do Cache stats.cacheHitRate=Taxa de Utilização do Cache
## Read ## Read
stats.read.throughput.idle=Leitura: ociosa stats.read.throughput.idle=Leitura: ociosa
stats.read.throughput.kibs=Leitura: %.2f MiB/s
stats.read.throughput.mibs=Leitura: %.2f MiB/s stats.read.throughput.mibs=Leitura: %.2f MiB/s
stats.read.total.data.none=Dados lidos: - stats.read.total.data.none=Dados lidos: -
stats.read.total.data.kib=Dados lidos: %.1f GiB
stats.read.total.data.mib=Dados lidos: %.1f MiB stats.read.total.data.mib=Dados lidos: %.1f MiB
stats.read.total.data.gib=Dados lidos: %.1f GiB stats.read.total.data.gib=Dados lidos: %.1f GiB
stats.decr.total.data.none=Dados descriptografados: - stats.decr.total.data.none=Dados descriptografados: -
stats.decr.total.data.kib=Dados descriptografados: %.1f GiB
stats.decr.total.data.mib=Dados descriptografados: %.1f MiB stats.decr.total.data.mib=Dados descriptografados: %.1f MiB
stats.decr.total.data.gib=Dados descriptografados: %.1f GiB stats.decr.total.data.gib=Dados descriptografados: %.1f GiB
stats.read.accessCount=Total de leituras: %d stats.read.accessCount=Total de leituras: %d
## Write ## Write
stats.write.throughput.idle=Escrita: ociosa stats.write.throughput.idle=Escrita: ociosa
stats.write.throughput.kibs=Escrita: %.2f MiB/s
stats.write.throughput.mibs=Escrita: %.2f MiB/s stats.write.throughput.mibs=Escrita: %.2f MiB/s
stats.write.total.data.none=Dados gravados: - stats.write.total.data.none=Dados gravados: -
stats.write.total.data.kib=Dados gravados: %.1f kiB
stats.write.total.data.mib=Dados gravados: %.1f MiB stats.write.total.data.mib=Dados gravados: %.1f MiB
stats.write.total.data.gib=Dados gravados: %.1f GiB stats.write.total.data.gib=Dados gravados: %.1f GiB
stats.encr.total.data.none=Dados criptografados: - stats.encr.total.data.none=Dados criptografados: -
stats.encr.total.data.kib=Dados encriptados: %.1f KiB
stats.encr.total.data.mib=Dados criptografados: %.1f MiB stats.encr.total.data.mib=Dados criptografados: %.1f MiB
stats.encr.total.data.gib=Dados criptografados: %.1f GiB stats.encr.total.data.gib=Dados criptografados: %.1f GiB
stats.write.accessCount=Total gravado: %d stats.write.accessCount=Total gravado: %d
@@ -370,7 +359,6 @@ main.vaultDetail.lockBtn=Bloquear
main.vaultDetail.bytesPerSecondRead=Leitura: main.vaultDetail.bytesPerSecondRead=Leitura:
main.vaultDetail.bytesPerSecondWritten=Escrita: main.vaultDetail.bytesPerSecondWritten=Escrita:
main.vaultDetail.throughput.idle=ocioso main.vaultDetail.throughput.idle=ocioso
main.vaultDetail.throughput.kbps=%.1f KiB/s
main.vaultDetail.throughput.mbps=%.1f MiB/s main.vaultDetail.throughput.mbps=%.1f MiB/s
main.vaultDetail.stats=Estatísticas do Cofre main.vaultDetail.stats=Estatísticas do Cofre
main.vaultDetail.locateEncryptedFileBtn=Localizar Arquivo Criptografado main.vaultDetail.locateEncryptedFileBtn=Localizar Arquivo Criptografado
@@ -434,9 +422,6 @@ vaultOptions.masterkey.recoveryKeyExplanation=Se você perder a sua senha, a ún
vaultOptions.masterkey.showRecoveryKeyBtn=Exibir chave de recuperação vaultOptions.masterkey.showRecoveryKeyBtn=Exibir chave de recuperação
vaultOptions.masterkey.recoverPasswordBtn=Redefinir Senha vaultOptions.masterkey.recoverPasswordBtn=Redefinir Senha
## Hub ## Hub
vaultOptions.hub=Recuperação
vaultOptions.hub.convertInfo=Você pode usar a chave de recuperação para converter este cofre do Hub em um cofre protegido por senha de emergência.
vaultOptions.hub.convertBtn=Converter para Cofre protegido por senha
# Recovery Key # Recovery Key
## Display Recovery Key ## Display Recovery Key
@@ -448,10 +433,7 @@ recoveryKey.display.StorageHints=Mantenha-a em um lugar bem seguro, por exemplo:
## Reset Password ## Reset Password
### Enter Recovery Key ### Enter Recovery Key
recoveryKey.recover.title=Redefinir Senha recoveryKey.recover.title=Redefinir Senha
recoveryKey.recover.prompt=Digite a chave de recuperação para "%s":
recoveryKey.recover.correctKey=Esta é uma chave de recuperação válida recoveryKey.recover.correctKey=Esta é uma chave de recuperação válida
recoveryKey.recover.wrongKey=Esta chave de recuperação pertence a um outro cofre
recoveryKey.recover.invalidKey=Esta chave de recuperação não é válida
recoveryKey.printout.heading=Chave de Recuperação do Cryptomator\n"%s"\n recoveryKey.printout.heading=Chave de Recuperação do Cryptomator\n"%s"\n
### Reset Password ### Reset Password
recoveryKey.recover.resetBtn=Redefinir recoveryKey.recover.resetBtn=Redefinir
@@ -460,11 +442,6 @@ recoveryKey.recover.resetSuccess.message=Senha redefinida com sucesso
recoveryKey.recover.resetSuccess.description=Você pode desbloquear o seu cofre com a nova senha. recoveryKey.recover.resetSuccess.description=Você pode desbloquear o seu cofre com a nova senha.
# Convert Vault # Convert Vault
convertVault.title=Converter Cofre
convertVault.convert.convertBtn.before=Converter
convertVault.convert.convertBtn.processing=Convertendo...
convertVault.success.message=Conversão bem sucedida
convertVault.hubToPassword.success.description=Agora você pode desbloquear o cofre com a senha escolhida sem exigir acesso ao Hub.
# New Password # New Password
newPassword.promptText=Digite a nova senha newPassword.promptText=Digite a nova senha
@@ -432,7 +432,6 @@ vaultOptions.masterkey.recoveryKeyExplanation=Kľúč pre obnovu je Vašou jedin
vaultOptions.masterkey.showRecoveryKeyBtn=Ukázať klúč obnovy vaultOptions.masterkey.showRecoveryKeyBtn=Ukázať klúč obnovy
vaultOptions.masterkey.recoverPasswordBtn=Resetovanie hesla vaultOptions.masterkey.recoverPasswordBtn=Resetovanie hesla
## Hub ## Hub
vaultOptions.hub=Obnova
# Recovery Key # Recovery Key
## Display Recovery Key ## Display Recovery Key
@@ -455,10 +454,6 @@ recoveryKey.recover.resetSuccess.message=Heslo úspešne zresetované
recoveryKey.recover.resetSuccess.description=Môžte odomknúť trezor s novým heslom. recoveryKey.recover.resetSuccess.description=Môžte odomknúť trezor s novým heslom.
# Convert Vault # Convert Vault
convertVault.title=Konvertovať trezor
convertVault.convert.convertBtn.before=Konvertovať
convertVault.convert.convertBtn.processing=Konvertujem…
convertVault.success.message=Konverzia úspešná
# New Password # New Password
newPassword.promptText=Zadajte nové heslo newPassword.promptText=Zadajte nové heslo
@@ -434,9 +434,6 @@ vaultOptions.masterkey.recoveryKeyExplanation=Bir kurtarma anahtarı şifrenizi
vaultOptions.masterkey.showRecoveryKeyBtn=Kurtarma Anahtarını Göster vaultOptions.masterkey.showRecoveryKeyBtn=Kurtarma Anahtarını Göster
vaultOptions.masterkey.recoverPasswordBtn=Şifreyi Sıfırla vaultOptions.masterkey.recoverPasswordBtn=Şifreyi Sıfırla
## Hub ## Hub
vaultOptions.hub=Kurtarma
vaultOptions.hub.convertInfo=Acil bir durumda bu Hub kasasını parola tabanlı bir kasaya dönüştürmek için kurtarma anahtarını kullanabilirsiniz.
vaultOptions.hub.convertBtn=Parola Tabanlı Kasaya Dönüştür
# Recovery Key # Recovery Key
## Display Recovery Key ## Display Recovery Key
@@ -448,7 +445,6 @@ recoveryKey.display.StorageHints=Bunu çok güvenli bir yerde saklayın, örneğ
## Reset Password ## Reset Password
### Enter Recovery Key ### Enter Recovery Key
recoveryKey.recover.title=Şifreyi Sıfırla recoveryKey.recover.title=Şifreyi Sıfırla
recoveryKey.recover.prompt="%s" için kurtarma anahtarını girin:
recoveryKey.recover.correctKey=Bu geçerli bir kurtarma anahtarı recoveryKey.recover.correctKey=Bu geçerli bir kurtarma anahtarı
recoveryKey.recover.wrongKey=Bu kurtarma anahtarı farklı bir kasaya ait recoveryKey.recover.wrongKey=Bu kurtarma anahtarı farklı bir kasaya ait
recoveryKey.recover.invalidKey=Bu kurtarma anahtarı geçerli değil recoveryKey.recover.invalidKey=Bu kurtarma anahtarı geçerli değil
@@ -460,11 +456,6 @@ recoveryKey.recover.resetSuccess.message=Şifre sıfırlama başarılı
recoveryKey.recover.resetSuccess.description=Yeni şifre ile kasanızın kilidini açabilirsiniz. recoveryKey.recover.resetSuccess.description=Yeni şifre ile kasanızın kilidini açabilirsiniz.
# Convert Vault # Convert Vault
convertVault.title=Kasayı Dönüştür
convertVault.convert.convertBtn.before=Dönüştür
convertVault.convert.convertBtn.processing=Dönüştürülüyor…
convertVault.success.message=Dönüştürme başarılı
convertVault.hubToPassword.success.description=Artık Hub erişimi gerektirmeden seçilen parola ile kasanın kilidini açabilirsiniz.
# New Password # New Password
newPassword.promptText=Yeni bir şifre girin newPassword.promptText=Yeni bir şifre girin
@@ -79,21 +79,17 @@ addvault.new.readme.storageLocation.10=Якщо вам потрібна допо
addvault.new.readme.accessLocation.fileName=ПРИВІТ.rtf addvault.new.readme.accessLocation.fileName=ПРИВІТ.rtf
addvault.new.readme.accessLocation.1=🔐️ ЗАШИФРОВАНИЙ ТОМ 🔐️ addvault.new.readme.accessLocation.1=🔐️ ЗАШИФРОВАНИЙ ТОМ 🔐️
addvault.new.readme.accessLocation.2=Це місце розташування вашого vault. addvault.new.readme.accessLocation.2=Це місце розташування вашого vault.
addvault.new.readme.accessLocation.3=Будь-які файли, додані до цього тому, будуть зашифровані за допомогою Cryptomator. Ви можете працювати із ним як із будь-якою іншою директорією або накопичувачем. Це лише розшифрований вигляд його вмісту, ваші файли завжди знаходяться в зашифрованому вигляді на диску.
addvault.new.readme.accessLocation.4=Якщо хочете, то можете видалити цей файл. addvault.new.readme.accessLocation.4=Якщо хочете, то можете видалити цей файл.
## Existing ## Existing
addvaultwizard.existing.instruction=Виберіть файл "vault.cryptomator" у вашому існуючому сховищі. Якщо існує лише файл з назвою "masterkey.cryptomator", виберіть його.
addvaultwizard.existing.chooseBtn=Обрати… addvaultwizard.existing.chooseBtn=Обрати…
addvaultwizard.existing.filePickerTitle=Виберіть Vault Файл addvaultwizard.existing.filePickerTitle=Виберіть Vault Файл
addvaultwizard.existing.filePickerMimeDesc=Cryptomator Vault addvaultwizard.existing.filePickerMimeDesc=Cryptomator Vault
## Success ## Success
addvaultwizard.success.nextStepsInstructions=Додано сховище "%s".\nДля доступу або додавання вмісту це сховище необхідно розблокувати. Також його можна розблокувати й пізніше.
addvaultwizard.success.unlockNow=Розблокувати addvaultwizard.success.unlockNow=Розблокувати
# Remove Vault # Remove Vault
removeVault.title=Видалити "%s" removeVault.title=Видалити "%s"
removeVault.message=Видалити vault? removeVault.message=Видалити vault?
removeVault.description=Це лише змусить Cryptomator забути про це сховище. Його можна буде додати пізніше. Зашифровані файли на жорсткому диску не будуть видалені.
removeVault.confirmBtn=Видалити сховище removeVault.confirmBtn=Видалити сховище
# Change Password # Change Password
@@ -123,10 +119,6 @@ unlock.success.description=Вміст vault "%s" тепер доступний
unlock.success.rememberChoice=Запам'ятайте мій вибір та більше не запитуйте unlock.success.rememberChoice=Запам'ятайте мій вибір та більше не запитуйте
unlock.success.revealBtn=Розкрити Диск unlock.success.revealBtn=Розкрити Диск
## Failure ## Failure
unlock.error.customPath.message=Не вдалося змонтувати сховище за вказаним шляхом
unlock.error.customPath.description.notSupported=Якщо ви хочете надалі використовувати власний шлях, будь ласка, перейдіть до налаштувань та виберіть тип тому, що його підтримує. В іншому випадку перейдіть до параметрів сховища та оберіть точку монтування, що підтримується.
unlock.error.customPath.description.notExists=Вказаний шлях підключення не існує. Створіть його в локальній файловій системі або змініть його в параметрах сховища.
unlock.error.customPath.description.generic=Ви створили власний шлях підключення для цього сховища, але скористатися ним не вдалося. Повідомлення про помилку: %s
## Hub ## Hub
hub.noKeychain.message=Не вдалося отримати доступ до ключа пристрою hub.noKeychain.message=Не вдалося отримати доступ до ключа пристрою
hub.noKeychain.description=Щоб розблокувати Hub vaults, необхідний ключ пристрою, який захищено за допомогою ланцюга ключів. Щоб продовжити, увімкніть “%s” та виберіть ланцюг ключів у налаштуваннях. hub.noKeychain.description=Щоб розблокувати Hub vaults, необхідний ключ пристрою, який захищено за допомогою ланцюга ключів. Щоб продовжити, увімкніть “%s” та виберіть ланцюг ключів у налаштуваннях.
@@ -154,8 +146,6 @@ hub.registerFailed.description=Виникла помилка у процесі
hub.unauthorized.message=У доступі відмовлено hub.unauthorized.message=У доступі відмовлено
hub.unauthorized.description=Ваш пристрій ще не має прав доступу до цього vault. Попросіть власника vault надати їх. hub.unauthorized.description=Ваш пристрій ще не має прав доступу до цього vault. Попросіть власника vault надати їх.
### License Exceeded ### License Exceeded
hub.invalidLicense.message=Недійсна ліцензія Hub
hub.invalidLicense.description=У вашого Cryptomator Hub недійсна ліцензія. Будь ласка, повідомте адміністратору Hub, що потрібно оновити або продовжити ліцензію.
# Lock # Lock
## Force ## Force
@@ -334,25 +324,16 @@ vaultOptions.masterkey.recoveryKeyExplanation=У разі втрати паро
vaultOptions.masterkey.showRecoveryKeyBtn=Показати Ключ Відновлення vaultOptions.masterkey.showRecoveryKeyBtn=Показати Ключ Відновлення
vaultOptions.masterkey.recoverPasswordBtn=Скинути Пароль vaultOptions.masterkey.recoverPasswordBtn=Скинути Пароль
## Hub ## Hub
vaultOptions.hub=Відновлення
vaultOptions.hub.convertInfo=Ключ відновлення використовується для перетворення цього Hub-сховища на сховище з паролем у надзвичайній ситуації.
vaultOptions.hub.convertBtn=Перетворити на сховище з паролем
# Recovery Key # Recovery Key
## Display Recovery Key ## Display Recovery Key
## Reset Password ## Reset Password
### Enter Recovery Key ### Enter Recovery Key
recoveryKey.recover.title=Скинути Пароль recoveryKey.recover.title=Скинути Пароль
recoveryKey.recover.prompt=Введіть ключ відновлення для "%s:
### Reset Password ### Reset Password
### Recovery Key Password Reset Success ### Recovery Key Password Reset Success
# Convert Vault # Convert Vault
convertVault.title=Перетворити сховище
convertVault.convert.convertBtn.before=Перетворити
convertVault.convert.convertBtn.processing=Перетворення…
convertVault.success.message=Перетворення виконано успішно
convertVault.hubToPassword.success.description=Тепер ви можете розблокувати сховище за допомогою обраного пароля без необхідності доступу до Hub.
# New Password # New Password
@@ -1,31 +0,0 @@
package org.cryptomator.launcher;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.stream.Stream;
public class SupportedLanguagesTest {
@DisplayName("test if resource bundle is localized")
@ParameterizedTest(name = "{0}")
@MethodSource("languageTags")
public void testResourceBundleExists(String tag) {
var locale = Locale.forLanguageTag(tag);
Assertions.assertNotEquals("und", locale.toLanguageTag(), "Undefined language tag");
var bundle = Assertions.assertDoesNotThrow(() -> ResourceBundle.getBundle("i18n.strings", locale));
Assertions.assertEquals(locale, bundle.getLocale());
Assertions.assertFalse(bundle.keySet().isEmpty());
}
public static Stream<String> languageTags() {
return SupportedLanguages.LANGUAGE_TAGS.stream() //
.filter(tag -> !"en".equals(tag)); // english uses the default bundle
}
}
+9
View File
@@ -55,4 +55,13 @@
<cve>CVE-2022-45688</cve> <cve>CVE-2022-45688</cve>
</suppress> </suppress>
<suppress>
<notes><![CDATA[
Suppress false positive, because com.google.common.io.Files.getTempDir() is not used
]]></notes>
<packageUrl regex="true">^pkg:maven/com\.google\.guava/guava@.*$</packageUrl>
<vulnerabilityName>CVE-2020-8908</vulnerabilityName>
<cve>CVE-2020-8908</cve>
</suppress>
</suppressions> </suppressions>