Merge branch 'main' into passkeys
This commit is contained in:
commit
942da28b53
79 changed files with 3766 additions and 1711 deletions
41
.github/workflows/auth-crowdin.yml
vendored
Normal file
41
.github/workflows/auth-crowdin.yml
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
name: "Sync Crowdin translations (auth)"
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
# Run action when auth's intl_en.arb is changed
|
||||
- "mobile/lib/l10n/arb/app_en.arb"
|
||||
# Or the workflow itself is changed
|
||||
- ".github/workflows/auth-crowdin.yml"
|
||||
branches: [main]
|
||||
schedule:
|
||||
# Run every 24 hours - https://crontab.guru/#0_*/24_*_*_*
|
||||
- cron: "0 */24 * * *"
|
||||
workflow_dispatch: # Allow manually running the action
|
||||
|
||||
jobs:
|
||||
synchronize-with-crowdin:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Crowdin's action
|
||||
uses: crowdin/github-action@v1
|
||||
with:
|
||||
base_path: "auth/"
|
||||
config: "auth/crowdin.yml"
|
||||
upload_sources: true
|
||||
upload_translations: true
|
||||
download_translations: true
|
||||
localization_branch_name: crowdin-translations-auth
|
||||
create_pull_request: true
|
||||
skip_untranslated_strings: true
|
||||
pull_request_title: "[auth] New translations"
|
||||
pull_request_body: "New translations from [Crowdin](https://crowdin.com/project/ente-authenticator-app)"
|
||||
pull_request_base_branch_name: "main"
|
||||
project_id: 575169
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
37
.github/workflows/auth-lint.yml
vendored
Normal file
37
.github/workflows/auth-lint.yml
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
name: "Lint (auth)"
|
||||
|
||||
on:
|
||||
# Run on every push to branches (this also covers pull requests)
|
||||
push:
|
||||
# See: [Note: Specify branch when specifying a path filter]
|
||||
branches: ["**"]
|
||||
# Only run if something changes in these paths
|
||||
paths:
|
||||
- "auth/**"
|
||||
- ".github/workflows/auth-lint.yml"
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.16.9"
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: auth
|
||||
steps:
|
||||
- name: Checkout code and submodules
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install Flutter ${{ env.FLUTTER_VERSION }}
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- run: flutter pub get
|
||||
|
||||
- run: flutter analyze --no-fatal-infos
|
291
.github/workflows/auth-release.yml
vendored
Normal file
291
.github/workflows/auth-release.yml
vendored
Normal file
|
@ -0,0 +1,291 @@
|
|||
name: "Release (auth)"
|
||||
|
||||
# [Note: Testing release workflows that are triggered by tags]
|
||||
#
|
||||
# To test this out, push a tag with a pre-release version. The version number
|
||||
# should be the version number of the next actual release.
|
||||
#
|
||||
# > When major, minor, and patch are equal, a pre-release version has lower
|
||||
# > precedence than a normal version. Example: 1.0.0-alpha < 1.0.0.
|
||||
# > https://semver.org
|
||||
#
|
||||
# So if the next release we intend to put out is 1.2.3, you can:
|
||||
#
|
||||
# git tag auth-v1.2.3-test
|
||||
# git push origin auth-v1.2.3-test
|
||||
#
|
||||
# We use a suffix like `-test` to indicate that these are test tags, and that
|
||||
# they belong to a pre-release.
|
||||
#
|
||||
# If you need to do multiple tests, add a +x at the end of the tag. e.g.
|
||||
# `auth-v1.2.3-test+1`.
|
||||
#
|
||||
# Once the testing is done, also delete the tag(s) please.
|
||||
|
||||
on:
|
||||
push:
|
||||
# Run when a tag matching the pattern "auth-v*"" is pushed
|
||||
tags:
|
||||
- "auth-v*"
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.16.9"
|
||||
|
||||
jobs:
|
||||
build-ubuntu:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: auth
|
||||
|
||||
steps:
|
||||
- name: Checkout code and submodules
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install Flutter ${{ env.FLUTTER_VERSION }}
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: Setup keys
|
||||
uses: timheuer/base64-to-file@v1
|
||||
with:
|
||||
fileName: "keystore/ente_auth_key.jks"
|
||||
encodedString: ${{ secrets.SIGNING_KEY }}
|
||||
|
||||
- name: Create artifacts directory
|
||||
run: mkdir artifacts
|
||||
|
||||
- name: Build independent APK
|
||||
run: |
|
||||
flutter build apk --release --flavor independent --dart-define=app.flavor=independent
|
||||
mv build/app/outputs/flutter-apk/app-independent-release.apk artifacts/ente-${{ github.ref_name }}.apk
|
||||
env:
|
||||
SIGNING_KEY_PATH: "/home/runner/work/_temp/keystore/ente_auth_key.jks"
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
|
||||
- name: Build PlayStore AAB
|
||||
run: |
|
||||
flutter build appbundle --release --flavor playstore --dart-define=app.flavor=playstore
|
||||
env:
|
||||
SIGNING_KEY_PATH: "/home/runner/work/_temp/keystore/ente_auth_key.jks"
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
|
||||
- name: Install dependencies for desktop build
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y libsecret-1-dev libsodium-dev libwebkit2gtk-4.0-dev libfuse2 ninja-build libgtk-3-dev dpkg-dev pkg-config rpm libsqlite3-dev locate
|
||||
|
||||
- name: Install appimagetool
|
||||
run: |
|
||||
wget -O appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
chmod +x appimagetool
|
||||
mv appimagetool /usr/local/bin/
|
||||
|
||||
- name: Build desktop app
|
||||
# Temporarily disable desktop builds
|
||||
if: false
|
||||
run: |
|
||||
flutter config --enable-linux-desktop
|
||||
dart pub global activate flutter_distributor
|
||||
flutter_distributor package --platform=linux --targets=deb --skip-clean
|
||||
flutter_distributor package --platform=linux --targets=rpm --skip-clean
|
||||
flutter_distributor package --platform=linux --targets=appimage --skip-clean
|
||||
mv dist/**/*-*-linux.deb artifacts/ente-${{ github.ref_name }}-x86_64.deb
|
||||
mv dist/**/*-*-linux.rpm artifacts/ente-${{ github.ref_name }}-x86_64.rpm
|
||||
mv dist/**/*-*-linux.AppImage artifacts/ente-${{ github.ref_name }}-x86_64.AppImage
|
||||
env:
|
||||
LIBSODIUM_USE_PKGCONFIG: 1
|
||||
|
||||
- name: Generate checksums
|
||||
run: sha256sum artifacts/ente-* > artifacts/sha256sum
|
||||
|
||||
- name: Create a draft GitHub release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "auth/artifacts/*"
|
||||
draft: true
|
||||
allowUpdates: true
|
||||
updateOnlyUnreleased: true
|
||||
|
||||
- name: Upload AAB to PlayStore
|
||||
# Temporarily disable GP upload, enable this once desktop build
|
||||
# testing is complete.
|
||||
if: false
|
||||
uses: r0adkll/upload-google-play@v1
|
||||
with:
|
||||
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
|
||||
packageName: io.ente.auth
|
||||
releaseFiles: build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab
|
||||
track: internal
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: auth
|
||||
|
||||
steps:
|
||||
- name: Checkout code and submodules
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install Flutter ${{ env.FLUTTER_VERSION }}
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: Create artifacts directory
|
||||
run: mkdir artifacts
|
||||
|
||||
- name: Build Windows installer
|
||||
# Temporarily disable desktop builds
|
||||
if: false
|
||||
run: |
|
||||
flutter config --enable-windows-desktop
|
||||
dart pub global activate flutter_distributor
|
||||
make innoinstall
|
||||
flutter_distributor package --platform=windows --targets=exe --skip-clean
|
||||
mv dist/**/ente_auth-*-windows-setup.exe artifacts/ente-${{ github.ref_name }}-installer.exe
|
||||
|
||||
- name: Retain Windows EXE and DLLs
|
||||
# Temporarily disable desktop builds
|
||||
if: false
|
||||
run: cp -r build/windows/x64/runner/Release ente-${{ github.ref_name }}-windows
|
||||
|
||||
- name: Code sign Windows installer and EXE
|
||||
# Temporarily disable desktop builds
|
||||
if: false
|
||||
uses: dlemstra/code-sign-action@v1
|
||||
with:
|
||||
certificate: "${{ secrets.WINDOWS_CERTIFICATE }}"
|
||||
password: "${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}"
|
||||
files: |
|
||||
auth/artifacts/ente-${{ github.ref_name }}-installer.exe
|
||||
auth/ente-${{ github.ref_name }}-windows/auth.exe
|
||||
|
||||
- name: Zip Windows EXE and DLLs
|
||||
# Temporarily disable desktop builds
|
||||
if: false
|
||||
run: tar.exe -a -c -f auth/artifacts/ente-${{ github.ref_name }}-windows.zip auth/ente-${{ github.ref_name }}-windows
|
||||
|
||||
- name: Create a draft GitHub release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "auth/artifacts/*"
|
||||
draft: true
|
||||
allowUpdates: true
|
||||
updateOnlyUnreleased: true
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-13 # latest is 12
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: auth
|
||||
|
||||
steps:
|
||||
- name: Checkout code and submodules
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install Flutter ${{ env.FLUTTER_VERSION }}
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: Install code signing dependencies
|
||||
run: |
|
||||
pip3 install codemagic-cli-tools
|
||||
|
||||
- name: Add provisioning profiles
|
||||
run: |
|
||||
PROFILES_HOME="$HOME/Library/MobileDevice/Provisioning Profiles"
|
||||
mkdir -p "$PROFILES_HOME"
|
||||
PROFILE_PATH="$(mktemp "$PROFILES_HOME"/$(uuidgen).provisionprofile)"
|
||||
echo ${CM_PROVISIONING_PROFILE} | base64 --decode > "$PROFILE_PATH"
|
||||
echo "Saved provisioning profile $PROFILE_PATH"
|
||||
env:
|
||||
CM_PROVISIONING_PROFILE: ${{ secrets.MAC_OS_BUILD_PROVISION_PROFILE_BASE64 }}
|
||||
|
||||
- name: Add certificates
|
||||
run: |
|
||||
# create variables
|
||||
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
|
||||
|
||||
# copy certificates from base64
|
||||
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
|
||||
|
||||
# add certificate to keychain
|
||||
keychain initialize
|
||||
keychain add-certificates --certificate $CERTIFICATE_PATH --certificate-password $P12_PASSWORD
|
||||
|
||||
# Use profile in current project
|
||||
xcode-project use-profiles --project=macos/**/*.xcodeproj
|
||||
env:
|
||||
BUILD_CERTIFICATE_BASE64: ${{ secrets.MAC_OS_CERTIFICATE }}
|
||||
P12_PASSWORD: ${{ secrets.MAC_OS_CERTIFICATE_PASSWORD }}
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
python3 -m pip install setuptools
|
||||
npm install -g appdmg
|
||||
|
||||
- name: Create artifacts directory
|
||||
run: mkdir artifacts
|
||||
|
||||
- name: Build macOS DMG
|
||||
# Temporarily disable desktop builds
|
||||
if: false
|
||||
run: |
|
||||
flutter config --enable-macos-desktop
|
||||
dart pub global activate flutter_distributor
|
||||
flutter_distributor package --platform=macos --targets=dmg --skip-clean
|
||||
mv dist/**/ente_auth-*-macos.dmg artifacts/ente-${{ github.ref_name }}.dmg
|
||||
|
||||
- name: Code sign DMG
|
||||
# Temporarily disable desktop builds
|
||||
if: false
|
||||
run: |
|
||||
CERT_NAME=$(security find-identity -v -p codesigning | grep "Developer ID Application" | awk -F'"' '{print $2}' | grep -m1 "")
|
||||
codesign --force --timestamp --sign "$CERT_NAME" --options runtime artifacts/ente-${{ github.ref_name }}.dmg
|
||||
codesign --verify --verbose=4 artifacts/ente-${{ github.ref_name }}.dmg
|
||||
|
||||
- name: Notarize and staple DMG
|
||||
# Temporarily disable desktop builds
|
||||
if: false
|
||||
run: |
|
||||
xcrun notarytool submit artifacts/ente-${{ github.ref_name }}.dmg \
|
||||
--wait \
|
||||
--apple-id $APPLE_ID \
|
||||
--password $APPLE_PASSWORD \
|
||||
--team-id $APPLE_TEAM_ID
|
||||
xcrun stapler staple artifacts/ente-${{ github.ref_name }}.dmg
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
|
||||
- name: Create a draft GitHub release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "auth/artifacts/*"
|
||||
draft: true
|
||||
allowUpdates: true
|
||||
updateOnlyUnreleased: true
|
51
.github/workflows/cli-release.yml
vendored
Normal file
51
.github/workflows/cli-release.yml
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
name: "Release (cli)"
|
||||
|
||||
on:
|
||||
push:
|
||||
# Run when a tag matching the pattern "cli-v*"" is pushed
|
||||
#
|
||||
# Tip: to test this workflow, push at tag with a pre-release version,
|
||||
# e.g. `cli-v1.2.3-test`, where 1.2.3 is the expected version number of
|
||||
# the next release that'll go out.
|
||||
#
|
||||
# See: [Note: Testing release workflows that are triggered by tags]
|
||||
tags:
|
||||
- "cli-v*"
|
||||
|
||||
jobs:
|
||||
draft-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Create a draft GitHub release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
draft: true
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
needs: draft-release
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
goos: [linux, windows, darwin]
|
||||
goarch: ["386", amd64, arm64]
|
||||
exclude:
|
||||
- goarch: "386"
|
||||
goos: darwin
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build binaries and add to the release
|
||||
uses: wangyoucao577/go-release-action@v1
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
asset_name: ente-${{ github.ref_name }}-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
release_name: ${{ github.ref_name }}
|
||||
goversion: "1.20"
|
||||
project_path: "./cli"
|
||||
md5sum: false
|
||||
sha256sum: true
|
41
.github/workflows/mobile-crowdin.yml
vendored
Normal file
41
.github/workflows/mobile-crowdin.yml
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
name: "Sync Crowdin translations (mobile)"
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
# Run action when mobiles's intl_en.arb is changed
|
||||
- "mobile/lib/l10n/intl_en.arb"
|
||||
# Or the workflow itself is changed
|
||||
- ".github/workflows/mobile-crowdin.yml"
|
||||
branches: [main]
|
||||
schedule:
|
||||
# Run every 24 hours - https://crontab.guru/#0_*/24_*_*_*
|
||||
- cron: "0 */24 * * *"
|
||||
workflow_dispatch: # Allow manually running the action
|
||||
|
||||
jobs:
|
||||
synchronize-with-crowdin:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Crowdin's action
|
||||
uses: crowdin/github-action@v1
|
||||
with:
|
||||
base_path: "mobile/"
|
||||
config: "mobile/crowdin.yml"
|
||||
upload_sources: true
|
||||
upload_translations: true
|
||||
download_translations: true
|
||||
localization_branch_name: crowdin-translations-mobile
|
||||
create_pull_request: true
|
||||
skip_untranslated_strings: true
|
||||
pull_request_title: "[mobile] New translations"
|
||||
pull_request_body: "New translations from [Crowdin](https://crowdin.com/project/ente-photos-app)"
|
||||
pull_request_base_branch_name: "main"
|
||||
project_id: 574741
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
37
.github/workflows/mobile-lint.yml
vendored
Normal file
37
.github/workflows/mobile-lint.yml
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
name: "Lint (mobile)"
|
||||
|
||||
on:
|
||||
# Run on every push (this also covers pull requests)
|
||||
push:
|
||||
# See: [Note: Specify branch when specifying a path filter]
|
||||
branches: ["**"]
|
||||
# Only run if something changes in these paths
|
||||
paths:
|
||||
- "mobile/**"
|
||||
- ".github/workflows/mobile-lint.yml"
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.13.4"
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: mobile
|
||||
steps:
|
||||
- name: Checkout code and submodules
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install Flutter ${{ env.FLUTTER_VERSION }}
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- run: flutter pub get
|
||||
|
||||
- run: flutter analyze --no-fatal-infos
|
56
.github/workflows/mobile-release.yml
vendored
Normal file
56
.github/workflows/mobile-release.yml
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
name: "Release (photos independent)"
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Allow manually running the action
|
||||
push:
|
||||
# Run when a tag matching the pattern "photos-v*"" is pushed
|
||||
# See: [Note: Testing release workflows that are triggered by tags]
|
||||
tags:
|
||||
- "photos-v*"
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.13.4"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: mobile
|
||||
|
||||
steps:
|
||||
- name: Checkout code and submodules
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install Flutter ${{ env.FLUTTER_VERSION }}
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: Setup keys
|
||||
uses: timheuer/base64-to-file@v1
|
||||
with:
|
||||
fileName: "keystore/ente_photos_key.jks"
|
||||
encodedString: ${{ secrets.SIGNING_KEY_PHOTOS }}
|
||||
|
||||
- name: Build independent APK
|
||||
run: flutter build apk --release --flavor independent && mv build/app/outputs/flutter-apk/app-independent-release.apk build/app/outputs/flutter-apk/ente.apk
|
||||
env:
|
||||
SIGNING_KEY_PATH: "/home/runner/work/_temp/keystore/ente_photos_key.jks"
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS_PHOTOS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD_PHOTOS }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD_PHOTOS }}
|
||||
|
||||
- name: Checksum
|
||||
run: sha256sum build/app/outputs/flutter-apk/ente.apk > build/app/outputs/flutter-apk/sha256sum
|
||||
|
||||
- name: Create a draft GitHub release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "mobile/build/app/outputs/flutter-apk/ente.apk,mobile/build/app/outputs/flutter-apk/sha256sum"
|
||||
draft: true
|
33
.github/workflows/server-lint.yml
vendored
Normal file
33
.github/workflows/server-lint.yml
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
name: "Lint (server)"
|
||||
|
||||
on:
|
||||
# Run on every push (this also covers pull requests)
|
||||
push:
|
||||
# See: [Note: Specify branch when specifying a path filter]
|
||||
branches: ["**"]
|
||||
# Only run if something changes in these paths
|
||||
paths:
|
||||
- "server/**"
|
||||
- ".github/workflows/server-lint.yml"
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: server
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "server/go.mod"
|
||||
cache: true
|
||||
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get update && sudo apt-get install libsodium-dev
|
||||
|
||||
- name: Lint
|
||||
run: "./scripts/lint.sh"
|
|
@ -1,16 +1,10 @@
|
|||
name: Prod CI
|
||||
name: "Release (server)"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# Enable manual run
|
||||
push:
|
||||
# Sequence of patterns matched against refs/tags
|
||||
tags:
|
||||
- "v*" # Push events to matching v*, i.e. v4.2.0
|
||||
workflow_dispatch: # Run manually
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# This job will run on ubuntu virtual machine
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
@ -19,6 +13,8 @@ jobs:
|
|||
- uses: mr-smithers-excellent/docker-build-push@v6
|
||||
name: Build & Push
|
||||
with:
|
||||
dockerfile: server/Dockerfile
|
||||
directory: server
|
||||
image: ente/museum-prod
|
||||
registry: rg.fr-par.scw.cloud
|
||||
enableBuildKit: true
|
41
.github/workflows/web-crowdin.yml
vendored
Normal file
41
.github/workflows/web-crowdin.yml
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
name: "Sync Crowdin translations (web)"
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
# Run action when web's en-US/translation.json is changed
|
||||
- "web/apps/photos/public/locales/en-US/translation.json"
|
||||
# Or the workflow itself is changed
|
||||
- ".github/workflows/web-crowdin.yml"
|
||||
branches: [main]
|
||||
schedule:
|
||||
# Run every 24 hours - https://crontab.guru/#0_*/24_*_*_*
|
||||
- cron: "0 */24 * * *"
|
||||
workflow_dispatch: # Allow manually running the action
|
||||
|
||||
jobs:
|
||||
synchronize-with-crowdin:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Crowdin's action
|
||||
uses: crowdin/github-action@v1
|
||||
with:
|
||||
base_path: "web/"
|
||||
config: "web/crowdin.yml"
|
||||
upload_sources: true
|
||||
upload_translations: true
|
||||
download_translations: true
|
||||
localization_branch_name: crowdin-translations-web
|
||||
create_pull_request: true
|
||||
skip_untranslated_strings: true
|
||||
pull_request_title: "[web] New translations"
|
||||
pull_request_body: "New translations from [Crowdin](https://crowdin.com/project/ente-photos-web)"
|
||||
pull_request_base_branch_name: "main"
|
||||
project_id: 569613
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
41
.github/workflows/web-lint.yml
vendored
Normal file
41
.github/workflows/web-lint.yml
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
name: "Lint (web)"
|
||||
|
||||
on:
|
||||
# Run on every push (this also covers pull requests)
|
||||
push:
|
||||
# [Note: Specify branch when specifying a path filter]
|
||||
#
|
||||
# Path filters are ignored for tag pushes, which causes this workflow to
|
||||
# always run when we push a tag. Defining an explicit branch solves the
|
||||
# issue. From GitHub's docs:
|
||||
#
|
||||
# > if you define both branches/branches-ignore and paths/paths-ignore,
|
||||
# > the workflow will only run when both filters are satisfied.
|
||||
#
|
||||
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
|
||||
branches: ["**"]
|
||||
# Only run if something changes in these paths
|
||||
paths:
|
||||
- "web/**"
|
||||
- ".github/workflows/web-lint.yml"
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: web
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup node and enable yarn caching
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "yarn"
|
||||
cache-dependency-path: "web/yarn.lock"
|
||||
|
||||
- run: yarn install
|
||||
|
||||
- run: yarn lint
|
|
@ -42,7 +42,7 @@ projects to get started:
|
|||
|
||||
|
||||
If your language is not listed for translation, please [create a GitHub
|
||||
issue](https://github.com/ente-io/ente/issues/new?title=Request+for+New+Language+Translation&body=Language+name%3A)
|
||||
issue](https://github.com/ente-io/ente/issues/new?title=Request+for+New+Language+Translation&body=Language+name%3A+%0AProject%3A+auth%2Fphotos%2Fboth)
|
||||
to have it added. It is okay to have partial translations. Once ~90% of the
|
||||
strings in a language get translated, we will start surfacing it in the apps.
|
||||
|
||||
|
|
86
auth/.github/workflows/ci.yml
vendored
86
auth/.github/workflows/ci.yml
vendored
|
@ -1,86 +0,0 @@
|
|||
name: release
|
||||
|
||||
# This workflow is triggered on pushes to the repository.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# Enable manual run
|
||||
push:
|
||||
# Sequence of patterns matched against refs/tags
|
||||
tags:
|
||||
- "v*" # Push events to matching v*, i.e. v4.2.0
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# This job will run on ubuntu virtual machine
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Setup Java environment in order to build the Android app.
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: "adopt"
|
||||
java-version: "11"
|
||||
|
||||
# Setup the flutter environment.
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: "3.13.4"
|
||||
|
||||
# Fetch sub modules
|
||||
- run: git submodule update --init --recursive
|
||||
|
||||
# Get flutter dependencies.
|
||||
- run: flutter pub get
|
||||
|
||||
- name: Setup keys
|
||||
uses: timheuer/base64-to-file@v1
|
||||
with:
|
||||
fileName: "keystore/ente_auth_key.jks"
|
||||
encodedString: ${{ secrets.SIGNING_KEY }}
|
||||
|
||||
# Build independent apk.
|
||||
- name: Build
|
||||
run: flutter build apk --release --flavor independent --dart-define=app.flavor=independent && mv build/app/outputs/flutter-apk/app-independent-release.apk build/app/outputs/flutter-apk/ente-auth.apk
|
||||
env:
|
||||
SIGNING_KEY_PATH: "/home/runner/work/_temp/keystore/ente_auth_key.jks"
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
|
||||
# Build Play store aab.
|
||||
- name: Build
|
||||
run: flutter build appbundle --release --flavor playstore --dart-define=app.flavor=playstore
|
||||
env:
|
||||
SIGNING_KEY_PATH: "/home/runner/work/_temp/keystore/ente_auth_key.jks"
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
|
||||
- name: Checksum
|
||||
run: sha256sum build/app/outputs/flutter-apk/ente-auth.apk > build/app/outputs/flutter-apk/sha256sum
|
||||
|
||||
# Upload generated apk to the artifacts.
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: release-apk
|
||||
path: build/app/outputs/flutter-apk/ente-auth.apk
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: release-checksum
|
||||
path: build/app/outputs/flutter-apk/sha256sum
|
||||
|
||||
# Create a Github release
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "build/app/outputs/flutter-apk/ente-auth.apk,build/app/outputs/flutter-apk/sha256sum"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Upload to Play store
|
||||
- uses: ente-io/upload-google-play@v1
|
||||
with:
|
||||
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
|
||||
packageName: io.ente.auth
|
||||
releaseFiles: build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab
|
||||
track: internal
|
12
auth/.github/workflows/desktop.yml
vendored
12
auth/.github/workflows/desktop.yml
vendored
|
@ -1,12 +0,0 @@
|
|||
name: desktop build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
name: Linux
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
35
auth/.github/workflows/l18n-crowdin.yml
vendored
35
auth/.github/workflows/l18n-crowdin.yml
vendored
|
@ -1,35 +0,0 @@
|
|||
name: Sync crowdin translation
|
||||
|
||||
on:
|
||||
push:
|
||||
paths: # run action automatically when app_en.arb file is changed
|
||||
- 'lib/l10n/arb/app_en.arb'
|
||||
branches: [ main ]
|
||||
schedule:
|
||||
- cron: '0 */12 * * *' # Every 12 hours - https://crontab.guru/#0_*/12_*_*_*
|
||||
workflow_dispatch: # for manually running the action
|
||||
|
||||
jobs:
|
||||
synchronize-with-crowdin:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: crowdin action
|
||||
uses: crowdin/github-action@v1
|
||||
with:
|
||||
upload_sources: true
|
||||
upload_translations: true
|
||||
download_translations: true
|
||||
localization_branch_name: l10n_translations
|
||||
create_pull_request: true
|
||||
skip_untranslated_strings: true
|
||||
pull_request_title: 'New Translations'
|
||||
pull_request_body: 'New translations via [Crowdin GH Action](https://github.com/crowdin/github-action)'
|
||||
pull_request_base_branch_name: 'main'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
|
@ -30,6 +30,10 @@
|
|||
{
|
||||
"title": "Bitwarden"
|
||||
},
|
||||
{
|
||||
"title": "Bloom Host",
|
||||
"slug": "bloom_host"
|
||||
},
|
||||
{
|
||||
"title": "BorgBase",
|
||||
"altNames": ["borg"],
|
||||
|
@ -110,7 +114,7 @@
|
|||
},
|
||||
{
|
||||
"title": "Healthchecks.io",
|
||||
"slug": "healthchecks",
|
||||
"slug": "healthchecks"
|
||||
},
|
||||
{
|
||||
"title": "ING"
|
||||
|
@ -298,7 +302,7 @@
|
|||
},
|
||||
{
|
||||
"title": "Synology DSM",
|
||||
"slug": "synology_dsm",
|
||||
"slug": "synology_dsm"
|
||||
},
|
||||
{
|
||||
"title": "TCPShield",
|
||||
|
|
8
auth/assets/custom-icons/icons/bloom_host.svg
Normal file
8
auth/assets/custom-icons/icons/bloom_host.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 30 KiB |
|
@ -1,20 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="102" height="20">
|
||||
<linearGradient id="b" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#bbb" stop-opacity=".1" />
|
||||
<stop offset="1" stop-opacity=".1" />
|
||||
</linearGradient>
|
||||
<clipPath id="a">
|
||||
<rect width="102" height="20" rx="3" fill="#fff" />
|
||||
</clipPath>
|
||||
<g clip-path="url(#a)">
|
||||
<path fill="#555" d="M0 0h59v20H0z" />
|
||||
<path fill="#44cc11" d="M59 0h43v20H59z" />
|
||||
<path fill="url(#b)" d="M0 0h102v20H0z" />
|
||||
</g>
|
||||
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110">
|
||||
<text x="305" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">coverage</text>
|
||||
<text x="305" y="140" transform="scale(.1)" textLength="490">coverage</text>
|
||||
<text x="795" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="330">100%</text>
|
||||
<text x="795" y="140" transform="scale(.1)" textLength="330">100%</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.1 KiB |
|
@ -1,6 +1,5 @@
|
|||
project_id_env: CROWDIN_PROJECT_ID
|
||||
api_token_env: CROWDIN_PERSONAL_TOKEN
|
||||
|
||||
files:
|
||||
- source: /lib/l10n/arb/app_en.arb
|
||||
translation: /lib/l10n/arb/app_%two_letters_code%.arb
|
||||
translation: /lib/l10n/arb/app_%two_letters_code%.arb
|
||||
|
|
|
@ -1,12 +1,28 @@
|
|||
# Releases
|
||||
|
||||
1. Create a PR to bump up the version number in `pubspec.yaml`.
|
||||
Create a PR to bump up the version in `pubspec.yaml`. Once that is merged, tag
|
||||
main, and push the tag.
|
||||
|
||||
2. Once that is merged, tag main. This'll trigger the
|
||||
[workflow](.github/workflows/ci.yml) to (a) create a new GitHub release with
|
||||
the independently distributed APK, and (b) build and upload a release to
|
||||
Google Play.
|
||||
```sh
|
||||
git tag auth-v1.2.3
|
||||
git push origin auth-v1.2.3
|
||||
```
|
||||
|
||||
3. Xcode Cloud has already been configured and will automatically build and
|
||||
release to TestFlight when step 1 was merged to main (you can see logs under
|
||||
the PR checks).
|
||||
This'll trigger a GitHub workflow that:
|
||||
|
||||
* Creates a new draft GitHub release and attaches all the build artifacts to it
|
||||
(mobile APKs and various desktop packages),
|
||||
|
||||
* Creates a new release in the internal track on Play Store.
|
||||
|
||||
Once the workflow completes, go to the draft GitHub release that was created.
|
||||
Set "Previous tag" to the last release of auth and press "Generate release
|
||||
notes". The generated release note will contain all PRs and new contributors
|
||||
from all the releases in the monorepo, so you'll need to filter them to keep
|
||||
only the things that relate to the auth.
|
||||
|
||||
---
|
||||
|
||||
(TODO(MR): Fix this after the monorepo move) Xcode Cloud has already been
|
||||
configured and will automatically build and release to TestFlight when step 1
|
||||
was merged to main (you can see logs under the PR checks).
|
||||
|
|
|
@ -1 +1,408 @@
|
|||
{}
|
||||
{
|
||||
"account": "حسابي",
|
||||
"unlock": "فتح القفل",
|
||||
"recoveryKey": "مفتاح الاسترداد",
|
||||
"counterAppBarTitle": "العداد",
|
||||
"@counterAppBarTitle": {
|
||||
"description": "Text shown in the AppBar of the Counter Page"
|
||||
},
|
||||
"onBoardingBody": "النسخ الاحتياطي لأوامر 2FA",
|
||||
"onBoardingGetStarted": "إبدأ الآن",
|
||||
"setupFirstAccount": "إعداد الحساب الأول الخاص بك",
|
||||
"importScanQrCode": "مسح رمز QR",
|
||||
"qrCode": "رمز QR",
|
||||
"importEnterSetupKey": "أدخِل مفتاح الإعداد",
|
||||
"importAccountPageTitle": "أدخل تفاصيل الحساب",
|
||||
"secretCanNotBeEmpty": "لا يمكن أن يكون رمز السر فارغ",
|
||||
"bothIssuerAndAccountCanNotBeEmpty": "لا يمكن أن يكون المُصدر والحساب فارغًا",
|
||||
"incorrectDetails": "بيانات غير صحيحة",
|
||||
"pleaseVerifyDetails": "من فضلك تأكد من بياناتك وحاول مرة أخرى",
|
||||
"codeIssuerHint": "المصدِّر",
|
||||
"codeSecretKeyHint": "الرمز السري",
|
||||
"codeAccountHint": "الحساب (you@domain.com)",
|
||||
"accountKeyType": "نوع المفتاح",
|
||||
"sessionExpired": "انتهت صلاحية الجلسة",
|
||||
"@sessionExpired": {
|
||||
"description": "Title of the dialog when the users current session is invalid/expired"
|
||||
},
|
||||
"pleaseLoginAgain": "الرجاء تسجيل الدخول مرة أخرى",
|
||||
"loggingOut": "جاري تسجيل الخروج...",
|
||||
"timeBasedKeyType": "على أساس الوقت (TOTP)",
|
||||
"counterBasedKeyType": "القائم على العداد (HOTP)",
|
||||
"saveAction": "حفظ",
|
||||
"nextTotpTitle": "التالي",
|
||||
"deleteCodeTitle": "حذف الرمز؟",
|
||||
"deleteCodeMessage": "هل أنت متأكد من أنك تريد حذف هذا الرمز ؟ هذا الإجراء لا رجعة فيه.",
|
||||
"viewLogsAction": "عرض السجل",
|
||||
"sendLogsDescription": "هذا سوف يرسل عبر السجلات لمساعدتنا على تصحيح مشكلتك. وبينما نتخذ الاحتياطات لضمان عدم تسجيل المعلومات الحساسة، نشجعك على رؤية هذه السجلات قبل تقاسمها.",
|
||||
"preparingLogsTitle": "جاري إعداد السجلات...",
|
||||
"emailLogsTitle": "سجلات البريد الإلكتروني",
|
||||
"emailLogsMessage": "الرجاء إرسال السجلات إلى {email}",
|
||||
"@emailLogsMessage": {
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"copyEmailAction": "نسخ البريد الإلكتروني",
|
||||
"exportLogsAction": "تصدير السجلات",
|
||||
"reportABug": "الابلاغ عن خلل تقني",
|
||||
"crashAndErrorReporting": "الإبلاغ عن الأعطال والأخطاء",
|
||||
"reportBug": "الإبلاغ عن خلل",
|
||||
"emailUsMessage": "الرجاء مراسلتنا على {email}",
|
||||
"@emailUsMessage": {
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"contactSupport": "الاتصال بالدعم",
|
||||
"rateUsOnStore": "قم بتقييمنا على {storeName}",
|
||||
"blog": "المدونة",
|
||||
"merchandise": "إدارة المنتجات",
|
||||
"verifyPassword": "التحقق من كلمة المرور",
|
||||
"pleaseWait": "الرجاء الإنتظار...",
|
||||
"generatingEncryptionKeysTitle": "توليد مفاتيح التشفير...",
|
||||
"recreatePassword": "إعادة كتابة كلمة المرور",
|
||||
"recreatePasswordMessage": "الجهاز الحالي ليس قويًا بما يكفي للتحقق من كلمة المرور الخاصة بك، لذا نحتاج إلى إعادة إنشائها مرة واحدة بطريقة تعمل مع جميع الأجهزة.\n\nالرجاء تسجيل الدخول باستخدام مفتاح الاسترداد وإعادة إنشاء كلمة المرور الخاصة بك (يمكنك استخدام نفس كلمة المرور مرة أخرى إذا كنت ترغب في ذلك).",
|
||||
"useRecoveryKey": "استخدم مفتاح الاسترداد",
|
||||
"incorrectPasswordTitle": "كلمة المرور غير صحيحة",
|
||||
"welcomeBack": "مرحبًا مجددًا!",
|
||||
"madeWithLoveAtPrefix": "مصنوعة مع ❤️ في ",
|
||||
"supportDevs": "اشترك في <bold-green>ente</bold-green> لدعمنا",
|
||||
"supportDiscount": "استخدم رمز القسيمة \"AUTH\" للحصول على 10% خصم من السنة الأولى",
|
||||
"changeEmail": "تغيير البريد الإلكتروني",
|
||||
"changePassword": "تغيير كلمة المرور",
|
||||
"data": "البيانات",
|
||||
"importCodes": "رمزالاستيراد",
|
||||
"importTypePlainText": "نص عادي",
|
||||
"importTypeEnteEncrypted": "تصدير مشفر ente",
|
||||
"passwordForDecryptingExport": "كلمة المرور لفك تشفير التصدير",
|
||||
"passwordEmptyError": "لا يمكن أن تكون كلمة المرور فارغة",
|
||||
"importFromApp": "استيراد الرموز من {appName}",
|
||||
"importGoogleAuthGuide": "قم بتصدير حساباتك من Google Authenticator إلى رمز QR code باستخدام خيار \"Transfer Accounts\" ثم استخدم جهازًا آخر لمسح رمز الاستجابة السريعة ضوئيًا.\n\nنصيحة: يمكنك استخدام كاميرا الويب الخاصة بالكمبيوتر المحمول لالتقاط صورة لرمز الاستجابة السريعة.",
|
||||
"importSelectJsonFile": "حدد ملف JSON",
|
||||
"importSelectAppExport": "حدد ملف التصدير {appName}",
|
||||
"importEnteEncGuide": "حدد ملف JSON المشفر الذي تم تصديره من ente",
|
||||
"importRaivoGuide": "استخدم خيار تصدير OTP إلى أرشيف Zip في إعدادات Raivo.\n\nاستخرج ملف zip واسترد ملف JSON.",
|
||||
"importBitwardenGuide": "استخدم خيار \"تصدير خزانة\" داخل أدوات Bitwarden واستيراد ملف JSON غير مشفر.",
|
||||
"importAegisGuide": "استخدم خيار \"Export the vault\" في إعدادات Aegis.\n\nإذا كان المخزن الخاص بك مشفرًا، فستحتاج إلى إدخال كلمة مرور المخزن لفك تشفير المخزن.",
|
||||
"import2FasGuide": "استخدم خيار \"الإعدادات -> النسخ الاحتياطي - التصدير\" في 2FAS.\n\nإذا تم تشفير النسخة الاحتياطية، سوف تحتاج إلى إدخال كلمة المرور لفك تشفير النسخة الاحتياطية",
|
||||
"importLastpassGuide": "استخدم خيار \"حسابات النقل\" ضمن إعدادات مصادقة Lastpass، واضغط على \"تصدير الحسابات إلى الملف\". استيراد JSON الذي تم تنزيله.",
|
||||
"exportCodes": "تصدير الرموز",
|
||||
"importLabel": "استيراد",
|
||||
"importInstruction": "الرجاء تحديد ملف يحتوي على قائمة بالرموز الخاصة بك بالشكل التالي",
|
||||
"importCodeDelimiterInfo": "يمكن فصل الرموز بفاصلة أو سطر جديد",
|
||||
"selectFile": "اختيار الملف",
|
||||
"emailVerificationToggle": "تأكيد عنوان البريد الإلكتروني",
|
||||
"emailVerificationEnableWarning": "لتجنب إقفال حسابك، تأكد من تخزين نسخة من بريدك الإلكتروني 2FA خارج Ente Auth قبل تمكين التحقق من البريد الإلكتروني.",
|
||||
"authToChangeEmailVerificationSetting": "الرجاء المصادقة لتغيير التحقق من البريد الإلكتروني",
|
||||
"authToViewYourRecoveryKey": "الرجاء المصادقة لعرض مفتاح الاسترداد الخاص بك",
|
||||
"authToChangeYourEmail": "الرجاء المصادقة لتغيير بريدك الإلكتروني",
|
||||
"authToChangeYourPassword": "الرجاء المصادقة لتغيير كلمة المرور الخاصة بك",
|
||||
"authToViewSecrets": "الرجاء المصادقة لعرض مفتاح الاسترداد الخاص بك",
|
||||
"authToInitiateSignIn": "الرجاء المصادقة لبدء تسجيل الدخول للنسخ الاحتياطي.",
|
||||
"ok": "حسناً",
|
||||
"cancel": "إلغاء",
|
||||
"yes": "نعم",
|
||||
"no": "لا",
|
||||
"email": "البريد الإلكتروني",
|
||||
"support": "الدعم",
|
||||
"general": "العامة",
|
||||
"settings": "الإعدادات",
|
||||
"copied": "تم النسخ",
|
||||
"pleaseTryAgain": "حاول مرة اخرى",
|
||||
"existingUser": "المستخدم موجود",
|
||||
"newUser": "جديد إلى Ente",
|
||||
"delete": "حذف",
|
||||
"enterYourPasswordHint": "أدخل كلمة المرور الخاصة بك",
|
||||
"forgotPassword": "هل نسيت كلمة المرور",
|
||||
"oops": "عذرًا",
|
||||
"suggestFeatures": "اقتراح ميزة",
|
||||
"faq": "الأسئلة الأكثر شيوعاً",
|
||||
"faq_q_1": "ما مدى أمان المصادقة؟",
|
||||
"faq_a_1": "يتم تشفير جميع الرموز التي تقوم بنسخها احتياطا عبر Ente. وهذا يعني أنه يمكنك فقط الوصول إلى الرموز الخاصة بك. تطبيقاتنا مفتوحة المصدر وقد تم مراجعة التشفير خارجيا.",
|
||||
"faq_q_2": "هل يمكنني الوصول إلى رموزي على سطح المكتب؟",
|
||||
"faq_a_2": "يمكنك الوصول إلى رموزك على الويب @ auth.ente.io.",
|
||||
"faq_q_3": "كيف يمكنني حذف الرموز؟",
|
||||
"faq_a_3": "يمكنك حذف الرمز عن طريق السحب لليسار على هذا العنصر.",
|
||||
"faq_q_4": "كيف يمكنني دعم هذا المشروع؟",
|
||||
"faq_a_4": "يمكنك دعم تطوير هذا المشروع عن طريق الاشتراك في تطبيق الصور @ ente.io.",
|
||||
"faq_q_5": "كيف يمكنني تمكين قفل FaceID في المصادقة Ente",
|
||||
"faq_a_5": "يمكنك تمكين قفل FaceID تحت الإعدادات => الحماية => قفل الشاشة.",
|
||||
"somethingWentWrongMessage": "حدث خطأ ما، يرجى المحاولة مرة أخرى",
|
||||
"leaveFamily": "مغادرة خطة العائلة",
|
||||
"leaveFamilyMessage": "هل أنت متأكد من الخروج من خطة العائلة؟",
|
||||
"inFamilyPlanMessage": "أنت مندرج ضمن خطة عائلية!",
|
||||
"swipeHint": "اسحب لليسار لتحرير أو إزالة الرموز",
|
||||
"scan": "مسح",
|
||||
"scanACode": "فحص رمز Qr",
|
||||
"verify": "التحقق",
|
||||
"verifyEmail": "تأكيد البريد الإلكتروني",
|
||||
"enterCodeHint": "أدخل الرمز المكون من 6 أرقام من\nتطبيق المصادقة",
|
||||
"lostDeviceTitle": "جهاز مفقود ؟",
|
||||
"twoFactorAuthTitle": "المصادقة الثنائية",
|
||||
"recoverAccount": "إسترجاع الحساب",
|
||||
"enterRecoveryKeyHint": "أدخل رمز الاسترداد",
|
||||
"recover": "استرداد",
|
||||
"contactSupportViaEmailMessage": "الرجاء إسقاط بريد إلكتروني إلى {email} من عنوان بريدك الإلكتروني المسجل",
|
||||
"@contactSupportViaEmailMessage": {
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"noRecoveryKeyTitle": "لا يوجد مفتاح استرجاع؟",
|
||||
"enterEmailHint": "أدخل عنوان البريد الإلكتروني الخاص بك",
|
||||
"invalidEmailTitle": "عنوان البريد الإلكتروني غير صالح",
|
||||
"invalidEmailMessage": "الرجاء إدخال بريد إلكتروني صالح.",
|
||||
"deleteAccount": "إزالة الحساب",
|
||||
"deleteAccountQuery": "سوف نأسف لرؤيتك تذهب. هل تواجه بعض المشاكل؟",
|
||||
"yesSendFeedbackAction": "نعم، ارسل الملاحظات",
|
||||
"noDeleteAccountAction": "لا، حذف الحساب",
|
||||
"initiateAccountDeleteTitle": "الرجاء المصادقة لبدء حذف الحساب",
|
||||
"sendEmail": "ارسل بريد الكتروني",
|
||||
"createNewAccount": "إنشاء حساب جديد",
|
||||
"weakStrength": "ضعيف",
|
||||
"strongStrength": "قوي",
|
||||
"moderateStrength": "متوسط",
|
||||
"confirmPassword": "تأكيد كلمة المرور",
|
||||
"close": "إغلاق",
|
||||
"oopsSomethingWentWrong": "المعذرة! حدث خطأ ما.",
|
||||
"selectLanguage": "اختر اللغة",
|
||||
"language": "اللغة",
|
||||
"social": "وسائل التواصل",
|
||||
"security": "الأمان",
|
||||
"lockscreen": "شاشة القفل",
|
||||
"authToChangeLockscreenSetting": "الرجاء المصادقة لتغيير إعدادات شاشة القفل",
|
||||
"lockScreenEnablePreSteps": "لتمكين شاشة القفل، الرجاء إعداد رمز مرور الجهاز أو قفل الشاشة في إعدادات النظام الخاص بك.",
|
||||
"viewActiveSessions": "عرض الجلسات النشطة",
|
||||
"authToViewYourActiveSessions": "الرجاء المصادقة لعرض جلساتك النشطة",
|
||||
"searchHint": "بحث...",
|
||||
"search": "بحث",
|
||||
"sorryUnableToGenCode": "عذراً، غير قادر على إنشاء رمز ل {issuerName}",
|
||||
"noResult": "لا توجد نتيجة",
|
||||
"addCode": "أضف رمز",
|
||||
"scanAQrCode": "مسح رمز QR",
|
||||
"enterDetailsManually": "أدخل التفاصيل يدوياً",
|
||||
"edit": "تعديل",
|
||||
"copiedToClipboard": "تم النسخ إلى الحافظة",
|
||||
"copiedNextToClipboard": "تم نسخ الرموز التالية إلى الحافظة",
|
||||
"error": "خطأ",
|
||||
"recoveryKeyCopiedToClipboard": "تم نسخ عبارة الاسترداد للحافظة",
|
||||
"recoveryKeyOnForgotPassword": "إذا نسيت كلمة المرور الخاصة بك، فالطريقة الوحيدة التي يمكنك بها استرداد بياناتك هي بهذا المفتاح.",
|
||||
"recoveryKeySaveDescription": "نحن لا نخزن هذا المفتاح، يرجى حفظ مفتاح الـ 24 كلمة هذا في مكان آمن.",
|
||||
"doThisLater": "قم بهذا لاحقاً",
|
||||
"saveKey": "حفظ المفتاح",
|
||||
"back": "الرجوع",
|
||||
"createAccount": "إنشاء حساب",
|
||||
"passwordStrength": "قوة كلمة المرور: {passwordStrengthValue}",
|
||||
"@passwordStrength": {
|
||||
"description": "Text to indicate the password strength",
|
||||
"placeholders": {
|
||||
"passwordStrengthValue": {
|
||||
"description": "The strength of the password as a string",
|
||||
"type": "String",
|
||||
"example": "Weak or Moderate or Strong"
|
||||
}
|
||||
},
|
||||
"message": "Password Strength: {passwordStrengthText}"
|
||||
},
|
||||
"password": "كلمة المرور",
|
||||
"signUpTerms": "أوافق على <u-terms>شروط الخدمة</u-terms> و<u-policy>سياسة الخصوصية</u-policy>",
|
||||
"privacyPolicyTitle": "سياسة الخصوصية",
|
||||
"termsOfServicesTitle": "الشروط",
|
||||
"encryption": "التشفير",
|
||||
"setPasswordTitle": "تعيين كلمة المرور",
|
||||
"changePasswordTitle": "تغيير كلمة المرور",
|
||||
"resetPasswordTitle": "إعادة تعيين كلمة المرور",
|
||||
"encryptionKeys": "مفاتيح التشفير",
|
||||
"passwordWarning": "نحن لا نقوم بتخزين كلمة المرور هذه، لذا إذا نسيتها، <underline>لا يمكننا فك تشفير بياناتك</underline>",
|
||||
"enterPasswordToEncrypt": "أدخل كلمة المرور التي يمكننا استخدامها لتشفير بياناتك",
|
||||
"enterNewPasswordToEncrypt": "أدخل كلمة مرور جديدة يمكننا استخدامها لتشفير بياناتك",
|
||||
"passwordChangedSuccessfully": "تم تغيير كلمة المرور بنجاح",
|
||||
"generatingEncryptionKeys": "توليد مفاتيح التشفير...",
|
||||
"continueLabel": "المتابعة",
|
||||
"insecureDevice": "جهاز غير آمن",
|
||||
"sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "عذرًا، لم نتمكن من إنشاء مفاتيح آمنة على هذا الجهاز.\n\nيرجى التسجيل من جهاز مختلف.",
|
||||
"howItWorks": "كيف يعمل",
|
||||
"ackPasswordLostWarning": "أنا أفهم أنه إذا فقدت كلمة المرور الخاصة بي، قد أفقد بياناتي لأن بياناتي هي <underline>مشفرة من الند للند</underline>.",
|
||||
"loginTerms": "بالنقر على تسجيل الدخول، أوافق على شروط الخدمة <u-terms></u-terms> و <u-policy>سياسة الخصوصية</u-policy>",
|
||||
"logInLabel": "تسجيل الدخول",
|
||||
"logout": "تسجيل الخروج",
|
||||
"areYouSureYouWantToLogout": "هل أنت متأكد من أنك تريد تسجيل الخروج؟",
|
||||
"yesLogout": "نعم، تسجيل الخروج",
|
||||
"exit": "خروج",
|
||||
"verifyingRecoveryKey": "التحقق من مفتاح الاسترداد...",
|
||||
"recoveryKeyVerified": "تم التحقق من مفتاح الاسترداد",
|
||||
"recoveryKeySuccessBody": "رائع! مفتاح الاسترداد الخاص بك صالح. شكرا لك على التحقق.\n\nيرجى تذكر الاحتفاظ بنسخة احتياطية من مفتاح الاسترداد بشكل آمن.",
|
||||
"invalidRecoveryKey": "مفتاح الاسترداد الذي أدخلته غير صالح. الرجاء التأكد من أنه يحتوي على 24 كلمة، والتحقق من تهجئة كل منها.\n\nإذا قمت بإدخال رمز الاسترداد القديم، تأكد من أن طوله 64 حرفاً، وتحقق من كل منها.",
|
||||
"recreatePasswordTitle": "إعادة كتابة كلمة المرور",
|
||||
"recreatePasswordBody": "الجهاز الحالي ليس قويًا بما يكفي للتحقق من كلمة المرور الخاصة بك، لذا نحتاج إلى إعادة إنشائها مرة واحدة بطريقة تعمل مع جميع الأجهزة.\n\nالرجاء تسجيل الدخول باستخدام مفتاح الاسترداد وإعادة إنشاء كلمة المرور الخاصة بك (يمكنك استخدام نفس كلمة المرور مرة أخرى إذا كنت ترغب في ذلك).",
|
||||
"invalidKey": "المفتاح غير صالح",
|
||||
"tryAgain": "حاول مرة أخرى",
|
||||
"viewRecoveryKey": "عرض مفتاح الاسترداد",
|
||||
"confirmRecoveryKey": "تأكيد مفتاح الاسترداد",
|
||||
"recoveryKeyVerifyReason": "مفتاح الاسترداد الخاص بك هو الطريقة الوحيدة لاسترداد صورك إذا نسيت كلمة المرور الخاصة بك. يمكنك العثور على مفتاح الاسترداد الخاص بك في الإعدادات > الحساب.\n\nالرجاء إدخال مفتاح الاسترداد الخاص بك هنا للتحقق من أنك قمت بحفظه بشكل صحيح.",
|
||||
"confirmYourRecoveryKey": "تأكيد مفتاح الاسترداد",
|
||||
"confirm": "تأكيد",
|
||||
"emailYourLogs": "إرسال السجلات عبر البريد الإلكتروني",
|
||||
"pleaseSendTheLogsTo": "الرجاء إرسال السجلات إلى {toEmail}",
|
||||
"copyEmailAddress": "نسخ عنوان البريد الإلكتروني",
|
||||
"exportLogs": "تصدير السجلات",
|
||||
"enterYourRecoveryKey": "أدخل رمز الاسترداد",
|
||||
"tempErrorContactSupportIfPersists": "يبدو أنه حدث خطأ ما. الرجاء إعادة المحاولة لاحقا. إذا استمر الخطأ، يرجى الاتصال بفريق الدعم.",
|
||||
"itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "يبدو أنه حدث خطأ ما. الرجاء إعادة المحاولة لاحقا. إذا استمر الخطأ، يرجى الاتصال بفريق الدعم.",
|
||||
"about": "حول",
|
||||
"weAreOpenSource": "الخدمة مفتوحة المصدر!",
|
||||
"privacy": "الخصوصية",
|
||||
"terms": "الشروط",
|
||||
"checkForUpdates": "بحث عن تحديثات",
|
||||
"downloadUpdate": "تحميل",
|
||||
"criticalUpdateAvailable": "تحديث حاسم متوفر",
|
||||
"updateAvailable": "التحديث متاح",
|
||||
"update": "تحديث",
|
||||
"checking": "جارٍ التحقق...",
|
||||
"youAreOnTheLatestVersion": "أنت في الإصدار الأخير",
|
||||
"warning": "تحذير",
|
||||
"exportWarningDesc": "الملف الذي تم تصديره يحتوي على معلومات حساسة. الرجاء تخزين هذا بشكل آمن.",
|
||||
"iUnderStand": "فهمت",
|
||||
"@iUnderStand": {
|
||||
"description": "Text for the button to confirm the user understands the warning"
|
||||
},
|
||||
"authToExportCodes": "الرجاء المصادقة لتصدير الرموز الخاصة بك",
|
||||
"importSuccessTitle": "مرحى!",
|
||||
"importSuccessDesc": "لقد استوردت {count} رمز!",
|
||||
"@importSuccessDesc": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"description": "The number of codes imported",
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sorry": "المعذرة",
|
||||
"importFailureDesc": "تعذر تحليل الملف المحدد.\nالرجاء الكتابة إلى support@ente.io إذا كنت بحاجة إلى مساعدة!",
|
||||
"pendingSyncs": "تحذير",
|
||||
"pendingSyncsWarningBody": "لم يتم نسخ بعض رموزك احتياطيًا.\n\nيرجى التأكد من أن لديك نسخة احتياطية لهذه الرموز قبل تسجيل الخروج.",
|
||||
"checkInboxAndSpamFolder": "الرجاء التحقق من صندوق الوارد (والرسائل غير المرغوب فيها) لإكمال التحقق",
|
||||
"tapToEnterCode": "انقر لإدخال الرمز",
|
||||
"resendEmail": "إعادة إرسال البريد الإلكتروني",
|
||||
"weHaveSendEmailTo": "لقد أرسلنا رسالة إلى <green>{email}</green>",
|
||||
"@weHaveSendEmailTo": {
|
||||
"description": "Text to indicate that we have sent a mail to the user",
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"description": "The email address of the user",
|
||||
"type": "String",
|
||||
"example": "example@ente.io"
|
||||
}
|
||||
}
|
||||
},
|
||||
"activeSessions": "الجلسات النشطة",
|
||||
"somethingWentWrongPleaseTryAgain": "حدث خطأ ما، يرجى المحاولة مرة أخرى",
|
||||
"thisWillLogYouOutOfThisDevice": "سيؤدي هذا إلى تسجيل خروجك من هذا الجهاز!",
|
||||
"thisWillLogYouOutOfTheFollowingDevice": "سيؤدي هذا إلى تسجيل خروجك من هذا الجهاز:",
|
||||
"terminateSession": "إنهاء الجلسة؟",
|
||||
"terminate": "إنهاء",
|
||||
"thisDevice": "هذا الجهاز",
|
||||
"toResetVerifyEmail": "لإعادة تعيين كلمة المرور الخاصة بك، يرجى التحقق من بريدك الإلكتروني أولاً.",
|
||||
"thisEmailIsAlreadyInUse": "هذا البريد مستخدم مسبقاً",
|
||||
"verificationFailedPleaseTryAgain": "فشل في المصادقة ، يرجى المحاولة مرة أخرى في وقت لاحق",
|
||||
"yourVerificationCodeHasExpired": "انتهت صلاحية رمز التحقق",
|
||||
"incorrectCode": "رمز غير صحيح",
|
||||
"sorryTheCodeYouveEnteredIsIncorrect": "عذراً، الرمز الذي أدخلته غير صحيح",
|
||||
"emailChangedTo": "تم تغيير البريد الإلكتروني إلى {newEmail}",
|
||||
"authenticationFailedPleaseTryAgain": "فشلت المصادقة. الرجاء المحاولة مرة أخرى",
|
||||
"authenticationSuccessful": "تمت المصادقة بنجاح!",
|
||||
"twofactorAuthenticationSuccessfullyReset": "تم تحديث المصادقة الثنائية بنجاح",
|
||||
"incorrectRecoveryKey": "مفتاح الاسترداد غير صحيح",
|
||||
"theRecoveryKeyYouEnteredIsIncorrect": "مفتاح الاسترداد الذي أدخلته غير صحيح",
|
||||
"enterPassword": "أدخل كلمة المرور",
|
||||
"selectExportFormat": "اختر صيغة التصدير",
|
||||
"exportDialogDesc": "سيتم حماية الصادرات المشفرة بكلمة مرور من اختيارك.",
|
||||
"encrypted": "مشفَّرة",
|
||||
"plainText": "نص عادي",
|
||||
"passwordToEncryptExport": "كلمة المرور لتشفير التصدير",
|
||||
"export": "تصدير",
|
||||
"useOffline": "استخدام بدون نسخ إحتياطية",
|
||||
"signInToBackup": "قم بتسجيل الدخول للنسخ الاحتياطي للرموز الخاصة بك",
|
||||
"singIn": "تسجل الدخول",
|
||||
"sigInBackupReminder": "يرجى تصدير الرموز الخاصة بك للتأكد من أن لديك نسخة احتياطية يمكنك استعادتها منها.",
|
||||
"offlineModeWarning": "لقد اخترت المضي قدما بدون نسخ احتياطية. يرجى أخذ نسخ احتياطية يدوية للتأكد من سلامة الرموز الخاصة بك.",
|
||||
"showLargeIcons": "إظهار أيقونات كبيرة",
|
||||
"shouldHideCode": "إخفاء الرموز",
|
||||
"doubleTapToViewHiddenCode": "يمكنك النقر مرتين على أي عنصر لعرض الرمز",
|
||||
"focusOnSearchBar": "التركيز على البحث عند بدء التطبيق",
|
||||
"confirmUpdatingkey": "هل أنت متأكد من أنك تريد تحديث المفتاح السري؟",
|
||||
"minimizeAppOnCopy": "تصغير التطبيق عند النسخ",
|
||||
"editCodeAuthMessage": "المصادقة لتعديل الرمز",
|
||||
"deleteCodeAuthMessage": "المصادقة لحذف الرمز",
|
||||
"showQRAuthMessage": "المصادقة لإظهار رمز QR",
|
||||
"confirmAccountDeleteTitle": "تأكيد حذف الحساب",
|
||||
"confirmAccountDeleteMessage": "هذا الحساب مرتبط بتطبيقات Ente أخرى، إذا كنت تستخدم أي منها.\n\nبياناتك التي تم تحميلها، عبر جميع تطبيقات Ente سيتم جدولتها للحذف، وسيتم حذف حسابك بشكل دائم.",
|
||||
"androidBiometricHint": "التحقق من الهوية",
|
||||
"@androidBiometricHint": {
|
||||
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricNotRecognized": "لم يتم التعرف عليه. حاول مرة أخرى.",
|
||||
"@androidBiometricNotRecognized": {
|
||||
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricSuccess": "تم بنجاح",
|
||||
"@androidBiometricSuccess": {
|
||||
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidCancelButton": "إلغاء",
|
||||
"@androidCancelButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
|
||||
},
|
||||
"androidSignInTitle": "المصادقة مطلوبة",
|
||||
"@androidSignInTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricRequiredTitle": "البيومترية مطلوبة",
|
||||
"@androidBiometricRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsRequiredTitle": "بيانات اعتماد الجهاز مطلوبة",
|
||||
"@androidDeviceCredentialsRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsSetupDescription": "بيانات اعتماد الجهاز مطلوبة",
|
||||
"@androidDeviceCredentialsSetupDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"goToSettings": "الانتقال إلى الإعدادات",
|
||||
"@goToSettings": {
|
||||
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
|
||||
},
|
||||
"androidGoToSettingsDescription": "لم يتم إعداد المصادقة الحيوية على جهازك. انتقل إلى 'الإعدادات > الأمن' لإضافة المصادقة البيومترية.",
|
||||
"@androidGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"iOSLockOut": "المصادقة البيومترية معطلة. الرجاء قفل الشاشة وفتح القفل لتفعيلها.",
|
||||
"@iOSLockOut": {
|
||||
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSGoToSettingsDescription": "لم يتم إعداد المصادقة البيومترية على جهازك. الرجاء تمكين معرف اللمس أو معرف الوجه على هاتفك.",
|
||||
"@iOSGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSOkButton": "حسناً",
|
||||
"@iOSOkButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
|
||||
},
|
||||
"noInternetConnection": "لا يوجد اتصال بالإنترنت",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "يرجى التحقق من اتصالك بالإنترنت ثم المحاولة من جديد.",
|
||||
"signOutFromOtherDevices": "تسجيل الخروج من الأجهزة الأخرى",
|
||||
"signOutOtherBody": "إذا كنت تعتقد أن شخصا ما يعرف كلمة المرور الخاصة بك، يمكنك إجبار جميع الأجهزة الأخرى الستخدمة حاليا لحسابك على تسجيل الخروج.",
|
||||
"signOutOtherDevices": "تسجيل الخروج من الأجهزة الأخرى",
|
||||
"doNotSignOut": "لا تقم بتسجيل الخروج",
|
||||
"hearUsWhereTitle": "كيف سمعت عن Ente؟ (اختياري)",
|
||||
"hearUsExplanation": "نحن لا نتتبع تثبيت التطبيق. سيكون من المفيد إذا أخبرتنا أين وجدتنا!"
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"account": "Cuenta",
|
||||
"unlock": "Desbloquear",
|
||||
"recoveryKey": "Clave de recuperación",
|
||||
"counterAppBarTitle": "Contador",
|
||||
"@counterAppBarTitle": {
|
||||
|
@ -83,9 +84,13 @@
|
|||
"importFromApp": "Importar códigos de {appName}",
|
||||
"importGoogleAuthGuide": "Exportar tus cuentas desde Google Authenticator a un código QR usando la opción \"Transferir Cuentas\". A continuación, usando otro dispositivo, escanee el código QR.\n\nConsejo: Puede usar la webcam de su portátil para tomar una foto del código QR.",
|
||||
"importSelectJsonFile": "Seleccione el archivo JSON",
|
||||
"importSelectAppExport": "Seleccione el archivo de exportación de {appName}",
|
||||
"importEnteEncGuide": "Seleccione el archivo JSON cifrado exportado desde ente",
|
||||
"importRaivoGuide": "Utilice la opción \"Exportar códigos a un archivo de Zip\" en la configuración de Raivo.\n\nExtraiga el archivo zip e importe el archivo JSON.",
|
||||
"importBitwardenGuide": "Use la opción \"Exportar caja fuerte\" dentro del menú Herramientas de Bitwarden e importe el fichero JSON no crifrado.",
|
||||
"importAegisGuide": "Utilice la opción \"Exportar la bóveda\" en ajustes de Aegis.\n\nSi tu bóveda es cifrada, necesitara entrar contraseña de bóveda para descifrar la bóveda.",
|
||||
"import2FasGuide": "Use la opción \"Configuración→Copia de seguridad→Exportar\" en 2FAS\n\nSi su copia de seguridad está cifrada, necesitará introducir la contraseña para descifrarla",
|
||||
"importLastpassGuide": "Utilice la opción \"Transferir cuentas\" en la configuración del autenticador de Lastpass y pulse \"Exportar cuentas al archivo\". Importe el archivo JSON descargado.",
|
||||
"exportCodes": "Exportar códigos",
|
||||
"importLabel": "Importar",
|
||||
"importInstruction": "Por favor, seleccione un archivo que contenga una lista de sus códigos en el siguiente formato",
|
||||
|
@ -97,6 +102,8 @@
|
|||
"authToViewYourRecoveryKey": "Por favor, autentifíquese para ver su clave de recuperación",
|
||||
"authToChangeYourEmail": "Por favor, autentifíquese para cambiar su correo electrónico",
|
||||
"authToChangeYourPassword": "Por favor, autentifíquese para cambiar su contraseña",
|
||||
"authToViewSecrets": "Por favor, autentifíquese para ver sus secretos",
|
||||
"authToInitiateSignIn": "Por favor, autentifíquese para iniciar la sesión para realizar la copia de seguridad.",
|
||||
"ok": "Ok",
|
||||
"cancel": "Cancelar",
|
||||
"yes": "Si",
|
||||
|
@ -329,6 +336,7 @@
|
|||
"offlineModeWarning": "Ha elegido proceder sin copia de seguridad. Por favor, tome copias de seguridad manuales para asegurarse de que sus códigos están seguros.",
|
||||
"showLargeIcons": "Mostrar iconos grandes",
|
||||
"shouldHideCode": "Ocultar códigos",
|
||||
"doubleTapToViewHiddenCode": "Puedes tocar dos veces en una entrada para ver el código",
|
||||
"focusOnSearchBar": "Enfocar búsqueda al iniciar la aplicación",
|
||||
"confirmUpdatingkey": "¿Estás seguro de que deseas actualizar la clave secreto?",
|
||||
"minimizeAppOnCopy": "Minimizar aplicación al copiar",
|
||||
|
@ -336,5 +344,65 @@
|
|||
"deleteCodeAuthMessage": "Autenticar para borrar código",
|
||||
"showQRAuthMessage": "Autenticar para mostrar código QR",
|
||||
"confirmAccountDeleteTitle": "Confirmar eliminación de la cuenta",
|
||||
"confirmAccountDeleteMessage": "Esta cuenta está vinculada a otras aplicaciones de ente, si utiliza alguna.\n\nSe programará la eliminación de los datos que cargue en todas las aplicaciones de ente y su cuenta se eliminará permanentemente."
|
||||
"confirmAccountDeleteMessage": "Esta cuenta está vinculada a otras aplicaciones de ente, si utiliza alguna.\n\nSe programará la eliminación de los datos que cargue en todas las aplicaciones de ente y su cuenta se eliminará permanentemente.",
|
||||
"androidBiometricHint": "Verificar identidad",
|
||||
"@androidBiometricHint": {
|
||||
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricNotRecognized": "No reconocido. Inténtelo de nuevo.",
|
||||
"@androidBiometricNotRecognized": {
|
||||
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricSuccess": "Realizado correctamente",
|
||||
"@androidBiometricSuccess": {
|
||||
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidCancelButton": "Cancelar",
|
||||
"@androidCancelButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
|
||||
},
|
||||
"androidSignInTitle": "Se requiere autenticación",
|
||||
"@androidSignInTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricRequiredTitle": "Biométrica necesaria",
|
||||
"@androidBiometricRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsRequiredTitle": "Se necesitan credenciales de dispositivo",
|
||||
"@androidDeviceCredentialsRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsSetupDescription": "Se necesitan credenciales de dispositivo",
|
||||
"@androidDeviceCredentialsSetupDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"goToSettings": "Ir a Ajustes",
|
||||
"@goToSettings": {
|
||||
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
|
||||
},
|
||||
"androidGoToSettingsDescription": "La autenticación biométrica no está configurada en su dispositivo. Vaya a 'Ajustes > Seguridad' para añadir autenticación biométrica.",
|
||||
"@androidGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"iOSLockOut": "La autenticación biométrica está deshabilitada. Por favor bloquee y desbloquee la pantalla para habilitarla.",
|
||||
"@iOSLockOut": {
|
||||
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSGoToSettingsDescription": "La autenticación biométrica no está configurada en tu dispositivo. Por favor, activa Touch ID o Face ID en tu teléfono.",
|
||||
"@iOSGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSOkButton": "Aceptar",
|
||||
"@iOSOkButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
|
||||
},
|
||||
"noInternetConnection": "No hay conexión a Internet",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "Compruebe su conexión a Internet e inténtelo de nuevo.",
|
||||
"signOutFromOtherDevices": "Cerrar sesión desde otros dispositivos",
|
||||
"signOutOtherBody": "Si cree que alguien puede conocer su contraseña, puede forzar a todos los demás dispositivos que usen su cuenta a cerrar la sesión.",
|
||||
"signOutOtherDevices": "Cerrar la sesión de otros dispositivos",
|
||||
"doNotSignOut": "No cerrar la sesión",
|
||||
"hearUsWhereTitle": "¿Cómo conoció Ente? (opcional)",
|
||||
"hearUsExplanation": "No rastreamos las aplicaciones instaladas. ¡Nos ayudaría si nos dijera dónde nos encontró!"
|
||||
}
|
|
@ -8,7 +8,7 @@
|
|||
},
|
||||
"onBoardingBody": "Храните ваши коды двухфакторной аутентификации в безопасности",
|
||||
"onBoardingGetStarted": "Начать",
|
||||
"setupFirstAccount": "Настройте свою первую учетную запись",
|
||||
"setupFirstAccount": "Настройте свой первый аккаунт",
|
||||
"importScanQrCode": "Сканировать QR-код",
|
||||
"qrCode": "QR-код",
|
||||
"importEnterSetupKey": "Ввести ключ настройки",
|
||||
|
@ -71,8 +71,8 @@
|
|||
"incorrectPasswordTitle": "Неправильный пароль",
|
||||
"welcomeBack": "С возвращением!",
|
||||
"madeWithLoveAtPrefix": "сделана с ❤️ в ",
|
||||
"supportDevs": "Подпишитесь на <bold-green>ente</bold-green> для поддержки этого проекта.",
|
||||
"supportDiscount": "Используйте код скидки \"AUTH\", чтобы получить скидку 10% в первый год",
|
||||
"supportDevs": "Подпишитесь на <bold-green>ente</bold-green> для поддержки нашего проекта",
|
||||
"supportDiscount": "Используйте код скидки \"AUTH\", чтобы получить скидку 10% на первый год",
|
||||
"changeEmail": "Изменить почту",
|
||||
"changePassword": "Изменить пароль",
|
||||
"data": "Данные",
|
||||
|
@ -84,10 +84,12 @@
|
|||
"importFromApp": "Импорт кодов из {appName}",
|
||||
"importGoogleAuthGuide": "Экспортируйте учетные записи из Google Authenticator в QR-код, используя опцию «Перенести учетные записи». Затем с помощью другого устройства отсканируйте QR-код.\n\nСовет: Чтобы сфотографировать QR-код, можно воспользоваться веб-камерой ноутбука.",
|
||||
"importSelectJsonFile": "Выбрать JSON-файл",
|
||||
"importSelectAppExport": "Выбрать файл экспорта {appName}",
|
||||
"importEnteEncGuide": "Выберите зашифрованный JSON-файл, экспортированный из ente",
|
||||
"importRaivoGuide": "Используйте опцию «Export OTPs to Zip archive» в настройках Raivo.\n\nРаспакуйте zip-архив и импортируйте JSON-файл.",
|
||||
"importBitwardenGuide": "Используйте опцию \"Экспортировать хранилище\" в Bitwarden Tools и импортируйте незашифрованный JSON файл.",
|
||||
"importAegisGuide": "Используйте опцию «Экспортировать хранилище» в настройках Aegis.\n\nЕсли ваше хранилище зашифровано, то для его расшифровки потребуется ввести пароль хранилища.",
|
||||
"import2FasGuide": "Используйте опцию \"Settings->Backup -Export\" в 2FAS.\n\nЕсли ваша резервная копия зашифрована, то для расшифровки резервной копии необходимо ввести пароль",
|
||||
"exportCodes": "Экспортировать коды",
|
||||
"importLabel": "Импорт",
|
||||
"importInstruction": "Пожалуйста, выберите файл, содержащий список ваших кодов в следующем формате",
|
||||
|
|
87
auth/lib/l10n/arb/app_sv.arb
Normal file
87
auth/lib/l10n/arb/app_sv.arb
Normal file
|
@ -0,0 +1,87 @@
|
|||
{
|
||||
"account": "Konto",
|
||||
"unlock": "Lås upp",
|
||||
"recoveryKey": "Återställningsnyckel",
|
||||
"onBoardingBody": "Säkerhetskopiera dina 2FA-koder",
|
||||
"onBoardingGetStarted": "Kom igång",
|
||||
"setupFirstAccount": "Konfigurera ditt första konto",
|
||||
"importScanQrCode": "Skanna en QR-kod",
|
||||
"qrCode": "QR-kod",
|
||||
"importEnterSetupKey": "Ange en konfigurationskod",
|
||||
"importAccountPageTitle": "Ange kontodetaljer",
|
||||
"secretCanNotBeEmpty": "Secret kan inte vara tomt",
|
||||
"bothIssuerAndAccountCanNotBeEmpty": "Både utgivare och konto kan inte vara tomma",
|
||||
"incorrectDetails": "Felaktiga uppgifter",
|
||||
"pleaseVerifyDetails": "Kontrollera dina detaljer och försök igen",
|
||||
"codeIssuerHint": "Utfärdare",
|
||||
"codeSecretKeyHint": "Secret Key",
|
||||
"codeAccountHint": "Konto (du@domän.com)",
|
||||
"accountKeyType": "Typ av nyckel",
|
||||
"sessionExpired": "Sessionen har gått ut",
|
||||
"@sessionExpired": {
|
||||
"description": "Title of the dialog when the users current session is invalid/expired"
|
||||
},
|
||||
"pleaseLoginAgain": "Vänligen logga in igen",
|
||||
"loggingOut": "Loggar ut...",
|
||||
"saveAction": "Spara",
|
||||
"nextTotpTitle": "nästa",
|
||||
"deleteCodeMessage": "Vill du ta bort den här koden? Det går inte att ångra den här åtgärden.",
|
||||
"viewLogsAction": "Visa loggar",
|
||||
"emailLogsTitle": "E-posta loggar",
|
||||
"emailLogsMessage": "Skicka loggarna till {email}",
|
||||
"@emailLogsMessage": {
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"copyEmailAction": "Kopiera e-post",
|
||||
"exportLogsAction": "Exportera loggar",
|
||||
"reportABug": "Rapportera en bugg",
|
||||
"crashAndErrorReporting": "Krasch och felrapportering",
|
||||
"reportBug": "Rapportera bugg",
|
||||
"emailUsMessage": "Skicka e-mail till {email}",
|
||||
"@emailUsMessage": {
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"contactSupport": "Kontakta support",
|
||||
"rateUsOnStore": "Betygsätt på {storeName}",
|
||||
"blog": "Blogg",
|
||||
"merchandise": "Merchandise",
|
||||
"verifyPassword": "Bekräfta lösenord",
|
||||
"pleaseWait": "Vänligen vänta...",
|
||||
"generatingEncryptionKeysTitle": "Skapar krypteringsnycklar...",
|
||||
"recreatePassword": "Återskapa lösenord",
|
||||
"useRecoveryKey": "Använd återställningsnyckel",
|
||||
"incorrectPasswordTitle": "Felaktigt lösenord",
|
||||
"pleaseTryAgain": "Försök igen",
|
||||
"existingUser": "Befintlig användare",
|
||||
"delete": "Radera",
|
||||
"enterYourPasswordHint": "Ange ditt lösenord",
|
||||
"forgotPassword": "Glömt lösenord",
|
||||
"oops": "Hoppsan",
|
||||
"suggestFeatures": "Föreslå funktionalitet",
|
||||
"faq": "FAQ",
|
||||
"faq_q_1": "Hur säkert är ente Auth?",
|
||||
"weakStrength": "Svag",
|
||||
"strongStrength": "Stark",
|
||||
"moderateStrength": "Måttligt",
|
||||
"searchHint": "Sök...",
|
||||
"search": "Sök",
|
||||
"sorryUnableToGenCode": "Tyvärr, det gick inte att generera en kod för {issuerName}",
|
||||
"noResult": "Inga resultat",
|
||||
"addCode": "Lägg till kod",
|
||||
"scanAQrCode": "Skanna en QR-kod",
|
||||
"enterDetailsManually": "Ange uppgifter manuellt",
|
||||
"edit": "Redigera",
|
||||
"copiedToClipboard": "Kopierat till urklipp",
|
||||
"copiedNextToClipboard": "Kopierade nästa kod till urklipp",
|
||||
"error": "Fel",
|
||||
"recoveryKeyCopiedToClipboard": "Återställningsnyckel kopierad till urklipp",
|
||||
"recoveryKeyOnForgotPassword": "Om du glömmer ditt lösenord är det enda sättet du kan återställa dina data med denna nyckel."
|
||||
}
|
1
auth/lib/l10n/arb/app_th.arb
Normal file
1
auth/lib/l10n/arb/app_th.arb
Normal file
|
@ -0,0 +1 @@
|
|||
{}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,82 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/argon2"
|
||||
)
|
||||
|
||||
// deriveArgonKey generates a 32-bit cryptographic key using the Argon2id algorithm.
|
||||
// Parameters:
|
||||
// - password: The plaintext password to be hashed.
|
||||
// - salt: The salt as a base64 encoded string.
|
||||
// - memLimit: The memory limit in bytes.
|
||||
// - opsLimit: The number of iterations.
|
||||
//
|
||||
// Returns:
|
||||
// - A byte slice representing the derived key.
|
||||
// - An error object, which is nil if no error occurs.
|
||||
func deriveArgonKey(password, salt string, memLimit, opsLimit int) ([]byte, error) {
|
||||
if memLimit < 1024 || opsLimit < 1 {
|
||||
return nil, fmt.Errorf("invalid memory or operation limits")
|
||||
}
|
||||
|
||||
// Decode salt from base64
|
||||
saltBytes, err := base64.StdEncoding.DecodeString(salt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid salt: %v", err)
|
||||
}
|
||||
|
||||
// Generate key using Argon2id
|
||||
// Note: We're assuming a fixed key length of 32 bytes and changing the threads
|
||||
key := argon2.IDKey([]byte(password), saltBytes, uint32(opsLimit), uint32(memLimit/1024), 1, 32)
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// decryptChaCha20poly1305 decrypts the given data using the ChaCha20-Poly1305 algorithm.
|
||||
// Parameters:
|
||||
// - data: The encrypted data as a byte slice.
|
||||
// - key: The key for decryption as a byte slice.
|
||||
// - nonce: The nonce for decryption as a byte slice.
|
||||
//
|
||||
// Returns:
|
||||
// - A byte slice representing the decrypted data.
|
||||
// - An error object, which is nil if no error occurs.
|
||||
// func decryptChaCha20poly13052(data []byte, key []byte, nonce []byte) ([]byte, error) {
|
||||
// reader := bytes.NewReader(data)
|
||||
// header := sodium.SecretStreamXCPHeader{Bytes: nonce}
|
||||
// decoder, err := sodium.MakeSecretStreamXCPDecoder(
|
||||
// sodium.SecretStreamXCPKey{Bytes: key},
|
||||
// reader,
|
||||
// header)
|
||||
// if err != nil {
|
||||
// log.Println("Failed to make secret stream decoder", err)
|
||||
// return nil, err
|
||||
// }
|
||||
// // Buffer to store the decrypted data
|
||||
// decryptedData := make([]byte, len(data))
|
||||
// n, err := decoder.Read(decryptedData)
|
||||
// if err != nil && err != io.EOF {
|
||||
// log.Println("Failed to read from decoder", err)
|
||||
// return nil, err
|
||||
// }
|
||||
// return decryptedData[:n], nil
|
||||
// }
|
||||
|
||||
func decryptChaCha20poly13052(data []byte, key []byte, nonce []byte) ([]byte, error) {
|
||||
decryptor, err := NewDecryptor(key, nonce)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decoded, tag, err := decryptor.Pull(data)
|
||||
if tag != TagFinal {
|
||||
return nil, errors.New("invalid tag")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decoded, nil
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
password = "test_password"
|
||||
kdfSalt = "vd0dcYMGNLKn/gpT6uTFTw=="
|
||||
memLimit = 64 * 1024 * 1024 // 64MB
|
||||
opsLimit = 2
|
||||
cipherText = "kBXQ2PuX6y/aje5r22H0AehRPh6sQ0ULoeAO"
|
||||
cipherNonce = "v7wsI+BFZsRMIjDm3rTxPhmi/CaUdkdJ"
|
||||
expectedPlainText = "plain_text"
|
||||
expectedDerivedKey = "vp8d8Nee0BbIML4ab8Cp34uYnyrN77cRwTl920flyT0="
|
||||
)
|
||||
|
||||
func TestDeriveArgonKey(t *testing.T) {
|
||||
derivedKey, err := deriveArgonKey(password, kdfSalt, memLimit, opsLimit)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to derive key: %v", err)
|
||||
}
|
||||
|
||||
if base64.StdEncoding.EncodeToString(derivedKey) != expectedDerivedKey {
|
||||
t.Fatalf("Derived key does not match expected key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecryptChaCha20poly1305(t *testing.T) {
|
||||
derivedKey, err := deriveArgonKey(password, kdfSalt, memLimit, opsLimit)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to derive key: %v", err)
|
||||
}
|
||||
|
||||
decodedCipherText, err := base64.StdEncoding.DecodeString(cipherText)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to decode cipher text: %v", err)
|
||||
}
|
||||
|
||||
decodedCipherNonce, err := base64.StdEncoding.DecodeString(cipherNonce)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to decode cipher nonce: %v", err)
|
||||
}
|
||||
|
||||
decryptedText, err := decryptChaCha20poly13052(decodedCipherText, derivedKey, decodedCipherNonce)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to decrypt: %v", err)
|
||||
}
|
||||
if string(decryptedText) != expectedPlainText {
|
||||
t.Fatalf("Decrypted text : %s does not match the expected text: %s", string(decryptedText), expectedPlainText)
|
||||
}
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Export struct {
|
||||
Version int `json:"version"`
|
||||
KDFParams KDF `json:"kdfParams"`
|
||||
EncryptedData string `json:"encryptedData"`
|
||||
EncryptionNonce string `json:"encryptionNonce"`
|
||||
}
|
||||
|
||||
type KDF struct {
|
||||
MemLimit int `json:"memLimit"`
|
||||
OpsLimit int `json:"opsLimit"`
|
||||
Salt string `json:"salt"`
|
||||
}
|
||||
|
||||
func resolvePath(path string) (string, error) {
|
||||
if path[:2] != "~/" {
|
||||
return path, nil
|
||||
}
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return home + path[1:], nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
fmt.Println("Error:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if len(os.Args) != 4 {
|
||||
fmt.Println("Usage: ./decrypt <export_file> <password> <output_file>")
|
||||
return
|
||||
}
|
||||
|
||||
exportFile, err := resolvePath(os.Args[1])
|
||||
if err != nil {
|
||||
fmt.Println("Error resolving exportFile path:", err)
|
||||
return
|
||||
}
|
||||
password := os.Args[2]
|
||||
outputFile, err := resolvePath(os.Args[3])
|
||||
if err != nil {
|
||||
fmt.Println("Error resolving outputFile path:", err)
|
||||
return
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(exportFile)
|
||||
if err != nil {
|
||||
fmt.Println("Error reading file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
var export Export
|
||||
if err := json.Unmarshal(data, &export); err != nil {
|
||||
fmt.Println("Error parsing JSON:", err)
|
||||
return
|
||||
}
|
||||
|
||||
if export.Version != 1 {
|
||||
fmt.Println("Unsupported version")
|
||||
return
|
||||
}
|
||||
|
||||
encryptedData, err := base64.StdEncoding.DecodeString(export.EncryptedData)
|
||||
if err != nil {
|
||||
fmt.Println("Error decoding encrypted data:", err)
|
||||
return
|
||||
}
|
||||
|
||||
nonce, err := base64.StdEncoding.DecodeString(export.EncryptionNonce)
|
||||
if err != nil {
|
||||
fmt.Println("Error decoding nonce:", err)
|
||||
return
|
||||
}
|
||||
|
||||
key, err := deriveArgonKey(password, export.KDFParams.Salt, export.KDFParams.MemLimit, export.KDFParams.OpsLimit)
|
||||
if err != nil {
|
||||
fmt.Println("Error deriving key:", err)
|
||||
return
|
||||
}
|
||||
|
||||
decryptedData, err := decryptChaCha20poly13052(encryptedData, key, nonce)
|
||||
if err != nil {
|
||||
fmt.Println("Error decrypting data:", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := os.WriteFile(outputFile, decryptedData, 0644); err != nil {
|
||||
fmt.Println("Error writing decrypted data to file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Decrypted data written to %s\n", outputFile)
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
module decrypt
|
||||
|
||||
go 1.20
|
||||
|
||||
require golang.org/x/crypto v0.11.0
|
||||
|
||||
require golang.org/x/sys v0.10.0 // indirect
|
|
@ -1,4 +0,0 @@
|
|||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
@ -1,43 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Create a "bin" directory if it doesn't exist
|
||||
mkdir -p bin
|
||||
|
||||
# List of target operating systems
|
||||
OS_TARGETS=("windows" "linux" "darwin")
|
||||
|
||||
# Corresponding architectures for each OS
|
||||
ARCH_TARGETS=("386 amd64" "386 amd64 arm arm64" "amd64 arm64")
|
||||
|
||||
# Loop through each OS target
|
||||
for index in "${!OS_TARGETS[@]}"
|
||||
do
|
||||
OS=${OS_TARGETS[$index]}
|
||||
for ARCH in ${ARCH_TARGETS[$index]}
|
||||
do
|
||||
# Set the GOOS environment variable for the current target OS
|
||||
export GOOS="$OS"
|
||||
export GOARCH="$ARCH"
|
||||
|
||||
# Set the output binary name to "ente-decrypt" for the current OS and architecture
|
||||
BINARY_NAME="ente-decrypt-$OS-$ARCH"
|
||||
|
||||
# Add .exe extension for Windows
|
||||
if [ "$OS" == "windows" ]; then
|
||||
BINARY_NAME="ente-decrypt-$OS-$ARCH.exe"
|
||||
fi
|
||||
|
||||
# Build the binary and place it in the "bin" directory
|
||||
go build -o "bin/$BINARY_NAME" decrypt.go crypt.go stream.go
|
||||
|
||||
# Print a message indicating the build is complete for the current OS and architecture
|
||||
echo "Built for $OS ($ARCH) as bin/$BINARY_NAME"
|
||||
done
|
||||
done
|
||||
|
||||
# Clean up any environment variables
|
||||
unset GOOS
|
||||
unset GOARCH
|
||||
|
||||
# Print a message indicating the build process is complete
|
||||
echo "Build process completed for all platforms and architectures. Binaries are in the 'bin' directory."
|
|
@ -1,409 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/chacha20"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/crypto/poly1305"
|
||||
)
|
||||
|
||||
// public constants
|
||||
const (
|
||||
//TagMessage the most common tag, that doesn't add any information about the nature of the message.
|
||||
TagMessage = 0
|
||||
// TagPush indicates that the message marks the end of a set of messages,
|
||||
// but not the end of the stream. For example, a huge JSON string sent as multiple chunks can use this tag to indicate to the application that the string is complete and that it can be decoded. But the stream itself is not closed, and more data may follow.
|
||||
TagPush = 0x01
|
||||
// TagRekey "forget" the key used to encrypt this message and the previous ones, and derive a new secret key.
|
||||
TagRekey = 0x02
|
||||
// TagFinal indicates that the message marks the end of the stream, and erases the secret key used to encrypt the previous sequence.
|
||||
TagFinal = TagPush | TagRekey
|
||||
|
||||
StreamKeyBytes = chacha20poly1305.KeySize
|
||||
StreamHeaderBytes = chacha20poly1305.NonceSizeX
|
||||
// XChaCha20Poly1305IetfABYTES links to crypto_secretstream_xchacha20poly1305_ABYTES
|
||||
XChaCha20Poly1305IetfABYTES = 16 + 1
|
||||
)
|
||||
|
||||
const cryptoCoreHchacha20InputBytes = 16
|
||||
|
||||
/* const crypto_secretstream_xchacha20poly1305_INONCEBYTES = 8 */
|
||||
const cryptoSecretStreamXchacha20poly1305Counterbytes = 4
|
||||
|
||||
var pad0 [16]byte
|
||||
|
||||
var invalidKey = errors.New("invalid key")
|
||||
var invalidInput = errors.New("invalid input")
|
||||
var cryptoFailure = errors.New("crypto failed")
|
||||
|
||||
func memZero(b []byte) {
|
||||
for i := range b {
|
||||
b[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
func xorBuf(out, in []byte) {
|
||||
for i := range out {
|
||||
out[i] ^= in[i]
|
||||
}
|
||||
}
|
||||
|
||||
func bufInc(n []byte) {
|
||||
c := 1
|
||||
|
||||
for i := range n {
|
||||
c += int(n[i])
|
||||
n[i] = byte(c)
|
||||
c >>= 8
|
||||
}
|
||||
}
|
||||
|
||||
// crypto_secretstream_xchacha20poly1305_state
|
||||
type streamState struct {
|
||||
k [StreamKeyBytes]byte
|
||||
nonce [chacha20poly1305.NonceSize]byte
|
||||
pad [8]byte
|
||||
}
|
||||
|
||||
func (s *streamState) reset() {
|
||||
for i := range s.nonce {
|
||||
s.nonce[i] = 0
|
||||
}
|
||||
s.nonce[0] = 1
|
||||
}
|
||||
|
||||
type Encryptor interface {
|
||||
Push(m []byte, tag byte) ([]byte, error)
|
||||
}
|
||||
|
||||
type Decryptor interface {
|
||||
Pull(m []byte) ([]byte, byte, error)
|
||||
}
|
||||
|
||||
type encryptor struct {
|
||||
streamState
|
||||
}
|
||||
|
||||
type decryptor struct {
|
||||
streamState
|
||||
}
|
||||
|
||||
func NewStreamKey() []byte {
|
||||
k := make([]byte, chacha20poly1305.KeySize)
|
||||
_, _ = rand.Read(k)
|
||||
return k
|
||||
}
|
||||
|
||||
func NewEncryptor(key []byte) (Encryptor, []byte, error) {
|
||||
if len(key) != StreamKeyBytes {
|
||||
return nil, nil, invalidKey
|
||||
}
|
||||
|
||||
header := make([]byte, StreamHeaderBytes)
|
||||
_, _ = rand.Read(header)
|
||||
|
||||
stream := &encryptor{}
|
||||
|
||||
k, err := chacha20.HChaCha20(key[:], header[:16])
|
||||
if err != nil {
|
||||
//fmt.Printf("error: %v", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
copy(stream.k[:], k)
|
||||
stream.reset()
|
||||
|
||||
for i := range stream.pad {
|
||||
stream.pad[i] = 0
|
||||
}
|
||||
|
||||
for i, b := range header[cryptoCoreHchacha20InputBytes:] {
|
||||
stream.nonce[i+cryptoSecretStreamXchacha20poly1305Counterbytes] = b
|
||||
}
|
||||
// fmt.Printf("stream: %+v\n", stream.streamState)
|
||||
|
||||
return stream, header, nil
|
||||
}
|
||||
|
||||
func (s *encryptor) Push(plain []byte, tag byte) ([]byte, error) {
|
||||
var err error
|
||||
|
||||
//crypto_onetimeauth_poly1305_state poly1305_state;
|
||||
var poly *poly1305.MAC
|
||||
|
||||
//unsigned char block[64U];
|
||||
var block [64]byte
|
||||
|
||||
//unsigned char slen[8U];
|
||||
var slen [8]byte
|
||||
|
||||
//unsigned char *c;
|
||||
//unsigned char *mac;
|
||||
//
|
||||
//if (outlen_p != NULL) {
|
||||
//*outlen_p = 0U;
|
||||
//}
|
||||
|
||||
mlen := len(plain)
|
||||
//if (mlen > crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX) {
|
||||
//sodium_misuse();
|
||||
//}
|
||||
|
||||
out := make([]byte, mlen+XChaCha20Poly1305IetfABYTES)
|
||||
|
||||
chacha, err := chacha20.NewUnauthenticatedCipher(s.k[:], s.nonce[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//crypto_stream_chacha20_ietf(block, sizeof block, state->nonce, state->k);
|
||||
chacha.XORKeyStream(block[:], block[:])
|
||||
|
||||
//crypto_onetimeauth_poly1305_init(&poly1305_state, block);
|
||||
var poly_init [32]byte
|
||||
copy(poly_init[:], block[:])
|
||||
poly = poly1305.New(&poly_init)
|
||||
|
||||
// TODO add support for add data
|
||||
//sodium_memzero(block, sizeof block);
|
||||
//crypto_onetimeauth_poly1305_update(&poly1305_state, ad, adlen);
|
||||
//crypto_onetimeauth_poly1305_update(&poly1305_state, _pad0,
|
||||
//(0x10 - adlen) & 0xf);
|
||||
|
||||
//memset(block, 0, sizeof block);
|
||||
//block[0] = tag;
|
||||
memZero(block[:])
|
||||
block[0] = tag
|
||||
|
||||
//
|
||||
//crypto_stream_chacha20_ietf_xor_ic(block, block, sizeof block, state->nonce, 1U, state->k);
|
||||
//crypto_onetimeauth_poly1305_update(&poly1305_state, block, sizeof block);
|
||||
//out[0] = block[0];
|
||||
chacha.XORKeyStream(block[:], block[:])
|
||||
_, _ = poly.Write(block[:])
|
||||
out[0] = block[0]
|
||||
|
||||
//
|
||||
//c = out + (sizeof tag);
|
||||
c := out[1:]
|
||||
//crypto_stream_chacha20_ietf_xor_ic(c, m, mlen, state->nonce, 2U, state->k);
|
||||
//crypto_onetimeauth_poly1305_update(&poly1305_state, c, mlen);
|
||||
//crypto_onetimeauth_poly1305_update (&poly1305_state, _pad0, (0x10 - (sizeof block) + mlen) & 0xf);
|
||||
chacha.XORKeyStream(c, plain)
|
||||
_, _ = poly.Write(c[:mlen])
|
||||
padlen := (0x10 - len(block) + mlen) & 0xf
|
||||
_, _ = poly.Write(pad0[:padlen])
|
||||
|
||||
//
|
||||
//STORE64_LE(slen, (uint64_t) adlen);
|
||||
//crypto_onetimeauth_poly1305_update(&poly1305_state, slen, sizeof slen);
|
||||
binary.LittleEndian.PutUint64(slen[:], uint64(0))
|
||||
_, _ = poly.Write(slen[:])
|
||||
|
||||
//STORE64_LE(slen, (sizeof block) + mlen);
|
||||
//crypto_onetimeauth_poly1305_update(&poly1305_state, slen, sizeof slen);
|
||||
binary.LittleEndian.PutUint64(slen[:], uint64(len(block)+mlen))
|
||||
_, _ = poly.Write(slen[:])
|
||||
|
||||
//
|
||||
//mac = c + mlen;
|
||||
//crypto_onetimeauth_poly1305_final(&poly1305_state, mac);
|
||||
mac := c[mlen:]
|
||||
copy(mac, poly.Sum(nil))
|
||||
//sodium_memzero(&poly1305_state, sizeof poly1305_state);
|
||||
//
|
||||
|
||||
//XOR_BUF(STATE_INONCE(state), mac, crypto_secretstream_xchacha20poly1305_INONCEBYTES);
|
||||
//sodium_increment(STATE_COUNTER(state), crypto_secretstream_xchacha20poly1305_COUNTERBYTES);
|
||||
xorBuf(s.nonce[cryptoSecretStreamXchacha20poly1305Counterbytes:], mac)
|
||||
bufInc(s.nonce[:cryptoSecretStreamXchacha20poly1305Counterbytes])
|
||||
|
||||
// TODO
|
||||
//if ((tag & crypto_secretstream_xchacha20poly1305_TAG_REKEY) != 0 ||
|
||||
//sodium_is_zero(STATE_COUNTER(state),
|
||||
//crypto_secretstream_xchacha20poly1305_COUNTERBYTES)) {
|
||||
//crypto_secretstream_xchacha20poly1305_rekey(state);
|
||||
//}
|
||||
|
||||
//if (outlen_p != NULL) {
|
||||
//*outlen_p = crypto_secretstream_xchacha20poly1305_ABYTES + mlen;
|
||||
//}
|
||||
|
||||
//return 0;
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func NewDecryptor(key, header []byte) (Decryptor, error) {
|
||||
stream := &decryptor{}
|
||||
|
||||
//crypto_core_hchacha20(state->k, in, k, NULL);
|
||||
k, err := chacha20.HChaCha20(key, header[:16])
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
copy(stream.k[:], k)
|
||||
|
||||
//_crypto_secretstream_xchacha20poly1305_counter_reset(state);
|
||||
stream.reset()
|
||||
|
||||
//memcpy(STATE_INONCE(state), in + crypto_core_hchacha20_INPUTBYTES,
|
||||
// crypto_secretstream_xchacha20poly1305_INONCEBYTES);
|
||||
copy(stream.nonce[cryptoSecretStreamXchacha20poly1305Counterbytes:],
|
||||
header[cryptoCoreHchacha20InputBytes:])
|
||||
|
||||
//memset(state->_pad, 0, sizeof state->_pad);
|
||||
copy(stream.pad[:], pad0[:])
|
||||
|
||||
//fmt.Printf("decryptor: %+v\n", stream.streamState)
|
||||
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
func (s *decryptor) Pull(cipher []byte) ([]byte, byte, error) {
|
||||
cipherLen := len(cipher)
|
||||
|
||||
//crypto_onetimeauth_poly1305_state poly1305_state;
|
||||
var poly1305State [32]byte
|
||||
|
||||
//unsigned char block[64U];
|
||||
var block [64]byte
|
||||
//unsigned char slen[8U];
|
||||
var slen [8]byte
|
||||
|
||||
//unsigned char mac[crypto_onetimeauth_poly1305_BYTES];
|
||||
//const unsigned char *c;
|
||||
//const unsigned char *stored_mac;
|
||||
//unsigned long long mlen; // length of the returned message
|
||||
//unsigned char tag; // for the return value
|
||||
//
|
||||
//if (mlen_p != NULL) {
|
||||
//*mlen_p = 0U;
|
||||
//}
|
||||
//if (tag_p != NULL) {
|
||||
//*tag_p = 0xff;
|
||||
//}
|
||||
|
||||
/*
|
||||
if (inlen < crypto_secretstream_xchacha20poly1305_ABYTES) {
|
||||
return -1;
|
||||
}
|
||||
mlen = inlen - crypto_secretstream_xchacha20poly1305_ABYTES;
|
||||
*/
|
||||
if cipherLen < XChaCha20Poly1305IetfABYTES {
|
||||
return nil, 0, invalidInput
|
||||
}
|
||||
mlen := cipherLen - XChaCha20Poly1305IetfABYTES
|
||||
|
||||
//if (mlen > crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX) {
|
||||
//sodium_misuse();
|
||||
//}
|
||||
|
||||
//crypto_stream_chacha20_ietf(block, sizeof block, state->nonce, state->k);
|
||||
chacha, err := chacha20.NewUnauthenticatedCipher(s.k[:], s.nonce[:])
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
chacha.XORKeyStream(block[:], block[:])
|
||||
|
||||
//crypto_onetimeauth_poly1305_init(&poly1305_state, block);
|
||||
|
||||
copy(poly1305State[:], block[:])
|
||||
poly := poly1305.New(&poly1305State)
|
||||
|
||||
// TODO
|
||||
//sodium_memzero(block, sizeof block);
|
||||
//crypto_onetimeauth_poly1305_update(&poly1305_state, ad, adlen);
|
||||
//crypto_onetimeauth_poly1305_update(&poly1305_state, _pad0,
|
||||
//(0x10 - adlen) & 0xf);
|
||||
//
|
||||
|
||||
//memset(block, 0, sizeof block);
|
||||
//block[0] = in[0];
|
||||
//crypto_stream_chacha20_ietf_xor_ic(block, block, sizeof block, state->nonce, 1U, state->k);
|
||||
memZero(block[:])
|
||||
block[0] = cipher[0]
|
||||
chacha.XORKeyStream(block[:], block[:])
|
||||
|
||||
//tag = block[0];
|
||||
//block[0] = in[0];
|
||||
//crypto_onetimeauth_poly1305_update(&poly1305_state, block, sizeof block);
|
||||
tag := block[0]
|
||||
block[0] = cipher[0]
|
||||
if _, err = poly.Write(block[:]); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
//c = in + (sizeof tag);
|
||||
//crypto_onetimeauth_poly1305_update(&poly1305_state, c, mlen);
|
||||
//crypto_onetimeauth_poly1305_update (&poly1305_state, _pad0, (0x10 - (sizeof block) + mlen) & 0xf);
|
||||
c := cipher[1:]
|
||||
if _, err = poly.Write(c[:mlen]); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
padLen := (0x10 - len(block) + mlen) & 0xf
|
||||
if _, err = poly.Write(pad0[:padLen]); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
//
|
||||
//STORE64_LE(slen, (uint64_t) adlen);
|
||||
//crypto_onetimeauth_poly1305_update(&poly1305_state, slen, sizeof slen);
|
||||
binary.LittleEndian.PutUint64(slen[:], uint64(0))
|
||||
if _, err = poly.Write(slen[:]); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
//STORE64_LE(slen, (sizeof block) + mlen);
|
||||
//crypto_onetimeauth_poly1305_update(&poly1305_state, slen, sizeof slen);
|
||||
binary.LittleEndian.PutUint64(slen[:], uint64(len(block)+mlen))
|
||||
if _, err = poly.Write(slen[:]); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
//
|
||||
//crypto_onetimeauth_poly1305_final(&poly1305_state, mac);
|
||||
//sodium_memzero(&poly1305_state, sizeof poly1305_state);
|
||||
|
||||
mac := poly.Sum(nil)
|
||||
memZero(poly1305State[:])
|
||||
|
||||
//stored_mac = c + mlen;
|
||||
//if (sodium_memcmp(mac, stored_mac, sizeof mac) != 0) {
|
||||
//sodium_memzero(mac, sizeof mac);
|
||||
//return -1;
|
||||
//}
|
||||
storedMac := c[mlen:]
|
||||
if !bytes.Equal(mac, storedMac) {
|
||||
memZero(mac)
|
||||
return nil, 0, cryptoFailure
|
||||
}
|
||||
|
||||
//crypto_stream_chacha20_ietf_xor_ic(m, c, mlen, state->nonce, 2U, state->k);
|
||||
//XOR_BUF(STATE_INONCE(state), mac, crypto_secretstream_xchacha20poly1305_INONCEBYTES);
|
||||
//sodium_increment(STATE_COUNTER(state), crypto_secretstream_xchacha20poly1305_COUNTERBYTES);
|
||||
m := make([]byte, mlen)
|
||||
chacha.XORKeyStream(m, c[:mlen])
|
||||
|
||||
xorBuf(s.nonce[cryptoSecretStreamXchacha20poly1305Counterbytes:], mac)
|
||||
bufInc(s.nonce[:cryptoSecretStreamXchacha20poly1305Counterbytes])
|
||||
|
||||
// TODO
|
||||
//if ((tag & crypto_secretstream_xchacha20poly1305_TAG_REKEY) != 0 ||
|
||||
//sodium_is_zero(STATE_COUNTER(state),
|
||||
//crypto_secretstream_xchacha20poly1305_COUNTERBYTES)) {
|
||||
//crypto_secretstream_xchacha20poly1305_rekey(state);
|
||||
//}
|
||||
|
||||
//if (mlen_p != NULL) {
|
||||
//*mlen_p = mlen;
|
||||
//}
|
||||
//if (tag_p != NULL) {
|
||||
//*tag_p = tag;
|
||||
//}
|
||||
//return 0;
|
||||
return m, tag, nil
|
||||
}
|
|
@ -53,11 +53,11 @@ For encryption, we are using `XChaCha20-Poly1305` algorithm.
|
|||
|
||||
## How to use the exported data
|
||||
|
||||
* **ente Authenticator app**: You can directly import the codes in the ente Authenticator app.
|
||||
* **Ente Authenticator app**: You can directly import the codes in the Ente Authenticator app.
|
||||
> Settings -> Data -> Import Codes -> ente Encrypted export.
|
||||
|
||||
* **Decryption Tool** : You can download the prebuilt [decryption tool](decrypt/bin/) (or build it from [source](decrypt)) and run the following command.
|
||||
* **Decrypt using Ente CLI** : Download the latest version of [Ente CLI](https://github.com/ente-io/ente/releases?q=CLI&expanded=false), and run the following command
|
||||
|
||||
```
|
||||
./decrypt <export_file> <password> <output_file>
|
||||
./ente auth decrypt <export_file> <output_file>
|
||||
```
|
||||
|
|
36
cli/.github/workflows/release.yml
vendored
36
cli/.github/workflows/release.yml
vendored
|
@ -1,36 +0,0 @@
|
|||
name: Release
|
||||
|
||||
on:
|
||||
# allow manual run
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*' # This will run the workflow when you push a new tag in the format v0.0.0
|
||||
- 'v*.*.*-beta.*'
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install latest Syft
|
||||
run: |
|
||||
wget $(curl -s https://api.github.com/repos/anchore/syft/releases/latest | grep 'browser_' | grep 'linux_amd64.rpm' | cut -d\" -f4) -O syft_latest_linux_amd64.rpm
|
||||
sudo rpm -i syft_latest_linux_amd64.rpm
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Important to ensure that GoReleaser works correctly
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.20' # You can adjust the Go version here
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --rm-dist
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Use the provided GITHUB_TOKEN secret
|
|
@ -1,58 +0,0 @@
|
|||
# This is an example .goreleaser.yml file with some sensible defaults.
|
||||
# Make sure to check the documentation at https://goreleaser.com
|
||||
|
||||
# The lines bellow are called `modelines`. See `:help modeline`
|
||||
# Feel free to remove those if you don't want/need to use them.
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
|
||||
project_name: ente
|
||||
before:
|
||||
hooks:
|
||||
# You may remove this if you don't use go modules.
|
||||
- go mod tidy
|
||||
# you may remove this if you don't need go generate
|
||||
- go generate ./...
|
||||
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
|
||||
nfpms:
|
||||
- package_name: ente
|
||||
homepage: https://github.com/ente-io/cli
|
||||
maintainer: ente.io <engineering@ente.io>
|
||||
description: |-
|
||||
Command Line Utility for exporting data from https://ente.io
|
||||
formats:
|
||||
- rpm
|
||||
- deb
|
||||
- apk
|
||||
|
||||
sboms:
|
||||
- artifacts: archive
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
# this name template makes the OS and Arch compatible with the results of `uname`.
|
||||
name_template: >-
|
||||
{{ .ProjectName }}_
|
||||
{{- title .Os }}_
|
||||
{{- if eq .Arch "amd64" }}x86_64
|
||||
{{- else if eq .Arch "386" }}i386
|
||||
{{- else }}{{ .Arch }}{{ end }}
|
||||
{{- if .Arm }}v{{ .Arm }}{{ end }}
|
||||
# use zip for windows archives
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- "^docs:"
|
||||
- "^test:"
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
## Install
|
||||
|
||||
You can either download the binary from the [release page](https://github.com/ente-io/cli/releases) or build it yourself.
|
||||
You can either download the binary from the [GitHub releases
|
||||
page](https://github.com/ente-io/ente/releases?q=cli&expanded=true) or build it
|
||||
yourself.
|
||||
|
||||
### Build from source
|
||||
|
||||
|
@ -29,10 +31,10 @@ ente account add
|
|||
```shell
|
||||
ente account list
|
||||
```
|
||||
|
||||
|
||||
##### Change export directory
|
||||
```shell
|
||||
ente account update --email email@domain.com --dir ~/photos
|
||||
ente account update --email email@domain.com --dir ~/photos
|
||||
```
|
||||
|
||||
### Export
|
||||
|
@ -58,7 +60,7 @@ docker build -t ente:latest .
|
|||
```
|
||||
|
||||
Start the container in detached mode
|
||||
```bash
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
|
@ -66,12 +68,12 @@ docker-compose up -d
|
|||
```shell
|
||||
docker-compose exec ente /bin/sh
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
#### Directly executing commands
|
||||
|
||||
```shell
|
||||
docker run -it --rm ente:latest ls
|
||||
docker run -it --rm ente:latest ls
|
||||
```
|
||||
|
||||
---
|
||||
|
|
29
cli/cmd/authenticator.go
Normal file
29
cli/cmd/authenticator.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/ente-io/cli/pkg/authenticator"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// Define the 'config' command and its subcommands
|
||||
var authenticatorCmd = &cobra.Command{
|
||||
Use: "auth",
|
||||
Short: "Authenticator commands",
|
||||
}
|
||||
|
||||
// Subcommand for 'config update'
|
||||
var decryptExportCmd = &cobra.Command{
|
||||
Use: "decrypt [input] [output]",
|
||||
Short: "Decrypt authenticator export",
|
||||
Args: cobra.ExactArgs(2), // Ensures exactly two arguments are passed
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
inputPath := args[0]
|
||||
outputPath := args[1]
|
||||
return authenticator.DecryptExport(inputPath, outputPath)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(authenticatorCmd)
|
||||
authenticatorCmd.AddCommand(decryptExportCmd)
|
||||
}
|
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const AppVersion = "0.1.10"
|
||||
const AppVersion = "0.1.11"
|
||||
|
||||
var ctrl *pkg.ClICtrl
|
||||
|
||||
|
|
71
cli/pkg/authenticator/decrypt.go
Normal file
71
cli/pkg/authenticator/decrypt.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
package authenticator
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/ente-io/cli/internal"
|
||||
eCrypto "github.com/ente-io/cli/internal/crypto"
|
||||
"os"
|
||||
)
|
||||
|
||||
type _Export struct {
|
||||
Version int `json:"version"`
|
||||
KDFParams _KDF `json:"kdfParams"`
|
||||
EncryptedData string `json:"encryptedData"`
|
||||
EncryptionNonce string `json:"encryptionNonce"`
|
||||
}
|
||||
|
||||
type _KDF struct {
|
||||
MemLimit int `json:"memLimit"`
|
||||
OpsLimit int `json:"opsLimit"`
|
||||
Salt string `json:"salt"`
|
||||
}
|
||||
|
||||
func DecryptExport(inputPath string, outputPath string) error {
|
||||
exportFile, err := internal.ResolvePath(inputPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error resolving exportFile path (in): %v", err)
|
||||
}
|
||||
outputFile, err := internal.ResolvePath(outputPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error resolving outputFile path (out): %v", err)
|
||||
} // Implement your decryption logic here
|
||||
|
||||
data, err := os.ReadFile(exportFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading file: %v", err)
|
||||
}
|
||||
|
||||
var export _Export
|
||||
if err := json.Unmarshal(data, &export); err != nil {
|
||||
return fmt.Errorf("error parsing JSON: %v", err)
|
||||
}
|
||||
|
||||
if export.Version != 1 {
|
||||
return fmt.Errorf("unsupported export version: %d", export.Version)
|
||||
}
|
||||
|
||||
password, err := internal.GetSensitiveField("Enter password to decrypt export")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("\n....")
|
||||
key, err := eCrypto.DeriveArgonKey(password, export.KDFParams.Salt, export.KDFParams.MemLimit, export.KDFParams.OpsLimit)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deriving key: %v", err)
|
||||
}
|
||||
|
||||
_, decryptedData, err := eCrypto.DecryptChaChaBase64(export.EncryptedData, key, export.EncryptionNonce)
|
||||
if err != nil {
|
||||
fmt.Printf("\nerror decrypting data %v", err)
|
||||
fmt.Println("\nPlease check your password and try again")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := os.WriteFile(outputFile, decryptedData, 0644); err != nil {
|
||||
return fmt.Errorf("error writing file: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\nExport decrypted successfully to %s\n", outputFile)
|
||||
return nil
|
||||
}
|
65
mobile/.github/workflows/build.yml
vendored
65
mobile/.github/workflows/build.yml
vendored
|
@ -1,65 +0,0 @@
|
|||
name: Manual build
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Enable manual run only
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# This job will run on ubuntu virtual machine
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
# Setup Java environment in order to build the Android app.
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
|
||||
# Setup the flutter environment.
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: 'stable'
|
||||
flutter-version: '3.13.4'
|
||||
|
||||
# Fetch sub modules
|
||||
- run: git submodule update --init --recursive
|
||||
|
||||
# Get flutter dependencies.
|
||||
- run: flutter pub get
|
||||
|
||||
- name: Setup keys
|
||||
uses: timheuer/base64-to-file@v1
|
||||
with:
|
||||
fileName: 'keystore/ente_photos_key.jks'
|
||||
encodedString: ${{ secrets.SIGNING_KEY }}
|
||||
|
||||
# Build independent apk.
|
||||
- name: Build
|
||||
run: flutter build apk --release --flavor independent && mv build/app/outputs/flutter-apk/app-independent-release.apk build/app/outputs/flutter-apk/ente.apk
|
||||
env:
|
||||
SIGNING_KEY_PATH: '/home/runner/work/_temp/keystore/ente_photos_key.jks'
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
|
||||
- name: Checksum
|
||||
run: sha256sum build/app/outputs/flutter-apk/ente.apk > build/app/outputs/flutter-apk/sha256sum
|
||||
|
||||
# Upload generated apk to the artifacts.
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-apk
|
||||
path: build/app/outputs/flutter-apk/ente.apk
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-checksum
|
||||
path: build/app/outputs/flutter-apk/sha256sum
|
||||
|
||||
# Create a pre-release
|
||||
- uses: ncipollo/release-action@v1.14.0
|
||||
with:
|
||||
artifacts: "build/app/outputs/flutter-apk/ente.apk,build/app/outputs/flutter-apk/sha256sum"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
prerelease: true
|
33
mobile/.github/workflows/code_quality.yml
vendored
33
mobile/.github/workflows/code_quality.yml
vendored
|
@ -1,33 +0,0 @@
|
|||
name: Check Linter Rules
|
||||
on:
|
||||
pull_request:
|
||||
types: [review_requested]
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
test:
|
||||
name: Check the source code
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ runner.tool_cache }}/flutter
|
||||
key: flutter-3.0.0-stable
|
||||
# Setup the flutter environment.
|
||||
- uses: subosito/flutter-action@v2.3.0
|
||||
with:
|
||||
channel: 'stable'
|
||||
flutter-version: '3.0.0'
|
||||
|
||||
# Fetch sub modules
|
||||
- run: git submodule update --init --recursive
|
||||
|
||||
# Get flutter dependencies.
|
||||
- name: Install packages
|
||||
run: flutter pub get
|
||||
|
||||
- name: Run Linter
|
||||
run: flutter analyze --no-fatal-infos
|
||||
- name: Run Test
|
||||
run: flutter test
|
36
mobile/.github/workflows/crowdin.yml
vendored
36
mobile/.github/workflows/crowdin.yml
vendored
|
@ -1,36 +0,0 @@
|
|||
name: Sync crowdin translation
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- 'lib/l10n/intl_en.arb'
|
||||
branches: [ main ]
|
||||
schedule:
|
||||
- cron: '0 */12 * * *' # Every 12 hours - https://crontab.guru/#0_*/12_*_*_*
|
||||
|
||||
jobs:
|
||||
synchronize-with-crowdin:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: crowdin action
|
||||
uses: crowdin/github-action@v1
|
||||
with:
|
||||
upload_sources: true
|
||||
upload_translations: true
|
||||
download_translations: true
|
||||
localization_branch_name: l10n_translations
|
||||
create_pull_request: true
|
||||
skip_untranslated_strings: true
|
||||
pull_request_title: 'New Translations'
|
||||
pull_request_body: 'New translations via [Crowdin GH Action](https://github.com/crowdin/github-action)'
|
||||
pull_request_base_branch_name: 'main'
|
||||
pull_request_reviewers: ashilkn,vishnukvmd,ua741
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
70
mobile/.github/workflows/release.yml
vendored
70
mobile/.github/workflows/release.yml
vendored
|
@ -1,70 +0,0 @@
|
|||
name: Release
|
||||
|
||||
# This workflow is triggered on pushes to the repository.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# Enable manual run
|
||||
push:
|
||||
# Sequence of patterns matched against refs/tags
|
||||
tags:
|
||||
- 'v*' # Push events to matching v*, i.e. v4.2.0
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# This job will run on ubuntu virtual machine
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
# Setup Java environment in order to build the Android app.
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
|
||||
# Setup the flutter environment.
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: 'stable'
|
||||
flutter-version: '3.13.4'
|
||||
|
||||
# Fetch sub modules
|
||||
- run: git submodule update --init --recursive
|
||||
|
||||
# Get flutter dependencies.
|
||||
- run: flutter pub get
|
||||
|
||||
- name: Setup keys
|
||||
uses: timheuer/base64-to-file@v1
|
||||
with:
|
||||
fileName: 'keystore/ente_photos_key.jks'
|
||||
encodedString: ${{ secrets.SIGNING_KEY }}
|
||||
|
||||
# Build independent apk.
|
||||
- name: Build
|
||||
run: flutter build apk --release --flavor independent && mv build/app/outputs/flutter-apk/app-independent-release.apk build/app/outputs/flutter-apk/ente.apk
|
||||
env:
|
||||
SIGNING_KEY_PATH: '/home/runner/work/_temp/keystore/ente_photos_key.jks'
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
|
||||
- name: Checksum
|
||||
run: sha256sum build/app/outputs/flutter-apk/ente.apk > build/app/outputs/flutter-apk/sha256sum
|
||||
|
||||
# Upload generated apk to the artifacts.
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: release-apk
|
||||
path: build/app/outputs/flutter-apk/ente.apk
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: release-checksum
|
||||
path: build/app/outputs/flutter-apk/checksum
|
||||
|
||||
# Create a Github release
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "build/app/outputs/flutter-apk/ente.apk,build/app/outputs/flutter-apk/sha256sum"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -1,4 +1,3 @@
|
|||
project_id_env: CROWDIN_PROJECT_ID
|
||||
api_token_env: CROWDIN_PERSONAL_TOKEN
|
||||
preserve_hierarchy: true
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ import "package:photos/models/user_details.dart";
|
|||
import 'package:photos/services/local_authentication_service.dart';
|
||||
import 'package:photos/services/user_service.dart';
|
||||
import 'package:photos/theme/ente_theme.dart';
|
||||
import "package:photos/ui/account/recovery_key_page.dart";
|
||||
import "package:photos/ui/account/request_pwd_verification_page.dart";
|
||||
import 'package:photos/ui/account/sessions_page.dart';
|
||||
import 'package:photos/ui/components/captioned_text_widget.dart';
|
||||
|
@ -20,7 +19,6 @@ import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
|
|||
import 'package:photos/ui/components/toggle_switch_widget.dart';
|
||||
import 'package:photos/ui/settings/common_settings.dart';
|
||||
import "package:photos/utils/crypto_util.dart";
|
||||
import "package:photos/utils/dialog_util.dart";
|
||||
import "package:photos/utils/navigation_util.dart";
|
||||
import "package:photos/utils/toast_util.dart";
|
||||
|
||||
|
@ -69,43 +67,6 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
|||
if (_config.hasConfiguredAccount()) {
|
||||
children.addAll(
|
||||
[
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: S.of(context).recoveryKey,
|
||||
),
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
showOnlyLoadingState: true,
|
||||
onTap: () async {
|
||||
final hasAuthenticated = await LocalAuthenticationService.instance
|
||||
.requestLocalAuthentication(
|
||||
context,
|
||||
S.of(context).authToViewYourRecoveryKey,
|
||||
);
|
||||
if (hasAuthenticated) {
|
||||
String recoveryKey;
|
||||
try {
|
||||
recoveryKey = await _getOrCreateRecoveryKey(context);
|
||||
} catch (e) {
|
||||
await showGenericErrorDialog(context: context, error: e);
|
||||
return;
|
||||
}
|
||||
unawaited(
|
||||
routeToPage(
|
||||
context,
|
||||
RecoveryKeyPage(
|
||||
recoveryKey,
|
||||
S.of(context).ok,
|
||||
showAppBar: true,
|
||||
onDone: () {},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
|
@ -255,12 +216,6 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
|||
);
|
||||
}
|
||||
|
||||
Future<String> _getOrCreateRecoveryKey(BuildContext context) async {
|
||||
return CryptoUtil.bin2hex(
|
||||
await UserService.instance.getOrCreateRecoveryKey(context),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> updateEmailMFA(bool isEnabled) async {
|
||||
try {
|
||||
final UserDetails details =
|
||||
|
|
28
server/.github/workflows/dev-ci.yml
vendored
28
server/.github/workflows/dev-ci.yml
vendored
|
@ -1,28 +0,0 @@
|
|||
name: Dev CI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# Enable manual run
|
||||
push:
|
||||
# Sequence of patterns matched against refs/tags
|
||||
tags:
|
||||
- "v*" # Push events to matching v*, i.e. v4.2.0
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# This job will run on ubuntu virtual machine
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
name: Check out code
|
||||
|
||||
- uses: mr-smithers-excellent/docker-build-push@v6
|
||||
name: Build & Push
|
||||
with:
|
||||
image: ente/museum-dev
|
||||
registry: rg.fr-par.scw.cloud
|
||||
enableBuildKit: true
|
||||
buildArgs: GIT_COMMIT=${GITHUB_SHA}
|
||||
tags: ${GITHUB_SHA}, latest
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
21
server/.github/workflows/pr.yml
vendored
21
server/.github/workflows/pr.yml
vendored
|
@ -1,21 +0,0 @@
|
|||
name: Code quality
|
||||
|
||||
on:
|
||||
# Enable manual run
|
||||
workflow_dispatch:
|
||||
# Run on every push; this also covers pull requests
|
||||
push:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
cache: true
|
||||
- run: sudo apt-get update && sudo apt-get install libsodium-dev
|
||||
- run:
|
||||
"./scripts/lint.sh"
|
||||
# - run: "go test ./..."
|
|
@ -587,6 +587,7 @@ func main() {
|
|||
privateAPI.POST("/storage-bonus/referral-claim", storageBonusHandler.ClaimReferral)
|
||||
|
||||
adminHandler := &api.AdminHandler{
|
||||
QueueRepo: queueRepo,
|
||||
UserRepo: userRepo,
|
||||
CollectionRepo: collectionRepo,
|
||||
UserAuthRepo: userAuthRepo,
|
||||
|
@ -615,6 +616,7 @@ func main() {
|
|||
adminAPI.GET("/email-hash", adminHandler.GetEmailHash)
|
||||
adminAPI.POST("/emails-from-hashes", adminHandler.GetEmailsFromHashes)
|
||||
adminAPI.PUT("/user/subscription", adminHandler.UpdateSubscription)
|
||||
adminAPI.POST("/queue/re-queue", adminHandler.ReQueueItem)
|
||||
adminAPI.POST("/user/bf-2013", adminHandler.UpdateBFDeal)
|
||||
adminAPI.POST("/job/clear-orphan-objects", adminHandler.ClearOrphanObjects)
|
||||
|
||||
|
|
|
@ -22,6 +22,12 @@ type AdminOpsForUserRequest struct {
|
|||
UserID int64 `json:"userID" binding:"required"`
|
||||
}
|
||||
|
||||
// ReQueueItemRequest puts an item back into the queue for processing.
|
||||
type ReQueueItemRequest struct {
|
||||
ID int64 `json:"id" binding:"required"`
|
||||
QueueName string `json:"queueName" binding:"required"`
|
||||
}
|
||||
|
||||
// RecoverAccount is used to recover accounts which are in soft-delete state.
|
||||
type RecoverAccountRequest struct {
|
||||
UserID int64 `json:"userID" binding:"required"`
|
||||
|
|
|
@ -33,6 +33,7 @@ import (
|
|||
|
||||
// AdminHandler exposes request handlers for all admin related requests
|
||||
type AdminHandler struct {
|
||||
QueueRepo *repo.QueueRepository
|
||||
UserRepo *repo.UserRepository
|
||||
CollectionRepo *repo.CollectionRepository
|
||||
UserAuthRepo *repo.UserAuthRepository
|
||||
|
@ -305,6 +306,24 @@ func (h *AdminHandler) UpdateSubscription(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, gin.H{})
|
||||
}
|
||||
|
||||
func (h *AdminHandler) ReQueueItem(c *gin.Context) {
|
||||
var r ente.ReQueueItemRequest
|
||||
if err := c.ShouldBindJSON(&r); err != nil {
|
||||
handler.Error(c, stacktrace.Propagate(ente.ErrBadRequest, "Bad request"))
|
||||
return
|
||||
}
|
||||
adminID := auth.GetUserID(c.Request.Header)
|
||||
go h.DiscordController.NotifyAdminAction(
|
||||
fmt.Sprintf("Admin (%d) requeueing item %d for queue: %s", adminID, r.ID, r.QueueName))
|
||||
err := h.QueueRepo.RequeueItem(c, r.QueueName, r.ID)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("Failed to re-queue item")
|
||||
handler.Error(c, stacktrace.Propagate(err, ""))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{})
|
||||
}
|
||||
|
||||
func (h *AdminHandler) UpdateBFDeal(c *gin.Context) {
|
||||
var r ente.UpdateBlackFridayDeal
|
||||
if err := c.ShouldBindJSON(&r); err != nil {
|
||||
|
|
|
@ -652,16 +652,16 @@ func (c *FileController) cleanupDeletedFile(qItem repo.QueueItem) {
|
|||
return
|
||||
}
|
||||
}
|
||||
err = c.QueueRepo.DeleteItem(repo.DeleteObjectQueue, qItem.Item)
|
||||
if err != nil {
|
||||
ctxLogger.WithError(err).Error("Failed to remove item from the queue")
|
||||
return
|
||||
}
|
||||
err = c.ObjectRepo.RemoveObjectsForKey(qItem.Item)
|
||||
if err != nil {
|
||||
ctxLogger.WithError(err).Error("Failed to remove item from object_keys")
|
||||
return
|
||||
}
|
||||
err = c.QueueRepo.DeleteItem(repo.DeleteObjectQueue, qItem.Item)
|
||||
if err != nil {
|
||||
ctxLogger.WithError(err).Error("Failed to remove item from the queue")
|
||||
return
|
||||
}
|
||||
ctxLogger.Info("Successfully deleted item")
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
@ -47,7 +48,7 @@ type QueueItem struct {
|
|||
|
||||
// InsertItem adds entry in the queue with given queueName and item. If entry already exists, it's no-op
|
||||
func (repo *QueueRepository) InsertItem(ctx context.Context, queueName string, item string) error {
|
||||
_, err := repo.DB.ExecContext(ctx, `INSERT INTO queue(queue_name, item) VALUES($1, $2)
|
||||
_, err := repo.DB.ExecContext(ctx, `INSERT INTO queue(queue_name, item) VALUES($1, $2)
|
||||
ON CONFLICT (queue_name, item) DO NOTHING`, queueName, item)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
|
@ -70,6 +71,22 @@ func (repo *QueueRepository) UpdateItem(ctx context.Context, queueName string, q
|
|||
return nil
|
||||
}
|
||||
|
||||
func (repo *QueueRepository) RequeueItem(ctx context.Context, queueName string, queueID int64) error {
|
||||
rows, err := repo.DB.ExecContext(ctx, `UPDATE queue SET is_deleted = false WHERE queue_name = $1 AND queue_id = $2`, queueName, queueID)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
count, err := rows.RowsAffected()
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
if count == 0 {
|
||||
return fmt.Errorf("no item found with queueID: %d for queue %s", queueID, queueName)
|
||||
}
|
||||
logrus.Infof("Re-queued %d item with queueID: %d for queue %s", count, queueID, queueName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddItems adds a list of item against a specified queue
|
||||
func (repo *QueueRepository) AddItems(ctx context.Context, tx *sql.Tx, queueName string, items []string) error {
|
||||
if len(items) == 0 {
|
||||
|
|
37
web/.github/workflows/l18n-crowdin.yml
vendored
37
web/.github/workflows/l18n-crowdin.yml
vendored
|
@ -1,37 +0,0 @@
|
|||
name: Sync crowdin translation
|
||||
|
||||
on:
|
||||
push:
|
||||
paths: # run action automatically when en-US/translation.json file is changed
|
||||
- "apps/photos/public/locales/en-US/translation.json"
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: "0 */24 * * *" # Every 24 hours - https://crontab.guru/#0_*/12_*_*_*
|
||||
workflow_dispatch: # for manually running the action
|
||||
|
||||
jobs:
|
||||
synchronize-with-crowdin:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: main
|
||||
|
||||
- name: crowdin action
|
||||
uses: crowdin/github-action@v1
|
||||
with:
|
||||
upload_sources: true
|
||||
upload_translations: true
|
||||
download_translations: true
|
||||
localization_branch_name: l10n_translations
|
||||
create_pull_request: true
|
||||
skip_untranslated_strings: true
|
||||
pull_request_title: "New Translations"
|
||||
pull_request_body: "New translations via [Crowdin GH Action](https://github.com/crowdin/github-action)"
|
||||
pull_request_base_branch_name: "main"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
13
web/.github/workflows/pr.yml
vendored
13
web/.github/workflows/pr.yml
vendored
|
@ -1,13 +0,0 @@
|
|||
name: Lint
|
||||
|
||||
on:
|
||||
# Run on every push (this also covers pull requests)
|
||||
push:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: yarn install
|
||||
- run: yarn lint
|
|
@ -12,7 +12,10 @@ export const AuthFooter = () => {
|
|||
}}
|
||||
>
|
||||
<p>{t("AUTH_DOWNLOAD_MOBILE_APP")}</p>
|
||||
<a href="https://github.com/ente-io/ente/tree/main/auth#-download" download>
|
||||
<a
|
||||
href="https://github.com/ente-io/ente/tree/main/auth#-download"
|
||||
download
|
||||
>
|
||||
<Button color="accent">{t("DOWNLOAD")}</Button>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# Alternatively, these variables can be provided as environment variables, say:
|
||||
#
|
||||
# NEXT_PUBLIC_ENTE_ENDPOINT=http://localhost:8080 NEXT_PUBLIC_ENTE_DIRECT_UPLOAD=true yarn dev:photos
|
||||
# NEXT_PUBLIC_ENTE_ENDPOINT=http://localhost:8080 yarn dev:photos
|
||||
#
|
||||
# Variables prefixed with NEXT_PUBLIC_ are made available when Next.js runs our
|
||||
# code in the browser (Behind the scenes, Next.js just hardcodes occurrences of
|
||||
|
@ -69,17 +69,6 @@
|
|||
#
|
||||
# NEXT_PUBLIC_ENTE_FAMILY_PORTAL_ENDPOINT = http://localhost:3003
|
||||
|
||||
# Set this to "true" to disable the upload of files via Cloudflare Workers.
|
||||
#
|
||||
# These workers were introduced as a way of make file uploads faster:
|
||||
# https://ente.io/blog/tech/making-uploads-faster/
|
||||
#
|
||||
# By default, that's the route we take. However, during development it can be
|
||||
# convenient to turn this flag on to directly upload to the S3-compatible URLs
|
||||
# returned by the ente API.
|
||||
#
|
||||
# NEXT_PUBLIC_ENTE_DIRECT_UPLOAD = true
|
||||
|
||||
# The path of the JSON file which contains the expected results of our
|
||||
# integration tests. See `upload.test.ts` for more details.
|
||||
#
|
||||
|
|
|
@ -7,15 +7,14 @@
|
|||
#
|
||||
# Equivalent CLI command using environment variables would be
|
||||
#
|
||||
# NEXT_PUBLIC_ENTE_ENDPOINT=http://localhost:8080 NEXT_PUBLIC_ENTE_DIRECT_UPLOAD=true yarn dev:photos
|
||||
# NEXT_PUBLIC_ENTE_ENDPOINT=http://localhost:8080 yarn dev:photos
|
||||
#
|
||||
|
||||
NEXT_PUBLIC_ENTE_ENDPOINT = http://localhost:8080
|
||||
NEXT_PUBLIC_ENTE_DIRECT_UPLOAD = true
|
||||
|
||||
# If you wish to preview how the shared albums work, you can use `yarn
|
||||
# dev:albums`. The equivalent CLI command using env vars would be
|
||||
#
|
||||
# NEXT_PUBLIC_ENTE_ENDPOINT=http://localhost:8080 NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT=http://localhost:3002 NEXT_PUBLIC_ENTE_DIRECT_UPLOAD=true yarn dev:albums
|
||||
# NEXT_PUBLIC_ENTE_ENDPOINT=http://localhost:8080 NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT=http://localhost:3002 yarn dev:albums
|
||||
|
||||
NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT = http://localhost:3002
|
||||
|
|
|
@ -38,8 +38,8 @@
|
|||
"KEY_GENERATION_IN_PROGRESS_MESSAGE": "Génération des clés de chiffrement...",
|
||||
"PASSPHRASE_HINT": "Mot de passe",
|
||||
"CONFIRM_PASSPHRASE": "Confirmer le mot de passe",
|
||||
"REFERRAL_CODE_HINT": "",
|
||||
"REFERRAL_INFO": "",
|
||||
"REFERRAL_CODE_HINT": "Comment avez-vous entendu parler de Ente? (facultatif)",
|
||||
"REFERRAL_INFO": "Nous ne suivons pas les installations d'applications. Il serait utile que vous nous disiez comment vous nous avez trouvés !",
|
||||
"PASSPHRASE_MATCH_ERROR": "Les mots de passe ne correspondent pas",
|
||||
"CONSOLE_WARNING_STOP": "STOP!",
|
||||
"CONSOLE_WARNING_DESC": "Ceci est une fonction de navigateur dédiée aux développeurs. Veuillez ne pas copier-coller un code non vérifié à cet endroit.",
|
||||
|
@ -85,9 +85,9 @@
|
|||
"ZOOM_IN_OUT": "Zoom +/-",
|
||||
"PREVIOUS": "Précédent (←)",
|
||||
"NEXT": "Suivant (→)",
|
||||
"TITLE_PHOTOS": "",
|
||||
"TITLE_ALBUMS": "",
|
||||
"TITLE_AUTH": "",
|
||||
"TITLE_PHOTOS": "Ente Photos",
|
||||
"TITLE_ALBUMS": "Ente Photos",
|
||||
"TITLE_AUTH": "Ente Auth",
|
||||
"UPLOAD_FIRST_PHOTO": "Chargez votre 1ere photo",
|
||||
"IMPORT_YOUR_FOLDERS": "Importez vos dossiers",
|
||||
"UPLOAD_DROPZONE_MESSAGE": "Déposez pour sauvegarder vos fichiers",
|
||||
|
@ -159,7 +159,7 @@
|
|||
"RENEWAL_ACTIVE_SUBSCRIPTION_STATUS": "Renouveler le {{date, dateTime}}",
|
||||
"RENEWAL_CANCELLED_SUBSCRIPTION_STATUS": "Pris fin le {{date, dateTime}}",
|
||||
"RENEWAL_CANCELLED_SUBSCRIPTION_INFO": "Votre abonnement sera annulé le {{date, dateTime}}",
|
||||
"ADD_ON_AVAILABLE_TILL": "",
|
||||
"ADD_ON_AVAILABLE_TILL": "Votre module {{storage, string}} est valable jusqu'au {{date, dateTime}}",
|
||||
"STORAGE_QUOTA_EXCEEDED_SUBSCRIPTION_INFO": "Vous avez dépassé votre quota de stockage, veuillez <a> mettre à niveau </a>",
|
||||
"SUBSCRIPTION_PURCHASE_SUCCESS": "<p>Nous avons reçu votre paiement </p><p>Votre abonnement est valide jusqu'au <strong>{{date, dateTime}}</strong></p>",
|
||||
"SUBSCRIPTION_PURCHASE_CANCELLED": "Votre achat est annulé, veuillez réessayer si vous souhaitez vous abonner",
|
||||
|
@ -174,7 +174,7 @@
|
|||
"UPDATE_SUBSCRIPTION": "Changer de plan",
|
||||
"CANCEL_SUBSCRIPTION": "Annuler l'abonnement",
|
||||
"CANCEL_SUBSCRIPTION_MESSAGE": "<p>Toutes vos données seront supprimées de nos serveurs à la fin de cette période d'abonnement.</p><p>Voulez-vous vraiment annuler votre abonnement?</p>",
|
||||
"CANCEL_SUBSCRIPTION_WITH_ADDON_MESSAGE": "",
|
||||
"CANCEL_SUBSCRIPTION_WITH_ADDON_MESSAGE": "Êtes-vous sûr de vouloir annuler votre abonnement ",
|
||||
"SUBSCRIPTION_CANCEL_FAILED": "Échec lors de l'annulation de l'abonnement",
|
||||
"SUBSCRIPTION_CANCEL_SUCCESS": "Votre abonnement a bien été annulé",
|
||||
"REACTIVATE_SUBSCRIPTION": "Réactiver l'abonnement",
|
||||
|
@ -210,7 +210,7 @@
|
|||
"SEARCH_TYPE": {
|
||||
"COLLECTION": "l'album",
|
||||
"LOCATION": "Emplacement",
|
||||
"CITY": "",
|
||||
"CITY": "Adresse",
|
||||
"DATE": "Date",
|
||||
"FILE_NAME": "Nom de fichier",
|
||||
"THING": "Chose",
|
||||
|
@ -623,22 +623,22 @@
|
|||
"PHOTO_EDITOR": "Éditeur de photos",
|
||||
"FASTER_UPLOAD": "Chargements plus rapides",
|
||||
"FASTER_UPLOAD_DESCRIPTION": "Router les chargements vers les serveurs à proximité",
|
||||
"MAGIC_SEARCH_STATUS": "",
|
||||
"MAGIC_SEARCH_STATUS": "Statut de la recherche magique",
|
||||
"INDEXED_ITEMS": "Éléments indexés",
|
||||
"CAST_ALBUM_TO_TV": "",
|
||||
"ENTER_CAST_PIN_CODE": "",
|
||||
"PAIR_DEVICE_TO_TV": "",
|
||||
"TV_NOT_FOUND": "",
|
||||
"AUTO_CAST_PAIR": "",
|
||||
"AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "",
|
||||
"PAIR_WITH_PIN": "",
|
||||
"CHOOSE_DEVICE_FROM_BROWSER": "",
|
||||
"PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "",
|
||||
"VISIT_CAST_ENTE_IO": "",
|
||||
"CAST_AUTO_PAIR_FAILED": "",
|
||||
"CACHE_DIRECTORY": "",
|
||||
"PASSKEYS": "",
|
||||
"FREEHAND": "",
|
||||
"APPLY_CROP": "",
|
||||
"PHOTO_EDIT_REQUIRED_TO_SAVE": ""
|
||||
"CAST_ALBUM_TO_TV": "Jouer l'album sur la TV",
|
||||
"ENTER_CAST_PIN_CODE": "Entrez le code que vous voyez sur la TV ci-dessous pour appairer cet appareil.",
|
||||
"PAIR_DEVICE_TO_TV": "Associer les appareils",
|
||||
"TV_NOT_FOUND": "TV introuvable. Avez-vous entré le code PIN correctement ?",
|
||||
"AUTO_CAST_PAIR": "Paire automatique",
|
||||
"AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "La paire automatique nécessite la connexion aux serveurs Google et ne fonctionne qu'avec les appareils pris en charge par Chromecast. Google ne recevra pas de données sensibles, telles que vos photos.",
|
||||
"PAIR_WITH_PIN": "Associer avec le code PIN",
|
||||
"CHOOSE_DEVICE_FROM_BROWSER": "Choisissez un périphérique compatible avec la caste à partir de la fenêtre pop-up du navigateur.",
|
||||
"PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "L'association avec le code PIN fonctionne pour tout appareil grand écran sur lequel vous voulez lire votre album.",
|
||||
"VISIT_CAST_ENTE_IO": "Visitez cast.ente.io sur l'appareil que vous voulez associer.",
|
||||
"CAST_AUTO_PAIR_FAILED": "La paire automatique de Chromecast a échoué. Veuillez réessayer.",
|
||||
"CACHE_DIRECTORY": "Dossier du cache",
|
||||
"PASSKEYS": "Clés d'accès",
|
||||
"FREEHAND": "Main levée",
|
||||
"APPLY_CROP": "Appliquer le recadrage",
|
||||
"PHOTO_EDIT_REQUIRED_TO_SAVE": "Au moins une transformation ou un ajustement de couleur doit être effectué avant de sauvegarder."
|
||||
}
|
||||
|
|
644
web/apps/photos/public/locales/ko-KR/translation.json
Normal file
644
web/apps/photos/public/locales/ko-KR/translation.json
Normal file
|
@ -0,0 +1,644 @@
|
|||
{
|
||||
"HERO_SLIDE_1_TITLE": "",
|
||||
"HERO_SLIDE_1": "",
|
||||
"HERO_SLIDE_2_TITLE": "",
|
||||
"HERO_SLIDE_2": "",
|
||||
"HERO_SLIDE_3_TITLE": "",
|
||||
"HERO_SLIDE_3": "",
|
||||
"LOGIN": "",
|
||||
"SIGN_UP": "",
|
||||
"NEW_USER": "",
|
||||
"EXISTING_USER": "",
|
||||
"ENTER_NAME": "",
|
||||
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
|
||||
"ENTER_EMAIL": "",
|
||||
"EMAIL_ERROR": "",
|
||||
"REQUIRED": "",
|
||||
"EMAIL_SENT": "",
|
||||
"CHECK_INBOX": "",
|
||||
"ENTER_OTT": "",
|
||||
"RESEND_MAIL": "",
|
||||
"VERIFY": "",
|
||||
"UNKNOWN_ERROR": "",
|
||||
"INVALID_CODE": "",
|
||||
"EXPIRED_CODE": "",
|
||||
"SENDING": "",
|
||||
"SENT": "",
|
||||
"PASSWORD": "",
|
||||
"LINK_PASSWORD": "",
|
||||
"RETURN_PASSPHRASE_HINT": "",
|
||||
"SET_PASSPHRASE": "",
|
||||
"VERIFY_PASSPHRASE": "",
|
||||
"INCORRECT_PASSPHRASE": "",
|
||||
"ENTER_ENC_PASSPHRASE": "",
|
||||
"PASSPHRASE_DISCLAIMER": "",
|
||||
"WELCOME_TO_ENTE_HEADING": "",
|
||||
"WELCOME_TO_ENTE_SUBHEADING": "",
|
||||
"WHERE_YOUR_BEST_PHOTOS_LIVE": "",
|
||||
"KEY_GENERATION_IN_PROGRESS_MESSAGE": "",
|
||||
"PASSPHRASE_HINT": "",
|
||||
"CONFIRM_PASSPHRASE": "",
|
||||
"REFERRAL_CODE_HINT": "",
|
||||
"REFERRAL_INFO": "",
|
||||
"PASSPHRASE_MATCH_ERROR": "",
|
||||
"CONSOLE_WARNING_STOP": "",
|
||||
"CONSOLE_WARNING_DESC": "",
|
||||
"CREATE_COLLECTION": "",
|
||||
"ENTER_ALBUM_NAME": "",
|
||||
"CLOSE_OPTION": "",
|
||||
"ENTER_FILE_NAME": "",
|
||||
"CLOSE": "",
|
||||
"NO": "",
|
||||
"NOTHING_HERE": "",
|
||||
"UPLOAD": "",
|
||||
"IMPORT": "",
|
||||
"ADD_PHOTOS": "",
|
||||
"ADD_MORE_PHOTOS": "",
|
||||
"add_photos_one": "",
|
||||
"add_photos_other": "",
|
||||
"SELECT_PHOTOS": "",
|
||||
"FILE_UPLOAD": "",
|
||||
"UPLOAD_STAGE_MESSAGE": {
|
||||
"0": "",
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": ""
|
||||
},
|
||||
"FILE_NOT_UPLOADED_LIST": "",
|
||||
"SUBSCRIPTION_EXPIRED": "",
|
||||
"SUBSCRIPTION_EXPIRED_MESSAGE": "",
|
||||
"STORAGE_QUOTA_EXCEEDED": "",
|
||||
"INITIAL_LOAD_DELAY_WARNING": "",
|
||||
"USER_DOES_NOT_EXIST": "",
|
||||
"NO_ACCOUNT": "",
|
||||
"ACCOUNT_EXISTS": "",
|
||||
"CREATE": "",
|
||||
"DOWNLOAD": "",
|
||||
"DOWNLOAD_OPTION": "",
|
||||
"DOWNLOAD_FAVORITES": "",
|
||||
"DOWNLOAD_UNCATEGORIZED": "",
|
||||
"DOWNLOAD_HIDDEN_ITEMS": "",
|
||||
"COPY_OPTION": "",
|
||||
"TOGGLE_FULLSCREEN": "",
|
||||
"ZOOM_IN_OUT": "",
|
||||
"PREVIOUS": "",
|
||||
"NEXT": "",
|
||||
"TITLE_PHOTOS": "",
|
||||
"TITLE_ALBUMS": "",
|
||||
"TITLE_AUTH": "",
|
||||
"UPLOAD_FIRST_PHOTO": "",
|
||||
"IMPORT_YOUR_FOLDERS": "",
|
||||
"UPLOAD_DROPZONE_MESSAGE": "",
|
||||
"WATCH_FOLDER_DROPZONE_MESSAGE": "",
|
||||
"TRASH_FILES_TITLE": "",
|
||||
"TRASH_FILE_TITLE": "",
|
||||
"DELETE_FILES_TITLE": "",
|
||||
"DELETE_FILES_MESSAGE": "",
|
||||
"DELETE": "",
|
||||
"DELETE_OPTION": "",
|
||||
"FAVORITE_OPTION": "",
|
||||
"UNFAVORITE_OPTION": "",
|
||||
"MULTI_FOLDER_UPLOAD": "",
|
||||
"UPLOAD_STRATEGY_CHOICE": "",
|
||||
"UPLOAD_STRATEGY_SINGLE_COLLECTION": "",
|
||||
"OR": "",
|
||||
"UPLOAD_STRATEGY_COLLECTION_PER_FOLDER": "",
|
||||
"SESSION_EXPIRED_MESSAGE": "",
|
||||
"SESSION_EXPIRED": "",
|
||||
"PASSWORD_GENERATION_FAILED": "",
|
||||
"CHANGE_PASSWORD": "",
|
||||
"GO_BACK": "",
|
||||
"RECOVERY_KEY": "",
|
||||
"SAVE_LATER": "",
|
||||
"SAVE": "",
|
||||
"RECOVERY_KEY_DESCRIPTION": "",
|
||||
"RECOVER_KEY_GENERATION_FAILED": "",
|
||||
"KEY_NOT_STORED_DISCLAIMER": "",
|
||||
"FORGOT_PASSWORD": "",
|
||||
"RECOVER_ACCOUNT": "",
|
||||
"RECOVERY_KEY_HINT": "",
|
||||
"RECOVER": "",
|
||||
"NO_RECOVERY_KEY": "",
|
||||
"INCORRECT_RECOVERY_KEY": "",
|
||||
"SORRY": "",
|
||||
"NO_RECOVERY_KEY_MESSAGE": "",
|
||||
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
|
||||
"CONTACT_SUPPORT": "",
|
||||
"REQUEST_FEATURE": "",
|
||||
"SUPPORT": "",
|
||||
"CONFIRM": "",
|
||||
"CANCEL": "",
|
||||
"LOGOUT": "",
|
||||
"DELETE_ACCOUNT": "",
|
||||
"DELETE_ACCOUNT_MESSAGE": "",
|
||||
"LOGOUT_MESSAGE": "",
|
||||
"CHANGE_EMAIL": "",
|
||||
"OK": "",
|
||||
"SUCCESS": "",
|
||||
"ERROR": "",
|
||||
"MESSAGE": "",
|
||||
"INSTALL_MOBILE_APP": "",
|
||||
"DOWNLOAD_APP_MESSAGE": "",
|
||||
"DOWNLOAD_APP": "",
|
||||
"EXPORT": "",
|
||||
"SUBSCRIPTION": "",
|
||||
"SUBSCRIBE": "",
|
||||
"MANAGEMENT_PORTAL": "",
|
||||
"MANAGE_FAMILY_PORTAL": "",
|
||||
"LEAVE_FAMILY_PLAN": "",
|
||||
"LEAVE": "",
|
||||
"LEAVE_FAMILY_CONFIRM": "",
|
||||
"CHOOSE_PLAN": "",
|
||||
"MANAGE_PLAN": "",
|
||||
"ACTIVE": "",
|
||||
"OFFLINE_MSG": "",
|
||||
"FREE_SUBSCRIPTION_INFO": "",
|
||||
"FAMILY_SUBSCRIPTION_INFO": "",
|
||||
"RENEWAL_ACTIVE_SUBSCRIPTION_STATUS": "",
|
||||
"RENEWAL_CANCELLED_SUBSCRIPTION_STATUS": "",
|
||||
"RENEWAL_CANCELLED_SUBSCRIPTION_INFO": "",
|
||||
"ADD_ON_AVAILABLE_TILL": "",
|
||||
"STORAGE_QUOTA_EXCEEDED_SUBSCRIPTION_INFO": "",
|
||||
"SUBSCRIPTION_PURCHASE_SUCCESS": "",
|
||||
"SUBSCRIPTION_PURCHASE_CANCELLED": "",
|
||||
"SUBSCRIPTION_PURCHASE_FAILED": "",
|
||||
"SUBSCRIPTION_UPDATE_FAILED": "",
|
||||
"UPDATE_PAYMENT_METHOD_MESSAGE": "",
|
||||
"STRIPE_AUTHENTICATION_FAILED": "",
|
||||
"UPDATE_PAYMENT_METHOD": "",
|
||||
"MONTHLY": "",
|
||||
"YEARLY": "",
|
||||
"UPDATE_SUBSCRIPTION_MESSAGE": "",
|
||||
"UPDATE_SUBSCRIPTION": "",
|
||||
"CANCEL_SUBSCRIPTION": "",
|
||||
"CANCEL_SUBSCRIPTION_MESSAGE": "",
|
||||
"CANCEL_SUBSCRIPTION_WITH_ADDON_MESSAGE": "",
|
||||
"SUBSCRIPTION_CANCEL_FAILED": "",
|
||||
"SUBSCRIPTION_CANCEL_SUCCESS": "",
|
||||
"REACTIVATE_SUBSCRIPTION": "",
|
||||
"REACTIVATE_SUBSCRIPTION_MESSAGE": "",
|
||||
"SUBSCRIPTION_ACTIVATE_SUCCESS": "",
|
||||
"SUBSCRIPTION_ACTIVATE_FAILED": "",
|
||||
"SUBSCRIPTION_PURCHASE_SUCCESS_TITLE": "",
|
||||
"CANCEL_SUBSCRIPTION_ON_MOBILE": "",
|
||||
"CANCEL_SUBSCRIPTION_ON_MOBILE_MESSAGE": "",
|
||||
"MAIL_TO_MANAGE_SUBSCRIPTION": "",
|
||||
"RENAME": "",
|
||||
"RENAME_FILE": "",
|
||||
"RENAME_COLLECTION": "",
|
||||
"DELETE_COLLECTION_TITLE": "",
|
||||
"DELETE_COLLECTION": "",
|
||||
"DELETE_COLLECTION_MESSAGE": "",
|
||||
"DELETE_PHOTOS": "",
|
||||
"KEEP_PHOTOS": "",
|
||||
"SHARE": "",
|
||||
"SHARE_COLLECTION": "",
|
||||
"SHAREES": "",
|
||||
"SHARE_WITH_SELF": "",
|
||||
"ALREADY_SHARED": "",
|
||||
"SHARING_BAD_REQUEST_ERROR": "",
|
||||
"SHARING_DISABLED_FOR_FREE_ACCOUNTS": "",
|
||||
"DOWNLOAD_COLLECTION": "",
|
||||
"DOWNLOAD_COLLECTION_MESSAGE": "",
|
||||
"CREATE_ALBUM_FAILED": "",
|
||||
"SEARCH": "",
|
||||
"SEARCH_RESULTS": "",
|
||||
"NO_RESULTS": "",
|
||||
"SEARCH_HINT": "",
|
||||
"SEARCH_TYPE": {
|
||||
"COLLECTION": "",
|
||||
"LOCATION": "",
|
||||
"CITY": "",
|
||||
"DATE": "",
|
||||
"FILE_NAME": "",
|
||||
"THING": "",
|
||||
"FILE_CAPTION": "",
|
||||
"FILE_TYPE": "",
|
||||
"CLIP": ""
|
||||
},
|
||||
"photos_count_zero": "",
|
||||
"photos_count_one": "",
|
||||
"photos_count_other": "",
|
||||
"TERMS_AND_CONDITIONS": "",
|
||||
"ADD_TO_COLLECTION": "",
|
||||
"SELECTED": "",
|
||||
"VIDEO_PLAYBACK_FAILED_DOWNLOAD_INSTEAD": "",
|
||||
"PEOPLE": "",
|
||||
"INDEXING_SCHEDULED": "",
|
||||
"ANALYZING_PHOTOS": "",
|
||||
"INDEXING_PEOPLE": "",
|
||||
"INDEXING_DONE": "",
|
||||
"UNIDENTIFIED_FACES": "",
|
||||
"OBJECTS": "",
|
||||
"TEXT": "",
|
||||
"INFO": "",
|
||||
"INFO_OPTION": "",
|
||||
"FILE_NAME": "",
|
||||
"CAPTION_PLACEHOLDER": "",
|
||||
"LOCATION": "",
|
||||
"SHOW_ON_MAP": "",
|
||||
"MAP": "",
|
||||
"MAP_SETTINGS": "",
|
||||
"ENABLE_MAPS": "",
|
||||
"ENABLE_MAP": "",
|
||||
"DISABLE_MAPS": "",
|
||||
"ENABLE_MAP_DESCRIPTION": "",
|
||||
"DISABLE_MAP_DESCRIPTION": "",
|
||||
"DISABLE_MAP": "",
|
||||
"DETAILS": "",
|
||||
"VIEW_EXIF": "",
|
||||
"NO_EXIF": "",
|
||||
"EXIF": "",
|
||||
"ISO": "",
|
||||
"TWO_FACTOR": "",
|
||||
"TWO_FACTOR_AUTHENTICATION": "",
|
||||
"TWO_FACTOR_QR_INSTRUCTION": "",
|
||||
"ENTER_CODE_MANUALLY": "",
|
||||
"TWO_FACTOR_MANUAL_CODE_INSTRUCTION": "",
|
||||
"SCAN_QR_CODE": "",
|
||||
"ENABLE_TWO_FACTOR": "",
|
||||
"ENABLE": "",
|
||||
"LOST_DEVICE": "",
|
||||
"INCORRECT_CODE": "",
|
||||
"TWO_FACTOR_INFO": "",
|
||||
"DISABLE_TWO_FACTOR_LABEL": "",
|
||||
"UPDATE_TWO_FACTOR_LABEL": "",
|
||||
"DISABLE": "",
|
||||
"RECONFIGURE": "",
|
||||
"UPDATE_TWO_FACTOR": "",
|
||||
"UPDATE_TWO_FACTOR_MESSAGE": "",
|
||||
"UPDATE": "",
|
||||
"DISABLE_TWO_FACTOR": "",
|
||||
"DISABLE_TWO_FACTOR_MESSAGE": "",
|
||||
"TWO_FACTOR_DISABLE_FAILED": "",
|
||||
"EXPORT_DATA": "",
|
||||
"SELECT_FOLDER": "",
|
||||
"DESTINATION": "",
|
||||
"START": "",
|
||||
"LAST_EXPORT_TIME": "",
|
||||
"EXPORT_AGAIN": "",
|
||||
"LOCAL_STORAGE_NOT_ACCESSIBLE": "",
|
||||
"LOCAL_STORAGE_NOT_ACCESSIBLE_MESSAGE": "",
|
||||
"SEND_OTT": "",
|
||||
"EMAIl_ALREADY_OWNED": "",
|
||||
"ETAGS_BLOCKED": "",
|
||||
"SKIPPED_VIDEOS_INFO": "",
|
||||
"LIVE_PHOTOS_DETECTED": "",
|
||||
"RETRY_FAILED": "",
|
||||
"FAILED_UPLOADS": "",
|
||||
"SKIPPED_FILES": "",
|
||||
"THUMBNAIL_GENERATION_FAILED_UPLOADS": "",
|
||||
"UNSUPPORTED_FILES": "",
|
||||
"SUCCESSFUL_UPLOADS": "",
|
||||
"SKIPPED_INFO": "",
|
||||
"UNSUPPORTED_INFO": "",
|
||||
"BLOCKED_UPLOADS": "",
|
||||
"SKIPPED_VIDEOS": "",
|
||||
"INPROGRESS_METADATA_EXTRACTION": "",
|
||||
"INPROGRESS_UPLOADS": "",
|
||||
"TOO_LARGE_UPLOADS": "",
|
||||
"LARGER_THAN_AVAILABLE_STORAGE_UPLOADS": "",
|
||||
"LARGER_THAN_AVAILABLE_STORAGE_INFO": "",
|
||||
"TOO_LARGE_INFO": "",
|
||||
"THUMBNAIL_GENERATION_FAILED_INFO": "",
|
||||
"UPLOAD_TO_COLLECTION": "",
|
||||
"UNCATEGORIZED": "",
|
||||
"ARCHIVE": "",
|
||||
"FAVORITES": "",
|
||||
"ARCHIVE_COLLECTION": "",
|
||||
"ARCHIVE_SECTION_NAME": "",
|
||||
"ALL_SECTION_NAME": "",
|
||||
"MOVE_TO_COLLECTION": "",
|
||||
"UNARCHIVE": "",
|
||||
"UNARCHIVE_COLLECTION": "",
|
||||
"HIDE_COLLECTION": "",
|
||||
"UNHIDE_COLLECTION": "",
|
||||
"MOVE": "",
|
||||
"ADD": "",
|
||||
"REMOVE": "",
|
||||
"YES_REMOVE": "",
|
||||
"REMOVE_FROM_COLLECTION": "",
|
||||
"TRASH": "",
|
||||
"MOVE_TO_TRASH": "",
|
||||
"TRASH_FILES_MESSAGE": "",
|
||||
"TRASH_FILE_MESSAGE": "",
|
||||
"DELETE_PERMANENTLY": "",
|
||||
"RESTORE": "",
|
||||
"RESTORE_TO_COLLECTION": "",
|
||||
"EMPTY_TRASH": "",
|
||||
"EMPTY_TRASH_TITLE": "",
|
||||
"EMPTY_TRASH_MESSAGE": "",
|
||||
"LEAVE_SHARED_ALBUM": "",
|
||||
"LEAVE_ALBUM": "",
|
||||
"LEAVE_SHARED_ALBUM_TITLE": "",
|
||||
"LEAVE_SHARED_ALBUM_MESSAGE": "",
|
||||
"NOT_FILE_OWNER": "",
|
||||
"CONFIRM_SELF_REMOVE_MESSAGE": "",
|
||||
"CONFIRM_SELF_AND_OTHER_REMOVE_MESSAGE": "",
|
||||
"SORT_BY_CREATION_TIME_ASCENDING": "",
|
||||
"SORT_BY_UPDATION_TIME_DESCENDING": "",
|
||||
"SORT_BY_NAME": "",
|
||||
"COMPRESS_THUMBNAILS": "",
|
||||
"THUMBNAIL_REPLACED": "",
|
||||
"FIX_THUMBNAIL": "",
|
||||
"FIX_THUMBNAIL_LATER": "",
|
||||
"REPLACE_THUMBNAIL_NOT_STARTED": "",
|
||||
"REPLACE_THUMBNAIL_COMPLETED": "",
|
||||
"REPLACE_THUMBNAIL_NOOP": "",
|
||||
"REPLACE_THUMBNAIL_COMPLETED_WITH_ERROR": "",
|
||||
"FIX_CREATION_TIME": "",
|
||||
"FIX_CREATION_TIME_IN_PROGRESS": "",
|
||||
"CREATION_TIME_UPDATED": "",
|
||||
"UPDATE_CREATION_TIME_NOT_STARTED": "",
|
||||
"UPDATE_CREATION_TIME_COMPLETED": "",
|
||||
"UPDATE_CREATION_TIME_COMPLETED_WITH_ERROR": "",
|
||||
"CAPTION_CHARACTER_LIMIT": "",
|
||||
"DATE_TIME_ORIGINAL": "",
|
||||
"DATE_TIME_DIGITIZED": "",
|
||||
"METADATA_DATE": "",
|
||||
"CUSTOM_TIME": "",
|
||||
"REOPEN_PLAN_SELECTOR_MODAL": "",
|
||||
"OPEN_PLAN_SELECTOR_MODAL_FAILED": "",
|
||||
"INSTALL": "",
|
||||
"SHARING_DETAILS": "",
|
||||
"MODIFY_SHARING": "",
|
||||
"ADD_COLLABORATORS": "",
|
||||
"ADD_NEW_EMAIL": "",
|
||||
"shared_with_people_zero": "",
|
||||
"shared_with_people_one": "",
|
||||
"shared_with_people_other": "",
|
||||
"participants_zero": "",
|
||||
"participants_one": "",
|
||||
"participants_other": "",
|
||||
"ADD_VIEWERS": "",
|
||||
"PARTICIPANTS": "",
|
||||
"CHANGE_PERMISSIONS_TO_VIEWER": "",
|
||||
"CHANGE_PERMISSIONS_TO_COLLABORATOR": "",
|
||||
"CONVERT_TO_VIEWER": "",
|
||||
"CONVERT_TO_COLLABORATOR": "",
|
||||
"CHANGE_PERMISSION": "",
|
||||
"REMOVE_PARTICIPANT": "",
|
||||
"CONFIRM_REMOVE": "",
|
||||
"MANAGE": "",
|
||||
"ADDED_AS": "",
|
||||
"COLLABORATOR_RIGHTS": "",
|
||||
"REMOVE_PARTICIPANT_HEAD": "",
|
||||
"OWNER": "",
|
||||
"COLLABORATORS": "",
|
||||
"ADD_MORE": "",
|
||||
"VIEWERS": "",
|
||||
"OR_ADD_EXISTING": "",
|
||||
"REMOVE_PARTICIPANT_MESSAGE": "",
|
||||
"NOT_FOUND": "",
|
||||
"LINK_EXPIRED": "",
|
||||
"LINK_EXPIRED_MESSAGE": "",
|
||||
"MANAGE_LINK": "",
|
||||
"LINK_TOO_MANY_REQUESTS": "",
|
||||
"FILE_DOWNLOAD": "",
|
||||
"LINK_PASSWORD_LOCK": "",
|
||||
"PUBLIC_COLLECT": "",
|
||||
"LINK_DEVICE_LIMIT": "",
|
||||
"NO_DEVICE_LIMIT": "",
|
||||
"LINK_EXPIRY": "",
|
||||
"NEVER": "",
|
||||
"DISABLE_FILE_DOWNLOAD": "",
|
||||
"DISABLE_FILE_DOWNLOAD_MESSAGE": "",
|
||||
"MALICIOUS_CONTENT": "",
|
||||
"COPYRIGHT": "",
|
||||
"SHARED_USING": "",
|
||||
"ENTE_IO": "",
|
||||
"SHARING_REFERRAL_CODE": "",
|
||||
"LIVE": "",
|
||||
"DISABLE_PASSWORD": "",
|
||||
"DISABLE_PASSWORD_MESSAGE": "",
|
||||
"PASSWORD_LOCK": "",
|
||||
"LOCK": "",
|
||||
"DOWNLOAD_UPLOAD_LOGS": "",
|
||||
"UPLOAD_FILES": "",
|
||||
"UPLOAD_DIRS": "",
|
||||
"UPLOAD_GOOGLE_TAKEOUT": "",
|
||||
"DEDUPLICATE_FILES": "",
|
||||
"AUTHENTICATOR_SECTION": "",
|
||||
"NO_DUPLICATES_FOUND": "",
|
||||
"CLUB_BY_CAPTURE_TIME": "",
|
||||
"FILES": "",
|
||||
"EACH": "",
|
||||
"DEDUPLICATE_BASED_ON_SIZE": "",
|
||||
"STOP_ALL_UPLOADS_MESSAGE": "",
|
||||
"STOP_UPLOADS_HEADER": "",
|
||||
"YES_STOP_UPLOADS": "",
|
||||
"STOP_DOWNLOADS_HEADER": "",
|
||||
"YES_STOP_DOWNLOADS": "",
|
||||
"STOP_ALL_DOWNLOADS_MESSAGE": "",
|
||||
"albums_one": "",
|
||||
"albums_other": "",
|
||||
"ALL_ALBUMS": "",
|
||||
"ALBUMS": "",
|
||||
"ALL_HIDDEN_ALBUMS": "",
|
||||
"HIDDEN_ALBUMS": "",
|
||||
"HIDDEN_ITEMS": "",
|
||||
"HIDDEN_ITEMS_SECTION_NAME": "",
|
||||
"ENTER_TWO_FACTOR_OTP": "",
|
||||
"CREATE_ACCOUNT": "",
|
||||
"COPIED": "",
|
||||
"CANVAS_BLOCKED_TITLE": "",
|
||||
"CANVAS_BLOCKED_MESSAGE": "",
|
||||
"WATCH_FOLDERS": "",
|
||||
"UPGRADE_NOW": "",
|
||||
"RENEW_NOW": "",
|
||||
"STORAGE": "",
|
||||
"USED": "",
|
||||
"YOU": "",
|
||||
"FAMILY": "",
|
||||
"FREE": "",
|
||||
"OF": "",
|
||||
"WATCHED_FOLDERS": "",
|
||||
"NO_FOLDERS_ADDED": "",
|
||||
"FOLDERS_AUTOMATICALLY_MONITORED": "",
|
||||
"UPLOAD_NEW_FILES_TO_ENTE": "",
|
||||
"REMOVE_DELETED_FILES_FROM_ENTE": "",
|
||||
"ADD_FOLDER": "",
|
||||
"STOP_WATCHING": "",
|
||||
"STOP_WATCHING_FOLDER": "",
|
||||
"STOP_WATCHING_DIALOG_MESSAGE": "",
|
||||
"YES_STOP": "",
|
||||
"MONTH_SHORT": "",
|
||||
"YEAR": "",
|
||||
"FAMILY_PLAN": "",
|
||||
"DOWNLOAD_LOGS": "",
|
||||
"DOWNLOAD_LOGS_MESSAGE": "",
|
||||
"CHANGE_FOLDER": "",
|
||||
"TWO_MONTHS_FREE": "",
|
||||
"GB": "",
|
||||
"POPULAR": "",
|
||||
"FREE_PLAN_OPTION_LABEL": "",
|
||||
"FREE_PLAN_DESCRIPTION": "",
|
||||
"CURRENT_USAGE": "",
|
||||
"WEAK_DEVICE": "",
|
||||
"DRAG_AND_DROP_HINT": "",
|
||||
"CONFIRM_ACCOUNT_DELETION_MESSAGE": "",
|
||||
"AUTHENTICATE": "",
|
||||
"UPLOADED_TO_SINGLE_COLLECTION": "",
|
||||
"UPLOADED_TO_SEPARATE_COLLECTIONS": "",
|
||||
"NEVERMIND": "",
|
||||
"UPDATE_AVAILABLE": "",
|
||||
"UPDATE_INSTALLABLE_MESSAGE": "",
|
||||
"INSTALL_NOW": "",
|
||||
"INSTALL_ON_NEXT_LAUNCH": "",
|
||||
"UPDATE_AVAILABLE_MESSAGE": "",
|
||||
"DOWNLOAD_AND_INSTALL": "",
|
||||
"IGNORE_THIS_VERSION": "",
|
||||
"TODAY": "",
|
||||
"YESTERDAY": "",
|
||||
"NAME_PLACEHOLDER": "",
|
||||
"ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED": "",
|
||||
"ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED_MESSAGE": "",
|
||||
"CHOSE_THEME": "",
|
||||
"ML_SEARCH": "",
|
||||
"ENABLE_ML_SEARCH_DESCRIPTION": "",
|
||||
"ML_MORE_DETAILS": "",
|
||||
"ENABLE_FACE_SEARCH": "",
|
||||
"ENABLE_FACE_SEARCH_TITLE": "",
|
||||
"ENABLE_FACE_SEARCH_DESCRIPTION": "",
|
||||
"DISABLE_BETA": "",
|
||||
"DISABLE_FACE_SEARCH": "",
|
||||
"DISABLE_FACE_SEARCH_TITLE": "",
|
||||
"DISABLE_FACE_SEARCH_DESCRIPTION": "",
|
||||
"ADVANCED": "",
|
||||
"FACE_SEARCH_CONFIRMATION": "",
|
||||
"LABS": "",
|
||||
"YOURS": "",
|
||||
"PASSPHRASE_STRENGTH_WEAK": "",
|
||||
"PASSPHRASE_STRENGTH_MODERATE": "",
|
||||
"PASSPHRASE_STRENGTH_STRONG": "",
|
||||
"PREFERENCES": "",
|
||||
"LANGUAGE": "",
|
||||
"EXPORT_DIRECTORY_DOES_NOT_EXIST": "",
|
||||
"EXPORT_DIRECTORY_DOES_NOT_EXIST_MESSAGE": "",
|
||||
"SUBSCRIPTION_VERIFICATION_ERROR": "",
|
||||
"STORAGE_UNITS": {
|
||||
"B": "",
|
||||
"KB": "",
|
||||
"MB": "",
|
||||
"GB": "",
|
||||
"TB": ""
|
||||
},
|
||||
"AFTER_TIME": {
|
||||
"HOUR": "",
|
||||
"DAY": "",
|
||||
"WEEK": "",
|
||||
"MONTH": "",
|
||||
"YEAR": ""
|
||||
},
|
||||
"COPY_LINK": "",
|
||||
"DONE": "",
|
||||
"LINK_SHARE_TITLE": "",
|
||||
"REMOVE_LINK": "",
|
||||
"CREATE_PUBLIC_SHARING": "",
|
||||
"PUBLIC_LINK_CREATED": "",
|
||||
"PUBLIC_LINK_ENABLED": "",
|
||||
"COLLECT_PHOTOS": "",
|
||||
"PUBLIC_COLLECT_SUBTEXT": "",
|
||||
"STOP_EXPORT": "",
|
||||
"EXPORT_PROGRESS": "",
|
||||
"MIGRATING_EXPORT": "",
|
||||
"RENAMING_COLLECTION_FOLDERS": "",
|
||||
"TRASHING_DELETED_FILES": "",
|
||||
"TRASHING_DELETED_COLLECTIONS": "",
|
||||
"EXPORT_NOTIFICATION": {
|
||||
"START": "",
|
||||
"IN_PROGRESS": "",
|
||||
"FINISH": "",
|
||||
"UP_TO_DATE": ""
|
||||
},
|
||||
"CONTINUOUS_EXPORT": "",
|
||||
"TOTAL_ITEMS": "",
|
||||
"PENDING_ITEMS": "",
|
||||
"EXPORT_STARTING": "",
|
||||
"DELETE_ACCOUNT_REASON_LABEL": "",
|
||||
"DELETE_ACCOUNT_REASON_PLACEHOLDER": "",
|
||||
"DELETE_REASON": {
|
||||
"MISSING_FEATURE": "",
|
||||
"BROKEN_BEHAVIOR": "",
|
||||
"FOUND_ANOTHER_SERVICE": "",
|
||||
"NOT_LISTED": ""
|
||||
},
|
||||
"DELETE_ACCOUNT_FEEDBACK_LABEL": "",
|
||||
"DELETE_ACCOUNT_FEEDBACK_PLACEHOLDER": "",
|
||||
"CONFIRM_DELETE_ACCOUNT_CHECKBOX_LABEL": "",
|
||||
"CONFIRM_DELETE_ACCOUNT": "",
|
||||
"FEEDBACK_REQUIRED": "",
|
||||
"FEEDBACK_REQUIRED_FOUND_ANOTHER_SERVICE": "",
|
||||
"RECOVER_TWO_FACTOR": "",
|
||||
"at": "",
|
||||
"AUTH_NEXT": "",
|
||||
"AUTH_DOWNLOAD_MOBILE_APP": "",
|
||||
"HIDDEN": "",
|
||||
"HIDE": "",
|
||||
"UNHIDE": "",
|
||||
"UNHIDE_TO_COLLECTION": "",
|
||||
"SORT_BY": "",
|
||||
"NEWEST_FIRST": "",
|
||||
"OLDEST_FIRST": "",
|
||||
"CONVERSION_FAILED_NOTIFICATION_MESSAGE": "",
|
||||
"SELECT_COLLECTION": "",
|
||||
"PIN_ALBUM": "",
|
||||
"UNPIN_ALBUM": "",
|
||||
"DOWNLOAD_COMPLETE": "",
|
||||
"DOWNLOADING_COLLECTION": "",
|
||||
"DOWNLOAD_FAILED": "",
|
||||
"DOWNLOAD_PROGRESS": "",
|
||||
"CRASH_REPORTING": "",
|
||||
"CHRISTMAS": "",
|
||||
"CHRISTMAS_EVE": "",
|
||||
"NEW_YEAR": "",
|
||||
"NEW_YEAR_EVE": "",
|
||||
"IMAGE": "",
|
||||
"VIDEO": "",
|
||||
"LIVE_PHOTO": "",
|
||||
"CONVERT": "",
|
||||
"CONFIRM_EDITOR_CLOSE_MESSAGE": "",
|
||||
"CONFIRM_EDITOR_CLOSE_DESCRIPTION": "",
|
||||
"BRIGHTNESS": "",
|
||||
"CONTRAST": "",
|
||||
"SATURATION": "",
|
||||
"BLUR": "",
|
||||
"INVERT_COLORS": "",
|
||||
"ASPECT_RATIO": "",
|
||||
"SQUARE": "",
|
||||
"ROTATE_LEFT": "",
|
||||
"ROTATE_RIGHT": "",
|
||||
"FLIP_VERTICALLY": "",
|
||||
"FLIP_HORIZONTALLY": "",
|
||||
"DOWNLOAD_EDITED": "",
|
||||
"SAVE_A_COPY_TO_ENTE": "",
|
||||
"RESTORE_ORIGINAL": "",
|
||||
"TRANSFORM": "",
|
||||
"COLORS": "",
|
||||
"FLIP": "",
|
||||
"ROTATION": "",
|
||||
"RESET": "",
|
||||
"PHOTO_EDITOR": "",
|
||||
"FASTER_UPLOAD": "",
|
||||
"FASTER_UPLOAD_DESCRIPTION": "",
|
||||
"MAGIC_SEARCH_STATUS": "",
|
||||
"INDEXED_ITEMS": "",
|
||||
"CAST_ALBUM_TO_TV": "",
|
||||
"ENTER_CAST_PIN_CODE": "",
|
||||
"PAIR_DEVICE_TO_TV": "",
|
||||
"TV_NOT_FOUND": "",
|
||||
"AUTO_CAST_PAIR": "",
|
||||
"AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "",
|
||||
"PAIR_WITH_PIN": "",
|
||||
"CHOOSE_DEVICE_FROM_BROWSER": "",
|
||||
"PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "",
|
||||
"VISIT_CAST_ENTE_IO": "",
|
||||
"CAST_AUTO_PAIR_FAILED": "",
|
||||
"CACHE_DIRECTORY": "",
|
||||
"PASSKEYS": "",
|
||||
"FREEHAND": "",
|
||||
"APPLY_CROP": "",
|
||||
"PHOTO_EDIT_REQUIRED_TO_SAVE": ""
|
||||
}
|
|
@ -1,162 +1,162 @@
|
|||
{
|
||||
"HERO_SLIDE_1_TITLE": "",
|
||||
"HERO_SLIDE_1": "",
|
||||
"HERO_SLIDE_2_TITLE": "",
|
||||
"HERO_SLIDE_2": "",
|
||||
"HERO_SLIDE_3_TITLE": "",
|
||||
"HERO_SLIDE_3": "",
|
||||
"LOGIN": "",
|
||||
"SIGN_UP": "",
|
||||
"NEW_USER": "",
|
||||
"EXISTING_USER": "",
|
||||
"ENTER_NAME": "",
|
||||
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
|
||||
"ENTER_EMAIL": "",
|
||||
"EMAIL_ERROR": "",
|
||||
"REQUIRED": "",
|
||||
"EMAIL_SENT": "",
|
||||
"CHECK_INBOX": "",
|
||||
"ENTER_OTT": "",
|
||||
"RESEND_MAIL": "",
|
||||
"VERIFY": "",
|
||||
"UNKNOWN_ERROR": "",
|
||||
"INVALID_CODE": "",
|
||||
"EXPIRED_CODE": "",
|
||||
"SENDING": "",
|
||||
"SENT": "",
|
||||
"PASSWORD": "",
|
||||
"LINK_PASSWORD": "",
|
||||
"RETURN_PASSPHRASE_HINT": "",
|
||||
"SET_PASSPHRASE": "",
|
||||
"VERIFY_PASSPHRASE": "",
|
||||
"INCORRECT_PASSPHRASE": "",
|
||||
"ENTER_ENC_PASSPHRASE": "",
|
||||
"HERO_SLIDE_1_TITLE": "<div>Личные резервные копии</div><div>для твоих воспоминаний</div>",
|
||||
"HERO_SLIDE_1": "Сквозное шифрование по умолчанию",
|
||||
"HERO_SLIDE_2_TITLE": "<div>Надежно хранится</div><div>в убежище от радиоактивных осадков</div>",
|
||||
"HERO_SLIDE_2": "Созданный для того, чтобы пережить",
|
||||
"HERO_SLIDE_3_TITLE": "<div>Доступно</div><div> везде</div>",
|
||||
"HERO_SLIDE_3": "Android, iOS, Веб, ПК",
|
||||
"LOGIN": "Авторизоваться",
|
||||
"SIGN_UP": "Регистрация",
|
||||
"NEW_USER": "Новенький в ente",
|
||||
"EXISTING_USER": "Существующий пользователь",
|
||||
"ENTER_NAME": "Введите имя",
|
||||
"PUBLIC_UPLOADER_NAME_MESSAGE": "Добавьте имя, чтобы ваши друзья знали, кого благодарить за эти замечательные фотографии!",
|
||||
"ENTER_EMAIL": "Введите адрес электронной почты",
|
||||
"EMAIL_ERROR": "Введите действительный адрес электронной почты",
|
||||
"REQUIRED": "Требуется",
|
||||
"EMAIL_SENT": "Проверочный код отправлен на <a>{{email}}</a>",
|
||||
"CHECK_INBOX": "Пожалуйста, проверьте свой почтовый ящик (и спам) для завершения проверки",
|
||||
"ENTER_OTT": "Проверочный код",
|
||||
"RESEND_MAIL": "Отправить код еще раз",
|
||||
"VERIFY": "Подтвердить",
|
||||
"UNKNOWN_ERROR": "Что-то пошло не так, Попробуйте еще раз",
|
||||
"INVALID_CODE": "Неверный код подтверждения",
|
||||
"EXPIRED_CODE": "Срок действия вашего проверочного кода истек",
|
||||
"SENDING": "Отправка...",
|
||||
"SENT": "Отправлено!",
|
||||
"PASSWORD": "Пароль",
|
||||
"LINK_PASSWORD": "Введите пароль, чтобы разблокировать альбом",
|
||||
"RETURN_PASSPHRASE_HINT": "Пароль",
|
||||
"SET_PASSPHRASE": "Установить пароль",
|
||||
"VERIFY_PASSPHRASE": "Войти",
|
||||
"INCORRECT_PASSPHRASE": "Неверный пароль",
|
||||
"ENTER_ENC_PASSPHRASE": "Пожалуйста, введите пароль, который мы можем использовать для шифрования ваших данных",
|
||||
"PASSPHRASE_DISCLAIMER": "",
|
||||
"WELCOME_TO_ENTE_HEADING": "",
|
||||
"WELCOME_TO_ENTE_HEADING": "Добро пожаловать в <a/>",
|
||||
"WELCOME_TO_ENTE_SUBHEADING": "",
|
||||
"WHERE_YOUR_BEST_PHOTOS_LIVE": "",
|
||||
"KEY_GENERATION_IN_PROGRESS_MESSAGE": "",
|
||||
"PASSPHRASE_HINT": "",
|
||||
"CONFIRM_PASSPHRASE": "",
|
||||
"REFERRAL_CODE_HINT": "",
|
||||
"REFERRAL_INFO": "",
|
||||
"PASSPHRASE_MATCH_ERROR": "",
|
||||
"CONSOLE_WARNING_STOP": "",
|
||||
"CONSOLE_WARNING_DESC": "",
|
||||
"CREATE_COLLECTION": "",
|
||||
"ENTER_ALBUM_NAME": "",
|
||||
"CLOSE_OPTION": "",
|
||||
"ENTER_FILE_NAME": "",
|
||||
"CLOSE": "",
|
||||
"NO": "",
|
||||
"NOTHING_HERE": "",
|
||||
"UPLOAD": "",
|
||||
"IMPORT": "",
|
||||
"ADD_PHOTOS": "",
|
||||
"ADD_MORE_PHOTOS": "",
|
||||
"add_photos_one": "",
|
||||
"add_photos_other": "",
|
||||
"SELECT_PHOTOS": "",
|
||||
"FILE_UPLOAD": "",
|
||||
"WHERE_YOUR_BEST_PHOTOS_LIVE": "Где живут ваши лучшие фотографии",
|
||||
"KEY_GENERATION_IN_PROGRESS_MESSAGE": "Генерируем ключи шифрования...",
|
||||
"PASSPHRASE_HINT": "Пароль",
|
||||
"CONFIRM_PASSPHRASE": "Подтвердите пароль",
|
||||
"REFERRAL_CODE_HINT": "Как вы узнали о Ente? (необязательно)",
|
||||
"REFERRAL_INFO": "Будет полезно, если вы укажете, где нашли нас, так как мы не отслеживаем установки приложения!",
|
||||
"PASSPHRASE_MATCH_ERROR": "Пароли не совпадают",
|
||||
"CONSOLE_WARNING_STOP": "Остановись!",
|
||||
"CONSOLE_WARNING_DESC": "Это функция браузера, предназначенная для разработчиков. Пожалуйста, не копируйте и не вставляйте сюда непроверенный код.",
|
||||
"CREATE_COLLECTION": "Новый альбом",
|
||||
"ENTER_ALBUM_NAME": "Название альбома",
|
||||
"CLOSE_OPTION": "Закрыть (Esc)",
|
||||
"ENTER_FILE_NAME": "Имя файла",
|
||||
"CLOSE": "Закрыть",
|
||||
"NO": "Нет",
|
||||
"NOTHING_HERE": "Здесь нечего смотреть! 👀",
|
||||
"UPLOAD": "Загрузить",
|
||||
"IMPORT": "Импорт",
|
||||
"ADD_PHOTOS": "Добавить фотографии",
|
||||
"ADD_MORE_PHOTOS": "Добавить больше фото",
|
||||
"add_photos_one": "Добавить 1 элемент",
|
||||
"add_photos_other": "Добавить {{count, number}} элементов",
|
||||
"SELECT_PHOTOS": "Выбрать фотографии",
|
||||
"FILE_UPLOAD": "Загрузка файла",
|
||||
"UPLOAD_STAGE_MESSAGE": {
|
||||
"0": "",
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": ""
|
||||
"0": "Подготовка к загрузке",
|
||||
"1": "Чтение файлов метаданных Google",
|
||||
"2": "{{uploadCounter.finished, number}} / {{uploadCounter.total, number}} файлов извлечены",
|
||||
"3": "{{uploadCounter.finished, number}} / {{uploadCounter.total, number}} файлов обработано",
|
||||
"4": "Отмена оставшихся загрузок",
|
||||
"5": "Резервное копирование завершено"
|
||||
},
|
||||
"FILE_NOT_UPLOADED_LIST": "",
|
||||
"SUBSCRIPTION_EXPIRED": "",
|
||||
"SUBSCRIPTION_EXPIRED_MESSAGE": "",
|
||||
"STORAGE_QUOTA_EXCEEDED": "",
|
||||
"INITIAL_LOAD_DELAY_WARNING": "",
|
||||
"USER_DOES_NOT_EXIST": "",
|
||||
"NO_ACCOUNT": "",
|
||||
"ACCOUNT_EXISTS": "",
|
||||
"CREATE": "",
|
||||
"DOWNLOAD": "",
|
||||
"DOWNLOAD_OPTION": "",
|
||||
"DOWNLOAD_FAVORITES": "",
|
||||
"DOWNLOAD_UNCATEGORIZED": "",
|
||||
"DOWNLOAD_HIDDEN_ITEMS": "",
|
||||
"COPY_OPTION": "",
|
||||
"TOGGLE_FULLSCREEN": "",
|
||||
"ZOOM_IN_OUT": "",
|
||||
"PREVIOUS": "",
|
||||
"NEXT": "",
|
||||
"TITLE_PHOTOS": "",
|
||||
"TITLE_ALBUMS": "",
|
||||
"FILE_NOT_UPLOADED_LIST": "Следующие файлы не были загружены",
|
||||
"SUBSCRIPTION_EXPIRED": "Подписка закончилась",
|
||||
"SUBSCRIPTION_EXPIRED_MESSAGE": "Срок действия вашей подписки истек, пожалуйста, <a>продлите</a>",
|
||||
"STORAGE_QUOTA_EXCEEDED": "Превышен лимит хранения",
|
||||
"INITIAL_LOAD_DELAY_WARNING": "Первая загрузка может занять некоторое время",
|
||||
"USER_DOES_NOT_EXIST": "Пользователь с таким email не найден",
|
||||
"NO_ACCOUNT": "У вас нет учетной записи",
|
||||
"ACCOUNT_EXISTS": "Уже есть аккаунт",
|
||||
"CREATE": "Создать",
|
||||
"DOWNLOAD": "Скачать",
|
||||
"DOWNLOAD_OPTION": "Скачать (D)",
|
||||
"DOWNLOAD_FAVORITES": "Скачать избранные",
|
||||
"DOWNLOAD_UNCATEGORIZED": "Скачать без категорий",
|
||||
"DOWNLOAD_HIDDEN_ITEMS": "Скачать скрытые элементы",
|
||||
"COPY_OPTION": "Скопировать как PNG (Ctrl/Cmd - C)",
|
||||
"TOGGLE_FULLSCREEN": "Полноэкранный режим (F)",
|
||||
"ZOOM_IN_OUT": "Увеличить/уменьшить",
|
||||
"PREVIOUS": "Предыдущий (←)",
|
||||
"NEXT": "Следующий (→)",
|
||||
"TITLE_PHOTOS": "Ente Фото",
|
||||
"TITLE_ALBUMS": "Ente Фото",
|
||||
"TITLE_AUTH": "",
|
||||
"UPLOAD_FIRST_PHOTO": "",
|
||||
"IMPORT_YOUR_FOLDERS": "",
|
||||
"UPLOAD_DROPZONE_MESSAGE": "",
|
||||
"WATCH_FOLDER_DROPZONE_MESSAGE": "",
|
||||
"TRASH_FILES_TITLE": "",
|
||||
"TRASH_FILE_TITLE": "",
|
||||
"DELETE_FILES_TITLE": "",
|
||||
"DELETE_FILES_MESSAGE": "",
|
||||
"DELETE": "",
|
||||
"DELETE_OPTION": "",
|
||||
"FAVORITE_OPTION": "",
|
||||
"UPLOAD_FIRST_PHOTO": "Загрузите своё первое фото",
|
||||
"IMPORT_YOUR_FOLDERS": "Импортируйте папки",
|
||||
"UPLOAD_DROPZONE_MESSAGE": "Перетащите для резервного копирования файлов",
|
||||
"WATCH_FOLDER_DROPZONE_MESSAGE": "Перетащите, чтобы добавить просматриваемую папку",
|
||||
"TRASH_FILES_TITLE": "Удалить файлы?",
|
||||
"TRASH_FILE_TITLE": "Удалить файл?",
|
||||
"DELETE_FILES_TITLE": "Удалить немедленно?",
|
||||
"DELETE_FILES_MESSAGE": "Выбранные файлы будут безвозвратно удалены из вашей учетной записи ente.",
|
||||
"DELETE": "Удалить",
|
||||
"DELETE_OPTION": "Удалить (DEL)",
|
||||
"FAVORITE_OPTION": "Избранное (L)",
|
||||
"UNFAVORITE_OPTION": "",
|
||||
"MULTI_FOLDER_UPLOAD": "",
|
||||
"UPLOAD_STRATEGY_CHOICE": "",
|
||||
"UPLOAD_STRATEGY_SINGLE_COLLECTION": "",
|
||||
"OR": "",
|
||||
"UPLOAD_STRATEGY_COLLECTION_PER_FOLDER": "",
|
||||
"SESSION_EXPIRED_MESSAGE": "",
|
||||
"SESSION_EXPIRED": "",
|
||||
"PASSWORD_GENERATION_FAILED": "",
|
||||
"CHANGE_PASSWORD": "",
|
||||
"GO_BACK": "",
|
||||
"RECOVERY_KEY": "",
|
||||
"SAVE_LATER": "",
|
||||
"SAVE": "",
|
||||
"RECOVERY_KEY_DESCRIPTION": "",
|
||||
"RECOVER_KEY_GENERATION_FAILED": "",
|
||||
"KEY_NOT_STORED_DISCLAIMER": "",
|
||||
"FORGOT_PASSWORD": "",
|
||||
"RECOVER_ACCOUNT": "",
|
||||
"RECOVERY_KEY_HINT": "",
|
||||
"RECOVER": "",
|
||||
"NO_RECOVERY_KEY": "",
|
||||
"INCORRECT_RECOVERY_KEY": "",
|
||||
"SORRY": "",
|
||||
"NO_RECOVERY_KEY_MESSAGE": "",
|
||||
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
|
||||
"CONTACT_SUPPORT": "",
|
||||
"REQUEST_FEATURE": "",
|
||||
"SUPPORT": "",
|
||||
"CONFIRM": "",
|
||||
"CANCEL": "",
|
||||
"LOGOUT": "",
|
||||
"DELETE_ACCOUNT": "",
|
||||
"DELETE_ACCOUNT_MESSAGE": "",
|
||||
"LOGOUT_MESSAGE": "",
|
||||
"CHANGE_EMAIL": "",
|
||||
"OK": "",
|
||||
"SUCCESS": "",
|
||||
"ERROR": "",
|
||||
"MESSAGE": "",
|
||||
"INSTALL_MOBILE_APP": "",
|
||||
"MULTI_FOLDER_UPLOAD": "Обнаружено несколько папок",
|
||||
"UPLOAD_STRATEGY_CHOICE": "Вы хотите загрузить их в",
|
||||
"UPLOAD_STRATEGY_SINGLE_COLLECTION": "Один альбом",
|
||||
"OR": "или",
|
||||
"UPLOAD_STRATEGY_COLLECTION_PER_FOLDER": "Отдельные альбомы",
|
||||
"SESSION_EXPIRED_MESSAGE": "Истёк срок действия вашей сессии. Для продолжения, пожалуйста, войдите снова",
|
||||
"SESSION_EXPIRED": "Время сессии истекло",
|
||||
"PASSWORD_GENERATION_FAILED": "Вашему браузеру не удалось сгенерировать надежный ключ, соответствующий стандартам шифрования ente, пожалуйста, попробуйте использовать мобильное приложение или другой браузер",
|
||||
"CHANGE_PASSWORD": "Изменить пароль",
|
||||
"GO_BACK": "Вернуться назад",
|
||||
"RECOVERY_KEY": "Ключ восстановления",
|
||||
"SAVE_LATER": "Сделать позже",
|
||||
"SAVE": "Сохранить ключ",
|
||||
"RECOVERY_KEY_DESCRIPTION": "Если вы забыли свой пароль, то восстановить данные можно только с помощью этого ключа.",
|
||||
"RECOVER_KEY_GENERATION_FAILED": "Не удалось сгенерировать код восстановления, пожалуйста, повторите попытку",
|
||||
"KEY_NOT_STORED_DISCLAIMER": "Мы не храним этот ключ, поэтому, пожалуйста, сохраните его в надежном месте",
|
||||
"FORGOT_PASSWORD": "Забыл пароль",
|
||||
"RECOVER_ACCOUNT": "Восстановить аккаунт",
|
||||
"RECOVERY_KEY_HINT": "Ключ восстановления",
|
||||
"RECOVER": "Восстановить",
|
||||
"NO_RECOVERY_KEY": "Нет ключа восстановления?",
|
||||
"INCORRECT_RECOVERY_KEY": "Неправильный ключ восстановления",
|
||||
"SORRY": "Извините",
|
||||
"NO_RECOVERY_KEY_MESSAGE": "Из-за природы нашего сквозного протокола шифрования ваши данные не могут быть расшифрованы без вашего пароля или ключа восстановления",
|
||||
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Пожалуйста, отправьте электронное письмо на адрес <a>{{emailID}}</a> с вашего зарегистрированного адреса электронной почты",
|
||||
"CONTACT_SUPPORT": "Связаться с поддержкой",
|
||||
"REQUEST_FEATURE": "Запросить функцию",
|
||||
"SUPPORT": "Поддержка",
|
||||
"CONFIRM": "Подтвердить",
|
||||
"CANCEL": "Отменить",
|
||||
"LOGOUT": "Выйти",
|
||||
"DELETE_ACCOUNT": "Удалить аккаунт",
|
||||
"DELETE_ACCOUNT_MESSAGE": "<p>Пожалуйста, отправьте письмо по адресу <a>{{emailID}}</a> с вашего зарегистрированного адреса электронной почты.</p><p> Ваш запрос будет обработан в течение 72 часов</p>",
|
||||
"LOGOUT_MESSAGE": "Вы уверены, что хотите выйти?",
|
||||
"CHANGE_EMAIL": "Изменить адрес электронной почты",
|
||||
"OK": "ОК",
|
||||
"SUCCESS": "Успешно",
|
||||
"ERROR": "Ошибка",
|
||||
"MESSAGE": "Сообщение",
|
||||
"INSTALL_MOBILE_APP": "Установите наше приложение <a>Android</a> или <b>iOS</b> для автоматического резервного копирования всех ваших фотографий",
|
||||
"DOWNLOAD_APP_MESSAGE": "",
|
||||
"DOWNLOAD_APP": "",
|
||||
"EXPORT": "",
|
||||
"SUBSCRIPTION": "",
|
||||
"SUBSCRIBE": "",
|
||||
"MANAGEMENT_PORTAL": "",
|
||||
"MANAGE_FAMILY_PORTAL": "",
|
||||
"LEAVE_FAMILY_PLAN": "",
|
||||
"LEAVE": "",
|
||||
"LEAVE_FAMILY_CONFIRM": "",
|
||||
"CHOOSE_PLAN": "",
|
||||
"MANAGE_PLAN": "",
|
||||
"ACTIVE": "",
|
||||
"OFFLINE_MSG": "",
|
||||
"FREE_SUBSCRIPTION_INFO": "",
|
||||
"FAMILY_SUBSCRIPTION_INFO": "",
|
||||
"RENEWAL_ACTIVE_SUBSCRIPTION_STATUS": "",
|
||||
"DOWNLOAD_APP": "Загрузить приложение для компьютера",
|
||||
"EXPORT": "Экспортировать данные",
|
||||
"SUBSCRIPTION": "Подписка",
|
||||
"SUBSCRIBE": "Подписаться",
|
||||
"MANAGEMENT_PORTAL": "Управлять платёжной информацией",
|
||||
"MANAGE_FAMILY_PORTAL": "Управление семьёй",
|
||||
"LEAVE_FAMILY_PLAN": "Покинуть семейный план",
|
||||
"LEAVE": "Выйти",
|
||||
"LEAVE_FAMILY_CONFIRM": "Вы уверены, что хотите покинуть семейный план?",
|
||||
"CHOOSE_PLAN": "Выбери свой план",
|
||||
"MANAGE_PLAN": "Управление подпиской",
|
||||
"ACTIVE": "Активный",
|
||||
"OFFLINE_MSG": "Вы не в сети, кэшированные воспоминания отображаются",
|
||||
"FREE_SUBSCRIPTION_INFO": "Вы используете <strong>бесплатный</strong> тарифный план, истекающий {{date, dateTime}}",
|
||||
"FAMILY_SUBSCRIPTION_INFO": "Вы используете семейный план, управляемый",
|
||||
"RENEWAL_ACTIVE_SUBSCRIPTION_STATUS": "Продление {{date, dateTime}}",
|
||||
"RENEWAL_CANCELLED_SUBSCRIPTION_STATUS": "",
|
||||
"RENEWAL_CANCELLED_SUBSCRIPTION_INFO": "",
|
||||
"ADD_ON_AVAILABLE_TILL": "",
|
||||
|
@ -167,70 +167,70 @@
|
|||
"SUBSCRIPTION_UPDATE_FAILED": "",
|
||||
"UPDATE_PAYMENT_METHOD_MESSAGE": "",
|
||||
"STRIPE_AUTHENTICATION_FAILED": "",
|
||||
"UPDATE_PAYMENT_METHOD": "",
|
||||
"MONTHLY": "",
|
||||
"YEARLY": "",
|
||||
"UPDATE_SUBSCRIPTION_MESSAGE": "",
|
||||
"UPDATE_SUBSCRIPTION": "",
|
||||
"CANCEL_SUBSCRIPTION": "",
|
||||
"CANCEL_SUBSCRIPTION_MESSAGE": "",
|
||||
"CANCEL_SUBSCRIPTION_WITH_ADDON_MESSAGE": "",
|
||||
"SUBSCRIPTION_CANCEL_FAILED": "",
|
||||
"SUBSCRIPTION_CANCEL_SUCCESS": "",
|
||||
"REACTIVATE_SUBSCRIPTION": "",
|
||||
"REACTIVATE_SUBSCRIPTION_MESSAGE": "",
|
||||
"SUBSCRIPTION_ACTIVATE_SUCCESS": "",
|
||||
"SUBSCRIPTION_ACTIVATE_FAILED": "",
|
||||
"SUBSCRIPTION_PURCHASE_SUCCESS_TITLE": "",
|
||||
"CANCEL_SUBSCRIPTION_ON_MOBILE": "",
|
||||
"CANCEL_SUBSCRIPTION_ON_MOBILE_MESSAGE": "",
|
||||
"MAIL_TO_MANAGE_SUBSCRIPTION": "",
|
||||
"RENAME": "",
|
||||
"RENAME_FILE": "",
|
||||
"RENAME_COLLECTION": "",
|
||||
"DELETE_COLLECTION_TITLE": "",
|
||||
"DELETE_COLLECTION": "",
|
||||
"DELETE_COLLECTION_MESSAGE": "",
|
||||
"DELETE_PHOTOS": "",
|
||||
"KEEP_PHOTOS": "",
|
||||
"SHARE": "",
|
||||
"SHARE_COLLECTION": "",
|
||||
"SHAREES": "",
|
||||
"SHARE_WITH_SELF": "",
|
||||
"ALREADY_SHARED": "",
|
||||
"SHARING_BAD_REQUEST_ERROR": "",
|
||||
"SHARING_DISABLED_FOR_FREE_ACCOUNTS": "",
|
||||
"DOWNLOAD_COLLECTION": "",
|
||||
"DOWNLOAD_COLLECTION_MESSAGE": "",
|
||||
"CREATE_ALBUM_FAILED": "",
|
||||
"SEARCH": "",
|
||||
"SEARCH_RESULTS": "",
|
||||
"NO_RESULTS": "",
|
||||
"SEARCH_HINT": "",
|
||||
"UPDATE_PAYMENT_METHOD": "Обновить платёжную информацию",
|
||||
"MONTHLY": "Ежемесячно",
|
||||
"YEARLY": "Ежегодно",
|
||||
"UPDATE_SUBSCRIPTION_MESSAGE": "Хотите сменить текущий план?",
|
||||
"UPDATE_SUBSCRIPTION": "Изменить план",
|
||||
"CANCEL_SUBSCRIPTION": "Отменить подписку",
|
||||
"CANCEL_SUBSCRIPTION_MESSAGE": "<p>Все ваши данные будут удалены с наших серверов в конце этого расчетного периода.</p><p> Вы уверены, что хотите отменить свою подписку?</p>",
|
||||
"CANCEL_SUBSCRIPTION_WITH_ADDON_MESSAGE": "<p>Вы уверены, что хотите отменить свою подписку?</p>",
|
||||
"SUBSCRIPTION_CANCEL_FAILED": "Не удалось отменить подписку",
|
||||
"SUBSCRIPTION_CANCEL_SUCCESS": "Подписка успешно отменена",
|
||||
"REACTIVATE_SUBSCRIPTION": "Возобновить подписку",
|
||||
"REACTIVATE_SUBSCRIPTION_MESSAGE": "После повторной активации вам будет выставлен счет в {{date, dateTime}}",
|
||||
"SUBSCRIPTION_ACTIVATE_SUCCESS": "Подписка успешно активирована ",
|
||||
"SUBSCRIPTION_ACTIVATE_FAILED": "Не удалось повторно активировать продление подписки",
|
||||
"SUBSCRIPTION_PURCHASE_SUCCESS_TITLE": "Спасибо",
|
||||
"CANCEL_SUBSCRIPTION_ON_MOBILE": "Отменить мобильную подписку",
|
||||
"CANCEL_SUBSCRIPTION_ON_MOBILE_MESSAGE": "Пожалуйста, отмените свою подписку в мобильном приложении, чтобы активировать подписку здесь",
|
||||
"MAIL_TO_MANAGE_SUBSCRIPTION": "Пожалуйста, свяжитесь с <a>{{emailID}}</a> для управления подпиской",
|
||||
"RENAME": "Переименовать",
|
||||
"RENAME_FILE": "Переименовать файл",
|
||||
"RENAME_COLLECTION": "Переименовать альбом",
|
||||
"DELETE_COLLECTION_TITLE": "Удалить альбом?",
|
||||
"DELETE_COLLECTION": "Удалить альбом",
|
||||
"DELETE_COLLECTION_MESSAGE": "Также удалить фотографии (и видео), которые есть в этом альбоме из <a>всех</a> других альбомов, где они есть?",
|
||||
"DELETE_PHOTOS": "Удалить фото",
|
||||
"KEEP_PHOTOS": "Оставить фото",
|
||||
"SHARE": "Поделиться",
|
||||
"SHARE_COLLECTION": "Поделиться альбомом",
|
||||
"SHAREES": "Поделиться с",
|
||||
"SHARE_WITH_SELF": "Ой, Вы не можете поделиться с самим собой",
|
||||
"ALREADY_SHARED": "Упс, Вы уже делились этим с {{email}}",
|
||||
"SHARING_BAD_REQUEST_ERROR": "Делиться альбомом запрещено",
|
||||
"SHARING_DISABLED_FOR_FREE_ACCOUNTS": "Совместное использование отключено для бесплатных аккаунтов",
|
||||
"DOWNLOAD_COLLECTION": "Загрузить альбом",
|
||||
"DOWNLOAD_COLLECTION_MESSAGE": "<p>Вы уверены, что хотите загрузить альбом полностью?</p><p> Все файлы будут последовательно помещены в очередь на загрузку</p>",
|
||||
"CREATE_ALBUM_FAILED": "Не удалось создать альбом, пожалуйста, попробуйте еще раз",
|
||||
"SEARCH": "Поиск",
|
||||
"SEARCH_RESULTS": "Результаты поиска",
|
||||
"NO_RESULTS": "Ничего не найдено",
|
||||
"SEARCH_HINT": "Поиск альбомов, дат, описаний, ...",
|
||||
"SEARCH_TYPE": {
|
||||
"COLLECTION": "",
|
||||
"LOCATION": "",
|
||||
"CITY": "",
|
||||
"DATE": "",
|
||||
"FILE_NAME": "",
|
||||
"THING": "",
|
||||
"FILE_CAPTION": "",
|
||||
"FILE_TYPE": "",
|
||||
"COLLECTION": "Альбом",
|
||||
"LOCATION": "Местоположение",
|
||||
"CITY": "Местоположение",
|
||||
"DATE": "Дата",
|
||||
"FILE_NAME": "Имя файла",
|
||||
"THING": "Содержимое",
|
||||
"FILE_CAPTION": "Описание",
|
||||
"FILE_TYPE": "Тип файла",
|
||||
"CLIP": ""
|
||||
},
|
||||
"photos_count_zero": "",
|
||||
"photos_count_zero": "Воспоминания отсутствуют",
|
||||
"photos_count_one": "",
|
||||
"photos_count_other": "",
|
||||
"TERMS_AND_CONDITIONS": "",
|
||||
"ADD_TO_COLLECTION": "",
|
||||
"SELECTED": "",
|
||||
"VIDEO_PLAYBACK_FAILED_DOWNLOAD_INSTEAD": "",
|
||||
"PEOPLE": "",
|
||||
"INDEXING_SCHEDULED": "",
|
||||
"ANALYZING_PHOTOS": "",
|
||||
"INDEXING_PEOPLE": "",
|
||||
"INDEXING_DONE": "",
|
||||
"UNIDENTIFIED_FACES": "",
|
||||
"ADD_TO_COLLECTION": "Добавить в альбом",
|
||||
"SELECTED": "выбрано",
|
||||
"VIDEO_PLAYBACK_FAILED_DOWNLOAD_INSTEAD": "Это видео нельзя воспроизвести в вашем браузере",
|
||||
"PEOPLE": "Люди",
|
||||
"INDEXING_SCHEDULED": "Индексация запланирована...",
|
||||
"ANALYZING_PHOTOS": "Индексирование фотографий ({{indexStatus.nSyncedFiles,number}} / {{indexStatus.nTotalFiles,number}})",
|
||||
"INDEXING_PEOPLE": "Индексирование людей на {{indexStatus.nSyncedFiles,number}} фотографиях...",
|
||||
"INDEXING_DONE": "Проиндексировано {{indexStatus.nSyncedFiles,number}} фотографий",
|
||||
"UNIDENTIFIED_FACES": "нераспознанные лица",
|
||||
"OBJECTS": "",
|
||||
"TEXT": "",
|
||||
"INFO": "",
|
||||
|
@ -253,36 +253,36 @@
|
|||
"EXIF": "",
|
||||
"ISO": "",
|
||||
"TWO_FACTOR": "",
|
||||
"TWO_FACTOR_AUTHENTICATION": "",
|
||||
"TWO_FACTOR_QR_INSTRUCTION": "",
|
||||
"ENTER_CODE_MANUALLY": "",
|
||||
"TWO_FACTOR_MANUAL_CODE_INSTRUCTION": "",
|
||||
"SCAN_QR_CODE": "",
|
||||
"ENABLE_TWO_FACTOR": "",
|
||||
"ENABLE": "",
|
||||
"LOST_DEVICE": "",
|
||||
"INCORRECT_CODE": "",
|
||||
"TWO_FACTOR_AUTHENTICATION": "Двухфакторная аутентификация",
|
||||
"TWO_FACTOR_QR_INSTRUCTION": "Сканируйте QR-код ниже с вашим любимым приложением для проверки подлинности",
|
||||
"ENTER_CODE_MANUALLY": "Введите код вручную",
|
||||
"TWO_FACTOR_MANUAL_CODE_INSTRUCTION": "Пожалуйста, введите этот код в вашем любимом приложении для аутентификации",
|
||||
"SCAN_QR_CODE": "Сканировать QR-код вместо",
|
||||
"ENABLE_TWO_FACTOR": "Включить двухфакторную аутентификацию",
|
||||
"ENABLE": "Включить",
|
||||
"LOST_DEVICE": "Потеряно двухфакторное устройство",
|
||||
"INCORRECT_CODE": "Неверный код",
|
||||
"TWO_FACTOR_INFO": "",
|
||||
"DISABLE_TWO_FACTOR_LABEL": "",
|
||||
"DISABLE_TWO_FACTOR_LABEL": "Отключить двухфакторную аутентификацию",
|
||||
"UPDATE_TWO_FACTOR_LABEL": "",
|
||||
"DISABLE": "",
|
||||
"RECONFIGURE": "",
|
||||
"UPDATE_TWO_FACTOR": "",
|
||||
"DISABLE": "Отключить",
|
||||
"RECONFIGURE": "Перенастроить",
|
||||
"UPDATE_TWO_FACTOR": "Обновить двухфакторную аутентификацию",
|
||||
"UPDATE_TWO_FACTOR_MESSAGE": "",
|
||||
"UPDATE": "",
|
||||
"DISABLE_TWO_FACTOR": "",
|
||||
"DISABLE_TWO_FACTOR_MESSAGE": "",
|
||||
"UPDATE": "Обновить",
|
||||
"DISABLE_TWO_FACTOR": "Отключить двухфакторную аутентификацию",
|
||||
"DISABLE_TWO_FACTOR_MESSAGE": "Вы уверены, что хотите отключить двухфакторную аутентификацию",
|
||||
"TWO_FACTOR_DISABLE_FAILED": "",
|
||||
"EXPORT_DATA": "",
|
||||
"SELECT_FOLDER": "",
|
||||
"DESTINATION": "",
|
||||
"START": "",
|
||||
"LAST_EXPORT_TIME": "",
|
||||
"EXPORT_AGAIN": "",
|
||||
"LOCAL_STORAGE_NOT_ACCESSIBLE": "",
|
||||
"EXPORT_DATA": "Экспортировать данные",
|
||||
"SELECT_FOLDER": "Выбрать папку",
|
||||
"DESTINATION": "Место назначения",
|
||||
"START": "Начать",
|
||||
"LAST_EXPORT_TIME": "Время последнего экспорта",
|
||||
"EXPORT_AGAIN": "Синхронизировать заново",
|
||||
"LOCAL_STORAGE_NOT_ACCESSIBLE": "Локальное хранилище недоступно",
|
||||
"LOCAL_STORAGE_NOT_ACCESSIBLE_MESSAGE": "",
|
||||
"SEND_OTT": "",
|
||||
"EMAIl_ALREADY_OWNED": "",
|
||||
"SEND_OTT": "Отправить одноразовый код",
|
||||
"EMAIl_ALREADY_OWNED": "Почта уже использована",
|
||||
"ETAGS_BLOCKED": "",
|
||||
"SKIPPED_VIDEOS_INFO": "",
|
||||
"LIVE_PHOTOS_DETECTED": "",
|
||||
|
@ -576,56 +576,56 @@
|
|||
"AUTH_NEXT": "",
|
||||
"AUTH_DOWNLOAD_MOBILE_APP": "",
|
||||
"HIDDEN": "",
|
||||
"HIDE": "",
|
||||
"UNHIDE": "",
|
||||
"HIDE": "Скрыть",
|
||||
"UNHIDE": "Показать",
|
||||
"UNHIDE_TO_COLLECTION": "",
|
||||
"SORT_BY": "",
|
||||
"NEWEST_FIRST": "",
|
||||
"OLDEST_FIRST": "",
|
||||
"SORT_BY": "Сортировать по",
|
||||
"NEWEST_FIRST": "Сначала новые",
|
||||
"OLDEST_FIRST": "Сначала старые",
|
||||
"CONVERSION_FAILED_NOTIFICATION_MESSAGE": "",
|
||||
"SELECT_COLLECTION": "",
|
||||
"PIN_ALBUM": "",
|
||||
"UNPIN_ALBUM": "",
|
||||
"DOWNLOAD_COMPLETE": "",
|
||||
"DOWNLOADING_COLLECTION": "",
|
||||
"DOWNLOAD_FAILED": "",
|
||||
"DOWNLOAD_PROGRESS": "",
|
||||
"CRASH_REPORTING": "",
|
||||
"CHRISTMAS": "",
|
||||
"CHRISTMAS_EVE": "",
|
||||
"NEW_YEAR": "",
|
||||
"NEW_YEAR_EVE": "",
|
||||
"IMAGE": "",
|
||||
"VIDEO": "",
|
||||
"LIVE_PHOTO": "",
|
||||
"CONVERT": "",
|
||||
"CONFIRM_EDITOR_CLOSE_MESSAGE": "",
|
||||
"CONFIRM_EDITOR_CLOSE_DESCRIPTION": "",
|
||||
"BRIGHTNESS": "",
|
||||
"CONTRAST": "",
|
||||
"SATURATION": "",
|
||||
"BLUR": "",
|
||||
"INVERT_COLORS": "",
|
||||
"ASPECT_RATIO": "",
|
||||
"SQUARE": "",
|
||||
"ROTATE_LEFT": "",
|
||||
"ROTATE_RIGHT": "",
|
||||
"FLIP_VERTICALLY": "",
|
||||
"FLIP_HORIZONTALLY": "",
|
||||
"DOWNLOAD_EDITED": "",
|
||||
"SAVE_A_COPY_TO_ENTE": "",
|
||||
"RESTORE_ORIGINAL": "",
|
||||
"TRANSFORM": "",
|
||||
"COLORS": "",
|
||||
"FLIP": "",
|
||||
"SELECT_COLLECTION": "Выбрать альбом",
|
||||
"PIN_ALBUM": "Закрепить альбом",
|
||||
"UNPIN_ALBUM": "Открепить альбом",
|
||||
"DOWNLOAD_COMPLETE": "Загрузка завершена",
|
||||
"DOWNLOADING_COLLECTION": "Загрузка {{name}}",
|
||||
"DOWNLOAD_FAILED": "Загрузка не удалась",
|
||||
"DOWNLOAD_PROGRESS": "{{progress.current}} / {{progress.total}} файлов",
|
||||
"CRASH_REPORTING": "Отчеты об ошибках",
|
||||
"CHRISTMAS": "Рождество",
|
||||
"CHRISTMAS_EVE": "Канун Рождества",
|
||||
"NEW_YEAR": "Новый год",
|
||||
"NEW_YEAR_EVE": "Канун Нового года",
|
||||
"IMAGE": "Изображение",
|
||||
"VIDEO": "Видео",
|
||||
"LIVE_PHOTO": "Живое фото",
|
||||
"CONVERT": "Преобразовать",
|
||||
"CONFIRM_EDITOR_CLOSE_MESSAGE": "Вы уверены, что хотите закрыть редактор?",
|
||||
"CONFIRM_EDITOR_CLOSE_DESCRIPTION": "Загрузите отредактированное изображение или сохраните копию в ente, чтобы сохранить внесенные изменения.",
|
||||
"BRIGHTNESS": "Яркость",
|
||||
"CONTRAST": "Контраст",
|
||||
"SATURATION": "Насыщенность",
|
||||
"BLUR": "Размытие",
|
||||
"INVERT_COLORS": "Инвертировать Цвета",
|
||||
"ASPECT_RATIO": "Соотношение Сторон",
|
||||
"SQUARE": "Квадрат",
|
||||
"ROTATE_LEFT": "Повернуть влево",
|
||||
"ROTATE_RIGHT": "Повернуть вправо",
|
||||
"FLIP_VERTICALLY": "Отразить вертикально",
|
||||
"FLIP_HORIZONTALLY": "Отразить горизонтально",
|
||||
"DOWNLOAD_EDITED": "Скачать отредактированный",
|
||||
"SAVE_A_COPY_TO_ENTE": "Сохранить копию в ente",
|
||||
"RESTORE_ORIGINAL": "Восстановить оригинал",
|
||||
"TRANSFORM": "Преобразовать",
|
||||
"COLORS": "Цвета",
|
||||
"FLIP": "Перевернуть",
|
||||
"ROTATION": "",
|
||||
"RESET": "",
|
||||
"PHOTO_EDITOR": "",
|
||||
"RESET": "Сбросить",
|
||||
"PHOTO_EDITOR": "Редактор фото",
|
||||
"FASTER_UPLOAD": "",
|
||||
"FASTER_UPLOAD_DESCRIPTION": "",
|
||||
"MAGIC_SEARCH_STATUS": "",
|
||||
"INDEXED_ITEMS": "",
|
||||
"CAST_ALBUM_TO_TV": "",
|
||||
"MAGIC_SEARCH_STATUS": "Статус волшебного поиска",
|
||||
"INDEXED_ITEMS": "Индексированные элементы",
|
||||
"CAST_ALBUM_TO_TV": "Воспроизвести альбом на ТВ",
|
||||
"ENTER_CAST_PIN_CODE": "",
|
||||
"PAIR_DEVICE_TO_TV": "",
|
||||
"TV_NOT_FOUND": "",
|
||||
|
|
644
web/apps/photos/public/locales/sv-SE/translation.json
Normal file
644
web/apps/photos/public/locales/sv-SE/translation.json
Normal file
|
@ -0,0 +1,644 @@
|
|||
{
|
||||
"HERO_SLIDE_1_TITLE": "",
|
||||
"HERO_SLIDE_1": "",
|
||||
"HERO_SLIDE_2_TITLE": "",
|
||||
"HERO_SLIDE_2": "",
|
||||
"HERO_SLIDE_3_TITLE": "",
|
||||
"HERO_SLIDE_3": "",
|
||||
"LOGIN": "",
|
||||
"SIGN_UP": "",
|
||||
"NEW_USER": "",
|
||||
"EXISTING_USER": "",
|
||||
"ENTER_NAME": "",
|
||||
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
|
||||
"ENTER_EMAIL": "",
|
||||
"EMAIL_ERROR": "",
|
||||
"REQUIRED": "",
|
||||
"EMAIL_SENT": "",
|
||||
"CHECK_INBOX": "",
|
||||
"ENTER_OTT": "",
|
||||
"RESEND_MAIL": "",
|
||||
"VERIFY": "",
|
||||
"UNKNOWN_ERROR": "",
|
||||
"INVALID_CODE": "",
|
||||
"EXPIRED_CODE": "",
|
||||
"SENDING": "",
|
||||
"SENT": "",
|
||||
"PASSWORD": "",
|
||||
"LINK_PASSWORD": "",
|
||||
"RETURN_PASSPHRASE_HINT": "",
|
||||
"SET_PASSPHRASE": "",
|
||||
"VERIFY_PASSPHRASE": "",
|
||||
"INCORRECT_PASSPHRASE": "",
|
||||
"ENTER_ENC_PASSPHRASE": "",
|
||||
"PASSPHRASE_DISCLAIMER": "",
|
||||
"WELCOME_TO_ENTE_HEADING": "",
|
||||
"WELCOME_TO_ENTE_SUBHEADING": "",
|
||||
"WHERE_YOUR_BEST_PHOTOS_LIVE": "",
|
||||
"KEY_GENERATION_IN_PROGRESS_MESSAGE": "",
|
||||
"PASSPHRASE_HINT": "",
|
||||
"CONFIRM_PASSPHRASE": "",
|
||||
"REFERRAL_CODE_HINT": "",
|
||||
"REFERRAL_INFO": "",
|
||||
"PASSPHRASE_MATCH_ERROR": "",
|
||||
"CONSOLE_WARNING_STOP": "",
|
||||
"CONSOLE_WARNING_DESC": "",
|
||||
"CREATE_COLLECTION": "",
|
||||
"ENTER_ALBUM_NAME": "",
|
||||
"CLOSE_OPTION": "",
|
||||
"ENTER_FILE_NAME": "",
|
||||
"CLOSE": "",
|
||||
"NO": "",
|
||||
"NOTHING_HERE": "",
|
||||
"UPLOAD": "",
|
||||
"IMPORT": "",
|
||||
"ADD_PHOTOS": "",
|
||||
"ADD_MORE_PHOTOS": "",
|
||||
"add_photos_one": "",
|
||||
"add_photos_other": "",
|
||||
"SELECT_PHOTOS": "",
|
||||
"FILE_UPLOAD": "",
|
||||
"UPLOAD_STAGE_MESSAGE": {
|
||||
"0": "",
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": ""
|
||||
},
|
||||
"FILE_NOT_UPLOADED_LIST": "",
|
||||
"SUBSCRIPTION_EXPIRED": "",
|
||||
"SUBSCRIPTION_EXPIRED_MESSAGE": "",
|
||||
"STORAGE_QUOTA_EXCEEDED": "",
|
||||
"INITIAL_LOAD_DELAY_WARNING": "",
|
||||
"USER_DOES_NOT_EXIST": "",
|
||||
"NO_ACCOUNT": "",
|
||||
"ACCOUNT_EXISTS": "",
|
||||
"CREATE": "",
|
||||
"DOWNLOAD": "",
|
||||
"DOWNLOAD_OPTION": "",
|
||||
"DOWNLOAD_FAVORITES": "",
|
||||
"DOWNLOAD_UNCATEGORIZED": "",
|
||||
"DOWNLOAD_HIDDEN_ITEMS": "",
|
||||
"COPY_OPTION": "",
|
||||
"TOGGLE_FULLSCREEN": "",
|
||||
"ZOOM_IN_OUT": "",
|
||||
"PREVIOUS": "",
|
||||
"NEXT": "",
|
||||
"TITLE_PHOTOS": "",
|
||||
"TITLE_ALBUMS": "",
|
||||
"TITLE_AUTH": "",
|
||||
"UPLOAD_FIRST_PHOTO": "",
|
||||
"IMPORT_YOUR_FOLDERS": "",
|
||||
"UPLOAD_DROPZONE_MESSAGE": "",
|
||||
"WATCH_FOLDER_DROPZONE_MESSAGE": "",
|
||||
"TRASH_FILES_TITLE": "",
|
||||
"TRASH_FILE_TITLE": "",
|
||||
"DELETE_FILES_TITLE": "",
|
||||
"DELETE_FILES_MESSAGE": "",
|
||||
"DELETE": "",
|
||||
"DELETE_OPTION": "",
|
||||
"FAVORITE_OPTION": "",
|
||||
"UNFAVORITE_OPTION": "",
|
||||
"MULTI_FOLDER_UPLOAD": "",
|
||||
"UPLOAD_STRATEGY_CHOICE": "",
|
||||
"UPLOAD_STRATEGY_SINGLE_COLLECTION": "",
|
||||
"OR": "",
|
||||
"UPLOAD_STRATEGY_COLLECTION_PER_FOLDER": "",
|
||||
"SESSION_EXPIRED_MESSAGE": "",
|
||||
"SESSION_EXPIRED": "",
|
||||
"PASSWORD_GENERATION_FAILED": "",
|
||||
"CHANGE_PASSWORD": "",
|
||||
"GO_BACK": "",
|
||||
"RECOVERY_KEY": "",
|
||||
"SAVE_LATER": "",
|
||||
"SAVE": "",
|
||||
"RECOVERY_KEY_DESCRIPTION": "",
|
||||
"RECOVER_KEY_GENERATION_FAILED": "",
|
||||
"KEY_NOT_STORED_DISCLAIMER": "",
|
||||
"FORGOT_PASSWORD": "",
|
||||
"RECOVER_ACCOUNT": "",
|
||||
"RECOVERY_KEY_HINT": "",
|
||||
"RECOVER": "",
|
||||
"NO_RECOVERY_KEY": "",
|
||||
"INCORRECT_RECOVERY_KEY": "",
|
||||
"SORRY": "",
|
||||
"NO_RECOVERY_KEY_MESSAGE": "",
|
||||
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
|
||||
"CONTACT_SUPPORT": "",
|
||||
"REQUEST_FEATURE": "",
|
||||
"SUPPORT": "",
|
||||
"CONFIRM": "",
|
||||
"CANCEL": "",
|
||||
"LOGOUT": "",
|
||||
"DELETE_ACCOUNT": "",
|
||||
"DELETE_ACCOUNT_MESSAGE": "",
|
||||
"LOGOUT_MESSAGE": "",
|
||||
"CHANGE_EMAIL": "",
|
||||
"OK": "",
|
||||
"SUCCESS": "",
|
||||
"ERROR": "",
|
||||
"MESSAGE": "",
|
||||
"INSTALL_MOBILE_APP": "",
|
||||
"DOWNLOAD_APP_MESSAGE": "",
|
||||
"DOWNLOAD_APP": "",
|
||||
"EXPORT": "",
|
||||
"SUBSCRIPTION": "",
|
||||
"SUBSCRIBE": "",
|
||||
"MANAGEMENT_PORTAL": "",
|
||||
"MANAGE_FAMILY_PORTAL": "",
|
||||
"LEAVE_FAMILY_PLAN": "",
|
||||
"LEAVE": "",
|
||||
"LEAVE_FAMILY_CONFIRM": "",
|
||||
"CHOOSE_PLAN": "",
|
||||
"MANAGE_PLAN": "",
|
||||
"ACTIVE": "",
|
||||
"OFFLINE_MSG": "",
|
||||
"FREE_SUBSCRIPTION_INFO": "",
|
||||
"FAMILY_SUBSCRIPTION_INFO": "",
|
||||
"RENEWAL_ACTIVE_SUBSCRIPTION_STATUS": "",
|
||||
"RENEWAL_CANCELLED_SUBSCRIPTION_STATUS": "",
|
||||
"RENEWAL_CANCELLED_SUBSCRIPTION_INFO": "",
|
||||
"ADD_ON_AVAILABLE_TILL": "",
|
||||
"STORAGE_QUOTA_EXCEEDED_SUBSCRIPTION_INFO": "",
|
||||
"SUBSCRIPTION_PURCHASE_SUCCESS": "",
|
||||
"SUBSCRIPTION_PURCHASE_CANCELLED": "",
|
||||
"SUBSCRIPTION_PURCHASE_FAILED": "",
|
||||
"SUBSCRIPTION_UPDATE_FAILED": "",
|
||||
"UPDATE_PAYMENT_METHOD_MESSAGE": "",
|
||||
"STRIPE_AUTHENTICATION_FAILED": "",
|
||||
"UPDATE_PAYMENT_METHOD": "",
|
||||
"MONTHLY": "",
|
||||
"YEARLY": "",
|
||||
"UPDATE_SUBSCRIPTION_MESSAGE": "",
|
||||
"UPDATE_SUBSCRIPTION": "",
|
||||
"CANCEL_SUBSCRIPTION": "",
|
||||
"CANCEL_SUBSCRIPTION_MESSAGE": "",
|
||||
"CANCEL_SUBSCRIPTION_WITH_ADDON_MESSAGE": "",
|
||||
"SUBSCRIPTION_CANCEL_FAILED": "",
|
||||
"SUBSCRIPTION_CANCEL_SUCCESS": "",
|
||||
"REACTIVATE_SUBSCRIPTION": "",
|
||||
"REACTIVATE_SUBSCRIPTION_MESSAGE": "",
|
||||
"SUBSCRIPTION_ACTIVATE_SUCCESS": "",
|
||||
"SUBSCRIPTION_ACTIVATE_FAILED": "",
|
||||
"SUBSCRIPTION_PURCHASE_SUCCESS_TITLE": "",
|
||||
"CANCEL_SUBSCRIPTION_ON_MOBILE": "",
|
||||
"CANCEL_SUBSCRIPTION_ON_MOBILE_MESSAGE": "",
|
||||
"MAIL_TO_MANAGE_SUBSCRIPTION": "",
|
||||
"RENAME": "",
|
||||
"RENAME_FILE": "",
|
||||
"RENAME_COLLECTION": "",
|
||||
"DELETE_COLLECTION_TITLE": "",
|
||||
"DELETE_COLLECTION": "",
|
||||
"DELETE_COLLECTION_MESSAGE": "",
|
||||
"DELETE_PHOTOS": "",
|
||||
"KEEP_PHOTOS": "",
|
||||
"SHARE": "",
|
||||
"SHARE_COLLECTION": "",
|
||||
"SHAREES": "",
|
||||
"SHARE_WITH_SELF": "",
|
||||
"ALREADY_SHARED": "",
|
||||
"SHARING_BAD_REQUEST_ERROR": "",
|
||||
"SHARING_DISABLED_FOR_FREE_ACCOUNTS": "",
|
||||
"DOWNLOAD_COLLECTION": "",
|
||||
"DOWNLOAD_COLLECTION_MESSAGE": "",
|
||||
"CREATE_ALBUM_FAILED": "",
|
||||
"SEARCH": "",
|
||||
"SEARCH_RESULTS": "",
|
||||
"NO_RESULTS": "",
|
||||
"SEARCH_HINT": "",
|
||||
"SEARCH_TYPE": {
|
||||
"COLLECTION": "",
|
||||
"LOCATION": "",
|
||||
"CITY": "",
|
||||
"DATE": "",
|
||||
"FILE_NAME": "",
|
||||
"THING": "",
|
||||
"FILE_CAPTION": "",
|
||||
"FILE_TYPE": "",
|
||||
"CLIP": ""
|
||||
},
|
||||
"photos_count_zero": "",
|
||||
"photos_count_one": "",
|
||||
"photos_count_other": "",
|
||||
"TERMS_AND_CONDITIONS": "",
|
||||
"ADD_TO_COLLECTION": "",
|
||||
"SELECTED": "",
|
||||
"VIDEO_PLAYBACK_FAILED_DOWNLOAD_INSTEAD": "",
|
||||
"PEOPLE": "",
|
||||
"INDEXING_SCHEDULED": "",
|
||||
"ANALYZING_PHOTOS": "",
|
||||
"INDEXING_PEOPLE": "",
|
||||
"INDEXING_DONE": "",
|
||||
"UNIDENTIFIED_FACES": "",
|
||||
"OBJECTS": "",
|
||||
"TEXT": "",
|
||||
"INFO": "",
|
||||
"INFO_OPTION": "",
|
||||
"FILE_NAME": "",
|
||||
"CAPTION_PLACEHOLDER": "",
|
||||
"LOCATION": "",
|
||||
"SHOW_ON_MAP": "",
|
||||
"MAP": "",
|
||||
"MAP_SETTINGS": "",
|
||||
"ENABLE_MAPS": "",
|
||||
"ENABLE_MAP": "",
|
||||
"DISABLE_MAPS": "",
|
||||
"ENABLE_MAP_DESCRIPTION": "",
|
||||
"DISABLE_MAP_DESCRIPTION": "",
|
||||
"DISABLE_MAP": "",
|
||||
"DETAILS": "",
|
||||
"VIEW_EXIF": "",
|
||||
"NO_EXIF": "",
|
||||
"EXIF": "",
|
||||
"ISO": "",
|
||||
"TWO_FACTOR": "",
|
||||
"TWO_FACTOR_AUTHENTICATION": "",
|
||||
"TWO_FACTOR_QR_INSTRUCTION": "",
|
||||
"ENTER_CODE_MANUALLY": "",
|
||||
"TWO_FACTOR_MANUAL_CODE_INSTRUCTION": "",
|
||||
"SCAN_QR_CODE": "",
|
||||
"ENABLE_TWO_FACTOR": "",
|
||||
"ENABLE": "",
|
||||
"LOST_DEVICE": "",
|
||||
"INCORRECT_CODE": "",
|
||||
"TWO_FACTOR_INFO": "",
|
||||
"DISABLE_TWO_FACTOR_LABEL": "",
|
||||
"UPDATE_TWO_FACTOR_LABEL": "",
|
||||
"DISABLE": "",
|
||||
"RECONFIGURE": "",
|
||||
"UPDATE_TWO_FACTOR": "",
|
||||
"UPDATE_TWO_FACTOR_MESSAGE": "",
|
||||
"UPDATE": "",
|
||||
"DISABLE_TWO_FACTOR": "",
|
||||
"DISABLE_TWO_FACTOR_MESSAGE": "",
|
||||
"TWO_FACTOR_DISABLE_FAILED": "",
|
||||
"EXPORT_DATA": "",
|
||||
"SELECT_FOLDER": "",
|
||||
"DESTINATION": "",
|
||||
"START": "",
|
||||
"LAST_EXPORT_TIME": "",
|
||||
"EXPORT_AGAIN": "",
|
||||
"LOCAL_STORAGE_NOT_ACCESSIBLE": "",
|
||||
"LOCAL_STORAGE_NOT_ACCESSIBLE_MESSAGE": "",
|
||||
"SEND_OTT": "",
|
||||
"EMAIl_ALREADY_OWNED": "",
|
||||
"ETAGS_BLOCKED": "",
|
||||
"SKIPPED_VIDEOS_INFO": "",
|
||||
"LIVE_PHOTOS_DETECTED": "",
|
||||
"RETRY_FAILED": "",
|
||||
"FAILED_UPLOADS": "",
|
||||
"SKIPPED_FILES": "",
|
||||
"THUMBNAIL_GENERATION_FAILED_UPLOADS": "",
|
||||
"UNSUPPORTED_FILES": "",
|
||||
"SUCCESSFUL_UPLOADS": "",
|
||||
"SKIPPED_INFO": "",
|
||||
"UNSUPPORTED_INFO": "",
|
||||
"BLOCKED_UPLOADS": "",
|
||||
"SKIPPED_VIDEOS": "",
|
||||
"INPROGRESS_METADATA_EXTRACTION": "",
|
||||
"INPROGRESS_UPLOADS": "",
|
||||
"TOO_LARGE_UPLOADS": "",
|
||||
"LARGER_THAN_AVAILABLE_STORAGE_UPLOADS": "",
|
||||
"LARGER_THAN_AVAILABLE_STORAGE_INFO": "",
|
||||
"TOO_LARGE_INFO": "",
|
||||
"THUMBNAIL_GENERATION_FAILED_INFO": "",
|
||||
"UPLOAD_TO_COLLECTION": "",
|
||||
"UNCATEGORIZED": "",
|
||||
"ARCHIVE": "",
|
||||
"FAVORITES": "",
|
||||
"ARCHIVE_COLLECTION": "",
|
||||
"ARCHIVE_SECTION_NAME": "",
|
||||
"ALL_SECTION_NAME": "",
|
||||
"MOVE_TO_COLLECTION": "",
|
||||
"UNARCHIVE": "",
|
||||
"UNARCHIVE_COLLECTION": "",
|
||||
"HIDE_COLLECTION": "",
|
||||
"UNHIDE_COLLECTION": "",
|
||||
"MOVE": "",
|
||||
"ADD": "",
|
||||
"REMOVE": "",
|
||||
"YES_REMOVE": "",
|
||||
"REMOVE_FROM_COLLECTION": "",
|
||||
"TRASH": "",
|
||||
"MOVE_TO_TRASH": "",
|
||||
"TRASH_FILES_MESSAGE": "",
|
||||
"TRASH_FILE_MESSAGE": "",
|
||||
"DELETE_PERMANENTLY": "",
|
||||
"RESTORE": "",
|
||||
"RESTORE_TO_COLLECTION": "",
|
||||
"EMPTY_TRASH": "",
|
||||
"EMPTY_TRASH_TITLE": "",
|
||||
"EMPTY_TRASH_MESSAGE": "",
|
||||
"LEAVE_SHARED_ALBUM": "",
|
||||
"LEAVE_ALBUM": "",
|
||||
"LEAVE_SHARED_ALBUM_TITLE": "",
|
||||
"LEAVE_SHARED_ALBUM_MESSAGE": "",
|
||||
"NOT_FILE_OWNER": "",
|
||||
"CONFIRM_SELF_REMOVE_MESSAGE": "",
|
||||
"CONFIRM_SELF_AND_OTHER_REMOVE_MESSAGE": "",
|
||||
"SORT_BY_CREATION_TIME_ASCENDING": "",
|
||||
"SORT_BY_UPDATION_TIME_DESCENDING": "",
|
||||
"SORT_BY_NAME": "",
|
||||
"COMPRESS_THUMBNAILS": "",
|
||||
"THUMBNAIL_REPLACED": "",
|
||||
"FIX_THUMBNAIL": "",
|
||||
"FIX_THUMBNAIL_LATER": "",
|
||||
"REPLACE_THUMBNAIL_NOT_STARTED": "",
|
||||
"REPLACE_THUMBNAIL_COMPLETED": "",
|
||||
"REPLACE_THUMBNAIL_NOOP": "",
|
||||
"REPLACE_THUMBNAIL_COMPLETED_WITH_ERROR": "",
|
||||
"FIX_CREATION_TIME": "",
|
||||
"FIX_CREATION_TIME_IN_PROGRESS": "",
|
||||
"CREATION_TIME_UPDATED": "",
|
||||
"UPDATE_CREATION_TIME_NOT_STARTED": "",
|
||||
"UPDATE_CREATION_TIME_COMPLETED": "",
|
||||
"UPDATE_CREATION_TIME_COMPLETED_WITH_ERROR": "",
|
||||
"CAPTION_CHARACTER_LIMIT": "",
|
||||
"DATE_TIME_ORIGINAL": "",
|
||||
"DATE_TIME_DIGITIZED": "",
|
||||
"METADATA_DATE": "",
|
||||
"CUSTOM_TIME": "",
|
||||
"REOPEN_PLAN_SELECTOR_MODAL": "",
|
||||
"OPEN_PLAN_SELECTOR_MODAL_FAILED": "",
|
||||
"INSTALL": "",
|
||||
"SHARING_DETAILS": "",
|
||||
"MODIFY_SHARING": "",
|
||||
"ADD_COLLABORATORS": "",
|
||||
"ADD_NEW_EMAIL": "",
|
||||
"shared_with_people_zero": "",
|
||||
"shared_with_people_one": "",
|
||||
"shared_with_people_other": "",
|
||||
"participants_zero": "",
|
||||
"participants_one": "",
|
||||
"participants_other": "",
|
||||
"ADD_VIEWERS": "",
|
||||
"PARTICIPANTS": "",
|
||||
"CHANGE_PERMISSIONS_TO_VIEWER": "",
|
||||
"CHANGE_PERMISSIONS_TO_COLLABORATOR": "",
|
||||
"CONVERT_TO_VIEWER": "",
|
||||
"CONVERT_TO_COLLABORATOR": "",
|
||||
"CHANGE_PERMISSION": "",
|
||||
"REMOVE_PARTICIPANT": "",
|
||||
"CONFIRM_REMOVE": "",
|
||||
"MANAGE": "",
|
||||
"ADDED_AS": "",
|
||||
"COLLABORATOR_RIGHTS": "",
|
||||
"REMOVE_PARTICIPANT_HEAD": "",
|
||||
"OWNER": "",
|
||||
"COLLABORATORS": "",
|
||||
"ADD_MORE": "",
|
||||
"VIEWERS": "",
|
||||
"OR_ADD_EXISTING": "",
|
||||
"REMOVE_PARTICIPANT_MESSAGE": "",
|
||||
"NOT_FOUND": "",
|
||||
"LINK_EXPIRED": "",
|
||||
"LINK_EXPIRED_MESSAGE": "",
|
||||
"MANAGE_LINK": "",
|
||||
"LINK_TOO_MANY_REQUESTS": "",
|
||||
"FILE_DOWNLOAD": "",
|
||||
"LINK_PASSWORD_LOCK": "",
|
||||
"PUBLIC_COLLECT": "",
|
||||
"LINK_DEVICE_LIMIT": "",
|
||||
"NO_DEVICE_LIMIT": "",
|
||||
"LINK_EXPIRY": "",
|
||||
"NEVER": "",
|
||||
"DISABLE_FILE_DOWNLOAD": "",
|
||||
"DISABLE_FILE_DOWNLOAD_MESSAGE": "",
|
||||
"MALICIOUS_CONTENT": "",
|
||||
"COPYRIGHT": "",
|
||||
"SHARED_USING": "",
|
||||
"ENTE_IO": "",
|
||||
"SHARING_REFERRAL_CODE": "",
|
||||
"LIVE": "",
|
||||
"DISABLE_PASSWORD": "",
|
||||
"DISABLE_PASSWORD_MESSAGE": "",
|
||||
"PASSWORD_LOCK": "",
|
||||
"LOCK": "",
|
||||
"DOWNLOAD_UPLOAD_LOGS": "",
|
||||
"UPLOAD_FILES": "",
|
||||
"UPLOAD_DIRS": "",
|
||||
"UPLOAD_GOOGLE_TAKEOUT": "",
|
||||
"DEDUPLICATE_FILES": "",
|
||||
"AUTHENTICATOR_SECTION": "",
|
||||
"NO_DUPLICATES_FOUND": "",
|
||||
"CLUB_BY_CAPTURE_TIME": "",
|
||||
"FILES": "",
|
||||
"EACH": "",
|
||||
"DEDUPLICATE_BASED_ON_SIZE": "",
|
||||
"STOP_ALL_UPLOADS_MESSAGE": "",
|
||||
"STOP_UPLOADS_HEADER": "",
|
||||
"YES_STOP_UPLOADS": "",
|
||||
"STOP_DOWNLOADS_HEADER": "",
|
||||
"YES_STOP_DOWNLOADS": "",
|
||||
"STOP_ALL_DOWNLOADS_MESSAGE": "",
|
||||
"albums_one": "",
|
||||
"albums_other": "",
|
||||
"ALL_ALBUMS": "",
|
||||
"ALBUMS": "",
|
||||
"ALL_HIDDEN_ALBUMS": "",
|
||||
"HIDDEN_ALBUMS": "",
|
||||
"HIDDEN_ITEMS": "",
|
||||
"HIDDEN_ITEMS_SECTION_NAME": "",
|
||||
"ENTER_TWO_FACTOR_OTP": "",
|
||||
"CREATE_ACCOUNT": "",
|
||||
"COPIED": "",
|
||||
"CANVAS_BLOCKED_TITLE": "",
|
||||
"CANVAS_BLOCKED_MESSAGE": "",
|
||||
"WATCH_FOLDERS": "",
|
||||
"UPGRADE_NOW": "",
|
||||
"RENEW_NOW": "",
|
||||
"STORAGE": "",
|
||||
"USED": "",
|
||||
"YOU": "",
|
||||
"FAMILY": "",
|
||||
"FREE": "",
|
||||
"OF": "",
|
||||
"WATCHED_FOLDERS": "",
|
||||
"NO_FOLDERS_ADDED": "",
|
||||
"FOLDERS_AUTOMATICALLY_MONITORED": "",
|
||||
"UPLOAD_NEW_FILES_TO_ENTE": "",
|
||||
"REMOVE_DELETED_FILES_FROM_ENTE": "",
|
||||
"ADD_FOLDER": "",
|
||||
"STOP_WATCHING": "",
|
||||
"STOP_WATCHING_FOLDER": "",
|
||||
"STOP_WATCHING_DIALOG_MESSAGE": "",
|
||||
"YES_STOP": "",
|
||||
"MONTH_SHORT": "",
|
||||
"YEAR": "",
|
||||
"FAMILY_PLAN": "",
|
||||
"DOWNLOAD_LOGS": "",
|
||||
"DOWNLOAD_LOGS_MESSAGE": "",
|
||||
"CHANGE_FOLDER": "",
|
||||
"TWO_MONTHS_FREE": "",
|
||||
"GB": "",
|
||||
"POPULAR": "",
|
||||
"FREE_PLAN_OPTION_LABEL": "",
|
||||
"FREE_PLAN_DESCRIPTION": "",
|
||||
"CURRENT_USAGE": "",
|
||||
"WEAK_DEVICE": "",
|
||||
"DRAG_AND_DROP_HINT": "",
|
||||
"CONFIRM_ACCOUNT_DELETION_MESSAGE": "",
|
||||
"AUTHENTICATE": "",
|
||||
"UPLOADED_TO_SINGLE_COLLECTION": "",
|
||||
"UPLOADED_TO_SEPARATE_COLLECTIONS": "",
|
||||
"NEVERMIND": "",
|
||||
"UPDATE_AVAILABLE": "",
|
||||
"UPDATE_INSTALLABLE_MESSAGE": "",
|
||||
"INSTALL_NOW": "",
|
||||
"INSTALL_ON_NEXT_LAUNCH": "",
|
||||
"UPDATE_AVAILABLE_MESSAGE": "",
|
||||
"DOWNLOAD_AND_INSTALL": "",
|
||||
"IGNORE_THIS_VERSION": "",
|
||||
"TODAY": "",
|
||||
"YESTERDAY": "",
|
||||
"NAME_PLACEHOLDER": "",
|
||||
"ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED": "",
|
||||
"ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED_MESSAGE": "",
|
||||
"CHOSE_THEME": "",
|
||||
"ML_SEARCH": "",
|
||||
"ENABLE_ML_SEARCH_DESCRIPTION": "",
|
||||
"ML_MORE_DETAILS": "",
|
||||
"ENABLE_FACE_SEARCH": "",
|
||||
"ENABLE_FACE_SEARCH_TITLE": "",
|
||||
"ENABLE_FACE_SEARCH_DESCRIPTION": "",
|
||||
"DISABLE_BETA": "",
|
||||
"DISABLE_FACE_SEARCH": "",
|
||||
"DISABLE_FACE_SEARCH_TITLE": "",
|
||||
"DISABLE_FACE_SEARCH_DESCRIPTION": "",
|
||||
"ADVANCED": "",
|
||||
"FACE_SEARCH_CONFIRMATION": "",
|
||||
"LABS": "",
|
||||
"YOURS": "",
|
||||
"PASSPHRASE_STRENGTH_WEAK": "",
|
||||
"PASSPHRASE_STRENGTH_MODERATE": "",
|
||||
"PASSPHRASE_STRENGTH_STRONG": "",
|
||||
"PREFERENCES": "",
|
||||
"LANGUAGE": "",
|
||||
"EXPORT_DIRECTORY_DOES_NOT_EXIST": "",
|
||||
"EXPORT_DIRECTORY_DOES_NOT_EXIST_MESSAGE": "",
|
||||
"SUBSCRIPTION_VERIFICATION_ERROR": "",
|
||||
"STORAGE_UNITS": {
|
||||
"B": "",
|
||||
"KB": "",
|
||||
"MB": "",
|
||||
"GB": "",
|
||||
"TB": ""
|
||||
},
|
||||
"AFTER_TIME": {
|
||||
"HOUR": "",
|
||||
"DAY": "",
|
||||
"WEEK": "",
|
||||
"MONTH": "",
|
||||
"YEAR": ""
|
||||
},
|
||||
"COPY_LINK": "",
|
||||
"DONE": "",
|
||||
"LINK_SHARE_TITLE": "",
|
||||
"REMOVE_LINK": "",
|
||||
"CREATE_PUBLIC_SHARING": "",
|
||||
"PUBLIC_LINK_CREATED": "",
|
||||
"PUBLIC_LINK_ENABLED": "",
|
||||
"COLLECT_PHOTOS": "",
|
||||
"PUBLIC_COLLECT_SUBTEXT": "",
|
||||
"STOP_EXPORT": "",
|
||||
"EXPORT_PROGRESS": "",
|
||||
"MIGRATING_EXPORT": "",
|
||||
"RENAMING_COLLECTION_FOLDERS": "",
|
||||
"TRASHING_DELETED_FILES": "",
|
||||
"TRASHING_DELETED_COLLECTIONS": "",
|
||||
"EXPORT_NOTIFICATION": {
|
||||
"START": "",
|
||||
"IN_PROGRESS": "",
|
||||
"FINISH": "",
|
||||
"UP_TO_DATE": ""
|
||||
},
|
||||
"CONTINUOUS_EXPORT": "",
|
||||
"TOTAL_ITEMS": "",
|
||||
"PENDING_ITEMS": "",
|
||||
"EXPORT_STARTING": "",
|
||||
"DELETE_ACCOUNT_REASON_LABEL": "",
|
||||
"DELETE_ACCOUNT_REASON_PLACEHOLDER": "",
|
||||
"DELETE_REASON": {
|
||||
"MISSING_FEATURE": "",
|
||||
"BROKEN_BEHAVIOR": "",
|
||||
"FOUND_ANOTHER_SERVICE": "",
|
||||
"NOT_LISTED": ""
|
||||
},
|
||||
"DELETE_ACCOUNT_FEEDBACK_LABEL": "",
|
||||
"DELETE_ACCOUNT_FEEDBACK_PLACEHOLDER": "",
|
||||
"CONFIRM_DELETE_ACCOUNT_CHECKBOX_LABEL": "",
|
||||
"CONFIRM_DELETE_ACCOUNT": "",
|
||||
"FEEDBACK_REQUIRED": "",
|
||||
"FEEDBACK_REQUIRED_FOUND_ANOTHER_SERVICE": "",
|
||||
"RECOVER_TWO_FACTOR": "",
|
||||
"at": "",
|
||||
"AUTH_NEXT": "",
|
||||
"AUTH_DOWNLOAD_MOBILE_APP": "",
|
||||
"HIDDEN": "",
|
||||
"HIDE": "",
|
||||
"UNHIDE": "",
|
||||
"UNHIDE_TO_COLLECTION": "",
|
||||
"SORT_BY": "",
|
||||
"NEWEST_FIRST": "",
|
||||
"OLDEST_FIRST": "",
|
||||
"CONVERSION_FAILED_NOTIFICATION_MESSAGE": "",
|
||||
"SELECT_COLLECTION": "",
|
||||
"PIN_ALBUM": "",
|
||||
"UNPIN_ALBUM": "",
|
||||
"DOWNLOAD_COMPLETE": "",
|
||||
"DOWNLOADING_COLLECTION": "",
|
||||
"DOWNLOAD_FAILED": "",
|
||||
"DOWNLOAD_PROGRESS": "",
|
||||
"CRASH_REPORTING": "",
|
||||
"CHRISTMAS": "",
|
||||
"CHRISTMAS_EVE": "",
|
||||
"NEW_YEAR": "",
|
||||
"NEW_YEAR_EVE": "",
|
||||
"IMAGE": "",
|
||||
"VIDEO": "",
|
||||
"LIVE_PHOTO": "",
|
||||
"CONVERT": "",
|
||||
"CONFIRM_EDITOR_CLOSE_MESSAGE": "",
|
||||
"CONFIRM_EDITOR_CLOSE_DESCRIPTION": "",
|
||||
"BRIGHTNESS": "",
|
||||
"CONTRAST": "",
|
||||
"SATURATION": "",
|
||||
"BLUR": "",
|
||||
"INVERT_COLORS": "",
|
||||
"ASPECT_RATIO": "",
|
||||
"SQUARE": "",
|
||||
"ROTATE_LEFT": "",
|
||||
"ROTATE_RIGHT": "",
|
||||
"FLIP_VERTICALLY": "",
|
||||
"FLIP_HORIZONTALLY": "",
|
||||
"DOWNLOAD_EDITED": "",
|
||||
"SAVE_A_COPY_TO_ENTE": "",
|
||||
"RESTORE_ORIGINAL": "",
|
||||
"TRANSFORM": "",
|
||||
"COLORS": "",
|
||||
"FLIP": "",
|
||||
"ROTATION": "",
|
||||
"RESET": "",
|
||||
"PHOTO_EDITOR": "",
|
||||
"FASTER_UPLOAD": "",
|
||||
"FASTER_UPLOAD_DESCRIPTION": "",
|
||||
"MAGIC_SEARCH_STATUS": "",
|
||||
"INDEXED_ITEMS": "",
|
||||
"CAST_ALBUM_TO_TV": "",
|
||||
"ENTER_CAST_PIN_CODE": "",
|
||||
"PAIR_DEVICE_TO_TV": "",
|
||||
"TV_NOT_FOUND": "",
|
||||
"AUTO_CAST_PAIR": "",
|
||||
"AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "",
|
||||
"PAIR_WITH_PIN": "",
|
||||
"CHOOSE_DEVICE_FROM_BROWSER": "",
|
||||
"PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "",
|
||||
"VISIT_CAST_ENTE_IO": "",
|
||||
"CAST_AUTO_PAIR_FAILED": "",
|
||||
"CACHE_DIRECTORY": "",
|
||||
"PASSKEYS": "",
|
||||
"FREEHAND": "",
|
||||
"APPLY_CROP": "",
|
||||
"PHOTO_EDIT_REQUIRED_TO_SAVE": ""
|
||||
}
|
644
web/apps/photos/public/locales/th-TH/translation.json
Normal file
644
web/apps/photos/public/locales/th-TH/translation.json
Normal file
|
@ -0,0 +1,644 @@
|
|||
{
|
||||
"HERO_SLIDE_1_TITLE": "",
|
||||
"HERO_SLIDE_1": "",
|
||||
"HERO_SLIDE_2_TITLE": "",
|
||||
"HERO_SLIDE_2": "",
|
||||
"HERO_SLIDE_3_TITLE": "",
|
||||
"HERO_SLIDE_3": "",
|
||||
"LOGIN": "",
|
||||
"SIGN_UP": "",
|
||||
"NEW_USER": "",
|
||||
"EXISTING_USER": "",
|
||||
"ENTER_NAME": "",
|
||||
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
|
||||
"ENTER_EMAIL": "",
|
||||
"EMAIL_ERROR": "",
|
||||
"REQUIRED": "",
|
||||
"EMAIL_SENT": "",
|
||||
"CHECK_INBOX": "",
|
||||
"ENTER_OTT": "",
|
||||
"RESEND_MAIL": "",
|
||||
"VERIFY": "",
|
||||
"UNKNOWN_ERROR": "",
|
||||
"INVALID_CODE": "",
|
||||
"EXPIRED_CODE": "",
|
||||
"SENDING": "",
|
||||
"SENT": "",
|
||||
"PASSWORD": "",
|
||||
"LINK_PASSWORD": "",
|
||||
"RETURN_PASSPHRASE_HINT": "",
|
||||
"SET_PASSPHRASE": "",
|
||||
"VERIFY_PASSPHRASE": "",
|
||||
"INCORRECT_PASSPHRASE": "",
|
||||
"ENTER_ENC_PASSPHRASE": "",
|
||||
"PASSPHRASE_DISCLAIMER": "",
|
||||
"WELCOME_TO_ENTE_HEADING": "",
|
||||
"WELCOME_TO_ENTE_SUBHEADING": "",
|
||||
"WHERE_YOUR_BEST_PHOTOS_LIVE": "",
|
||||
"KEY_GENERATION_IN_PROGRESS_MESSAGE": "",
|
||||
"PASSPHRASE_HINT": "",
|
||||
"CONFIRM_PASSPHRASE": "",
|
||||
"REFERRAL_CODE_HINT": "",
|
||||
"REFERRAL_INFO": "",
|
||||
"PASSPHRASE_MATCH_ERROR": "",
|
||||
"CONSOLE_WARNING_STOP": "",
|
||||
"CONSOLE_WARNING_DESC": "",
|
||||
"CREATE_COLLECTION": "",
|
||||
"ENTER_ALBUM_NAME": "",
|
||||
"CLOSE_OPTION": "",
|
||||
"ENTER_FILE_NAME": "",
|
||||
"CLOSE": "",
|
||||
"NO": "",
|
||||
"NOTHING_HERE": "",
|
||||
"UPLOAD": "",
|
||||
"IMPORT": "",
|
||||
"ADD_PHOTOS": "",
|
||||
"ADD_MORE_PHOTOS": "",
|
||||
"add_photos_one": "",
|
||||
"add_photos_other": "",
|
||||
"SELECT_PHOTOS": "",
|
||||
"FILE_UPLOAD": "",
|
||||
"UPLOAD_STAGE_MESSAGE": {
|
||||
"0": "",
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": ""
|
||||
},
|
||||
"FILE_NOT_UPLOADED_LIST": "",
|
||||
"SUBSCRIPTION_EXPIRED": "",
|
||||
"SUBSCRIPTION_EXPIRED_MESSAGE": "",
|
||||
"STORAGE_QUOTA_EXCEEDED": "",
|
||||
"INITIAL_LOAD_DELAY_WARNING": "",
|
||||
"USER_DOES_NOT_EXIST": "",
|
||||
"NO_ACCOUNT": "",
|
||||
"ACCOUNT_EXISTS": "",
|
||||
"CREATE": "",
|
||||
"DOWNLOAD": "",
|
||||
"DOWNLOAD_OPTION": "",
|
||||
"DOWNLOAD_FAVORITES": "",
|
||||
"DOWNLOAD_UNCATEGORIZED": "",
|
||||
"DOWNLOAD_HIDDEN_ITEMS": "",
|
||||
"COPY_OPTION": "",
|
||||
"TOGGLE_FULLSCREEN": "",
|
||||
"ZOOM_IN_OUT": "",
|
||||
"PREVIOUS": "",
|
||||
"NEXT": "",
|
||||
"TITLE_PHOTOS": "",
|
||||
"TITLE_ALBUMS": "",
|
||||
"TITLE_AUTH": "",
|
||||
"UPLOAD_FIRST_PHOTO": "",
|
||||
"IMPORT_YOUR_FOLDERS": "",
|
||||
"UPLOAD_DROPZONE_MESSAGE": "",
|
||||
"WATCH_FOLDER_DROPZONE_MESSAGE": "",
|
||||
"TRASH_FILES_TITLE": "",
|
||||
"TRASH_FILE_TITLE": "",
|
||||
"DELETE_FILES_TITLE": "",
|
||||
"DELETE_FILES_MESSAGE": "",
|
||||
"DELETE": "",
|
||||
"DELETE_OPTION": "",
|
||||
"FAVORITE_OPTION": "",
|
||||
"UNFAVORITE_OPTION": "",
|
||||
"MULTI_FOLDER_UPLOAD": "",
|
||||
"UPLOAD_STRATEGY_CHOICE": "",
|
||||
"UPLOAD_STRATEGY_SINGLE_COLLECTION": "",
|
||||
"OR": "",
|
||||
"UPLOAD_STRATEGY_COLLECTION_PER_FOLDER": "",
|
||||
"SESSION_EXPIRED_MESSAGE": "",
|
||||
"SESSION_EXPIRED": "",
|
||||
"PASSWORD_GENERATION_FAILED": "",
|
||||
"CHANGE_PASSWORD": "",
|
||||
"GO_BACK": "",
|
||||
"RECOVERY_KEY": "",
|
||||
"SAVE_LATER": "",
|
||||
"SAVE": "",
|
||||
"RECOVERY_KEY_DESCRIPTION": "",
|
||||
"RECOVER_KEY_GENERATION_FAILED": "",
|
||||
"KEY_NOT_STORED_DISCLAIMER": "",
|
||||
"FORGOT_PASSWORD": "",
|
||||
"RECOVER_ACCOUNT": "",
|
||||
"RECOVERY_KEY_HINT": "",
|
||||
"RECOVER": "",
|
||||
"NO_RECOVERY_KEY": "",
|
||||
"INCORRECT_RECOVERY_KEY": "",
|
||||
"SORRY": "",
|
||||
"NO_RECOVERY_KEY_MESSAGE": "",
|
||||
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
|
||||
"CONTACT_SUPPORT": "",
|
||||
"REQUEST_FEATURE": "",
|
||||
"SUPPORT": "",
|
||||
"CONFIRM": "",
|
||||
"CANCEL": "",
|
||||
"LOGOUT": "",
|
||||
"DELETE_ACCOUNT": "",
|
||||
"DELETE_ACCOUNT_MESSAGE": "",
|
||||
"LOGOUT_MESSAGE": "",
|
||||
"CHANGE_EMAIL": "",
|
||||
"OK": "",
|
||||
"SUCCESS": "",
|
||||
"ERROR": "",
|
||||
"MESSAGE": "",
|
||||
"INSTALL_MOBILE_APP": "",
|
||||
"DOWNLOAD_APP_MESSAGE": "",
|
||||
"DOWNLOAD_APP": "",
|
||||
"EXPORT": "",
|
||||
"SUBSCRIPTION": "",
|
||||
"SUBSCRIBE": "",
|
||||
"MANAGEMENT_PORTAL": "",
|
||||
"MANAGE_FAMILY_PORTAL": "",
|
||||
"LEAVE_FAMILY_PLAN": "",
|
||||
"LEAVE": "",
|
||||
"LEAVE_FAMILY_CONFIRM": "",
|
||||
"CHOOSE_PLAN": "",
|
||||
"MANAGE_PLAN": "",
|
||||
"ACTIVE": "",
|
||||
"OFFLINE_MSG": "",
|
||||
"FREE_SUBSCRIPTION_INFO": "",
|
||||
"FAMILY_SUBSCRIPTION_INFO": "",
|
||||
"RENEWAL_ACTIVE_SUBSCRIPTION_STATUS": "",
|
||||
"RENEWAL_CANCELLED_SUBSCRIPTION_STATUS": "",
|
||||
"RENEWAL_CANCELLED_SUBSCRIPTION_INFO": "",
|
||||
"ADD_ON_AVAILABLE_TILL": "",
|
||||
"STORAGE_QUOTA_EXCEEDED_SUBSCRIPTION_INFO": "",
|
||||
"SUBSCRIPTION_PURCHASE_SUCCESS": "",
|
||||
"SUBSCRIPTION_PURCHASE_CANCELLED": "",
|
||||
"SUBSCRIPTION_PURCHASE_FAILED": "",
|
||||
"SUBSCRIPTION_UPDATE_FAILED": "",
|
||||
"UPDATE_PAYMENT_METHOD_MESSAGE": "",
|
||||
"STRIPE_AUTHENTICATION_FAILED": "",
|
||||
"UPDATE_PAYMENT_METHOD": "",
|
||||
"MONTHLY": "",
|
||||
"YEARLY": "",
|
||||
"UPDATE_SUBSCRIPTION_MESSAGE": "",
|
||||
"UPDATE_SUBSCRIPTION": "",
|
||||
"CANCEL_SUBSCRIPTION": "",
|
||||
"CANCEL_SUBSCRIPTION_MESSAGE": "",
|
||||
"CANCEL_SUBSCRIPTION_WITH_ADDON_MESSAGE": "",
|
||||
"SUBSCRIPTION_CANCEL_FAILED": "",
|
||||
"SUBSCRIPTION_CANCEL_SUCCESS": "",
|
||||
"REACTIVATE_SUBSCRIPTION": "",
|
||||
"REACTIVATE_SUBSCRIPTION_MESSAGE": "",
|
||||
"SUBSCRIPTION_ACTIVATE_SUCCESS": "",
|
||||
"SUBSCRIPTION_ACTIVATE_FAILED": "",
|
||||
"SUBSCRIPTION_PURCHASE_SUCCESS_TITLE": "",
|
||||
"CANCEL_SUBSCRIPTION_ON_MOBILE": "",
|
||||
"CANCEL_SUBSCRIPTION_ON_MOBILE_MESSAGE": "",
|
||||
"MAIL_TO_MANAGE_SUBSCRIPTION": "",
|
||||
"RENAME": "",
|
||||
"RENAME_FILE": "",
|
||||
"RENAME_COLLECTION": "",
|
||||
"DELETE_COLLECTION_TITLE": "",
|
||||
"DELETE_COLLECTION": "",
|
||||
"DELETE_COLLECTION_MESSAGE": "",
|
||||
"DELETE_PHOTOS": "",
|
||||
"KEEP_PHOTOS": "",
|
||||
"SHARE": "",
|
||||
"SHARE_COLLECTION": "",
|
||||
"SHAREES": "",
|
||||
"SHARE_WITH_SELF": "",
|
||||
"ALREADY_SHARED": "",
|
||||
"SHARING_BAD_REQUEST_ERROR": "",
|
||||
"SHARING_DISABLED_FOR_FREE_ACCOUNTS": "",
|
||||
"DOWNLOAD_COLLECTION": "",
|
||||
"DOWNLOAD_COLLECTION_MESSAGE": "",
|
||||
"CREATE_ALBUM_FAILED": "",
|
||||
"SEARCH": "",
|
||||
"SEARCH_RESULTS": "",
|
||||
"NO_RESULTS": "",
|
||||
"SEARCH_HINT": "",
|
||||
"SEARCH_TYPE": {
|
||||
"COLLECTION": "",
|
||||
"LOCATION": "",
|
||||
"CITY": "",
|
||||
"DATE": "",
|
||||
"FILE_NAME": "",
|
||||
"THING": "",
|
||||
"FILE_CAPTION": "",
|
||||
"FILE_TYPE": "",
|
||||
"CLIP": ""
|
||||
},
|
||||
"photos_count_zero": "",
|
||||
"photos_count_one": "",
|
||||
"photos_count_other": "",
|
||||
"TERMS_AND_CONDITIONS": "",
|
||||
"ADD_TO_COLLECTION": "",
|
||||
"SELECTED": "",
|
||||
"VIDEO_PLAYBACK_FAILED_DOWNLOAD_INSTEAD": "",
|
||||
"PEOPLE": "",
|
||||
"INDEXING_SCHEDULED": "",
|
||||
"ANALYZING_PHOTOS": "",
|
||||
"INDEXING_PEOPLE": "",
|
||||
"INDEXING_DONE": "",
|
||||
"UNIDENTIFIED_FACES": "",
|
||||
"OBJECTS": "",
|
||||
"TEXT": "",
|
||||
"INFO": "",
|
||||
"INFO_OPTION": "",
|
||||
"FILE_NAME": "",
|
||||
"CAPTION_PLACEHOLDER": "",
|
||||
"LOCATION": "",
|
||||
"SHOW_ON_MAP": "",
|
||||
"MAP": "",
|
||||
"MAP_SETTINGS": "",
|
||||
"ENABLE_MAPS": "",
|
||||
"ENABLE_MAP": "",
|
||||
"DISABLE_MAPS": "",
|
||||
"ENABLE_MAP_DESCRIPTION": "",
|
||||
"DISABLE_MAP_DESCRIPTION": "",
|
||||
"DISABLE_MAP": "",
|
||||
"DETAILS": "",
|
||||
"VIEW_EXIF": "",
|
||||
"NO_EXIF": "",
|
||||
"EXIF": "",
|
||||
"ISO": "",
|
||||
"TWO_FACTOR": "",
|
||||
"TWO_FACTOR_AUTHENTICATION": "",
|
||||
"TWO_FACTOR_QR_INSTRUCTION": "",
|
||||
"ENTER_CODE_MANUALLY": "",
|
||||
"TWO_FACTOR_MANUAL_CODE_INSTRUCTION": "",
|
||||
"SCAN_QR_CODE": "",
|
||||
"ENABLE_TWO_FACTOR": "",
|
||||
"ENABLE": "",
|
||||
"LOST_DEVICE": "",
|
||||
"INCORRECT_CODE": "",
|
||||
"TWO_FACTOR_INFO": "",
|
||||
"DISABLE_TWO_FACTOR_LABEL": "",
|
||||
"UPDATE_TWO_FACTOR_LABEL": "",
|
||||
"DISABLE": "",
|
||||
"RECONFIGURE": "",
|
||||
"UPDATE_TWO_FACTOR": "",
|
||||
"UPDATE_TWO_FACTOR_MESSAGE": "",
|
||||
"UPDATE": "",
|
||||
"DISABLE_TWO_FACTOR": "",
|
||||
"DISABLE_TWO_FACTOR_MESSAGE": "",
|
||||
"TWO_FACTOR_DISABLE_FAILED": "",
|
||||
"EXPORT_DATA": "",
|
||||
"SELECT_FOLDER": "",
|
||||
"DESTINATION": "",
|
||||
"START": "",
|
||||
"LAST_EXPORT_TIME": "",
|
||||
"EXPORT_AGAIN": "",
|
||||
"LOCAL_STORAGE_NOT_ACCESSIBLE": "",
|
||||
"LOCAL_STORAGE_NOT_ACCESSIBLE_MESSAGE": "",
|
||||
"SEND_OTT": "",
|
||||
"EMAIl_ALREADY_OWNED": "",
|
||||
"ETAGS_BLOCKED": "",
|
||||
"SKIPPED_VIDEOS_INFO": "",
|
||||
"LIVE_PHOTOS_DETECTED": "",
|
||||
"RETRY_FAILED": "",
|
||||
"FAILED_UPLOADS": "",
|
||||
"SKIPPED_FILES": "",
|
||||
"THUMBNAIL_GENERATION_FAILED_UPLOADS": "",
|
||||
"UNSUPPORTED_FILES": "",
|
||||
"SUCCESSFUL_UPLOADS": "",
|
||||
"SKIPPED_INFO": "",
|
||||
"UNSUPPORTED_INFO": "",
|
||||
"BLOCKED_UPLOADS": "",
|
||||
"SKIPPED_VIDEOS": "",
|
||||
"INPROGRESS_METADATA_EXTRACTION": "",
|
||||
"INPROGRESS_UPLOADS": "",
|
||||
"TOO_LARGE_UPLOADS": "",
|
||||
"LARGER_THAN_AVAILABLE_STORAGE_UPLOADS": "",
|
||||
"LARGER_THAN_AVAILABLE_STORAGE_INFO": "",
|
||||
"TOO_LARGE_INFO": "",
|
||||
"THUMBNAIL_GENERATION_FAILED_INFO": "",
|
||||
"UPLOAD_TO_COLLECTION": "",
|
||||
"UNCATEGORIZED": "",
|
||||
"ARCHIVE": "",
|
||||
"FAVORITES": "",
|
||||
"ARCHIVE_COLLECTION": "",
|
||||
"ARCHIVE_SECTION_NAME": "",
|
||||
"ALL_SECTION_NAME": "",
|
||||
"MOVE_TO_COLLECTION": "",
|
||||
"UNARCHIVE": "",
|
||||
"UNARCHIVE_COLLECTION": "",
|
||||
"HIDE_COLLECTION": "",
|
||||
"UNHIDE_COLLECTION": "",
|
||||
"MOVE": "",
|
||||
"ADD": "",
|
||||
"REMOVE": "",
|
||||
"YES_REMOVE": "",
|
||||
"REMOVE_FROM_COLLECTION": "",
|
||||
"TRASH": "",
|
||||
"MOVE_TO_TRASH": "",
|
||||
"TRASH_FILES_MESSAGE": "",
|
||||
"TRASH_FILE_MESSAGE": "",
|
||||
"DELETE_PERMANENTLY": "",
|
||||
"RESTORE": "",
|
||||
"RESTORE_TO_COLLECTION": "",
|
||||
"EMPTY_TRASH": "",
|
||||
"EMPTY_TRASH_TITLE": "",
|
||||
"EMPTY_TRASH_MESSAGE": "",
|
||||
"LEAVE_SHARED_ALBUM": "",
|
||||
"LEAVE_ALBUM": "",
|
||||
"LEAVE_SHARED_ALBUM_TITLE": "",
|
||||
"LEAVE_SHARED_ALBUM_MESSAGE": "",
|
||||
"NOT_FILE_OWNER": "",
|
||||
"CONFIRM_SELF_REMOVE_MESSAGE": "",
|
||||
"CONFIRM_SELF_AND_OTHER_REMOVE_MESSAGE": "",
|
||||
"SORT_BY_CREATION_TIME_ASCENDING": "",
|
||||
"SORT_BY_UPDATION_TIME_DESCENDING": "",
|
||||
"SORT_BY_NAME": "",
|
||||
"COMPRESS_THUMBNAILS": "",
|
||||
"THUMBNAIL_REPLACED": "",
|
||||
"FIX_THUMBNAIL": "",
|
||||
"FIX_THUMBNAIL_LATER": "",
|
||||
"REPLACE_THUMBNAIL_NOT_STARTED": "",
|
||||
"REPLACE_THUMBNAIL_COMPLETED": "",
|
||||
"REPLACE_THUMBNAIL_NOOP": "",
|
||||
"REPLACE_THUMBNAIL_COMPLETED_WITH_ERROR": "",
|
||||
"FIX_CREATION_TIME": "",
|
||||
"FIX_CREATION_TIME_IN_PROGRESS": "",
|
||||
"CREATION_TIME_UPDATED": "",
|
||||
"UPDATE_CREATION_TIME_NOT_STARTED": "",
|
||||
"UPDATE_CREATION_TIME_COMPLETED": "",
|
||||
"UPDATE_CREATION_TIME_COMPLETED_WITH_ERROR": "",
|
||||
"CAPTION_CHARACTER_LIMIT": "",
|
||||
"DATE_TIME_ORIGINAL": "",
|
||||
"DATE_TIME_DIGITIZED": "",
|
||||
"METADATA_DATE": "",
|
||||
"CUSTOM_TIME": "",
|
||||
"REOPEN_PLAN_SELECTOR_MODAL": "",
|
||||
"OPEN_PLAN_SELECTOR_MODAL_FAILED": "",
|
||||
"INSTALL": "",
|
||||
"SHARING_DETAILS": "",
|
||||
"MODIFY_SHARING": "",
|
||||
"ADD_COLLABORATORS": "",
|
||||
"ADD_NEW_EMAIL": "",
|
||||
"shared_with_people_zero": "",
|
||||
"shared_with_people_one": "",
|
||||
"shared_with_people_other": "",
|
||||
"participants_zero": "",
|
||||
"participants_one": "",
|
||||
"participants_other": "",
|
||||
"ADD_VIEWERS": "",
|
||||
"PARTICIPANTS": "",
|
||||
"CHANGE_PERMISSIONS_TO_VIEWER": "",
|
||||
"CHANGE_PERMISSIONS_TO_COLLABORATOR": "",
|
||||
"CONVERT_TO_VIEWER": "",
|
||||
"CONVERT_TO_COLLABORATOR": "",
|
||||
"CHANGE_PERMISSION": "",
|
||||
"REMOVE_PARTICIPANT": "",
|
||||
"CONFIRM_REMOVE": "",
|
||||
"MANAGE": "",
|
||||
"ADDED_AS": "",
|
||||
"COLLABORATOR_RIGHTS": "",
|
||||
"REMOVE_PARTICIPANT_HEAD": "",
|
||||
"OWNER": "",
|
||||
"COLLABORATORS": "",
|
||||
"ADD_MORE": "",
|
||||
"VIEWERS": "",
|
||||
"OR_ADD_EXISTING": "",
|
||||
"REMOVE_PARTICIPANT_MESSAGE": "",
|
||||
"NOT_FOUND": "",
|
||||
"LINK_EXPIRED": "",
|
||||
"LINK_EXPIRED_MESSAGE": "",
|
||||
"MANAGE_LINK": "",
|
||||
"LINK_TOO_MANY_REQUESTS": "",
|
||||
"FILE_DOWNLOAD": "",
|
||||
"LINK_PASSWORD_LOCK": "",
|
||||
"PUBLIC_COLLECT": "",
|
||||
"LINK_DEVICE_LIMIT": "",
|
||||
"NO_DEVICE_LIMIT": "",
|
||||
"LINK_EXPIRY": "",
|
||||
"NEVER": "",
|
||||
"DISABLE_FILE_DOWNLOAD": "",
|
||||
"DISABLE_FILE_DOWNLOAD_MESSAGE": "",
|
||||
"MALICIOUS_CONTENT": "",
|
||||
"COPYRIGHT": "",
|
||||
"SHARED_USING": "",
|
||||
"ENTE_IO": "",
|
||||
"SHARING_REFERRAL_CODE": "",
|
||||
"LIVE": "",
|
||||
"DISABLE_PASSWORD": "",
|
||||
"DISABLE_PASSWORD_MESSAGE": "",
|
||||
"PASSWORD_LOCK": "",
|
||||
"LOCK": "",
|
||||
"DOWNLOAD_UPLOAD_LOGS": "",
|
||||
"UPLOAD_FILES": "",
|
||||
"UPLOAD_DIRS": "",
|
||||
"UPLOAD_GOOGLE_TAKEOUT": "",
|
||||
"DEDUPLICATE_FILES": "",
|
||||
"AUTHENTICATOR_SECTION": "",
|
||||
"NO_DUPLICATES_FOUND": "",
|
||||
"CLUB_BY_CAPTURE_TIME": "",
|
||||
"FILES": "",
|
||||
"EACH": "",
|
||||
"DEDUPLICATE_BASED_ON_SIZE": "",
|
||||
"STOP_ALL_UPLOADS_MESSAGE": "",
|
||||
"STOP_UPLOADS_HEADER": "",
|
||||
"YES_STOP_UPLOADS": "",
|
||||
"STOP_DOWNLOADS_HEADER": "",
|
||||
"YES_STOP_DOWNLOADS": "",
|
||||
"STOP_ALL_DOWNLOADS_MESSAGE": "",
|
||||
"albums_one": "",
|
||||
"albums_other": "",
|
||||
"ALL_ALBUMS": "",
|
||||
"ALBUMS": "",
|
||||
"ALL_HIDDEN_ALBUMS": "",
|
||||
"HIDDEN_ALBUMS": "",
|
||||
"HIDDEN_ITEMS": "",
|
||||
"HIDDEN_ITEMS_SECTION_NAME": "",
|
||||
"ENTER_TWO_FACTOR_OTP": "",
|
||||
"CREATE_ACCOUNT": "",
|
||||
"COPIED": "",
|
||||
"CANVAS_BLOCKED_TITLE": "",
|
||||
"CANVAS_BLOCKED_MESSAGE": "",
|
||||
"WATCH_FOLDERS": "",
|
||||
"UPGRADE_NOW": "",
|
||||
"RENEW_NOW": "",
|
||||
"STORAGE": "",
|
||||
"USED": "",
|
||||
"YOU": "",
|
||||
"FAMILY": "",
|
||||
"FREE": "",
|
||||
"OF": "",
|
||||
"WATCHED_FOLDERS": "",
|
||||
"NO_FOLDERS_ADDED": "",
|
||||
"FOLDERS_AUTOMATICALLY_MONITORED": "",
|
||||
"UPLOAD_NEW_FILES_TO_ENTE": "",
|
||||
"REMOVE_DELETED_FILES_FROM_ENTE": "",
|
||||
"ADD_FOLDER": "",
|
||||
"STOP_WATCHING": "",
|
||||
"STOP_WATCHING_FOLDER": "",
|
||||
"STOP_WATCHING_DIALOG_MESSAGE": "",
|
||||
"YES_STOP": "",
|
||||
"MONTH_SHORT": "",
|
||||
"YEAR": "",
|
||||
"FAMILY_PLAN": "",
|
||||
"DOWNLOAD_LOGS": "",
|
||||
"DOWNLOAD_LOGS_MESSAGE": "",
|
||||
"CHANGE_FOLDER": "",
|
||||
"TWO_MONTHS_FREE": "",
|
||||
"GB": "",
|
||||
"POPULAR": "",
|
||||
"FREE_PLAN_OPTION_LABEL": "",
|
||||
"FREE_PLAN_DESCRIPTION": "",
|
||||
"CURRENT_USAGE": "",
|
||||
"WEAK_DEVICE": "",
|
||||
"DRAG_AND_DROP_HINT": "",
|
||||
"CONFIRM_ACCOUNT_DELETION_MESSAGE": "",
|
||||
"AUTHENTICATE": "",
|
||||
"UPLOADED_TO_SINGLE_COLLECTION": "",
|
||||
"UPLOADED_TO_SEPARATE_COLLECTIONS": "",
|
||||
"NEVERMIND": "",
|
||||
"UPDATE_AVAILABLE": "",
|
||||
"UPDATE_INSTALLABLE_MESSAGE": "",
|
||||
"INSTALL_NOW": "",
|
||||
"INSTALL_ON_NEXT_LAUNCH": "",
|
||||
"UPDATE_AVAILABLE_MESSAGE": "",
|
||||
"DOWNLOAD_AND_INSTALL": "",
|
||||
"IGNORE_THIS_VERSION": "",
|
||||
"TODAY": "",
|
||||
"YESTERDAY": "",
|
||||
"NAME_PLACEHOLDER": "",
|
||||
"ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED": "",
|
||||
"ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED_MESSAGE": "",
|
||||
"CHOSE_THEME": "",
|
||||
"ML_SEARCH": "",
|
||||
"ENABLE_ML_SEARCH_DESCRIPTION": "",
|
||||
"ML_MORE_DETAILS": "",
|
||||
"ENABLE_FACE_SEARCH": "",
|
||||
"ENABLE_FACE_SEARCH_TITLE": "",
|
||||
"ENABLE_FACE_SEARCH_DESCRIPTION": "",
|
||||
"DISABLE_BETA": "",
|
||||
"DISABLE_FACE_SEARCH": "",
|
||||
"DISABLE_FACE_SEARCH_TITLE": "",
|
||||
"DISABLE_FACE_SEARCH_DESCRIPTION": "",
|
||||
"ADVANCED": "",
|
||||
"FACE_SEARCH_CONFIRMATION": "",
|
||||
"LABS": "",
|
||||
"YOURS": "",
|
||||
"PASSPHRASE_STRENGTH_WEAK": "",
|
||||
"PASSPHRASE_STRENGTH_MODERATE": "",
|
||||
"PASSPHRASE_STRENGTH_STRONG": "",
|
||||
"PREFERENCES": "",
|
||||
"LANGUAGE": "",
|
||||
"EXPORT_DIRECTORY_DOES_NOT_EXIST": "",
|
||||
"EXPORT_DIRECTORY_DOES_NOT_EXIST_MESSAGE": "",
|
||||
"SUBSCRIPTION_VERIFICATION_ERROR": "",
|
||||
"STORAGE_UNITS": {
|
||||
"B": "",
|
||||
"KB": "",
|
||||
"MB": "",
|
||||
"GB": "",
|
||||
"TB": ""
|
||||
},
|
||||
"AFTER_TIME": {
|
||||
"HOUR": "",
|
||||
"DAY": "",
|
||||
"WEEK": "",
|
||||
"MONTH": "",
|
||||
"YEAR": ""
|
||||
},
|
||||
"COPY_LINK": "",
|
||||
"DONE": "",
|
||||
"LINK_SHARE_TITLE": "",
|
||||
"REMOVE_LINK": "",
|
||||
"CREATE_PUBLIC_SHARING": "",
|
||||
"PUBLIC_LINK_CREATED": "",
|
||||
"PUBLIC_LINK_ENABLED": "",
|
||||
"COLLECT_PHOTOS": "",
|
||||
"PUBLIC_COLLECT_SUBTEXT": "",
|
||||
"STOP_EXPORT": "",
|
||||
"EXPORT_PROGRESS": "",
|
||||
"MIGRATING_EXPORT": "",
|
||||
"RENAMING_COLLECTION_FOLDERS": "",
|
||||
"TRASHING_DELETED_FILES": "",
|
||||
"TRASHING_DELETED_COLLECTIONS": "",
|
||||
"EXPORT_NOTIFICATION": {
|
||||
"START": "",
|
||||
"IN_PROGRESS": "",
|
||||
"FINISH": "",
|
||||
"UP_TO_DATE": ""
|
||||
},
|
||||
"CONTINUOUS_EXPORT": "",
|
||||
"TOTAL_ITEMS": "",
|
||||
"PENDING_ITEMS": "",
|
||||
"EXPORT_STARTING": "",
|
||||
"DELETE_ACCOUNT_REASON_LABEL": "",
|
||||
"DELETE_ACCOUNT_REASON_PLACEHOLDER": "",
|
||||
"DELETE_REASON": {
|
||||
"MISSING_FEATURE": "",
|
||||
"BROKEN_BEHAVIOR": "",
|
||||
"FOUND_ANOTHER_SERVICE": "",
|
||||
"NOT_LISTED": ""
|
||||
},
|
||||
"DELETE_ACCOUNT_FEEDBACK_LABEL": "",
|
||||
"DELETE_ACCOUNT_FEEDBACK_PLACEHOLDER": "",
|
||||
"CONFIRM_DELETE_ACCOUNT_CHECKBOX_LABEL": "",
|
||||
"CONFIRM_DELETE_ACCOUNT": "",
|
||||
"FEEDBACK_REQUIRED": "",
|
||||
"FEEDBACK_REQUIRED_FOUND_ANOTHER_SERVICE": "",
|
||||
"RECOVER_TWO_FACTOR": "",
|
||||
"at": "",
|
||||
"AUTH_NEXT": "",
|
||||
"AUTH_DOWNLOAD_MOBILE_APP": "",
|
||||
"HIDDEN": "",
|
||||
"HIDE": "",
|
||||
"UNHIDE": "",
|
||||
"UNHIDE_TO_COLLECTION": "",
|
||||
"SORT_BY": "",
|
||||
"NEWEST_FIRST": "",
|
||||
"OLDEST_FIRST": "",
|
||||
"CONVERSION_FAILED_NOTIFICATION_MESSAGE": "",
|
||||
"SELECT_COLLECTION": "",
|
||||
"PIN_ALBUM": "",
|
||||
"UNPIN_ALBUM": "",
|
||||
"DOWNLOAD_COMPLETE": "",
|
||||
"DOWNLOADING_COLLECTION": "",
|
||||
"DOWNLOAD_FAILED": "",
|
||||
"DOWNLOAD_PROGRESS": "",
|
||||
"CRASH_REPORTING": "",
|
||||
"CHRISTMAS": "",
|
||||
"CHRISTMAS_EVE": "",
|
||||
"NEW_YEAR": "",
|
||||
"NEW_YEAR_EVE": "",
|
||||
"IMAGE": "",
|
||||
"VIDEO": "",
|
||||
"LIVE_PHOTO": "",
|
||||
"CONVERT": "",
|
||||
"CONFIRM_EDITOR_CLOSE_MESSAGE": "",
|
||||
"CONFIRM_EDITOR_CLOSE_DESCRIPTION": "",
|
||||
"BRIGHTNESS": "",
|
||||
"CONTRAST": "",
|
||||
"SATURATION": "",
|
||||
"BLUR": "",
|
||||
"INVERT_COLORS": "",
|
||||
"ASPECT_RATIO": "",
|
||||
"SQUARE": "",
|
||||
"ROTATE_LEFT": "",
|
||||
"ROTATE_RIGHT": "",
|
||||
"FLIP_VERTICALLY": "",
|
||||
"FLIP_HORIZONTALLY": "",
|
||||
"DOWNLOAD_EDITED": "",
|
||||
"SAVE_A_COPY_TO_ENTE": "",
|
||||
"RESTORE_ORIGINAL": "",
|
||||
"TRANSFORM": "",
|
||||
"COLORS": "",
|
||||
"FLIP": "",
|
||||
"ROTATION": "",
|
||||
"RESET": "",
|
||||
"PHOTO_EDITOR": "",
|
||||
"FASTER_UPLOAD": "",
|
||||
"FASTER_UPLOAD_DESCRIPTION": "",
|
||||
"MAGIC_SEARCH_STATUS": "",
|
||||
"INDEXED_ITEMS": "",
|
||||
"CAST_ALBUM_TO_TV": "",
|
||||
"ENTER_CAST_PIN_CODE": "",
|
||||
"PAIR_DEVICE_TO_TV": "",
|
||||
"TV_NOT_FOUND": "",
|
||||
"AUTO_CAST_PAIR": "",
|
||||
"AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "",
|
||||
"PAIR_WITH_PIN": "",
|
||||
"CHOOSE_DEVICE_FROM_BROWSER": "",
|
||||
"PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "",
|
||||
"VISIT_CAST_ENTE_IO": "",
|
||||
"CAST_AUTO_PAIR_FAILED": "",
|
||||
"CACHE_DIRECTORY": "",
|
||||
"PASSKEYS": "",
|
||||
"FREEHAND": "",
|
||||
"APPLY_CROP": "",
|
||||
"PHOTO_EDIT_REQUIRED_TO_SAVE": ""
|
||||
}
|
|
@ -338,8 +338,24 @@ export const updateMapEnabledStatus = async (newStatus: boolean) => {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return true to disable the upload of files via Cloudflare Workers.
|
||||
*
|
||||
* These workers were introduced as a way of make file uploads faster:
|
||||
* https://ente.io/blog/tech/making-uploads-faster/
|
||||
*
|
||||
* By default, that's the route we take. However, during development or when
|
||||
* self-hosting it can be convenient to turn this flag on to directly upload to
|
||||
* the S3-compatible URLs returned by the ente API.
|
||||
*
|
||||
* Note the double negative (Enhancement: maybe remove the double negative,
|
||||
* rename this to say getUseDirectUpload).
|
||||
*/
|
||||
export async function getDisableCFUploadProxyFlag(): Promise<boolean> {
|
||||
if (process.env.NEXT_PUBLIC_ENTE_DIRECT_UPLOAD === "true") return true;
|
||||
// If NEXT_PUBLIC_ENTE_ENDPOINT is set, that means we're not running a
|
||||
// production deployment. Disable the Cloudflare upload proxy, and instead
|
||||
// just directly use the upload URLs that museum gives us.
|
||||
if (process.env.NEXT_PUBLIC_ENTE_ENDPOINT) return true;
|
||||
|
||||
try {
|
||||
const featureFlags = (
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
project_id_env: CROWDIN_PROJECT_ID
|
||||
api_token_env: CROWDIN_PERSONAL_TOKEN
|
||||
|
||||
files:
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
If you just want to run ente locally or develop on it, you can do
|
||||
|
||||
yarn
|
||||
yarn dev:photos
|
||||
yarn dev
|
||||
|
||||
The docs in this directory are for more advanced or infrequently needed details.
|
||||
|
|
|
@ -1,44 +1,39 @@
|
|||
# Deploying the web apps
|
||||
|
||||
## tl;dr;
|
||||
|
||||
```sh
|
||||
yarn deploy:photos
|
||||
```
|
||||
|
||||
## Details
|
||||
|
||||
The various web apps (Ente Photos, Ente Auth) are deployed on Cloudflare Pages.
|
||||
They also use Cloudflare Workers for some tasks.
|
||||
|
||||
This repository deploys multiple different apps (the Photos app, the Auth app).
|
||||
Some of them get deployed to multiple different endpoints (e.g. the main branch
|
||||
of photos app gets deployed to testing.ente.io, the while the photos-release
|
||||
branch is the production deployment).
|
||||
|
||||
The apps are under the app directory:
|
||||
|
||||
- photos - The Ente Photos app
|
||||
- auth - The Ente Auth app
|
||||
- cast - The cast app, which can be thought of as an independent subset of
|
||||
Photos app functionality
|
||||
- ... and more
|
||||
|
||||
For deploying, we've added the GitHub integration provided by Cloudflare Pages
|
||||
app to this repository. This integration watches for pushes to all branches. In
|
||||
all cases, it runs the same script, `scripts/deploy.sh`.
|
||||
|
||||
Internally it uses the `CF_PAGES_BRANCH` environment variable to decide what
|
||||
exactly to build ([CF
|
||||
The deployment is done using the GitHub app provided by Cloudflare Pages. The
|
||||
Cloudflare integration watches for pushes to all branches named "deploy/*". In
|
||||
all cases, it runs the same script, `scripts/deploy.sh`, using the
|
||||
`CF_PAGES_BRANCH` environment variable to decide what exactly to build ([CF
|
||||
docs](https://developers.cloudflare.com/pages/how-to/build-commands-branches/)).
|
||||
|
||||
Then, for some special branches, we have configured CNAME aliases (Cloudflare
|
||||
calls them Custom Domains) to give a stable URL to some of these deployments
|
||||
Here is a potentially out of date list of CNAMEs and the corresponding branch;
|
||||
see the Cloudflare dashboard for the latest:
|
||||
For each of these branches, we have configured CNAME aliases (Cloudflare calls
|
||||
them Custom Domains) to give a stable URL to the deployments.
|
||||
|
||||
- _testing.ente.io_: `main`
|
||||
- _web.ente.io_: `photos-release`
|
||||
- _auth.ente.io_: `auth-release`
|
||||
- _accounts.ente.io_: `accounts-release`
|
||||
- _cast.ente.io_: `cast-release`
|
||||
- `deploy/photos` → _web.ente.io_
|
||||
- `deploy/auth` → _auth.ente.io_
|
||||
- `deploy/accounts` → _accounts.ente.io_
|
||||
- `deploy/cast` → _cast.ente.io_
|
||||
|
||||
Thus to trigger a, say, production deployment of the photos app, we can open and
|
||||
merge a PR into the `photos-release` branch. Cloudflare will then build and
|
||||
merge a PR into the `deploy/photos` branch. Cloudflare will then build and
|
||||
deploy the code to _web.ente.io_.
|
||||
|
||||
The command `yarn deploy:photos` just does that - it'll open a new PR to fast
|
||||
forward the current main onto `deploy/photos`. There are similar `yarn deploy:*`
|
||||
commands for the other apps.
|
||||
|
||||
## Other subdomains
|
||||
|
||||
Apart from this, there are also some subdomains:
|
||||
|
||||
- `albums.ente.io` is a CNAME alias to the production deployment
|
||||
|
@ -49,14 +44,17 @@ Apart from this, there are also some subdomains:
|
|||
- `payments.ente.io` and `family.ente.io` are currently in a separate
|
||||
repositories (Enhancement: bring them in here).
|
||||
|
||||
In Cloudflare Pages setting the following environment variables are defined:
|
||||
## NODE_VERSION
|
||||
|
||||
- `NODE_VERSION`: Determines which version of Node is used when we do `yarn
|
||||
build:foo`. Currently this is set to `20.11.1`. The major version here should
|
||||
match that of `@types/node` in our dev dependencies.
|
||||
In Cloudflare Pages setting the `NODE_VERSION` environment variables is defined.
|
||||
|
||||
- `SENTRY_AUTH_TOKEN`: An encrypted environment variable that is used by the
|
||||
Sentry Webpack Plugin to upload sourcemaps during the build.
|
||||
This determines which version of Node is used when we do `yarn build:foo`.
|
||||
Currently this is set to `20.11.1`. The major version here should match that of
|
||||
`@types/node` in our dev dependencies.
|
||||
|
||||
It is a good idea to also use the same major version of node on your machine.
|
||||
For example, for macOS you can install the the latest from the v20 series using
|
||||
`brew install node@20`.
|
||||
|
||||
## Adding a new app
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
The monorepo uses Yarn (classic) workspaces.
|
||||
|
||||
To run a command for a workspace `<ws>`, invoke `yarn workspace <ws> <cmd>` from
|
||||
the root folder instead the the `yarn <cmd>` you’d have done otherwise. For
|
||||
the root folder instead the `yarn <cmd>` you’d have done otherwise. For
|
||||
example, to start a development server for the `photos` app, we can do
|
||||
|
||||
```sh
|
||||
|
|
|
@ -9,7 +9,7 @@ development, here is a recommended workflow:
|
|||
|
||||
3. Enable the VS Code setting to format on save.
|
||||
|
||||
4. Install node on your machine `brew install node`. Our package manager, `yarn`
|
||||
comes with it.
|
||||
4. Install node on your machine `brew install node@20`. Our package manager,
|
||||
`yarn` comes with it.
|
||||
|
||||
That's it. Enjoy coding!
|
||||
|
|
|
@ -12,6 +12,10 @@
|
|||
"build:auth": "yarn workspace auth next build",
|
||||
"build:cast": "yarn workspace cast next build",
|
||||
"build:photos": "yarn workspace photos next build",
|
||||
"deploy:accounts": "open 'https://github.com/ente-io/ente/pull/new/deploy/accounts..main'",
|
||||
"deploy:auth": "open 'https://github.com/ente-io/ente/pull/new/deploy/auth..main'",
|
||||
"deploy:cast": "open 'https://github.com/ente-io/ente/pull/new/deploy/cast..main'",
|
||||
"deploy:photos": "open 'https://github.com/ente-io/ente/pull/new/deploy/photos..main'",
|
||||
"dev": "yarn dev:photos",
|
||||
"dev:accounts": "yarn workspace accounts next dev",
|
||||
"dev:albums": "yarn workspace photos next dev -p 3002",
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#!/bin/sh
|
||||
|
||||
# This script is run by the Cloudflare Pages integration when deploying the apps
|
||||
# in this repository. The app to build and the environment variables to use is
|
||||
# decided based on the value of the CF_PAGES_BRANCH environment variable.
|
||||
# in this repository. The app to build is decided based on the value of the
|
||||
# CF_PAGES_BRANCH environment variable.
|
||||
#
|
||||
# The Cloudflare Pages build configuration is set to use `out/` as the build
|
||||
# output directory, so once we're done building we copy the app specific output
|
||||
|
@ -12,29 +12,40 @@
|
|||
#
|
||||
# To test this script locally, run
|
||||
#
|
||||
# CF_PAGES_BRANCH=foo-bar ./scripts/deploy.sh
|
||||
# CF_PAGES_BRANCH=deploy/foo ./scripts/deploy.sh
|
||||
#
|
||||
|
||||
set -o errexit
|
||||
set -o xtrace
|
||||
|
||||
if test "$(basename $(pwd))" != "web"
|
||||
then
|
||||
echo "ERROR: This script should be run from the web directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf out
|
||||
|
||||
case "$CF_PAGES_BRANCH" in
|
||||
accounts-*)
|
||||
deploy/accounts)
|
||||
yarn build:accounts
|
||||
cp -R apps/accounts/out .
|
||||
;;
|
||||
auth-*)
|
||||
deploy/auth)
|
||||
yarn build:auth
|
||||
cp -R apps/auth/out .
|
||||
;;
|
||||
cast-*)
|
||||
deploy/cast)
|
||||
yarn build:cast
|
||||
cp -R apps/cast/out .
|
||||
;;
|
||||
*)
|
||||
deploy/photos)
|
||||
yarn build:photos
|
||||
cp -R apps/photos/out .
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: We don't know how to build and deploy a branch named $CF_PAGES_BRANCH."
|
||||
echo " Maybe you forgot to add a new case in web/scripts/deploy.sh"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
|
Loading…
Add table
Reference in a new issue