Merge branch 'main' into passkeys

This commit is contained in:
Neeraj Gupta 2024-03-06 13:21:42 +05:30
commit 942da28b53
79 changed files with 3766 additions and 1711 deletions

41
.github/workflows/auth-crowdin.yml vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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"

View file

@ -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
View 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
View 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

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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 }}

View file

@ -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",

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 30 KiB

View file

@ -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

View file

@ -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

View file

@ -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).

View file

@ -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": "نحن لا نتتبع تثبيت التطبيق. سيكون من المفيد إذا أخبرتنا أين وجدتنا!"
}

View file

@ -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ó!"
}

View file

@ -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": "Пожалуйста, выберите файл, содержащий список ваших кодов в следующем формате",

View 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."
}

View file

@ -0,0 +1 @@
{}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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

View file

@ -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=

View file

@ -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."

View file

@ -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
}

View file

@ -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>
```

View file

@ -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

View file

@ -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:"

View file

@ -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
View 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)
}

View file

@ -11,7 +11,7 @@ import (
"github.com/spf13/cobra"
)
const AppVersion = "0.1.10"
const AppVersion = "0.1.11"
var ctrl *pkg.ClICtrl

View file

View 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
}

View file

@ -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

View file

@ -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

View file

@ -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 }}

View file

@ -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 }}

View file

@ -1,4 +1,3 @@
project_id_env: CROWDIN_PROJECT_ID
api_token_env: CROWDIN_PERSONAL_TOKEN
preserve_hierarchy: true

View file

@ -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 =

View file

@ -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 }}

View file

@ -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 ./..."

View file

@ -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)

View file

@ -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"`

View file

@ -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 {

View file

@ -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")
}

View file

@ -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 {

View file

@ -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 }}

View file

@ -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

View file

@ -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>

View file

@ -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.
#

View file

@ -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

View file

@ -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."
}

View 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": ""
}

View file

@ -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": "",

View 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": ""
}

View 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": ""
}

View file

@ -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 = (

View file

@ -1,4 +1,3 @@
project_id_env: CROWDIN_PROJECT_ID
api_token_env: CROWDIN_PERSONAL_TOKEN
files:

View file

@ -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.

View file

@ -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

View file

@ -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>` youd have done otherwise. For
the root folder instead the `yarn <cmd>` youd have done otherwise. For
example, to start a development server for the `photos` app, we can do
```sh

View file

@ -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!

View file

@ -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",

View file

@ -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