diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index fbbc879b6..5821a7fde 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -10,8 +10,8 @@ on: required: false env: - JAVA_DIST: 'temurin' - JAVA_VERSION: 20 + JAVA_DIST: 'zulu' + JAVA_VERSION: '21.0.1+12' jobs: get-version: @@ -36,7 +36,7 @@ jobs: openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-aarch64_bin-jmods.zip' openjfx-sha: 'c0d80ebbe0aab404ef9ad8b46c05bf533a1e40b39b2720eebd9238d81f6326ca' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Java uses: actions/setup-java@v3 with: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 13acee970..fbb57cbbf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,8 +6,8 @@ on: types: [labeled] env: - JAVA_DIST: 'temurin' - JAVA_VERSION: 20 + JAVA_DIST: 'zulu' + JAVA_VERSION: 21 defaults: run: @@ -18,7 +18,7 @@ jobs: name: Compile and Test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-java@v3 with: distribution: ${{ env.JAVA_DIST }} diff --git a/.github/workflows/check-jdk-updates.yml b/.github/workflows/check-jdk-updates.yml new file mode 100644 index 000000000..30954b9e4 --- /dev/null +++ b/.github/workflows/check-jdk-updates.yml @@ -0,0 +1,64 @@ +name: Checks JDK version for minor updates + +on: + schedule: + - cron: '0 0 1 * *' # run once a month at the first day of month + +env: + JDK_VERSION: '21.0.1+12' + JDK_VENDOR: zulu + +jobs: + jdk-current: + name: Check out current version + runs-on: ubuntu-latest + outputs: + jdk-date: ${{ steps.get-data.outputs.jdk-date}} + steps: + - uses: actions/setup-java@v3 + with: + java-version: ${{ env.JDK_VERSION }} + distribution: ${{ env.JDK_VENDOR }} + check-latest: false + - name: Read JAVA_VERSION_DATE and store in env variable + id: get-data + run: | + date=$(cat ${JAVA_HOME}/release | grep "JAVA_VERSION_DATE=\"" | awk -F'=' '{print $2}' | tr -d '"') + echo "jdk-date=${date}" >> "$GITHUB_OUTPUT" + jdk-latest: + name: Checkout latest jdk version + runs-on: ubuntu-latest + outputs: + jdk-date: ${{ steps.get-data.outputs.jdk-date}} + jdk-version: ${{ steps.get-data.outputs.jdk-version}} + steps: + - uses: actions/setup-java@v3 + with: + java-version: 21 + distribution: ${{ env.JDK_VENDOR }} + check-latest: true + - name: Read JAVA_VERSION_DATE and store in env variable + id: get-data + run: | + date=$(cat ${JAVA_HOME}/release | grep "JAVA_VERSION_DATE=\"" | awk -F'=' '{print $2}' | tr -d '"') + echo "jdk-date=${date}" >> "$GITHUB_OUTPUT" + version=$(cat ${JAVA_HOME}/release | grep "JAVA_RUNTIME_VERSION=\"" | awk -F'=' '{print $2}' | tr -d '"') + echo "jdk-version=${version}" >> "$GITHUB_OUTPUT" + notify: + name: Notifies for jdk update + runs-on: ubuntu-latest + needs: [jdk-current, jdk-latest] + if: ${{ needs.jdk-latest.outputs.jdk-date }} > ${{ needs.jdk-current.outputs.jdk-date }} + steps: + - name: Slack Notification + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} + SLACK_USERNAME: 'Cryptobot' + SLACK_ICON: false + SLACK_ICON_EMOJI: ':bot:' + SLACK_CHANNEL: 'cryptomator-desktop' + SLACK_TITLE: "JDK update available" + SLACK_MESSAGE: "Cryptomator-CI JDK can be upgraded to ${{ needs.jdk-latest.outputs.jdk-version }}. See https://github.com/cryptomator/cryptomator/wiki/How-to-update-the-build-JDK for instructions." + SLACK_FOOTER: false + MSG_MINIMAL: true diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml index 2ae6c3262..e8fd5da22 100644 --- a/.github/workflows/debian.yml +++ b/.github/workflows/debian.yml @@ -16,8 +16,10 @@ on: type: boolean env: - JAVA_DIST: 'temurin' - JAVA_VERSION: 20 + JAVA_DIST: 'zulu' + JAVA_VERSION: '21.0.1+12' + COFFEELIBS_JDK: 21 + COFFEELIBS_JDK_VERSION: '21.0.1+12-0ppa1' OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-x64_bin-jmods.zip' OPENJFX_JMODS_AMD64_HASH: 'f522ac2ae4bdd61f0219b7b8d2058ff72a22f36a44378453bcfdcd82f8f5e08c' OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-aarch64_bin-jmods.zip' @@ -28,7 +30,7 @@ jobs: name: Build Debian Package runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - id: versions name: Get version information run: | @@ -42,7 +44,7 @@ jobs: run: | sudo add-apt-repository ppa:coffeelibs/openjdk 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-${{ env.COFFEELIBS_JDK }}=${{ env.COFFEELIBS_JDK_VERSION }} libgtk2.0-0 - name: Setup Java uses: actions/setup-java@v3 with: @@ -148,4 +150,4 @@ jobs: GITHUB_TOKEN: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} run: | artifacts=$(ls | grep cryptomator*.deb) - gh release upload ${{ github.ref_name }} $artifacts \ No newline at end of file + gh release upload ${{ github.ref_name }} $artifacts diff --git a/.github/workflows/error-db.yml b/.github/workflows/error-db.yml index 09a15fe1f..e885af4a2 100644 --- a/.github/workflows/error-db.yml +++ b/.github/workflows/error-db.yml @@ -2,7 +2,7 @@ name: Update Error Database on: discussion: - types: [created, edited, category_changed, answered, unanswered] + types: [created, edited, deleted, category_changed, answered, unanswered] discussion_comment: types: [created, edited, deleted] @@ -12,6 +12,7 @@ jobs: if: github.event.discussion.category.name == 'Errors' steps: - name: Query Discussion Data + if: github.event_name == 'discussion_comment' || github.event_name == 'discussion' && github.event.action != 'deleted' id: query-data uses: actions/github-script@v6 with: @@ -47,8 +48,13 @@ jobs: - name: Merge Error Code Data run: | jq -c '.' ${{ steps.get-gist.outputs.file }} > original.json - echo $DISCUSSION | jq -c '.repository.discussion | .comments = .comments.totalCount | {(.id|tostring) : .}' > new.json - jq -s '.[0] * .[1]' original.json new.json > merged.json + if [ ! -z "$DISCUSSION" ] + then + echo $DISCUSSION | jq -c '.repository.discussion | .comments = .comments.totalCount | {(.id|tostring) : .}' > new.json + jq -s '.[0] * .[1]' original.json new.json > merged.json + else + cat original.json | jq 'del(.[] | select(.url=="https://github.com/cryptomator/cryptomator/discussions/${{ github.event.discussion.number }}"))' > merged.json + fi env: DISCUSSION: ${{ steps.query-data.outputs.result }} - name: Patch Gist diff --git a/.github/workflows/get-version.yml b/.github/workflows/get-version.yml index 44f5ccd85..1bed1cff8 100644 --- a/.github/workflows/get-version.yml +++ b/.github/workflows/get-version.yml @@ -22,8 +22,8 @@ on: value: ${{ jobs.determine-version.outputs.type }} env: - JAVA_DIST: 'temurin' - JAVA_VERSION: 20 + JAVA_DIST: 'zulu' + JAVA_VERSION: 21 jobs: determine-version: @@ -35,7 +35,7 @@ jobs: revNum: ${{ steps.versions.outputs.revNum }} type: ${{ steps.versions.outputs.type}} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Java diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml index a394101ff..1372fe6ce 100644 --- a/.github/workflows/mac-dmg.yml +++ b/.github/workflows/mac-dmg.yml @@ -15,8 +15,8 @@ on: type: boolean env: - JAVA_DIST: 'temurin' - JAVA_VERSION: 20 + JAVA_DIST: 'zulu' + JAVA_VERSION: '21.0.1+12' jobs: get-version: @@ -47,7 +47,7 @@ jobs: openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_osx-aarch64_bin-jmods.zip' openjfx-sha: 'c60f5f19aa847e0e620e0b011e5de68f2c6755641c2141cec27a0b89f612beaf' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Java uses: actions/setup-java@v3 with: @@ -62,7 +62,7 @@ jobs: curl -L ${{ matrix.openjfx-url }} -o openjfx-jmods.zip echo "${{ matrix.openjfx-sha }} *openjfx-jmods.zip" | shasum -a256 --check mkdir -p openjfx-jmods/ - unzip -j openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods + unzip -jo openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods - name: Ensure major jfx version in pom and in jmods is the same run: | JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1) @@ -72,7 +72,7 @@ jobs: POM_JFX_VERSION=${POM_JFX_VERSION#*@} POM_JFX_VERSION=${POM_JFX_VERSION%%.*} - if [ $POM_JFX_VERSION -ne $JMOD_VERSION ]; then + if [ "${POM_JFX_VERSION}" -ne "${JMOD_VERSION}" ]; then >&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != jmod version (${JMOD_VERSION})" exit 1 fi @@ -222,7 +222,6 @@ jobs: --app-drop-link 512 245 --eula "dist/mac/dmg/resources/license.rtf" --icon ".background" 128 758 - --icon ".fseventsd" 320 758 --icon ".VolumeIcon.icns" 512 758 Cryptomator-${VERSION_NO}-${{ matrix.output-suffix }}.dmg dmg env: diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 14146d0cb..931817418 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -4,8 +4,8 @@ on: pull_request: env: - JAVA_DIST: 'temurin' - JAVA_VERSION: 20 + JAVA_DIST: 'zulu' + JAVA_VERSION: 21 defaults: run: @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-java@v3 with: distribution: ${{ env.JAVA_DIST }} diff --git a/.github/workflows/release-check.yml b/.github/workflows/release-check.yml index ec532081b..1bbfb5d1a 100644 --- a/.github/workflows/release-check.yml +++ b/.github/workflows/release-check.yml @@ -15,7 +15,7 @@ jobs: name: Validate commits pushed to release/hotfix branch to fulfill release requirements runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - id: validate-pom-version name: Validate POM version run: | diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml index 066b7d49e..dece30a8b 100644 --- a/.github/workflows/win-exe.yml +++ b/.github/workflows/win-exe.yml @@ -14,10 +14,12 @@ on: env: - JAVA_DIST: 'temurin' - JAVA_VERSION: 20 + JAVA_DIST: 'zulu' + JAVA_VERSION: '21.0.1+12' OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_windows-x64_bin-jmods.zip' OPENJFX_JMODS_AMD64_HASH: '18625bbc13c57dbf802486564247a8d8cab72ec558c240a401bf6440384ebd77' + WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.0/winfsp-2.0.23075.msi' + WINFSP_UNINSTALLER: 'https://github.com/cryptomator/winfsp-uninstaller/releases/download/1.0.0-beta9/winfsp-uninstaller.exe' defaults: run: @@ -37,7 +39,7 @@ jobs: LOOPBACK_ALIAS: 'cryptomator-vault' WIN_CONSOLE_FLAG: '' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Java uses: actions/setup-java@v3 with: @@ -143,9 +145,29 @@ jobs: - name: Fix permissions run: attrib -r appdir/Cryptomator/Cryptomator.exe shell: pwsh - - name: Extract integrations DLL for code signing + - name: Extract jars with DLLs for Codesigning shell: pwsh - run: gci ./appdir/Cryptomator/app/mods/ -File integrations-win-*.jar | ForEach-Object {Set-Location -Path $_.Directory; jar --file=$($_.FullName) --extract integrations.dll } + run: | + Add-Type -AssemblyName "System.io.compression.filesystem" + $jarFolder = Resolve-Path ".\appdir\Cryptomator\app\mods" + $jarExtractDir = New-Item -Path ".\appdir\jar-extract" -ItemType Directory + + #for all jars inspect + Get-ChildItem -Path $jarFolder -Filter "*.jar" | ForEach-Object { + $jar = [Io.compression.zipfile]::OpenRead($_.FullName) + if (@($jar.Entries | Where-Object {$_.Name.ToString().EndsWith(".dll")} | Select-Object -First 1).Count -gt 0) { + #jars containing dlls extract + Set-Location $jarExtractDir + Expand-Archive -Path $_.FullName + } + $jar.Dispose() + } + - name: Extract wixhelper.dll for Codesigning #see https://github.com/cryptomator/cryptomator/issues/3130 + shell: pwsh + run: | + New-Item -Path appdir/jpackage-jmod -ItemType Directory + & $env:JAVA_HOME\bin\jmod.exe extract --dir jpackage-jmod "${env:JAVA_HOME}\jmods\jdk.jpackage.jmod" + Get-ChildItem -Recurse -Path "jpackage-jmod" -File wixhelper.dll | Select-Object -Last 1 | Copy-Item -Destination "appdir" - name: Codesign uses: skymatic/code-sign-action@v2 with: @@ -154,12 +176,22 @@ jobs: certificatesha1: 5FC94CE149E5B511E621F53A060AC67CBD446B3A description: Cryptomator timestampUrl: 'http://timestamp.digicert.com' - folder: appdir/Cryptomator + folder: appdir recursive: true - - name: Repack signed DLL into jar + - name: Replace DLLs inside jars with signed ones shell: pwsh run: | - gci ./appdir/Cryptomator/app/mods/ -File integrations-win-*.jar | ForEach-Object {Set-Location -Path $_.Directory; jar --file=$($_.FullName) --update integrations.dll; Remove-Item integrations.dll} + $jarExtractDir = Resolve-Path ".\appdir\jar-extract" + $jarFolder = Resolve-Path ".\appdir\Cryptomator\app\mods" + Get-ChildItem -Path $jarExtractDir | ForEach-Object { + $jarName = $_.Name + $jarFile = "${jarFolder}\${jarName}.jar" + Set-Location $_ + Get-ChildItem -Path $_ -Recurse -File "*.dll" | ForEach-Object { + # update jar with signed dll + jar --file="$jarFile" --update $(Resolve-Path -Relative -Path $_) + } + } - name: Generate license for MSI run: > mvn -B license:add-third-party @@ -193,6 +225,7 @@ jobs: --file-associations dist/win/resources/FAvaultFile.properties env: JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs + JP_WIXHELPER_DIR: ${{ github.workspace }}\appdir - name: Codesign MSI uses: skymatic/code-sign-action@v2 with: @@ -234,7 +267,7 @@ jobs: runs-on: windows-latest needs: [get-version, build-msi] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Download .msi uses: actions/download-artifact@v3 with: @@ -261,8 +294,11 @@ jobs: shell: pwsh - name: Download WinFsp run: | - $winfspUrl = (Select-String -Path ".\dist\win\bundle\resources\winFspMetaData.wxi" -Pattern '<\?define BundledWinFspDownloadLink="(.+)".*?>').Matches.Groups[1].Value - curl --output dist/win/bundle/resources/winfsp.msi -L $winfspUrl + curl --output dist/win/bundle/resources/winfsp.msi -L ${{ env.WINFSP_MSI }} + shell: pwsh + - name: Download Legacy-WinFsp uninstaller + run: | + curl --output dist/win/bundle/resources/winfsp-uninstaller.exe -L ${{ env.WINFSP_UNINSTALLER }} shell: pwsh - name: Compile to wixObj file run: > diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 1d25cbef3..a3e38d3aa 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -14,10 +14,10 @@ - + \ No newline at end of file diff --git a/README.md b/README.md index b78fb7d88..ec021ab5d 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,10 @@ Cryptomator is provided free of charge as an open-source project despite the hig - + +
Mow CapitalMow Capital EaseUS Hassmann IT-ForensikEnte
diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh index d3390c717..0a4b7f65d 100755 --- a/dist/linux/appimage/build.sh +++ b/dist/linux/appimage/build.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -e cd $(dirname $0) REVISION_NO=`git rev-list --count HEAD` @@ -10,6 +11,7 @@ command -v curl >/dev/null 2>&1 || { echo >&2 "curl not found."; exit 1; } VERSION=$(mvn -f ../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout) SEMVER_STR=${VERSION} +MACHINE_TYPE=$(uname -m) mvn -f ../../../pom.xml versions:set -DnewVersion=${SEMVER_STR} @@ -83,17 +85,17 @@ ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.App ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun # load AppImageTool -curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage -o /tmp/appimagetool.AppImage +curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-${MACHINE_TYPE}.AppImage -o /tmp/appimagetool.AppImage chmod +x /tmp/appimagetool.AppImage # create AppImage /tmp/appimagetool.AppImage \ Cryptomator.AppDir \ - cryptomator-${SEMVER_STR}-x86_64.AppImage \ - -u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync' + cryptomator-${SEMVER_STR}-${MACHINE_TYPE}.AppImage \ + -u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-${MACHINE_TYPE}.AppImage.zsync' echo "" -echo "Done. AppImage successfully created: cryptomator-${SEMVER_STR}-x86_64.AppImage" +echo "Done. AppImage successfully created: cryptomator-${SEMVER_STR}-${MACHINE_TYPE}.AppImage" echo "" echo >&2 "To clean up, run: rm -rf Cryptomator.AppDir appdir jni runtime squashfs-root; rm launcher-gtk2.properties /tmp/appimagetool.AppImage" echo "" \ No newline at end of file diff --git a/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml b/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml index 6e4873712..e28172efe 100644 --- a/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml +++ b/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml @@ -66,6 +66,7 @@ + diff --git a/dist/linux/debian/control b/dist/linux/debian/control index b04812fbb..0e7266c75 100644 --- a/dist/linux/debian/control +++ b/dist/linux/debian/control @@ -2,7 +2,7 @@ Source: cryptomator Maintainer: Cryptobot Section: utils Priority: optional -Build-Depends: debhelper (>=10), coffeelibs-jdk-20, libgtk2.0-0, libgtk-3-0, libxxf86vm1, libgl1 +Build-Depends: debhelper (>=10), coffeelibs-jdk-21 (= 21.0.1+12-0ppa1), libgtk2.0-0, libgtk-3-0, libxxf86vm1, libgl1 Standards-Version: 4.5.0 Homepage: https://cryptomator.org Vcs-Git: https://github.com/cryptomator/cryptomator.git diff --git a/dist/linux/debian/rules b/dist/linux/debian/rules index c12879025..d0a12e380 100755 --- a/dist/linux/debian/rules +++ b/dist/linux/debian/rules @@ -4,7 +4,7 @@ # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 -JAVA_HOME = /usr/lib/jvm/java-20-coffeelibs +JAVA_HOME = /usr/lib/jvm/java-21-coffeelibs DEB_BUILD_ARCH ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH) ifeq ($(DEB_BUILD_ARCH),amd64) JMODS_PATH = jmods/amd64:${JAVA_HOME}/jmods diff --git a/dist/mac/dmg/build.sh b/dist/mac/dmg/build.sh index 6d586f02c..b2c8d55e3 100755 --- a/dist/mac/dmg/build.sh +++ b/dist/mac/dmg/build.sh @@ -49,21 +49,22 @@ fi # download and check jmods curl -L ${OPENJFX_JMODS} -o openjfx-jmods.zip mkdir -p openjfx-jmods/ -unzip -j openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods/ +unzip -jo openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1) JMOD_VERSION=${JMOD_VERSION#*@} JMOD_VERSION=${JMOD_VERSION%%.*} -POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) +POM_JFX_VERSION=$(mvn -f../../../pom.xml help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) POM_JFX_VERSION=${POM_JFX_VERSION#*@} POM_JFX_VERSION=${POM_JFX_VERSION%%.*} -if [ $POM_JFX_VERSION -ne $JMOD_VERSION ]; then ->&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != jmod version (${JMOD_VERSION})" -exit 1 +if [ "${POM_JFX_VERSION}" -ne "${JMOD_VERSION}" ]; then + >&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != jmod version (${JMOD_VERSION})" + exit 1 fi # compile mvn -B -f../../../pom.xml clean package -DskipTests -Pmac +cp ../../../LICENSE.txt ../../../target cp ../../../target/${MAIN_JAR_GLOB} ../../../target/mods # add runtime @@ -168,6 +169,5 @@ create-dmg \ --app-drop-link 512 245 \ --eula "resources/license.rtf" \ --icon ".background" 128 758 \ - --icon ".fseventsd" 320 758 \ --icon ".VolumeIcon.icns" 512 758 \ ${APP_NAME}-${VERSION_NO}.dmg dmg diff --git a/dist/win/.gitignore b/dist/win/.gitignore index 9cce929df..32316fd59 100644 --- a/dist/win/.gitignore +++ b/dist/win/.gitignore @@ -4,4 +4,6 @@ installer *.wixobj *.pdb *.msi +*.exe +*.jmod license.rtf \ No newline at end of file diff --git a/dist/win/build.ps1 b/dist/win/build.ps1 index bef7a9acb..2606a3778 100644 --- a/dist/win/build.ps1 +++ b/dist/win/build.ps1 @@ -63,9 +63,10 @@ if( !(Test-Path -Path $jfxJmodsZip) ) { $jmodsChecksumActual = $(Get-FileHash -Path $jfxJmodsZip -Algorithm SHA256).Hash if( $jmodsChecksumActual -ne $jfxJmodsChecksum ) { Write-Error "Checksum mismatch for jfxJmods.zip. Expected: $jfxJmodsChecksum, actual: $jmodsChecksumActual" - exit 1; + exit 1; } -Expand-Archive -Path $jfxJmodsZip -DestinationPath ".\resources\" +Expand-Archive -Path $jfxJmodsZip -Force -DestinationPath ".\resources\" +Remove-Item -Recurse -Force -Path ".\resources\javafx-jmods" Move-Item -Force -Path ".\resources\javafx-jmods-*" -Destination ".\resources\javafx-jmods" -ErrorAction Stop @@ -143,6 +144,7 @@ try { # create .msi $Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources" +$Env:JP_WIXHELPER_DIR = "." & "$Env:JAVA_HOME\bin\jpackage" ` --verbose ` --type msi ` @@ -174,10 +176,15 @@ $Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources" "-Dlicense.licenseMergesUrl=file:///$buildDir/../../license/merges" # download Winfsp -$winfspMsiUrl= (Select-String -Path ".\bundle\resources\winFspMetaData.wxi" -Pattern '<\?define BundledWinFspDownloadLink="(.+)".*?>').Matches.Groups[1].Value +$winfspMsiUrl= 'https://github.com/winfsp/winfsp/releases/download/v2.0/winfsp-2.0.23075.msi' Write-Output "Downloading ${winfspMsiUrl}..." Invoke-WebRequest $winfspMsiUrl -OutFile ".\bundle\resources\winfsp.msi" # redirects are followed by default +# download legacy-winfsp uninstaller +$winfspUninstaller= 'https://github.com/cryptomator/winfsp-uninstaller/releases/download/1.0.0-beta9/winfsp-uninstaller.exe' +Write-Output "Downloading ${winfspUninstaller}..." +Invoke-WebRequest $winfspUninstaller -OutFile ".\bundle\resources\winfsp-uninstaller.exe" # redirects are followed by default + # copy MSI to bundle resources Copy-Item ".\installer\$AppName-*.msi" -Destination ".\bundle\resources\$AppName.msi" diff --git a/dist/win/bundle/bundleWithWinfsp.wxs b/dist/win/bundle/bundleWithWinfsp.wxs index 90ac93802..8e009526e 100644 --- a/dist/win/bundle/bundleWithWinfsp.wxs +++ b/dist/win/bundle/bundleWithWinfsp.wxs @@ -1,5 +1,6 @@ + - - - (InstalledWinFspVersion = v0.0.0.0) OR ($(var.BundledWinFspVersion) <= InstalledWinFspVersion) + UpgradeCode="82F812D9-4083-4EF1-8BC8-0F1EDA05B46B"/> @@ -36,26 +26,41 @@ SuppressOptionsUI="yes" ThemeFile="bundle\customBootstrapperTheme.xml" LocalizationFile="bundle\customBootstrapperTheme.wxl" - LogoFile="bundle\resources\logo.png" - /> + LogoFile="bundle\resources\logo.png"/> + + + + + + + + + + + + + Visible="no"/> + Permanent="yes"/> diff --git a/dist/win/bundle/resources/winFspMetaData.wxi b/dist/win/bundle/resources/winFspMetaData.wxi deleted file mode 100644 index 53e0400e5..000000000 --- a/dist/win/bundle/resources/winFspMetaData.wxi +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/dist/win/resources/main.wxs b/dist/win/resources/main.wxs index c940b9f9a..2fe2eb348 100644 --- a/dist/win/resources/main.wxs +++ b/dist/win/resources/main.wxs @@ -70,7 +70,7 @@ - + diff --git a/pom.xml b/pom.xml index 08cb4bb73..1ae5759a5 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.cryptomator cryptomator - 1.10.1 + 1.11.0 Cryptomator Desktop App @@ -26,7 +26,7 @@ UTF-8 - 20 + 21 @@ -35,36 +35,36 @@ 2.6.7 1.3.0 - 1.2.3 + 1.2.4 1.2.2 - 1.3.0 - 3.0.0 + 1.4.0-beta2 + 4.0.0-beta4 2.0.0 - 2.0.4 + 2.0.5 3.13.0 - 2.48 + 2.48.1 2.2 - 32.1.2-jre - 2.15.2 + 32.1.3-jre + 2.15.3 20.0.2 4.4.0 - 9.31 + 9.37 1.4.11 2.0.9 - 0.6.0 + 0.8.0 1.8.2 5.10.0 - 5.5.0 + 5.6.0 2.2 24.0.1 8.4.0 - 0.8.10 + 0.8.11 2.2.0 1.2.1 3.11.0 diff --git a/src/main/java/org/cryptomator/common/locationpresets/OneDriveWindowsLocationPresetsProvider.java b/src/main/java/org/cryptomator/common/locationpresets/OneDriveWindowsLocationPresetsProvider.java index 1d5bffd70..467d7785b 100644 --- a/src/main/java/org/cryptomator/common/locationpresets/OneDriveWindowsLocationPresetsProvider.java +++ b/src/main/java/org/cryptomator/common/locationpresets/OneDriveWindowsLocationPresetsProvider.java @@ -62,7 +62,7 @@ public final class OneDriveWindowsLocationPresetsProvider implements LocationPre ProcessBuilder command = new ProcessBuilder(args); Process p = command.start(); waitForSuccess(p, 3, "`reg query`"); - return p.inputReader(StandardCharsets.UTF_8).lines().filter(outputFilter); + return p.inputReader(StandardCharsets.ISO_8859_1).lines().filter(outputFilter); } diff --git a/src/main/java/org/cryptomator/logging/LogbackConfigurator.java b/src/main/java/org/cryptomator/logging/LogbackConfigurator.java index 511599132..3b77993cc 100644 --- a/src/main/java/org/cryptomator/logging/LogbackConfigurator.java +++ b/src/main/java/org/cryptomator/logging/LogbackConfigurator.java @@ -5,6 +5,7 @@ import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.encoder.PatternLayoutEncoder; import ch.qos.logback.classic.spi.Configurator; +import ch.qos.logback.classic.spi.ConfiguratorRank; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; import ch.qos.logback.core.ConsoleAppender; @@ -19,6 +20,7 @@ import org.cryptomator.common.Environment; import java.nio.file.Path; import java.util.Map; +@ConfiguratorRank(ConfiguratorRank.CUSTOM_NORMAL_PRIORITY) public class LogbackConfigurator extends ContextAwareBase implements Configurator { private static final String LOG_PATTERN = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"; diff --git a/src/main/java/org/cryptomator/ui/common/FxmlFile.java b/src/main/java/org/cryptomator/ui/common/FxmlFile.java index 678795662..46542ccb9 100644 --- a/src/main/java/org/cryptomator/ui/common/FxmlFile.java +++ b/src/main/java/org/cryptomator/ui/common/FxmlFile.java @@ -20,10 +20,12 @@ public enum FxmlFile { HUB_AUTH_FLOW("/fxml/hub_auth_flow.fxml"), // HUB_INVALID_LICENSE("/fxml/hub_invalid_license.fxml"), // HUB_RECEIVE_KEY("/fxml/hub_receive_key.fxml"), // - HUB_REGISTER_DEVICE("/fxml/hub_register_device.fxml"), // + HUB_LEGACY_REGISTER_DEVICE("/fxml/hub_legacy_register_device.fxml"), // HUB_REGISTER_SUCCESS("/fxml/hub_register_success.fxml"), // - HUB_REGISTER_FAILED("/fxml/hub_register_failed.fxml"), + HUB_REGISTER_FAILED("/fxml/hub_register_failed.fxml"), // + HUB_SETUP_DEVICE("/fxml/hub_setup_device.fxml"), // HUB_UNAUTHORIZED_DEVICE("/fxml/hub_unauthorized_device.fxml"), // + HUB_REQUIRE_ACCOUNT_INIT("/fxml/hub_require_account_init.fxml"), // LOCK_FORCED("/fxml/lock_forced.fxml"), // LOCK_FAILED("/fxml/lock_failed.fxml"), // MAIN_WINDOW("/fxml/main_window.fxml"), // diff --git a/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java b/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java index 33a674b11..c5ec19929 100644 --- a/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java +++ b/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java @@ -53,6 +53,7 @@ public enum FontAwesome5Icon { TIMES("\uF00D"), // TRASH("\uF1F8"), // UNLINK("\uf127"), // + USER_COG("\uf4fe"), // WRENCH("\uF0AD"), // WINDOW_MINIMIZE("\uF2D1"), // ; diff --git a/src/main/java/org/cryptomator/ui/error/ErrorController.java b/src/main/java/org/cryptomator/ui/error/ErrorController.java index 61f2b0e10..45c940bcb 100644 --- a/src/main/java/org/cryptomator/ui/error/ErrorController.java +++ b/src/main/java/org/cryptomator/ui/error/ErrorController.java @@ -31,6 +31,7 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.util.Comparator; import java.util.Map; import java.util.Optional; @@ -154,6 +155,7 @@ public class ErrorController implements FxController { HttpClient httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build(); HttpRequest httpRequest = HttpRequest.newBuilder()// .header("User-Agent", userAgent) + .timeout(Duration.ofSeconds(10)) .uri(URI.create(ERROR_CODES_URL_FORMAT.formatted(URLEncoder.encode(errorCode.toString(),StandardCharsets.UTF_8))))// .build(); httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofInputStream())// diff --git a/src/main/java/org/cryptomator/ui/fxapp/UpdateCheckerModule.java b/src/main/java/org/cryptomator/ui/fxapp/UpdateCheckerModule.java index d70301e78..b5f06d7e5 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/UpdateCheckerModule.java +++ b/src/main/java/org/cryptomator/ui/fxapp/UpdateCheckerModule.java @@ -63,6 +63,7 @@ public abstract class UpdateCheckerModule { return HttpRequest.newBuilder() // .uri(LATEST_VERSION_URI) // .header("User-Agent", userAgent) // + .timeout(java.time.Duration.ofSeconds(10)) .build(); } diff --git a/src/main/java/org/cryptomator/ui/health/StartController.java b/src/main/java/org/cryptomator/ui/health/StartController.java index 4e95b6b0f..9ff2502da 100644 --- a/src/main/java/org/cryptomator/ui/health/StartController.java +++ b/src/main/java/org/cryptomator/ui/health/StartController.java @@ -101,16 +101,16 @@ public class StartController implements FxController { } } - private void loadingKeyFailed(Throwable e) { - switch (e) { - case UnlockCancelledException uce -> {} //ok - case VaultKeyInvalidException vkie -> { - LOG.error("Invalid key"); //TODO: specific error screen + private void loadingKeyFailed(Throwable t) { + switch (t) { + case UnlockCancelledException e -> {} // ok // TODO: rename to _ with JEP 443 + case VaultKeyInvalidException e -> { // TODO: rename to _ with JEP 443 + LOG.error("Invalid key"); // TODO: specific error screen appWindows.showErrorWindow(e, window, null); } default -> { - LOG.error("Failed to load key.", e); - appWindows.showErrorWindow(e, window, null); + LOG.error("Failed to load key.", t); + appWindows.showErrorWindow(t, window, null); } } } diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/AuthFlowController.java b/src/main/java/org/cryptomator/ui/keyloading/hub/AuthFlowController.java index 5765f56e0..06e488581 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/AuthFlowController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/AuthFlowController.java @@ -35,13 +35,13 @@ public class AuthFlowController implements FxController { private final String deviceId; private final HubConfig hubConfig; private final AtomicReference tokenRef; - private final CompletableFuture result; + private final CompletableFuture result; private final Lazy receiveKeyScene; private final ObjectProperty authUri; private AuthFlowTask task; @Inject - public AuthFlowController(Application application, @KeyLoading Stage window, ExecutorService executor, @Named("deviceId") String deviceId, HubConfig hubConfig, @Named("bearerToken") AtomicReference tokenRef, CompletableFuture result, @FxmlScene(FxmlFile.HUB_RECEIVE_KEY) Lazy receiveKeyScene) { + public AuthFlowController(Application application, @KeyLoading Stage window, ExecutorService executor, @Named("deviceId") String deviceId, HubConfig hubConfig, @Named("bearerToken") AtomicReference tokenRef, CompletableFuture result, @FxmlScene(FxmlFile.HUB_RECEIVE_KEY) Lazy receiveKeyScene) { this.application = application; this.window = window; this.executor = executor; diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/AuthFlowTask.java b/src/main/java/org/cryptomator/ui/keyloading/hub/AuthFlowTask.java index 0379d4331..766f23932 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/AuthFlowTask.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/AuthFlowTask.java @@ -1,13 +1,14 @@ package org.cryptomator.ui.keyloading.hub; import com.fasterxml.jackson.databind.ObjectMapper; -import io.github.coffeelibs.tinyoauth2client.AuthFlow; import io.github.coffeelibs.tinyoauth2client.TinyOAuth2; import io.github.coffeelibs.tinyoauth2client.http.response.Response; import javafx.concurrent.Task; import java.io.IOException; import java.net.URI; +import java.net.http.HttpClient; +import java.time.Duration; import java.util.function.Consumer; class AuthFlowTask extends Task { @@ -21,7 +22,7 @@ class AuthFlowTask extends Task { /** * Spawns a server and waits for the redirectUri to be called. * - * @param hubConfig Configuration object holding parameters required by {@link AuthFlow} + * @param hubConfig Configuration object holding parameters required by {@link io.github.coffeelibs.tinyoauth2client.AuthorizationCodeGrant} * @param redirectUriConsumer A callback invoked with the redirectUri, as soon as the server has started */ public AuthFlowTask(HubConfig hubConfig, AuthFlowContext authFlowContext, Consumer redirectUriConsumer) { @@ -34,10 +35,11 @@ class AuthFlowTask extends Task { protected String call() throws IOException, InterruptedException { var response = TinyOAuth2.client(hubConfig.clientId) // .withTokenEndpoint(URI.create(hubConfig.tokenEndpoint)) // - .authFlow(URI.create(hubConfig.authEndpoint)) // + .withRequestTimeout(Duration.ofSeconds(10)) // + .authorizationCodeGrant(URI.create(hubConfig.authEndpoint)) // .setSuccessResponse(Response.redirect(URI.create(hubConfig.authSuccessUrl + "&device=" + authFlowContext.deviceId()))) // .setErrorResponse(Response.redirect(URI.create(hubConfig.authErrorUrl + "&device=" + authFlowContext.deviceId()))) // - .authorize(redirectUriConsumer); + .authorize(HttpClient.newHttpClient(), redirectUriConsumer); if (response.statusCode() != 200) { throw new NotOkResponseException("Authorization returned status code " + response.statusCode()); } diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/CreateDeviceDto.java b/src/main/java/org/cryptomator/ui/keyloading/hub/CreateDeviceDto.java deleted file mode 100644 index ed10a9257..000000000 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/CreateDeviceDto.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.cryptomator.ui.keyloading.hub; - -record CreateDeviceDto(String id, String name, String publicKey) { - -} diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/HttpHelper.java b/src/main/java/org/cryptomator/ui/keyloading/hub/HttpHelper.java deleted file mode 100644 index 0077467cc..000000000 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/HttpHelper.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.cryptomator.ui.keyloading.hub; - -import com.google.common.io.CharStreams; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.http.HttpResponse; -import java.nio.charset.StandardCharsets; - -class HttpHelper { - - public static String readBody(HttpResponse response) throws IOException { - try (var in = response.body(); var reader = new InputStreamReader(in, StandardCharsets.UTF_8)) { - return CharStreams.toString(reader); - } - } - -} diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/HubConfig.java b/src/main/java/org/cryptomator/ui/keyloading/hub/HubConfig.java index 5f462b170..f8ec7b854 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/HubConfig.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/HubConfig.java @@ -1,6 +1,10 @@ package org.cryptomator.ui.keyloading.hub; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.net.URI; // needs to be accessible by JSON decoder @JsonIgnoreProperties(ignoreUnknown = true) @@ -9,8 +13,24 @@ public class HubConfig { public String clientId; public String authEndpoint; public String tokenEndpoint; - public String devicesResourceUrl; public String authSuccessUrl; public String authErrorUrl; + public @Nullable String apiBaseUrl; + @Deprecated // use apiBaseUrl + "/devices/" + public String devicesResourceUrl; + public URI getApiBaseUrl() { + if (apiBaseUrl != null) { + // make sure to end on "/": + return URI.create(apiBaseUrl + "/").normalize(); + } else { // legacy approach + assert devicesResourceUrl != null; + // make sure to end on "/": + return URI.create(devicesResourceUrl + "/..").normalize(); + } + } + + public URI getWebappBaseUrl() { + return getApiBaseUrl().resolve("../app/"); + } } diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingModule.java b/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingModule.java index 7b8aae875..235fbf639 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingModule.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingModule.java @@ -1,7 +1,6 @@ package org.cryptomator.ui.keyloading.hub; import com.google.common.io.BaseEncoding; -import com.nimbusds.jose.JWEObject; import dagger.Binds; import dagger.Module; import dagger.Provides; @@ -69,7 +68,7 @@ public abstract class HubKeyLoadingModule { @Provides @KeyLoadingScoped - static CompletableFuture provideResult() { + static CompletableFuture provideResult() { return new CompletableFuture<>(); } @@ -114,12 +113,13 @@ public abstract class HubKeyLoadingModule { } @Provides - @FxmlScene(FxmlFile.HUB_REGISTER_DEVICE) + @FxmlScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE) @KeyLoadingScoped - static Scene provideHubRegisterDeviceScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) { - return fxmlLoaders.createScene(FxmlFile.HUB_REGISTER_DEVICE); + static Scene provideHubLegacyRegisterDeviceScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE); } + @Provides @FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS) @KeyLoadingScoped @@ -134,6 +134,13 @@ public abstract class HubKeyLoadingModule { return fxmlLoaders.createScene(FxmlFile.HUB_REGISTER_FAILED); } + @Provides + @FxmlScene(FxmlFile.HUB_SETUP_DEVICE) + @KeyLoadingScoped + static Scene provideHubRegisterDeviceScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene(FxmlFile.HUB_SETUP_DEVICE); + } + @Provides @FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) @KeyLoadingScoped @@ -141,6 +148,13 @@ public abstract class HubKeyLoadingModule { return fxmlLoaders.createScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE); } + @Provides + @FxmlScene(FxmlFile.HUB_REQUIRE_ACCOUNT_INIT) + @KeyLoadingScoped + static Scene provideRequireAccountInitScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene(FxmlFile.HUB_REQUIRE_ACCOUNT_INIT); + } + @Binds @IntoMap @FxControllerKey(NoKeychainController.class) @@ -166,6 +180,11 @@ public abstract class HubKeyLoadingModule { @FxControllerKey(RegisterDeviceController.class) abstract FxController bindRegisterDeviceController(RegisterDeviceController controller); + @Binds + @IntoMap + @FxControllerKey(LegacyRegisterDeviceController.class) + abstract FxController bindLegacyRegisterDeviceController(LegacyRegisterDeviceController controller); + @Binds @IntoMap @FxControllerKey(RegisterSuccessController.class) @@ -180,4 +199,9 @@ public abstract class HubKeyLoadingModule { @IntoMap @FxControllerKey(UnauthorizedDeviceController.class) abstract FxController bindUnauthorizedDeviceController(UnauthorizedDeviceController controller); + + @Binds + @IntoMap + @FxControllerKey(RequireAccountInitController.class) + abstract FxController bindRequireAccountInitController(RequireAccountInitController controller); } diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingStrategy.java b/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingStrategy.java index cc5edfcb4..9ea5e7735 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingStrategy.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingStrategy.java @@ -36,11 +36,11 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy { private final KeychainManager keychainManager; private final Lazy authFlowScene; private final Lazy noKeychainScene; - private final CompletableFuture result; + private final CompletableFuture result; private final DeviceKey deviceKey; @Inject - public HubKeyLoadingStrategy(@KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy authFlowScene, @FxmlScene(FxmlFile.HUB_NO_KEYCHAIN) Lazy noKeychainScene, CompletableFuture result, DeviceKey deviceKey, KeychainManager keychainManager, @Named("windowTitle") String windowTitle) { + public HubKeyLoadingStrategy(@KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy authFlowScene, @FxmlScene(FxmlFile.HUB_NO_KEYCHAIN) Lazy noKeychainScene, CompletableFuture result, DeviceKey deviceKey, KeychainManager keychainManager, @Named("windowTitle") String windowTitle) { this.window = window; this.keychainManager = keychainManager; window.setTitle(windowTitle); @@ -60,7 +60,7 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy { var keypair = deviceKey.get(); showWindow(authFlowScene); var jwe = result.get(); - return JWEHelper.decrypt(jwe, keypair.getPrivate()); + return jwe.decryptMasterkey(keypair.getPrivate()); } catch (NoKeychainAccessProviderException e) { showWindow(noKeychainScene); throw new UnlockCancelledException("Unlock canceled due to missing prerequisites", e); diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/JWEHelper.java b/src/main/java/org/cryptomator/ui/keyloading/hub/JWEHelper.java index 2c2b9baa4..2333051be 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/JWEHelper.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/JWEHelper.java @@ -2,35 +2,103 @@ package org.cryptomator.ui.keyloading.hub; import com.google.common.base.Preconditions; import com.google.common.io.BaseEncoding; +import com.nimbusds.jose.EncryptionMethod; import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWEAlgorithm; +import com.nimbusds.jose.JWEHeader; import com.nimbusds.jose.JWEObject; +import com.nimbusds.jose.Payload; import com.nimbusds.jose.crypto.ECDHDecrypter; +import com.nimbusds.jose.crypto.ECDHEncrypter; +import com.nimbusds.jose.crypto.PasswordBasedDecrypter; +import com.nimbusds.jose.jwk.Curve; +import com.nimbusds.jose.jwk.gen.ECKeyGenerator; +import com.nimbusds.jose.jwk.gen.JWKGenerator; import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; import java.util.Arrays; +import java.util.Base64; +import java.util.Map; +import java.util.function.Function; class JWEHelper { private static final Logger LOG = LoggerFactory.getLogger(JWEHelper.class); - private static final String JWE_PAYLOAD_MASTERKEY_FIELD = "key"; + private static final String JWE_PAYLOAD_KEY_FIELD = "key"; + private static final String EC_ALG = "EC"; private JWEHelper(){} - - public static Masterkey decrypt(JWEObject jwe, ECPrivateKey privateKey) throws MasterkeyLoadingFailedException { + public static JWEObject encryptUserKey(ECPrivateKey userKey, ECPublicKey deviceKey) { try { - jwe.decrypt(new ECDHDecrypter(privateKey)); - return readKey(jwe); + var encodedUserKey = Base64.getEncoder().encodeToString(userKey.getEncoded()); + var keyGen = new ECKeyGenerator(Curve.P_384); + var ephemeralKeyPair = keyGen.generate(); + var header = new JWEHeader.Builder(JWEAlgorithm.ECDH_ES, EncryptionMethod.A256GCM).ephemeralPublicKey(ephemeralKeyPair.toPublicJWK()).build(); + var payload = new Payload(Map.of(JWE_PAYLOAD_KEY_FIELD, encodedUserKey)); + var jwe = new JWEObject(header, payload); + jwe.encrypt(new ECDHEncrypter(deviceKey)); + return jwe; } catch (JOSEException e) { - LOG.warn("Failed to decrypt JWE: {}", jwe); - throw new MasterkeyLoadingFailedException("Failed to decrypt JWE", e); + throw new RuntimeException(e); } } - private static Masterkey readKey(JWEObject jwe) throws MasterkeyLoadingFailedException { + public static ECPrivateKey decryptUserKey(JWEObject jwe, String setupCode) throws InvalidJweKeyException { + try { + jwe.decrypt(new PasswordBasedDecrypter(setupCode)); + return decodeUserKey(jwe); + } catch (JOSEException e) { + throw new InvalidJweKeyException(e); + } + } + + public static ECPrivateKey decryptUserKey(JWEObject jwe, ECPrivateKey deviceKey) throws InvalidJweKeyException { + try { + jwe.decrypt(new ECDHDecrypter(deviceKey)); + return decodeUserKey(jwe); + } catch (JOSEException e) { + throw new InvalidJweKeyException(e); + } + } + + private static ECPrivateKey decodeUserKey(JWEObject decryptedJwe) { + try { + var keySpec = readKey(decryptedJwe, JWE_PAYLOAD_KEY_FIELD, PKCS8EncodedKeySpec::new); + var factory = KeyFactory.getInstance(EC_ALG); + var privateKey = factory.generatePrivate(keySpec); + if (privateKey instanceof ECPrivateKey ecPrivateKey) { + return ecPrivateKey; + } else { + throw new IllegalStateException(EC_ALG + " key factory not generating ECPrivateKeys"); + } + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(EC_ALG + " not supported"); + } catch (InvalidKeySpecException e) { + LOG.warn("Unexpected JWE payload: {}", decryptedJwe.getPayload()); + throw new MasterkeyLoadingFailedException("Unexpected JWE payload", e); + } + } + + public static Masterkey decryptVaultKey(JWEObject jwe, ECPrivateKey privateKey) throws InvalidJweKeyException { + try { + jwe.decrypt(new ECDHDecrypter(privateKey)); + return readKey(jwe, JWE_PAYLOAD_KEY_FIELD, Masterkey::new); + } catch (JOSEException e) { + throw new InvalidJweKeyException(e); + } + } + + private static T readKey(JWEObject jwe, String keyField, Function rawKeyFactory) throws MasterkeyLoadingFailedException { Preconditions.checkArgument(jwe.getState() == JWEObject.State.DECRYPTED); var fields = jwe.getPayload().toJSONObject(); if (fields == null) { @@ -39,11 +107,11 @@ class JWEHelper { } var keyBytes = new byte[0]; try { - if (fields.get(JWE_PAYLOAD_MASTERKEY_FIELD) instanceof String key) { + if (fields.get(keyField) instanceof String key) { keyBytes = BaseEncoding.base64().decode(key); - return new Masterkey(keyBytes); + return rawKeyFactory.apply(keyBytes); } else { - throw new IllegalArgumentException("JWE payload doesn't contain field " + JWE_PAYLOAD_MASTERKEY_FIELD); + throw new IllegalArgumentException("JWE payload doesn't contain field " + keyField); } } catch (IllegalArgumentException e) { LOG.error("Unexpected JWE payload: {}", jwe.getPayload()); @@ -52,4 +120,11 @@ class JWEHelper { Arrays.fill(keyBytes, (byte) 0x00); } } + + public static class InvalidJweKeyException extends MasterkeyLoadingFailedException { + + public InvalidJweKeyException(Throwable cause) { + super("Invalid key", cause); + } + } } diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/LegacyRegisterDeviceController.java b/src/main/java/org/cryptomator/ui/keyloading/hub/LegacyRegisterDeviceController.java new file mode 100644 index 000000000..113ecd249 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/LegacyRegisterDeviceController.java @@ -0,0 +1,191 @@ +package org.cryptomator.ui.keyloading.hub; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.databind.ObjectMapper; +import dagger.Lazy; +import org.cryptomator.common.settings.DeviceKey; +import org.cryptomator.cryptolib.common.P384KeyPair; +import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlScene; +import org.cryptomator.ui.keyloading.KeyLoading; +import org.cryptomator.ui.keyloading.KeyLoadingScoped; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Named; +import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.fxml.FXML; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.ContentDisplay; +import javafx.scene.control.TextField; +import javafx.stage.Stage; +import javafx.stage.WindowEvent; +import java.io.IOException; +import java.net.InetAddress; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicReference; + +@KeyLoadingScoped +public class LegacyRegisterDeviceController implements FxController { + + private static final Logger LOG = LoggerFactory.getLogger(LegacyRegisterDeviceController.class); + private static final ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true); + private static final List EXPECTED_RESPONSE_CODES = List.of(201, 409); + + private final Stage window; + private final HubConfig hubConfig; + private final String bearerToken; + private final Lazy registerSuccessScene; + private final Lazy registerFailedScene; + private final String deviceId; + private final P384KeyPair keyPair; + private final CompletableFuture result; + private final DecodedJWT jwt; + private final HttpClient httpClient; + private final BooleanProperty deviceNameAlreadyExists = new SimpleBooleanProperty(false); + + public TextField deviceNameField; + public Button registerBtn; + + @Inject + public LegacyRegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, CompletableFuture result, @Named("bearerToken") AtomicReference bearerToken, @FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS) Lazy registerSuccessScene, @FxmlScene(FxmlFile.HUB_REGISTER_FAILED) Lazy registerFailedScene) { + this.window = window; + this.hubConfig = hubConfig; + this.deviceId = deviceId; + this.keyPair = Objects.requireNonNull(deviceKey.get()); + this.result = result; + this.bearerToken = Objects.requireNonNull(bearerToken.get()); + this.registerSuccessScene = registerSuccessScene; + this.registerFailedScene = registerFailedScene; + this.jwt = JWT.decode(this.bearerToken); + this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed); + this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build(); + } + + public void initialize() { + deviceNameField.setText(determineHostname()); + deviceNameField.textProperty().addListener(observable -> deviceNameAlreadyExists.set(false)); + } + + private String determineHostname() { + try { + var hostName = InetAddress.getLocalHost().getHostName(); + return Objects.requireNonNullElse(hostName, ""); + } catch (IOException e) { + return ""; + } + } + + @FXML + public void register() { + deviceNameAlreadyExists.set(false); + registerBtn.setContentDisplay(ContentDisplay.LEFT); + registerBtn.setDisable(true); + + var deviceUri = URI.create(hubConfig.devicesResourceUrl + deviceId); + var deviceKey = keyPair.getPublic().getEncoded(); + var dto = new CreateDeviceDto(); + dto.id = deviceId; + dto.name = deviceNameField.getText(); + dto.publicKey = Base64.getUrlEncoder().withoutPadding().encodeToString(deviceKey); + var json = toJson(dto); + var request = HttpRequest.newBuilder(deviceUri) // + .PUT(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) // + .header("Authorization", "Bearer " + bearerToken) // + .header("Content-Type", "application/json") // + .build(); + httpClient.sendAsync(request, HttpResponse.BodyHandlers.discarding()) // + .thenApply(response -> { + if (EXPECTED_RESPONSE_CODES.contains(response.statusCode())) { + return response; + } else { + throw new RuntimeException("Server answered with unexpected status code " + response.statusCode()); + } + }).handleAsync((response, throwable) -> { + if (response != null) { + this.handleResponse(response); + } else { + this.registrationFailed(throwable); + } + return null; + }, Platform::runLater); + } + + private String toJson(CreateDeviceDto dto) { + try { + return JSON.writer().writeValueAsString(dto); + } catch (JacksonException e) { + throw new IllegalStateException("Failed to serialize DTO", e); + } + } + + private void handleResponse(HttpResponse voidHttpResponse) { + assert EXPECTED_RESPONSE_CODES.contains(voidHttpResponse.statusCode()); + + if (voidHttpResponse.statusCode() == 409) { + deviceNameAlreadyExists.set(true); + registerBtn.setContentDisplay(ContentDisplay.TEXT_ONLY); + registerBtn.setDisable(false); + } else { + LOG.debug("Device registration for hub instance {} successful.", hubConfig.authSuccessUrl); + window.setScene(registerSuccessScene.get()); + } + } + + private void registrationFailed(Throwable cause) { + LOG.warn("Device registration failed.", cause); + window.setScene(registerFailedScene.get()); + result.completeExceptionally(cause); + } + + @FXML + public void close() { + window.close(); + } + + private void windowClosed(WindowEvent windowEvent) { + result.cancel(true); + } + + /* Getter */ + + public String getUserName() { + return jwt.getClaim("email").asString(); + } + + + //--- Getters & Setters + + public BooleanProperty deviceNameAlreadyExistsProperty() { + return deviceNameAlreadyExists; + } + + public boolean getDeviceNameAlreadyExists() { + return deviceNameAlreadyExists.get(); + } + + private static class CreateDeviceDto { + public String id; + public String name; + public final String type = "DESKTOP"; + public String publicKey; + + } + +} \ No newline at end of file diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java b/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java index bd7497bec..c0681d4bb 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java @@ -1,5 +1,8 @@ package org.cryptomator.ui.keyloading.hub; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.jose.JWEObject; import dagger.Lazy; import org.cryptomator.common.vaults.Vault; @@ -8,6 +11,9 @@ import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.keyloading.KeyLoading; import org.cryptomator.ui.keyloading.KeyLoadingScoped; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; @@ -17,14 +23,16 @@ import javafx.scene.Scene; import javafx.stage.Stage; import javafx.stage.WindowEvent; import java.io.IOException; -import java.io.InputStream; import java.io.UncheckedIOException; import java.net.URI; import java.net.URISyntaxException; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; import java.text.ParseException; +import java.time.Duration; +import java.time.Instant; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; @@ -33,26 +41,35 @@ import java.util.concurrent.atomic.AtomicReference; @KeyLoadingScoped public class ReceiveKeyController implements FxController { + private static final Logger LOG = LoggerFactory.getLogger(ReceiveKeyController.class); private static final String SCHEME_PREFIX = "hub+"; + private static final ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true); + private static final Duration REQ_TIMEOUT = Duration.ofSeconds(10); private final Stage window; + private final HubConfig hubConfig; private final String deviceId; private final String bearerToken; - private final CompletableFuture result; - private final Lazy registerDeviceScene; + private final CompletableFuture result; + private final Lazy setupDeviceScene; + private final Lazy legacyRegisterDeviceScene; private final Lazy unauthorizedScene; + private final Lazy accountInitializationScene; private final URI vaultBaseUri; private final Lazy invalidLicenseScene; private final HttpClient httpClient; @Inject - public ReceiveKeyController(@KeyLoading Vault vault, ExecutorService executor, @KeyLoading Stage window, @Named("deviceId") String deviceId, @Named("bearerToken") AtomicReference tokenRef, CompletableFuture result, @FxmlScene(FxmlFile.HUB_REGISTER_DEVICE) Lazy registerDeviceScene, @FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) Lazy unauthorizedScene, @FxmlScene(FxmlFile.HUB_INVALID_LICENSE) Lazy invalidLicenseScene) { + public ReceiveKeyController(@KeyLoading Vault vault, ExecutorService executor, @KeyLoading Stage window, HubConfig hubConfig, @Named("deviceId") String deviceId, @Named("bearerToken") AtomicReference tokenRef, CompletableFuture result, @FxmlScene(FxmlFile.HUB_SETUP_DEVICE) Lazy setupDeviceScene, @FxmlScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE) Lazy legacyRegisterDeviceScene, @FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) Lazy unauthorizedScene, @FxmlScene(FxmlFile.HUB_REQUIRE_ACCOUNT_INIT) Lazy accountInitializationScene, @FxmlScene(FxmlFile.HUB_INVALID_LICENSE) Lazy invalidLicenseScene) { this.window = window; + this.hubConfig = hubConfig; this.deviceId = deviceId; this.bearerToken = Objects.requireNonNull(tokenRef.get()); this.result = result; - this.registerDeviceScene = registerDeviceScene; + this.setupDeviceScene = setupDeviceScene; + this.legacyRegisterDeviceScene = legacyRegisterDeviceScene; this.unauthorizedScene = unauthorizedScene; + this.accountInitializationScene = accountInitializationScene; this.vaultBaseUri = getVaultBaseUri(vault); this.invalidLicenseScene = invalidLicenseScene; this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed); @@ -61,23 +78,121 @@ public class ReceiveKeyController implements FxController { @FXML public void initialize() { - var keyUri = appendPath(vaultBaseUri, "/keys/" + deviceId); - var request = HttpRequest.newBuilder(keyUri) // + requestVaultMasterkey(); + } + + /** + * STEP 1 (Request): GET vault key for this user + */ + private void requestVaultMasterkey() { + var accessTokenUri = appendPath(vaultBaseUri, "/access-token"); + var request = HttpRequest.newBuilder(accessTokenUri) // .header("Authorization", "Bearer " + bearerToken) // .GET() // + .timeout(REQ_TIMEOUT) // .build(); - httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream()) // - .thenAcceptAsync(this::loadedExistingKey, Platform::runLater) // + httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.US_ASCII)) // + .thenAcceptAsync(this::receivedVaultMasterkey, Platform::runLater) // .exceptionally(this::retrievalFailed); } - private void loadedExistingKey(HttpResponse response) { + /** + * STEP 1 (Response): GET vault key for this user + * + * @param response Response + */ + private void receivedVaultMasterkey(HttpResponse response) { + LOG.debug("GET {} -> Status Code {}", response.request().uri(), response.statusCode()); + switch (response.statusCode()) { + case 200 -> requestUserKey(response.body()); + case 402 -> licenseExceeded(); + case 403, 410 -> accessNotGranted(); // or vault has been archived, effectively disallowing access - TODO: add specific dialog? + case 449 -> accountInitializationRequired(); + case 404 -> requestLegacyAccessToken(); + default -> throw new IllegalStateException("Unexpected response " + response.statusCode()); + } + } + + /** + * STEP 2 (Request): GET user key for this device + */ + private void requestUserKey(String encryptedVaultKey) { + var deviceTokenUri = URI.create(hubConfig.getApiBaseUrl() + "/devices/" + deviceId); + var request = HttpRequest.newBuilder(deviceTokenUri) // + .header("Authorization", "Bearer " + bearerToken) // + .GET() // + .timeout(REQ_TIMEOUT) // + .build(); + httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)) // + .thenAcceptAsync(response -> receivedUserKey(encryptedVaultKey, response), Platform::runLater) // + .exceptionally(this::retrievalFailed); + } + + /** + * STEP 2 (Response): GET user key for this device + * + * @param response Response + */ + private void receivedUserKey(String encryptedVaultKey, HttpResponse response) { + LOG.debug("GET {} -> Status Code {}", response.request().uri(), response.statusCode()); try { switch (response.statusCode()) { - case 200 -> retrievalSucceeded(response); + case 200 -> { + var device = JSON.reader().readValue(response.body(), DeviceDto.class); + receivedBothEncryptedKeys(encryptedVaultKey, device.userPrivateKey); + } + case 404 -> needsDeviceSetup(); // TODO: using the setup code, we can theoretically immediately unlock + default -> throw new IllegalStateException("Unexpected response " + response.statusCode()); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private void needsDeviceSetup() { + window.setScene(setupDeviceScene.get()); + } + + private void receivedBothEncryptedKeys(String encryptedVaultKey, String encryptedUserKey) throws IOException { + try { + var vaultKeyJwe = JWEObject.parse(encryptedVaultKey); + var userKeyJwe = JWEObject.parse(encryptedUserKey); + result.complete(ReceivedKey.vaultKeyAndUserKey(vaultKeyJwe, userKeyJwe)); + window.close(); + } catch (ParseException e) { + throw new IOException("Failed to parse JWE", e); + } + } + + /** + * LEGACY FALLBACK (Request): GET the legacy access token from Hub 1.x + */ + @Deprecated + private void requestLegacyAccessToken() { + var legacyAccessTokenUri = appendPath(vaultBaseUri, "/keys/" + deviceId); + var request = HttpRequest.newBuilder(legacyAccessTokenUri) // + .header("Authorization", "Bearer " + bearerToken) // + .GET() // + .timeout(REQ_TIMEOUT) // + .build(); + httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.US_ASCII)) // + .thenAcceptAsync(this::receivedLegacyAccessTokenResponse, Platform::runLater) // + .exceptionally(this::retrievalFailed); + } + + /** + * LEGACY FALLBACK (Response) + * + * @param response Response + */ + @Deprecated + private void receivedLegacyAccessTokenResponse(HttpResponse response) { + try { + switch (response.statusCode()) { + case 200 -> receivedLegacyAccessTokenSuccess(response.body()); case 402 -> licenseExceeded(); case 403, 410 -> accessNotGranted(); // or vault has been archived, effectively disallowing access - case 404 -> needsDeviceRegistration(); + case 404 -> needsLegacyDeviceRegistration(); default -> throw new IOException("Unexpected response " + response.statusCode()); } } catch (IOException e) { @@ -85,10 +200,11 @@ public class ReceiveKeyController implements FxController { } } - private void retrievalSucceeded(HttpResponse response) throws IOException { + @Deprecated + private void receivedLegacyAccessTokenSuccess(String rawToken) throws IOException { try { - var string = HttpHelper.readBody(response); - result.complete(JWEObject.parse(string)); + var token = JWEObject.parse(rawToken); + result.complete(ReceivedKey.legacyDeviceKey(token)); window.close(); } catch (ParseException e) { throw new IOException("Failed to parse JWE", e); @@ -99,14 +215,19 @@ public class ReceiveKeyController implements FxController { window.setScene(invalidLicenseScene.get()); } - private void needsDeviceRegistration() { - window.setScene(registerDeviceScene.get()); + @Deprecated + private void needsLegacyDeviceRegistration() { + window.setScene(legacyRegisterDeviceScene.get()); } private void accessNotGranted() { window.setScene(unauthorizedScene.get()); } + private void accountInitializationRequired() { + window.setScene(accountInitializationScene.get()); + } + private Void retrievalFailed(Throwable cause) { result.completeExceptionally(cause); return null; @@ -132,14 +253,17 @@ public class ReceiveKeyController implements FxController { private static URI getVaultBaseUri(Vault vault) { try { - var kid = vault.getVaultConfigCache().get().getKeyId(); - assert kid.getScheme().startsWith(SCHEME_PREFIX); - var hubUriScheme = kid.getScheme().substring(SCHEME_PREFIX.length()); - return new URI(hubUriScheme, kid.getSchemeSpecificPart(), kid.getFragment()); + var url = vault.getVaultConfigCache().get().getKeyId(); + assert url.getScheme().startsWith(SCHEME_PREFIX); + var correctedScheme = url.getScheme().substring(SCHEME_PREFIX.length()); + return new URI(correctedScheme, url.getSchemeSpecificPart(), url.getFragment()); } catch (IOException e) { throw new UncheckedIOException(e); } catch (URISyntaxException e) { throw new IllegalStateException("URI constructed from params known to be valid", e); } } + + @JsonIgnoreProperties(ignoreUnknown = true) + private record DeviceDto(@JsonProperty(value = "userPrivateKey", required = true) String userPrivateKey) {} } diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/ReceivedKey.java b/src/main/java/org/cryptomator/ui/keyloading/hub/ReceivedKey.java new file mode 100644 index 000000000..74da388d7 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/ReceivedKey.java @@ -0,0 +1,45 @@ +package org.cryptomator.ui.keyloading.hub; + +import com.nimbusds.jose.JWEObject; +import org.cryptomator.cryptolib.api.Masterkey; + +import java.security.interfaces.ECPrivateKey; + +@FunctionalInterface +interface ReceivedKey { + + /** + * Decrypts the vault key. + * + * @param deviceKey This device's private key. + * @return The decrypted vault key + */ + Masterkey decryptMasterkey(ECPrivateKey deviceKey); + + /** + * Creates an unlock response object from the user key + vault key. + * + * @param vaultKeyJwe a JWE containing the symmetric vault key, encrypted for this device's user. + * @param userKeyJwe a JWE containing the user's private key, encrypted for this device. + * @return Ciphertext received by Hub, which can be decrypted using this device's private key. + */ + static ReceivedKey vaultKeyAndUserKey(JWEObject vaultKeyJwe, JWEObject userKeyJwe) { + return deviceKey -> { + var userKey = JWEHelper.decryptUserKey(userKeyJwe, deviceKey); + return JWEHelper.decryptVaultKey(vaultKeyJwe, userKey); + }; + } + + /** + * Creates an unlock response object from the received legacy "access token" JWE. + * + * @param vaultKeyJwe a JWE containing the symmetric vault key, encrypted for this device. + * @return Ciphertext received by Hub, which can be decrypted using this device's private key. + * @deprecated Only for compatibility with Hub 1.0 - 1.2 + */ + @Deprecated + static ReceivedKey legacyDeviceKey(JWEObject vaultKeyJwe) { + return deviceKey -> JWEHelper.decryptVaultKey(vaultKeyJwe, deviceKey); + } + +} diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterDeviceController.java b/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterDeviceController.java index 6fa6aa424..837dc5032 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterDeviceController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterDeviceController.java @@ -1,7 +1,7 @@ package org.cryptomator.ui.keyloading.hub; -import com.auth0.jwt.JWT; -import com.auth0.jwt.interfaces.DecodedJWT; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.io.BaseEncoding; @@ -20,6 +20,7 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; import javafx.application.Platform; +import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.fxml.FXML; @@ -31,14 +32,16 @@ import javafx.stage.Stage; import javafx.stage.WindowEvent; import java.io.IOException; import java.net.InetAddress; -import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; -import java.util.List; +import java.text.ParseException; +import java.time.Duration; +import java.time.Instant; import java.util.Objects; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicReference; @@ -47,7 +50,7 @@ public class RegisterDeviceController implements FxController { private static final Logger LOG = LoggerFactory.getLogger(RegisterDeviceController.class); private static final ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true); - private static final List EXPECTED_RESPONSE_CODES = List.of(201, 409); + private static final Duration REQ_TIMEOUT = Duration.ofSeconds(10); private final Stage window; private final HubConfig hubConfig; @@ -55,26 +58,27 @@ public class RegisterDeviceController implements FxController { private final Lazy registerSuccessScene; private final Lazy registerFailedScene; private final String deviceId; - private final P384KeyPair keyPair; - private final CompletableFuture result; - private final DecodedJWT jwt; + private final P384KeyPair deviceKeyPair; + private final CompletableFuture result; private final HttpClient httpClient; - private final BooleanProperty deviceNameAlreadyExists = new SimpleBooleanProperty(false); + private final BooleanProperty deviceNameAlreadyExists = new SimpleBooleanProperty(false); + private final BooleanProperty invalidSetupCode = new SimpleBooleanProperty(false); + private final BooleanProperty workInProgress = new SimpleBooleanProperty(false); + public TextField setupCodeField; public TextField deviceNameField; public Button registerBtn; @Inject - public RegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, CompletableFuture result, @Named("bearerToken") AtomicReference bearerToken, @FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS) Lazy registerSuccessScene, @FxmlScene(FxmlFile.HUB_REGISTER_FAILED) Lazy registerFailedScene) { + public RegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, CompletableFuture result, @Named("bearerToken") AtomicReference bearerToken, @FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS) Lazy registerSuccessScene, @FxmlScene(FxmlFile.HUB_REGISTER_FAILED) Lazy registerFailedScene) { this.window = window; this.hubConfig = hubConfig; this.deviceId = deviceId; - this.keyPair = Objects.requireNonNull(deviceKey.get()); + this.deviceKeyPair = Objects.requireNonNull(deviceKey.get()); this.result = result; this.bearerToken = Objects.requireNonNull(bearerToken.get()); this.registerSuccessScene = registerSuccessScene; this.registerFailedScene = registerFailedScene; - this.jwt = JWT.decode(this.bearerToken); this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed); this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build(); } @@ -82,6 +86,13 @@ public class RegisterDeviceController implements FxController { public void initialize() { deviceNameField.setText(determineHostname()); deviceNameField.textProperty().addListener(observable -> deviceNameAlreadyExists.set(false)); + deviceNameField.disableProperty().bind(workInProgress); + setupCodeField.textProperty().addListener(observable -> invalidSetupCode.set(false)); + setupCodeField.disableProperty().bind(workInProgress); + var missingSetupCode = setupCodeField.textProperty().isEmpty(); + var missingDeviceName = deviceNameField.textProperty().isEmpty(); + registerBtn.disableProperty().bind(workInProgress.or(missingSetupCode).or(missingDeviceName)); + registerBtn.contentDisplayProperty().bind(Bindings.when(workInProgress).then(ContentDisplay.LEFT).otherwise(ContentDisplay.TEXT_ONLY)); } private String determineHostname() { @@ -95,35 +106,62 @@ public class RegisterDeviceController implements FxController { @FXML public void register() { - deviceNameAlreadyExists.set(false); - registerBtn.setContentDisplay(ContentDisplay.LEFT); - registerBtn.setDisable(true); + workInProgress.set(true); - var keyUri = URI.create(hubConfig.devicesResourceUrl + deviceId); - var deviceKey = keyPair.getPublic().getEncoded(); - var dto = new CreateDeviceDto(deviceId, deviceNameField.getText(), BaseEncoding.base64Url().omitPadding().encode(deviceKey)); - var json = toJson(dto); - var request = HttpRequest.newBuilder(keyUri) // + var apiRootUrl = hubConfig.getApiBaseUrl(); + + var userReq = HttpRequest.newBuilder(apiRootUrl.resolve("users/me")) // + .GET() // + .timeout(REQ_TIMEOUT) // .header("Authorization", "Bearer " + bearerToken) // - .header("Content-Type", "application/json").PUT(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) // + .header("Content-Type", "application/json") // .build(); - httpClient.sendAsync(request, HttpResponse.BodyHandlers.discarding()) // + httpClient.sendAsync(userReq, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)) // .thenApply(response -> { - if (EXPECTED_RESPONSE_CODES.contains(response.statusCode())) { - return response; + if (response.statusCode() == 200) { + var dto = fromJson(response.body()); + return Objects.requireNonNull(dto, "null or empty response body"); } else { throw new RuntimeException("Server answered with unexpected status code " + response.statusCode()); } - }).handleAsync((response, throwable) -> { + }).thenApply(user -> { + try { + assert user.privateKey != null; // api/vaults/{v}/user-tokens/me would have returned 403, if user wasn't fully set up yet + var userKey = JWEHelper.decryptUserKey(JWEObject.parse(user.privateKey), setupCodeField.getText()); + return JWEHelper.encryptUserKey(userKey, deviceKeyPair.getPublic()); + } catch (ParseException e) { + throw new RuntimeException("Server answered with unparsable user key", e); + } + }).thenCompose(jwe -> { + var now = Instant.now().toString(); + var dto = new CreateDeviceDto(deviceId, deviceNameField.getText(), BaseEncoding.base64().encode(deviceKeyPair.getPublic().getEncoded()), "DESKTOP", jwe.serialize(), now); + var json = toJson(dto); + var deviceUri = apiRootUrl.resolve("devices/" + deviceId); + var putDeviceReq = HttpRequest.newBuilder(deviceUri) // + .PUT(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) // + .timeout(REQ_TIMEOUT) // + .header("Authorization", "Bearer " + bearerToken) // + .header("Content-Type", "application/json") // + .build(); + return httpClient.sendAsync(putDeviceReq, HttpResponse.BodyHandlers.discarding()); + }).whenCompleteAsync((response, throwable) -> { if (response != null) { this.handleResponse(response); } else { - this.registrationFailed(throwable); + this.setupFailed(throwable); } - return null; + workInProgress.set(false); }, Platform::runLater); } + private UserDto fromJson(String json) { + try { + return JSON.reader().readValue(json, UserDto.class); + } catch (IOException e) { + throw new IllegalStateException("Failed to deserialize DTO", e); + } + } + private String toJson(CreateDeviceDto dto) { try { return JSON.writer().writeValueAsString(dto); @@ -132,23 +170,26 @@ public class RegisterDeviceController implements FxController { } } - private void handleResponse(HttpResponse voidHttpResponse) { - assert EXPECTED_RESPONSE_CODES.contains(voidHttpResponse.statusCode()); - - if (voidHttpResponse.statusCode() == 409) { - deviceNameAlreadyExists.set(true); - registerBtn.setContentDisplay(ContentDisplay.TEXT_ONLY); - registerBtn.setDisable(false); - } else { + private void handleResponse(HttpResponse response) { + if (response.statusCode() == 201) { LOG.debug("Device registration for hub instance {} successful.", hubConfig.authSuccessUrl); window.setScene(registerSuccessScene.get()); + } else if (response.statusCode() == 409) { + deviceNameAlreadyExists.set(true); + } else { + setupFailed(new IllegalStateException("Unexpected http status code " + response.statusCode())); } } - private void registrationFailed(Throwable cause) { - LOG.warn("Device registration failed.", cause); - window.setScene(registerFailedScene.get()); - result.completeExceptionally(cause); + private void setupFailed(Throwable cause) { + switch (cause) { + case CompletionException e when e.getCause() instanceof JWEHelper.InvalidJweKeyException -> invalidSetupCode.set(true); + default -> { + LOG.warn("Device setup failed.", cause); + window.setScene(registerFailedScene.get()); + result.completeExceptionally(cause); + } + } } @FXML @@ -160,13 +201,6 @@ public class RegisterDeviceController implements FxController { result.cancel(true); } - /* Getter */ - - public String getUserName() { - return jwt.getClaim("email").asString(); - } - - //--- Getters & Setters public BooleanProperty deviceNameAlreadyExistsProperty() { @@ -177,5 +211,21 @@ public class RegisterDeviceController implements FxController { return deviceNameAlreadyExists.get(); } + public BooleanProperty invalidSetupCodeProperty() { + return invalidSetupCode; + } + public boolean isInvalidSetupCode() { + return invalidSetupCode.get(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + private record UserDto(String id, String name, String publicKey, String privateKey, String setupCode) {} + + private record CreateDeviceDto(@JsonProperty(required = true) String id, // + @JsonProperty(required = true) String name, // + @JsonProperty(required = true) String publicKey, // + @JsonProperty(required = true, defaultValue = "DESKTOP") String type, // + @JsonProperty(required = true) String userPrivateKey, // + @JsonProperty(required = true) String creationTime) {} } diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterFailedController.java b/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterFailedController.java index 8a4278d72..57150390c 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterFailedController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterFailedController.java @@ -12,10 +12,10 @@ import java.util.concurrent.CompletableFuture; public class RegisterFailedController implements FxController { private final Stage window; - private final CompletableFuture result; + private final CompletableFuture result; @Inject - public RegisterFailedController(@KeyLoading Stage window, CompletableFuture result) { + public RegisterFailedController(@KeyLoading Stage window, CompletableFuture result) { this.window = window; this.result = result; } diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/RequireAccountInitController.java b/src/main/java/org/cryptomator/ui/keyloading/hub/RequireAccountInitController.java new file mode 100644 index 000000000..892d00de0 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/RequireAccountInitController.java @@ -0,0 +1,46 @@ +package org.cryptomator.ui.keyloading.hub; + +import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.keyloading.KeyLoading; +import org.cryptomator.ui.keyloading.KeyLoadingScoped; + +import javax.inject.Inject; +import javafx.application.Application; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.stage.Stage; +import javafx.stage.WindowEvent; +import java.util.concurrent.CompletableFuture; + +@KeyLoadingScoped +public class RequireAccountInitController implements FxController { + + private final Application application; + private final HubConfig hubConfig; + private final Stage window; + private final CompletableFuture result; + + @Inject + public RequireAccountInitController(Application application, HubConfig hubConfig, @KeyLoading Stage window, CompletableFuture result) { + this.application = application; + this.hubConfig = hubConfig; + this.window = window; + this.result = result; + this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed); + } + + @FXML + public void completeSetup() { + application.getHostServices().showDocument(hubConfig.getWebappBaseUrl().resolve("profile").toString()); + close(); + } + + @FXML + public void close() { + window.close(); + } + + private void windowClosed(WindowEvent windowEvent) { + result.cancel(true); + } +} diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/UnauthorizedDeviceController.java b/src/main/java/org/cryptomator/ui/keyloading/hub/UnauthorizedDeviceController.java index 1a7cbab02..c42ee1cd7 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/UnauthorizedDeviceController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/UnauthorizedDeviceController.java @@ -15,10 +15,10 @@ import java.util.concurrent.CompletableFuture; public class UnauthorizedDeviceController implements FxController { private final Stage window; - private final CompletableFuture result; + private final CompletableFuture result; @Inject - public UnauthorizedDeviceController(@KeyLoading Stage window, CompletableFuture result) { + public UnauthorizedDeviceController(@KeyLoading Stage window, CompletableFuture result) { this.window = window; this.result = result; this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed); diff --git a/src/main/java/org/cryptomator/ui/mainwindow/ResizeController.java b/src/main/java/org/cryptomator/ui/mainwindow/ResizeController.java index b136fa55c..2c3838ea0 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/ResizeController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/ResizeController.java @@ -7,7 +7,6 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; import javafx.beans.binding.BooleanBinding; -import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.geometry.Rectangle2D; import javafx.scene.input.MouseEvent; @@ -67,37 +66,7 @@ public class ResizeController implements FxController { return (settings.windowHeight.get() == 0) && (settings.windowWidth.get() == 0) && (settings.windowXPosition.get() == 0) && (settings.windowYPosition.get() == 0); } - private boolean isWithinDisplayBounds() { - // (x1, y1) is the top left corner of the window, (x2, y2) is the bottom right corner - final double slack = 10; - final double width = window.getWidth() - 2 * slack; - final double height = window.getHeight() - 2 * slack; - final double x1 = window.getX() + slack; - final double y1 = window.getY() + slack; - final double x2 = x1 + width; - final double y2 = y1 + height; - - final ObservableList screens = Screen.getScreensForRectangle(x1, y1, width, height); - - // Find the total visible area of the window - double visibleArea = 0; - for (Screen screen : screens) { - Rectangle2D bounds = screen.getVisualBounds(); - - double xOverlap = Math.min(x2, bounds.getMaxX()) - Math.max(x1, bounds.getMinX()); - double yOverlap = Math.min(y2, bounds.getMaxY()) - Math.max(y1, bounds.getMinY()); - - visibleArea += xOverlap * yOverlap; - } - - final double windowArea = width * height; - - // Within bounds if the visible area matches the window area - return visibleArea == windowArea; - } - private void checkDisplayBounds(WindowEvent evt) { - // Minimizing a window in Windows and closing it could result in an out of bounds position at (x, y) = (-32000, -32000) // See https://devblogs.microsoft.com/oldnewthing/20041028-00/?p=37453 // If the position is (-32000, -32000), restore to the last saved position @@ -108,8 +77,9 @@ public class ResizeController implements FxController { window.setHeight(settings.windowHeight.get()); } - if (!isWithinDisplayBounds()) { + if (isOutOfDisplayBounds()) { // If the position is illegal, then the window appears on the main screen in the middle of the window. + LOG.debug("Resetting window position due to insufficient screen overlap"); Rectangle2D primaryScreenBounds = Screen.getPrimary().getBounds(); window.setX((primaryScreenBounds.getWidth() - window.getMinWidth()) / 2); window.setY((primaryScreenBounds.getHeight() - window.getMinHeight()) / 2); @@ -119,6 +89,22 @@ public class ResizeController implements FxController { } } + private boolean isOutOfDisplayBounds() { + // define a rect which is inset on all sides from the window's rect: + final double x = window.getX() + 20; // 20px left + final double y = window.getY() + 5; // 5px top + final double w = window.getWidth() - 40; // 20px left + 20px right + final double h = window.getHeight() - 25; // 5px top + 20px bottom + return isRectangleOutOfScreen(x, y, 0, h) // Left pixel column + || isRectangleOutOfScreen(x + w, y, 0, h) // Right pixel column + || isRectangleOutOfScreen(x, y, w, 0) // Top pixel row + || isRectangleOutOfScreen(x, y + h, w, 0); // Bottom pixel row + } + + private boolean isRectangleOutOfScreen(double x, double y, double width, double height) { + return Screen.getScreensForRectangle(x, y, width, height).isEmpty(); + } + private void startResize(MouseEvent evt) { origX = window.getX(); origY = window.getY(); diff --git a/src/main/resources/fxml/hub_register_device.fxml b/src/main/resources/fxml/hub_legacy_register_device.fxml similarity index 96% rename from src/main/resources/fxml/hub_register_device.fxml rename to src/main/resources/fxml/hub_legacy_register_device.fxml index 8db67c272..51d4cf8b7 100644 --- a/src/main/resources/fxml/hub_register_device.fxml +++ b/src/main/resources/fxml/hub_legacy_register_device.fxml @@ -15,7 +15,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/i18n/strings.properties b/src/main/resources/i18n/strings.properties index bba386cee..6fc33396f 100644 --- a/src/main/resources/i18n/strings.properties +++ b/src/main/resources/i18n/strings.properties @@ -154,9 +154,10 @@ hub.auth.loginLink=Not redirected? Click here to open it. hub.receive.message=Processing response… hub.receive.description=Cryptomator is receiving and processing the response from Hub. Please wait. ### Register Device -hub.register.message=Device name required -hub.register.description=This seems to be the first Hub access from this device. In order to identify it for access authorization, you need to name this device. +hub.register.message=New Device +hub.register.description=This is the first Hub access from this device. Please authorize it using your Account Key. hub.register.nameLabel=Device Name +hub.register.invalidAccountKeyLabel=Invalid Account Key hub.register.occupiedMsg=Name already in use hub.register.registerBtn=Confirm ### Registration Success @@ -168,6 +169,11 @@ hub.registerFailed.description=An error was thrown in the naming process. For mo ### Unauthorized hub.unauthorized.message=Access denied hub.unauthorized.description=Your device has not yet been authorized to access this vault. Ask the vault owner to authorize it. +### Requires Account Initialization +hub.requireAccountInit.message=Action required +hub.requireAccountInit.description.0=To proceed, please complete the steps required in your +hub.requireAccountInit.description.1=Hub user profile +hub.requireAccountInit.description.2=. ### License Exceeded hub.invalidLicense.message=Hub License invalid hub.invalidLicense.description=Your Cryptomator Hub instance has an invalid license. Please inform a Hub administrator to upgrade or renew the license. diff --git a/src/main/resources/i18n/strings_ar.properties b/src/main/resources/i18n/strings_ar.properties index 832667de1..92e18ac9c 100644 --- a/src/main/resources/i18n/strings_ar.properties +++ b/src/main/resources/i18n/strings_ar.properties @@ -153,8 +153,6 @@ hub.auth.loginLink=لم يتم إعادة توجيهك؟ انقر هنا لفت hub.receive.message=معالجة الاستجابة… hub.receive.description=Cryptomator يتلقى ويعالج الاستجابة من المركز. الرجاء الانتظار. ### Register Device -hub.register.message=اسم الجهاز متطلب -hub.register.description=يبدو أن هذا هو أول وصول للمركز من هذا الجهاز. من أجل تحديده للحصول على إذن الوصول، تحتاج إلى تسمية هذا الجهاز. hub.register.nameLabel=اسم الجهاز hub.register.occupiedMsg=الاسم مستخدم مسبقاً hub.register.registerBtn=تأكيد @@ -167,6 +165,7 @@ hub.registerFailed.description=خطأ في عملية التسمية. لمزيد ### Unauthorized hub.unauthorized.message=تم رفض الوصول hub.unauthorized.description=لم يتم بعد منح الإذن لجهازك بالوصول إلى هذا المخزن. اطلب من مالك المخزن أن يأذن بذلك. +### Requires Account Initialization ### License Exceeded hub.invalidLicense.message=ترخيص المركز غير صالح hub.invalidLicense.description=نموذج المركز Cryptomator الخاص بك لديه ترخيص غير صالح. الرجاء إبلاغ مسؤول مركز لترقية أو تجديد الترخيص. diff --git a/src/main/resources/i18n/strings_be.properties b/src/main/resources/i18n/strings_be.properties index ae6c1f201..493f70382 100644 --- a/src/main/resources/i18n/strings_be.properties +++ b/src/main/resources/i18n/strings_be.properties @@ -22,6 +22,9 @@ error.hyperlink.report=Паведаміць пра гэтую памылку error.technicalDetails=Падрабязнасці: error.existingSolutionDescription=Cryptomator не чакаў такога, але мы знайшлі, як можна выправіць гэтую хібу. Калі ласка, скарыстайся спасылкаю. error.hyperlink.solution=Паглядзець на рашэнне +error.lookupPermissionMessage=Cryptomator можа шукаць рашэнне праблемаў онлайн, дасылаючы запыт да нашай база даных хібаў з твайго IP-адрасу. +error.dismiss=Адхіліць +error.lookUpSolution=Пашукаць вырашэнне # Defaults defaults.vault.vaultName=Скарбніца @@ -38,6 +41,7 @@ traymenu.vault.reveal=Паказаць # Add Vault Wizard addvaultwizard.title=Дадаць скарбніцу ## New +addvaultwizard.new.title=Дадаць новую скарбніцу ### Name addvaultwizard.new.nameInstruction=Абраць назву для скарбніцы addvaultwizard.new.namePrompt=Назва скарбніцы @@ -59,6 +63,11 @@ addvaultwizard.new.validCharacters.chars=Слоўныя знакі, накшта addvaultwizard.new.validCharacters.numbers=Лічбы addvaultwizard.new.validCharacters.dashes=Злучок (%s) або падкрэслінік (%s) ### Expert Settings +addvaultwizard.new.expertSettings.enableExpertSettingsCheckbox=Актываваць экспэртныя функцыі +addvaultwizard.new.expertSettings.shorteningThreshold.invalid=Увядзі значэнне ад 36 да 220 (прадвызначана 220) +addvaultwizard.new.expertSettings.shorteningThreshold.tooltip=Адкрый дакумэнтацыю, каб атрымаць больш інфармацыі. +addvaultwizard.new.expertSettings.shorteningThreshold.title=Максымальная даўжыня назваў зашыфраваных файлаў +addvaultwizard.new.expertSettings.shorteningThreshold.valid=Дзейсны ### Password addvaultwizard.new.createVaultBtn=Стварыць скарбніцу addvaultwizard.new.generateRecoveryKeyChoice=Бяз гэтага пароля ты ня зможаш атрымаць доступ да сваіх даных. Ці хочаш ты мець ключ аднаўлення на выпадак, калі ты згубіш свой пароль? @@ -82,6 +91,7 @@ addvault.new.readme.accessLocation.2=Гэта месца дуступу да т addvault.new.readme.accessLocation.3=Любы дададзены сюды файл будзе зашыфраваны праз Cryptomator. Ты можаш працаваць тут як са звычайнаю тэчкаю альбо дыскам. Гэта толькі расшыфраваны агляд кантэнту, самі файлы заўжды захоўваюцца зашыфраванымі на тваім цвёрдым дыску. addvault.new.readme.accessLocation.4=Ты можаш выдаліць гэты файл. ## Existing +addvaultwizard.existing.title=Дадаць існуючую скрабніцу addvaultwizard.existing.instruction=Абяры файл "vault.cryptomator" у існуючай скарбніцы. Калі існуе толькі файл "masterkey.cryptomator", абяры яго. addvaultwizard.existing.chooseBtn=Абраць… addvaultwizard.existing.filePickerTitle=Абяры файл скарбніцы @@ -126,6 +136,7 @@ unlock.success.revealBtn=Паказаць дыск unlock.error.customPath.message=Не магчыма змантажаваць скарбніцу да карыстальніцкай сцежкі unlock.error.customPath.description.notSupported=Калі ты надалей жадаеш карыстацца адмысловаю сцежкаю, калі ласка, пайдзі ў налады ды абары там тып тому, які падтрымлівае яе. У іншым выпадку пайдзі ў опцыі скарбніцы ды абяры там пункт мантажавання, які падтрымліваецца. unlock.error.customPath.description.notExists=Адмысловая сцежка мантажавання не існуе. Ствары яе ў сваёй файлавай сістэме, альбо змяні яе ў опцыях скарбніцы. +unlock.error.customPath.description.inUse=Дыскавая літара або карыстальніцкая мантажная сьцежка "%s" ужо выкарыстоўваецца. ## Hub hub.noKeychain.message=Няма доступу да ключа прылады hub.noKeychain.description=Каб разамкнуць скарбніцы Hub, патрэбны ключ прылады, які захаваны ў звязку ключоў. Каб працягнуць, уключы "%s" ды абяры звязак ключоў у наладах. @@ -138,8 +149,7 @@ hub.auth.loginLink=Не перанакіраваўся? Пстрыкні сюд hub.receive.message=Апрацоўка адказу… hub.receive.description=Cryptomator атрымлівае ды апрацоўвае адказ ад Hub. Калі ласка, пачакай. ### Register Device -hub.register.message=Патрабуецца назва прылады -hub.register.description=Здаецца, што ты ў першы раз увайшла/-оў у Hub з гэтай прылады. Каб ідэнтыфікаваць яе для спраўджання доступу, табе трэба назваць гэтую прыладу. +hub.register.message=Новая прылада hub.register.nameLabel=Назва прылады hub.register.occupiedMsg=Назва ўжо ўжытая hub.register.registerBtn=Пацвердзіць @@ -152,6 +162,7 @@ hub.registerFailed.description=Падчас прысваення імя адбы ### Unauthorized hub.unauthorized.message=Адмова ў доступе hub.unauthorized.description=Тваёй прыладзе ў дадзены момант не дазволена мець доступ да гэтай скрабніцы. Запытайся ўладальніка скрабніцы за дазволам. +### Requires Account Initialization ### License Exceeded hub.invalidLicense.message=Несапраўдная ліцэнзія Hub hub.invalidLicense.description=Твая інстанцыя Cryptomator Hub мае некарэктную ліцэнзію. Калі ласка, паведамі адміністратару Hub пра гэта, каб абнавіць альбо аднавіць ліцэнзію. @@ -351,6 +362,8 @@ main.vaultlist.contextMenu.unlockNow=Разамкнуць зараз main.vaultlist.contextMenu.vaultoptions=Паказаць параметры скарбніцы main.vaultlist.contextMenu.reveal=Паказаць дыск main.vaultlist.addVaultBtn=Дадаць +main.vaultlist.addVaultBtn.menuItemNew=Новая скарбніца... +main.vaultlist.addVaultBtn.menuItemExisting=Існуючая скрабніца... ## Vault Detail ### Welcome main.vaultDetail.welcomeOnboarding=Дзякуй, што ты абраў Cryptomator для абароны тваіх файлаў. Калі табе патрэбна дапамога, калі ласка, паглядзі нашы інструкцыі: @@ -434,6 +447,7 @@ vaultOptions.masterkey.showRecoveryKeyBtn=Паказаць ключ аднаўл vaultOptions.masterkey.recoverPasswordBtn=Скінуць пароль ## Hub vaultOptions.hub=Аднаўленне +vaultOptions.hub.convertBtn=Сканвэртаваць у скарбніцу з паролем # Recovery Key ## Display Recovery Key @@ -485,4 +499,9 @@ quit.forced.message=Некаторыя скарбніцы не магчыма з quit.forced.description=Замыканне скарбніц было заблакавана праз дзеючыя аперацыі альбо праз адчыненыя файлы. Ты можаш прымусова замкнуць скарбніцы, але гэта можа прывесці да страты незахаваных даных. quit.forced.forceAndQuitBtn=Прымусіць і выйсці -# Update Reminder \ No newline at end of file +# Update Reminder +updateReminder.title=Праверка абнаўленняў +updateReminder.message=Ці праверыць абнаўленні? +updateReminder.notNow=Не цяпер +updateReminder.yesOnce=Так, аднойчы +updateReminder.yesAutomatically=Так, аўтаматычна \ No newline at end of file diff --git a/src/main/resources/i18n/strings_bg.properties b/src/main/resources/i18n/strings_bg.properties index 172b27b5c..6ceb6c3bb 100644 --- a/src/main/resources/i18n/strings_bg.properties +++ b/src/main/resources/i18n/strings_bg.properties @@ -20,6 +20,11 @@ error.description=Това е неочаквано за Криптоматор. error.hyperlink.lookup=Търсене на грешката error.hyperlink.report=Докладване на грешката error.technicalDetails=Подробности: +error.existingSolutionDescription=Това е неочаквано за Криптоматор, но открихме съществуващо решение. Прочетете повече на следната препратка. +error.hyperlink.solution=Преглеждане на решението +error.lookupPermissionMessage=Криптоматор може да потърси решение за проблема. За целта ще направи заявка към базата от знания от вашия IP адрес. +error.dismiss=Отхвърляне +error.lookUpSolution=Търсене на решение # Defaults defaults.vault.vaultName=Хранилище @@ -36,6 +41,7 @@ traymenu.vault.reveal=Разкриване # Add Vault Wizard addvaultwizard.title=Добавяне на хранилище ## New +addvaultwizard.new.title=Добавяне на хранилище ### Name addvaultwizard.new.nameInstruction=Изберете име на хранилището addvaultwizard.new.namePrompt=Наименование @@ -57,12 +63,18 @@ addvaultwizard.new.validCharacters.chars=Букви (напр. a, ж или 수) addvaultwizard.new.validCharacters.numbers=Числа addvaultwizard.new.validCharacters.dashes=Тире (%s) или долна черта (%s) ### Expert Settings +addvaultwizard.new.expertSettings.enableExpertSettingsCheckbox=Разширени настройки +addvaultwizard.new.expertSettings.shorteningThreshold.invalid=Въведете стойност между 36 и 220 (по подразбиране 200) +addvaultwizard.new.expertSettings.shorteningThreshold.tooltip=Прочетете повече в документацията. +addvaultwizard.new.expertSettings.shorteningThreshold.title=Максимална дължина на шифрованите имена на файлове +addvaultwizard.new.expertSettings.shorteningThreshold.valid=Валидно ### Password addvaultwizard.new.createVaultBtn=Създаване addvaultwizard.new.generateRecoveryKeyChoice=Без парола няма да имате достъп до данните си. Желаете ли да бъде създаден ключ за възстановяване, в случай че загубите паролата си? addvaultwizard.new.generateRecoveryKeyChoice.yes=Да, нека имам за всеки случай addvaultwizard.new.generateRecoveryKeyChoice.no=Не, няма да загубя паролата си ### Information +addvault.new.readme.storageLocation.fileName=ВАЖНО.rtf addvault.new.readme.storageLocation.1=⚠️ ФАЙЛОВЕ НА ХРАНИЛИЩЕ ⚠️ addvault.new.readme.storageLocation.2=Това е местоположението на хранилището. addvault.new.readme.storageLocation.3=НЕДЕЙТЕ @@ -73,12 +85,14 @@ 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.fileName=ДОБРЕ ДОШЛИ.rtf 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.title=Добавяне на съществуващо хранилище +addvaultwizard.existing.instruction=Изберете файла „vault.cryptomator“ от съществуващото хранилище, но ако има само файл „masterkey.cryptomator“, изберете него. addvaultwizard.existing.chooseBtn=Избиране… addvaultwizard.existing.filePickerTitle=Избор на файл на хранилището addvaultwizard.existing.filePickerMimeDesc=Хранилище на Криптоматор @@ -122,6 +136,11 @@ unlock.success.revealBtn=Разкриване на диска unlock.error.customPath.message=Хранилището не може да бъде монтирано в потребителския път unlock.error.customPath.description.notSupported=Ако искате да продължите да използвате потребитрлския път, отидете в настройките и изберете вид на дял, който поддържа потребителски пътища. В противен случай отидете в настройките на хранилището и изберете поддържана точка за монтиране. unlock.error.customPath.description.notExists=Потребителският път на монтиране не съществува. Създайте го в местната файлова система или го променете в настройките на хранилището. +unlock.error.customPath.description.inUse=Буквата на диска или потребителският път на монтиране „%s“ е зает. +unlock.error.customPath.description.hideawayNotDir=Временният, скрит файл „%3$s“, използван за отключване не може да бъде премахнат. Проверете файла и го изтройте ръчно. +unlock.error.customPath.description.couldNotBeCleaned=Хранилището не може да бъде монтирано на „%s“. Опитайте отново или изберете друг път. +unlock.error.customPath.description.notEmptyDir=Потребителският път на монтиране „%s“ не е празна папка. Изберете празна папка и опитайте отново. +unlock.error.customPath.description.generic=Избрали сте потребителски път за монтиране на това хранилище, но при използването му възникна следната грешка: %2$s ## Hub hub.noKeychain.message=Няма достъп до ключа на устройството hub.noKeychain.description=За да отключите хранилищата в Hub е необходим ключ за устройството, който се защитава с помощта на ключодържател. За да продължите, разрешете „%s“ и изберете ключодържателя в настройките. @@ -134,9 +153,10 @@ hub.auth.loginLink=Не сте пренасочени? Щракнете тук, hub.receive.message=Обработване на отговора… hub.receive.description=Криптоматор получава и обработва отговора от Hub. Изчакайте. ### Register Device -hub.register.message=Изисква се име на устройство -hub.register.description=Изглежда, че това е първи достъп до Hub от това устройство. За да го разпознаете при разрешаване на достъпа, трябва да му дадете име. +hub.register.message=Ново устройство +hub.register.description=За пръв път посещавате Hub от това устройство. Удостоверете го, като използвате ключа на профила. hub.register.nameLabel=Име на устройството +hub.register.invalidAccountKeyLabel=Неприемлив ключ на профила hub.register.occupiedMsg=Това име вече е заето hub.register.registerBtn=Потвърждаване ### Registration Success @@ -148,31 +168,78 @@ hub.registerFailed.description=В процеса на именуване е до ### Unauthorized hub.unauthorized.message=Отказан достъп hub.unauthorized.description=Устройството не е упълномощено за достъп до това хранилище. Поискайте достъп от собственика. +### Requires Account Initialization ### License Exceeded hub.invalidLicense.message=Лиценза за Hub е недействителен +hub.invalidLicense.description=Лиценза на екземпляра на Концентратора на Криптоматор който вие използвате е лиценз. Информирайте администратора на Концентратора, за да поднови или надгради лиценза. # Lock ## Force +lock.forced.message=Грешка при заключване +lock.forced.description=Хранилището „%s“ не може да бъде заключено, поради извършващо се действие или отворен файл. Можете принудително да го заключите, но прекъсването на действия на четене/писане в хранилището може да доведе до загуба на незапазени данни. lock.forced.retryBtn=Повторен опит +lock.forced.forceBtn=Принудително заключване ## Failure +lock.fail.message=Грешка при заключване на хранилището +lock.fail.description=Хранилището „%s“ не може да бъде заключено. Уверете се, че разполагате другаде с назапазената информация и че действията на четене/писане в хранилището са завършили. За да бъде затворено хранилището, трябва процесът на Криптоматор да бъда прекъснат. # Migration +migration.title=Надграждане на хранилище ## Start +migration.start.header=Надграждане на хранилището +migration.start.text=За да може това ново издание на Криптоматор да отвори хранилището „%s“, то трябва да бъде надстроено до новия формат. Преди да го надстроите, трябва да знаете следното: +migration.start.remarkUndone=Надграждането е еднопосочен процес. +migration.start.remarkVersions=По-ранните издания на Криптоматор няма да могат да отварят надградените хранилища. +migration.start.remarkCanRun=Трябва да сте сигурни, че всички устройства от които достъпвате хранилището могат да работят с това издание на Криптоматор. +migration.start.remarkSynced=Трябва да сте сигурни, че хранилището е напълно синхронизирано между всички устройства, преди да пристъпите към надграждане. +migration.start.confirm=Прочетох и разбрах гореизложената информация ## Run +migration.run.enterPassword=Въведете паролата за „%s“ +migration.run.startMigrationBtn=Надграждане на хранилището +migration.run.progressHint=Ще отнеме малко време… ## Success +migration.success.nextStepsInstructions=Хранилището „%s“ е надградено.\nМожете да го отключите. migration.success.unlockNow=Отключване сега ## Missing file system capabilities migration.error.missingFileSystemCapabilities.title=Неподдържана файлова система +migration.error.missingFileSystemCapabilities.description=Не е започнато надграждане, защото хранилището се намира на неподдържана файлова система. +migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Файловата система не поддържа дълги имена на файлове. +migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Файловата система не поддържа дълги пътища. +migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=Файловата система не дава права за четене. +migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=Файловата система не дава права за писане. ## Impossible +migration.impossible.heading=Хранилището не е надградено +migration.impossible.reason=Хранилището не може да бъде надградено автоматично, защото местоположението или точката за достъп са несъвместими. +migration.impossible.moreInfo=Хранилището миже да бъде отворено с по-ранно издание на Криптоматор. За инструкции как ръчно да надстроите хранилището постете # Health Check ## Start health.title=Проверка на състоянието на „%s“ health.intro.header=Проверка на състоянието health.intro.text=Проверката на състоянието е набор от проверки и евентуално поправки за проблеми във вътрешната структура на хранилището. Имайте предвид, че: +health.intro.remarkSync=Трябва да сте сигурни, че хранилището е напълно синхронизирано между всички устройства, по този начин ще резрешите повечето проблеми. +health.intro.remarkFix=Не всички проблеми могат да бъдат разрешени. +health.intro.remarkBackup=Ако данните са повредени, само наличие на резервно копие ще помогне. +health.intro.affirmation=Прочетох и разбрах гореизложената информация ## Start Failure +health.fail.header=Грешка при зареждане на настройките на хранилището +health.fail.ioError=Грешка при достъп или четене на файла с настройките. +health.fail.parseError=Грешка при разбор на файла с настройките на хранилището. +health.fail.moreInfo=Повече информация ## Check Selection +health.checkList.description=Използвайте отметките отляво или бутоните отдолу. +health.checkList.selectAllButton=Избор всичко +health.checkList.deselectAllButton=Избор нищо +health.check.runBatchBtn=Извършване на избраните ## Detail view +health.check.detail.noSelectedCheck=За резултати изберете завършила проверка от списъка от ляво. +health.check.detail.checkScheduled=Извършването на проверката предстои. +health.check.detail.checkRunning=Проверката в момента се извършва… +health.check.detail.checkSkipped=Проверката не е избрана за извършване. +health.check.detail.checkFinished=Проверката е завършена. +health.check.detail.checkFinishedAndFound=Проверката е извършена. Прегледайте резултатите. +health.check.detail.checkFailed=Проверката не е завършена поради грешка. +health.check.detail.checkCancelled=Проверката е прекъсната. health.check.detail.listFilters.label=Филтър health.check.detail.fixAllSpecificBtn=Поправка всички от вида health.check.exportBtn=Отчет @@ -181,79 +248,269 @@ health.result.severityFilter.all=Сериозност - Всички health.result.severityFilter.good=Добре health.result.severityFilter.info=Информация health.result.severityFilter.warn=Предупреждение -health.result.severityFilter.crit=Криточно +health.result.severityFilter.crit=Критично +health.result.severityTip.good=Сериозност: Добре\nСтруктурата на хранилището е наред. +health.result.severityTip.info=Сериозност: Информация\nСтруктурата на хранилището е непокътната, препоръчва се поправка. +health.result.severityTip.warn=Сериозност: Предупреждение\nСтруктурата на хранилището е нарушена, поправката е силно препоръчителна. +health.result.severityTip.crit=Сериозност: Критично\nСтруктурата на хранилището е нарушена, установена е загуба на информация. +health.result.fixStateFilter.all=Поправимост - Всички +health.result.fixStateFilter.fixable=Поправими +health.result.fixStateFilter.notFixable=Непоправими +health.result.fixStateFilter.fixing=Поправяне… +health.result.fixStateFilter.fixed=Поправени +health.result.fixStateFilter.fixFailed=Грешка при поправка ## Fix Application +health.fix.fixBtn=Поправяне +health.fix.successTip=Поправката е успешна +health.fix.failTip=Грешка при поправката, вижте дневника за подробности # Preferences preferences.title=Настройки ## General +preferences.general=Общи +preferences.general.startHidden=Скриване на прозореца при отваряне на Криптоматор +preferences.general.autoCloseVaults=Заключване на хранилищата при затваряне на приложението +preferences.general.debugLogging=Дневник за отстраняване на дефекти +preferences.general.debugDirectory=Файлове на дневниците +preferences.general.autoStart=Отваряне на Криптоматор при старт на системата +preferences.general.keychainBackend=Съхраняванеа паролите в ## Interface -preferences.interface.theme=Оформление +preferences.interface=Външен вид +preferences.interface.theme=Тема preferences.interface.theme.automatic=Автоматично +preferences.interface.theme.dark=Тъмна +preferences.interface.theme.light=Светла +preferences.interface.unlockThemes=Отключване на тъмната тема +preferences.interface.language=Език (изисква рестарт) +preferences.interface.language.auto=Подразбиран +preferences.interface.interfaceOrientation=Посока на текста +preferences.interface.interfaceOrientation.ltr=От ляво на дясно +preferences.interface.interfaceOrientation.rtl=От дясно на ляво +preferences.interface.showMinimizeButton=Бутон за скриване +preferences.interface.showTrayIcon=Икона в областта за известия (изисква рестарт) ## Volume +preferences.volume=Виртуален диск +preferences.volume.type=Вид на тома preferences.volume.type.automatic=Автоматично +preferences.volume.docsTooltip=Вижте документацията относно видовете томове. +preferences.volume.fuseRestartRequired=За да бъдат приложени промените, Криптоматор трябва да бъде рестартиран. +preferences.volume.tcp.port=Порт на TCP +preferences.volume.supportedFeatures=Избрания вид на тома има следните възможности: +preferences.volume.feature.mountAuto=Автоматичен избор на точка за монтиране +preferences.volume.feature.mountToDir=Папка по избор като точка за монтиране +preferences.volume.feature.mountToDriveLetter=Буква на диск като точка за монтиране +preferences.volume.feature.mountFlags=Потребителски настройки на монтиране +preferences.volume.feature.readOnly=Монтиране само за четене ## Updates +preferences.updates=Издания +preferences.updates.currentVersion=Текущо издание: %s +preferences.updates.autoUpdateCheck=Автоматична проверка за издания +preferences.updates.checkNowBtn=Проверяване +preferences.updates.updateAvailable=Налично е ново издание %s. ## Contribution +preferences.contribute=Подкрепете ни +preferences.contribute.registeredFor=Сертификатът за дарение е регистриран на %s +preferences.contribute.noCertificate=Ако подкрепите Криптоматор ще получите сертификат за дарение. Това е като лицензен ключ, но за готини хора, които използват безплатен софтуер. ;-) +preferences.contribute.getCertificate=Все още нямате? Научете как да се сдобиете. +preferences.contribute.promptText=Поставете тук кода на сертификата за дарение #<-- Add entries for donations and code/translation/documentation contribution --> ## About +preferences.about=Относно # Vault Statistics +stats.title=Статистика на %s +stats.cacheHitRate=Съотношения на попадения ## Read +stats.read.throughput.idle=Четене: бездействие +stats.read.throughput.kibs=Четене: %.2f КБ/с +stats.read.throughput.mibs=Четене: %.2f МБ/с +stats.read.total.data.none=Прочетени данни: - +stats.read.total.data.kib=Прочетени данни: %.1f КБ +stats.read.total.data.mib=Прочетени данни: %.1f МБ +stats.read.total.data.gib=Прочетени данни: %.1f ГБ +stats.decr.total.data.none=Разшифровани данни: - +stats.decr.total.data.kib=Разшифровани данни: %.1f КБ +stats.decr.total.data.mib=Разшифровани данни: %.1f МБ +stats.decr.total.data.gib=Разшифровани данни: %.1f ГБ +stats.read.accessCount=Общо прочитания: %d ## Write +stats.write.throughput.idle=Писане: бездействие +stats.write.throughput.kibs=Писане: %.2f КБ/с +stats.write.throughput.mibs=Писане: %.2f МБ/с +stats.write.total.data.none=Записани данни: - +stats.write.total.data.kib=Записани данни: %.1f КБ +stats.write.total.data.mib=Записани данни: %.1f МБ +stats.write.total.data.gib=Записани данни: %.1f ГБ +stats.encr.total.data.none=Шифровани данни: - +stats.encr.total.data.kib=Шифровани данни: %.1f КБ +stats.encr.total.data.mib=Шифровани данни: %.1f МБ +stats.encr.total.data.gib=Шифровани данни: %.1f ГБ +stats.write.accessCount=Общо записвания: %d ## Accesses +stats.access.current=Достъпване: %d +stats.access.total=Общо достъпвания: %d # Main Window main.closeBtn.tooltip=Затваряне +main.minimizeBtn.tooltip=Скриване main.preferencesBtn.tooltip=Настройки +main.debugModeEnabled.tooltip=Режимът за премахване на дефекти е включен +main.supporterCertificateMissing.tooltip=Обмислете дарение ## Vault List +main.vaultlist.emptyList.onboardingInstruction=Щракнете, за да добавите хранилище +main.vaultlist.contextMenu.remove=Премахване… main.vaultlist.contextMenu.lock=Заключване main.vaultlist.contextMenu.unlock=Отключване… main.vaultlist.contextMenu.unlockNow=Отключване сега main.vaultlist.contextMenu.vaultoptions=Настройки на хранилището main.vaultlist.contextMenu.reveal=Разкриване на диска +main.vaultlist.addVaultBtn=Добавяне +main.vaultlist.addVaultBtn.menuItemNew=Ново хранилище… +main.vaultlist.addVaultBtn.menuItemExisting=Съществуващо хранилище… ## Vault Detail ### Welcome +main.vaultDetail.welcomeOnboarding=Благодарим ви, че избрахте Криптоматор, за да предпазвате файловете си. Ако имате нужда от съдействие прочетете ръководствата за започване на работа с приложението: ### Locked +main.vaultDetail.lockedStatus=ЗАКЛЮЧЕНО main.vaultDetail.unlockBtn=Отключване… main.vaultDetail.unlockNowBtn=Отключване сега +main.vaultDetail.optionsBtn=Настройки на хранилището +main.vaultDetail.passwordSavedInKeychain=Паролата е запазена ### Unlocked main.vaultDetail.unlockedStatus=ОТКЛЮЧЕНО +main.vaultDetail.accessLocation=Съдържанието на хранилището е достъпно тук: main.vaultDetail.revealBtn=Разкриване на диска +main.vaultDetail.copyUri=Копиране на URI main.vaultDetail.lockBtn=Заключване +main.vaultDetail.bytesPerSecondRead=Четене: +main.vaultDetail.bytesPerSecondWritten=Писане: +main.vaultDetail.throughput.idle=бездействие +main.vaultDetail.throughput.kbps=%.1f КБ/с +main.vaultDetail.throughput.mbps=%.1f МБ/с +main.vaultDetail.stats=Статистики на хранилището +main.vaultDetail.locateEncryptedFileBtn=Намиране на шифровани файлове +main.vaultDetail.locateEncryptedFileBtn.tooltip=Изберете файл от хранилището, за да бъде намерено шифрованото му копие +main.vaultDetail.encryptedPathsCopied=Пътищата са копирани! +main.vaultDetail.filePickerTitle=Изберете файл от хранилището ### Missing +main.vaultDetail.missing.info=Криптоматор не намира хранилище на това място. +main.vaultDetail.missing.recheck=Повторен опит +main.vaultDetail.missing.remove=Премахване от списъка с хранилищата… +main.vaultDetail.missing.changeLocation=Смяна на мястото на хранилището… ### Needs Migration +main.vaultDetail.migrateButton=Надграждане на хранилище +main.vaultDetail.migratePrompt=Преди да можете да го достъпвате, хранилището трябва да бъде надградено до новия формат ### Error +main.vaultDetail.error.info=Грешка при зареждане на хранилището от диска. +main.vaultDetail.error.reload=Презареждане +main.vaultDetail.error.windowTitle=Грешка при зареждане на хранилще # Wrong File Alert +wrongFileAlert.title=Как да шифровате файлове +wrongFileAlert.message=Пробвахте ли да шифровате файловете? +wrongFileAlert.description=За тази цел Криптоматор създава том, достъпен от приложението за управление на файлове. +wrongFileAlert.instruction.0=За да шифровета файлове следвайте тези стъпки: +wrongFileAlert.instruction.1=1. Отключете хранилището. +wrongFileAlert.instruction.2=2. Щракнете върху „Разкриване“, за да се покаже хранилището. +wrongFileAlert.instruction.3=3. Добавете файловете в тома. +wrongFileAlert.link=За допълнителна помощ, посетете # Vault Options ## General +vaultOptions.general=Общи vaultOptions.general.vaultName=Наименование +vaultOptions.general.autoLock.lockAfterTimePart1=Заключване при бездействие от +vaultOptions.general.autoLock.lockAfterTimePart2=минути +vaultOptions.general.unlockAfterStartup=Отключване на хранилището при старт на Криптоматор +vaultOptions.general.actionAfterUnlock=След отключване +vaultOptions.general.actionAfterUnlock.ignore=Без действие vaultOptions.general.actionAfterUnlock.reveal=Разкриване на диска vaultOptions.general.actionAfterUnlock.ask=Запитване +vaultOptions.general.startHealthCheckBtn=Проверка на състоянието ## Mount +vaultOptions.mount=Монтиране +vaultOptions.mount.info=Възможностите зависят от вида на избрания том. +vaultOptions.mount.linkToPreferences=Настройки на виртуалния диск +vaultOptions.mount.readonly=Само за четене +vaultOptions.mount.customMountFlags=Флагове на монтиране +vaultOptions.mount.winDriveLetterOccupied=използвана +vaultOptions.mount.mountPoint=Точка на монтиране +vaultOptions.mount.mountPoint.auto=Автоматично избиране на подходящо място +vaultOptions.mount.mountPoint.driveLetter=Използване на определената буква на диска +vaultOptions.mount.mountPoint.custom=Използване на избрана папка vaultOptions.mount.mountPoint.directoryPickerButton=Избиране… +vaultOptions.mount.mountPoint.directoryPickerTitle=Избиране на папка ## Master Key +vaultOptions.masterkey=Парола vaultOptions.masterkey.changePasswordBtn=Промяна на парола +vaultOptions.masterkey.forgetSavedPasswordBtn=Забравяне на запазената парола +vaultOptions.masterkey.recoveryKeyExplanation=Ключът за възстановяване е единствения начин да си върнете достъпа да хранилището ако загубите паролата. +vaultOptions.masterkey.showRecoveryKeyBtn=Показване на ключ за възстановяване +vaultOptions.masterkey.recoverPasswordBtn=Нулиране на паролата ## Hub +vaultOptions.hub=Възстановяване +vaultOptions.hub.convertInfo=В случай на спешност можете да използвате ключа за възстановяване, за да преобразувате това хранилище от хранилище от концентратор в хранилище с парола. +vaultOptions.hub.convertBtn=Преобразуване в хранилище с парола # Recovery Key ## Display Recovery Key +recoveryKey.display.title=Показване на ключ за възстановяване +recoveryKey.create.message=Изисква се парола +recoveryKey.create.description=За да видите ключа за възстановяване въведете паролата за „%s“. +recoveryKey.display.description=Следния ключ за възстановязане може да бъде използван да си върнете достъпа до „%s“: +recoveryKey.display.StorageHints=Пазете го на много сигурно място, например:\n • Пазете го в софтуер за управление на пароли\n • Запишете го на USB памет\n • Отпечатайте го на хартия ## Reset Password ### Enter Recovery Key +recoveryKey.recover.title=Нулиране на паролата +recoveryKey.recover.prompt=Въведете ключа за възстановяване на „%s“: +recoveryKey.recover.correctKey=Верен ключ за възстановяване +recoveryKey.recover.wrongKey=Този ключ за възстановяване принадлежи на друго хранилище +recoveryKey.recover.invalidKey=Неприемлив ключ за възстановяване +recoveryKey.printout.heading=Ключ за възстановяване на Криптоматор\n„%s“\n ### Reset Password +recoveryKey.recover.resetBtn=Нулиране ### Recovery Key Password Reset Success +recoveryKey.recover.resetSuccess.message=Паролата е променена +recoveryKey.recover.resetSuccess.description=Можете да отключите хранилището с новата парола. # Convert Vault +convertVault.title=Преобразуване на хранилище +convertVault.convert.convertBtn.before=Преобразуване +convertVault.convert.convertBtn.processing=Преобразуване… +convertVault.success.message=Преобразуване е успешно +convertVault.hubToPassword.success.description=Можете да отключите хранилището с избраната парола без необходимост от достъп до Концентратор. # New Password +newPassword.promptText=Въведете нова парола +newPassword.reenterPassword=Потвърдете новата парола +newPassword.passwordsMatch=Паролите съвпадат! +newPassword.passwordsDoNotMatch=Паролите не съвпадат +passwordStrength.messageLabel.tooShort=Използвайте минимум %d знака +passwordStrength.messageLabel.0=Много слаба +passwordStrength.messageLabel.1=Слаба +passwordStrength.messageLabel.2=Задоволителна +passwordStrength.messageLabel.3=Добра +passwordStrength.messageLabel.4=Много добра # Quit +quit.title=Изход от приложението +quit.message=Има отключени хранилища +quit.description=Потвърдете излизане от приложението. За да предотврати загуба на и формация Криптоматор ще заключи отворените хранилища. +quit.lockAndQuitBtn=Заключване и изход # Forced Quit +quit.forced.message=Някои от хранилищата не могат да бъдат заключени +quit.forced.description=Хранилището не може да бъде заключено, поради извършващо се действие или отворен файл. Можете принудително да заключите останалите хранилища, но прекъсването на входно-изходни процеси може да доведе до загуба на незапазени данни. +quit.forced.forceAndQuitBtn=Прекъсване и изход -# Update Reminder \ No newline at end of file +# Update Reminder +updateReminder.title=Проверка за обновяване +updateReminder.message=Проверяване за обновяване? +updateReminder.description=Получавайте нови възможности на приложението, поправки на дефекти и подобрения на сигурността. Препоръчваме ви автоматичното обновяване. +updateReminder.notNow=Не сега +updateReminder.yesOnce=Да, веднъж +updateReminder.yesAutomatically=Да, автоматично \ No newline at end of file diff --git a/src/main/resources/i18n/strings_bn.properties b/src/main/resources/i18n/strings_bn.properties index 85b748f87..b68c0528d 100644 --- a/src/main/resources/i18n/strings_bn.properties +++ b/src/main/resources/i18n/strings_bn.properties @@ -93,6 +93,7 @@ hub.register.registerBtn=নিশ্চিত করুন ### Registration Success ### Registration Failed ### Unauthorized +### Requires Account Initialization ### License Exceeded # Lock diff --git a/src/main/resources/i18n/strings_bs.properties b/src/main/resources/i18n/strings_bs.properties index f884e5a50..61990d5fb 100644 --- a/src/main/resources/i18n/strings_bs.properties +++ b/src/main/resources/i18n/strings_bs.properties @@ -109,6 +109,7 @@ hub.register.registerBtn=Potvrdi ### Registration Success ### Registration Failed ### Unauthorized +### Requires Account Initialization ### License Exceeded # Lock diff --git a/src/main/resources/i18n/strings_ca.properties b/src/main/resources/i18n/strings_ca.properties index c5829afae..c43c2422c 100644 --- a/src/main/resources/i18n/strings_ca.properties +++ b/src/main/resources/i18n/strings_ca.properties @@ -22,6 +22,9 @@ error.hyperlink.report=Notifica aquest error error.technicalDetails=Detalls: error.existingSolutionDescription=Cryptomator no esperava que això ocorreguera. Però hem trobat una solució per a aquest error. Per favor, done una ullada al següent enllaç. error.hyperlink.solution=Buscar la solució +error.lookupPermissionMessage=Cryptomator pot cercar una solució a aquest problema en línia. S'enviarà una petició a la nostra base de dades de problemes des de la teva adreça IP. +error.dismiss=Descartar +error.lookUpSolution=Buscar Solució # Defaults defaults.vault.vaultName=Caixa forta @@ -38,6 +41,7 @@ traymenu.vault.reveal=Mostra # Add Vault Wizard addvaultwizard.title=Afegir una caixa forta ## New +addvaultwizard.new.title=Afegeix nova caixa forta ### Name addvaultwizard.new.nameInstruction=Introduiu el nom de la caixa forta addvaultwizard.new.namePrompt=Nom de la caixa forta @@ -59,6 +63,11 @@ addvaultwizard.new.validCharacters.chars=Caràcters que formen paraules (ex.: a, addvaultwizard.new.validCharacters.numbers=Nombres addvaultwizard.new.validCharacters.dashes=Guionet (%s) o guió baix (%s) ### Expert Settings +addvaultwizard.new.expertSettings.enableExpertSettingsCheckbox=Habilitar mode expert +addvaultwizard.new.expertSettings.shorteningThreshold.invalid=Introduir un valor entre 36 i 220 (per defecte 220) +addvaultwizard.new.expertSettings.shorteningThreshold.tooltip=Obre la documentació per saber-ne més. +addvaultwizard.new.expertSettings.shorteningThreshold.title=Llargària màxima dels fitxers xifrats +addvaultwizard.new.expertSettings.shorteningThreshold.valid=Vàlid ### Password addvaultwizard.new.createVaultBtn=Crea la caixa forta addvaultwizard.new.generateRecoveryKeyChoice=No podreu accedir a les vostres dades sense la contrasenya. Voleu crear una clau de recuperació en cas perdre la vostra contrasenya? @@ -82,6 +91,7 @@ addvault.new.readme.accessLocation.2=Aquesta és la ubicació d'accès de la vos addvault.new.readme.accessLocation.3=Cryptomator xifra tots els fitxers afegits a aquest volum. Podeu treballar en aquest volum com en qualsevol altra unitat o carpeta. La vista mostra el contingut desxifrat però els fitxers sempre estan xifrats en el vostre disc dur. addvault.new.readme.accessLocation.4=Pots esborrar aquest fitxer si vols. ## Existing +addvaultwizard.existing.title=Afegeix una caixa forta existent addvaultwizard.existing.instruction=Selecciona el fitxer "vault.cryptomator" de la teva caixa forta. Si només existeix un fitxer anomenat "masterkey.cryptomator", selecciona aquest. addvaultwizard.existing.chooseBtn=Trieu… addvaultwizard.existing.filePickerTitle=Selecciona el fitxer de la Caixa forta @@ -126,6 +136,11 @@ unlock.success.revealBtn=Mostra la unitat unlock.error.customPath.message=No es pot muntar la caixa forta en la ruta personalitzada unlock.error.customPath.description.notSupported=Si vol continuar fent servir una ruta personalitzada, vagi si us plau a Preferències i seleccioni un tipus de volum que la suporti. Altrament, vagi a les opcions de la caixa forta i escolli un punt de muntatge suportat. unlock.error.customPath.description.notExists=La ruta de muntatge personalitzada no existeix. Creï-la o canviï-la en les opcions de la caixa forta. +unlock.error.customPath.description.inUse=La unitat o el punt de muntatge "%s" ja és en ús. +unlock.error.customPath.description.hideawayNotDir=El fitxer temporal "%3$s" utilitzat per desbloquejar no es pot esborrar. Revisi si us plau aquest fitxer i esborri'l manualment. +unlock.error.customPath.description.couldNotBeCleaned=La caixa forta no es pot muntar en "%s". Intenteu-ho de nou o escolliu un destí diferent, si us plau. +unlock.error.customPath.description.notEmptyDir=El punt de muntatge "%s" no és una carpeta buida. Trieu una carpeta buida i proveu-ho de nou. +unlock.error.customPath.description.generic=Al anar a desar la caixa forta en el lloc indicat ha fallat amb el missatge: %2$s ## Hub hub.noKeychain.message=No es pot accedir a la clau del dispositiu hub.noKeychain.description=Per poder desblocar caixes fortes del Hub es requereix la clau d'un dispositiu, que s'emmagatzema de forma segura en un clauer. Per continuar, habiliti "%s" i seleccioni un clauer en les Preferències. @@ -138,8 +153,7 @@ hub.auth.loginLink=No heu estat redirigit? Feu clic aquí per a obrir-la. hub.receive.message=S'està processant la resposta… hub.receive.description=Cryptomator està rebent i processant la resposta del Hub. Espereu, si us plau. ### Register Device -hub.register.message=Cal un nom de dispositiu -hub.register.description=Sembla que és el primer accés al Hub des d'aquest dispositiu. Per identificar-lo i poder autoritzar l'accés necessiteu anomenar aquest dispositiu. +hub.register.message=Nou dispositiu hub.register.nameLabel=Nom del dispositiu hub.register.occupiedMsg=El nom ja està en ús hub.register.registerBtn=Confirma @@ -152,6 +166,7 @@ hub.registerFailed.description=S'ha produït un error en el procés de nomenamen ### Unauthorized hub.unauthorized.message=Accés denegat hub.unauthorized.description=El vostre dispositiu no ha estat encara autoritzat a accedir a aquesta caixa forta. Demaneu autorització al propietari. +### Requires Account Initialization ### License Exceeded hub.invalidLicense.message=La llicència del Hub no és vàlida hub.invalidLicense.description=Aquest Cryptomator Hub no té una llicència vàlida. Informa si us plau a l'administrador perquè actualitzi o renovi la llicència. @@ -273,8 +288,10 @@ preferences.interface.showMinimizeButton=Mostra el botó 'minimitzar' preferences.interface.showTrayIcon=Mostra la icona en la barra (cal reiniciar) ## Volume preferences.volume=Unitat virtual +preferences.volume.type=Tipus de volum preferences.volume.type.automatic=Automàtic preferences.volume.docsTooltip=Obre la documentació per aprendre més sobre els diferents tipus de volums. +preferences.volume.fuseRestartRequired=Per aplicar els canvis Cryptomator necessita reiniciar-se. preferences.volume.tcp.port=Port TCP preferences.volume.supportedFeatures=El tipus de volum escollit suporta les següents característiques: preferences.volume.feature.mountAuto=Selecció automàtica del punt de muntatge @@ -349,6 +366,8 @@ main.vaultlist.contextMenu.unlockNow=Desbloqueja ara main.vaultlist.contextMenu.vaultoptions=Opcions de la caixa forta main.vaultlist.contextMenu.reveal=Mostra la unitat main.vaultlist.addVaultBtn=Afegir +main.vaultlist.addVaultBtn.menuItemNew=Nova caixa forta... +main.vaultlist.addVaultBtn.menuItemExisting=Caixa forta existent... ## Vault Detail ### Welcome main.vaultDetail.welcomeOnboarding=Gràcies per escollir Cryptomator per protegir els vostres fitxers. Si vos cal ajuda, llegiu les nostres guies per donar els Primers passos: @@ -431,6 +450,9 @@ vaultOptions.masterkey.recoveryKeyExplanation=La clau de recuperació és l'unic vaultOptions.masterkey.showRecoveryKeyBtn=Mostra la clau de recuperació vaultOptions.masterkey.recoverPasswordBtn=Canviar contrasenya ## Hub +vaultOptions.hub=Recuperació +vaultOptions.hub.convertInfo=En cas d'emergència pot fer servir la clau de recuperació per convertir aquesta caixa forta de Hub a una caixa forta amb paraula clau. +vaultOptions.hub.convertBtn=Convertir a una caixa forta amb paraula clau # Recovery Key ## Display Recovery Key @@ -442,6 +464,7 @@ recoveryKey.display.StorageHints=Conserveu-la en un lloc molt segur. P. ex.:\n ## Reset Password ### Enter Recovery Key recoveryKey.recover.title=Canviar contrasenya +recoveryKey.recover.prompt=Introduir la clau de recuperació per "%s": recoveryKey.recover.correctKey=La clau de recuperació és vàlida recoveryKey.recover.wrongKey=Aquesta clau de recuperació pertany a una caixa forta diferent recoveryKey.recover.invalidKey=Aquesta clau de recuperació no és vàlida @@ -453,6 +476,11 @@ recoveryKey.recover.resetSuccess.message=S'ha modificat la contrasenya correctam recoveryKey.recover.resetSuccess.description=Pots desbloquejar la caixa forta amb la nova contrasenya. # Convert Vault +convertVault.title=Convertir la caixa forta +convertVault.convert.convertBtn.before=Convertir +convertVault.convert.convertBtn.processing=Convertint… +convertVault.success.message=Conversió correcta +convertVault.hubToPassword.success.description=Ara pot desbloquejar la caixa forta amb la paraula clau sense haver d'accedir al Hub. # New Password newPassword.promptText=Introdueix una contrasenya nova @@ -477,4 +505,10 @@ quit.forced.message=Algunes caixes fortes no s'han pogut bloquejar quit.forced.description=No s'ha pogut blocar la caixa forta perquè hi ha operacions pendents o fitxers oberts. Podeu forçar-ne el blocatge, però heu de saber que interrompre l'entrada/sortida pot produir la pèrdua de dades. quit.forced.forceAndQuitBtn=Forçar i sortir -# Update Reminder \ No newline at end of file +# Update Reminder +updateReminder.title=Comprovar actualitzacions +updateReminder.message=Comprova si hi ha actualitzacions? +updateReminder.description=Per estar actualitzat amb funcions noves, correccions d'errors i millores de seguretat recomanem comprovar les actualitzacions automàticament. +updateReminder.notNow=Ara no +updateReminder.yesOnce=Sí, una vegada +updateReminder.yesAutomatically=Sí, automàticament \ No newline at end of file diff --git a/src/main/resources/i18n/strings_cs.properties b/src/main/resources/i18n/strings_cs.properties index acd8e4473..274f282e7 100644 --- a/src/main/resources/i18n/strings_cs.properties +++ b/src/main/resources/i18n/strings_cs.properties @@ -142,7 +142,6 @@ hub.auth.loginLink=Nebyli jste přesměrováni? Klikněte zde pro otevření. ### Receive Key hub.receive.message=Zpracovávání odezvy… ### Register Device -hub.register.message=Je vyžadován název zařízení hub.register.nameLabel=Název zařízení hub.register.occupiedMsg=Jméno je již používáno hub.register.registerBtn=Potvrdit @@ -154,6 +153,7 @@ hub.registerFailed.message=Pojmenování zařízení se nezdařilo ### Unauthorized hub.unauthorized.message=Přístup odepřen hub.unauthorized.description=Vaše zařízení dosud nebylo oprávněno k přístupu k tomuto trezoru. Požádejte vlastníka trezoru, aby jej autorizoval. +### Requires Account Initialization ### License Exceeded hub.invalidLicense.message=Licence Hubu je neplatná diff --git a/src/main/resources/i18n/strings_da.properties b/src/main/resources/i18n/strings_da.properties index 6b4d0df7a..9b0f5b01f 100644 --- a/src/main/resources/i18n/strings_da.properties +++ b/src/main/resources/i18n/strings_da.properties @@ -22,6 +22,9 @@ error.hyperlink.report=Rapportér denne fejl error.technicalDetails=Detaljer: error.existingSolutionDescription=Cryptomator forventede ikke, at dette ville ske. Men vi har fundet en eksisterende løsning på denne fejl. Tag et kig på følgende link. error.hyperlink.solution=Tjek løsningen +error.lookupPermissionMessage=Cryptomator kan finde en løsning på dette problem online. Dette vil sende en anmodning til vores problemdatabase fra din IP-adresse. +error.dismiss=Luk +error.lookUpSolution=Slå løsning op # Defaults defaults.vault.vaultName=Boks @@ -38,6 +41,7 @@ traymenu.vault.reveal=Vis # Add Vault Wizard addvaultwizard.title=Tilføj boks ## New +addvaultwizard.new.title=Tilføj Ny Boks ### Name addvaultwizard.new.nameInstruction=Vælg et navn til boksen addvaultwizard.new.namePrompt=Boks-navn @@ -87,6 +91,7 @@ addvault.new.readme.accessLocation.2=Det er her indholdet af din boks tilgås. addvault.new.readme.accessLocation.3=Filer du tilføjer til dette drev vil blive krypteret af Cryptomator. Du har arbejde med filerne ligesom enhver anden fil/mappe. Dette er blot en dekrypteret visning af indholdet. Dine filer er stadig krypterede på din harddisk hele tiden. addvault.new.readme.accessLocation.4=Fjern denne fil hvis du har lyst. ## Existing +addvaultwizard.existing.title=Tilføj Eksisterende Boks addvaultwizard.existing.instruction=Vælgt filen "vault.cryptomator" i mappen med dine boks-filer. Hvis der kun findes en fil med navnet "masterkey.cryptomator", skal du vælge den i stedet. addvaultwizard.existing.chooseBtn=Vælg… addvaultwizard.existing.filePickerTitle=Vælg boks-fil @@ -131,6 +136,11 @@ unlock.success.revealBtn=Vis drev unlock.error.customPath.message=Kan ikke montere boks til brugerdefineret sti unlock.error.customPath.description.notSupported=Hvis du ønsker at fortsætte med at bruge den brugerdefinerede sti, skal du gå til præferencer og vælge en type drev der understøtter det. Hvis ikke, skal du gå til boksens indstillinger og vælge et understøttet monteringspunkt. unlock.error.customPath.description.notExists=Den brugerdefinerede monteringssti eksisterer ikke. Opret den enten i dit lokale filsystem eller skift monteringssti i boksens indstillinger. +unlock.error.customPath.description.inUse=Drevbogstavet eller den brugerdefinerede monteringssti "%s" er allerede i brug. +unlock.error.customPath.description.hideawayNotDir=Den midlertidige, skjulte fil "%3$s", der blev brugt til oplåsning, kunne ikke fjernes. Kontroller filen og slet den manuelt. +unlock.error.customPath.description.couldNotBeCleaned=Din boks kunne ikke monteres på stien "%s". Prøv venligst igen eller vælg en anden sti. +unlock.error.customPath.description.notEmptyDir=Den brugerdefinerede monteringssti "%s" er ikke en tom mappe. Vælg venligst en tom mappe og prøv igen. +unlock.error.customPath.description.generic=Du har valgt en brugerdefineret monteringssti til denne boks, men det mislykkedes med følgende besked: %2$s ## Hub hub.noKeychain.message=Kan ikke tilgå enhedsnøgle hub.noKeychain.description=En enhedsnøgle er påkrævet for at låse Hub bokse op. Enhedsnøglen er sikret i en nøglering. For at fortsætte, aktivér “%s” og vælg en nøglering i indstillingerne. @@ -143,8 +153,7 @@ hub.auth.loginLink=Ingen omdirigering? Klik her for at åbne den. hub.receive.message=Behandler svar… hub.receive.description=Cryptomator modtager og behandler svaret fra hubben. Vent venligst. ### Register Device -hub.register.message=Enheds-navn krævet -hub.register.description=Det ser ud til at dette er første gang denne enhed tilgår hubben. For at identificere denne enhed, skal du navngive den. +hub.register.message=Ny Enhed hub.register.nameLabel=Enheds-navn hub.register.occupiedMsg=Navnet er allerede i brug hub.register.registerBtn=Bekræft @@ -157,6 +166,7 @@ hub.registerFailed.description=Der opstod en fejl i navngivnings-processen. Kig ### Unauthorized hub.unauthorized.message=Adgang nægtet hub.unauthorized.description=Din enhed er endnu ikke blevet godkendt til at få adgang til denne boks. Spørg boks-ejeren om godkendelse. +### Requires Account Initialization ### License Exceeded hub.invalidLicense.message=Ugyldig Hub-licens hub.invalidLicense.description=Din Cryptomator Hub har en ugyldig licens. Få venligst en Hub administrator til at opgradere eller forny licensen. @@ -356,6 +366,8 @@ main.vaultlist.contextMenu.unlockNow=Lås op nu main.vaultlist.contextMenu.vaultoptions=Vis boksindstillinger main.vaultlist.contextMenu.reveal=Vis drev main.vaultlist.addVaultBtn=Tilføj +main.vaultlist.addVaultBtn.menuItemNew=Ny Boks... +main.vaultlist.addVaultBtn.menuItemExisting=Eksisterende Boks... ## Vault Detail ### Welcome main.vaultDetail.welcomeOnboarding=Tak fordi du valgte Cryptomator til at beskytte dine filer. Hvis du har brug for hjælp, så tjek vores guider for at komme i gang: diff --git a/src/main/resources/i18n/strings_de.properties b/src/main/resources/i18n/strings_de.properties index 25bcf8541..14037a823 100644 --- a/src/main/resources/i18n/strings_de.properties +++ b/src/main/resources/i18n/strings_de.properties @@ -139,7 +139,7 @@ unlock.error.customPath.description.notExists=Der benutzerdefinierte Einhängepu unlock.error.customPath.description.inUse=Der Laufwerksbuchstabe oder benutzerdefinierte Einhängepunkt „%s“ wird bereits verwendet. unlock.error.customPath.description.hideawayNotDir=Die temporäre, versteckte Datei „%3$s“, die für das Entsperren verwendet wurde, konnte nicht entfernt werden. Bitte überprüfe die Datei und lösche sie manuell. unlock.error.customPath.description.couldNotBeCleaned=Dein Tresor konnte nicht in den Pfad „%s“ eingehängt werden. Bitte versuche es erneut oder wähle einen anderen Pfad aus. -unlock.error.customPath.description.notEmptyDir=Der benutzerdefinierte Einhängepunkt "%s" ist kein leerer Ordner. Bitte wähle einen leeren Ordner und versuche es erneut. +unlock.error.customPath.description.notEmptyDir=Der benutzerdefinierte Einhängepunkt „%s“ ist kein leerer Ordner. Bitte wähle einen leeren Ordner und versuche es erneut. unlock.error.customPath.description.generic=Du hast für diesen Tresor einen benutzerdefinierten Einhängepunkt ausgewählt, aber dessen Verwendung ist mit folgender Meldung fehlgeschlagen: %2$s ## Hub hub.noKeychain.message=Zugriff auf Geräteschlüssel nicht möglich @@ -153,9 +153,10 @@ hub.auth.loginLink=Keine Weiterleitung? Zum Öffnen hier anklicken. hub.receive.message=Antwort wird verarbeitet … hub.receive.description=Cryptomator empfängt und verarbeitet gerade die Antwort vom Hub. Bitte warten. ### Register Device -hub.register.message=Gerätename erforderlich -hub.register.description=Dies scheint der erste Hub-Zugriff von diesem Gerät zu sein. Um es für die Zugriffsberechtigung zu identifizieren, mussst du diesem Gerät einen Namen geben. +hub.register.message=Neues Gerät +hub.register.description=Dies ist der erste Hub-Zugriff von diesem Gerät. Bitte autorisiere dich mit deinem Account Key. hub.register.nameLabel=Gerätename +hub.register.invalidAccountKeyLabel=Ungültiger Account Key hub.register.occupiedMsg=Name bereits in Verwendung hub.register.registerBtn=Bestätigen ### Registration Success @@ -167,6 +168,11 @@ hub.registerFailed.description=Während der Namensvergabe wurde ein Fehler ausge ### Unauthorized hub.unauthorized.message=Zugriff verweigert hub.unauthorized.description=Dein Gerät wurde noch nicht für den Zugriff auf diesen Tresor autorisiert. Bitte den Tresorbesitzer, dein Gerät zu autorisieren. +### Requires Account Initialization +hub.requireAccountInit.message=Aktion erforderlich +hub.requireAccountInit.description.0=Um fortzufahren, führe bitte die erforderlichen Schritte in deinem +hub.requireAccountInit.description.1=Hub-Benutzerprofil +hub.requireAccountInit.description.2=aus. ### License Exceeded hub.invalidLicense.message=Hub-Lizenz ungültig hub.invalidLicense.description=Die Lizenz deiner Cryptomator-Hub-Instanz ist ungültig. Bitte informiere deinen Hub-Administrator, um die Lizenz zu erweitern oder zu erneuern. diff --git a/src/main/resources/i18n/strings_el.properties b/src/main/resources/i18n/strings_el.properties index 6745894c2..029656ddc 100644 --- a/src/main/resources/i18n/strings_el.properties +++ b/src/main/resources/i18n/strings_el.properties @@ -153,9 +153,10 @@ hub.auth.loginLink=Δεν έγινε ανακατεύθυνση; Κάντε κλ hub.receive.message=Επεξεργασία απάντησης… hub.receive.description=Το Cryptomator λαμβάνει και επεξεργάζεται την απάντηση από το Hub. Παρακαλώ περιμένετε. ### Register Device -hub.register.message=Απαιτείται όνομα συσκευής -hub.register.description=Αυτή φαίνεται να είναι η πρώτη πρόσβαση στο Hub από αυτήν τη συσκευή. Για να την αναγνωρίσετε για εξουσιοδότηση πρόσβασης, πρέπει να ονομάσετε αυτήν τη συσκευή. +hub.register.message=Νέα Συσκευή +hub.register.description=Αυτή είναι η πρώτη πρόσβαση Hub από αυτήν τη συσκευή. Παρακαλούμε εξουσιοδοτήστε την χρησιμοποιώντας το κλειδί του λογαριασμού σας. hub.register.nameLabel=Όνομα Συσκευής +hub.register.invalidAccountKeyLabel=Μη Έγκυρο Κλειδί Λογαριασμού hub.register.occupiedMsg=Το όνομα χρησιμοποιείται ήδη hub.register.registerBtn=Επιβεβαίωση ### Registration Success @@ -167,6 +168,9 @@ hub.registerFailed.description=Παρουσιάστηκε σφάλμα στη δ ### Unauthorized hub.unauthorized.message=Δεν επιτρέπεται η πρόσβαση hub.unauthorized.description=Η συσκευή σας δεν έχει ακόμη εξουσιοδοτηθεί να έχει πρόσβαση σε αυτή την κρύπτη. Ζητήστε από τον κάτοχο της κρύπτης να την εξουσιοδοτήσει. +### Requires Account Initialization +hub.requireAccountInit.message=Απαιτείται ενέργεια +hub.requireAccountInit.description.2=. ### License Exceeded hub.invalidLicense.message=Μη έγκυρη Άδεια Hub hub.invalidLicense.description=Η συνεδρία σας στο Cryptomator Hub έχει μη έγκυρη άδεια χρήσης. Ενημερώστε έναν διαχειριστή του Hub για να αναβαθμίσει ή να ανανεώσει την άδεια χρήσης. diff --git a/src/main/resources/i18n/strings_es.properties b/src/main/resources/i18n/strings_es.properties index 27804b73c..fb804e757 100644 --- a/src/main/resources/i18n/strings_es.properties +++ b/src/main/resources/i18n/strings_es.properties @@ -153,9 +153,10 @@ hub.auth.loginLink=¿No se ha redireccionado? Haga clic aquí para abrirla. hub.receive.message=Procesando la respuesta… hub.receive.description=Cryptomator está recibiendo y procesando la respuesta del Hub. Por favor espere. ### Register Device -hub.register.message=Nombre del dispositivo requerido -hub.register.description=Este parece ser el primer acceso al Hub desde este dispositivo. Para identificarlo y autorizar el acceso, necesita nombrar este dispositivo. +hub.register.message=Nuevo dispositivo +hub.register.description=Este es el primer acceso al Hub desde este dispositivo. Por favor, autorícelo usando su Account Key. hub.register.nameLabel=Nombre del dispositivo +hub.register.invalidAccountKeyLabel=Account Key no válida hub.register.occupiedMsg=Nombre ya en uso hub.register.registerBtn=Confirmar ### Registration Success @@ -167,6 +168,9 @@ hub.registerFailed.description=Ocurrió un error en el nombramiento. Para más d ### Unauthorized hub.unauthorized.message=Acceso denegado hub.unauthorized.description=Su dispositivo aún no ha sido autorizado para acceder a esta bóveda. Pídale al propietario de la bóveda que lo autorice. +### Requires Account Initialization +hub.requireAccountInit.description.0=Para continuar, por favor complete los pasos necesarios en su +hub.requireAccountInit.description.1=Perfil de usuario del Hub ### License Exceeded hub.invalidLicense.message=Licencia del Hub inválida hub.invalidLicense.description=Su instancia del Hub de Cryptomator tiene una licencia inválida. Informe a un administrador del Hub para actualizar o renovar la licencia. diff --git a/src/main/resources/i18n/strings_fa.properties b/src/main/resources/i18n/strings_fa.properties index c4ad622a0..8a6aadf9e 100644 --- a/src/main/resources/i18n/strings_fa.properties +++ b/src/main/resources/i18n/strings_fa.properties @@ -65,6 +65,7 @@ unlock.unlockBtn=بازکردن قفل ### Registration Success ### Registration Failed ### Unauthorized +### Requires Account Initialization ### License Exceeded # Lock diff --git a/src/main/resources/i18n/strings_fi.properties b/src/main/resources/i18n/strings_fi.properties index 3270ecfc8..dbda989a3 100644 --- a/src/main/resources/i18n/strings_fi.properties +++ b/src/main/resources/i18n/strings_fi.properties @@ -138,8 +138,6 @@ hub.auth.loginLink=Uudelleenohjaus epäonnistui? Avaa tästä manuaalisesti. 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 @@ -152,6 +150,7 @@ hub.registerFailed.description=Nimeämisprosessissa tapahtui virhe. Löydät lis ### 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. +### Requires Account Initialization ### 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. diff --git a/src/main/resources/i18n/strings_fil.properties b/src/main/resources/i18n/strings_fil.properties index b9fb10c8e..56ce0c11d 100644 --- a/src/main/resources/i18n/strings_fil.properties +++ b/src/main/resources/i18n/strings_fil.properties @@ -124,106 +124,391 @@ unlock.savePassword=Maalala ang password unlock.unlockBtn=I-unlock ## Select unlock.chooseMasterkey.message=Hindi nahanap ang masterkey file +unlock.chooseMasterkey.description=Hindi mahanap ng Cryptomator ang masterkey file para sa vault na "%s". Mangyaring piliin ang key file nang manu-mano. unlock.chooseMasterkey.filePickerTitle=Piliin ang masterkey file unlock.chooseMasterkey.filePickerMimeDesc=Cryptomator Masterkey ## Success +unlock.success.message=Matagumpay ang pag-unlock +unlock.success.description=Ang nilalaman sa vault na "%s" ay naa-access na ngayon sa mount point nito. unlock.success.rememberChoice=Paalala ang pinili ko, huwag mag tanong ulit +unlock.success.revealBtn=Ibunyag ang Drive ## Failure +unlock.error.customPath.message=Hindi ma-mount ang vault sa custom na path +unlock.error.customPath.description.notSupported=Kung gusto mong patuloy na gamitin ang custom na path, mangyaring pumunta sa mga kagustuhan at pumili ng uri ng volume na sumusuporta dito. Kung hindi, pumunta sa mga opsyon sa vault at pumili ng sinusuportahang mount point. +unlock.error.customPath.description.notExists=Ang custom na mount path ay hindi umiiral. Alinman sa lumikha ito sa iyong lokal na filesystem o baguhin ito sa mga pagpipilian sa vault. +unlock.error.customPath.description.inUse=Ang drive letter o custom na mount path na "%s" ay ginagamit na. +unlock.error.customPath.description.hideawayNotDir=Ang pansamantalang nakatagong file na "%3$s" na ginamit para sa pag-unlock ay hindi maalis. Pakisuri ang file at pagkatapos ay tanggalin ito nang manu-mano. +unlock.error.customPath.description.couldNotBeCleaned=Hindi ma-mount ang iyong vault sa path na "%s". Pakisubukang muli o pumili ng ibang landas. +unlock.error.customPath.description.notEmptyDir=Ang custom na mount path na "%s" ay hindi isang walang laman na folder. Mangyaring pumili ng isang walang laman na folder at subukang muli. +unlock.error.customPath.description.generic=Pumili ka ng custom na mount path para sa vault na ito, ngunit nabigo ang paggamit nito sa mensaheng: %2$s ## Hub +hub.noKeychain.message=Hindi ma-access ang key ng device +hub.noKeychain.description=Para ma-unlock ang mga Hub vault, kailangan ng device key, na sini-secure gamit ang keychain. Upang magpatuloy, paganahin ang “%s” at pumili ng keychain sa mga kagustuhan. +hub.noKeychain.openBtn=Buksan ang Mga Kagustuhan ### Waiting +hub.auth.message=Naghihintay para sa pagpapatunay… +hub.auth.description=Dapat kang awtomatikong mai-redirect sa pahina ng pag-login. +hub.auth.loginLink=Hindi na-redirect? Mag-click dito upang buksan ito. ### Receive Key +hub.receive.message=Pinoproseso ang tugon… +hub.receive.description=Ang Cryptomator ay tumatanggap at nagpoproseso ng tugon mula sa Hub. Mangyaring maghintay. ### Register Device +hub.register.message=Bagong Device +hub.register.nameLabel=Pangalan ng device hub.register.occupiedMsg=Ang pangalan ay nagamit na hub.register.registerBtn=Kumpirmahin ### Registration Success +hub.registerSuccess.message=Pinangalanan ang device +hub.registerSuccess.description=Para ma-access ang vault, kailangang pahintulutan ng may-ari ng vault ang iyong device. ### Registration Failed +hub.registerFailed.message=Nabigo ang pagpapangalan ng device +hub.registerFailed.description=Nagkaroon ng error sa proseso ng pagbibigay ng pangalan. Para sa higit pang mga detalye, tingnan ang log ng aplikasyon. ### Unauthorized +hub.unauthorized.message=Walang pahintulot +hub.unauthorized.description=Hindi pa pinahihintulutan ang iyong device na i-access ang vault na ito. Hilingin sa may-ari ng vault na pahintulutan ito. +### Requires Account Initialization ### License Exceeded +hub.invalidLicense.message=Di-wasto ang Lisensya ng Hub +hub.invalidLicense.description=Ang iyong Cryptomator Hub instance ay may di-wastong lisensya. Mangyaring ipagbigay-alam sa administrator ng Hub na mag-upgrade o mag-renew ng lisensya. # Lock ## Force +lock.forced.message=Nabigo ang pag-lock +lock.forced.description=Ang pag-lock ng "%s" ay na-block ng mga nakabinbing operasyon o mga bukas na file. Maaari mong pilitin na i-lock ang vault na ito, gayunpaman ang pagkagambala sa I/O ay maaaring magresulta sa pagkawala ng hindi na-save na data. lock.forced.retryBtn=Subukan muli +lock.forced.forceBtn=Force Lock ## Failure +lock.fail.message=Nabigo ang pag-lock ng vault +lock.fail.description=Hindi ma-lock ang Vault "%s". Tiyaking nai-save ang hindi na-save na gawain sa ibang lugar at natapos ang mahahalagang operasyon ng Read/Write. Upang isara ang vault, patayin ang proseso ng Cryptomator. # Migration +migration.title=I-upgrade ang Vault ## Start +migration.start.header=I-upgrade ang Vault +migration.start.text=Upang mabuksan ang iyong vault na "%s" sa bagong bersyon na ito ng Cryptomator, kailangang i-upgrade ang vault sa mas bagong format. Bago gawin ito, dapat mong malaman ang mga sumusunod: +migration.start.remarkUndone=Hindi maa-undo ang pag-upgrade na ito. +migration.start.remarkVersions=Hindi mabubuksan ng mga lumang bersyon ng Cryptomator ang na-upgrade na vault. +migration.start.remarkCanRun=Dapat mong tiyakin na ang bawat device kung saan mo ina-access ang vault ay maaaring magpatakbo ng bersyong ito ng Cryptomator. +migration.start.remarkSynced=Dapat mong tiyakin na ang iyong vault ay ganap na naka-sync sa device na ito, at sa iyong iba pang mga device, bago ito i-upgrade. +migration.start.confirm=Nabasa at naunawaan ko ang impormasyon sa itaas ## Run +migration.run.enterPassword=Ilagay ang password para sa "%s" +migration.run.startMigrationBtn=I-migrate ang Vault +migration.run.progressHint=Maaaring tumagal ito ng ilang oras… ## Success +migration.success.nextStepsInstructions=Matagumpay na nailipat ang "%s".\nMaaari mo na ngayong i-unlock ang iyong vault. migration.success.unlockNow=I-unlock Ngayon ## Missing file system capabilities +migration.error.missingFileSystemCapabilities.title=Hindi sinusuportahang File System +migration.error.missingFileSystemCapabilities.description=Hindi nasimulan ang paglipat, dahil ang iyong vault ay matatagpuan sa isang hindi sapat na file system. +migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Hindi sinusuportahan ng file system ang mahabang pangalan ng file. +migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Hindi sinusuportahan ng file system ang mahahabang landas. +migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=Hindi pinapayagan ng file system na basahin. +migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=Ang file system ay hindi pinapayagang isulat sa. ## Impossible +migration.impossible.heading=Hindi ma-migrate ang vault +migration.impossible.reason=Hindi maaaring awtomatikong i-migrate ang vault dahil hindi tugma ang lokasyon ng storage o access point nito. +migration.impossible.moreInfo=Mabubuksan pa rin ang vault gamit ang mas lumang bersyon. Para sa mga tagubilin kung paano manu-manong mag-migrate ng vault, bisitahin ang # Health Check ## Start +health.title=Health Check ng "%s" +health.intro.header=Health Check +health.intro.text=Ang Health Check ay isang koleksyon ng mga pagsusuri upang makita at posibleng ayusin ang mga problema sa panloob na istraktura ng iyong vault. Mangyaring tandaan: +health.intro.remarkSync=Tiyaking ganap na naka-sync ang lahat ng device, nalulutas nito ang karamihan sa mga problema. +health.intro.remarkFix=Hindi lahat ng problema ay kayang ayusin. +health.intro.remarkBackup=Kung nasira ang data, isang backup lamang ang makakatulong. +health.intro.affirmation=Nabasa at naunawaan ko ang impormasyon sa itaas ## Start Failure +health.fail.header=Error sa pag-load ng Vault Configuration +health.fail.ioError=Nagkaroon ng error habang ina-access at binabasa ang config file. +health.fail.parseError=Nagkaroon ng error habang pina-parse ang config ng vault. +health.fail.moreInfo=Karagdagang impormasyon ## Check Selection +health.checkList.description=Pumili ng mga tseke sa kaliwang listahan o gamitin ang mga button sa ibaba. +health.checkList.selectAllButton=Piliin ang Lahat ng Pagsusuri +health.checkList.deselectAllButton=Alisin sa pagkakapili ang Lahat ng Pagsusuri +health.check.runBatchBtn=Patakbuhin ang Mga Napiling Pagsusuri ## Detail view +health.check.detail.noSelectedCheck=Para sa mga resulta pumili ng natapos na pagsusuri sa kalusugan sa kaliwang listahan. +health.check.detail.checkScheduled=The check is scheduled. +health.check.detail.checkRunning=The check is currently running… +health.check.detail.checkSkipped=Ang tseke ay hindi napiling tumakbo. +health.check.detail.checkFinished=Matagumpay na natapos ang tseke. +health.check.detail.checkFinishedAndFound=Ang tseke ay tapos nang tumakbo. Mangyaring suriin ang mga resulta. +health.check.detail.checkFailed=Ang tseke ay lumabas dahil sa isang error. +health.check.detail.checkCancelled=Kinansela ang tseke. +health.check.detail.listFilters.label=Filter +health.check.detail.fixAllSpecificBtn=Ayusin ang lahat ng uri +health.check.exportBtn=I-export ang Ulat ## Result view +health.result.severityFilter.all=Kalubhaan - Lahat +health.result.severityFilter.good=Mabuti +health.result.severityFilter.info=Impormasyon +health.result.severityFilter.warn=Babala +health.result.severityFilter.crit=Mapanganib +health.result.severityTip.good=Seryoso: Mabuti\nNormal na istraktura ng vault. +health.result.severityTip.info=Kalubhaan: Impormasyon\nBuo ang istraktura ng Vault, iminungkahing ayusin. +health.result.severityTip.warn=Kalubhaan: Babala\nNasira ang istraktura ng Vault, lubos na pinapayuhan ang pag-aayos. +health.result.severityTip.crit=Kalubhaan: Kritikal\nNasira ang istraktura ng Vault, natukoy ang pagkawala ng data. +health.result.fixStateFilter.all=Ayusin ang estado - Lahat +health.result.fixStateFilter.fixable=Naaayos +health.result.fixStateFilter.notFixable=Hindi naaayos +health.result.fixStateFilter.fixing=Inaayos… +health.result.fixStateFilter.fixed=Nakapirming +health.result.fixStateFilter.fixFailed=Nabigo ang pag-aayos ## Fix Application +health.fix.fixBtn=Ayusin +health.fix.successTip=Ayusin ang matagumpay +health.fix.failTip=Nabigo ang pag-aayos, tingnan ang log para sa mga detalye # Preferences preferences.title=Mga Kagustuhan ## General +preferences.general=Heneral +preferences.general.startHidden=Itago ang window kapag sinimulan ang Cryptomator +preferences.general.autoCloseVaults=Awtomatikong i-lock ang mga bukas na vault kapag huminto sa aplikasyon +preferences.general.debugLogging=Paganahin ang pag-log ng debug +preferences.general.debugDirectory=Magbunyag ng mga log file +preferences.general.autoStart=Ilunsad ang Cryptomator sa pagsisimula ng system +preferences.general.keychainBackend=Mag-imbak ng mga password gamit ang ## Interface +preferences.interface=Interface +preferences.interface.theme=Tingnan at Pakiramdam +preferences.interface.theme.automatic=Awtomatiko preferences.interface.theme.dark=Dark preferences.interface.theme.light=Light +preferences.interface.unlockThemes=I-unlock ang dark mode +preferences.interface.language=Wika (nangangailangan ng pag-restart) +preferences.interface.language.auto=System Default +preferences.interface.interfaceOrientation=Oryentasyon ng Interface +preferences.interface.interfaceOrientation.ltr=Kaliwa hanggang Kanan +preferences.interface.interfaceOrientation.rtl=Kanan papuntang Kaliwa +preferences.interface.showMinimizeButton=Ipakita ang pindutan ng minimize +preferences.interface.showTrayIcon=Ipakita ang icon ng tray (kailangan i-restart) ## Volume +preferences.volume=Virtual Drive +preferences.volume.type=Uri ng Dami +preferences.volume.type.automatic=Awtomatiko +preferences.volume.docsTooltip=Buksan ang dokumentasyon para matuto pa tungkol sa iba't ibang uri ng volume. +preferences.volume.fuseRestartRequired=Upang mailapat ang mga pagbabago, kailangang i-restart ang Cryptomator. +preferences.volume.tcp.port=TCP Port +preferences.volume.supportedFeatures=Sinusuportahan ng napiling uri ng volume ang mga sumusunod na tampok: +preferences.volume.feature.mountAuto=Awtomatikong pagpili ng mount point +preferences.volume.feature.mountToDir=Custom na direktoryo bilang mount point +preferences.volume.feature.mountToDriveLetter=Drive letter bilang mount point +preferences.volume.feature.mountFlags=Mga pagpipilian sa custom na pag-mount +preferences.volume.feature.readOnly=Read-only mount ## Updates +preferences.updates=Mga update +preferences.updates.currentVersion=Kasalukuyang Bersyon: %s +preferences.updates.autoUpdateCheck=Awtomatikong suriin ang mga update +preferences.updates.checkNowBtn=Tingnan ngayon +preferences.updates.updateAvailable=Available ang update sa bersyong %s. ## Contribution +preferences.contribute=Suportahan Kami +preferences.contribute.registeredFor=Nakarehistro ang sertipiko ng tagasuporta para sa %s +preferences.contribute.noCertificate=Suportahan ang Cryptomator at tumanggap ng sertipiko ng tagasuporta. Ito ay tulad ng isang susi ng lisensya ngunit para sa mga kahanga-hangang tao na gumagamit ng libreng software. ;-) +preferences.contribute.getCertificate=Wala ka na ba? Alamin kung paano mo ito makukuha. +preferences.contribute.promptText=I-paste ang code ng certificate ng tagasuporta dito #<-- Add entries for donations and code/translation/documentation contribution --> ## About +preferences.about=Tungkol sa # Vault Statistics +stats.title=Mga istatistika para sa %s +stats.cacheHitRate=Rate ng Cache Hit ## Read +stats.read.throughput.idle=Basahin: walang ginagawa +stats.read.throughput.kibs=Read: %.2f KiB/s +stats.read.throughput.mibs=Basahin: %.2f MiB/s +stats.read.total.data.none=Nabasa ang data: - +stats.read.total.data.kib=Nabasa ang data: %.1f KiB +stats.read.total.data.mib=Nabasa ang data: %.1f MiB +stats.read.total.data.gib=Nabasa ang data: %.1f GiB +stats.decr.total.data.none=Na-decrypt ang data: - +stats.decr.total.data.kib=Na-decrypt ang data: %.1f KiB +stats.decr.total.data.mib=Na-decrypt ang data: %.1f MiB +stats.decr.total.data.gib=Na-decrypt ang data: %.1f GiB +stats.read.accessCount=Kabuuang nabasa: %d ## Write +stats.write.throughput.idle=Sumulat: walang ginagawa +stats.write.throughput.kibs=Sumulat: %.2f KiB/s +stats.write.throughput.mibs=Sumulat: %.2f MiB/s +stats.write.total.data.none=Nakasulat na datos: - +stats.write.total.data.kib=Nakasulat na datos: %.1f KiB +stats.write.total.data.mib=Data na nakasulat: %.1f MiB +stats.write.total.data.gib=Nakasulat na datos: %.1f GiB +stats.encr.total.data.none=Naka-encrypt na data: - +stats.encr.total.data.kib=Naka-encrypt na data: %.1f KiB +stats.encr.total.data.mib=Naka-encrypt na data: %.1f MiB +stats.encr.total.data.gib=Naka-encrypt na data: %.1f GiB +stats.write.accessCount=Kabuuang pagsusulat: %d ## Accesses +stats.access.current=Access: %d +stats.access.total=Kabuuang mga access: %d # Main Window main.closeBtn.tooltip=Isara +main.minimizeBtn.tooltip=I-minimize main.preferencesBtn.tooltip=Mga Kagustuhan +main.debugModeEnabled.tooltip=Naka-enable ang debug mode +main.supporterCertificateMissing.tooltip=Mangyaring isaalang-alang ang pagbibigay ng donasyon ## Vault List +main.vaultlist.emptyList.onboardingInstruction=Mag-click dito para magdagdag ng vault +main.vaultlist.contextMenu.remove=Alisin… main.vaultlist.contextMenu.lock=I-lock +main.vaultlist.contextMenu.unlock=I-unlock… main.vaultlist.contextMenu.unlockNow=I-unlock Ngayon +main.vaultlist.contextMenu.vaultoptions=Ipakita ang Mga Opsyon sa Vault +main.vaultlist.contextMenu.reveal=Ibunyag ang Drive main.vaultlist.addVaultBtn=Idagdag +main.vaultlist.addVaultBtn.menuItemNew=Bagong Vault... +main.vaultlist.addVaultBtn.menuItemExisting=Kasalukuyang Vault... ## Vault Detail ### Welcome +main.vaultDetail.welcomeOnboarding=Salamat sa pagpili sa Cryptomator para protektahan ang iyong mga file. Kung kailangan mo ng anumang tulong, tingnan ang aming mga gabay sa pagsisimula: ### Locked +main.vaultDetail.lockedStatus=NAKA-LOCK +main.vaultDetail.unlockBtn=I-unlock… main.vaultDetail.unlockNowBtn=I-unlock Ngayon +main.vaultDetail.optionsBtn=Mga Pagpipilian sa Vault +main.vaultDetail.passwordSavedInKeychain=Na-save ang password ### Unlocked +main.vaultDetail.unlockedStatus=NAKA-unlock +main.vaultDetail.accessLocation=Ang mga nilalaman ng iyong vault ay maa-access dito: +main.vaultDetail.revealBtn=Ibunyag ang Drive +main.vaultDetail.copyUri=Kopyahin ang URI main.vaultDetail.lockBtn=I-lock +main.vaultDetail.bytesPerSecondRead=Basahin: +main.vaultDetail.bytesPerSecondWritten=Sumulat: +main.vaultDetail.throughput.idle=walang ginagawa +main.vaultDetail.throughput.kbps=%.1f KiB/s +main.vaultDetail.throughput.mbps=%.1f MiB/s +main.vaultDetail.stats=Mga Istatistika ng Vault +main.vaultDetail.locateEncryptedFileBtn=Hanapin ang Naka-encrypt na File +main.vaultDetail.locateEncryptedFileBtn.tooltip=Pumili ng file mula sa iyong vault upang mahanap ang naka-encrypt na katapat nito +main.vaultDetail.encryptedPathsCopied=Mga Path na Nakopya sa Clipboard! +main.vaultDetail.filePickerTitle=Piliin ang File Inside Vault ### Missing +main.vaultDetail.missing.info=Hindi makahanap ng vault ang Cryptomator sa landas na ito. +main.vaultDetail.missing.recheck=Suriin muli +main.vaultDetail.missing.remove=Alisin sa Listahan ng Vault… +main.vaultDetail.missing.changeLocation=Baguhin ang Lokasyon ng Vault… ### Needs Migration +main.vaultDetail.migrateButton=I-upgrade ang Vault +main.vaultDetail.migratePrompt=Kailangang i-upgrade ang iyong vault sa bagong format, bago mo ito ma-access ### Error +main.vaultDetail.error.info=Nagkaroon ng error sa paglo-load ng vault mula sa disk. +main.vaultDetail.error.reload=Reload +main.vaultDetail.error.windowTitle=Error sa paglo-load ng vault # Wrong File Alert +wrongFileAlert.title=Paano Mag-encrypt ng mga File +wrongFileAlert.message=Sinubukan mo bang i-encrypt ang mga file na ito? +wrongFileAlert.description=Para sa layuning ito, nagbibigay ang Cryptomator ng volume sa iyong system file manager. +wrongFileAlert.instruction.0=Upang i-encrypt ang mga file, sundin ang mga hakbang na ito: +wrongFileAlert.instruction.1=1. I-unlock ang iyong vault. +wrongFileAlert.instruction.2=2. Mag-click sa "Reveal" para buksan ang volume sa iyong file manager. +wrongFileAlert.instruction.3=3. Idagdag ang iyong mga file sa volume na ito. +wrongFileAlert.link=Para sa karagdagang tulong, bisitahin ang # Vault Options ## General +vaultOptions.general=Heneral vaultOptions.general.vaultName=Pangalan ng Vault +vaultOptions.general.autoLock.lockAfterTimePart1=I-lock kapag idle para sa +vaultOptions.general.autoLock.lockAfterTimePart2=minuto +vaultOptions.general.unlockAfterStartup=I-unlock ang vault kapag sinimulan ang Cryptomator +vaultOptions.general.actionAfterUnlock=Pagkatapos ng matagumpay na pag-unlock +vaultOptions.general.actionAfterUnlock.ignore=walang gawin +vaultOptions.general.actionAfterUnlock.reveal=Ibunyag ang Drive +vaultOptions.general.actionAfterUnlock.ask=Magtanong +vaultOptions.general.startHealthCheckBtn=Simulan ang Health Check ## Mount +vaultOptions.mount=Pag-mount +vaultOptions.mount.info=Ang mga opsyon ay depende sa napiling uri ng volume. +vaultOptions.mount.linkToPreferences=Buksan ang mga kagustuhan sa virtual drive +vaultOptions.mount.readonly=Basahin lamang +vaultOptions.mount.customMountFlags=Mga custom na naka-mount na flag +vaultOptions.mount.winDriveLetterOccupied=inookupahan +vaultOptions.mount.mountPoint=Mount point +vaultOptions.mount.mountPoint.auto=Awtomatikong pumili ng angkop na lokasyon +vaultOptions.mount.mountPoint.driveLetter=Gumamit ng nakatalagang drive letter +vaultOptions.mount.mountPoint.custom=Gamitin ang napiling direktoryo vaultOptions.mount.mountPoint.directoryPickerButton=Mamili… +vaultOptions.mount.mountPoint.directoryPickerTitle=Pumili ng isang direktoryo ## Master Key +vaultOptions.masterkey=Password vaultOptions.masterkey.changePasswordBtn=Palitan ANG password +vaultOptions.masterkey.forgetSavedPasswordBtn=Kalimutan ang Naka-save na Password +vaultOptions.masterkey.recoveryKeyExplanation=Ang recovery key ay ang tanging paraan mo upang maibalik ang access sa isang vault kung mawala mo ang iyong password. +vaultOptions.masterkey.showRecoveryKeyBtn=Display Recovery Key +vaultOptions.masterkey.recoverPasswordBtn=I-reset ang Password ## Hub +vaultOptions.hub=Pagbawi +vaultOptions.hub.convertInfo=Maaari mong gamitin ang recovery key upang i-convert ang Hub vault na ito sa isang password-based na vault sa isang emergency. +vaultOptions.hub.convertBtn=I-convert sa Password-Based Vault # Recovery Key ## Display Recovery Key +recoveryKey.display.title=Ipakita ang Recovery Key +recoveryKey.create.message=Kailangan ng password +recoveryKey.create.description=Ilagay ang password para sa "%s" upang ipakita ang recovery key nito. +recoveryKey.display.description=Ang sumusunod na recovery key ay maaaring gamitin upang ibalik ang access sa "%s": +recoveryKey.display.StorageHints=Itago ito sa isang lugar na napaka-secure, hal.:\n • I-store ito gamit ang isang password manager\n • I-save ito sa isang USB flash drive\n • I-print ito sa papel ## Reset Password ### Enter Recovery Key +recoveryKey.recover.title=I-reset ang Password +recoveryKey.recover.prompt=Ilagay ang recovery key para sa "%s": +recoveryKey.recover.correctKey=Tama ang recovery key na ito +recoveryKey.recover.wrongKey=Ang recovery key na ito ay kabilang sa ibang vault +recoveryKey.recover.invalidKey=Hindi wasto ang recovery key na ito +recoveryKey.printout.heading=Cryptomator Recovery Key\n"%s"\n ### Reset Password +recoveryKey.recover.resetBtn=I-reset ### Recovery Key Password Reset Success +recoveryKey.recover.resetSuccess.message=Matagumpay ang pag-reset ng password +recoveryKey.recover.resetSuccess.description=Maaari mong i-unlock ang iyong vault gamit ang bagong password. # Convert Vault +convertVault.title=I-convert ang Vault +convertVault.convert.convertBtn.before=Magbalik-loob +convertVault.convert.convertBtn.processing=Kino-convert… +convertVault.success.message=Matagumpay ang conversion +convertVault.hubToPassword.success.description=Maaari mo na ngayong i-unlock ang vault gamit ang napiling password nang hindi nangangailangan ng access sa Hub. # New Password +newPassword.promptText=Maglagay ng bagong password +newPassword.reenterPassword=Kumpirmahin ang bagong password +newPassword.passwordsMatch=Tugma ang mga password! +newPassword.passwordsDoNotMatch=Hindi tugma ang mga password +passwordStrength.messageLabel.tooShort=Gumamit ng hindi bababa sa %d character +passwordStrength.messageLabel.0=Napakahina +passwordStrength.messageLabel.1=Mahina +passwordStrength.messageLabel.2=Patas +passwordStrength.messageLabel.3=Malakas +passwordStrength.messageLabel.4=Napakalakas # Quit +quit.title=Ihinto ang Application +quit.message=May mga naka-unlock na vault +quit.description=Pakikumpirma na gusto mong umalis. Ila-lock ng Cryptomator ang lahat ng naka-unlock na vault para maiwasan ang pagkawala ng data. +quit.lockAndQuitBtn=Lock at Quit # Forced Quit +quit.forced.message=Hindi ma-lock ang ilang vault +quit.forced.description=Ang pag-lock ng mga vault ay na-block ng mga nakabinbing operasyon o bukas na mga file. Maaari mong pilitin na i-lock ang mga natitirang vault, gayunpaman ang pagkagambala sa I/O ay maaaring magresulta sa pagkawala ng hindi na-save na data. +quit.forced.forceAndQuitBtn=Puwersa at Umalis # Update Reminder -updateReminder.message=I-tsek kung may bagong update? \ No newline at end of file +updateReminder.title=Muling iwasto +updateReminder.message=I-tsek kung may bagong update? +updateReminder.description=Manatiling updated sa mga bagong feature, pag-aayos ng bug, at pagpapahusay sa seguridad. Inirerekomenda naming awtomatikong suriin ang mga update. +updateReminder.notNow=Hindi ngayon +updateReminder.yesOnce=Oo, Minsan +updateReminder.yesAutomatically=Oo, Awtomatiko \ No newline at end of file diff --git a/src/main/resources/i18n/strings_fr.properties b/src/main/resources/i18n/strings_fr.properties index 88be64099..bb08d22e2 100644 --- a/src/main/resources/i18n/strings_fr.properties +++ b/src/main/resources/i18n/strings_fr.properties @@ -153,9 +153,10 @@ hub.auth.loginLink=Vous n'avez pas été redirigé(e) ? Cliquez ici pour l'ouvri hub.receive.message=Traitement de la réponse… hub.receive.description=Cryptomator est en train de recevoir et de traiter la réponse de Hub. Veuillez patienter. ### Register Device -hub.register.message=Le nom de l'appareil est requis -hub.register.description=Il semble que ce soit le premier accès à Hub depuis cet appareil. Afin de l'identifier pour l'autorisation d'accès, vous devez nommer cet appareil. +hub.register.message=Nouvel Appareil +hub.register.description=Il s'agit du premier accès de cet appareil au Hub. Veuillez l'autoriser à l'aide de votre clé de compte. hub.register.nameLabel=Nom de l'appareil +hub.register.invalidAccountKeyLabel=Clé de compte invalide hub.register.occupiedMsg=Nom déjà utilisé hub.register.registerBtn=Confirmer ### Registration Success @@ -167,6 +168,11 @@ hub.registerFailed.description=Le processus de nommage a retourné une erreur. P ### Unauthorized hub.unauthorized.message=Accès refusé hub.unauthorized.description=Votre appareil n'a pas encore été autorisé à accéder à ce coffre. Demandez au propriétaire du coffre de l'autoriser. +### Requires Account Initialization +hub.requireAccountInit.message=Action requise +hub.requireAccountInit.description.0=Pour continuer, veuillez compléter les étapes requises +hub.requireAccountInit.description.1=Profil utilisateur Hub +hub.requireAccountInit.description.2=. ### License Exceeded hub.invalidLicense.message=Licence de Hub invalide hub.invalidLicense.description=Votre instance Cryptomator Hub a une licence invalide. Veuillez informer un administrateur Hub pour la mettre à niveau ou la renouveler. diff --git a/src/main/resources/i18n/strings_gl.properties b/src/main/resources/i18n/strings_gl.properties index 315bc6f25..f2071a8ac 100644 --- a/src/main/resources/i18n/strings_gl.properties +++ b/src/main/resources/i18n/strings_gl.properties @@ -48,6 +48,7 @@ error.message=Produciuse un erro ### Registration Success ### Registration Failed ### Unauthorized +### Requires Account Initialization ### License Exceeded # Lock diff --git a/src/main/resources/i18n/strings_he.properties b/src/main/resources/i18n/strings_he.properties index 4d9b7c076..a253f0007 100644 --- a/src/main/resources/i18n/strings_he.properties +++ b/src/main/resources/i18n/strings_he.properties @@ -20,6 +20,11 @@ error.description=Cryptomator לא ציפתה שזה יקרה. ניתן לחפש error.hyperlink.lookup=חיפוש שגיאה error.hyperlink.report=דיווח על שגיאה error.technicalDetails=פרטים: +error.existingSolutionDescription=Cryptomator לא ציפה שזה יקרה. אך מצאנו פתרון קיים לשגיאה זו. בבקשה לבדוק את הקישור הבא. +error.hyperlink.solution=חפש את הפתרון +error.lookupPermissionMessage=Cryptomator יכול לחפש פתרון לבעיה זו באינטרנט. זה ישלח בקשה למסד הנתונים של הבעיה מכתובת ה-IP שלך. +error.dismiss=שחרור +error.lookUpSolution=חפש את הפתרון # Defaults defaults.vault.vaultName=כספת @@ -36,6 +41,7 @@ traymenu.vault.reveal=חשוף # Add Vault Wizard addvaultwizard.title=הוספת כספת ## New +addvaultwizard.new.title=הוסף ארגז חדש ### Name addvaultwizard.new.nameInstruction=בחירת שם עבור הכספת addvaultwizard.new.namePrompt=שם הכספת @@ -57,6 +63,11 @@ addvaultwizard.new.validCharacters.chars=תווי מילים (למשל: a, ж or addvaultwizard.new.validCharacters.numbers=מספרים addvaultwizard.new.validCharacters.dashes=מקף (%s) או קו תחתון (%s) ### Expert Settings +addvaultwizard.new.expertSettings.enableExpertSettingsCheckbox=הפעל הגדרות מתקדמות +addvaultwizard.new.expertSettings.shorteningThreshold.invalid=הזן ערך בין 36 ל-220 (ברירת המחדל 220) +addvaultwizard.new.expertSettings.shorteningThreshold.tooltip=פתח את התיעוד כדי ללמוד עוד. +addvaultwizard.new.expertSettings.shorteningThreshold.title=האורך המרבי של שמות הקבצים המוצפנים +addvaultwizard.new.expertSettings.shorteningThreshold.valid=בתוקף ### Password addvaultwizard.new.createVaultBtn=צור כספת חדשה addvaultwizard.new.generateRecoveryKeyChoice=לא תיהיה אפשרות לקבל גישה למידע שלך בלי הסיסמה שלך. האם תרצה/י מפתח שחזור למקרה ותאבד/י את הסיסמה שלך? @@ -80,6 +91,7 @@ addvault.new.readme.accessLocation.2=זהו מיקום גישה לכספת של addvault.new.readme.accessLocation.3=כל קובץ אשר יצורף לספרייה זו יעבור הצפנה באמצעות Cryptomator. את/ה תוכל/י לעבוד עליו כמו עם כל קבוץ/מחיצה רגילים. זהו מצב הצגה מפוענח של התוכן, הקבצים שלך נשארים מוצפנים על הדיסק הקשיח שלך בכל רגע. addvault.new.readme.accessLocation.4=תרגיש/י בנוח להסיר את הקובץ הזה. ## Existing +addvaultwizard.existing.title=הוסף כספת קיימת addvaultwizard.existing.instruction=בחר את קובץ "vault.cryptomator" של כספת קיימת. אם קיים קובץ בשם "masterkey.cryptomator" בלבד, בחר/י אותו במקום. addvaultwizard.existing.chooseBtn=בחר... addvaultwizard.existing.filePickerTitle=בחר קובץ כספת @@ -124,7 +136,10 @@ unlock.success.revealBtn=חשוף את הכונן unlock.error.customPath.message=כשלון בקישור הכספת לנתיב הידני שהוגדר unlock.error.customPath.description.notSupported=אם ברצונך להשתמש בנתיב ידני, אנא גש להעדפות ובחר סוג volume שתומך בכך. אחרת, לך לאפשרויות הכספת ובחר אפשרות יעד קישור נתמך. unlock.error.customPath.description.notExists=יעד הקישור הידני לא קיים. או שתיצור אותו במערכת הקבצים המקומית או שנה אותו באפשרויות הכספת. +unlock.error.customPath.description.inUse=אות דיסק או הנתיב המותאם "%s" כבר בשימוש. unlock.error.customPath.description.hideawayNotDir=הקובץ הזמני, הנסתר %3$s עבור פתיחה לא ניתן להסרה. יש לבדוק את הקובץ ולמחוק אותו ידנית. +unlock.error.customPath.description.couldNotBeCleaned=הקלדת חנייה שלך לא יכולה להיות מחוברת לנתיב "%s". אנא נסה שוב או בחר נתיב אחר. +unlock.error.customPath.description.notEmptyDir=הנתיב המותאם "%s" אינו תיקייה ריקה. אנא בחר תיקייה ריקה ונסה שוב. ## Hub hub.noKeychain.message=לא ניתן לגשת למפתח המכשיר hub.noKeychain.description=כדאי לשחרר כספות האב נדרש מפתח מכשיר שיאובטח בצרור מפתחות. כדאי להמשיך, אפשר ״%s״ ובחר את צרור המפתחות בהעדפות. @@ -137,8 +152,6 @@ hub.auth.loginLink=לא הופנת? לחצן כאן לפתיחה. hub.receive.message=מעבד תשובה… hub.receive.description=Cryptomator מקבל ומעבד את התשובה מה- האב. אנא המתן. ### Register Device -hub.register.message=שם המכשיר נדרש -hub.register.description=נראה שזו הגישה הראשונה ל- האב ממכשיר זה. כדי לזהות אותך למתן הרשאות, עליך לתת שם למכשיר זה. hub.register.nameLabel=שם מכשיר hub.register.occupiedMsg=שם זה נמצא כבר בשימוש hub.register.registerBtn=אישור @@ -151,6 +164,7 @@ hub.registerFailed.description=ארעה שגיאה בתהליך עם השם. ל ### Unauthorized hub.unauthorized.message=הגישה נדחתה hub.unauthorized.description=המכשיר שלך טרם אושר לגשת לכספת הזאת. יש לבקש אישור גישה מבעל הכספת. +### Requires Account Initialization ### License Exceeded hub.invalidLicense.message=רישיון האב לא תקף hub.invalidLicense.description=הרישיון שמותקן במופע ה- Cryptomator האב שלך אינו תקף. אנא ידע את מנהל ההאב שלך לשדרג או לחדש את הרישיון. @@ -350,6 +364,7 @@ main.vaultlist.contextMenu.unlockNow=בטל נעילה כעת main.vaultlist.contextMenu.vaultoptions=הצג את אפשרויות הכספת main.vaultlist.contextMenu.reveal=חשוף את הכונן main.vaultlist.addVaultBtn=הוספה +main.vaultlist.addVaultBtn.menuItemNew=פתיחת כספת... ## Vault Detail ### Welcome main.vaultDetail.welcomeOnboarding=תודה שבחרת ב- Cryptomator להגן על הקבצים שלך. אם אתה זקוק לסיוע, אנא עיין במדריכים שלנו: @@ -455,6 +470,8 @@ recoveryKey.recover.resetSuccess.message=איפוס סיסמה הצליח recoveryKey.recover.resetSuccess.description=ניתן לפתוח את הכספת עם הסיסמה החדשה. # Convert Vault +convertVault.convert.convertBtn.before=להמיר +convertVault.success.message=המרה הושלמה בהצלחה # New Password newPassword.promptText=הקש סיסמה חדשה @@ -479,4 +496,7 @@ quit.forced.message=חלק מה vaults לא היו ניתנים לנעילה quit.forced.description=נעילה כספות נחסמה על ידי תהליכים ממתינים או קבצים פתוחים. אתה יכול לנעול את הכספת בכוח, אולם הפרעה לפעולת קריאה וכתיבה עשויה לגרום לאובדן מידע לא שמור. quit.forced.forceAndQuitBtn=נעילה בכח ויציאה -# Update Reminder \ No newline at end of file +# Update Reminder +updateReminder.title=בדיקת עדכון +updateReminder.message=חפש עדכונים? +updateReminder.notNow=לא עכשיו \ No newline at end of file diff --git a/src/main/resources/i18n/strings_hi.properties b/src/main/resources/i18n/strings_hi.properties index 4aafeb341..aa1601acf 100644 --- a/src/main/resources/i18n/strings_hi.properties +++ b/src/main/resources/i18n/strings_hi.properties @@ -124,6 +124,7 @@ hub.register.registerBtn=पुष्टि करें ### Registration Failed ### Unauthorized hub.unauthorized.message=प्रवेश अस्वीकृत +### Requires Account Initialization ### License Exceeded # Lock diff --git a/src/main/resources/i18n/strings_hr.properties b/src/main/resources/i18n/strings_hr.properties index f317da9e0..682f19ff0 100644 --- a/src/main/resources/i18n/strings_hr.properties +++ b/src/main/resources/i18n/strings_hr.properties @@ -119,6 +119,7 @@ hub.register.registerBtn=Potvrdi ### Registration Success ### Registration Failed ### Unauthorized +### Requires Account Initialization ### License Exceeded # Lock diff --git a/src/main/resources/i18n/strings_hu.properties b/src/main/resources/i18n/strings_hu.properties index aea19a502..fdb53f898 100644 --- a/src/main/resources/i18n/strings_hu.properties +++ b/src/main/resources/i18n/strings_hu.properties @@ -22,6 +22,9 @@ error.hyperlink.report=Hiba jelentése error.technicalDetails=Részletek: error.existingSolutionDescription=A Cryptomator nem számított arra, hogy ez megtörténjen. Viszont már találtunk egy létező megoldást erre a problémára. Kérem, látogassa meg az alábbi linket. error.hyperlink.solution=Megoldás megnézése +error.lookupPermissionMessage=A Cryptomator tud online keresni megoldást erre a problémára. Ezáltal az IP-címedről kérést küld a probléma-adatbázisunkba. +error.dismiss=Elvet +error.lookUpSolution=Megoldás keresése # Defaults defaults.vault.vaultName=Széf @@ -38,6 +41,7 @@ traymenu.vault.reveal=Megmutatás # Add Vault Wizard addvaultwizard.title=Széf hozzáadása ## New +addvaultwizard.new.title=Széf hozzáadása ### Name addvaultwizard.new.nameInstruction=Válasszon egy nevet az új széf számára addvaultwizard.new.namePrompt=A széf neve @@ -87,6 +91,7 @@ addvault.new.readme.accessLocation.2=Ez a széf hozzáférési helye. addvault.new.readme.accessLocation.3=Bármilyen, a kötethez hozzáadott fájl titkosításra kerül a Cryptomator által. Úgy dolgozhat vele, mint minden más meghajtóval/mappával. Ez az egyetlen dekódolt tartalmi nézet. A fájlai folyamatosan titkosítva maradnak a merevlemezén. addvault.new.readme.accessLocation.4=Bátran eltávolíthatja ezt a fájlt. ## Existing +addvaultwizard.existing.title=Meglévő széf hozzáadása addvaultwizard.existing.instruction=Válassza ki a "vault.cryptomatotor" fájlt a meglévő tárolóhoz. Ha csak egy "masterkey.cryptomatotor" nevű fájl létezik, válassza azt. addvaultwizard.existing.chooseBtn=Kiválaszt… addvaultwizard.existing.filePickerTitle=Trezor fájl kiválasztása @@ -132,6 +137,7 @@ unlock.error.customPath.message=Nem lehet csatolni a széfet az egyéni útvonal 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.inUse=A meghajtó betűjele vagy a csatolási útvonal: "%s" már foglalt. +unlock.error.customPath.description.hideawayNotDir=A feloldáshoz használt, "%3$s" nevű ideiglenes, rejtett fájlt nem lehet eltávolítani. Kérjük, ellenőrizze a fájlt, majd törölje manuálisan. unlock.error.customPath.description.couldNotBeCleaned=A széfet nem lehetett a "%s" elérési útvonalra csatlakoztatni. Kérjük, próbálja meg újra, vagy válasszon másik elérési útvonalat. unlock.error.customPath.description.notEmptyDir=A megadott "%s" elérési útvonal nem egy üres mappa. Kérjük, válasszon egy üres mappát, és próbálja meg újra. unlock.error.customPath.description.generic=Egyéni csatolási útvonalat választott ehhez a széfhez, de használatakor ez a hibaüzenet érkezett: %2$s @@ -147,8 +153,7 @@ hub.auth.loginLink=Nem sikerült az átirányítás? Kattintson ide a megnyitás 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 -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.message=Új eszköz hub.register.nameLabel=Készülék neve hub.register.occupiedMsg=Ez a név már használatban van hub.register.registerBtn=Megerősítés @@ -161,6 +166,9 @@ hub.registerFailed.description=Hiba állt fel az elnevezési folyamatban. Továb ### 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. +### Requires Account Initialization +hub.requireAccountInit.message=Beavatkozás szükséges +hub.requireAccountInit.description.2=. ### 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. @@ -360,6 +368,8 @@ main.vaultlist.contextMenu.unlockNow=Azonnali feloldás main.vaultlist.contextMenu.vaultoptions=Széf beállítások main.vaultlist.contextMenu.reveal=Széf megjelenítése main.vaultlist.addVaultBtn=Hozzáadás +main.vaultlist.addVaultBtn.menuItemNew=Új széf... +main.vaultlist.addVaultBtn.menuItemExisting=Meglévő széf... ## Vault Detail ### Welcome main.vaultDetail.welcomeOnboarding=Köszönjük, hogy a Cryptomator programot választotta a fájlai védelmére. Ha segítségre van szüksége, akkor olvassa el a kezdő útmutatónk lépéseit: diff --git a/src/main/resources/i18n/strings_id.properties b/src/main/resources/i18n/strings_id.properties index 67569a23d..847aa504c 100644 --- a/src/main/resources/i18n/strings_id.properties +++ b/src/main/resources/i18n/strings_id.properties @@ -119,6 +119,7 @@ hub.register.registerBtn=Konfirmasi ### Registration Success ### Registration Failed ### Unauthorized +### Requires Account Initialization ### License Exceeded # Lock diff --git a/src/main/resources/i18n/strings_it.properties b/src/main/resources/i18n/strings_it.properties index dcbb5e69f..f7bb8210c 100644 --- a/src/main/resources/i18n/strings_it.properties +++ b/src/main/resources/i18n/strings_it.properties @@ -15,7 +15,7 @@ generic.button.next=Avanti generic.button.print=Stampa # Error -error.message=Errore %s +error.message=Si è verificato un errore error.description=Oops! Cryptomator non si aspettava che ciò accadesse. Puoi cercare soluzioni esistenti per questo errore. Oppure se non è ancora stato segnalato, sentitevi liberi di farlo. error.hyperlink.lookup=Cerca questo errore error.hyperlink.report=Segnala questo errore @@ -49,9 +49,9 @@ addvaultwizard.new.namePrompt=Nome della Cassaforte addvaultwizard.new.locationInstruction=Dove dovrebbe memorizzare Cryptomator i file crittografati della tua cassaforte? addvaultwizard.new.locationLabel=Posizione archivio addvaultwizard.new.locationPrompt=… -addvaultwizard.new.directoryPickerLabel=Posizione Personalizzata +addvaultwizard.new.directoryPickerLabel=Posizione personalizzata addvaultwizard.new.directoryPickerButton=Scegli… -addvaultwizard.new.directoryPickerTitle=Seleziona Cartella +addvaultwizard.new.directoryPickerTitle=Seleziona cartella addvaultwizard.new.fileAlreadyExists=Un file o una cartella con il nome della cassaforte esiste già addvaultwizard.new.locationDoesNotExist=Una cartella nel percorso specificato non esiste o non è accessibile addvaultwizard.new.locationIsNotWritable=Nessun accesso in scrittura nel percorso specificato @@ -61,7 +61,7 @@ addvaultwizard.new.validName=Nome cassaforte valido addvaultwizard.new.validCharacters.message=Il nome della cassaforte può contenere i seguenti caratteri: addvaultwizard.new.validCharacters.chars=Caratteri della parola (e.g. a, ж or 수) addvaultwizard.new.validCharacters.numbers=Numeri -addvaultwizard.new.validCharacters.dashes=Trattino (%s) o tratto basso (%s) +addvaultwizard.new.validCharacters.dashes=Trattino (%s) o trattino basso (%s) ### Expert Settings addvaultwizard.new.expertSettings.enableExpertSettingsCheckbox=Abilita le impostazioni avanzate addvaultwizard.new.expertSettings.shorteningThreshold.invalid=Inserisci un valore compreso tra 36 e 220 (predefinito 220) @@ -78,13 +78,13 @@ addvault.new.readme.storageLocation.fileName=IMPORTANTE.rtf addvault.new.readme.storageLocation.1=⚠️ FILE DELLA CASSAFORTE ⚠️ addvault.new.readme.storageLocation.2=Questa è la posizione d'archiviazione della tua cassaforte. addvault.new.readme.storageLocation.3=NON -addvault.new.readme.storageLocation.4=• alterare nessun file in questa cartella o +addvault.new.readme.storageLocation.4=• modificare alcun file in questa cartella o addvault.new.readme.storageLocation.5=• incollare alcun file per la crittografia in questa cartella. addvault.new.readme.storageLocation.6=Se si desidera crittografare i file e visualizzare il contenuto della cassaforte, effettuare le seguenti operazioni: addvault.new.readme.storageLocation.7=1. Aggiungi questa cassaforte a Cryptomator. -addvault.new.readme.storageLocation.8=2. Sblocca la cassaforte su Cryptomator. +addvault.new.readme.storageLocation.8=2. Sblocca la cassaforte in Criptomator. addvault.new.readme.storageLocation.9=3. Apri la posizione d'accesso cliccando sul pulsante "Rivela". -addvault.new.readme.storageLocation.10=Se ti serve aiuto, visita la documentazione: %s +addvault.new.readme.storageLocation.10=Se hai bisogno di aiuto, leggi la documentazione: %s addvault.new.readme.accessLocation.fileName=BENVENUTO.rtf addvault.new.readme.accessLocation.1=🔐 VOLUME CRITTOGRAFATO 🔐 addvault.new.readme.accessLocation.2=Questa è la posizione d'accesso della tua cassaforte. @@ -109,7 +109,7 @@ removeVault.confirmBtn=Rimuovi Cassaforte # Change Password changepassword.title=Modifica la Password changepassword.enterOldPassword=Inserisci la password corrente per "%s" -changepassword.finalConfirmation=Capisco che non potrò accedere ai miei dati se dimentico la mia password +changepassword.finalConfirmation=Ho capito che non sarò in grado di accedere ai miei dati se dimentico la mia password # Forget Password forgetPassword.title=Dimentica la Password @@ -120,7 +120,7 @@ forgetPassword.confirmBtn=Dimentica Password # Unlock unlock.title=Sblocca "%s" unlock.passwordPrompt=Inserisci la password per "%s": -unlock.savePassword=Ricorda la Password +unlock.savePassword=Ricorda la password unlock.unlockBtn=Sblocca ## Select unlock.chooseMasterkey.message=File Masterkey non trovato @@ -135,8 +135,8 @@ unlock.success.revealBtn=Rivela l'Unità ## Failure unlock.error.customPath.message=Impossibile montare la cassaforte sul percorso personalizzato unlock.error.customPath.description.notSupported=Se desideri continuare a utilizzare il percorso personalizzato, vai alle preferenze e seleziona un tipo di volume che lo supporta. Altrimenti, vai alle opzioni della cassaforte e scegli un punto di montaggio supportato. -unlock.error.customPath.description.notExists=Il percorso di mount personalizzato non esiste. Crealo nel tuo file system locale o cambialo nelle opzioni della cassaforte. -unlock.error.customPath.description.inUse=La lettera di unità o il percorso di montaggio selezionato "%s" è già in uso. +unlock.error.customPath.description.notExists=Il percorso di montaggio selezionato non esiste. Crealo nel tuo file system locale o cambialo nelle opzioni della cassaforte. +unlock.error.customPath.description.inUse=La lettera dell'unità o il percorso di montaggio selezionato "%s" è già in uso. unlock.error.customPath.description.hideawayNotDir=Impossibile rimuovere il file temporaneo nascosto "%3$s" utilizzato per lo sblocco. Controllare il file e quindi eliminarlo manualmente. unlock.error.customPath.description.couldNotBeCleaned=La tua cassaforte non può essere montata sul percorso "%s". Riprova o scegli un percorso diverso. unlock.error.customPath.description.notEmptyDir=Il percorso di montaggio selezionato "%s" non è una cartella vuota. Scegli una cartella vuota e riprova. @@ -153,9 +153,10 @@ hub.auth.loginLink=Non reindirizzato? Clicca qui per aprirlo. hub.receive.message=Elaborazione della risposta… hub.receive.description=Cryptomator sta ricevendo ed elaborando la risposta da Hub. Attendere prego. ### Register Device -hub.register.message=Nome del dispositivo richiesto -hub.register.description=Questo sembra essere il primo accesso Hub da questo dispositivo. Per identificarlo per l'autorizzazione di accesso, è necessario nominare questo dispositivo. +hub.register.message=Nuovo dispositivo +hub.register.description=Questo è il primo accesso all'Hub da questo dispositivo. Per favore autorizzalo utilizzando la tua chiave dell'account. hub.register.nameLabel=Nome Del Dispositivo +hub.register.invalidAccountKeyLabel=Chiave dell'account non valida hub.register.occupiedMsg=Nome già in uso hub.register.registerBtn=Conferma ### Registration Success @@ -167,6 +168,11 @@ hub.registerFailed.description=Si è verificato un errore nel processo di nomina ### Unauthorized hub.unauthorized.message=Accesso negato hub.unauthorized.description=Il tuo dispositivo non è ancora stato autorizzato ad accedere a questa cassaforte. Chiedi al proprietario della cassaforte di autorizzarlo. +### Requires Account Initialization +hub.requireAccountInit.message=Azione richiesta +hub.requireAccountInit.description.0=Per procedere, completa i passaggi richiesti nel tuo +hub.requireAccountInit.description.1=profilo utente Hub +hub.requireAccountInit.description.2=. ### License Exceeded hub.invalidLicense.message=Licenza Hub non valida hub.invalidLicense.description=La tua istanza Cryptomator Hub ha una licenza non valida. Si prega di informare un amministratore Hub per aggiornare o rinnovare la licenza. diff --git a/src/main/resources/i18n/strings_ja.properties b/src/main/resources/i18n/strings_ja.properties index aa7af70d2..02b78820a 100644 --- a/src/main/resources/i18n/strings_ja.properties +++ b/src/main/resources/i18n/strings_ja.properties @@ -153,8 +153,7 @@ hub.auth.loginLink=リダイレクトされませんでしたか? ここをク hub.receive.message=応答を処理中… hub.receive.description=Cryptomator が Hub からの応答を受信、処理中です。しばらくお待ちください。 ### Register Device -hub.register.message=デバイスの名前が必要です -hub.register.description=このデバイスからハブにアクセスするのは初めてのようです。アクセス認証の際のデバイス識別のためにこのデバイスに名前を付ける必要があります。 +hub.register.message=新しいデバイス hub.register.nameLabel=デバイス名 hub.register.occupiedMsg=この名前は既に使われています hub.register.registerBtn=確認 @@ -167,6 +166,11 @@ hub.registerFailed.description=デバイス名登録中にエラーが発生し ### Unauthorized hub.unauthorized.message=アクセスが拒否されました hub.unauthorized.description=お使いのデバイスはまだこの金庫にアクセスする権限がありません。金庫のオーナーに権限を与えてもらってください。 +### Requires Account Initialization +hub.requireAccountInit.message=アクションが必要です +hub.requireAccountInit.description.0=続行するには以下のサイトで必要な手順を完了してください +hub.requireAccountInit.description.1=Hub ユーザープロフィール +hub.requireAccountInit.description.2=。 ### License Exceeded hub.invalidLicense.message=Hub のライセンスが無効です hub.invalidLicense.description=Cryptomator Hub インスタンスのライセンスが無効です。ライセンスをアップグレードまたは更新するには、Hub の管理者にご連絡ください。 diff --git a/src/main/resources/i18n/strings_ko.properties b/src/main/resources/i18n/strings_ko.properties index ccf375690..2cbb0a057 100644 --- a/src/main/resources/i18n/strings_ko.properties +++ b/src/main/resources/i18n/strings_ko.properties @@ -20,6 +20,7 @@ error.description=예상치 못한 에러가 발생했습니다. 온라인에 error.hyperlink.lookup=에러 검색하기 error.hyperlink.report=에러 보고하기 error.technicalDetails=상세 정보: +error.dismiss=무시 # Defaults defaults.vault.vaultName=Vault @@ -36,6 +37,7 @@ traymenu.vault.reveal=표시 # Add Vault Wizard addvaultwizard.title=Vault 추가 ## New +addvaultwizard.new.title=새로운 금고 추가 ### Name addvaultwizard.new.nameInstruction=새 Vault의 이름을 입력하십시요 addvaultwizard.new.namePrompt=Vault 이름 @@ -57,6 +59,7 @@ addvaultwizard.new.validCharacters.chars=문자 (예시: a, ж or 수) addvaultwizard.new.validCharacters.numbers=숫자 addvaultwizard.new.validCharacters.dashes=대시 (%s) 또는 언더바 (%s) ### Expert Settings +addvaultwizard.new.expertSettings.enableExpertSettingsCheckbox=전문가용 설정 활성화 ### Password addvaultwizard.new.createVaultBtn=Vault 생성 addvaultwizard.new.generateRecoveryKeyChoice=비밀번호가 없으면 데이터에 접근할 수 없습니다. 비밀번호를 잊었을 때를 대비한 복구 키를 원하십니까? @@ -80,6 +83,7 @@ addvault.new.readme.accessLocation.2=이것은 당신의 Vault 접근 위치입 addvault.new.readme.accessLocation.3=이 볼륨에 추가된 모든 파일은 Cryptomator로 암호화됩니다. 다른 드라이브/폴더처럼 작업할 수 있습니다. 볼륨의 내용은 복호화 된 것 처럼 보여지지만, 모든 파일은 항상 암호화되어 하드디스크에 저장됩니다. addvault.new.readme.accessLocation.4=이 파일은 지우셔도 무방합니다. ## Existing +addvaultwizard.existing.title=기존 금고 추가 addvaultwizard.existing.instruction=이미 존재하는 vault 폴더 내에서 "vault.cryptomator" 파일을 선택하세요. 만약 "masterkey.cryptomator"만 있다면 그걸 대신 선택하세요. addvaultwizard.existing.chooseBtn=선택 addvaultwizard.existing.filePickerTitle=Vault 파일 선택 @@ -122,6 +126,7 @@ unlock.success.rememberChoice=선택 기억함, 다시 묻지 않음 unlock.success.revealBtn=드라이브 표시 ## Failure ## Hub +hub.noKeychain.openBtn=설정 열기 ### Waiting ### Receive Key ### Register Device @@ -133,6 +138,7 @@ hub.register.registerBtn=확인 ### Unauthorized hub.unauthorized.message=액세스 거부 hub.unauthorized.description=귀하의 기기는 아직 이 저장소에 액세스할 수 있는 권한이 없습니다. Vault 소유자에게 승인을 요청하세요. +### Requires Account Initialization ### License Exceeded hub.invalidLicense.message=Hub 라이선스가 잘못되었습니다. hub.invalidLicense.description=Cryptomator Hub 인스턴스에 잘못된 라이선스가 있습니다. 라이센스를 업그레이드하거나 갱신하려면 허브 관리자에게 알리십시오. @@ -204,6 +210,7 @@ health.check.detail.checkCancelled=검사가 취소되었습니다 health.check.detail.listFilters.label=필터 health.check.exportBtn=보고서 내보내기 ## Result view +health.result.severityFilter.warn=경고 ## Fix Application health.fix.fixBtn=문제해결 health.fix.successTip=문제 해결이 성공적으로 완료되었습니다 @@ -238,6 +245,8 @@ preferences.volume.type=볼륨 유형 preferences.volume.type.automatic=자동 preferences.volume.tcp.port=TCP 포트 preferences.volume.supportedFeatures=현재 선택한 볼륨 타입은 다음과 같은 기능들을 지원합니다: +preferences.volume.feature.mountToDir=마운트할 폴더 지정 +preferences.volume.feature.mountToDriveLetter=마운트할 드라이브 문자 preferences.volume.feature.mountFlags=사용자 정의 마운트 설정 preferences.volume.feature.readOnly=읽기 전용 마운트 ## Updates @@ -262,16 +271,20 @@ stats.title=%s에 대한 통계 stats.cacheHitRate=캐시 히트율 ## Read stats.read.throughput.idle=읽기: 대기중 +stats.read.throughput.kibs=읽기: %.2f KiB/s stats.read.throughput.mibs=읽기: %.2f MiB/s stats.read.total.data.none=데이터 읽기: - +stats.read.total.data.kib=데이터 읽기: %.1f KiB stats.read.total.data.mib=데이터 읽기: %.1f MiB stats.read.total.data.gib=데이터 읽기: %.1f GiB stats.decr.total.data.none=데이터 복호화: - +stats.decr.total.data.kib=데이터 복호화: %.1f KiB stats.decr.total.data.mib=데이터 복호화: %.1f MiB stats.decr.total.data.gib=데이터 복호화: %.1f GiB stats.read.accessCount=총 읽기 횟수: %d ## Write stats.write.throughput.idle=쓰기: 대기중 +stats.write.throughput.kibs=쓰기: %.2f KiB/s stats.write.throughput.mibs=쓰기: %.2f MiB/s stats.write.total.data.none=데이터 기록됨: - stats.write.total.data.kib=데이터 쓰기: %.1f KiB @@ -284,6 +297,7 @@ stats.encr.total.data.gib=데이터 암호화: %.1f GiB stats.write.accessCount=총 쓰기 횟수: %d ## Accesses +stats.access.current=접근: %d # Main Window @@ -301,6 +315,8 @@ main.vaultlist.contextMenu.unlockNow=지금 잠금해제 main.vaultlist.contextMenu.vaultoptions=Vault 옵션 보기 main.vaultlist.contextMenu.reveal=드라이브 표시 main.vaultlist.addVaultBtn=추가 +main.vaultlist.addVaultBtn.menuItemNew=새로운 금고... +main.vaultlist.addVaultBtn.menuItemExisting=기존 금고... ## Vault Detail ### Welcome main.vaultDetail.welcomeOnboarding=파일을 보호하기 위해 Cryptomator를 선택해주셔서 감사합니다. 만약 다른 도움이 필요하시면, 시작안내서를 참조하시기 바랍니다. @@ -322,6 +338,7 @@ main.vaultDetail.throughput.idle=대기 main.vaultDetail.throughput.kbps=%.1f KiB/s main.vaultDetail.throughput.mbps=%.1f MiB/s main.vaultDetail.stats=Vault 통계 +main.vaultDetail.locateEncryptedFileBtn=암호화된 파일 위치 ### Missing main.vaultDetail.missing.info=Cryptomator가 이 경로에 있는 Vault를 찾지 못했습니다. main.vaultDetail.missing.recheck=다시 시도 @@ -400,6 +417,9 @@ recoveryKey.recover.resetSuccess.message=비밀번호 재설정 성공 recoveryKey.recover.resetSuccess.description=이제 해당 vault를 새 비밀번호로 잠금 해제할 수 있습니다. # Convert Vault +convertVault.convert.convertBtn.before=변환 +convertVault.convert.convertBtn.processing=변환중… +convertVault.success.message=변환 완료 # New Password newPassword.promptText=새 비밀번호를 입력하세요 @@ -421,4 +441,6 @@ quit.lockAndQuitBtn=Vault 잠금 후 종료하기 # Forced Quit quit.forced.forceAndQuitBtn=Vault 강제 잠금 후 종료하기 -# Update Reminder \ No newline at end of file +# Update Reminder +updateReminder.title=업데이트 확인 +updateReminder.notNow=나중에 \ No newline at end of file diff --git a/src/main/resources/i18n/strings_lv.properties b/src/main/resources/i18n/strings_lv.properties index aa6a8b26b..85d470355 100644 --- a/src/main/resources/i18n/strings_lv.properties +++ b/src/main/resources/i18n/strings_lv.properties @@ -113,6 +113,7 @@ hub.register.registerBtn=Apstiprināt ### Registration Success ### Registration Failed ### Unauthorized +### Requires Account Initialization ### License Exceeded # Lock diff --git a/src/main/resources/i18n/strings_mk.properties b/src/main/resources/i18n/strings_mk.properties index 55252aba3..90ae09ac8 100644 --- a/src/main/resources/i18n/strings_mk.properties +++ b/src/main/resources/i18n/strings_mk.properties @@ -77,6 +77,7 @@ hub.register.registerBtn=Потврди ### Registration Success ### Registration Failed ### Unauthorized +### Requires Account Initialization ### License Exceeded # Lock diff --git a/src/main/resources/i18n/strings_nb.properties b/src/main/resources/i18n/strings_nb.properties index 4a146b15c..600822e3a 100644 --- a/src/main/resources/i18n/strings_nb.properties +++ b/src/main/resources/i18n/strings_nb.properties @@ -153,8 +153,7 @@ hub.auth.loginLink=Ikke videresendt? Klikk her for å åpne den. hub.receive.message=Prosesserer svar… hub.receive.description=Cryptomator mottar og behandler svaret fra Hub. Vennligst vent. ### Register Device -hub.register.message=Enhetsnavn påkrevd -hub.register.description=Dette ser ut til å være den første Hub-tilgangen fra denne enheten. For å kunne identifisere den for tilgangsautorisasjon, må du å navngi denne enheten. +hub.register.message=Ny Enhet hub.register.nameLabel=Enhetsnavn hub.register.occupiedMsg=Navnet er allerede i bruk hub.register.registerBtn=Bekreft @@ -167,6 +166,7 @@ hub.registerFailed.description=Under navngivingsprosessen oppsto det en feilmeld ### Unauthorized hub.unauthorized.message=Ingen tilgang hub.unauthorized.description=Enheten din har ikke blitt autorisert til å få tilgang til dette hvelvet ennå. Spør hvelveieren om å tillate det. +### Requires Account Initialization ### License Exceeded hub.invalidLicense.message=Hub-lisens er ugyldig hub.invalidLicense.description=Cryptomator Hub instansen din har en ugyldig lisens. Vennligst informer en Hub-administrator om å oppgradere eller fornye lisensen. diff --git a/src/main/resources/i18n/strings_nl.properties b/src/main/resources/i18n/strings_nl.properties index 02d858db5..82f4585bd 100644 --- a/src/main/resources/i18n/strings_nl.properties +++ b/src/main/resources/i18n/strings_nl.properties @@ -153,8 +153,7 @@ hub.auth.loginLink=Niet omgeleid? Klik hier om het te openen. hub.receive.message=Antwoord verwerken… hub.receive.description=Cryptomator ontvangt en verwerkt de reactie van Hub. Een ogenblik geduld. ### Register Device -hub.register.message=Apparaatnaam vereist -hub.register.description=Dit lijkt de eerste Hub toegang te zijn vanaf dit apparaat. Om dit apparaat te kunnen identificeren voor autorisatie, moet u dit apparaat benoemen. +hub.register.message=Nieuw apparaat hub.register.nameLabel=Apparaatnaam hub.register.occupiedMsg=Naam al in gebruik hub.register.registerBtn=Bevestig @@ -167,6 +166,9 @@ hub.registerFailed.description=Er is een fout in het naamproces geworpen. Kijk i ### Unauthorized hub.unauthorized.message=Toegang geweigerd hub.unauthorized.description=Uw apparaat is nog niet gemachtigd om toegang te krijgen tot deze kluis. Vraag de eigenaar van de kluis om toestemming te geven. +### Requires Account Initialization +hub.requireAccountInit.message=Actie vereist +hub.requireAccountInit.description.2=. ### License Exceeded hub.invalidLicense.message=Hub Licentie ongeldig hub.invalidLicense.description=Uw Cryptomator Hub installatie heeft een ongeldige licentie. Informeer een Hub administrator om de licentie te upgraden of te verlengen. diff --git a/src/main/resources/i18n/strings_nn.properties b/src/main/resources/i18n/strings_nn.properties index 7d7539d70..dad0e246a 100644 --- a/src/main/resources/i18n/strings_nn.properties +++ b/src/main/resources/i18n/strings_nn.properties @@ -100,6 +100,7 @@ unlock.success.revealBtn=Gjer eininga synleg ### Registration Success ### Registration Failed ### Unauthorized +### Requires Account Initialization ### License Exceeded # Lock diff --git a/src/main/resources/i18n/strings_pa.properties b/src/main/resources/i18n/strings_pa.properties index c27cd4729..98b2e4a4b 100644 --- a/src/main/resources/i18n/strings_pa.properties +++ b/src/main/resources/i18n/strings_pa.properties @@ -102,6 +102,7 @@ unlock.success.revealBtn=ਡਰਾਇਵ ਦਿਖਾਓ ### Registration Success ### Registration Failed ### Unauthorized +### Requires Account Initialization ### License Exceeded # Lock diff --git a/src/main/resources/i18n/strings_pl.properties b/src/main/resources/i18n/strings_pl.properties index a50b9f9ad..73106ba00 100644 --- a/src/main/resources/i18n/strings_pl.properties +++ b/src/main/resources/i18n/strings_pl.properties @@ -153,8 +153,7 @@ hub.auth.loginLink=Nie przekierowano? Kliknij tutaj. hub.receive.message=Przetwarzanie odpowiedzi… hub.receive.description=Cryptomator odbiera i przetwarza odpowiedź z Huba, proszę czekać. ### Register Device -hub.register.message=Wymagana nazwa urządzenia -hub.register.description=Wygląda że jest to pierwszy dostęp do Huba z tego urządzenia. Aby zidentyfikować go w celu uzyskania autoryzacji dostępu, musisz nazwać to urządzenie. +hub.register.message=Nowe Urządzenie hub.register.nameLabel=Nazwa urządzenia hub.register.occupiedMsg=Nazwa jest już używana hub.register.registerBtn=Zatwierdź @@ -167,6 +166,7 @@ hub.registerFailed.description=Wystąpił błąd podczas ustawiania nazwy. Aby u ### Unauthorized hub.unauthorized.message=Brak dostępu hub.unauthorized.description=Twoje urządzenie nie zostało jeszcze upoważnione do dostępu do tego sejfu. Poproś właściciela sejfu o autoryzację. +### Requires Account Initialization ### License Exceeded hub.invalidLicense.message=Nieważna licencja Huba hub.invalidLicense.description=Twoja instancja Hub ma nieprawidłową licencję. Poproś administratora Hub o uaktualnienie lub odnowienie licencji. @@ -340,10 +340,10 @@ stats.write.total.data.none=Zapisane dane: - stats.write.total.data.kib=Zapis danych: %.1f KiB stats.write.total.data.mib=Zapisane dane: %.1f MiB stats.write.total.data.gib=Zapisane dane: %.1f GiB -stats.encr.total.data.none=Dane odszyfrowane: - +stats.encr.total.data.none=Dane zaszyfrowane: - stats.encr.total.data.kib=Dane zaszyfrowane: %.1f KiB -stats.encr.total.data.mib=Dane odszyfrowane: %.1f MiB -stats.encr.total.data.gib=Dane odszyfrowane: %.1f GiB +stats.encr.total.data.mib=Dane zaszyfrowane: %.1f MiB +stats.encr.total.data.gib=Dane zaszyfrowane: %.1f GiB stats.write.accessCount=Całkowity zapis: %d ## Accesses diff --git a/src/main/resources/i18n/strings_pt.properties b/src/main/resources/i18n/strings_pt.properties index c10bf780a..f237e11d5 100644 --- a/src/main/resources/i18n/strings_pt.properties +++ b/src/main/resources/i18n/strings_pt.properties @@ -151,8 +151,7 @@ hub.auth.loginLink=Não foi redirecionado? Clique aqui para abrir. hub.receive.message=A processar a resposta… hub.receive.description=Cryptomator está a receber e a processar a resposta do Hub. Por favor aguarde. ### Register Device -hub.register.message=Nome do dispositivo necessário -hub.register.description=Parece ser o primeiro acesso ao Hub a partir deste dispositivo. Para identificá-lo para autorização de acesso, é preciso dar um nome a este dispositivo. +hub.register.message=Novo dispositivo hub.register.nameLabel=Nome do dispositivo hub.register.occupiedMsg=Nome já utilizado hub.register.registerBtn=Confirmar @@ -165,9 +164,12 @@ hub.registerFailed.description=Houve um erro no processo de nomear. Para mais de ### Unauthorized hub.unauthorized.message=Acesso negado hub.unauthorized.description=O seu dispositivo ainda não foi autorizado a aceder a este cofre. Peça ao proprietário do cofre para o autorizar. +### Requires Account Initialization +hub.requireAccountInit.message=Ação requerida +hub.requireAccountInit.description.2=. ### License Exceeded hub.invalidLicense.message=Licença Hub inválida -hub.invalidLicense.description=O pedido do seu Cryptomator Hub tem uma licença inválida. Informe um administrador do Hub para atualizar ou renovar a licença. +hub.invalidLicense.description=A entidade do seu Cryptomator Hub tem uma licença inválida. Por favor, informe o administrador do Hub para atualizar ou renovar a licença. # Lock ## Force diff --git a/src/main/resources/i18n/strings_pt_BR.properties b/src/main/resources/i18n/strings_pt_BR.properties index 970c50888..ea8df1fbd 100644 --- a/src/main/resources/i18n/strings_pt_BR.properties +++ b/src/main/resources/i18n/strings_pt_BR.properties @@ -153,9 +153,10 @@ hub.auth.loginLink=Não foi redirecionado? Clique aqui para abrir. hub.receive.message=Processando resposta… hub.receive.description=Cryptomator está recebendo e processando a resposta do Hub. Por favor, aguarde. ### Register Device -hub.register.message=Nome do dispositivo necessário -hub.register.description=Este parece ser o seu primeiro acesso ao Hub deste dispositivo. Para ser identificado e ter autorização de acesso, você precisa nomear este dispositivo. +hub.register.message=Novo Dispositivo +hub.register.description=Este é o primeiro acesso do Hub deste dispositivo. Por favor, autorize-o usando sua Chave de Conta. hub.register.nameLabel=Nome do dispositivo +hub.register.invalidAccountKeyLabel=Chave de Conta inválida hub.register.occupiedMsg=Este nome já está em uso hub.register.registerBtn=Confirme ### Registration Success @@ -167,6 +168,9 @@ hub.registerFailed.description=Ocorreu um erro no processo de nomeação do disp ### Unauthorized 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. +### Requires Account Initialization +hub.requireAccountInit.message=Ação necessária +hub.requireAccountInit.description.2=. ### 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. diff --git a/src/main/resources/i18n/strings_ro.properties b/src/main/resources/i18n/strings_ro.properties index ff5c45a6d..f63ca5e7e 100644 --- a/src/main/resources/i18n/strings_ro.properties +++ b/src/main/resources/i18n/strings_ro.properties @@ -41,6 +41,7 @@ traymenu.vault.reveal=Afişare # Add Vault Wizard addvaultwizard.title=Adaugă un seif ## New +addvaultwizard.new.title=Adaugă seif nou ### Name addvaultwizard.new.nameInstruction=Alege un nume pentru seif addvaultwizard.new.namePrompt=Nume seif @@ -55,8 +56,8 @@ addvaultwizard.new.fileAlreadyExists=Există deja un fișier sau un dosar cu num addvaultwizard.new.locationDoesNotExist=Dosarul în calea specificată nu există sau nu poate fi accesat addvaultwizard.new.locationIsNotWritable=Nu există acces la scriere la calea specificată addvaultwizard.new.locationIsOk=Locația potrivită pentru seiful dumneavoastră -addvaultwizard.new.invalidName=Nume de seif invalid -addvaultwizard.new.validName=Nume de seif valid +addvaultwizard.new.invalidName=Nume nevalid pentru seif +addvaultwizard.new.validName=Nume valid pentru seif addvaultwizard.new.validCharacters.message=Numele seifului poate conține următoarele caractere: addvaultwizard.new.validCharacters.chars=Caractere (e.x. a, ж or 수) addvaultwizard.new.validCharacters.numbers=Numere @@ -90,6 +91,7 @@ addvault.new.readme.accessLocation.2=Aceasta este locația de acces a seifului d addvault.new.readme.accessLocation.3=Orice fișier adăugat la acest volum va fi criptat de către Cryptomator. Puteți lucra la el ca pe orice altă unitate/folder. Aceasta este doar o vizualizare decriptată a conținutului său, fișierele sunt criptate tot timpul pe hard disk-ul tău. addvault.new.readme.accessLocation.4=Puteți să ștergeți acest fișier. ## Existing +addvaultwizard.existing.title=Adaugă seif existent addvaultwizard.existing.instruction=Alegeți fișierul "vault.cryptomator" al seifului dvs. existent. Dacă există doar un fișier numit "masterkey.cryptomator", alegeți-l pe acesta. addvaultwizard.existing.chooseBtn=Alege… addvaultwizard.existing.filePickerTitle=Selectați fișierul seif @@ -134,6 +136,11 @@ unlock.success.revealBtn=Dezvăluie partiția unlock.error.customPath.message=Nu se poate monta seiful în locația alesă unlock.error.customPath.description.notSupported=Dacă doriți să continuați să utilizați locația aleasă, vă rugăm să mergeți la preferințe și să selectați o unitate de stocare suportată. În caz contrar, mergeți la opțiunile seifului și alegeți o locație de montare suportată. unlock.error.customPath.description.notExists=Locația de montare aleasă nu există. Creați-o în sistemul de fișiere local sau schimbați-o din opțiunile seifului. +unlock.error.customPath.description.inUse=Litera de unitate sau locația de montare personalizată ''%s'' este deja folosită. +unlock.error.customPath.description.hideawayNotDir=Fișierul ascuns, temporar "%3$s" folosit pentru deblocare nu a putut fi șters. Vă rugăm să verificați fișierul și să îl ștergeți manual. +unlock.error.customPath.description.couldNotBeCleaned=Seiful dvs. nu a putut fi montat în locația ''%s''. Vă rugăm încercați din nou sau alegeți o altă locație. +unlock.error.customPath.description.notEmptyDir=Locația personalizată ''%s'' nu este un dosar gol. Vă rugăm alegeți un dosar gol și încercați din nou. +unlock.error.customPath.description.generic=Ați selectat o locație de montare personalizată pentru acest seif, dar utilizarea acesteia a eșuat cu mesajul: %2$s ## Hub hub.noKeychain.message=Cheia dispozitivului nu a putut fi accesată hub.noKeychain.description=Pentru a debloca seifele de tip Hub, este necesară o cheie a dispozitivului, care este securizată prin intermediul unui keychain. Pentru a continua, activați „%s” și selectați un keychain în preferințe. @@ -146,8 +153,6 @@ hub.auth.loginLink=Nu ați fost redirecționat? Apăsați aici pentru a deschide hub.receive.message=Se procesează răspunsul… hub.receive.description=In acest moment Criptomatorul primește și procesează răspunsul de la Hub. Vă rugăm să așteptați. ### Register Device -hub.register.message=Numele dispozitivului este necesar -hub.register.description=Se pare că este prima data când accesați Hub-ul de pe acest dispozitiv. Trebuie sa denumiți acest dispozitiv pentru autorizarea accesului. hub.register.nameLabel=Numele dispozitivului hub.register.occupiedMsg=Acest nume este deja utilizat hub.register.registerBtn=Confirmați @@ -160,6 +165,7 @@ hub.registerFailed.description=O eroare a fost întâmpinata în procesul de den ### Unauthorized hub.unauthorized.message=Acces respins hub.unauthorized.description=Dispozitivul dvs. nu a fost autorizat să acceseze acest seif. Solicitați proprietarului seifului să va autorizeze accesul. +### Requires Account Initialization ### License Exceeded hub.invalidLicense.message=Licență de Hub invalidă hub.invalidLicense.description=Instanța Hub are o licență invalidă. Vă rugăm să informați un administrator Hub să actualizeze sau să reînnoiască licența. @@ -359,6 +365,8 @@ main.vaultlist.contextMenu.unlockNow=Deblochează acum main.vaultlist.contextMenu.vaultoptions=Arată opțiunile seifului main.vaultlist.contextMenu.reveal=Dezvăluie unitatea main.vaultlist.addVaultBtn=Adaugă +main.vaultlist.addVaultBtn.menuItemNew=Seif nou... +main.vaultlist.addVaultBtn.menuItemExisting=Seif existent... ## Vault Detail ### Welcome main.vaultDetail.welcomeOnboarding=Vă mulțumim că ați ales Cryptomator pentru a vă proteja fișierele. Dacă aveți nevoie de asistență, verificați ghidurile noastre de pornire: diff --git a/src/main/resources/i18n/strings_ru.properties b/src/main/resources/i18n/strings_ru.properties index c1c352e01..e371ba77a 100644 --- a/src/main/resources/i18n/strings_ru.properties +++ b/src/main/resources/i18n/strings_ru.properties @@ -153,8 +153,7 @@ hub.auth.loginLink=Не перенаправлено? Нажмите здесь, hub.receive.message=Обработка ответа… hub.receive.description=Cryptomator принимает и обрабатывает ответ от хаба. Подождите. ### Register Device -hub.register.message=Требуется имя устройства -hub.register.description=Похоже, это первый доступ к хабу с данного устройства. Чтобы идентифицирваоть его для предоставления доступа, нужно дать устройству имя. +hub.register.message=Новое устройство hub.register.nameLabel=Имя устройства hub.register.occupiedMsg=Имя уже используется hub.register.registerBtn=Подтвердить @@ -167,6 +166,11 @@ hub.registerFailed.description=Ошибка присвоения имени. Б ### Unauthorized hub.unauthorized.message=Доступ запрещен hub.unauthorized.description=Устройство ещё не авторизовано для доступа к этому хранилищу. Попросите владельца хранилища разрешить его. +### Requires Account Initialization +hub.requireAccountInit.message=Требуется действие +hub.requireAccountInit.description.0=Для продолжения выполните необходимые шаги в +hub.requireAccountInit.description.1=профиле пользователя хаба +hub.requireAccountInit.description.2=. ### License Exceeded hub.invalidLicense.message=Лицензия хаба недействительна hub.invalidLicense.description=У вашего хаба Cryptomator неверная лицензия. Попросите администратора хаба обновить или продлить лицензию. diff --git a/src/main/resources/i18n/strings_si.properties b/src/main/resources/i18n/strings_si.properties index 6d8708f2e..567a1b9a5 100644 --- a/src/main/resources/i18n/strings_si.properties +++ b/src/main/resources/i18n/strings_si.properties @@ -52,6 +52,7 @@ unlock.unlockBtn=අගුළුහරින්න ### Registration Success ### Registration Failed ### Unauthorized +### Requires Account Initialization ### License Exceeded # Lock diff --git a/src/main/resources/i18n/strings_sk.properties b/src/main/resources/i18n/strings_sk.properties index 66daeee97..c88e398ab 100644 --- a/src/main/resources/i18n/strings_sk.properties +++ b/src/main/resources/i18n/strings_sk.properties @@ -41,6 +41,7 @@ traymenu.vault.reveal=Odkryť # Add Vault Wizard addvaultwizard.title=Pridať trezor ## New +addvaultwizard.new.title=Pridať nový trezor ### Name addvaultwizard.new.nameInstruction=Zvoľte názov pre trezor addvaultwizard.new.namePrompt=Názov trezoru @@ -90,6 +91,7 @@ addvault.new.readme.accessLocation.2=Toto je prístupové miesto vášho trezoru addvault.new.readme.accessLocation.3=Všetky súbory pridané do tohto zväzku budú šifrované programom Cryptomator. Môžete na tom pracovať ako na akomkoľvek inom disku / priečinku. Toto je iba dešifrované zobrazenie jeho obsahu, vaše súbory zostávajú stále šifrované na pevnom disku. addvault.new.readme.accessLocation.4=Tento súbor môžete kedykoľvek odstrániť. ## Existing +addvaultwizard.existing.title=Pridať existujúci trezor addvaultwizard.existing.instruction=Zvoľte "vault.cryptomator" súbor Vášho existujúceho trezora. Ak existuje iba súbor s menom "masterkey.cryptomator", vyberte ho namiesto. addvaultwizard.existing.chooseBtn=Vybrať… addvaultwizard.existing.filePickerTitle=Zvoľte súbor trezora @@ -133,7 +135,12 @@ unlock.success.revealBtn=Odkryť disk ## Failure unlock.error.customPath.message=Nie je možné namapovať trezor na uživateĺskej ceste unlock.error.customPath.description.notSupported=Ak chcete naďalej používať vlastnú cestu, prejdite do nastavení a vyberte typ zväzku, ktorý ju podporuje. V opačnom prípade prejdite na možnosti trezoru a vyberte podporovaný bod pripojenia. +unlock.error.customPath.description.notExists=Vlastná cesta pripojenia neexistuje. Buď ju vytvorte v miestnom súborovom systéme, alebo ju zmeňte v možnostiach trezora. +unlock.error.customPath.description.inUse=Písmeno disku alebo vlastná cesta pripojenia "%s" sa už používa. unlock.error.customPath.description.hideawayNotDir=Dočasne, skrytý súbor "%3$s" použitý pre odomknutie nemôže byť odstránený. Prosím skontrolujte súbor a následne zmažte manuálne. +unlock.error.customPath.description.couldNotBeCleaned=Váš trezor sa nepodarilo pripojiť na cestu "%s". Skúste to prosím znova alebo vyberte inú cestu. +unlock.error.customPath.description.notEmptyDir=Vlastná cesta pripojenia "%s" nie je prázdny priečinok. Vyberte prázdny priečinok a skúste to znova. +unlock.error.customPath.description.generic=Pre tento trezor ste vybrali vlastnú cestu pripojenia, ale jej použitie zlyhalo so správou: %2$s ## Hub hub.noKeychain.message=Nemôžem pristúpiť ku kľúču zariadenia hub.noKeychain.description=V poradí odomknutia Hub trezorov, je požadovaný kľúč zariadenia ktorý je zabezpečený použitím keychain. K vykonaniu povoľte "%s" a zvoľte keychain v nastaveniach. @@ -146,8 +153,7 @@ hub.auth.loginLink=Nepresmerované? Kliknúť tu pre otvorenie. hub.receive.message=Spracovávanie odpovede… hub.receive.description=Cryptomator prijíma a spracúva odpovede z Hub-u. Prosím počkajte. ### Register Device -hub.register.message=Názov zariadenia je požadovaný -hub.register.description=Zdá sa, že ide o prvý prístup k Hub-u z tohto zariadenia. Z dôvodu identifikácie prístupovej autorizácie, je potrebné pomenovať toto zariadenie. +hub.register.message=Nové zariadenie hub.register.nameLabel=Názov zariadenia hub.register.occupiedMsg=Názov už existuje hub.register.registerBtn=Potvrdiť @@ -160,6 +166,7 @@ hub.registerFailed.description=Vznikla chyba počas pomenovávacieho procesu. Pr ### Unauthorized hub.unauthorized.message=Prístup zamietnutý hub.unauthorized.description=Vaše zaradenie zatiaľ ešte nebolo autorizované pre pristúp tohto trezora. Požiadajte majiteľa trezora o autorizovanie. +### Requires Account Initialization ### License Exceeded hub.invalidLicense.message=Neplatná licencia Hub-u hub.invalidLicense.description=Vaša inštancia Cryptomator Hub-u má neplatnú licenciu. Prosím informujte Hub administrátora pre aktualizáciu alebo obnovenie licencie. @@ -359,6 +366,8 @@ main.vaultlist.contextMenu.unlockNow=Odomknúť teraz main.vaultlist.contextMenu.vaultoptions=Ukáž možnosti trezora main.vaultlist.contextMenu.reveal=Odkry disk main.vaultlist.addVaultBtn=Pridať +main.vaultlist.addVaultBtn.menuItemNew=Nový trezor... +main.vaultlist.addVaultBtn.menuItemExisting=Existujúci trezor... ## Vault Detail ### Welcome main.vaultDetail.welcomeOnboarding=Ďakujeme Vám že ste si zvolili Cryptomator pre ochranu Vaších súborov. Ak potrebujete akúkoľvek pomoc, pozrite si našu príručku ako začať: @@ -442,6 +451,7 @@ vaultOptions.masterkey.showRecoveryKeyBtn=Ukázať klúč obnovy vaultOptions.masterkey.recoverPasswordBtn=Obnoviť heslo ## Hub vaultOptions.hub=Obnova +vaultOptions.hub.convertInfo=Kľúč na obnovenie môžete v prípade núdze použiť na konverziu tohto trezora Hub na trezor založený na hesle. vaultOptions.hub.convertBtn=Konvertovať do Password-Based trezora # Recovery Key diff --git a/src/main/resources/i18n/strings_sl.properties b/src/main/resources/i18n/strings_sl.properties index 4557d9d84..c1a513fc1 100644 --- a/src/main/resources/i18n/strings_sl.properties +++ b/src/main/resources/i18n/strings_sl.properties @@ -52,6 +52,7 @@ error.technicalDetails=Podrobnosti: ### Registration Success ### Registration Failed ### Unauthorized +### Requires Account Initialization ### License Exceeded # Lock diff --git a/src/main/resources/i18n/strings_sr.properties b/src/main/resources/i18n/strings_sr.properties index f6938a3f4..ad5dc211c 100644 --- a/src/main/resources/i18n/strings_sr.properties +++ b/src/main/resources/i18n/strings_sr.properties @@ -107,6 +107,7 @@ unlock.success.revealBtn=Otvori disk ### Registration Success ### Registration Failed ### Unauthorized +### Requires Account Initialization ### License Exceeded # Lock diff --git a/src/main/resources/i18n/strings_sr_Latn.properties b/src/main/resources/i18n/strings_sr_Latn.properties index d7522af10..8c743a114 100644 --- a/src/main/resources/i18n/strings_sr_Latn.properties +++ b/src/main/resources/i18n/strings_sr_Latn.properties @@ -100,6 +100,7 @@ unlock.success.revealBtn=Otvori disk ### Registration Success ### Registration Failed ### Unauthorized +### Requires Account Initialization ### License Exceeded # Lock diff --git a/src/main/resources/i18n/strings_sv.properties b/src/main/resources/i18n/strings_sv.properties index c36dc1cf6..b30e9848b 100644 --- a/src/main/resources/i18n/strings_sv.properties +++ b/src/main/resources/i18n/strings_sv.properties @@ -153,8 +153,7 @@ hub.auth.loginLink=Inte omdirigerad? Klicka här för att öppna den. hub.receive.message=Bearbetar svar… hub.receive.description=Cryptomator tar emot och bearbetar svaret från Hub. Vänligen vänta. ### Register Device -hub.register.message=Enhetens namn krävs -hub.register.description=Detta verkar vara den första Hub-åtkomsten från den här enheten. För att identifiera den för åtkomstbehörighet, måste du namnge den här enheten. +hub.register.message=Ny enhet hub.register.nameLabel=Enhetsnamn hub.register.occupiedMsg=Namnet används redan hub.register.registerBtn=Bekräfta @@ -167,6 +166,7 @@ hub.registerFailed.description=Ett fel uppstod i namngivningsprocessen. För mer ### Unauthorized hub.unauthorized.message=Åtkomst nekad hub.unauthorized.description=Din enhet har ännu inte behörighet att komma åt detta valv. Be valvägaren att godkänna det. +### Requires Account Initialization ### License Exceeded hub.invalidLicense.message=Din Hub-licens är ogiltig hub.invalidLicense.description=Din Cryptomator Hub-instans har en ogiltig licens. Vänligen informera en Hub administratör för att uppgradera eller förnya licensen. diff --git a/src/main/resources/i18n/strings_sw.properties b/src/main/resources/i18n/strings_sw.properties index 5d2c26049..0f403021c 100644 --- a/src/main/resources/i18n/strings_sw.properties +++ b/src/main/resources/i18n/strings_sw.properties @@ -136,8 +136,6 @@ hub.auth.loginLink=Je, haijaelekezwa kwingine? Bofya hapa ili kuifungua. hub.receive.message=Inachakata jibu… hub.receive.description=Cryptomator inapokea na kuchakata jibu kutoka kwenye Kitovu. Tafadhali subiri. ### Register Device -hub.register.message=Jina la kifaa linahitajika -hub.register.description=Hii inaonekana kuwa ufikiaji wa kwanza wa Hub kutoka kwa kifaa hiki. Ili kukitambua kwa uidhinishaji wa ufikiaji, unahitaji kukipa kifaa hiki jina. hub.register.nameLabel=Jina la Kifaa hub.register.occupiedMsg=Jina tayari linatumika hub.register.registerBtn=Thibitisha @@ -150,6 +148,7 @@ hub.registerFailed.description=Hitilafu imetupwa katika mchakato wa kumtaja. Kwa ### Unauthorized hub.unauthorized.message=Ufikiaji umekataliwa hub.unauthorized.description=Kifaa chako bado hakijaidhinishwa kufikia kuba hii. Uliza mwenye kuba aidhinishe. +### Requires Account Initialization ### License Exceeded hub.invalidLicense.message=Leseni ya Hub ni batili hub.invalidLicense.description=Mfano wako wa Cryptomator Hub una leseni batili. Tafadhali mjulishe msimamizi wa Hub ili kuboresha au kusasisha leseni. diff --git a/src/main/resources/i18n/strings_ta.properties b/src/main/resources/i18n/strings_ta.properties index fc62393dc..3b4a43d25 100644 --- a/src/main/resources/i18n/strings_ta.properties +++ b/src/main/resources/i18n/strings_ta.properties @@ -122,6 +122,7 @@ unlock.success.revealBtn=இயக்ககத்தை வெளிப்பட ### Registration Success ### Registration Failed ### Unauthorized +### Requires Account Initialization ### License Exceeded # Lock diff --git a/src/main/resources/i18n/strings_te.properties b/src/main/resources/i18n/strings_te.properties index c04391f35..12b325351 100644 --- a/src/main/resources/i18n/strings_te.properties +++ b/src/main/resources/i18n/strings_te.properties @@ -36,6 +36,7 @@ ### Registration Success ### Registration Failed ### Unauthorized +### Requires Account Initialization ### License Exceeded # Lock diff --git a/src/main/resources/i18n/strings_th.properties b/src/main/resources/i18n/strings_th.properties index bc826f071..437adddb8 100644 --- a/src/main/resources/i18n/strings_th.properties +++ b/src/main/resources/i18n/strings_th.properties @@ -93,6 +93,7 @@ unlock.unlockBtn=ปลดล็อก ### Registration Success ### Registration Failed ### Unauthorized +### Requires Account Initialization ### License Exceeded # Lock diff --git a/src/main/resources/i18n/strings_tr.properties b/src/main/resources/i18n/strings_tr.properties index 0fd5a0beb..4c3d95e76 100644 --- a/src/main/resources/i18n/strings_tr.properties +++ b/src/main/resources/i18n/strings_tr.properties @@ -153,8 +153,7 @@ hub.auth.loginLink=Yönlendirilmedi mi? Açmak için buraya tıklayın. hub.receive.message=Yanıt işleniyor… hub.receive.description=Cryptomator, Hub'dan yanıtı alıyor ve işliyor. Lütfen bekleyin. ### Register Device -hub.register.message=Cihaz adı gerekli -hub.register.description=Bu cihazdan ilk Hub erişimi gibi görünüyor. Erişim yetkilendirmesini tanımlamak için bu cihazı isimlendirmeniz gerekir. +hub.register.message=Yeni Cihaz hub.register.nameLabel=Cihaz adı hub.register.occupiedMsg=Ad zaten kullanımda hub.register.registerBtn=Onayla @@ -167,6 +166,9 @@ hub.registerFailed.description=İsimlendirme işleminde bir hata oluştu. Daha f ### Unauthorized hub.unauthorized.message=Erişim engellendi hub.unauthorized.description=Cihazınıza henüz bu kasaya erişim yetkisi verilmedi. Kasa sahibinden yetkilendirmesini isteyin. +### Requires Account Initialization +hub.requireAccountInit.description.0=Devam etmek için, lütfen gerekli adımları tamamlayın +hub.requireAccountInit.description.1=Hub kullanıcı profili ### License Exceeded hub.invalidLicense.message=Hub Lisansı geçersiz hub.invalidLicense.description=Cryptomator Hub örneğinizde geçersiz bir lisans var. Lisansı yükseltmesi veya yenilemesi için lütfen bir Hub yöneticisini bilgilendirin. diff --git a/src/main/resources/i18n/strings_uk.properties b/src/main/resources/i18n/strings_uk.properties index cf4c5b0e1..c95ee155e 100644 --- a/src/main/resources/i18n/strings_uk.properties +++ b/src/main/resources/i18n/strings_uk.properties @@ -141,8 +141,6 @@ hub.auth.loginLink=Не переадресувало? Натисніть тут, hub.receive.message=Обробка відповіді… hub.receive.description=Cryptomator отримує та обробляє відповідь від Hub. Будь ласка, зачекайте. ### Register Device -hub.register.message=Потрібне ім'я пристрою -hub.register.description=Схоже, це перша спроба доступу до Hub з цього пристрою. Для того, щоб ідентифікувати його для авторизації доступу, вам потрібно назвати цей пристрій. hub.register.nameLabel=Назва пристрою hub.register.occupiedMsg=Ця назва вже використовується hub.register.registerBtn=Підтвердити @@ -155,6 +153,7 @@ hub.registerFailed.description=Виникла помилка у процесі ### Unauthorized hub.unauthorized.message=У доступі відмовлено hub.unauthorized.description=Ваш пристрій ще не має прав доступу до цього vault. Попросіть власника vault надати їх. +### Requires Account Initialization ### License Exceeded hub.invalidLicense.message=Недійсна ліцензія Hub hub.invalidLicense.description=У вашого Cryptomator Hub недійсна ліцензія. Будь ласка, повідомте адміністратору Hub, що потрібно оновити або продовжити ліцензію. diff --git a/src/main/resources/i18n/strings_vi.properties b/src/main/resources/i18n/strings_vi.properties index b434b0ca3..718b73f2d 100644 --- a/src/main/resources/i18n/strings_vi.properties +++ b/src/main/resources/i18n/strings_vi.properties @@ -138,8 +138,6 @@ hub.auth.loginLink=Chưa được chuyển hướng? Nhấn vào đây để m hub.receive.message=Đang xử lý phản hồi… hub.receive.description=Cryptomator đang nhận và xử lý phản hồi từ Hub. Vui lòng chờ. ### Register Device -hub.register.message=Tên thiết bị bắt buộc -hub.register.description=Đây dường như là lần truy cập Hub đầu tiên từ thiết bị này. Để xác định nó để cấp quyền truy cập, bạn cần đặt tên cho thiết bị này. hub.register.nameLabel=Tên thiết bị hub.register.occupiedMsg=Tên đã sử dụng hub.register.registerBtn=Xác nhận @@ -152,6 +150,7 @@ hub.registerFailed.description=Lỗi phát sinh trong quá trình đặt tên. ### Unauthorized hub.unauthorized.message=Truy cập bị từ chối hub.unauthorized.description=Thiết bị của bạn chưa được phép truy cập vault này. Yêu cầu chủ sở hữu cấp phép. +### Requires Account Initialization ### License Exceeded hub.invalidLicense.message=Giấy phép Hub không hợp lệ hub.invalidLicense.description=Phiên bản Cryptomator Hub của bạn có giấy phép không hợp lệ. Vui lòng thông báo cho quản trị viên Hub để nâng cấp hoặc gia hạn giấy phép. diff --git a/src/main/resources/i18n/strings_zh.properties b/src/main/resources/i18n/strings_zh.properties index d47632466..e3298d38c 100644 --- a/src/main/resources/i18n/strings_zh.properties +++ b/src/main/resources/i18n/strings_zh.properties @@ -153,9 +153,10 @@ hub.auth.loginLink=未重定向?点此打开 hub.receive.message=正在处理响应… hub.receive.description=Cryptomator 正在接收和处理来自 Hub 的响应,请稍等 ### Register Device -hub.register.message=设备名称(必填) -hub.register.description=这似乎是设备的首次 Hub 访问。为了识别它以进行访问授权,您需要命名此设备。 +hub.register.message=新设备 +hub.register.description=这是通过此设备首次访问 Hub,请使用您的账户密钥进行授权 hub.register.nameLabel=设备名称 +hub.register.invalidAccountKeyLabel=无效的账户密钥 hub.register.occupiedMsg=名称已被占用 hub.register.registerBtn=确定 ### Registration Success @@ -167,6 +168,9 @@ hub.registerFailed.description=命名过程中出现错误,详情请查看应 ### Unauthorized hub.unauthorized.message=拒绝访问 hub.unauthorized.description=您的设备尚未授权访问此保险库,请联系保险库所有者, +### Requires Account Initialization +hub.requireAccountInit.message=操作请求 +hub.requireAccountInit.description.2=。 ### License Exceeded hub.invalidLicense.message=Hub 许可证无效 hub.invalidLicense.description=此 Cryptomator Hub 实例许可证无效,请联系Hub管理员升级或者续订许可证。 diff --git a/src/main/resources/i18n/strings_zh_HK.properties b/src/main/resources/i18n/strings_zh_HK.properties index 3837b7a88..5da99c3f4 100644 --- a/src/main/resources/i18n/strings_zh_HK.properties +++ b/src/main/resources/i18n/strings_zh_HK.properties @@ -147,8 +147,6 @@ hub.auth.loginLink=未被轉送?點擊這裡打開 hub.receive.message=正在處理程式回應… hub.receive.description=Cryptomator正在接收並處理來自Hub的回應。 請等待。 ### Register Device -hub.register.message=需要設備名稱 -hub.register.description=似乎為第一次透過 Hub 訪問此設備。您需要命名此設備,以便識別設備並授予讀取權限。 hub.register.nameLabel=設備名稱 hub.register.occupiedMsg=名称已被占用 hub.register.registerBtn=確認 @@ -161,6 +159,7 @@ hub.registerFailed.description=命名過程中引發錯誤。有關詳細信息 ### Unauthorized hub.unauthorized.message=拒絕存取 hub.unauthorized.description=您的設備權限尚未允許存取加密庫,請聯絡加密庫擁有者 +### Requires Account Initialization ### License Exceeded hub.invalidLicense.message=Hub 授權無效 hub.invalidLicense.description=此 Cryptomator Hub 實例授權無效,請聯繫管理員升級或續訂授權。 diff --git a/src/main/resources/i18n/strings_zh_TW.properties b/src/main/resources/i18n/strings_zh_TW.properties index 3f2ee62fd..b8d231361 100644 --- a/src/main/resources/i18n/strings_zh_TW.properties +++ b/src/main/resources/i18n/strings_zh_TW.properties @@ -153,8 +153,6 @@ hub.auth.loginLink=未轉送?點擊這裡打開 hub.receive.message=處理著回應… hub.receive.description=Cryptomator 正在接收並處理來自 Hub 的回應, 請等待。 ### Register Device -hub.register.message=需要設備名稱 -hub.register.description=似乎這是第一次 Hub 訪問此設備。為了辨識它以授權訪問,您需要命名此設備。 hub.register.nameLabel=設備名稱 hub.register.occupiedMsg=名稱已被使用 hub.register.registerBtn=確認 @@ -167,6 +165,7 @@ hub.registerFailed.description=命名過程中出現錯誤。更多詳情,請 ### Unauthorized hub.unauthorized.message=拒絕存取 hub.unauthorized.description=您的設備權限尚未允許存取檔案庫,請聯絡檔案庫擁有者 +### Requires Account Initialization ### License Exceeded hub.invalidLicense.message=Hub 憑證無效 hub.invalidLicense.description=此 Cryptomator Hub 實例授權無效,請聯繫管理員升級或續訂授權。 diff --git a/src/test/java/org/cryptomator/ui/keyloading/hub/JWEHelperTest.java b/src/test/java/org/cryptomator/ui/keyloading/hub/JWEHelperTest.java index 3d495e8c1..a2e46cc28 100644 --- a/src/test/java/org/cryptomator/ui/keyloading/hub/JWEHelperTest.java +++ b/src/test/java/org/cryptomator/ui/keyloading/hub/JWEHelperTest.java @@ -4,6 +4,7 @@ import com.nimbusds.jose.JWEObject; import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.cryptomator.cryptolib.common.P384KeyPair; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -15,18 +16,106 @@ import java.text.ParseException; import java.util.Arrays; import java.util.Base64; +@SuppressWarnings("resource") public class JWEHelperTest { - private static final String JWE = "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMzg0Iiwia2V5X29wcyI6W10sImV4dCI6dHJ1ZSwieCI6IllUcEY3bGtTc3JvZVVUVFdCb21LNzBTN0FhVTJyc0ptMURpZ1ZzbjRMY2F5eUxFNFBabldkYmFVcE9jQVV5a1ciLCJ5IjoiLU5pS3loUktjSk52Nm02Z0ZJUWc4cy1Xd1VXUW9uT3A5dkQ4cHpoa2tUU3U2RzFlU2FUTVlhZGltQ2Q4V0ExMSJ9LCJhcHUiOiIiLCJhcHYiOiIifQ..BECWGzd9UvhHcTJC.znt4TlS-qiNEjxiu2v-du_E1QOBnyBR6LCt865SHxD-kwRc1JwX_Lq9XVoFj2GnK9-9CgxhCLGurg5Jt9g38qv2brGAzWL7eSVeY1fIqdO_kUhLpGslRTN6h2U0NHJi2-iE.WDVI2kOk9Dy3PWHyIg8gKA"; + // key pairs from frontend tests (crypto.spec.ts): + private static final String USER_PRIV_KEY = "MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDCi4K1Ts3DgTz/ufkLX7EGMHjGpJv+WJmFgyzLwwaDFSfLpDw0Kgf3FKK+LAsV8r+hZANiAARLOtFebIjxVYUmDV09Q1sVxz2Nm+NkR8fu6UojVSRcCW13tEZatx8XGrIY9zC7oBCEdRqDc68PMSvS5RA0Pg9cdBNc/kgMZ1iEmEv5YsqOcaNADDSs0bLlXb35pX7Kx5Y="; + private static final String USER_PUB_KEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAESzrRXmyI8VWFJg1dPUNbFcc9jZvjZEfH7ulKI1UkXAltd7RGWrcfFxqyGPcwu6AQhHUag3OvDzEr0uUQND4PXHQTXP5IDGdYhJhL+WLKjnGjQAw0rNGy5V29+aV+yseW"; + private static final String DEVICE_PRIV_KEY = "MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDB2bmFCWy2p+EbAn8NWS5Om+GA7c5LHhRZb8g2pSMSf0fsd7k7dZDVrnyHFiLdd/YGhZANiAAR6bsjTEdXKWIuu1Bvj6Y8wySlIROy7YpmVZTY128ItovCD8pcR4PnFljvAIb2MshCdr1alX4g6cgDOqcTeREiObcSfucOU9Ry1pJ/GnX6KA0eSljrk6rxjSDos8aiZ6Mg="; + private static final String DEVICE_PUB_KEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEem7I0xHVyliLrtQb4+mPMMkpSETsu2KZlWU2NdvCLaLwg/KXEeD5xZY7wCG9jLIQna9WpV+IOnIAzqnE3kRIjm3En7nDlPUctaSfxp1+igNHkpY65Oq8Y0g6LPGomejI"; + + // used for JWE generation in frontend: (jwe.spec.ts): private static final String PRIV_KEY = "ME8CAQAwEAYHKoZIzj0CAQYFK4EEACIEODA2AgEBBDEA6QybmBitf94veD5aCLr7nlkF5EZpaXHCfq1AXm57AKQyGOjTDAF9EQB28fMywTDQ"; private static final String PUB_KEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAERxQR+NRN6Wga01370uBBzr2NHDbKIC56tPUEq2HX64RhITGhii8Zzbkb1HnRmdF0aq6uqmUy4jUhuxnKxsv59A6JeK7Unn+mpmm3pQAygjoGc9wrvoH4HWJSQYUlsXDu"; @Test - public void testDecrypt() throws ParseException, InvalidKeySpecException { - var jwe = JWEObject.parse(JWE); - var keyPair = P384KeyPair.create(new X509EncodedKeySpec(Base64.getDecoder().decode(PUB_KEY)), new PKCS8EncodedKeySpec(Base64.getDecoder().decode(PRIV_KEY))); + @DisplayName("decryptUserKey with device key") + public void testDecryptUserKeyECDHES() throws ParseException, InvalidKeySpecException { + var jwe = JWEObject.parse(""" + eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrZXlfb3BzIjpbXSwiZXh0Ijp\ + 0cnVlLCJrdHkiOiJFQyIsIngiOiJoeHpiSWh6SUJza3A5ZkZFUmJSQ2RfOU1fbWYxNElqaDZhcnNoVX\ + NkcEEyWno5ejZZNUs4NHpZR2I4b2FHemNUIiwieSI6ImJrMGRaNWhpelZ0TF9hN2hNejBjTUduNjhIR\ + jZFdWlyNHdlclNkTFV5QWd2NWUzVzNYSG5sdHJ2VlRyU3pzUWYiLCJjcnYiOiJQLTM4NCJ9LCJhcHUi\ + OiIiLCJhcHYiOiIifQ..pu3Q1nR_yvgRAapG.4zW0xm0JPxbcvZ66R-Mn3k841lHelDQfaUvsZZAtWs\ + L2w4FMi6H_uu6ArAWYLtNREa_zfcPuyuJsFferYPSNRUWt4OW6aWs-l_wfo7G1ceEVxztQXzQiwD30U\ + TA8OOdPcUuFfEq2-d9217jezrcyO6m6FjyssEZIrnRArUPWKzGdghXccGkkf0LTZcGJoHeKal-RtyP8\ + PfvEAWTjSOCpBlSdUJ-1JL3tyd97uVFNaVuH3i7vvcMoUP_bdr0XW3rvRgaeC6X4daPLUvR1hK5Msut\ + QMtM2vpFghS_zZxIQRqz3B2ECxa9Bjxhmn8kLX5heZ8fq3lH-bmJp1DxzZ4V1RkWk.yVwXG9yARa5Ih\ + q2koh2NbQ"""); + var deviceKeyPair = P384KeyPair.create(new X509EncodedKeySpec(Base64.getDecoder().decode(DEVICE_PUB_KEY)), new PKCS8EncodedKeySpec(Base64.getDecoder().decode(DEVICE_PRIV_KEY))); - var masterkey = JWEHelper.decrypt(jwe, keyPair.getPrivate()); + var userKey = JWEHelper.decryptUserKey(jwe, deviceKeyPair.getPrivate()); + + Assertions.assertArrayEquals(Base64.getDecoder().decode(USER_PRIV_KEY), userKey.getEncoded()); + } + + @Test + @DisplayName("decryptUserKey with incorrect device key") + public void testDecryptUserKeyECDHESWrongKey() throws ParseException, InvalidKeySpecException { + var jwe = JWEObject.parse(""" + eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrZXlfb3BzIjpbXSwiZXh0Ijp\ + 0cnVlLCJrdHkiOiJFQyIsIngiOiJoeHpiSWh6SUJza3A5ZkZFUmJSQ2RfOU1fbWYxNElqaDZhcnNoVX\ + NkcEEyWno5ejZZNUs4NHpZR2I4b2FHemNUIiwieSI6ImJrMGRaNWhpelZ0TF9hN2hNejBjTUduNjhIR\ + jZFdWlyNHdlclNkTFV5QWd2NWUzVzNYSG5sdHJ2VlRyU3pzUWYiLCJjcnYiOiJQLTM4NCJ9LCJhcHUi\ + OiIiLCJhcHYiOiIifQ..pu3Q1nR_yvgRAapG.4zW0xm0JPxbcvZ66R-Mn3k841lHelDQfaUvsZZAtWs\ + L2w4FMi6H_uu6ArAWYLtNREa_zfcPuyuJsFferYPSNRUWt4OW6aWs-l_wfo7G1ceEVxztQXzQiwD30U\ + TA8OOdPcUuFfEq2-d9217jezrcyO6m6FjyssEZIrnRArUPWKzGdghXccGkkf0LTZcGJoHeKal-RtyP8\ + PfvEAWTjSOCpBlSdUJ-1JL3tyd97uVFNaVuH3i7vvcMoUP_bdr0XW3rvRgaeC6X4daPLUvR1hK5Msut\ + QMtM2vpFghS_zZxIQRqz3B2ECxa9Bjxhmn8kLX5heZ8fq3lH-bmJp1DxzZ4V1RkWk.yVwXG9yARa5Ih\ + q2koh2NbQ"""); + var userKeyPair = P384KeyPair.create(new X509EncodedKeySpec(Base64.getDecoder().decode(USER_PUB_KEY)), new PKCS8EncodedKeySpec(Base64.getDecoder().decode(USER_PRIV_KEY))); + var incorrectDevicePrivateKey = userKeyPair.getPrivate(); + + Assertions.assertThrows(JWEHelper.InvalidJweKeyException.class, () -> JWEHelper.decryptUserKey(jwe, incorrectDevicePrivateKey)); + } + + @Test + @DisplayName("decryptUserKey with setup code") + public void testDecryptUserKeyPBES2() throws ParseException { + var jwe = JWEObject.parse(""" + eyJhbGciOiJQQkVTMi1IUzUxMitBMjU2S1ciLCJlbmMiOiJBMjU2R0NNIiwicDJzIjoiT3hMY0Q\ + xX1pCODc1c2hvUWY2Q1ZHQSIsInAyYyI6MTAwMCwiYXB1IjoiIiwiYXB2IjoiIn0.FD4fcrP4Pb\ + aKOQ9ZfXl0gpMM6Fa2rfqAvL0K5ZyYUiVeHCNV-A02Rg.urT1ShSv6qQxh8X7.gEqAiUWD98a2E\ + P7ITCPTw4DJo6-BpqrxA73D6gNIj9z4d1hN-EP99Q4mWBWLH97H8ugbG5rGsm8xsjsBqpWORQqF\ + mJZR2AhlPiwFaC7n_MDDBupSy_swDnCfj731Lal297IP5WbkFcmozKsyhmwdkctxjf_VHA.fJki\ + kDjUaxwUKqpvT7qaAQ + """); + + var userKey = JWEHelper.decryptUserKey(jwe, "123456"); + + Assertions.assertArrayEquals(Base64.getDecoder().decode(PRIV_KEY), userKey.getEncoded()); + } + + @Test + @DisplayName("decryptUserKey with incorrect setup code") + public void testDecryptUserKeyPBES2WrongKey() throws ParseException { + var jwe = JWEObject.parse(""" + eyJhbGciOiJQQkVTMi1IUzUxMitBMjU2S1ciLCJlbmMiOiJBMjU2R0NNIiwicDJzIjoiT3hMY0Q\ + xX1pCODc1c2hvUWY2Q1ZHQSIsInAyYyI6MTAwMCwiYXB1IjoiIiwiYXB2IjoiIn0.FD4fcrP4Pb\ + aKOQ9ZfXl0gpMM6Fa2rfqAvL0K5ZyYUiVeHCNV-A02Rg.urT1ShSv6qQxh8X7.gEqAiUWD98a2E\ + P7ITCPTw4DJo6-BpqrxA73D6gNIj9z4d1hN-EP99Q4mWBWLH97H8ugbG5rGsm8xsjsBqpWORQqF\ + mJZR2AhlPiwFaC7n_MDDBupSy_swDnCfj731Lal297IP5WbkFcmozKsyhmwdkctxjf_VHA.fJki\ + kDjUaxwUKqpvT7qaAQ + """); + + Assertions.assertThrows(JWEHelper.InvalidJweKeyException.class, () -> JWEHelper.decryptUserKey(jwe, "654321")); + } + + @Test + @DisplayName("decryptVaultKey") + public void testDecryptVaultKey() throws ParseException, InvalidKeySpecException { + var jwe = JWEObject.parse(""" + eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlA\ + tMzg0Iiwia2V5X29wcyI6W10sImV4dCI6dHJ1ZSwieCI6IllUcEY3bGtTc3JvZVVUVFdCb21LNzBTN0\ + FhVTJyc0ptMURpZ1ZzbjRMY2F5eUxFNFBabldkYmFVcE9jQVV5a1ciLCJ5IjoiLU5pS3loUktjSk52N\ + m02Z0ZJUWc4cy1Xd1VXUW9uT3A5dkQ4cHpoa2tUU3U2RzFlU2FUTVlhZGltQ2Q4V0ExMSJ9LCJhcHUi\ + OiIiLCJhcHYiOiIifQ..BECWGzd9UvhHcTJC.znt4TlS-qiNEjxiu2v-du_E1QOBnyBR6LCt865SHxD\ + -kwRc1JwX_Lq9XVoFj2GnK9-9CgxhCLGurg5Jt9g38qv2brGAzWL7eSVeY1fIqdO_kUhLpGslRTN6h2\ + U0NHJi2-iE.WDVI2kOk9Dy3PWHyIg8gKA"""); + var privateKey = P384KeyPair.create(new X509EncodedKeySpec(Base64.getDecoder().decode(PUB_KEY)), new PKCS8EncodedKeySpec(Base64.getDecoder().decode(PRIV_KEY))).getPrivate(); + + var masterkey = JWEHelper.decryptVaultKey(jwe, privateKey); var expectedEncKey = new byte[32]; var expectedMacKey = new byte[32]; @@ -44,13 +133,11 @@ public class JWEHelperTest { "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMzg0Iiwia2V5X29wcyI6W10sImV4dCI6dHJ1ZSwieCI6IkJyYm9UQkl5Y0NDUEdJQlBUekU2RjBnbTRzRjRCamZPN1I0a2x0aWlCaThKZkxxcVdXNVdUSVBLN01yMXV5QVUiLCJ5IjoiNUpGVUI0WVJiYjM2RUZpN2Y0TUxMcFFyZXd2UV9Tc3dKNHRVbFd1a2c1ZU04X1ZyM2pkeml2QXI2WThRczVYbSJ9LCJhcHUiOiIiLCJhcHYiOiIifQ..QEq4Z2m6iwBx2ioS.IBo8TbKJTS4pug.61Z-agIIXgP8bX10O_yEMA", // json payload field "key" not a string "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMzg0Iiwia2V5X29wcyI6W10sImV4dCI6dHJ1ZSwieCI6ImNZdlVFZm9LYkJjenZySE5zQjUxOGpycUxPMGJDOW5lZjR4NzFFMUQ5dk95MXRqd1piZzV3cFI0OE5nU1RQdHgiLCJ5IjoiaWRJekhCWERzSzR2NTZEeU9yczJOcDZsSG1zb29fMXV0VTlzX3JNdVVkbkxuVXIzUXdLZkhYMWdaVXREM1RKayJ9LCJhcHUiOiIiLCJhcHYiOiIifQ..0VZqu5ei9U3blGtq.eDvhU6drw7mIwvXu6Q.f05QnhI7JWG3IYHvexwdFQ" // json payload field "key" invalid base64 data }) - public void testDecryptInvalid(String malformed) throws ParseException, InvalidKeySpecException { + public void testDecryptInvalidVaultKey(String malformed) throws ParseException, InvalidKeySpecException { var jwe = JWEObject.parse(malformed); - var keyPair = P384KeyPair.create(new X509EncodedKeySpec(Base64.getDecoder().decode(PUB_KEY)), new PKCS8EncodedKeySpec(Base64.getDecoder().decode(PRIV_KEY))); + var privateKey = P384KeyPair.create(new X509EncodedKeySpec(Base64.getDecoder().decode(PUB_KEY)), new PKCS8EncodedKeySpec(Base64.getDecoder().decode(PRIV_KEY))).getPrivate(); - Assertions.assertThrows(MasterkeyLoadingFailedException.class, () -> { - JWEHelper.decrypt(jwe, keyPair.getPrivate()); - }); + Assertions.assertThrows(MasterkeyLoadingFailedException.class, () -> JWEHelper.decryptVaultKey(jwe, privateKey)); } } \ No newline at end of file