Compare commits

...

197 Commits

Author SHA1 Message Date
Neeraj Gupta
fa361904f6 [mob] Misc bug fixes (#3522)
## Description

## Tests
2024-09-29 11:17:51 +05:30
Neeraj Gupta
00e75c0fb2 [mob] Lint 2024-09-29 09:54:20 +05:30
Neeraj Gupta
026ab8dcc6 [mob] Bump version: v0.9.46 2024-09-29 09:53:15 +05:30
Neeraj Gupta
7d42ed37e1 Merge remote-tracking branch 'origin/main' into bug_fixes 2024-09-29 09:52:49 +05:30
Neeraj Gupta
a5d01a9ffe [mob] Fix avatar faceID 2024-09-29 09:52:41 +05:30
Neeraj Gupta
675b7f6cea [mob] Sync cgroup as part of ML 2024-09-29 09:45:48 +05:30
Neeraj Gupta
772373580a [mob] Stop consuming errors for trash 2024-09-29 09:42:25 +05:30
Neeraj Gupta
f0a19e38aa [mob] Fix hide cluster property 2024-09-29 09:41:35 +05:30
Manav Rathi
d1d6590547 [web] Sync locations only once on app start if there are pending uploads (#3521) 2024-09-29 05:52:32 +05:30
Manav Rathi
03da960c33 Start with a idle state
Not sure why it was true - there is a possibility that this was intentional, but
I can't think why. The reason for changing it is to fix our "isForced" logic
(otherwise the non-file-related sync doesn't run on app start), without
introducing _another_ flag to track if the sync was initiated from a gallery
useEffect or by the preUploadSync.
2024-09-29 05:40:37 +05:30
Manav Rathi
2bdc010849 [web] Do not perform multiple non-file-syncs in parallel
e.g. this might cause multiple requests to getOrCreate a location tag entity
key. Remote will reject the second one, so no harm will come of it, but still
its better to enforce serialization to make the mental model of the code easier
to reason about.
2024-09-29 05:20:12 +05:30
Prateek Sunal
c160afc6de [auth] fix x64 installer on arm64 Windows (#3502)
Portable x64 Ente Auth works on arm64 Windows, but the installer version
fails due to not supporting arm64. As per Inno Setup's documentation the
'x64' option will only allow installing on x64 Windows, changing to
'x64compatible' allows the x64 installer to work on arm64 as well.
2024-09-28 21:33:40 +05:30
Manav Rathi
f971b968af [desktop] Start next release cycle (#3515) 2024-09-28 19:35:58 +05:30
Manav Rathi
c8468efd20 [desktop] Start next release cycle 2024-09-28 19:21:49 +05:30
Manav Rathi
9515cf70f5 photosd-v1.7.5 (#3514) 2024-09-28 18:54:54 +05:30
Manav Rathi
963650db4b [desktop] Use most recent face as the auto-cover (#3513) 2024-09-28 18:40:54 +05:30
Manav Rathi
a2841a8af2 Use most recent face as the auto-cover 2024-09-28 18:32:30 +05:30
Manav Rathi
6a6db0813f [desktop] People: Remove from behind internal user flag (#3511) 2024-09-28 17:51:14 +05:30
Manav Rathi
66fad15743 Add CHANGELOG entry 2024-09-28 17:41:54 +05:30
Manav Rathi
eb4d77bd24 Un-ff 2024-09-28 17:37:46 +05:30
Manav Rathi
e2c0aed2e4 Remove early exit threshold
It did not appear to be obviously helping the speed (or hurting the quality), so
remove it to reduce the number of concepts at play.
2024-09-28 17:22:44 +05:30
Manav Rathi
5a5e046192 Fix hidden sync with existing mobile app 2024-09-28 17:22:03 +05:30
Manav Rathi
42a6e3ac25 [web] New translations (#3509)
New translations from
[Crowdin](https://crowdin.com/project/ente-photos-web)
2024-09-28 16:50:31 +05:30
Crowdin Bot
7fdb8a6dae New Crowdin translations by GitHub Action 2024-09-28 11:18:59 +00:00
Manav Rathi
ef63d4c7a0 [web] Translation cleanup (#3508) 2024-09-28 16:40:28 +05:30
Manav Rathi
60b9d1e43f Rename 2024-09-28 16:32:45 +05:30
Manav Rathi
c453c7dc81 Remove unused 2024-09-28 16:30:31 +05:30
Manav Rathi
5c41e8ad3c Rename 2024-09-28 16:28:49 +05:30
Manav Rathi
53c706fba7 Rename 2024-09-28 16:27:26 +05:30
Manav Rathi
27a34a08f4 Rename 2024-09-28 16:25:29 +05:30
Manav Rathi
6bea1fa0f5 Rename 2024-09-28 16:24:22 +05:30
Manav Rathi
a97ca411d7 Rename 2024-09-28 16:12:45 +05:30
Manav Rathi
636cd1395c Rename 2024-09-28 16:08:16 +05:30
Manav Rathi
6e23e5e453 Cons 2024-09-28 16:07:17 +05:30
Manav Rathi
77f5d21dad Rename 2024-09-28 16:04:38 +05:30
Manav Rathi
5d210ab740 Rename 2024-09-28 16:02:41 +05:30
Manav Rathi
8d14997f36 [web] New translations (#3507)
New translations from
[Crowdin](https://crowdin.com/project/ente-photos-web)
2024-09-28 16:00:58 +05:30
Crowdin Bot
b6059273fb New Crowdin translations by GitHub Action 2024-09-28 10:27:04 +00:00
Neeraj Gupta
1dde716d26 [mob] Fix: Enable video upload without thumbnail for shared files (#3506)
## Description

## Tests
2024-09-28 14:22:35 +05:30
Neeraj Gupta
8629212584 [mob] Allow video upload with empty thumbnail 2024-09-28 14:18:21 +05:30
Neeraj Gupta
08cf14a72b [mob] Minor improvement in magicSearch cache refresh 2024-09-28 14:14:08 +05:30
Vishnu Mohandas
fe5da2ee8b [docs] Update ML article (#3504) 2024-09-27 23:13:06 -07:00
vishnukvmd
f255ded0b6 [docs] Update ML article 2024-09-27 23:07:32 -07:00
Manav Rathi
59bf51346c [web] Fix redirect on parallel login (#3503) 2024-09-28 10:23:49 +05:30
Manav Rathi
3288f3250b Extract 2024-09-28 10:16:12 +05:30
Manav Rathi
1eb5eaece9 Freshness check 2024-09-28 10:08:11 +05:30
Manav Rathi
08f84c9cf8 Also handle for auth 2024-09-28 09:54:51 +05:30
omove
6d969ab72a [auth] fix x64 install on arm64 Windows
Inno Setup's 'x64' option only allows install on x64 Windows, changing to 'x64compatible' allows x64 installation on arm64 and x64 Windows.
2024-09-28 00:05:59 -04:00
Manav Rathi
da8326229c [web] Redirect to password input on no-email-MFA + new tab
Fixes the following bug report, for a user who has email verification disabled:

> and about verify in new tab...
> it happens when u r at password page after entering email and opening
  ente.auth.io in new tab opens the verify page instead of password
2024-09-28 09:32:25 +05:30
Manav Rathi
05763a5d83 [desktop] People - Empty state (#3498)
+ thumbnails
2024-09-27 20:33:57 +05:30
Manav Rathi
d7e2330f20 Fix render loop 2024-09-27 20:29:14 +05:30
Manav Rathi
99ba5a31d3 Fix warning 2024-09-27 19:27:48 +05:30
Manav Rathi
2aaa23312b Both buttons 2024-09-27 19:15:12 +05:30
Manav Rathi
cc262aad0c New semantics 2024-09-27 19:06:32 +05:30
Manav Rathi
72c93a1703 Tweak styling 2024-09-27 18:44:53 +05:30
Manav Rathi
2f27ae7b19 Empty state 2024-09-27 18:37:16 +05:30
Manav Rathi
039256cb05 wip checkpoint people empty state 2024-09-27 18:28:44 +05:30
Manav Rathi
167c4efc40 wip empty state 2024-09-27 18:05:49 +05:30
Manav Rathi
5b73eee14c Don't show bar controls in people section 2024-09-27 18:01:16 +05:30
Manav Rathi
eafc8fc4cb Fix logout 2024-09-27 17:12:56 +05:30
Manav Rathi
370d4af008 Thumbnails shouldn't be revoked
So make the face crops behave the same too
2024-09-27 17:07:37 +05:30
Manav Rathi
4bb6aa2b39 Use 2024-09-27 16:48:18 +05:30
Manav Rathi
01f31c352b Support face crops 2024-09-27 16:47:35 +05:30
Manav Rathi
50c60dff1c [desktop] People - Enable for internal (#3492)
Nearing readiness for beta release
2024-09-27 14:08:39 +05:30
Manav Rathi
7bdbaec443 Unconditionally enable for internal 2024-09-27 14:01:44 +05:30
Manav Rathi
57d245d9e0 Select person 2024-09-27 13:57:04 +05:30
Manav Rathi
8a953cab88 Fix alignment etc 2024-09-27 13:33:03 +05:30
Manav Rathi
2827a166dc people list checkpoint 2024-09-27 13:24:31 +05:30
Manav Rathi
4e04739d54 wip checkpoint 2024-09-27 12:54:17 +05:30
Manav Rathi
7a60b1e15e wip checkpoint 2024-09-27 12:34:41 +05:30
Manav Rathi
e2e374fbf4 wip checkpoint 2024-09-27 12:25:53 +05:30
Manav Rathi
71adb1e366 Another 2024-09-27 11:46:01 +05:30
Manav Rathi
393878a52e More workarounds 2024-09-27 11:35:35 +05:30
Manav Rathi
7644900bd8 Use 2024-09-27 11:26:35 +05:30
Manav Rathi
e70f9b5ccd Ignore temp deleted etc 2024-09-27 11:03:30 +05:30
Manav Rathi
a37ff3cf57 Workarounds 2024-09-27 10:53:28 +05:30
Manav Rathi
9235e41855 Prepare to allow filtering people at the gallery layer 2024-09-27 10:46:34 +05:30
Manav Rathi
71369bf5c9 State 2024-09-27 10:29:33 +05:30
Manav Rathi
924f5ce19b Keep people first 2024-09-27 10:14:53 +05:30
Manav Rathi
4d4b3f8bef Notify about live uploads 2024-09-27 10:06:56 +05:30
Manav Rathi
27a0d7707e Return the count of items indexed 2024-09-27 08:39:29 +05:30
Manav Rathi
57ea097a5d Use new nomenclature 2024-09-27 08:21:58 +05:30
Manav Rathi
2c0f2d43e7 Allow flex 2024-09-27 08:15:56 +05:30
Manav Rathi
968067d6aa [desktop] People WIP - More CRUD ops (#3485) 2024-09-26 21:49:42 +05:30
Manav Rathi
de6a494da7 Reset selection 2024-09-26 21:42:59 +05:30
Prateek Sunal
e13f2a379a [auth] add show notes for mobile and desktop menu (#3451)
## Description

## Tests
2024-09-26 21:40:54 +05:30
Manav Rathi
4c0c05a54a qp 2024-09-26 21:20:35 +05:30
Manav Rathi
ac04ceadce Delete 2024-09-26 21:06:40 +05:30
Manav Rathi
91127b6ce5 Don't used cached data
e.g. after a rename, the active person still has the old name even though the
list of people has updated.
2024-09-26 20:48:51 +05:30
Manav Rathi
f95cc1f135 Fix 2024-09-26 20:37:03 +05:30
Manav Rathi
64d7959c95 Split 2024-09-26 20:33:51 +05:30
Manav Rathi
9a444b4881 Rename 2024-09-26 20:33:02 +05:30
Manav Rathi
795187177d Need to also update local clusters 2024-09-26 20:21:16 +05:30
Manav Rathi
54c5c2ce7e Reload 2024-09-26 20:03:12 +05:30
Manav Rathi
1a966fdedd Add workaround
306bc56c21/mobile/lib/ui/viewer/actions/file_selection_actions_widget.dart (L701)
2024-09-26 18:46:14 +05:30
Manav Rathi
6f9cd84b6d Shorten to reduce noise 2024-09-26 18:31:10 +05:30
Manav Rathi
4506e5b6d9 [desktop] People WIP - Allow adding (#3481) 2024-09-26 18:24:34 +05:30
Manav Rathi
1c74eae132 Add a workaround 2024-09-26 18:21:19 +05:30
Manav Rathi
262ff01999 Fix lints 2024-09-26 17:44:18 +05:30
Manav Rathi
1daa0f3e07 Proxy 2024-09-26 17:31:15 +05:30
Manav Rathi
c4931de42e Add 2024-09-26 17:28:19 +05:30
Manav Rathi
77cdf673a9 Add a quick action 2024-09-26 17:22:35 +05:30
Manav Rathi
7ff5f74fa6 Use a slide 2024-09-26 17:18:10 +05:30
Neeraj Gupta
57a425e14c [mob] Run discovery forcefully when ml is toggled 2024-09-26 17:13:29 +05:30
Manav Rathi
642c85fe59 Make tsc happy 2024-09-26 17:13:24 +05:30
Manav Rathi
0e046b9c8a wip checkpoint 2024-09-26 17:03:22 +05:30
Manav Rathi
be222f53bf rename 2024-09-26 16:24:50 +05:30
Manav Rathi
0498c70bad Uncustom 2024-09-26 16:19:44 +05:30
Manav Rathi
08fcc02282 Scaffold 2024-09-26 16:03:50 +05:30
Manav Rathi
aef32027a1 Name input 2024-09-26 15:49:25 +05:30
Manav Rathi
f9cbce66c0 Newer 2024-09-26 15:01:29 +05:30
Manav Rathi
39d39eb195 Opts 2024-09-26 14:51:59 +05:30
Manav Rathi
b052026526 Fix console warning 2024-09-26 14:45:57 +05:30
Manav Rathi
8a79ae9b96 Use 2024-09-26 14:37:04 +05:30
Manav Rathi
dbd160c135 Start at attempting to abstract wrap
This can be done much better in many small ways, for now just attempting a
start.
2024-09-26 14:30:56 +05:30
Manav Rathi
7634ba0dea Split options 2024-09-26 14:17:35 +05:30
Manav Rathi
840ba2803e Take 2 2024-09-26 14:11:16 +05:30
Manav Rathi
3093894b12 as const wasn't working
Current tsc - 5.6
2024-09-26 14:09:08 +05:30
Manav Rathi
41d5960d1f Retain type 2024-09-26 13:52:31 +05:30
Manav Rathi
fb63b1d832 Center align 1 2024-09-26 13:18:57 +05:30
Manav Rathi
68c93537d3 Fix thin outline on placeholder 2024-09-26 12:38:45 +05:30
Manav Rathi
0f73a68681 Tweak spacing to account for the "People" option 2024-09-26 12:33:26 +05:30
Manav Rathi
306bc56c21 [desktop] People WIP (Part x/x) - Start uploading updates to cgroups (#3477) 2024-09-26 12:17:24 +05:30
Manav Rathi
feec9a475b Remove unnecessary sort 2024-09-26 12:10:46 +05:30
Manav Rathi
8314cda12a Tweak logging 2024-09-26 12:09:11 +05:30
Manav Rathi
4cc8be748c Fix indexing into the wrong faces 2024-09-26 12:02:27 +05:30
Manav Rathi
5a33820877 Fix 2024-09-26 12:00:39 +05:30
Neeraj Gupta
830f1b9c18 [auth] Enable l10n for Vietnamese, Arabic, Greek, & Slovak (#3476)
## Description

## Tests
2024-09-26 11:31:11 +05:30
Neeraj Gupta
c81cf5a432 Add altName back to T-Mobile icon that was previously removed from json (#3465)
## Description

## Tests
2024-09-26 11:29:39 +05:30
Neeraj Gupta
f71c3f4171 [auth] Enable l10n for Vietnamese, Arabic, Greek, & Slovak 2024-09-26 11:28:05 +05:30
Manav Rathi
5b99902d68 Workaround a spurious tsc error 2024-09-26 11:17:40 +05:30
Neeraj Gupta
2569337be8 [auth] Auth minor improvements & bug fixes (#3475)
## Description

## Tests
2024-09-26 11:14:59 +05:30
Neeraj Gupta
87f7d3a484 [auth] Show theme option for windows & linux 2024-09-26 11:12:16 +05:30
Neeraj Gupta
8fa6adb16a [auth] Default to non-compact mode 2024-09-26 11:09:58 +05:30
Neeraj Gupta
00c9572b43 [auth] Remove double authentication for passkey 2024-09-26 11:09:28 +05:30
Manav Rathi
9424b7a65e Remove unused intermediate concept 2024-09-26 11:09:02 +05:30
Manav Rathi
af680b6da3 Update 2024-09-26 10:54:50 +05:30
Manav Rathi
3826c1f957 Reorder 2024-09-26 10:49:02 +05:30
Manav Rathi
3b6bee6042 Only one diff sync per set 2024-09-26 10:47:42 +05:30
Manav Rathi
2163a5fbea refactor 2024-09-26 10:39:42 +05:30
Manav Rathi
8e485bfe39 Layer 2024-09-26 10:36:04 +05:30
Manav Rathi
c361fcbff4 Entity CRUD 2024-09-26 10:01:27 +05:30
Manav Rathi
ba063bf4a7 Enable for internal 2024-09-26 09:45:48 +05:30
Manav Rathi
82c0bbb879 Tinker 2024-09-26 09:36:25 +05:30
Manav Rathi
711f31991d Doc 2024-09-26 09:35:58 +05:30
Manav Rathi
9c0d9ac538 Optimize no-op case 2024-09-26 09:34:05 +05:30
Manav Rathi
2521cd6d31 Tweak spacing 2024-09-26 09:09:25 +05:30
Manav Rathi
b5db5e2b83 kMinimumClusterSizeSearchResult
Although that is the file count
2024-09-26 08:57:20 +05:30
Manav Rathi
7d52b60cd9 Retain all clusters so that files get marked 2024-09-26 08:54:48 +05:30
Manav Rathi
08765ccd39 Clip with preprocessing inside ONNX on web (#3466)
## Description

Switched to new clip ONNX model on web, where preprocessing is done
inside ORT. This means it's more performant and more advanced image
processing.

## Tests

Not tested at all! I'm sure I've made a small mistake somewhere, please
review and test carefully.
The only thing properly tested is the model itself, this approach is
tested and passed on mobile.

Also, there's one "TODO: manav" pending, please check.
2024-09-26 08:25:52 +05:30
Manav Rathi
3dcf5fa860 Remove unused anti-aliasing code (since CLIP now uses ONNX) 2024-09-26 07:59:36 +05:30
Manav Rathi
cda925fc80 Tweaks (non-functional) 2024-09-26 07:25:25 +05:30
Laurens Priem
7b2206161e Clip with preprocessing inside ONNX on mobile (#3467)
## Description

Switched to new clip ONNX model on mobile, where preprocessing is done
inside ORT. This means it's more performant and more advanced image
processing.

## Tests

Tested in debug mode on my Pixel 8.
2024-09-26 05:44:19 +05:30
Prateek Sunal
bbd7be4423 fix: remove onNotes tap for mobile 2024-09-26 01:11:29 +05:30
laurenspriem
c8ab6be9f8 [web][photos] Remove old clip preprocessing code web 2024-09-25 23:54:37 +05:30
laurenspriem
5609309660 [web][photos] Move to new clip with preprocessing included 2024-09-25 23:53:28 +05:30
laurenspriem
fa19254bbc [mob][photos remove old clip preprocessing code 2024-09-25 23:04:29 +05:30
laurenspriem
70476b2011 [mob][photos] Remove todo 2024-09-25 23:00:52 +05:30
laurenspriem
7dd9d8aef3 [mob][photos] new clip works 2024-09-25 22:58:23 +05:30
casualsailo
13727b9a96 Add altName back to T-Mobile icon that was previously removed from json 2024-09-25 09:13:33 -07:00
Manav Rathi
6decb15be7 [desktop] People WIP- Part x/x (+ bugfix, clear ML DB on logout) (#3464) 2024-09-25 20:59:09 +05:30
Manav Rathi
a93c43d341 Add missing % symbol 2024-09-25 20:46:18 +05:30
Manav Rathi
40b1cdcabb Clear ML DB on logout 2024-09-25 20:42:16 +05:30
Manav Rathi
f5ee46189c Extract and tweak 2024-09-25 20:35:36 +05:30
Manav Rathi
48cab57d53 Wait for indexing to complete before clustering 2024-09-25 20:00:33 +05:30
Manav Rathi
7c867f94bf [desktop] People WIP - Part x/x (#3460) 2024-09-25 19:16:12 +05:30
Manav Rathi
3465253fcc Transform 2024-09-25 18:53:41 +05:30
Manav Rathi
61b324ca05 Rearrange 2024-09-25 18:53:41 +05:30
Manav Rathi
cb57351951 Split 2024-09-25 18:53:41 +05:30
Manav Rathi
3f4250dab3 Reconcile 1 2024-09-25 18:53:41 +05:30
Manav Rathi
1efbbf2b2f Rearrange 2024-09-25 18:53:41 +05:30
Manav Rathi
1ff21b3d8e Start with existing clusters 2024-09-25 18:53:41 +05:30
Manav Rathi
31ffc5bee5 Doc 2024-09-25 18:53:40 +05:30
Manav Rathi
6cd43707a8 Pull 2024-09-25 18:53:40 +05:30
Manav Rathi
3324019b38 Percentage during clustering 2024-09-25 18:53:40 +05:30
Manav Rathi
cc02236ca9 Rename 2024-09-25 18:53:40 +05:30
Manav Rathi
ece4980d94 Mention our experience so far 2024-09-25 18:53:40 +05:30
Neeraj Gupta
47b6e758d2 [mob] Fix Handling live photo dups with different zip side (#3452)
## Description

## Tests
2024-09-25 14:48:46 +05:30
laurenspriem
53c19bc64e [mob][photos] Temp disable custom plugin 2024-09-25 14:40:40 +05:30
laurenspriem
bd232c151a [mob][photos] Test for iOS 2024-09-25 14:34:25 +05:30
Manav Rathi
8970074f80 [web] Store user entities verbatim (#3458) 2024-09-25 13:58:46 +05:30
Manav Rathi
27d901bc60 Fix key names in migration 2024-09-25 13:49:39 +05:30
Manav Rathi
e87a6a5106 Fix 2024-09-25 13:47:24 +05:30
Manav Rathi
7f132b1827 cgroups 2024-09-25 13:41:54 +05:30
Manav Rathi
b763cab1ba Generic pull 2024-09-25 13:26:15 +05:30
Manav Rathi
15a7e0b805 Mig 2024-09-25 12:27:51 +05:30
Manav Rathi
f702a93031 Save JSON directly 2024-09-25 12:23:56 +05:30
Manav Rathi
432ef74101 Support arbitrary JSON values in kv store 2024-09-25 12:18:06 +05:30
Manav Rathi
dd5dae2833 Notes 2024-09-25 12:06:16 +05:30
Manav Rathi
2c0739e1d1 Footprint 2024-09-25 10:40:17 +05:30
Manav Rathi
3449021272 Move to utils 2024-09-25 10:23:50 +05:30
Manav Rathi
c01a439b81 Split 2024-09-25 10:22:38 +05:30
Manav Rathi
1d5cca6ee1 namespace 2024-09-25 10:00:07 +05:30
Neeraj Gupta
821787f81d Merge remote-tracking branch 'origin/main' into handle_live_photo_dups 2024-09-25 06:34:51 +05:30
Neeraj Gupta
bc7704916a [mob] Bump version 2024-09-25 06:34:44 +05:30
Neeraj Gupta
7ed620d817 [mob] Fix dedupe for live photos with different size 2024-09-25 06:34:19 +05:30
Prateek Sunal
1bfa7b1998 fix: add show notes for mobile and desktop menu 2024-09-25 05:39:22 +05:30
131 changed files with 3920 additions and 2247 deletions

View File

@@ -618,7 +618,8 @@
{
"title": "T-Mobile",
"altNames": [
"T Mobile"
"T Mobile",
"T-Mobile ID"
]
},
{

View File

@@ -5,8 +5,10 @@ import 'package:shared_preferences/shared_preferences.dart';
// Add more language to the list only when at least 90% of the strings are
// translated in the corresponding language.
const List<Locale> appSupportedLocales = <Locale>[
Locale('ar'),
Locale('bg'),
Locale('de'),
Locale('el'),
Locale('en'),
Locale('es', 'ES'),
Locale('fa'),
@@ -17,8 +19,10 @@ const List<Locale> appSupportedLocales = <Locale>[
Locale('pl'),
Locale('pt', 'BR'),
Locale('ru'),
Locale('sk'),
Locale('tr'),
Locale('uk'),
Locale('vi'),
Locale("zh", "CN"),
];

View File

@@ -8,7 +8,6 @@ import 'package:ente_auth/ente_theme_data.dart';
import 'package:ente_auth/events/trigger_logout_event.dart';
import "package:ente_auth/l10n/l10n.dart";
import 'package:ente_auth/locale.dart';
import 'package:ente_auth/services/preference_service.dart';
import 'package:ente_auth/theme/text_style.dart';
import 'package:ente_auth/ui/account/email_entry_page.dart';
import 'package:ente_auth/ui/account/login_page.dart';
@@ -50,7 +49,6 @@ class _OnboardingPageState extends State<OnboardingPage> {
await autoLogoutAlert(context);
});
super.initState();
PreferenceService.instance.configureDefaults().ignore();
}
@override

View File

@@ -57,12 +57,6 @@ class PreferenceService {
await _prefs.setBool(kCompactMode, value);
}
Future<void> configureDefaults() async {
if (!_prefs.containsKey(kCompactMode)) {
await _prefs.setBool(kCompactMode, true);
}
}
Future<void> setHideCodes(bool value) async {
await _prefs.setBool(kShouldHideCodesKey, value);
Bus.instance.fire(IconsChangedEvent());

View File

@@ -15,6 +15,7 @@ import 'package:ente_auth/store/code_store.dart';
import 'package:ente_auth/theme/ente_theme.dart';
import 'package:ente_auth/ui/code_timer_progress.dart';
import 'package:ente_auth/ui/components/bottom_action_bar_widget.dart';
import 'package:ente_auth/ui/components/models/button_type.dart';
import 'package:ente_auth/ui/share/code_share.dart';
import 'package:ente_auth/ui/utils/icon_utils.dart';
import 'package:ente_auth/utils/dialog_util.dart';
@@ -263,6 +264,12 @@ class _CodeWidgetState extends State<CodeWidget> {
icon: Icons.qr_code_2_outlined,
onSelected: () => _onShowQrPressed(null),
),
if (widget.code.note.isNotEmpty)
MenuItem(
label: context.l10n.notes,
icon: Icons.notes_outlined,
onSelected: () => _onShowNotesPressed(null),
),
if (!widget.code.isTrashed)
MenuItem(
label:
@@ -487,6 +494,20 @@ class _CodeWidgetState extends State<CodeWidget> {
}
}
Future<void> _onShowNotesPressed([bool? pop]) async {
if (mounted && pop == true) {
Navigator.of(context).pop();
}
await showChoiceDialog(
context,
title: context.l10n.notes,
body: widget.code.note,
firstButtonLabel: context.l10n.close,
firstButtonType: ButtonType.secondary,
secondButtonLabel: null,
);
}
Future<void> _onEditPressed([bool? pop]) async {
if (mounted && pop == true) {
Navigator.of(context).pop();

View File

@@ -77,17 +77,19 @@ class DialogWidget extends StatelessWidget {
color: Colors.transparent,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ContentContainer(
title: title,
body: body,
icon: icon,
),
const SizedBox(height: 36),
Actions(buttons),
],
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ContentContainer(
title: title,
body: body,
icon: icon,
),
const SizedBox(height: 36),
Actions(buttons),
],
),
),
),
),

View File

@@ -5,7 +5,6 @@ import 'package:ente_auth/events/codes_updated_event.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/services/preference_service.dart';
import 'package:ente_auth/utils/platform_util.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class CoachMarkWidget extends StatelessWidget {

View File

@@ -124,10 +124,14 @@ class _ItemsWidgetState extends State<ItemsWidget> {
String _getLanguageName(Locale locale) {
switch (locale.languageCode) {
case 'ar':
return 'العربية';
case 'en':
return 'English';
case 'bg':
return 'Български';
case 'el':
return 'Ελληνικά';
case 'es':
switch (locale.countryCode) {
case 'ES':
@@ -154,10 +158,14 @@ class _ItemsWidgetState extends State<ItemsWidget> {
}
case 'ru':
return 'Русский';
case 'sk':
return 'Slovenčina';
case 'tr':
return 'Türkçe';
case 'uk':
return 'Українська';
case 'vi':
return 'Tiếng Việt';
case 'fi':
return 'Suomi';
case 'zh':

View File

@@ -102,16 +102,7 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
final bool hasAuthenticated = await LocalAuthenticationService
.instance
.requestLocalAuthentication(
context,
context.l10n.authToViewPasskey,
);
await PlatformUtil.refocusWindows();
if (hasAuthenticated) {
await onPasskeyClick(context);
}
await onPasskeyClick(context);
},
),
sectionOptionSpacing,

View File

@@ -142,7 +142,10 @@ class SettingsPage extends StatelessWidget {
sectionSpacing,
]);
if (Platform.isAndroid || kDebugMode) {
if (Platform.isAndroid ||
Platform.isWindows ||
Platform.isLinux ||
kDebugMode) {
contents.addAll([
const ThemeSwitchWidget(),
sectionSpacing,

View File

@@ -239,7 +239,7 @@ Future<ButtonResult?> showChoiceDialog(
required String title,
String? body,
required String firstButtonLabel,
String secondButtonLabel = "Cancel",
String? secondButtonLabel = "Cancel",
ButtonType firstButtonType = ButtonType.neutral,
ButtonType secondButtonType = ButtonType.secondary,
ButtonAction firstButtonAction = ButtonAction.first,
@@ -258,13 +258,14 @@ Future<ButtonResult?> showChoiceDialog(
onTap: firstButtonOnTap,
buttonAction: firstButtonAction,
),
ButtonWidget(
buttonType: secondButtonType,
labelText: secondButtonLabel,
isInAlert: true,
onTap: secondButtonOnTap,
buttonAction: secondButtonAction,
),
if (secondButtonLabel != null)
ButtonWidget(
buttonType: secondButtonType,
labelText: secondButtonLabel,
isInAlert: true,
onTap: secondButtonOnTap,
buttonAction: secondButtonAction,
),
];
return showDialogWidget(
context: context,

View File

@@ -16,8 +16,8 @@ SetupIconFile={{SETUP_ICON_FILE}}
WizardStyle=modern
;PrivilegesRequired={{PRIVILEGES_REQUIRED}}
PrivilegesRequiredOverridesAllowed=dialog
ArchitecturesAllowed=x64
ArchitecturesInstallIn64BitMode=x64
ArchitecturesAllowed=x64compatible
ArchitecturesInstallIn64BitMode=x64compatible
UninstallDisplayIcon={app}\auth.exe
[Languages]

View File

@@ -1,11 +1,16 @@
# CHANGELOG
## v1.7.5 (Unreleased)
## v1.7.6 (Unreleased)
- Directly upload to selected album on drag and drop.
- Include shared files in export.
- .
## v1.7.5
- Face grouping (beta).
- Include shared files in export.
- Directly upload to selected album on drag and drop.
- Improve heuristics for clubbing a photo and video into a live photo.
## v1.7.4
- Improved date search, including support for day of week and hour of day.

View File

@@ -1,6 +1,6 @@
{
"name": "ente",
"version": "1.7.5-beta",
"version": "1.7.6-beta",
"private": true,
"description": "Desktop client for Ente Photos",
"repository": "github:ente-io/photos-desktop",

View File

@@ -202,8 +202,8 @@ const createInferenceSession = async (modelPath: string) => {
};
const cachedCLIPImageSession = makeCachedInferenceSession(
"mobileclip_s2_image.onnx",
143061211 /* 143 MB */,
"mobileclip_s2_image_opset18_rgba_sim.onnx",
143093992 /* 143 MB */,
);
/**
@@ -211,10 +211,14 @@ const cachedCLIPImageSession = makeCachedInferenceSession(
*
* The embeddings are computed using ONNX runtime, with MobileCLIP as the model.
*/
export const computeCLIPImageEmbedding = async (input: Float32Array) => {
export const computeCLIPImageEmbedding = async (
input: Uint8ClampedArray,
inputShape: number[],
) => {
const session = await cachedCLIPImageSession();
const inputArray = new Uint8Array(input.buffer);
const feeds = {
input: new ort.Tensor("float32", input, [1, 3, 256, 256]),
input: new ort.Tensor("uint8", inputArray, inputShape),
};
const t = Date.now();
const results = await session.run(feeds);

View File

@@ -7,45 +7,41 @@ description:
# Machine learning
> [!NOTE]
>
> This document describes a beta feature that will be present in an upcoming
> release.
Ente supports on-device machine learning. This allows you to use the latest
advances in AI in a privacy preserving manner.
- You can search for your photos by the **faces** of the people in them. Ente
- You can search for your photos by the **Faces** of the people in them. Ente
will show you all the faces in a photo, and will also try to group similar
faces together to create clusters of people so that you can give them names,
and quickly find all photos with a given person in them.
- You can search for your photos by typing natural language descriptions of
them. For example, you can search for "night", "by the seaside", or "the red
motorcycle next to a fountain". Within the app, this ability is sometimes
referred to as **magic search**.
motorcycle next to a fountain". Within the app, this ability is referred to
as **Magic search**.
- We will build on this foundation to add more forms of advanced search.
You can enable face recognition and magic search in the app's preferences on
either the mobile app or the desktop app.
You can enable face and magic search in the app's preferences on either the
mobile app or the desktop app.
On mobile, this is available under `General > Advanced > Machine learning`.
If you have a big library, we recommend enabling this on the desktop app first,
because it can index your existing photos faster (The app needs to download your
originals to index them which can happen faster over WiFi, and indexing is also
faster on your computer as compared to your mobile device).
On desktop, this is available under `Preferences > Machine learning`.
Once your existing photos have been indexed, then you can use either. The mobile
app is fast enough to easily and seamlessly index the new photos that you take.
---
The app needs to download your original photos to index them. This is faster
over WiFi. Indexing is also faster on your computer as compared to your mobile
device.
> [!TIP]
>
> Even for the initial indexing, you don't necessarily need the desktop app, it
> just will be a bit faster.
> If you have a large library on Ente, we recommend enabling this feature on the
> desktop app first, because it can index your existing photos faster. Once your
> existing photos have been indexed, then you can use either. The mobile app is
> fast enough to index new photos as they are being backed up.
The indexes are synced across all your devices automatically using the same
end-to-end encypted security that we use for syncing your photos.
end-to-end encrypted security that we use for syncing your photos.
Note that the desktop app does not currently support viewing and modifying the
automatically generated face groupings, that is only supported by the mobile
app.
Note that the desktop app does not currently support modifying the face
groupings, that is only supported by the mobile app.

View File

@@ -4,6 +4,7 @@ import 'package:photos/core/network/network.dart';
import 'package:photos/db/files_db.dart';
import 'package:photos/models/duplicate_files.dart';
import 'package:photos/models/file/file.dart';
import "package:photos/models/file/file_type.dart";
import "package:photos/services/collections_service.dart";
import "package:photos/services/files_service.dart";
@@ -27,7 +28,7 @@ class DeduplicationService {
}
// Returns a list of DuplicateFiles, where each DuplicateFiles object contains
// a list of files that have the same size and hash
// a list of files that have the same hash
Future<List<DuplicateFiles>> _getDuplicateFiles() async {
Map<int, int> uploadIDToSize = {};
final bool hasFileSizes = await FilesService.instance.hasMigratedSizes();
@@ -62,29 +63,55 @@ class DeduplicationService {
final Map<String, List<EnteFile>> sizeHashToFilesMap = {};
final Map<String, Set<int>> sizeHashToCollectionsSet = {};
final Map<String, List<EnteFile>> livePhotoHashToFilesMap = {};
final Map<String, Set<int>> livePhotoHashToCollectionsSet = {};
final Set<int> processedFileIds = <int>{};
for (final file in filteredFiles) {
final key = '${file.fileSize}-${file.hash}';
if (!sizeHashToFilesMap.containsKey(key)) {
sizeHashToFilesMap[key] = <EnteFile>[];
sizeHashToCollectionsSet[key] = <int>{};
}
sizeHashToCollectionsSet[key]!.add(file.collectionID!);
if (!processedFileIds.contains(file.uploadedFileID)) {
sizeHashToFilesMap[key]!.add(file);
processedFileIds.add(file.uploadedFileID!);
// Note: For live photos, the zipped file size could be different if
// the files were uploaded from different devices. So, we dedupe live
// photos based on hash only.
if (file.fileType == FileType.livePhoto) {
final key = '${file.hash}';
if (!livePhotoHashToFilesMap.containsKey(key)) {
livePhotoHashToFilesMap[key] = <EnteFile>[];
livePhotoHashToCollectionsSet[key] = <int>{};
}
livePhotoHashToCollectionsSet[key]!.add(file.collectionID!);
if (!processedFileIds.contains(file.uploadedFileID)) {
livePhotoHashToFilesMap[key]!.add(file);
processedFileIds.add(file.uploadedFileID!);
}
} else {
final key = '${file.fileSize}-${file.hash}';
if (!sizeHashToFilesMap.containsKey(key)) {
sizeHashToFilesMap[key] = <EnteFile>[];
sizeHashToCollectionsSet[key] = <int>{};
}
sizeHashToCollectionsSet[key]!.add(file.collectionID!);
if (!processedFileIds.contains(file.uploadedFileID)) {
sizeHashToFilesMap[key]!.add(file);
processedFileIds.add(file.uploadedFileID!);
}
}
}
final List<DuplicateFiles> dupesBySizeHash = [];
final List<DuplicateFiles> dupesByHash = [];
for (final key in sizeHashToFilesMap.keys) {
final List<EnteFile> files = sizeHashToFilesMap[key]!;
final Set<int> collectionIds = sizeHashToCollectionsSet[key]!;
if (files.length > 1) {
final size = files[0].fileSize!;
dupesBySizeHash.add(DuplicateFiles(files, size, collectionIds));
dupesByHash.add(DuplicateFiles(files, size, collectionIds));
}
}
return dupesBySizeHash;
for (final key in livePhotoHashToFilesMap.keys) {
final List<EnteFile> files = livePhotoHashToFilesMap[key]!;
final Set<int> collectionIds = livePhotoHashToCollectionsSet[key]!;
if (files.length > 1 && (files.first.fileSize ?? 0) > 0) {
dupesByHash
.add(DuplicateFiles(files, files.first.fileSize!, collectionIds));
}
}
return dupesByHash;
}
Future<DuplicateFilesResponse> _fetchDuplicateFileIDs() async {

View File

@@ -107,6 +107,14 @@ class EntityService {
}
}
Future<void> syncEntity(EntityType type) async {
try {
await _remoteToLocalSync(type);
} catch (e) {
_logger.severe("Failed to sync entities", e);
}
}
Future<void> _remoteToLocalSync(EntityType type) async {
final int lastSyncTime =
_prefs.getInt(_getEntityLastSyncTimePrefix(type)) ?? 0;
@@ -116,7 +124,6 @@ class EntityService {
limit: fetchLimit,
);
if (result.isEmpty) {
debugPrint("No $type entries to sync");
return;
}
final bool hasMoreItems = result.length == fetchLimit;

View File

@@ -6,6 +6,8 @@ import "package:logging/logging.dart";
import "package:photos/core/event_bus.dart";
import "package:photos/events/diff_sync_complete_event.dart";
import "package:photos/events/people_changed_event.dart";
import "package:photos/models/api/entity/type.dart";
import "package:photos/services/entity_service.dart";
import "package:photos/services/machine_learning/face_ml/face_detection/detection.dart";
import "package:photos/services/machine_learning/face_ml/face_detection/face_detection_service.dart";
import "package:photos/services/machine_learning/face_ml/face_embedding/face_embedding_service.dart";
@@ -59,6 +61,7 @@ class FaceRecognitionService {
return;
}
_isSyncing = true;
await EntityService.instance.syncEntity(EntityType.cgroup);
if (_shouldSyncPeople) {
await PersonService.instance.reconcileClusters();
Bus.instance.fire(PeopleChangedEvent(type: PeopleEventType.syncDone));

View File

@@ -335,7 +335,7 @@ class ClusterFeedbackService {
}
Future<void> ignoreCluster(String clusterID) async {
await PersonService.instance.addPerson('', clusterID);
await PersonService.instance.addPerson('', clusterID, isHidden: true);
Bus.instance.fire(PeopleChangedEvent());
return;
}

View File

@@ -8,6 +8,8 @@ import "package:photos/db/ml/db.dart";
import "package:photos/events/people_changed_event.dart";
import "package:photos/extensions/stop_watch.dart";
import "package:photos/models/api/entity/type.dart";
import "package:photos/models/file/file.dart";
import 'package:photos/models/ml/face/face.dart';
import "package:photos/models/ml/face/person.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/entity_service.dart";
@@ -292,6 +294,24 @@ class PersonService {
await faceMLDataDB.bulkAssignClusterToPersonID(clusterToPersonID);
}
Future<void> updateAvatar(PersonEntity p, EnteFile file) async {
final Face? face = await MLDataDB.instance.getCoverFaceForPerson(
recentFileID: file.uploadedFileID!,
personID: p.remoteID,
);
if (face == null) {
throw Exception(
"No face found for person ${p.remoteID} in file ${file.uploadedFileID}",
);
}
final person = (await getPerson(p.remoteID))!;
final updatedPerson = person.copyWith(
data: person.data.copyWith(avatarFaceId: face.faceID),
);
await _updatePerson(updatedPerson);
}
Future<void> updateAttributes(
String id, {
String? name,

View File

@@ -133,7 +133,7 @@ class MLService {
}
if (_mlControllerStatus == true) {
// refresh discover section
MagicCacheService.instance.updateCache().ignore();
MagicCacheService.instance.updateCache(forced: force).ignore();
}
await indexAllImages();
if ((await MLDataDB.instance.getUnclusteredFaceCount()) > 0) {

View File

@@ -1,15 +1,15 @@
import "dart:typed_data" show Uint8List, Float32List;
import "dart:typed_data" show Int32List, Uint8List;
import "dart:ui" show Image;
import "package:logging/logging.dart";
import "package:onnx_dart/onnx_dart.dart";
import "package:onnxruntime/onnxruntime.dart";
import "package:photos/services/machine_learning/ml_model.dart";
import "package:photos/utils/image_ml_util.dart";
import "package:photos/utils/ml_util.dart";
class ClipImageEncoder extends MlModel {
static const kRemoteBucketModelPath = "mobileclip_s2_image.onnx";
static const kRemoteBucketModelPath =
"mobileclip_s2_image_opset18_rgba_sim.onnx";
static const _modelName = "ClipImageEncoder";
@override
@@ -34,16 +34,13 @@ class ClipImageEncoder extends MlModel {
int? enteFileID,
]) async {
final startTime = DateTime.now();
final inputList = await preprocessImageClip(image, rawRgbaBytes);
final preprocessingTime = DateTime.now();
final preprocessingMs =
preprocessingTime.difference(startTime).inMilliseconds;
final inputShape = <int>[image.height, image.width, 4]; // [H, W, C]
late List<double> result;
try {
if (MlModel.usePlatformPlugin) {
result = await _runPlatformPluginPredict(inputList);
result = await _runPlatformPluginPredict(rawRgbaBytes, inputShape);
} else {
result = _runFFIBasedPredict(inputList, sessionAddress);
result = _runFFIBasedPredict(rawRgbaBytes, inputShape, sessionAddress);
}
} catch (e, stackTrace) {
_logger.severe(
@@ -53,21 +50,22 @@ class ClipImageEncoder extends MlModel {
);
rethrow;
}
final inferTime = DateTime.now();
final inferenceMs = inferTime.difference(preprocessingTime).inMilliseconds;
final totalMs = inferTime.difference(startTime).inMilliseconds;
final totalMs = DateTime.now().difference(startTime).inMilliseconds;
_logger.info(
"Clip image predict took $totalMs ms${enteFileID != null ? " with fileID $enteFileID" : ""} (inference: $inferenceMs ms, preprocessing: $preprocessingMs ms)",
"Clip image predict took $totalMs ms${enteFileID != null ? " with fileID $enteFileID" : ""}",
);
return result;
}
static List<double> _runFFIBasedPredict(
Float32List inputList,
Uint8List inputImageList,
List<int> inputImageShape,
int sessionAddress,
) {
final inputOrt =
OrtValueTensor.createTensorWithDataList(inputList, [1, 3, 256, 256]);
final inputOrt = OrtValueTensor.createTensorWithDataList(
inputImageList,
inputImageShape,
);
final inputs = {'input': inputOrt};
final session = OrtSession.fromAddress(sessionAddress);
final runOptions = OrtRunOptions();
@@ -83,11 +81,13 @@ class ClipImageEncoder extends MlModel {
}
static Future<List<double>> _runPlatformPluginPredict(
Float32List inputImageList,
Uint8List inputImageList,
List<int> inputImageShape,
) async {
final OnnxDart plugin = OnnxDart();
final result = await plugin.predict(
final result = await plugin.predictRgba(
inputImageList,
Int32List.fromList(inputImageShape),
_modelName,
);
final List<double> embedding = result!.sublist(0, 512);

View File

@@ -205,7 +205,7 @@ class MagicCacheService {
queueUpdate("Prompts data updated");
} else if (lastMagicCacheUpdateTime <
DateTime.now()
.subtract(const Duration(days: 1))
.subtract(const Duration(hours: 12))
.millisecondsSinceEpoch) {
queueUpdate("Cache is old");
}

View File

@@ -121,13 +121,7 @@ class RemoteSyncService {
await syncDeviceCollectionFilesForUpload();
}
await _pullDiff();
// sync trash but consume error during initial launch.
// this is to ensure that we don't pause upload due to any error during
// the trash sync. Impact: We may end up re-uploading a file which was
// recently trashed.
await TrashSyncService.instance
.syncTrash()
.onError((e, s) => _logger.severe('trash sync failed', e, s));
await TrashSyncService.instance.syncTrash();
if (!hasSyncedBefore) {
await _prefs.setBool(_isFirstRemoteSyncDone, true);
await syncDeviceCollectionFilesForUpload();

View File

@@ -696,10 +696,7 @@ class _FileSelectionActionsWidgetState
Future<void> _setPersonCover() async {
final EnteFile file = widget.selectedFiles.files.first;
await PersonService.instance.updateAttributes(
widget.person!.remoteID,
avatarFaceId: file.uploadedFileID.toString(),
);
await PersonService.instance.updateAvatar(widget.person!, file);
widget.selectedFiles.clearAll();
if (mounted) {
setState(() => {});

View File

@@ -342,7 +342,8 @@ Future<MediaUploadData> _getMediaUploadDataFromAppCache(EnteFile file) async {
Map<String, int>? dimensions;
if (file.fileType == FileType.image) {
dimensions = await getImageHeightAndWith(imagePath: localPath);
} else {
} else if (thumbnailData != null) {
// the thumbnail null check is to ensure that we are able to generate thum
// for video, we need to use the thumbnail data with any max width/height
final thumbnailFilePath = await VideoThumbnail.thumbnailFile(
video: localPath,
@@ -406,14 +407,19 @@ Future<Uint8List?> getThumbnailFromInAppCacheFile(EnteFile file) async {
return null;
}
if (file.fileType == FileType.video) {
final thumbnailFilePath = await VideoThumbnail.thumbnailFile(
video: localFile.path,
imageFormat: ImageFormat.JPEG,
thumbnailPath: (await getTemporaryDirectory()).path,
maxWidth: thumbnailLargeSize,
quality: 80,
);
localFile = File(thumbnailFilePath!);
try {
final thumbnailFilePath = await VideoThumbnail.thumbnailFile(
video: localFile.path,
imageFormat: ImageFormat.JPEG,
thumbnailPath: (await getTemporaryDirectory()).path,
maxWidth: thumbnailLargeSize,
quality: 80,
);
localFile = File(thumbnailFilePath!);
} catch (e) {
_logger.warning('Failed to generate video thumbnail', e);
return null;
}
}
var thumbnailData = await localFile.readAsBytes();
int compressionAttempts = 0;

View File

@@ -1,6 +1,6 @@
import "dart:async";
import "dart:io" show File, Platform;
import "dart:math" show exp, max, min, pi;
import "dart:math" show exp, min, pi;
import "dart:typed_data" show Float32List, Uint8List;
import "dart:ui";
@@ -231,44 +231,6 @@ Future<(Float32List, Dimensions)> preprocessImageToFloat32ChannelsFirst(
return (processedBytes, Dimensions(width: scaledWidth, height: scaledHeight));
}
Future<Float32List> preprocessImageClip(
Image image,
Uint8List rawRgbaBytes,
) async {
const int requiredWidth = 256;
const int requiredHeight = 256;
const int requiredSize = 3 * requiredWidth * requiredHeight;
final scale = max(requiredWidth / image.width, requiredHeight / image.height);
final bool useAntiAlias = scale < 0.8;
final scaledWidth = (image.width * scale).round();
final scaledHeight = (image.height * scale).round();
final widthOffset = max(0, scaledWidth - requiredWidth) / 2;
final heightOffset = max(0, scaledHeight - requiredHeight) / 2;
final processedBytes = Float32List(requiredSize);
final buffer = Float32List.view(processedBytes.buffer);
int pixelIndex = 0;
const int greenOff = requiredHeight * requiredWidth;
const int blueOff = 2 * requiredHeight * requiredWidth;
for (var h = 0 + heightOffset; h < scaledHeight - heightOffset; h++) {
for (var w = 0 + widthOffset; w < scaledWidth - widthOffset; w++) {
final RGB pixel = _getPixelBilinear(
w / scale,
h / scale,
image,
rawRgbaBytes,
antiAlias: useAntiAlias,
);
buffer[pixelIndex] = pixel.$1 / 255;
buffer[pixelIndex + greenOff] = pixel.$2 / 255;
buffer[pixelIndex + blueOff] = pixel.$3 / 255;
pixelIndex++;
}
}
return processedBytes;
}
Future<(Float32List, List<AlignmentResult>, List<bool>, List<double>, Size)>
preprocessToMobileFaceNetFloat32List(
Image image,

View File

@@ -93,13 +93,15 @@ class OnnxDartPlugin: FlutterPlugin, MethodCallHandler {
val modelType = call.argument<String>("modelType")
val inputDataArray = call.argument<FloatArray>("inputData")
val inputIntDataArray = call.argument<IntArray>("inputDataInt")
val inputUint8DataArray = call.argument<ByteArray>("inputDataUint8")
val inputShapeArray = call.argument<IntArray>("inputShapeList")
if (sessionAddress == null || modelType == null || (inputDataArray == null && inputIntDataArray == null)) {
if (sessionAddress == null || modelType == null || (inputDataArray == null && inputIntDataArray == null && inputUint8DataArray == null)) {
result.error("INVALID_ARGUMENT", "Session address, model type, or input data is missing", null)
return
}
predict(ModelType.valueOf(modelType), sessionAddress, inputDataArray, inputIntDataArray, result)
predict(ModelType.valueOf(modelType), sessionAddress, inputDataArray, inputIntDataArray, inputUint8DataArray, inputShapeArray, result)
}
else -> {
result.notImplemented()
@@ -155,9 +157,8 @@ class OnnxDartPlugin: FlutterPlugin, MethodCallHandler {
}
}
private fun predict(modelType: ModelType, sessionAddress: Int, inputDataFloat: FloatArray? = null, inputDataInt: IntArray? = null, result: Result) {
// Assert that exactly one of inputDataFloat or inputDataInt is provided
assert((inputDataFloat != null).xor(inputDataInt != null)) { "Exactly one of inputDataFloat or inputDataInt must be provided" }
private fun predict(modelType: ModelType, sessionAddress: Int, inputDataFloat: FloatArray? = null, inputDataInt: IntArray? = null, inputUint8DataArray: ByteArray? = null, inputShapeArray: IntArray? = null, result: Result) {
assert((inputDataFloat != null).xor((inputDataInt != null).xor(inputUint8DataArray != null))) { "Exactly one of inputDataFloat, inputDataInt or inputUint8DataArray must be provided" }
scope.launch {
val modelState = sessionMap[modelType]
@@ -178,8 +179,11 @@ class OnnxDartPlugin: FlutterPlugin, MethodCallHandler {
inputTensorShape = longArrayOf(totalSize, 112, 112, 3)
}
ModelType.ClipImageEncoder -> {
inputTensorShape = longArrayOf(1, 3, 256, 256)
// inputTensorShape = longArrayOf(1, 3, 256, 256)
if (inputShapeArray != null) {
inputTensorShape = inputShapeArray.map { it.toLong() }.toLongArray()
} else {
result.error("INVALID_ARGUMENT", "Input shape is missing for clip image input", null)
}
}
ModelType.ClipTextEncoder -> {
inputTensorShape = longArrayOf(1, 77)
@@ -192,6 +196,7 @@ class OnnxDartPlugin: FlutterPlugin, MethodCallHandler {
val inputTensor = when {
inputDataFloat != null -> OnnxTensor.createTensor(env, FloatBuffer.wrap(inputDataFloat), inputTensorShape)
inputDataInt != null -> OnnxTensor.createTensor(env, IntBuffer.wrap(inputDataInt), inputTensorShape)
inputUint8DataArray != null -> OnnxTensor.createTensor(env, ByteBuffer.wrap(inputUint8DataArray), inputTensorShape, OnnxJavaType.UINT8)
else -> throw IllegalArgumentException("No input data provided")
}
val inputs = mutableMapOf<String, OnnxTensor>()
@@ -219,7 +224,7 @@ class OnnxDartPlugin: FlutterPlugin, MethodCallHandler {
inputTensor.close()
} catch (e: OrtException) {
withContext(Dispatchers.Main) {
result.error("PREDICTION_ERROR", "Error during prediction: ${e.message}", null)
result.error("PREDICTION_ERROR", "Error during prediction: ${e.message} ${e.stackTraceToString()}", null)
}
} catch (e: Exception) {
Log.e(TAG, "Error during prediction: ${e.message}", e)

View File

@@ -25,8 +25,13 @@ class OnnxDart {
String modelType, {
int sessionAddress = 0,
}) async {
final result = await OnnxDartPlatform.instance
.predict(inputData, null, modelType, sessionAddress: sessionAddress);
final result = await OnnxDartPlatform.instance.predict(
inputData,
null,
null,
modelType,
sessionAddress: sessionAddress,
);
return result;
}
@@ -35,8 +40,30 @@ class OnnxDart {
String modelType, {
int sessionAddress = 0,
}) async {
final result = await OnnxDartPlatform.instance
.predict(null, inputDataInt, modelType, sessionAddress: sessionAddress);
final result = await OnnxDartPlatform.instance.predict(
null,
inputDataInt,
null,
modelType,
sessionAddress: sessionAddress,
);
return result;
}
Future<Float32List?> predictRgba(
Uint8List inputBytes,
Int32List inputShape,
String modelType, {
int sessionAddress = 0,
}) async {
final result = await OnnxDartPlatform.instance.predict(
null,
null,
inputBytes,
modelType,
sessionAddress: sessionAddress,
inputShapeList: inputShape,
);
return result;
}
}

View File

@@ -40,8 +40,10 @@ class MethodChannelOnnxDart extends OnnxDartPlatform {
Future<Float32List?> predict(
Float32List? inputData,
Int32List? inputDataInt,
Uint8List? inputDataUint8,
String modelType, {
int sessionAddress = 0,
Int32List? inputShapeList,
}) {
return methodChannel.invokeMethod<Float32List?>(
'predict',
@@ -49,7 +51,9 @@ class MethodChannelOnnxDart extends OnnxDartPlatform {
'sessionAddress': sessionAddress,
'inputData': inputData,
'inputDataInt': inputDataInt,
'inputDataUint8': inputDataUint8,
'modelType': modelType,
"inputShapeList": inputShapeList,
},
);
}

View File

@@ -42,12 +42,12 @@ abstract class OnnxDartPlatform extends PlatformInterface {
Future<Float32List?> predict(
Float32List? inputData,
Int32List? inputDataInt,
Int32List? inputDataInt,
Uint8List? inputDataRgba,
String modelType, {
int sessionAddress = 0,
Int32List? inputShapeList,
}) {
throw UnimplementedError('predict() has not been implemented.');
}
}

View File

@@ -12,7 +12,7 @@ description: ente photos application
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.9.44+944
version: 0.9.46+946
publish_to: none
environment:

View File

@@ -158,7 +158,7 @@ const AuthNavbar: React.FC = () => {
startIcon={<LogoutOutlined />}
onClick={logout}
>
{t("LOGOUT")}
{t("logout")}
</OverflowMenuOption>
</OverflowMenu>
</HorizontalFlex>

View File

@@ -1,15 +1,14 @@
import type { Collection } from "@/media/collection";
import { PersonListHeader } from "@/new/photos/components/Gallery";
import {
GalleryBarImpl,
type GalleryBarImplProps,
} from "@/new/photos/components/Gallery/BarImpl";
import { PeopleHeader } from "@/new/photos/components/Gallery/PeopleHeader";
import {
collectionsSortBy,
type CollectionsSortBy,
type CollectionSummaries,
} from "@/new/photos/types/collection";
import { ensure } from "@/utils/ensure";
import { includes } from "@/utils/type-guards";
import {
getData,
@@ -20,7 +19,14 @@ import AllCollections from "components/Collections/AllCollections";
import { SetCollectionNamerAttributes } from "components/Collections/CollectionNamer";
import CollectionShare from "components/Collections/CollectionShare";
import { ITEM_TYPE, TimeStampListItem } from "components/PhotoList";
import { useCallback, useEffect, useMemo, useState } from "react";
import { AppContext } from "pages/_app";
import React, {
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from "react";
import { sortCollectionSummaries } from "services/collectionService";
import { SetFilesDownloadProgressAttributesCreator } from "types/gallery";
import {
@@ -80,6 +86,7 @@ type CollectionsProps = Omit<
*/
export const GalleryBarAndListHeader: React.FC<CollectionsProps> = ({
shouldHide,
showPeopleSectionButton,
mode,
onChangeMode,
collectionSummaries,
@@ -95,6 +102,8 @@ export const GalleryBarAndListHeader: React.FC<CollectionsProps> = ({
filesDownloadProgressAttributesList,
setFilesDownloadProgressAttributesCreator,
}) => {
const appContext = useContext(AppContext);
const [openAllCollectionDialog, setOpenAllCollectionDialog] =
useState(false);
const [openCollectionShareView, setOpenCollectionShareView] =
@@ -162,8 +171,13 @@ export const GalleryBarAndListHeader: React.FC<CollectionsProps> = ({
}
onCollectionCast={() => setOpenAlbumCastDialog(true)}
/>
) : activePerson ? (
<PeopleHeader
person={activePerson}
{...{ onSelectPerson, appContext }}
/>
) : (
<PersonListHeader person={ensure(activePerson)} />
<></>
),
itemType: ITEM_TYPE.HEADER,
height: 68,
@@ -174,7 +188,6 @@ export const GalleryBarAndListHeader: React.FC<CollectionsProps> = ({
toShowCollectionSummaries,
activeCollectionID,
isActiveCollectionDownloadInProgress,
people,
activePerson,
]);
@@ -186,6 +199,7 @@ export const GalleryBarAndListHeader: React.FC<CollectionsProps> = ({
<>
<GalleryBarImpl
{...{
showPeopleSectionButton,
mode,
onChangeMode,
activeCollectionID,

View File

@@ -66,7 +66,7 @@ export default function ExportFinished(props: Props) {
</DialogContent>
<DialogActions>
<Button color="secondary" size="large" onClick={props.onHide}>
{t("CLOSE")}
{t("close")}
</Button>
<Button size="large" color="primary" onClick={props.onResync}>
{t("EXPORT_AGAIN")}

View File

@@ -91,7 +91,7 @@ export default function ExportInProgress(props: Props) {
size="large"
onClick={props.closeExportDialog}
>
{t("CLOSE")}
{t("close")}
</Button>
<Button
size="large"

View File

@@ -65,7 +65,7 @@ const ExportPendingList = (props: Iprops) => {
title: t("PENDING_ITEMS"),
close: {
action: props.onClose,
text: t("CLOSE"),
text: t("close"),
},
}}
>

View File

@@ -82,7 +82,7 @@ export const FilesDownloadProgress: React.FC<FilesDownloadProgressProps> = ({
},
},
close: {
text: t("NO"),
text: t("no"),
variant: "secondary",
action: () => {},
},

View File

@@ -252,7 +252,7 @@ const Footer = ({ step, startFix, ...props }) => {
)}
{step == "completed" && (
<Button color="primary" size="large" onClick={props.hide}>
{t("CLOSE")}
{t("close")}
</Button>
)}
{(!step || step == "completed-with-errors") && (

View File

@@ -8,7 +8,7 @@ import { PHOTOS_PAGES } from "@ente/shared/constants/pages";
import { CustomError } from "@ente/shared/error";
import useMemoSingleThreaded from "@ente/shared/hooks/useMemoSingleThreaded";
import { styled } from "@mui/material";
import PhotoViewer from "components/PhotoViewer";
import PhotoViewer, { type PhotoViewerProps } from "components/PhotoViewer";
import { useRouter } from "next/router";
import { GalleryContext } from "pages/gallery";
import PhotoSwipe from "photoswipe";
@@ -72,6 +72,7 @@ interface Props {
isInHiddenSection?: boolean;
setFilesDownloadProgressAttributesCreator?: SetFilesDownloadProgressAttributesCreator;
selectable?: boolean;
onSelectPerson?: PhotoViewerProps["onSelectPerson"];
}
const PhotoFrame = ({
@@ -95,6 +96,7 @@ const PhotoFrame = ({
isInHiddenSection,
setFilesDownloadProgressAttributesCreator,
selectable,
onSelectPerson,
}: Props) => {
const [open, setOpen] = useState(false);
const [currentIndex, setCurrentIndex] = useState<number>(0);
@@ -580,6 +582,7 @@ const PhotoFrame = ({
setFilesDownloadProgressAttributesCreator={
setFilesDownloadProgressAttributesCreator
}
onSelectPerson={onSelectPerson}
/>
</Container>
);

View File

@@ -510,7 +510,7 @@ export function PhotoList({
itemType: ITEM_TYPE.OTHER,
item: (
<NothingContainer span={columns}>
<div>{t("NOTHING_HERE")}</div>
<div>{t("nothing_here")}</div>
</NothingContainer>
),
id: "empty-list-banner",

View File

@@ -12,11 +12,19 @@ import {
type ParsedMetadataDate,
} from "@/media/file-metadata";
import { FileType } from "@/media/file-type";
import { UnidentifiedFaces } from "@/new/photos/components/PeopleList";
import {
AnnotatedFacePeopleList,
UnclusteredFaceList,
} from "@/new/photos/components/PeopleList";
import { PhotoDateTimePicker } from "@/new/photos/components/PhotoDateTimePicker";
import { photoSwipeZIndex } from "@/new/photos/components/PhotoViewer";
import { tagNumericValue, type RawExifTags } from "@/new/photos/services/exif";
import { isMLEnabled } from "@/new/photos/services/ml";
import {
AnnotatedFacesForFile,
getAnnotatedFacesForFile,
isMLEnabled,
type AnnotatedFaceID,
} from "@/new/photos/services/ml";
import { EnteFile } from "@/new/photos/types/file";
import { formattedByteSize } from "@/new/photos/utils/units";
import CopyButton from "@ente/shared/components/CodeBlock/CopyButton";
@@ -61,7 +69,7 @@ export interface FileInfoExif {
parsed: ParsedMetadata | undefined;
}
interface FileInfoProps {
export interface FileInfoProps {
showInfo: boolean;
handleCloseInfo: () => void;
closePhotoViewer: () => void;
@@ -73,6 +81,10 @@ interface FileInfoProps {
fileToCollectionsMap?: Map<number, number[]>;
collectionNameMap?: Map<number, string>;
showCollectionChips: boolean;
/**
* Called when the user selects a person in the file info panel.
*/
onSelectPerson?: ((personID: string) => void) | undefined;
}
export const FileInfo: React.FC<FileInfoProps> = ({
@@ -87,6 +99,7 @@ export const FileInfo: React.FC<FileInfoProps> = ({
collectionNameMap,
showCollectionChips,
closePhotoViewer,
onSelectPerson,
}) => {
const { mapEnabled, updateMapEnabled, setDialogBoxAttributesV2 } =
useContext(AppContext);
@@ -97,6 +110,9 @@ export const FileInfo: React.FC<FileInfoProps> = ({
const [exifInfo, setExifInfo] = useState<ExifInfo | undefined>();
const [openRawExif, setOpenRawExif] = useState(false);
const [annotatedFaces, setAnnotatedFaces] = useState<
AnnotatedFacesForFile | undefined
>();
const location = useMemo(() => {
if (file) {
@@ -106,6 +122,21 @@ export const FileInfo: React.FC<FileInfoProps> = ({
return exif?.parsed?.location;
}, [file, exif]);
useEffect(() => {
if (!file) return;
let didCancel = false;
void (async () => {
const result = await getAnnotatedFacesForFile(file);
!didCancel && setAnnotatedFaces(result);
})();
return () => {
didCancel = true;
};
}, [file]);
useEffect(() => {
setExifInfo(parseExifInfo(exif));
}, [exif]);
@@ -129,6 +160,13 @@ export const FileInfo: React.FC<FileInfoProps> = ({
getMapDisableConfirmationDialog(() => updateMapEnabled(false)),
);
const handleSelectFace = (annotatedFaceID: AnnotatedFaceID) => {
if (onSelectPerson) {
onSelectPerson(annotatedFaceID.personID);
closePhotoViewer();
}
};
return (
<FileInfoSidebar open={showInfo} onClose={handleCloseInfo}>
<Titlebar onClose={handleCloseInfo} title={t("INFO")} backIsClose />
@@ -267,10 +305,17 @@ export const FileInfo: React.FC<FileInfoProps> = ({
</InfoItem>
)}
{isMLEnabled() && (
{isMLEnabled() && annotatedFaces && (
<>
{/* TODO-Cluster <PhotoPeopleList file={file} /> */}
<UnidentifiedFaces enteFile={file} />
<AnnotatedFacePeopleList
enteFile={file}
annotatedFaceIDs={annotatedFaces.annotatedFaceIDs}
onSelectFace={handleSelectFace}
/>
<UnclusteredFaceList
enteFile={file}
faceIDs={annotatedFaces.otherFaceIDs}
/>
</>
)}
</Stack>

View File

@@ -658,7 +658,7 @@ const ImageEditorOverlay = (props: IProps) => {
/>
</Tabs>
</HorizontalFlex>
<MenuSectionTitle title={t("RESET")} />
<MenuSectionTitle title={t("reset")} />
<MenuItemGroup
style={{
marginBottom: "0.5rem",

View File

@@ -48,7 +48,7 @@ import { SetFilesDownloadProgressAttributesCreator } from "types/gallery";
import { pauseVideo, playVideo } from "utils/photoFrame";
import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery";
import { getTrashFileMessage } from "utils/ui";
import { FileInfo, type FileInfoExif } from "./FileInfo";
import { FileInfo, type FileInfoExif, type FileInfoProps } from "./FileInfo";
import ImageEditorOverlay from "./ImageEditorOverlay";
import CircularProgressWithLabel from "./styledComponents/CircularProgressWithLabel";
import { ConversionFailedNotification } from "./styledComponents/ConversionFailedNotification";
@@ -98,7 +98,8 @@ const CaptionContainer = styled("div")(({ theme }) => ({
backgroundColor: theme.colors.backdrop.faint,
backdropFilter: `blur(${theme.colors.blur.base})`,
}));
interface Iprops {
export interface PhotoViewerProps {
isOpen: boolean;
items: any[];
currentIndex?: number;
@@ -115,9 +116,10 @@ interface Iprops {
fileToCollectionsMap: Map<number, number[]>;
collectionNameMap: Map<number, string>;
setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator;
onSelectPerson?: FileInfoProps["onSelectPerson"];
}
function PhotoViewer(props: Iprops) {
function PhotoViewer(props: PhotoViewerProps) {
const galleryContext = useContext(GalleryContext);
const appContext = useContext(AppContext);
const publicCollectionGalleryContext = useContext(
@@ -812,7 +814,7 @@ function PhotoViewer(props: Iprops) {
<button
className="pswp__button pswp__button--custom"
title={t("CLOSE_OPTION")}
title={t("close_key")}
onClick={handleClose}
>
<CloseIcon />
@@ -969,6 +971,7 @@ function PhotoViewer(props: Iprops) {
refreshPhotoswipe={refreshPhotoswipe}
fileToCollectionsMap={props.fileToCollectionsMap}
collectionNameMap={props.collectionNameMap}
onSelectPerson={props.onSelectPerson}
/>
<ImageEditorOverlay
show={showImageEditorOverlay}

View File

@@ -506,7 +506,7 @@ const UtilitySection: React.FC<UtilitySectionProps> = ({ closeSidebar }) => {
<EnteMenuItem
variant="secondary"
onClick={openRecoveryKeyModal}
label={t("RECOVERY_KEY")}
label={t("recovery_key")}
/>
{isInternalUserViaEmailCheck() && (
<EnteMenuItem
@@ -600,13 +600,13 @@ const HelpSection: React.FC = () => {
<>
<EnteMenuItem
onClick={requestFeature}
label={t("REQUEST_FEATURE")}
label={t("request_feature")}
variant="secondary"
/>
<EnteMenuItem
onClick={contactSupport}
labelComponent={
<span title="support@ente.io">{t("SUPPORT")}</span>
<span title="support@ente.io">{t("support")}</span>
}
variant="secondary"
/>
@@ -634,9 +634,9 @@ const ExitSection: React.FC = () => {
const confirmLogout = () => {
setDialogMessage({
title: t("LOGOUT_MESSAGE"),
title: t("logout_message"),
proceed: {
text: t("LOGOUT"),
text: t("logout"),
action: logout,
variant: "critical",
},
@@ -649,7 +649,7 @@ const ExitSection: React.FC = () => {
<EnteMenuItem
onClick={confirmLogout}
color="critical"
label={t("LOGOUT")}
label={t("logout")}
variant="secondary"
/>
<EnteMenuItem

View File

@@ -22,7 +22,7 @@ export function UploadProgressFooter() {
</Button>
) : (
<Button variant="contained" fullWidth onClick={onClose}>
{t("CLOSE")}
{t("close")}
</Button>
))}
</DialogActions>

View File

@@ -57,7 +57,7 @@ export default function UploadProgress({
action: props.cancelUploads,
},
close: {
text: t("NO"),
text: t("no"),
variant: "secondary",
action: () => {},
},

View File

@@ -21,7 +21,7 @@ export default function UserNameInputDialog({
<DialogBox maxWidth="xs" open={open} onClose={onClose}>
<DialogIcon icon={<AutoAwesomeOutlinedIcon />} />
<DialogTitle>{t("ENTER_NAME")}</DialogTitle>
<DialogTitle>{t("enter_name")}</DialogTitle>
<DialogContent>
<Typography color={"text.muted"} pb={1}>

View File

@@ -162,7 +162,7 @@ function PlanSelectorCard(props: PlanSelectorCardProps) {
appContext.setDialogMessage({
title: t("OPEN_PLAN_SELECTOR_MODAL_FAILED"),
content: t("UNKNOWN_ERROR"),
close: { text: t("CLOSE"), variant: "secondary" },
close: { text: t("close"), variant: "secondary" },
proceed: {
text: t("REOPEN_PLAN_SELECTOR_MODAL"),
variant: "accent",

View File

@@ -191,7 +191,7 @@ const SelectedFileOptions = ({
<DownloadIcon />
</IconButton>
</Tooltip>
<Tooltip title={t("ADD")}>
<Tooltip title={t("add")}>
<IconButton onClick={addToCollection}>
<AddIcon />
</IconButton>
@@ -225,7 +225,7 @@ const SelectedFileOptions = ({
<DownloadIcon />
</IconButton>
</Tooltip>
<Tooltip title={t("ADD")}>
<Tooltip title={t("add")}>
<IconButton onClick={addToCollection}>
<AddIcon />
</IconButton>
@@ -328,7 +328,7 @@ const SelectedFileOptions = ({
<DownloadIcon />
</IconButton>
</Tooltip>
<Tooltip title={t("ADD")}>
<Tooltip title={t("add")}>
<IconButton onClick={addToCollection}>
<AddIcon />
</IconButton>

View File

@@ -47,7 +47,14 @@ import isElectron from "is-electron";
import type { AppProps } from "next/app";
import { useRouter } from "next/router";
import "photoswipe/dist/photoswipe.css";
import { createContext, useContext, useEffect, useRef, useState } from "react";
import {
createContext,
useCallback,
useContext,
useEffect,
useRef,
useState,
} from "react";
import LoadingBar from "react-top-loading-bar";
import { resumeExportsIfNeeded } from "services/export";
import { photosLogout } from "services/logout";
@@ -83,6 +90,7 @@ type AppContextT = AccountsContextT & {
themeColor: THEME_COLOR;
setThemeColor: (themeColor: THEME_COLOR) => void;
somethingWentWrong: () => void;
onGenericError: (error: unknown) => void;
isCFProxyDisabled: boolean;
setIsCFProxyDisabled: (disabled: boolean) => void;
};
@@ -269,12 +277,28 @@ export default function App({ Component, pageProps }: AppProps) {
const closeMessageDialog = () => setMessageDialogView(false);
const closeDialogBoxV2 = () => setDialogBoxV2View(false);
const somethingWentWrong = () =>
setDialogMessage({
title: t("error"),
close: { variant: "critical" },
content: t("UNKNOWN_ERROR"),
});
// Use `onGenericError` instead.
const somethingWentWrong = useCallback(
() =>
setDialogMessage({
title: t("error"),
close: { variant: "critical" },
content: t("UNKNOWN_ERROR"),
}),
[setDialogMessage],
);
const onGenericError = useCallback(
(e: unknown) => (
log.error("Error", e),
setDialogBoxAttributesV2({
title: t("error"),
content: t("UNKNOWN_ERROR"),
close: { variant: "critical" },
})
),
[setDialogBoxAttributesV2],
);
const logout = () => {
void photosLogout().then(() => router.push("/"));
@@ -294,6 +318,7 @@ export default function App({ Component, pageProps }: AppProps) {
themeColor,
setThemeColor,
somethingWentWrong,
onGenericError,
setDialogBoxAttributesV2,
mapEnabled,
updateMapEnabled,

View File

@@ -3,8 +3,12 @@ import { NavbarBase } from "@/base/components/Navbar";
import { useIsMobileWidth } from "@/base/hooks";
import log from "@/base/log";
import type { Collection } from "@/media/collection";
import { SearchResultsHeader } from "@/new/photos/components/Gallery";
import {
PeopleEmptyState,
SearchResultsHeader,
} from "@/new/photos/components/Gallery";
import type { GalleryBarMode } from "@/new/photos/components/Gallery/BarImpl";
import { GalleryPeopleState } from "@/new/photos/components/Gallery/PeopleHeader";
import {
SearchBar,
type SearchBarProps,
@@ -251,7 +255,7 @@ export default function Gallery() {
accept: ".zip",
});
const syncInProgress = useRef(true);
const syncInProgress = useRef(false);
const syncInterval = useRef<NodeJS.Timeout>();
const resync = useRef<{ force: boolean; silent: boolean }>();
@@ -317,8 +321,8 @@ export default function Gallery() {
// If visible, what should the (sticky) gallery bar show.
const [barMode, setBarMode] = useState<GalleryBarMode>("albums");
// The currently selected person in the gallery bar (if any).
const [activePerson, setActivePerson] = useState<Person | undefined>();
// The ID of the currently selected person in the gallery bar (if any).
const [activePersonID, setActivePersonID] = useState<string | undefined>();
const people = useSyncExternalStore(peopleSubscribe, peopleSnapshot);
@@ -526,9 +530,16 @@ export default function Gallery() {
);
}, [collections, activeCollectionID]);
const filteredData = useMemoSingleThreaded(async (): Promise<
EnteFile[]
> => {
// The derived UI state when we are in "people" mode.
//
// TODO: This spawns even more workarounds below. Move this to a
// reducer/store.
type DerivedState1 = {
filteredData: EnteFile[];
galleryPeopleState: GalleryPeopleState | undefined;
};
const derived1: DerivedState1 = useMemoSingleThreaded(async () => {
if (
!files ||
!user ||
@@ -536,32 +547,56 @@ export default function Gallery() {
!hiddenFiles ||
!archivedCollections
) {
return;
return { filteredData: [], galleryPeopleState: undefined };
}
if (activeCollectionID === TRASH_SECTION && !selectedSearchOption) {
return getUniqueFiles([
const filteredData = getUniqueFiles([
...trashedFiles,
...files.filter((file) => tempDeletedFileIds?.has(file.id)),
]);
return { filteredData, galleryPeopleState: undefined };
}
let filteredFiles: EnteFile[] = [];
let galleryPeopleState: GalleryPeopleState;
if (selectedSearchOption) {
filteredFiles = await filterSearchableFiles(
selectedSearchOption.suggestion,
);
} else if (barMode == "people") {
const pfSet = new Set(ensure(activePerson).fileIDs);
let filteredPeople = people ?? [];
if (tempDeletedFileIds?.size ?? tempHiddenFileIds?.size) {
// Prune the in-memory temp updates from the actual state to
// obtain the UI state. Kept inside an preflight check to so
// that the common path remains fast.
filteredPeople = filteredPeople
.map((p) => ({
...p,
fileIDs: p.fileIDs.filter(
(id) =>
!tempDeletedFileIds?.has(id) &&
!tempHiddenFileIds?.has(id),
),
}))
.filter((p) => p.fileIDs.length > 0);
}
const activePerson =
filteredPeople.find((p) => p.id == activePersonID) ??
// We don't have an "All" pseudo-album in people mode currently,
// so default to the first person in the list.
filteredPeople[0];
const pfSet = new Set(activePerson?.fileIDs ?? []);
filteredFiles = getUniqueFiles(
files.filter(({ id }) => {
if (!pfSet.has(id)) return false;
// TODO-Cluster
// if (tempDeletedFileIds?.has(id)) return false;
// if (tempHiddenFileIds?.has(id)) return false;
return true;
}),
);
galleryPeopleState = {
activePerson,
people: filteredPeople,
};
} else {
const baseFiles = barMode == "hidden-albums" ? hiddenFiles : files;
filteredFiles = getUniqueFiles(
@@ -627,10 +662,10 @@ export default function Gallery() {
}
const sortAsc = activeCollection?.pubMagicMetadata?.data?.asc ?? false;
if (sortAsc) {
return sortFiles(filteredFiles, true);
} else {
return filteredFiles;
filteredFiles = sortFiles(filteredFiles, true);
}
return { filteredData: filteredFiles, galleryPeopleState };
}, [
barMode,
files,
@@ -642,9 +677,15 @@ export default function Gallery() {
selectedSearchOption,
activeCollectionID,
archivedCollections,
activePerson,
people,
activePersonID,
]);
const { filteredData, galleryPeopleState } = derived1 ?? {
filteredData: [],
galleryPeopleState: undefined,
};
const selectAll = (e: KeyboardEvent) => {
// ignore ctrl/cmd + a if the user is typing in a text field
if (
@@ -675,10 +716,13 @@ export default function Gallery() {
count: 0,
collectionID: activeCollectionID,
context:
barMode == "people" && activePerson
? { mode: "people" as const, personID: activePerson.id }
barMode == "people" && galleryPeopleState?.activePerson?.id
? {
mode: "people" as const,
personID: galleryPeopleState.activePerson.id,
}
: {
mode: "albums" as const,
mode: barMode as "albums" | "hidden-albums",
collectionID: ensure(activeCollectionID),
},
};
@@ -741,6 +785,7 @@ export default function Gallery() {
resync.current = { force, silent };
return;
}
const isForced = syncInProgress.current && force;
syncInProgress.current = true;
try {
const token = getToken();
@@ -761,7 +806,14 @@ export default function Gallery() {
await syncFiles("normal", normalCollections, setFiles);
await syncFiles("hidden", hiddenCollections, setHiddenFiles);
await syncTrash(collections, setTrashedFiles);
await sync();
// syncWithRemote is called with the force flag set to true before
// doing an upload. So it is possible, say when resuming a pending
// upload, that we get two syncWithRemotes happening in parallel.
//
// Do the non-file-related sync only for one of these parallel ones.
if (!isForced) {
await sync();
}
} catch (e) {
switch (e.message) {
case CustomError.SESSION_EXPIRED:
@@ -996,7 +1048,7 @@ export default function Gallery() {
setActiveCollectionID(searchOption.suggestion.collectionID);
} else {
setBarMode("people");
setActivePerson(searchOption.suggestion.person);
setActivePersonID(searchOption.suggestion.person.id);
}
} else {
setIsInSearchMode(!!searchOption);
@@ -1049,23 +1101,23 @@ export default function Gallery() {
};
const handleSelectPerson = (person: Person | undefined) => {
// The person bar currently does not have an "all" mode, so default to
// the first person when no specific person is provided. This can happen
// when the user clicks the "People" header in the search empty state (it
// is guaranteed that this header will only be shown if there is at
// least one person).
setActivePerson(person ?? ensure(people[0]));
setActivePersonID(person?.id);
setBarMode("people");
};
if (activePerson) {
log.debug(() => ["person", activePerson]);
}
const handleSelectFileInfoPerson = (personID: string) => {
setActivePersonID(personID);
setBarMode("people");
};
if (!collectionSummaries || !filteredData) {
return <div></div>;
}
// `people` will be undefined only when ML is disabled, otherwise it'll be
// an empty array (even if people are loading).
const showPeopleSectionButton = people !== undefined;
return (
<GalleryContext.Provider
value={{
@@ -1135,7 +1187,12 @@ export default function Gallery() {
attributes={fixCreationTimeAttributes}
/>
<NavbarBase
sx={{ background: "transparent", position: "absolute" }}
sx={{
background: "transparent",
position: "absolute",
// Override the default 16px we get from NavbarBase
marginBottom: "12px",
}}
>
{barMode == "hidden-albums" ? (
<HiddenSectionNavbarContents
@@ -1165,8 +1222,9 @@ export default function Gallery() {
activeCollectionID,
setActiveCollectionID,
hiddenCollectionSummaries,
people,
activePerson,
showPeopleSectionButton,
people: galleryPeopleState?.people ?? [],
activePerson: galleryPeopleState?.activePerson,
onSelectPerson: handleSelectPerson,
setCollectionNamerAttributes,
setPhotoListHeader,
@@ -1229,6 +1287,11 @@ export default function Gallery() {
!hiddenFiles?.length &&
activeCollectionID === ALL_SECTION ? (
<GalleryEmptyState openUploader={openUploader} />
) : !isInSearchMode &&
!isFirstLoad &&
barMode == "people" &&
!galleryPeopleState?.activePerson ? (
<PeopleEmptyState />
) : (
<PhotoFrame
page={PAGES.GALLERY}
@@ -1242,7 +1305,7 @@ export default function Gallery() {
setTempDeletedFileIds={setTempDeletedFileIds}
setIsPhotoSwipeOpen={setIsPhotoSwipeOpen}
activeCollectionID={activeCollectionID}
activePersonID={activePerson?.id}
activePersonID={galleryPeopleState?.activePerson?.id}
enableDownload={true}
fileToCollectionsMap={fileToCollectionsMap}
collectionNameMap={collectionNameMap}
@@ -1254,6 +1317,7 @@ export default function Gallery() {
setFilesDownloadProgressAttributesCreator
}
selectable={true}
onSelectPerson={handleSelectFileInfoPerson}
/>
)}
{selected.count > 0 &&

View File

@@ -318,9 +318,9 @@ const Slideshow: React.FC = () => {
/images/onboarding-lock/3x.png 3x"
/>
<FeatureText>
<Trans i18nKey={"HERO_SLIDE_1_TITLE"} />
<Trans i18nKey={"intro_slide_1_title"} />
</FeatureText>
<TextContainer>{t("HERO_SLIDE_1")}</TextContainer>
<TextContainer>{t("intro_slide_1")}</TextContainer>
</Slide>
<Slide index={1}>
<SlideContents>
@@ -330,9 +330,9 @@ const Slideshow: React.FC = () => {
/images/onboarding-safe/3x.png 3x"
/>
<FeatureText>
<Trans i18nKey={"HERO_SLIDE_2_TITLE"} />
<Trans i18nKey={"intro_slide_2_title"} />
</FeatureText>
<TextContainer>{t("HERO_SLIDE_2")}</TextContainer>
<TextContainer>{t("intro_slide_2")}</TextContainer>
</SlideContents>
</Slide>
<Slide index={2}>
@@ -343,9 +343,9 @@ const Slideshow: React.FC = () => {
/images/onboarding-sync/3x.png 3x"
/>
<FeatureText>
<Trans i18nKey={"HERO_SLIDE_3_TITLE"} />
<Trans i18nKey={"intro_slide_3_title"} />
</FeatureText>
<TextContainer>{t("HERO_SLIDE_3")}</TextContainer>
<TextContainer>{t("intro_slide_3")}</TextContainer>
</SlideContents>
</Slide>
</Slider>

View File

@@ -20,7 +20,7 @@ export const getDownloadAppMessage = (): DialogBoxAttributes => {
variant: "accent",
},
close: {
text: t("CLOSE"),
text: t("close"),
},
};
};
@@ -182,7 +182,7 @@ export const getEditorCloseConfirmationMessage = (
content: t("CONFIRM_EDITOR_CLOSE_DESCRIPTION"),
proceed: {
action: doClose,
text: t("CLOSE"),
text: t("close"),
variant: "critical",
},
close: { text: t("cancel") },

View File

@@ -99,7 +99,7 @@ const Page: React.FC<PageProps> = ({ appContext }) => {
const showNoRecoveryKeyMessage = () =>
setDialogBoxAttributesV2({
title: t("SORRY"),
title: t("sorry"),
close: {},
content: t("NO_RECOVERY_KEY_MESSAGE"),
});

View File

@@ -149,7 +149,7 @@ const Page: React.FC<RecoverPageProps> = ({ appContext, twoFactorType }) => {
dialogClose?: DialogBoxAttributesV2["close"],
) => {
appContext.setDialogBoxAttributesV2({
title: t("CONTACT_SUPPORT"),
title: t("contact_support"),
close: dialogClose ?? {},
content: (
<Trans

View File

@@ -33,6 +33,7 @@ import { t } from "i18next";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { Trans } from "react-i18next";
import { getSRPAttributes } from "../api/srp";
import { putAttributes, sendOtt, verifyOtt } from "../api/user";
import { PAGES } from "../constants/pages";
import {
@@ -42,7 +43,7 @@ import {
import { unstashRedirect } from "../services/redirect";
import { configureSRP } from "../services/srp";
import type { PageProps } from "../types/page";
import type { SRPSetupAttributes } from "../types/srp";
import type { SRPAttributes, SRPSetupAttributes } from "../types/srp";
const Page: React.FC<PageProps> = ({ appContext }) => {
const { logout, showNavBar, setDialogBoxAttributesV2 } = appContext;
@@ -58,16 +59,9 @@ const Page: React.FC<PageProps> = ({ appContext }) => {
useEffect(() => {
const main = async () => {
const user: User = getData(LS_KEYS.USER);
const keyAttributes: KeyAttributes = getData(
LS_KEYS.KEY_ATTRIBUTES,
);
if (!user?.email) {
router.push("/");
} else if (
keyAttributes?.encryptedKey &&
(user.token || user.encryptedToken)
) {
router.push(PAGES.CREDENTIALS);
const redirect = await redirectionIfNeeded(user);
if (redirect) {
router.push(redirect);
} else {
setEmail(user.email);
}
@@ -253,3 +247,45 @@ const Page: React.FC<PageProps> = ({ appContext }) => {
};
export default Page;
/**
* A function called during page load to see if a redirection is required
*
* @returns The slug to redirect to, if needed.
*/
const redirectionIfNeeded = async (user: User | undefined) => {
const email = user?.email;
if (!email) {
return "/";
}
const keyAttributes: KeyAttributes = getData(LS_KEYS.KEY_ATTRIBUTES);
if (keyAttributes?.encryptedKey && (user.token || user.encryptedToken)) {
return PAGES.CREDENTIALS;
}
// The user might have email verification disabled, but after previously
// entering their email on the login screen, they might've closed the tab
// before proceeding (or opened a us in a new tab at this point).
//
// In such cases, we'll end up here with an email present.
//
// To distinguish this scenario from the normal email verification flow, we
// can check to see the SRP attributes (the login page would've fetched and
// saved them). If they are present and indicate that email verification is
// not required, redirect to the password verification page.
const srpAttributes: SRPAttributes = getData(LS_KEYS.SRP_ATTRIBUTES);
if (srpAttributes && !srpAttributes.isEmailMFAEnabled) {
// Fetch the latest SRP attributes instead of relying on the potentially
// stale stored values. This is an infrequent scenario path, so extra
// API calls are fine.
const latestSRPAttributes = await getSRPAttributes(email);
if (latestSRPAttributes && !latestSRPAttributes.isEmailMFAEnabled) {
return PAGES.CREDENTIALS;
}
}
return undefined;
};

View File

@@ -140,7 +140,9 @@ export const encryptBlob = (data: BytesOrB64, key: BytesOrB64) =>
* strings.
*/
export const encryptBlobB64 = (data: BytesOrB64, key: BytesOrB64) =>
assertInWorker(ei._encryptBlobB64(data, key));
inWorker()
? ei._encryptBlobB64(data, key)
: sharedCryptoWorker().then((w) => w._encryptBlobB64(data, key));
/**
* Encrypt the thumbnail for a file.

View File

@@ -15,6 +15,7 @@ import * as libsodium from "./libsodium";
export class CryptoWorker {
encryptBoxB64 = ei._encryptBoxB64;
encryptThumbnail = ei._encryptThumbnail;
_encryptBlobB64 = ei._encryptBlobB64;
encryptMetadataJSON_New = ei._encryptMetadataJSON_New;
encryptMetadataJSON = ei._encryptMetadataJSON;
decryptBox = ei._decryptBox;

View File

@@ -9,12 +9,36 @@ import log from "./log";
* storage is limited to the main thread).
*
* The "kv" database consists of one object store, "kv". Keys are strings.
* Values can be strings or number or booleans.
* Values can be arbitrary JSON objects.
*
* [Note: Avoiding IndexedDB flakiness by avoiding indexes]
*
* Sporadically, rarely, but definitely, we ran into issues with IndexedDB
* losing data. e.g. saves from the ML web worker would complete successfully,
* but the saves would not reflect on the main thread, and that data would just
* get lost when the app would refresh.
*
* I'm not sure where the problem lay - in our own code, in the library that we
* are using (idb), within IndexedDB itself, or in Electron/Chrome's
* implementation of it (these cases all came up in the context of the ML code
* that only ran in our desktop app).
*
* A piece of advice I randomly ran across on the internet was to keep it very
* simple, and avoid all indexes. I also recalled that we did not see such
* issues with our older (but now unmaintained) library, localforage, which also
* doesn't use any indexes, and just has a flat single-store schema.
*
* So this may be superstition, but for now the approach I'm taking is to use
* IndexedDB as a key value store only.
*/
interface KVDBSchema extends DBSchema {
kv: {
key: string;
value: string | number | boolean;
/**
* Typescript doesn't have a native JSON type, so this needs to be
* unknown
*/
value: unknown;
};
}
@@ -101,8 +125,16 @@ export const clearKVDB = async () => {
/**
* Return the string value stored corresponding to {@link key}, or `undefined`
* if there is no such entry.
*
* Typescript doesn't have a native JSON type, so the return value is type as an
* `unknown`. For primitive types, you can avoid casting by using the
* {@link getKVS} (string), {@link getKVN} (number) or {@link getKVB} (boolean)
* methods that do an additional runtime check of the type.
*/
export const getKV = async (key: string) => _getKV<string>(key, "string");
export const getKV = async (key: string) => {
const db = await kvDB();
return db.get("kv", key);
};
export const _getKV = async <T extends string | number | boolean>(
key: string,
@@ -113,11 +145,14 @@ export const _getKV = async <T extends string | number | boolean>(
if (v === undefined) return undefined;
if (typeof v != type)
throw new Error(
`Expected the value corresponding to key ${key} to be a ${type}, but instead got ${v}`,
`Expected the value corresponding to key ${key} to be a ${type}, but instead got ${String(v)}`,
);
return v as T;
};
/** String variant of {@link getKV}. */
export const getKVS = async (key: string) => _getKV<string>(key, "string");
/** Numeric variant of {@link getKV}. */
export const getKVN = async (key: string) => _getKV<number>(key, "number");
@@ -127,8 +162,11 @@ export const getKVB = async (key: string) => _getKV<boolean>(key, "boolean");
/**
* Save the given {@link value} corresponding to {@link key}, overwriting any
* existing value.
*
* @param value Any arbitrary JSON object. Typescript doesn't have a native JSON
* type, so this is typed as a unknown
*/
export const setKV = async (key: string, value: string | number | boolean) => {
export const setKV = async (key: string, value: unknown) => {
const db = await kvDB();
await db.put("kv", value, key);
};

View File

@@ -2,7 +2,7 @@
import { ensure } from "@/utils/ensure";
import { z } from "zod";
import { getKV } from "./kv";
import { getKVS } from "./kv";
// TODO: During login the only field present is email. Which makes this
// optionality indicated by these types incorrect.
@@ -57,4 +57,4 @@ export const ensureLocalUser = (): LocalUser => {
* The underlying data is stored in IndexedDB, and can be accessed from web
* workers.
*/
export const ensureAuthToken = async () => ensure(await getKV("token"));
export const ensureAuthToken = async () => ensure(await getKVS("token"));

View File

@@ -1,15 +1,15 @@
{
"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": "أندرويد، آي أو إس، ويب، سطح المكتب",
"intro_slide_1_title": "<div>نسخ احتياطية خاصة</div><div>لذكرياتك</div>",
"intro_slide_1": "تشفير من طرف إلى طرف بشكل افتراضي",
"intro_slide_2_title": "<div>يتم تخزينها بأمان</div><div>في ملجأ للطوارئ</div>",
"intro_slide_2": "مصممة لتدوم",
"intro_slide_3_title": "<div>متاح</div><div> في كل مكان</div>",
"intro_slide_3": "أندرويد، آي أو إس، ويب، سطح المكتب",
"login": "تسجيل الدخول",
"sign_up": "تسجيل",
"NEW_USER": "جديد في Ente",
"EXISTING_USER": "مستخدم موجود",
"ENTER_NAME": "أدخل الاسم",
"enter_name": "أدخل الاسم",
"PUBLIC_UPLOADER_NAME_MESSAGE": "أضف اسما حتى يتمكن أصدقاؤك من معرفة من يشكرون على هذه الصور الرائعة!",
"ENTER_EMAIL": "أدخل عنوان البريد الإلكتروني",
"EMAIL_ERROR": "أدخل بريد إلكتروني صالح",
@@ -44,11 +44,11 @@
"create_albums": "إنشاء ألبومات",
"CREATE_COLLECTION": "ألبوم جديد",
"enter_album_name": "اسم الألبوم",
"CLOSE_OPTION": "إغلاق (Esc)",
"close_key": "إغلاق (Esc)",
"enter_file_name": "إسم الملف",
"CLOSE": "إغلاق",
"NO": "لا",
"NOTHING_HERE": "",
"close": "إغلاق",
"no": "لا",
"nothing_here": "",
"upload": "تحميل",
"import": "استيراد",
"add_photos": "إضافة صور",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "تم تغيير كلمة المرور في مكان آخر",
"password_changed_elsewhere_message": "يرجى تسجيل الدخول مرة أخرى على هذا الجهاز لاستخدام كلمة المرور الجديدة للمصادقة.",
"GO_BACK": "رجوع",
"RECOVERY_KEY": "مفتاح الاستعادة",
"SAVE_LATER": "قم بهذا لاحقا",
"SAVE": "حفظ المفتاح",
"recovery_key": "مفتاح الاستعادة",
"do_this_later": "قم بهذا لاحقا",
"save_key": "حفظ المفتاح",
"RECOVERY_KEY_DESCRIPTION": "إذا نسيت كلمة المرور الخاصة بك، فالطريقة الوحيدة التي يمكنك بها استرداد البيانات الخاصة بك هي بهذا المفتاح.",
"RECOVER_KEY_GENERATION_FAILED": "لا يمكن إنشاء رمز الاسترداد، الرجاء المحاولة مرة أخرى",
"KEY_NOT_STORED_DISCLAIMER": "لا يتم تخزين هذا المفتاح أليا، لذا يرجى حفظ هذا في مكان آمن",
@@ -121,18 +121,17 @@
"RECOVER": "استعادة",
"NO_RECOVERY_KEY": "ما من مفتاح استعادة؟",
"INCORRECT_RECOVERY_KEY": "مفتاح استعادة غير صحيح",
"SORRY": "عذرا",
"sorry": "عذرا",
"NO_RECOVERY_KEY_MESSAGE": "بسبب طبيعة نظام التشفير التام بين الطرفين، لا يمكن فك تشفير بياناتك دون كلمة المرور أو مفتاح الاسترداد الخاص بك",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "الاتصال بالدعم",
"REQUEST_FEATURE": "طلب ميزة",
"SUPPORT": "الدعم",
"CONFIRM": "تأكيد",
"contact_support": "الاتصال بالدعم",
"request_feature": "طلب ميزة",
"support": "الدعم",
"cancel": "إلغاء",
"LOGOUT": "تسجيل الخروج",
"logout": "تسجيل الخروج",
"logout_message": "هل أنت متأكد من أنك تريد تسجيل الخروج؟",
"delete_account": "حذف الحساب",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "هل أنت متأكد من أنك تريد تسجيل الخروج؟",
"CHANGE_EMAIL": "تغيير البريد الإلكتروني",
"ok": "حسنا",
"success": "تم بنجاح",
@@ -324,7 +323,7 @@
"hide_collection": "إخفاء الألبوم",
"unhide_collection": "إلغاء إخفاء الألبوم",
"MOVE": "نقل",
"ADD": "إضافة",
"add": "إضافة",
"REMOVE": "ازالة",
"YES_REMOVE": "نعم، إزالة",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Личен бекъп</div><div>на твоите спомени</div>",
"HERO_SLIDE_1": "Криптиран от край до край по подразбиране",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "<div>Личен бекъп</div><div>на твоите спомени</div>",
"intro_slide_1": "Криптиран от край до край по подразбиране",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "",
"NO": "",
"NOTHING_HERE": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "",
"NO": "",
"NOTHING_HERE": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -0,0 +1,651 @@
{
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_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_description": "",
"unlock": "",
"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": "",
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"close_key": "",
"enter_file_name": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
"add_more_photos": "",
"add_photos_count_one": "",
"add_photos_count": "",
"select_photos": "",
"FILE_UPLOAD": "",
"UPLOAD_STAGE_MESSAGE": {
"0": "",
"1": "",
"2": "",
"3": "",
"4": "",
"5": ""
},
"FILE_NOT_UPLOADED_LIST": "",
"INITIAL_LOAD_DELAY_WARNING": "",
"USER_DOES_NOT_EXIST": "",
"NO_ACCOUNT": "",
"ACCOUNT_EXISTS": "",
"CREATE": "",
"download": "",
"download_album": "",
"download_favorites": "",
"download_uncategorized": "",
"download_hidden_items": "",
"download_key": "",
"copy_key": "",
"toggle_fullscreen_key": "",
"zoom_in_out_key": "",
"previous_key": "",
"next_key": "",
"title_photos": "",
"title_auth": "",
"title_accounts": "",
"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": "",
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"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": "",
"cancel": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
"error": "",
"OFFLINE_MSG": "",
"install": "",
"install_mobile_app": "",
"download_app": "",
"download_app_message": "",
"EXPORT": "",
"SUBSCRIPTION": "",
"SUBSCRIBE": "",
"MANAGEMENT_PORTAL": "",
"MANAGE_FAMILY_PORTAL": "",
"LEAVE_FAMILY_PLAN": "",
"LEAVE": "",
"LEAVE_FAMILY_CONFIRM": "",
"CHOOSE_PLAN": "",
"MANAGE_PLAN": "",
"CURRENT_USAGE": "",
"TWO_MONTHS_FREE": "",
"POPULAR": "",
"free_plan_option": "",
"free_plan_description": "",
"active": "",
"subscription_info_free": "",
"subscription_info_family": "",
"subscription_info_expired": "",
"subscription_info_renewal_cancelled": "",
"subscription_info_storage_quota_exceeded": "",
"subscription_status_renewal_active": "",
"subscription_status_renewal_cancelled": "",
"add_on_valid_till": "",
"subscription_expired": "",
"storage_quota_exceeded": "",
"SUBSCRIPTION_PURCHASE_SUCCESS": "",
"SUBSCRIPTION_PURCHASE_CANCELLED": "",
"SUBSCRIPTION_PURCHASE_FAILED": "",
"SUBSCRIPTION_UPDATE_FAILED": "",
"UPDATE_PAYMENT_METHOD_MESSAGE": "",
"STRIPE_AUTHENTICATION_FAILED": "",
"UPDATE_PAYMENT_METHOD": "",
"MONTHLY": "",
"YEARLY": "",
"MONTH_SHORT": "",
"YEAR": "",
"update_subscription_title": "",
"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_album": "",
"delete_album": "",
"delete_album_title": "",
"delete_album_message": "",
"delete_photos": "",
"keep_photos": "",
"share_album": "",
"SHARE_WITH_SELF": "",
"ALREADY_SHARED": "",
"SHARING_BAD_REQUEST_ERROR": "",
"SHARING_DISABLED_FOR_FREE_ACCOUNTS": "",
"CREATE_ALBUM_FAILED": "",
"search": "",
"search_results": "",
"no_results": "",
"search_hint": "",
"album": "",
"date": "",
"description": "",
"file_type": "",
"magic": "",
"photos_count_zero": "",
"photos_count_one": "",
"photos_count": "",
"TERMS_AND_CONDITIONS": "",
"SELECTED": "",
"people": "",
"indexing_scheduled": "",
"indexing_photos": "",
"indexing_fetching": "",
"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": "",
"enabled": "",
"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": "",
"select_zips": "",
"faq": "",
"takeout_hint": "",
"DESTINATION": "",
"START": "",
"LAST_EXPORT_TIME": "",
"EXPORT_AGAIN": "",
"LOCAL_STORAGE_NOT_ACCESSIBLE": "",
"LOCAL_STORAGE_NOT_ACCESSIBLE_MESSAGE": "",
"SEND_OTT": "",
"EMAIl_ALREADY_OWNED": "",
"ETAGS_BLOCKED": "",
"LIVE_PHOTOS_DETECTED": "",
"RETRY_FAILED": "",
"FAILED_UPLOADS": "",
"failed_uploads_hint": "",
"SKIPPED_FILES": "",
"THUMBNAIL_GENERATION_FAILED_UPLOADS": "",
"UNSUPPORTED_FILES": "",
"SUCCESSFUL_UPLOADS": "",
"SKIPPED_INFO": "",
"UNSUPPORTED_INFO": "",
"BLOCKED_UPLOADS": "",
"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": "",
"select_album": "",
"upload_to_album": "",
"add_to_album": "",
"move_to_album": "",
"unhide_to_album": "",
"restore_to_album": "",
"section_all": "",
"section_uncategorized": "",
"section_archive": "",
"section_hidden": "",
"section_trash": "",
"favorites": "",
"archive": "",
"archive_album": "",
"unarchive": "",
"unarchive_album": "",
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
"MOVE_TO_TRASH": "",
"TRASH_FILES_MESSAGE": "",
"TRASH_FILE_MESSAGE": "",
"DELETE_PERMANENTLY": "",
"RESTORE": "",
"empty_trash": "",
"empty_trash_title": "",
"empty_trash_message": "",
"leave_album": "",
"leave_shared_album_title": "",
"leave_shared_album_message": "",
"leave_shared_album": "",
"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": "",
"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": "",
"sharing_details": "",
"modify_sharing": "",
"ADD_COLLABORATORS": "",
"ADD_NEW_EMAIL": "",
"shared_with_people_count_zero": "",
"shared_with_people_count_one": "",
"shared_with_people_count": "",
"participants_count_zero": "",
"participants_count_one": "",
"participants_count": "",
"ADD_VIEWERS": "",
"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": "",
"SHARED_USING": "",
"SHARING_REFERRAL_CODE": "",
"LIVE": "",
"DISABLE_PASSWORD": "",
"DISABLE_PASSWORD_MESSAGE": "",
"PASSWORD_LOCK": "",
"LOCK": "",
"file": "",
"folder": "",
"google_takeout": "",
"DEDUPLICATE_FILES": "",
"NO_DUPLICATES_FOUND": "",
"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": "",
"albums_count_one": "",
"albums_count": "",
"all_albums": "",
"all_hidden_albums": "",
"hidden_albums": "",
"hidden_items": "",
"ENTER_TWO_FACTOR_OTP": "",
"CREATE_ACCOUNT": "",
"COPIED": "",
"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": "",
"CHANGE_FOLDER": "",
"FAMILY_PLAN": "",
"debug_logs": "",
"DOWNLOAD_LOGS": "",
"DOWNLOAD_LOGS_MESSAGE": "",
"WEAK_DEVICE": "",
"drag_and_drop_hint": "",
"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": "",
"more_details": "",
"ml_search": "",
"ml_search_description": "",
"ml_search_footnote": "",
"indexing": "",
"processed": "",
"indexing_status_running": "",
"indexing_status_fetching": "",
"indexing_status_scheduled": "",
"indexing_status_done": "",
"ml_search_disable": "",
"ml_search_disable_confirm": "",
"ml_consent": "",
"ml_consent_title": "",
"ml_consent_description": "",
"ml_consent_confirmation": "",
"labs": "",
"YOURS": "",
"passphrase_strength_weak": "",
"passphrase_strength_moderate": "",
"passphrase_strength_strong": "",
"preferences": "",
"language": "",
"advanced": "",
"EXPORT_DIRECTORY_DOES_NOT_EXIST": "",
"EXPORT_DIRECTORY_DOES_NOT_EXIST_MESSAGE": "",
"SUBSCRIPTION_VERIFICATION_ERROR": "",
"storage_unit": {
"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": "",
"CONTINUOUS_EXPORT": "",
"PENDING_ITEMS": "",
"EXPORT_STARTING": "",
"delete_account_reason_label": "",
"delete_account_reason_placeholder": "",
"delete_reason": {
"missing_feature": "",
"behaviour": "",
"found_another_service": "",
"not_listed": ""
},
"delete_account_feedback_label": "",
"delete_account_feedback_placeholder": "",
"delete_account_confirm_checkbox_label": "",
"delete_account_confirm": "",
"delete_account_confirm_message": "",
"feedback_required": "",
"feedback_required_found_another_service": "",
"RECOVER_TWO_FACTOR": "",
"at": "",
"AUTH_NEXT": "",
"AUTH_DOWNLOAD_MOBILE_APP": "",
"HIDE": "",
"UNHIDE": "",
"sort_by": "",
"newest_first": "",
"oldest_first": "",
"CONVERSION_FAILED_NOTIFICATION_MESSAGE": "",
"pin_album": "",
"unpin_album": "",
"DOWNLOAD_COMPLETE": "",
"DOWNLOADING_COLLECTION": "",
"DOWNLOAD_FAILED": "",
"DOWNLOAD_PROGRESS": "",
"CHRISTMAS": "",
"CHRISTMAS_EVE": "",
"NEW_YEAR": "",
"NEW_YEAR_EVE": "",
"IMAGE": "",
"VIDEO": "",
"LIVE_PHOTO": "",
"editor": {
"crop": ""
},
"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": "",
"cast_album_to_tv": "",
"enter_cast_pin_code": "",
"pair_device_to_tv": "",
"tv_not_found": "",
"cast_auto_pair": "",
"cast_auto_pair_description": "",
"choose_device_from_browser": "",
"cast_auto_pair_failed": "",
"pair_with_pin": "",
"pair_with_pin_description": "",
"visit_cast_url": "",
"FREEHAND": "",
"APPLY_CROP": "",
"PHOTO_EDIT_REQUIRED_TO_SAVE": "",
"passkeys": "",
"passkey_fetch_failed": "",
"manage_passkey": "",
"delete_passkey": "",
"delete_passkey_confirmation": "",
"rename_passkey": "",
"add_passkey": "",
"enter_passkey_name": "",
"passkeys_description": "",
"CREATED_AT": "",
"passkey_add_failed": "",
"passkey_login_failed": "",
"passkey_login_invalid_url": "",
"passkey_login_already_claimed_session": "",
"passkey_login_generic_error": "",
"passkey_login_credential_hint": "",
"passkeys_not_supported": "",
"try_again": "",
"check_status": "",
"passkey_login_instructions": "",
"passkey_login": "",
"passkey": "",
"passkey_verify_description": "",
"waiting_for_verification": "",
"verification_still_pending": "",
"passkey_verified": "",
"redirecting_back_to_app": "",
"redirect_close_instructions": "",
"redirect_again": "",
"autogenerated_first_album_name": "",
"autogenerated_default_album_name": "",
"developer_settings": "",
"server_endpoint": "",
"more_information": "",
"save": ""
}

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Private Sicherungen</div><div>für deine Erinnerungen</div>",
"HERO_SLIDE_1": "Standardmäßig Ende-zu-Ende verschlüsselt",
"HERO_SLIDE_2_TITLE": "<div>Sicher gespeichert</div><div>in einem Luftschutzbunker</div>",
"HERO_SLIDE_2": "Entwickelt, um zu überleben",
"HERO_SLIDE_3_TITLE": "<div>Überall</div><div> verfügbar</div>",
"HERO_SLIDE_3": "Android, iOS, Web, Desktop",
"intro_slide_1_title": "<div>Private Sicherungen</div><div>für deine Erinnerungen</div>",
"intro_slide_1": "Standardmäßig Ende-zu-Ende verschlüsselt",
"intro_slide_2_title": "<div>Sicher gespeichert</div><div>in einem Luftschutzbunker</div>",
"intro_slide_2": "Entwickelt, um zu überleben",
"intro_slide_3_title": "<div>Überall</div><div> verfügbar</div>",
"intro_slide_3": "Android, iOS, Web, Desktop",
"login": "Anmelden",
"sign_up": "Registrieren",
"NEW_USER": "Neu bei Ente",
"EXISTING_USER": "Existierender Benutzer",
"ENTER_NAME": "Name eingeben",
"enter_name": "Name eingeben",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Füge einen Namen hinzu, damit deine Freunde wissen, wem sie für diese tollen Fotos zu danken haben!",
"ENTER_EMAIL": "E-Mail-Adresse eingeben",
"EMAIL_ERROR": "Geben Sie eine gültige E-Mail-Adresse ein",
@@ -44,11 +44,11 @@
"create_albums": "Alben erstellen",
"CREATE_COLLECTION": "Neues Album",
"enter_album_name": "Albumname",
"CLOSE_OPTION": "Schließen (Esc)",
"close_key": "Schließen (Esc)",
"enter_file_name": "Dateiname",
"CLOSE": "Schließen",
"NO": "Nein",
"NOTHING_HERE": "",
"close": "Schließen",
"no": "Nein",
"nothing_here": "",
"upload": "Hochladen",
"import": "Importieren",
"add_photos": "Fotos hinzufügen",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "Passwort anderswo geändert",
"password_changed_elsewhere_message": "Bitte melde dich erneut auf diesem Gerät an, um dein neues Passwort zur Authentifizierung zu verwenden.",
"GO_BACK": "Zurück",
"RECOVERY_KEY": "Wiederherstellungsschlüssel",
"SAVE_LATER": "Auf später verschieben",
"SAVE": "Schlüssel speichern",
"recovery_key": "Wiederherstellungsschlüssel",
"do_this_later": "Auf später verschieben",
"save_key": "Schlüssel speichern",
"RECOVERY_KEY_DESCRIPTION": "Falls du dein Passwort vergisst, kannst du deine Daten nur mit diesem Schlüssel wiederherstellen.",
"RECOVER_KEY_GENERATION_FAILED": "Wiederherstellungsschlüssel konnte nicht generiert werden, bitte versuche es erneut",
"KEY_NOT_STORED_DISCLAIMER": "Wir speichern diesen Schlüssel nicht, also speichere ihn bitte an einem sicheren Ort",
@@ -121,18 +121,17 @@
"RECOVER": "Wiederherstellen",
"NO_RECOVERY_KEY": "Kein Wiederherstellungsschlüssel?",
"INCORRECT_RECOVERY_KEY": "Falscher Wiederherstellungs-Schlüssel",
"SORRY": "Entschuldigung",
"sorry": "Entschuldigung",
"NO_RECOVERY_KEY_MESSAGE": "Aufgrund unseres Ende-zu-Ende-Verschlüsselungsprotokolls können Ihre Daten nicht ohne Ihr Passwort oder Ihren Wiederherstellungsschlüssel entschlüsselt werden",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Bitte sende eine E-Mail an <a>{{emailID}}</a> von deiner registrierten E-Mail-Adresse",
"CONTACT_SUPPORT": "Support kontaktieren",
"REQUEST_FEATURE": "Feature anfragen",
"SUPPORT": "Support",
"CONFIRM": "Bestätigen",
"contact_support": "Support kontaktieren",
"request_feature": "Feature anfragen",
"support": "Support",
"cancel": "Abbrechen",
"LOGOUT": "Ausloggen",
"logout": "Ausloggen",
"logout_message": "Sind sie sicher, dass sie sich ausloggen möchten?",
"delete_account": "Konto löschen",
"delete_account_manually_message": "<p>Bitte sende eine E-Mail an <a>{{emailID}}</a> mit deiner registrierten E-Mail-Adresse.</p><p>Deine Anfrage wird innerhalb von 72 Stunden bearbeitet.</p>",
"LOGOUT_MESSAGE": "Sind sie sicher, dass sie sich ausloggen möchten?",
"CHANGE_EMAIL": "E-Mail-Adresse ändern",
"ok": "OK",
"success": "Erfolgreich",
@@ -324,7 +323,7 @@
"hide_collection": "Album ausblenden",
"unhide_collection": "Album wieder einblenden",
"MOVE": "Verschieben",
"ADD": "Hinzufügen",
"add": "Hinzufügen",
"REMOVE": "Entfernen",
"YES_REMOVE": "Ja, entfernen",
"REMOVE_FROM_COLLECTION": "Aus Album entfernen",
@@ -596,7 +595,7 @@
"COLORS": "Farben",
"FLIP": "Spiegeln",
"ROTATION": "Drehen",
"RESET": "Zurücksetzen",
"reset": "Zurücksetzen",
"PHOTO_EDITOR": "Foto-Editor",
"FASTER_UPLOAD": "Schnelleres Hochladen",
"FASTER_UPLOAD_DESCRIPTION": "Uploads über nahegelegene Server leiten",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Ιδιωτικά αντίγραφα ασφαλείας</div><div>για τις αναμνήσεις σας</div>",
"HERO_SLIDE_1": "Από προεπιλογή κρυπτογραφημένο από άκρο σε άκρο",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "Σχεδιάστηκε για να επιζήσει",
"HERO_SLIDE_3_TITLE": "<div>Διαθέσιμο</div><div> παντού</div>",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "<div>Ιδιωτικά αντίγραφα ασφαλείας</div><div>για τις αναμνήσεις σας</div>",
"intro_slide_1": "Από προεπιλογή κρυπτογραφημένο από άκρο σε άκρο",
"intro_slide_2_title": "",
"intro_slide_2": "Σχεδιάστηκε για να επιζήσει",
"intro_slide_3_title": "<div>Διαθέσιμο</div><div> παντού</div>",
"intro_slide_3": "",
"login": "Σύνδεση",
"sign_up": "Εγγραφή",
"NEW_USER": "Νέος/α στο Ente",
"EXISTING_USER": "Υπάρχων χρήστης",
"ENTER_NAME": "Εισάγετε όνομα",
"enter_name": "Εισάγετε όνομα",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Προσθέστε ένα όνομα, ώστε οι φίλοι σας να γνωρίζουν ποιον να ευχαριστήσουν για αυτές τις υπέροχες φωτογραφίες!",
"ENTER_EMAIL": "Εισάγετε διεύθυνση ηλ. ταχυδρομείου",
"EMAIL_ERROR": "Εισάγετε μία έγκυρη διεύθυνση ηλ. ταχυδρομείου",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "Νέο άλμπουμ",
"enter_album_name": "Όνομα άλμπουμ",
"CLOSE_OPTION": "Κλείσιμο (Esc)",
"close_key": "Κλείσιμο (Esc)",
"enter_file_name": "Όνομα αρχείου",
"CLOSE": "Κλείσιμο",
"NO": "Όχι",
"NOTHING_HERE": "",
"close": "Κλείσιμο",
"no": "Όχι",
"nothing_here": "",
"upload": "Μεταφόρτωση",
"import": "Εισαγωγή",
"add_photos": "Προσθήκη φωτογραφιών",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "Παρακαλώ συνδεθείτε ξανά σε αυτήν τη συσκευή για να χρησιμοποιήσετε το νέο σας κωδικό πρόσβασης για αυθεντικοποίηση.",
"GO_BACK": "Επιστροφή",
"RECOVERY_KEY": "Κλειδί ανάκτησης",
"SAVE_LATER": "Κάντε το αργότερα",
"SAVE": "Αποθήκευση Κλειδιού",
"recovery_key": "Κλειδί ανάκτησης",
"do_this_later": "Κάντε το αργότερα",
"save_key": "Αποθήκευση Κλειδιού",
"RECOVERY_KEY_DESCRIPTION": "Εάν ξεχάσετε τον κωδικό πρόσβασής σας, ο μόνος τρόπος για να ανακτήσετε τα δεδομένα σας είναι με αυτό το κλειδί.",
"RECOVER_KEY_GENERATION_FAILED": "Δεν ήταν δυνατή η δημιουργία κωδικού ανάκτησης, παρακαλώ προσπαθήστε ξανά",
"KEY_NOT_STORED_DISCLAIMER": "Δεν αποθηκεύουμε αυτό το κλειδί, οπότε παρακαλώ αποθηκεύστε αυτό το κλειδί σε μια ασφαλή τοποθεσία",
@@ -121,18 +121,17 @@
"RECOVER": "Ανάκτηση",
"NO_RECOVERY_KEY": "Χωρίς κλειδί ανάκτησης;",
"INCORRECT_RECOVERY_KEY": "Εσφαλμένο κλειδί ανάκτησης",
"SORRY": "Συγνώμη",
"sorry": "Συγνώμη",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Παρακαλώ αφήστε ένα μήνυμα ηλ. ταχυδρομείου στο <a>{{emailID}}</a> από την καταχωρημένη διεύθυνση σας",
"CONTACT_SUPPORT": "Επικοινωνήστε με την υποστήριξη",
"REQUEST_FEATURE": "Αίτηση Λειτουργίας",
"SUPPORT": "Υποστήριξη",
"CONFIRM": "Επιβεβαίωση",
"contact_support": "Επικοινωνήστε με την υποστήριξη",
"request_feature": "Αίτηση Λειτουργίας",
"support": "Υποστήριξη",
"cancel": "Ακύρωση",
"LOGOUT": "Αποσυνδέση",
"logout": "Αποσυνδέση",
"logout_message": "Είστε σίγουροι ότι θέλετε να αποσυνδεθείτε;",
"delete_account": "Διαγραφή λογαριασμού",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "Είστε σίγουροι ότι θέλετε να αποσυνδεθείτε;",
"CHANGE_EMAIL": "Αλλαγή διεύθυνσης ηλ. ταχυδρομείου",
"ok": "ΟΚ",
"success": "Επιτυχία",
@@ -324,7 +323,7 @@
"hide_collection": "Απόκρυψη άλμπουμ",
"unhide_collection": "Επανεμφάνιση άλμπουμ",
"MOVE": "Μετακίνηση",
"ADD": "Προσθήκη",
"add": "Προσθήκη",
"REMOVE": "Αφαίρεση",
"YES_REMOVE": "Ναι, αφαίρεση",
"REMOVE_FROM_COLLECTION": "Αφαίρεση από το άλμπουμ",
@@ -596,7 +595,7 @@
"COLORS": "Χρώματα",
"FLIP": "",
"ROTATION": "Περιστροφή",
"RESET": "Επαναφορά",
"reset": "Επαναφορά",
"PHOTO_EDITOR": "Επεξεργαστής φωτογραφιών",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Private backups</div><div>for your memories</div>",
"HERO_SLIDE_1": "End-to-end encrypted by default",
"HERO_SLIDE_2_TITLE": "<div>Safely stored</div><div>at a fallout shelter</div>",
"HERO_SLIDE_2": "Designed to outlive",
"HERO_SLIDE_3_TITLE": "<div>Available</div><div> everywhere</div>",
"HERO_SLIDE_3": "Android, iOS, Web, Desktop",
"intro_slide_1_title": "<div>Private backups</div><div>for your memories</div>",
"intro_slide_1": "End-to-end encrypted by default",
"intro_slide_2_title": "<div>Safely stored</div><div>at a fallout shelter</div>",
"intro_slide_2": "Designed to outlive",
"intro_slide_3_title": "<div>Available</div><div> everywhere</div>",
"intro_slide_3": "Android, iOS, Web, Desktop",
"login": "Login",
"sign_up": "Signup",
"NEW_USER": "New to Ente",
"EXISTING_USER": "Existing user",
"ENTER_NAME": "Enter name",
"enter_name": "Enter name",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Add a name so that your friends know who to thank for these great photos!",
"ENTER_EMAIL": "Enter email address",
"EMAIL_ERROR": "Enter a valid email",
@@ -44,11 +44,11 @@
"create_albums": "Create albums",
"CREATE_COLLECTION": "New album",
"enter_album_name": "Album name",
"CLOSE_OPTION": "Close (Esc)",
"close_key": "Close (Esc)",
"enter_file_name": "File name",
"CLOSE": "Close",
"NO": "No",
"NOTHING_HERE": "Nothing here yet",
"close": "Close",
"no": "No",
"nothing_here": "Nothing here yet",
"upload": "Upload",
"import": "Import",
"add_photos": "Add photos",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "Password changed elsewhere",
"password_changed_elsewhere_message": "Please login again on this device to use your new password to authenticate.",
"GO_BACK": "Go back",
"RECOVERY_KEY": "Recovery key",
"SAVE_LATER": "Do this later",
"SAVE": "Save Key",
"recovery_key": "Recovery key",
"do_this_later": "Do this later",
"save_key": "Save Key",
"RECOVERY_KEY_DESCRIPTION": "If you forget your password, the only way you can recover your data is with this key.",
"RECOVER_KEY_GENERATION_FAILED": "Recovery code could not be generated, please try again",
"KEY_NOT_STORED_DISCLAIMER": "We don't store this key, so please save this in a safe place",
@@ -121,18 +121,17 @@
"RECOVER": "Recover",
"NO_RECOVERY_KEY": "No recovery key?",
"INCORRECT_RECOVERY_KEY": "Incorrect recovery key",
"SORRY": "Sorry",
"sorry": "Sorry",
"NO_RECOVERY_KEY_MESSAGE": "Due to the nature of our end-to-end encryption protocol, your data cannot be decrypted without your password or recovery key",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Please drop an email to <a>{{emailID}}</a> from your registered email address",
"CONTACT_SUPPORT": "Contact support",
"REQUEST_FEATURE": "Request Feature",
"SUPPORT": "Support",
"CONFIRM": "Confirm",
"contact_support": "Contact support",
"request_feature": "Request Feature",
"support": "Support",
"cancel": "Cancel",
"LOGOUT": "Logout",
"logout": "Logout",
"logout_message": "Are you sure you want to logout?",
"delete_account": "Delete account",
"delete_account_manually_message": "<p>Please send an email to <a>{{emailID}}</a> from your registered email address.</p><p>Your request will be processed within 72 hours.</p>",
"LOGOUT_MESSAGE": "Are you sure you want to logout?",
"CHANGE_EMAIL": "Change email",
"ok": "OK",
"success": "Success",
@@ -324,7 +323,7 @@
"hide_collection": "Hide album",
"unhide_collection": "Unhide album",
"MOVE": "Move",
"ADD": "Add",
"add": "Add",
"REMOVE": "Remove",
"YES_REMOVE": "Yes, remove",
"REMOVE_FROM_COLLECTION": "Remove from album",
@@ -596,7 +595,7 @@
"COLORS": "Colors",
"FLIP": "Flip",
"ROTATION": "Rotation",
"RESET": "Reset",
"reset": "Reset",
"PHOTO_EDITOR": "Photo Editor",
"FASTER_UPLOAD": "Faster uploads",
"FASTER_UPLOAD_DESCRIPTION": "Route uploads through nearby servers",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Copias de seguridad privadas</div><div>para su recuerdos</div>",
"HERO_SLIDE_1": "Encriptado de extremo a extremo por defecto",
"HERO_SLIDE_2_TITLE": "<div>Almacenado de forma segura</div><div>en un refugio de llenos</div>",
"HERO_SLIDE_2": "Diseñado para superar",
"HERO_SLIDE_3_TITLE": "<div>Disponible</div><div> en todas partes</div>",
"HERO_SLIDE_3": "Android, iOS, web, computadora",
"intro_slide_1_title": "<div>Copias de seguridad privadas</div><div>para su recuerdos</div>",
"intro_slide_1": "Encriptado de extremo a extremo por defecto",
"intro_slide_2_title": "<div>Almacenado de forma segura</div><div>en un refugio de llenos</div>",
"intro_slide_2": "Diseñado para superar",
"intro_slide_3_title": "<div>Disponible</div><div> en todas partes</div>",
"intro_slide_3": "Android, iOS, web, computadora",
"login": "Conectar",
"sign_up": "Registro",
"NEW_USER": "Nuevo en Ente",
"EXISTING_USER": "Usuario existente",
"ENTER_NAME": "Introducir nombre",
"enter_name": "Introducir nombre",
"PUBLIC_UPLOADER_NAME_MESSAGE": "¡Añade un nombre para que tus amigos sepan a quién dar las gracias por estas fotos geniales!",
"ENTER_EMAIL": "Introducir email",
"EMAIL_ERROR": "Introduce un email válido",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "Nuevo álbum",
"enter_album_name": "Nombre del álbum",
"CLOSE_OPTION": "Cerrar (Esc)",
"close_key": "Cerrar (Esc)",
"enter_file_name": "Nombre del archivo",
"CLOSE": "Cerrar",
"NO": "No",
"NOTHING_HERE": "",
"close": "Cerrar",
"no": "No",
"nothing_here": "",
"upload": "Cargar",
"import": "Importar",
"add_photos": "Añadir fotos",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "Retroceder",
"RECOVERY_KEY": "Clave de recuperación",
"SAVE_LATER": "Hacer más tarde",
"SAVE": "Guardar Clave",
"recovery_key": "Clave de recuperación",
"do_this_later": "Hacer más tarde",
"save_key": "Guardar Clave",
"RECOVERY_KEY_DESCRIPTION": "Si olvida su contraseña, la única forma de recuperar sus datos es con esta clave.",
"RECOVER_KEY_GENERATION_FAILED": "El código de recuperación no pudo ser generado, por favor inténtalo de nuevo",
"KEY_NOT_STORED_DISCLAIMER": "No almacenamos esta clave, así que por favor guarde esto en un lugar seguro",
@@ -121,18 +121,17 @@
"RECOVER": "Recuperar",
"NO_RECOVERY_KEY": "No hay clave de recuperación?",
"INCORRECT_RECOVERY_KEY": "Clave de recuperación incorrecta",
"SORRY": "Lo sentimos",
"sorry": "Lo sentimos",
"NO_RECOVERY_KEY_MESSAGE": "Debido a la naturaleza de nuestro protocolo de cifrado de extremo a extremo, sus datos no pueden ser descifrados sin su contraseña o clave de recuperación",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Por favor, envíe un email a <a>{{emailID}}</a> desde su dirección de correo electrónico registrada",
"CONTACT_SUPPORT": "Contacta con soporte",
"REQUEST_FEATURE": "Solicitar una función",
"SUPPORT": "Soporte",
"CONFIRM": "Confirmar",
"contact_support": "Contacta con soporte",
"request_feature": "Solicitar una función",
"support": "Soporte",
"cancel": "Cancelar",
"LOGOUT": "Cerrar sesión",
"logout": "Cerrar sesión",
"logout_message": "Seguro que quiere cerrar la sesión?",
"delete_account": "Eliminar cuenta",
"delete_account_manually_message": "<p>Por favor, envíe un email a <a>{{emailID}}</a> desde su dirección de correo electrónico registrada</p><p>Su solicitud será procesada en 72 horas.</p>",
"LOGOUT_MESSAGE": "Seguro que quiere cerrar la sesión?",
"CHANGE_EMAIL": "Cambiar email",
"ok": "OK",
"success": "Completado",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "Mover",
"ADD": "Añadir",
"add": "Añadir",
"REMOVE": "Eliminar",
"YES_REMOVE": "Sí, eliminar",
"REMOVE_FROM_COLLECTION": "Eliminar del álbum",
@@ -596,7 +595,7 @@
"COLORS": "Colores",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "",
"NO": "",
"NOTHING_HERE": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "",
"NO": "",
"NOTHING_HERE": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Yksityiset varmuuskopiot</div><div>muistoillesi</div>",
"HERO_SLIDE_1": "Päästä päähän -salaus käytössä oletuksena",
"HERO_SLIDE_2_TITLE": "<div>Turvallisesti varastoitu</div><div>väestönsuojan tiloissa</div>",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "<div>Saatavilla</div><div> kaikkialla</div>",
"HERO_SLIDE_3": "Android, iOS, Web, Tietokone",
"intro_slide_1_title": "<div>Yksityiset varmuuskopiot</div><div>muistoillesi</div>",
"intro_slide_1": "Päästä päähän -salaus käytössä oletuksena",
"intro_slide_2_title": "<div>Turvallisesti varastoitu</div><div>väestönsuojan tiloissa</div>",
"intro_slide_2": "",
"intro_slide_3_title": "<div>Saatavilla</div><div> kaikkialla</div>",
"intro_slide_3": "Android, iOS, Web, Tietokone",
"login": "Kirjaudu sisään",
"sign_up": "Rekisteröidy",
"NEW_USER": "Uusi Ente-käyttäjä",
"EXISTING_USER": "Jo valmiiksi olemassaoleva käyttäjä",
"ENTER_NAME": "Lisää nimi",
"enter_name": "Lisää nimi",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Lisää nimi, jotta ystäväsi tietävät, ketä kiittää näistä hienoista kuvista!",
"ENTER_EMAIL": "Syötä sähköpostiosoite",
"EMAIL_ERROR": "Syötä voimassa oleva sähköpostiosoite",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "Uusi albumi",
"enter_album_name": "Albumin nimi",
"CLOSE_OPTION": "Sulje (Esc)",
"close_key": "Sulje (Esc)",
"enter_file_name": "Tiedoston nimi",
"CLOSE": "Sulje",
"NO": "Ei",
"NOTHING_HERE": "",
"close": "Sulje",
"no": "Ei",
"nothing_here": "",
"upload": "Lataa",
"import": "Tuo",
"add_photos": "Lisää kuvia",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Sauvegardes privées</div><div>pour vos souvenirs</div>",
"HERO_SLIDE_1": "Chiffrement de bout en bout par défaut",
"HERO_SLIDE_2_TITLE": "<div>Sécurisé </div><div>dans un abri antiatomique</div>",
"HERO_SLIDE_2": "Conçu pour survivre",
"HERO_SLIDE_3_TITLE": "<div>Disponible</div><div> en tout lieu</div>",
"HERO_SLIDE_3": "Android, iOS, Web, Ordinateur",
"intro_slide_1_title": "<div>Sauvegardes privées</div><div>pour vos souvenirs</div>",
"intro_slide_1": "Chiffrement de bout en bout par défaut",
"intro_slide_2_title": "<div>Sécurisé </div><div>dans un abri antiatomique</div>",
"intro_slide_2": "Conçu pour survivre",
"intro_slide_3_title": "<div>Disponible</div><div> en tout lieu</div>",
"intro_slide_3": "Android, iOS, Web, Ordinateur",
"login": "Connexion",
"sign_up": "Inscription",
"NEW_USER": "Nouveau sur Ente",
"EXISTING_USER": "Utilisateur existant",
"ENTER_NAME": "Saisir un nom",
"enter_name": "Saisir un nom",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Ajouter un nom afin que vos amis sachent qui remercier pour ces magnifiques photos!",
"ENTER_EMAIL": "Saisir l'adresse e-mail",
"EMAIL_ERROR": "Saisir un e-mail valide",
@@ -44,11 +44,11 @@
"create_albums": "Créer des albums",
"CREATE_COLLECTION": "Nouvel album",
"enter_album_name": "Nom de l'album",
"CLOSE_OPTION": "Fermer (Échap)",
"close_key": "Fermer (Échap)",
"enter_file_name": "Nom du fichier",
"CLOSE": "Fermer",
"NO": "Non",
"NOTHING_HERE": "",
"close": "Fermer",
"no": "Non",
"nothing_here": "",
"upload": "Charger",
"import": "Importer",
"add_photos": "Ajouter des photos",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "Mot de passe modifié ailleurs",
"password_changed_elsewhere_message": "Veuillez vous reconnecter sur cet appareil pour utiliser votre nouveau mot de passe pour vous authentifier.",
"GO_BACK": "Retour",
"RECOVERY_KEY": "Clé de récupération",
"SAVE_LATER": "Plus tard",
"SAVE": "Sauvegarder la clé",
"recovery_key": "Clé de récupération",
"do_this_later": "Plus tard",
"save_key": "Sauvegarder la clé",
"RECOVERY_KEY_DESCRIPTION": "Si vous oubliez votre mot de passe, la seule façon de récupérer vos données sera grâce à cette clé.",
"RECOVER_KEY_GENERATION_FAILED": "Le code de récupération ne peut être généré, veuillez réessayer",
"KEY_NOT_STORED_DISCLAIMER": "Nous ne stockons pas cette clé, veuillez donc la sauvegarder dans un endroit sûr",
@@ -121,18 +121,17 @@
"RECOVER": "Récupérer",
"NO_RECOVERY_KEY": "Pas de clé de récupération?",
"INCORRECT_RECOVERY_KEY": "Clé de récupération non valide",
"SORRY": "Désolé",
"sorry": "Désolé",
"NO_RECOVERY_KEY_MESSAGE": "En raison de notre protocole de chiffrement de bout en bout, vos données ne peuvent être décryptées sans votre mot de passe ou clé de récupération",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Veuillez envoyer un e-mail à <a>{{emailID}}</a> depuis votre adresse enregistrée",
"CONTACT_SUPPORT": "Contacter le support",
"REQUEST_FEATURE": "Soumettre une idée",
"SUPPORT": "Support",
"CONFIRM": "Confirmer",
"contact_support": "Contacter le support",
"request_feature": "Soumettre une idée",
"support": "Support",
"cancel": "Annuler",
"LOGOUT": "Déconnexion",
"logout": "Déconnexion",
"logout_message": "Voulez-vous vraiment vous déconnecter?",
"delete_account": "Supprimer le compte",
"delete_account_manually_message": "<p>Veuillez envoyer un e-mail à <a>{{emailID}}</a>depuis Votre adresse enregistrée.</p><p> Votre demande sera traitée dans les 72 heures.</p>",
"LOGOUT_MESSAGE": "Voulez-vous vraiment vous déconnecter?",
"CHANGE_EMAIL": "Modifier l'e-mail",
"ok": "Ok",
"success": "Parfait",
@@ -324,7 +323,7 @@
"hide_collection": "Masquer l'album",
"unhide_collection": "Dévoiler l'album",
"MOVE": "Déplacer",
"ADD": "Ajouter",
"add": "Ajouter",
"REMOVE": "Retirer",
"YES_REMOVE": "Oui, retirer",
"REMOVE_FROM_COLLECTION": "Retirer de l'album",
@@ -596,7 +595,7 @@
"COLORS": "Couleurs",
"FLIP": "Retourner",
"ROTATION": "Rotation",
"RESET": "Réinitialiser",
"reset": "Réinitialiser",
"PHOTO_EDITOR": "Éditeur de photos",
"FASTER_UPLOAD": "Chargements plus rapides",
"FASTER_UPLOAD_DESCRIPTION": "Router les chargements vers les serveurs à proximité",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "",
"NO": "",
"NOTHING_HERE": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "",
"NO": "",
"NOTHING_HERE": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Cadangan pribadi</div><div>untuk kenanganmu</div>",
"HERO_SLIDE_1": "Dirancang dengan enkripsi ujung ke ujung",
"HERO_SLIDE_2_TITLE": "<div>Tersimpan aman</div><div>di tempat pengungsian</div>",
"HERO_SLIDE_2": "Dibuat untuk melestarikan",
"HERO_SLIDE_3_TITLE": "<div>Tersedia</div><div> di mana saja</div>",
"HERO_SLIDE_3": "Android, iOS, Web, Desktop",
"intro_slide_1_title": "<div>Cadangan pribadi</div><div>untuk kenanganmu</div>",
"intro_slide_1": "Dirancang dengan enkripsi ujung ke ujung",
"intro_slide_2_title": "<div>Tersimpan aman</div><div>di tempat pengungsian</div>",
"intro_slide_2": "Dibuat untuk melestarikan",
"intro_slide_3_title": "<div>Tersedia</div><div> di mana saja</div>",
"intro_slide_3": "Android, iOS, Web, Desktop",
"login": "Masuk",
"sign_up": "Daftar",
"NEW_USER": "Baru di Ente",
"EXISTING_USER": "Pengguna yang sudah ada",
"ENTER_NAME": "Masukkan nama",
"enter_name": "Masukkan nama",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Tambahkan nama agar teman Anda tahu kepada siapa harus berterima kasih atas foto-foto hebat ini!",
"ENTER_EMAIL": "Masukkan alamat email",
"EMAIL_ERROR": "Masukkan email yang sah",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "Album baru",
"enter_album_name": "Nama album",
"CLOSE_OPTION": "Tutup (Esc)",
"close_key": "Tutup (Esc)",
"enter_file_name": "Nama file",
"CLOSE": "Tutup",
"NO": "Tidak",
"NOTHING_HERE": "",
"close": "Tutup",
"no": "Tidak",
"nothing_here": "",
"upload": "Unggah",
"import": "Impor",
"add_photos": "Tambahkan foto",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "Kembali",
"RECOVERY_KEY": "Kunci pemulihan",
"SAVE_LATER": "Lakukan lain kali",
"SAVE": "Simpan Kunci",
"recovery_key": "Kunci pemulihan",
"do_this_later": "Lakukan lain kali",
"save_key": "Simpan Kunci",
"RECOVERY_KEY_DESCRIPTION": "Jika Anda lupa kata sandi, satu-satunya cara memulihkan data Anda adalah dengan kunci ini.",
"RECOVER_KEY_GENERATION_FAILED": "Tidak dapat menghasilkan kode pemulihan, silakan coba lagi",
"KEY_NOT_STORED_DISCLAIMER": "Kami tidak menyimpan kunci ini, jadi harap simpan kunci ini dengan aman",
@@ -121,18 +121,17 @@
"RECOVER": "Pulihkan",
"NO_RECOVERY_KEY": "Tidak punya kunci pemulihan?",
"INCORRECT_RECOVERY_KEY": "Kunci pemulihan salah",
"SORRY": "Maaf",
"sorry": "Maaf",
"NO_RECOVERY_KEY_MESSAGE": "Karena sifat protokol enkripsi ujung ke ujung kami, data kamu tidak dapat didekripsi tanpa sandi atau kunci pemulihan kamu",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Silakan kirimkan email ke <a>{{emailID}}</a> dari alamat email terdaftar kamu",
"CONTACT_SUPPORT": "Hubungi dukungan",
"REQUEST_FEATURE": "Minta Fitur",
"SUPPORT": "Dukungan",
"CONFIRM": "Konfirmasi",
"contact_support": "Hubungi dukungan",
"request_feature": "Minta Fitur",
"support": "Dukungan",
"cancel": "Batal",
"LOGOUT": "Keluar akun",
"logout": "Keluar akun",
"logout_message": "Apakah kamu yakin ingin keluar akun?",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "Apakah kamu yakin ingin keluar akun?",
"CHANGE_EMAIL": "Ubah email",
"ok": "OK",
"success": "Berhasil",
@@ -324,7 +323,7 @@
"hide_collection": "Sembunyikan album",
"unhide_collection": "",
"MOVE": "Pindahkan",
"ADD": "Tambah",
"add": "Tambah",
"REMOVE": "Hapus",
"YES_REMOVE": "Ya, hapus",
"REMOVE_FROM_COLLECTION": "Hapus dari album",
@@ -596,7 +595,7 @@
"COLORS": "Warna",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "Editor Foto",
"FASTER_UPLOAD": "Pengunggahan lebih cepat",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "Loka",
"NO": "Nei",
"NOTHING_HERE": "",
"close": "Loka",
"no": "Nei",
"nothing_here": "",
"upload": "Hlaða upp",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "Fara til baka",
"RECOVERY_KEY": "",
"SAVE_LATER": "Gera þetta seinna",
"SAVE": "Vista Lykil",
"recovery_key": "",
"do_this_later": "Gera þetta seinna",
"save_key": "Vista Lykil",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "Endurheimta",
"NO_RECOVERY_KEY": "Enginn endurheimtunarlykill?",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "Fyrirgefðu",
"sorry": "Fyrirgefðu",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "Staðfesta",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "Hætta við",
"LOGOUT": "Útskrá",
"logout": "Útskrá",
"logout_message": "Ertu viss um að þú viljir skrá þig út?",
"delete_account": "Eyða aðgangi",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "Ertu viss um að þú viljir skrá þig út?",
"CHANGE_EMAIL": "Breyta netfangi",
"ok": "Í lagi",
"success": "Tókst",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Backup privati</div><div>dei tuoi ricordi</div>",
"HERO_SLIDE_1": "Crittografia end-to-end",
"HERO_SLIDE_2_TITLE": "<div>Salvati in modo sicuro</div><div>in un rifugio antiatomico</div>",
"HERO_SLIDE_2": "Progettato per sopravvivere",
"HERO_SLIDE_3_TITLE": "<div>Disponibile</div><div> ovunque</div>",
"HERO_SLIDE_3": "Android, iOS, Web, Desktop",
"intro_slide_1_title": "<div>Backup privati</div><div>dei tuoi ricordi</div>",
"intro_slide_1": "Crittografia end-to-end",
"intro_slide_2_title": "<div>Salvati in modo sicuro</div><div>in un rifugio antiatomico</div>",
"intro_slide_2": "Progettato per sopravvivere",
"intro_slide_3_title": "<div>Disponibile</div><div> ovunque</div>",
"intro_slide_3": "Android, iOS, Web, Desktop",
"login": "Accedi",
"sign_up": "Registrati",
"NEW_USER": "Prima volta con Ente",
"EXISTING_USER": "Accedi",
"ENTER_NAME": "Inserisci il nome",
"enter_name": "Inserisci il nome",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Aggiungi un nome in modo che i tuoi amici sappiano chi ringraziare per queste fantastiche foto!",
"ENTER_EMAIL": "Inserisci l'indirizzo email",
"EMAIL_ERROR": "Inserisci un indirizzo email valido",
@@ -44,11 +44,11 @@
"create_albums": "Crea album",
"CREATE_COLLECTION": "Nuovo album",
"enter_album_name": "Nome album",
"CLOSE_OPTION": "Chiudi (Esc)",
"close_key": "Chiudi (Esc)",
"enter_file_name": "Nome del file",
"CLOSE": "Chiudi",
"NO": "No",
"NOTHING_HERE": "",
"close": "Chiudi",
"no": "No",
"nothing_here": "Non c'e ancora niente qui",
"upload": "Carica",
"import": "Importa",
"add_photos": "Aggiungi foto",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "Password cambiata altrove",
"password_changed_elsewhere_message": "Effettua nuovamente il login su questo dispositivo per utilizzare la nuova password per autenticarti.",
"GO_BACK": "Torna indietro",
"RECOVERY_KEY": "Chiave di recupero",
"SAVE_LATER": "Fallo più tardi",
"SAVE": "Salva Chiave",
"recovery_key": "Chiave di recupero",
"do_this_later": "Fallo più tardi",
"save_key": "Salva Chiave",
"RECOVERY_KEY_DESCRIPTION": "Se dimentichi la tua password, l'unico modo per recuperare i tuoi dati è con questa chiave.",
"RECOVER_KEY_GENERATION_FAILED": "Impossibile generare il codice di recupero, riprova",
"KEY_NOT_STORED_DISCLAIMER": "Non memorizziamo questa chiave, quindi salvala in un luogo sicuro",
@@ -121,18 +121,17 @@
"RECOVER": "Recupera",
"NO_RECOVERY_KEY": "Nessuna chiave di recupero?",
"INCORRECT_RECOVERY_KEY": "Chiave di recupero errata",
"SORRY": "Siamo spiacenti",
"sorry": "Siamo spiacenti",
"NO_RECOVERY_KEY_MESSAGE": "A causa della natura del nostro protocollo di crittografia end-to-end, i tuoi dati non possono essere decifrati senza la tua password o chiave di ripristino",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Per favore invia un'email a <a>{{emailID}}</a> dal tuo indirizzo email registrato",
"CONTACT_SUPPORT": "Contatta il supporto",
"REQUEST_FEATURE": "Richiedi una funzionalità",
"SUPPORT": "Supporto",
"CONFIRM": "Conferma",
"contact_support": "Contatta il supporto",
"request_feature": "Richiedi una funzionalità",
"support": "Supporto",
"cancel": "Annulla",
"LOGOUT": "Disconnettiti",
"logout": "Disconnettiti",
"logout_message": "Sei sicuro di volerti disconnettere?",
"delete_account": "Elimina account",
"delete_account_manually_message": "<p>Per favore invia una email a <a>{{emailID}}</a> dal tuo indirizzo email registrato.</p><p>La tua richiesta verrà elaborata entro 72 ore.</p>",
"LOGOUT_MESSAGE": "Sei sicuro di volerti disconnettere?",
"CHANGE_EMAIL": "Cambia email",
"ok": "OK",
"success": "Operazione riuscita",
@@ -324,7 +323,7 @@
"hide_collection": "Nascondi album",
"unhide_collection": "Rimuovi album dai nascosti",
"MOVE": "Sposta",
"ADD": "Aggiungi",
"add": "Aggiungi",
"REMOVE": "Rimuovi",
"YES_REMOVE": "Sì, rimuovi",
"REMOVE_FROM_COLLECTION": "Rimuovi dall'album",
@@ -596,7 +595,7 @@
"COLORS": "Colori",
"FLIP": "Capovolgi",
"ROTATION": "Rotazione",
"RESET": "Reimposta",
"reset": "Reimposta",
"PHOTO_EDITOR": "Editor Foto",
"FASTER_UPLOAD": "Upload più veloci",
"FASTER_UPLOAD_DESCRIPTION": "Effettua l'upload attraverso i server vicini",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "",
"NO": "",
"NOTHING_HERE": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "",
"NO": "",
"NOTHING_HERE": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"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": "안드로이드, iOS, 웹, 데스크탑",
"intro_slide_1_title": "<div>당신의 추억을 위한</div><div>비공개 백업</div>",
"intro_slide_1": "종단간 암호화를 기본적으로 지원합니다",
"intro_slide_2_title": "<div>낙진 대피소에</div><div>안전하게 보관됨</div>",
"intro_slide_2": "장기 보존을 위해 설계되었습니다",
"intro_slide_3_title": "<div>모든 기기에서</div><div>사용 가능</div>",
"intro_slide_3": "안드로이드, iOS, 웹, 데스크탑",
"login": "로그인",
"sign_up": "회원가입",
"NEW_USER": "Ente 의 새소식",
"EXISTING_USER": "기존 회원 로그인",
"ENTER_NAME": "이름 입력",
"enter_name": "이름 입력",
"PUBLIC_UPLOADER_NAME_MESSAGE": "친구들이 이 멋진 사진에 대해 고마워할 수 있도록 이름을 추가하세요!",
"ENTER_EMAIL": "이메일 주소를 입력하세요",
"EMAIL_ERROR": "올바른 이메일을 입력하세요",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "새 앨범",
"enter_album_name": "앨범 이름",
"CLOSE_OPTION": "닫기 (Esc)",
"close_key": "닫기 (Esc)",
"enter_file_name": "파일 이름",
"CLOSE": "닫기",
"NO": "아니오",
"NOTHING_HERE": "",
"close": "닫기",
"no": "아니오",
"nothing_here": "",
"upload": "업로드",
"import": "가져오기",
"add_photos": "사진 추가",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "뒤로 가기",
"RECOVERY_KEY": "키 복구하기",
"SAVE_LATER": "나중에 저장하기",
"SAVE": "키 저장하기",
"recovery_key": "키 복구하기",
"do_this_later": "나중에 저장하기",
"save_key": "키 저장하기",
"RECOVERY_KEY_DESCRIPTION": "암호 분실시, 오직 이 키를 이용해야만 데이터를 복구할 수 있습니다.",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "확인",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "취소",
"LOGOUT": "로그아웃",
"logout": "로그아웃",
"logout_message": "정말로 로그아웃 하시겠습니까?",
"delete_account": "계정 삭제",
"delete_account_manually_message": "<p>회원가입에 사용한 이메일을 통해 <a>{{emailID}}</a> (으)로 메일을 보내주세요.</p><p>귀하의 요청은 72시간 내로 처리됩니다.</p>",
"LOGOUT_MESSAGE": "정말로 로그아웃 하시겠습니까?",
"CHANGE_EMAIL": "이메일 주소 변경",
"ok": "확인",
"success": "성공",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Privé back-ups</div><div>voor uw herinneringen</div>",
"HERO_SLIDE_1": "Standaard end-to-end versleuteld",
"HERO_SLIDE_2_TITLE": "<div>Veilig opgeslagen</div><div>in een kernbunker</div>",
"HERO_SLIDE_2": "Ontworpen om levenslang mee te gaan",
"HERO_SLIDE_3_TITLE": "<div>Overal</div><div> beschikbaar</div>",
"HERO_SLIDE_3": "Android, iOS, Web, Desktop",
"intro_slide_1_title": "<div>Privé back-ups</div><div>voor uw herinneringen</div>",
"intro_slide_1": "Standaard end-to-end versleuteld",
"intro_slide_2_title": "<div>Veilig opgeslagen</div><div>in een kernbunker</div>",
"intro_slide_2": "Ontworpen om levenslang mee te gaan",
"intro_slide_3_title": "<div>Overal</div><div> beschikbaar</div>",
"intro_slide_3": "Android, iOS, Web, Desktop",
"login": "Inloggen",
"sign_up": "Registreren",
"NEW_USER": "Nieuw bij Ente",
"EXISTING_USER": "Bestaande gebruiker",
"ENTER_NAME": "Naam invoeren",
"enter_name": "Naam invoeren",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Voeg een naam toe zodat je vrienden weten wie ze moeten bedanken voor deze geweldige foto's!",
"ENTER_EMAIL": "Vul e-mailadres in",
"EMAIL_ERROR": "Vul een geldig e-mailadres in",
@@ -44,11 +44,11 @@
"create_albums": "Albums aanmaken",
"CREATE_COLLECTION": "Nieuw album",
"enter_album_name": "Albumnaam",
"CLOSE_OPTION": "Sluiten (Esc)",
"close_key": "Sluiten (Esc)",
"enter_file_name": "Bestandsnaam",
"CLOSE": "Sluiten",
"NO": "Nee",
"NOTHING_HERE": "",
"close": "Sluiten",
"no": "Nee",
"nothing_here": "",
"upload": "Uploaden",
"import": "Importeren",
"add_photos": "Foto's toevoegen",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "Wachtwoord elders gewijzigd",
"password_changed_elsewhere_message": "Log opnieuw in op dit apparaat om uw nieuwe wachtwoord te gebruiken om te verifiëren.",
"GO_BACK": "Ga terug",
"RECOVERY_KEY": "Herstelsleutel",
"SAVE_LATER": "Doe dit later",
"SAVE": "Sleutel opslaan",
"recovery_key": "Herstelsleutel",
"do_this_later": "Doe dit later",
"save_key": "Sleutel opslaan",
"RECOVERY_KEY_DESCRIPTION": "Als je je wachtwoord vergeet, kun je alleen met deze sleutel je gegevens herstellen.",
"RECOVER_KEY_GENERATION_FAILED": "Herstelcode kon niet worden gegenereerd, probeer het opnieuw",
"KEY_NOT_STORED_DISCLAIMER": "We slaan deze sleutel niet op, bewaar dit op een veilige plaats",
@@ -121,18 +121,17 @@
"RECOVER": "Herstellen",
"NO_RECOVERY_KEY": "Geen herstelsleutel?",
"INCORRECT_RECOVERY_KEY": "Onjuiste herstelsleutel",
"SORRY": "Sorry",
"sorry": "Sorry",
"NO_RECOVERY_KEY_MESSAGE": "Door de aard van ons end-to-end encryptieprotocol kunnen je gegevens niet worden ontsleuteld zonder je wachtwoord of herstelsleutel",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Stuur een e-mail naar <a>{{emailID}}</a> vanaf het door jou geregistreerde e-mailadres",
"CONTACT_SUPPORT": "Klantenservice",
"REQUEST_FEATURE": "Vraag nieuwe functie aan",
"SUPPORT": "Ondersteuning",
"CONFIRM": "Bevestigen",
"contact_support": "Klantenservice",
"request_feature": "Vraag nieuwe functie aan",
"support": "Ondersteuning",
"cancel": "Annuleren",
"LOGOUT": "Uitloggen",
"logout": "Uitloggen",
"logout_message": "Weet u zeker dat u wilt uitloggen?",
"delete_account": "Account verwijderen",
"delete_account_manually_message": "<p>Stuur een e-mail naar <a>{{emailID}}</a> vanaf uw geregistreerde e-mailadres.</p><p>Uw aanvraag wordt binnen 72 uur verwerkt.</p>",
"LOGOUT_MESSAGE": "Weet u zeker dat u wilt uitloggen?",
"CHANGE_EMAIL": "E-mail wijzigen",
"ok": "Oké",
"success": "Succes",
@@ -324,7 +323,7 @@
"hide_collection": "Verberg album",
"unhide_collection": "Album zichtbaar maken",
"MOVE": "Verplaatsen",
"ADD": "Toevoegen",
"add": "Toevoegen",
"REMOVE": "Verwijderen",
"YES_REMOVE": "Ja, verwijderen",
"REMOVE_FROM_COLLECTION": "Verwijderen uit album",
@@ -596,7 +595,7 @@
"COLORS": "Kleuren",
"FLIP": "Omdraaien",
"ROTATION": "Draaiing",
"RESET": "Herstellen",
"reset": "Herstellen",
"PHOTO_EDITOR": "Fotobewerker",
"FASTER_UPLOAD": "Snellere uploads",
"FASTER_UPLOAD_DESCRIPTION": "Uploaden door nabije servers",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Prywatne kopie zapasowe</div><div>dla Twoich wspomnień</div>",
"HERO_SLIDE_1": "Domyślnie zaszyfrowane metodą end-to-end",
"HERO_SLIDE_2_TITLE": "<div>Bezpiecznie przechowywane</div><div>w awaryjnym schronieniu</div>",
"HERO_SLIDE_2": "Zaprojektowane do przetrwania",
"HERO_SLIDE_3_TITLE": "<div>Dostępne</div><div> wszędzie</div>",
"HERO_SLIDE_3": "Android, iOS, Strona Internetowa, Aplikacja Komputerowa",
"intro_slide_1_title": "<div>Prywatne kopie zapasowe</div><div>dla Twoich wspomnień</div>",
"intro_slide_1": "Domyślnie zaszyfrowane metodą end-to-end",
"intro_slide_2_title": "<div>Bezpiecznie przechowywane</div><div>w awaryjnym schronieniu</div>",
"intro_slide_2": "Zaprojektowane do przetrwania",
"intro_slide_3_title": "<div>Dostępne</div><div> wszędzie</div>",
"intro_slide_3": "Android, iOS, Strona Internetowa, Aplikacja Komputerowa",
"login": "Zaloguj się",
"sign_up": "Zarejestruj się",
"NEW_USER": "Nowy/a do Ente",
"EXISTING_USER": "Istniejący użytkownik",
"ENTER_NAME": "Wprowadź nazwę",
"enter_name": "Wprowadź nazwę",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Dodaj imię, aby Twoi znajomi wiedzieli, kto będzie mógł podziękować za te wspaniałe zdjęcia!",
"ENTER_EMAIL": "Wprowadź adres e-mail",
"EMAIL_ERROR": "Wprowadź prawidłowy adres e-mail",
@@ -44,11 +44,11 @@
"create_albums": "Utwórz albumy",
"CREATE_COLLECTION": "Nowy album",
"enter_album_name": "Nazwa albumu",
"CLOSE_OPTION": "Zamknij (Esc)",
"close_key": "Zamknij (Esc)",
"enter_file_name": "Nazwa pliku",
"CLOSE": "Zamknij",
"NO": "Nie",
"NOTHING_HERE": "",
"close": "Zamknij",
"no": "Nie",
"nothing_here": "Nic tu jeszcze nie ma",
"upload": "Prześlij",
"import": "Importuj",
"add_photos": "Dodaj zdjęcia",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "Hasło zostało zmienione gdzie indziej",
"password_changed_elsewhere_message": "Zaloguj się ponownie na tym urządzeniu, aby użyć nowego hasła, aby się uwierzytelnić.",
"GO_BACK": "Cofnij się",
"RECOVERY_KEY": "Klucz odzyskiwania",
"SAVE_LATER": "Zrób to później",
"SAVE": "Zapisz Klucz",
"recovery_key": "Klucz odzyskiwania",
"do_this_later": "Zrób to później",
"save_key": "Zapisz Klucz",
"RECOVERY_KEY_DESCRIPTION": "Jeśli zapomnisz swojego hasła, jedynym sposobem na odzyskanie Twoich danych jest ten klucz.",
"RECOVER_KEY_GENERATION_FAILED": "Nie można wygenerować kodu odzyskiwania, spróbuj ponownie",
"KEY_NOT_STORED_DISCLAIMER": "Nie przechowujemy tego klucza, prosimy zapisać to w bezpiecznym miejscu",
@@ -121,18 +121,17 @@
"RECOVER": "Odzyskaj",
"NO_RECOVERY_KEY": "Brak klucza odzyskiwania?",
"INCORRECT_RECOVERY_KEY": "Nieprawidłowy klucz odzyskiwania",
"SORRY": "Przepraszamy",
"sorry": "Przepraszamy",
"NO_RECOVERY_KEY_MESSAGE": "Ze względu na charakter naszego protokołu szyfrowania end-to-end, Twoje dane nie mogą być odszyfrowane bez hasła lub klucza odzyskiwania",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Wyślij wiadomość e-mail na <a>{{emailID}}</a> z zarejestrowanego adresu e-mail",
"CONTACT_SUPPORT": "Skontaktuj się z pomocą techniczną",
"REQUEST_FEATURE": "Zaproponuj Funkcję",
"SUPPORT": "Wsparcie Techniczne",
"CONFIRM": "Potwierdź",
"contact_support": "Skontaktuj się z pomocą techniczną",
"request_feature": "Zaproponuj Funkcję",
"support": "Wsparcie Techniczne",
"cancel": "Anuluj",
"LOGOUT": "Wyloguj się",
"logout": "Wyloguj się",
"logout_message": "Czy na pewno chcesz się wylogować?",
"delete_account": "Usuń konto",
"delete_account_manually_message": "<p>Prosimy wysłać wiadomość e-mail na <a>{{emailID}}</a> z Twojego zarejestrowanego adresu e-mail. </p><p>Twoja prośba zostanie przetworzona w ciągu 72 godzin.</p>",
"LOGOUT_MESSAGE": "Czy na pewno chcesz się wylogować?",
"CHANGE_EMAIL": "Zmień adres e-mail",
"ok": "OK",
"success": "Sukces",
@@ -324,7 +323,7 @@
"hide_collection": "Ukryj album",
"unhide_collection": "Odkryj album",
"MOVE": "Przenieś",
"ADD": "Dodaj",
"add": "Dodaj",
"REMOVE": "Usuń",
"YES_REMOVE": "Tak, usuń",
"REMOVE_FROM_COLLECTION": "Usuń z albumu",
@@ -596,7 +595,7 @@
"COLORS": "Kolory",
"FLIP": "Obróć",
"ROTATION": "Rotacja",
"RESET": "Zresetuj",
"reset": "Zresetuj",
"PHOTO_EDITOR": "Edytor Zdjęć",
"FASTER_UPLOAD": "Szybsze przesłania",
"FASTER_UPLOAD_DESCRIPTION": "Kieruj przesłania przez pobliskie serwery",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Backups privados</div><div>para as suas memórias</div>",
"HERO_SLIDE_1": "Criptografia de ponta a ponta por padrão",
"HERO_SLIDE_2_TITLE": "<div>Armazenado com segurança</div><div>em um abrigo avançado</div>",
"HERO_SLIDE_2": "Feito para ter longevidade",
"HERO_SLIDE_3_TITLE": "<div>Disponível</div><div> em qualquer lugar</div>",
"HERO_SLIDE_3": "Android, iOS, Web, Desktop",
"intro_slide_1_title": "<div>Backups privados</div><div>para as suas memórias</div>",
"intro_slide_1": "Criptografia de ponta a ponta por padrão",
"intro_slide_2_title": "<div>Armazenado com segurança</div><div>em um abrigo avançado</div>",
"intro_slide_2": "Feito para ter longevidade",
"intro_slide_3_title": "<div>Disponível</div><div> em qualquer lugar</div>",
"intro_slide_3": "Android, iOS, Web, Desktop",
"login": "Entrar",
"sign_up": "Registrar",
"NEW_USER": "Novo no Ente",
"EXISTING_USER": "Usuário existente",
"ENTER_NAME": "Insira o nome",
"enter_name": "Insira o nome",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Adicione um nome para que os seus amigos saibam a quem agradecer por estas ótimas fotos!",
"ENTER_EMAIL": "Insira o endereço de e-mail",
"EMAIL_ERROR": "Inserir um endereço de e-mail válido",
@@ -44,11 +44,11 @@
"create_albums": "Criar álbuns",
"CREATE_COLLECTION": "Novo álbum",
"enter_album_name": "Nome do álbum",
"CLOSE_OPTION": "Fechar (Esc)",
"close_key": "Fechar (Esc)",
"enter_file_name": "Nome do arquivo",
"CLOSE": "Fechar",
"NO": "Não",
"NOTHING_HERE": "",
"close": "Fechar",
"no": "Não",
"nothing_here": "Nada aqui ainda",
"upload": "Enviar",
"import": "Importar",
"add_photos": "Adicionar fotos",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "Senha alterada em outro lugar",
"password_changed_elsewhere_message": "Por favor, inicie sessão novamente neste dispositivo para usar sua nova senha para autenticar.",
"GO_BACK": "Voltar",
"RECOVERY_KEY": "Chave de recuperação",
"SAVE_LATER": "Fazer isso mais tarde",
"SAVE": "Salvar Chave",
"recovery_key": "Chave de recuperação",
"do_this_later": "Fazer isso mais tarde",
"save_key": "Salvar Chave",
"RECOVERY_KEY_DESCRIPTION": "Caso você esqueça sua senha, a única maneira de recuperar seus dados é com essa chave.",
"RECOVER_KEY_GENERATION_FAILED": "Não foi possível gerar o código de recuperação, tente novamente",
"KEY_NOT_STORED_DISCLAIMER": "Não armazenamos essa chave, por favor, salve essa chave de palavras em um lugar seguro",
@@ -121,18 +121,17 @@
"RECOVER": "Recuperar",
"NO_RECOVERY_KEY": "Não possui a chave de recuperação?",
"INCORRECT_RECOVERY_KEY": "Chave de recuperação incorreta",
"SORRY": "Desculpe",
"sorry": "Desculpe",
"NO_RECOVERY_KEY_MESSAGE": "Devido à natureza do nosso protocolo de criptografia de ponta a ponta, seus dados não podem ser descriptografados sem sua senha ou chave de recuperação",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Por favor, envie um e-mail para <a>{{emailID}}<a> a partir do seu endereço de e-mail registrado",
"CONTACT_SUPPORT": "Falar com o suporte",
"REQUEST_FEATURE": "Solicitar recurso",
"SUPPORT": "Suporte",
"CONFIRM": "Confirmar",
"contact_support": "Falar com o suporte",
"request_feature": "Solicitar recurso",
"support": "Suporte",
"cancel": "Cancelar",
"LOGOUT": "Encerrar sessão",
"logout": "Encerrar sessão",
"logout_message": "Você tem certeza que deseja encerrar a sessão?",
"delete_account": "Excluir conta",
"delete_account_manually_message": "<p>Por favor, envie um e-mail para <a>{{emailID}}</a> a partir do seu endereço de e-mail registrado.</p><p>Seu pedido será processado dentro de 72 horas.</p>",
"LOGOUT_MESSAGE": "Você tem certeza que deseja encerrar a sessão?",
"CHANGE_EMAIL": "Mudar e-mail",
"ok": "Aceitar",
"success": "Bem-sucedido",
@@ -324,7 +323,7 @@
"hide_collection": "Ocultar álbum",
"unhide_collection": "Reexibir álbum",
"MOVE": "Mover",
"ADD": "Adicionar",
"add": "Adicionar",
"REMOVE": "Remover",
"YES_REMOVE": "Sim, remover",
"REMOVE_FROM_COLLECTION": "Remover do álbum",
@@ -596,7 +595,7 @@
"COLORS": "Cores",
"FLIP": "Inverter",
"ROTATION": "Rotação",
"RESET": "Redefinir",
"reset": "Redefinir",
"PHOTO_EDITOR": "Editor de Fotos",
"FASTER_UPLOAD": "Envios mais rápidos",
"FASTER_UPLOAD_DESCRIPTION": "Rotas enviam em servidores próximos",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Backups privados</div><div>para as suas memórias</div>",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "<div>Disponível</div><div> em qualquer lugar</div>",
"HERO_SLIDE_3": "Android, iOS, Web, Desktop",
"intro_slide_1_title": "<div>Backups privados</div><div>para as suas memórias</div>",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "<div>Disponível</div><div> em qualquer lugar</div>",
"intro_slide_3": "Android, iOS, Web, Desktop",
"login": "Entrar",
"sign_up": "Registar",
"NEW_USER": "Novo no Ente",
"EXISTING_USER": "Utilizador existente",
"ENTER_NAME": "Insira o nome",
"enter_name": "Insira o nome",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Adicione um nome para que os seus amigos saibam a quem agradecer por estas ótimas fotos!",
"ENTER_EMAIL": "Insira o endereço de email",
"EMAIL_ERROR": "Inserir um endereço de email válido",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "Novo álbum",
"enter_album_name": "Nome do álbum",
"CLOSE_OPTION": "Fechar (Esc)",
"close_key": "Fechar (Esc)",
"enter_file_name": "Nome do ficheiro",
"CLOSE": "Fechar",
"NO": "Não",
"NOTHING_HERE": "",
"close": "Fechar",
"no": "Não",
"nothing_here": "",
"upload": "",
"import": "Importar",
"add_photos": "Adicionar fotos",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"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, Веб, ПК",
"intro_slide_1_title": "<div>Приватные резервные копии</div><div>для ваших воспоминаний</div>",
"intro_slide_1": "Сквозное шифрование по умолчанию",
"intro_slide_2_title": "<div>Надежно хранится</div><div>в убежище от радиоактивных осадков</div>",
"intro_slide_2": "Созданный для того, чтобы пережить",
"intro_slide_3_title": "<div>Доступно</div><div> везде</div>",
"intro_slide_3": "Android, iOS, Веб, ПК",
"login": "Войти",
"sign_up": "Регистрация",
"NEW_USER": "Новенький в Ente",
"EXISTING_USER": "Существующий пользователь",
"ENTER_NAME": "Введите имя",
"enter_name": "Введите имя",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Добавьте имя, чтобы ваши друзья знали, кого благодарить за эти замечательные фотографии!",
"ENTER_EMAIL": "Введите адрес электронной почты",
"EMAIL_ERROR": "Введите действительный адрес электронной почты",
@@ -44,11 +44,11 @@
"create_albums": "Создать альбомы",
"CREATE_COLLECTION": "Новый альбом",
"enter_album_name": "Название альбома",
"CLOSE_OPTION": "Закрыть (Esc)",
"close_key": "Закрыть (Esc)",
"enter_file_name": "Имя файла",
"CLOSE": "Закрыть",
"NO": "Нет",
"NOTHING_HERE": "",
"close": "Закрыть",
"no": "Нет",
"nothing_here": "",
"upload": "Загрузить",
"import": "Импорт",
"add_photos": "Добавить фотографии",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "Пароль изменен в другом месте",
"password_changed_elsewhere_message": "Пожалуйста, войдите снова на этом устройстве, чтобы использовать новый пароль для аутентификации.",
"GO_BACK": "Вернуться назад",
"RECOVERY_KEY": "Ключ восстановления",
"SAVE_LATER": "Сделать позже",
"SAVE": "Сохранить ключ",
"recovery_key": "Ключ восстановления",
"do_this_later": "Сделать позже",
"save_key": "Сохранить ключ",
"RECOVERY_KEY_DESCRIPTION": "Если вы забыли свой пароль, то восстановить данные можно только с помощью этого ключа.",
"RECOVER_KEY_GENERATION_FAILED": "Не удалось сгенерировать код восстановления, пожалуйста, повторите попытку",
"KEY_NOT_STORED_DISCLAIMER": "Мы не храним этот ключ, поэтому, пожалуйста, сохраните его в надежном месте",
@@ -121,18 +121,17 @@
"RECOVER": "Восстановить",
"NO_RECOVERY_KEY": "Нет ключа восстановления?",
"INCORRECT_RECOVERY_KEY": "Неправильный ключ восстановления",
"SORRY": "Извините",
"sorry": "Извините",
"NO_RECOVERY_KEY_MESSAGE": "Из-за природы нашего сквозного протокола шифрования ваши данные не могут быть расшифрованы без вашего пароля или ключа восстановления",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Пожалуйста, отправьте электронное письмо на адрес <a>{{emailID}}</a> с вашего зарегистрированного адреса электронной почты",
"CONTACT_SUPPORT": "Связаться с поддержкой",
"REQUEST_FEATURE": "Запросить функцию",
"SUPPORT": "Поддержка",
"CONFIRM": "Подтвердить",
"contact_support": "Связаться с поддержкой",
"request_feature": "Запросить функцию",
"support": "Поддержка",
"cancel": "Отменить",
"LOGOUT": "Выйти",
"logout": "Выйти",
"logout_message": "Вы уверены, что хотите выйти?",
"delete_account": "Удалить аккаунт",
"delete_account_manually_message": "<p>Пожалуйста, отправьте письмо по адресу <a>{{emailID}}</a> с вашего зарегистрированного адреса электронной почты.</p><p> Ваш запрос будет обработан в течение 72 часов</p>",
"LOGOUT_MESSAGE": "Вы уверены, что хотите выйти?",
"CHANGE_EMAIL": "Изменить адрес электронной почты",
"ok": "ОК",
"success": "Успешно",
@@ -324,7 +323,7 @@
"hide_collection": "Скрыть альбом",
"unhide_collection": "Показать альбом",
"MOVE": "Подвиньте",
"ADD": "Добавь",
"add": "Добавь",
"REMOVE": "Удалять",
"YES_REMOVE": "Да, удалить",
"REMOVE_FROM_COLLECTION": "Удалить из альбома",
@@ -596,7 +595,7 @@
"COLORS": "Цвета",
"FLIP": "Перевернуть",
"ROTATION": "Вращение",
"RESET": "Сбросить",
"reset": "Сбросить",
"PHOTO_EDITOR": "Редактор фото",
"FASTER_UPLOAD": "Более быстрая загрузка данных",
"FASTER_UPLOAD_DESCRIPTION": "Загрузка маршрута через близлежащие серверы",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Privata säkerhetskopior</div><div>för dina minnen</div>",
"HERO_SLIDE_1": "Totalsträckskryptering som standard",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "Utformad för att överleva",
"HERO_SLIDE_3_TITLE": "<div>Tillgänglig</div><div> överallt</div>",
"HERO_SLIDE_3": "Android, iOS, webb, skrivbord",
"intro_slide_1_title": "<div>Privata säkerhetskopior</div><div>för dina minnen</div>",
"intro_slide_1": "Totalsträckskryptering som standard",
"intro_slide_2_title": "",
"intro_slide_2": "Utformad för att överleva",
"intro_slide_3_title": "<div>Tillgänglig</div><div> överallt</div>",
"intro_slide_3": "Android, iOS, webb, skrivbord",
"login": "Logga in",
"sign_up": "Registrera dig",
"NEW_USER": "Ny hos Ente",
"EXISTING_USER": "Befintlig användare",
"ENTER_NAME": "Ange namn",
"enter_name": "Ange namn",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Lägg till ett namn så att dina vänner vet vem de ska tacka för dessa fantastiska bilder!",
"ENTER_EMAIL": "Ange e-postadress",
"EMAIL_ERROR": "Ange en giltig e-postadress",
@@ -44,11 +44,11 @@
"create_albums": "Skapa album",
"CREATE_COLLECTION": "Nytt album",
"enter_album_name": "Albumnamn",
"CLOSE_OPTION": "Stäng (Esc)",
"close_key": "Stäng (Esc)",
"enter_file_name": "Filnamn",
"CLOSE": "Stäng",
"NO": "Nej",
"NOTHING_HERE": "",
"close": "Stäng",
"no": "Nej",
"nothing_here": "",
"upload": "Ladda upp",
"import": "Importera",
"add_photos": "Lägg till foton",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "Återställningsnyckel",
"SAVE_LATER": "",
"SAVE": "Spara nyckel",
"recovery_key": "Återställningsnyckel",
"do_this_later": "",
"save_key": "Spara nyckel",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "Återställ",
"NO_RECOVERY_KEY": "Ingen återställningsnyckel?",
"INCORRECT_RECOVERY_KEY": "Felaktig återställningsnyckel",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "Support",
"CONFIRM": "Bekräfta",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "Avbryt",
"LOGOUT": "Logga ut",
"logout": "Logga ut",
"logout_message": "Är du säker på att du vill logga ut?",
"delete_account": "Radera konto",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "Är du säker på att du vill logga ut?",
"CHANGE_EMAIL": "Ändra e-postadress",
"ok": "OK",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "Dölj album",
"unhide_collection": "",
"MOVE": "Flytta",
"ADD": "Lägg till",
"add": "Lägg till",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "Färger",
"FLIP": "",
"ROTATION": "",
"RESET": "Återställ",
"reset": "Återställ",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "",
"NO": "",
"NOTHING_HERE": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "",
"NO": "",
"NOTHING_HERE": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "",
"NO": "",
"NOTHING_HERE": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "",
"NO": "",
"NOTHING_HERE": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "Giriş yap",
"sign_up": "Hesap aç",
"NEW_USER": "Yeni ente kullanıcısı",
"EXISTING_USER": "Mevcut kullanıcı",
"ENTER_NAME": "İsim gir",
"enter_name": "İsim gir",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Arkadaşlarının bu harika fotoğraflar için kime teşekkür etmeleri gerektiğini bilmeleri için bir isim ekle!",
"ENTER_EMAIL": "E-posta adresini girin",
"EMAIL_ERROR": "Geçerli bir e-posta gir",
@@ -44,11 +44,11 @@
"create_albums": "Albüm oluştur",
"CREATE_COLLECTION": "Yeni albüm",
"enter_album_name": "Albüm adı",
"CLOSE_OPTION": "Kapat (Esc)",
"close_key": "Kapat (Esc)",
"enter_file_name": "Dosya adı",
"CLOSE": "Kapat",
"NO": "Hayır",
"NOTHING_HERE": "",
"close": "Kapat",
"no": "Hayır",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "Parola başka bir yerde değiştirildi",
"password_changed_elsewhere_message": "Lütfen yeni parolanı kullanarak kimlik doğrulaması yapmak için bu cihazda tekrar oturum aç.",
"GO_BACK": "Geri dön",
"RECOVERY_KEY": "Kurtarma anahtarı",
"SAVE_LATER": "Sonra yap",
"SAVE": "Anahtarı kaydet",
"recovery_key": "Kurtarma anahtarı",
"do_this_later": "Sonra yap",
"save_key": "Anahtarı kaydet",
"RECOVERY_KEY_DESCRIPTION": "Eğer parolanı unutursan, verilerini kurtarabileceğin tek yol bu anahtardır.",
"RECOVER_KEY_GENERATION_FAILED": "Kurtarma kodu oluşturulamadı, lütfen tekrar dene",
"KEY_NOT_STORED_DISCLAIMER": "Bu anahtarı saklamıyoruz, bu nedenle lütfen güvenli bir yerde sakla",
@@ -121,18 +121,17 @@
"RECOVER": "Kurtar",
"NO_RECOVERY_KEY": "Kurtarma anahtarı yok mu?",
"INCORRECT_RECOVERY_KEY": "Kurtarma anahtarı yanlış",
"SORRY": "Üzgünüz",
"sorry": "Üzgünüz",
"NO_RECOVERY_KEY_MESSAGE": "Uçtan uca şifreleme protokolümüzün doğası gereği, verilerin parolan veya kurtarma anahtarın olmadan çözülemez",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Lütfen kaydolduğun e-posta adresinden <a>{{emailID}}</a> adresine bir e-posta bırak",
"CONTACT_SUPPORT": "Destek ile iletişime geç",
"REQUEST_FEATURE": "Özellik İste",
"SUPPORT": "Destek",
"CONFIRM": "Onayla",
"contact_support": "Destek ile iletişime geç",
"request_feature": "Özellik İste",
"support": "Destek",
"cancel": "İptal",
"LOGOUT": ıkış yap",
"logout": ıkış yap",
"logout_message": ıkış yapmak istediğine emin misin?",
"delete_account": "Hesabı sil",
"delete_account_manually_message": "<p>Lütfen kaydolduğun e-posta adresinden <a>{{emailID}}</a> adresine bir e-posta gönder.</p><p>İsteğin 72 saat içinde işleme alınacaktır.</p>",
"LOGOUT_MESSAGE": ıkış yapmak istediğine emin misin?",
"CHANGE_EMAIL": "E-posta adresini değiştir",
"ok": "Tamam",
"success": "Başarılı",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"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": "安卓, iOS, 网页端, 桌面端",
"intro_slide_1_title": "<div>私人备份</div><div>为您的回忆</div>",
"intro_slide_1": "默认端到端加密",
"intro_slide_2_title": "<div>安全地存放</div><div>在一个掩护所中</div>",
"intro_slide_2": "经久耐用",
"intro_slide_3_title": "<div>可用于</div><div> 各处</div>",
"intro_slide_3": "安卓, iOS, 网页端, 桌面端",
"login": "登录",
"sign_up": "注册",
"NEW_USER": "初来 Ente",
"EXISTING_USER": "现有用户",
"ENTER_NAME": "输入名字",
"enter_name": "输入名字",
"PUBLIC_UPLOADER_NAME_MESSAGE": "请添加一个名字,以便您的朋友知晓该感谢谁拍摄了这些精美的照片!",
"ENTER_EMAIL": "请输入电子邮件地址",
"EMAIL_ERROR": "请输入有效的电子邮件",
@@ -44,11 +44,11 @@
"create_albums": "创建相册",
"CREATE_COLLECTION": "新建相册",
"enter_album_name": "相册名称",
"CLOSE_OPTION": "关闭 (或按Esc键)",
"close_key": "关闭 (或按Esc键)",
"enter_file_name": "文件名",
"CLOSE": "关闭",
"NO": "否",
"NOTHING_HERE": "",
"close": "关闭",
"no": "否",
"nothing_here": "这里什么也没有",
"upload": "上传",
"import": "导入",
"add_photos": "添加照片",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "密码已在别处更改",
"password_changed_elsewhere_message": "请在此设备上再次登录以使用您的新密码进行身份验证。",
"GO_BACK": "返回",
"RECOVERY_KEY": "恢复密钥",
"SAVE_LATER": "稍后再做",
"SAVE": "保存密钥",
"recovery_key": "恢复密钥",
"do_this_later": "稍后再做",
"save_key": "保存密钥",
"RECOVERY_KEY_DESCRIPTION": "如果您忘记了密码,恢复数据的唯一方法就是使用此密钥。",
"RECOVER_KEY_GENERATION_FAILED": "无法生成恢复代码,请重试",
"KEY_NOT_STORED_DISCLAIMER": "我们不存储此密钥,因此请将其保存在安全的地方",
@@ -121,18 +121,17 @@
"RECOVER": "恢复",
"NO_RECOVERY_KEY": "没有恢复密钥?",
"INCORRECT_RECOVERY_KEY": "不正确的恢复密钥",
"SORRY": "抱歉",
"sorry": "抱歉",
"NO_RECOVERY_KEY_MESSAGE": "由于我们端到端加密协议的性质,如果没有您的密码或恢复密钥,您的数据将无法解密",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "请用您注册Ente账户的电子邮箱发一封邮件给 <a>{{emailID}}</a>",
"CONTACT_SUPPORT": "联系支持",
"REQUEST_FEATURE": "功能建议",
"SUPPORT": "支持",
"CONFIRM": "确认",
"contact_support": "联系支持",
"request_feature": "功能建议",
"support": "支持",
"cancel": "取消",
"LOGOUT": "退出登录",
"logout": "退出登录",
"logout_message": "你确定要退出登录吗?",
"delete_account": "删除账户",
"delete_account_manually_message": "<p>请从您注册的电子邮件地址发送一封电子邮件到 <a>{{emailID}}</a></p><p>。您的请求将在72小时内处理。</p>",
"LOGOUT_MESSAGE": "你确定要退出登录吗?",
"CHANGE_EMAIL": "更换邮箱",
"ok": "确定",
"success": "成功",
@@ -324,7 +323,7 @@
"hide_collection": "隐藏相册",
"unhide_collection": "取消隐藏相册",
"MOVE": "移动",
"ADD": "添加",
"add": "添加",
"REMOVE": "移除",
"YES_REMOVE": "是,移除",
"REMOVE_FROM_COLLECTION": "从相册中移除",
@@ -596,7 +595,7 @@
"COLORS": "颜色",
"FLIP": "上下翻转",
"ROTATION": "回转",
"RESET": "重设",
"reset": "重设",
"PHOTO_EDITOR": "照片编辑器",
"FASTER_UPLOAD": "更快上传",
"FASTER_UPLOAD_DESCRIPTION": "通过附近的服务器路由上传",

View File

@@ -1,4 +1,4 @@
import { getKV } from "@/base/kv";
import { getKVS } from "@/base/kv";
/**
* Return the origin (scheme, host, port triple) that should be used for making
@@ -19,9 +19,23 @@ export const apiOrigin = async () =>
* @param path The URL path usually, but can be anything that needs to be
* suffixed to the origin. It must begin with a "/".
*
* @param queryParams An optional object containing query params. This is
* appended to the generated URL after funneling it through
* {@link URLSearchParams}.
*
* @returns path prefixed by {@link apiOrigin}.
*/
export const apiURL = async (path: string) => (await apiOrigin()) + path;
export const apiURL = async (
path: string,
queryParams?: Record<string, string>,
) => {
let url = (await apiOrigin()) + path;
if (queryParams) {
const params = new URLSearchParams(queryParams);
url = `${url}?${params.toString()}`;
}
return url;
};
/**
* Return the overridden API origin, if one is defined by either (in priority
@@ -35,7 +49,7 @@ export const apiURL = async (path: string) => (await apiOrigin()) + path;
* Otherwise return undefined.
*/
export const customAPIOrigin = async () =>
(await getKV("apiOrigin")) ??
(await getKVS("apiOrigin")) ??
process.env.NEXT_PUBLIC_ENTE_ENDPOINT ??
undefined;

View File

@@ -553,12 +553,16 @@ export interface ElectronMLWorker {
* See: [Note: Natural language search using CLIP]
*
* The input is a opaque float32 array representing the image. The layout
* and exact encoding of the input is specific to our implementation and the
* ML model (CLIP) we use.
* and exact encoding of the input is specific to the runtime (ONNX) and the
* ML model (a MobileCLIP variant) we use. In particular, the image
* pre-processing happens within our model itself.
*
* @returns A CLIP embedding (an array of 512 floating point values).
*/
computeCLIPImageEmbedding: (input: Float32Array) => Promise<Float32Array>;
computeCLIPImageEmbedding: (
input: Uint8ClampedArray,
inputShape: number[],
) => Promise<Float32Array>;
/**
* Return a CLIP embedding of the given image if we already have the model

View File

@@ -1,6 +1,6 @@
import { useIsMobileWidth } from "@/base/hooks";
import { ensureOk } from "@/base/http";
import { getKV, removeKV, setKV } from "@/base/kv";
import { getKVS, removeKV, setKV } from "@/base/kv";
import log from "@/base/log";
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
import {
@@ -69,7 +69,8 @@ const Contents: React.FC<ContentsProps> = (props) => {
>();
useEffect(
() => void getKV("apiOrigin").then((o) => setInitialAPIOrigin(o ?? "")),
() =>
void getKVS("apiOrigin").then((o) => setInitialAPIOrigin(o ?? "")),
[],
);

View File

@@ -52,6 +52,10 @@ import {
export type GalleryBarMode = "albums" | "hidden-albums" | "people";
export interface GalleryBarImplProps {
/**
* When `true`, the bar shows a button to switch to the people section.
*/
showPeopleSectionButton: boolean;
/**
* What are we displaying currently.
*/
@@ -94,18 +98,18 @@ export interface GalleryBarImplProps {
*/
people: Person[];
/**
* The currently selected person.
*
* Required if mode is "people".
* The currently selected person, if any.
*/
activePerson: Person | undefined;
/**
* Called when the user selects a new person in the bar.
* Called when the selection should be moved to a new person in the bar, or
* reset to the default state (when {@link person} is `undefined`).
*/
onSelectPerson: (person: Person) => void;
onSelectPerson: (person: Person | undefined) => void;
}
export const GalleryBarImpl: React.FC<GalleryBarImplProps> = ({
showPeopleSectionButton,
mode,
onChangeMode,
collectionSummaries,
@@ -209,9 +213,9 @@ export const GalleryBarImpl: React.FC<GalleryBarImplProps> = ({
onSelectCollectionID,
}
: {
type: "people",
type: "people" as const,
people,
activePerson: ensure(activePerson),
activePerson,
onSelectPerson,
},
[
@@ -225,7 +229,7 @@ export const GalleryBarImpl: React.FC<GalleryBarImplProps> = ({
],
);
const controls1 = isMobile && (
const controls1 = isMobile && mode != "people" && (
<Box display="flex" alignItems={"center"} gap={1}>
<CollectionsSortOptions
activeSortBy={collectionsSortBy}
@@ -238,7 +242,7 @@ export const GalleryBarImpl: React.FC<GalleryBarImplProps> = ({
</Box>
);
const controls2 = !isMobile && (
const controls2 = !isMobile && mode != "people" && (
<Box display="flex" alignItems={"center"} gap={1} height={"64px"}>
<CollectionsSortOptions
activeSortBy={collectionsSortBy}
@@ -251,9 +255,14 @@ export const GalleryBarImpl: React.FC<GalleryBarImplProps> = ({
);
return (
<BarWrapper>
// Hide the bottom border if we're showing the empty state for people.
<BarWrapper
sx={people.length ? {} : { borderBlockEndColor: "transparent" }}
>
<Row1>
<ModeIndicator {...{ mode, onChangeMode }} />
<ModeIndicator
{...{ showPeopleSectionButton, mode, onChangeMode }}
/>
{controls1}
</Row1>
<Row2>
@@ -300,7 +309,7 @@ export const Row1 = styled(Box)`
align-items: center;
justify-content: space-between;
gap: 10px;
margin-block-end: 8px;
margin-block-end: 12px;
`;
export const Row2 = styled(Box)`
@@ -310,33 +319,46 @@ export const Row2 = styled(Box)`
`;
const ModeIndicator: React.FC<
Pick<GalleryBarImplProps, "mode" | "onChangeMode">
> = ({ mode, onChangeMode }) => {
Pick<
GalleryBarImplProps,
"showPeopleSectionButton" | "mode" | "onChangeMode"
>
> = ({ showPeopleSectionButton, mode, onChangeMode }) => {
// Mode switcher is not shown in the hidden albums section.
if (mode == "hidden-albums") {
return <Typography>{t("hidden_albums")}</Typography>;
}
// Show the static mode indicator with only the "Albums" title unless we
// come here with the people mode already set. This is because we don't
// currently have an empty state for the People mode when ML is not enabled.
if (mode == "albums") {
// Show the static mode indicator with only the "Albums" title if we have
// not been asked to show the people button (there are no other sections to
// switch to in such a case).
if (!showPeopleSectionButton) {
return <Typography>{t("albums")}</Typography>;
}
return (
<Stack direction="row" sx={{ gap: "10px" }}>
<AlbumModeButton onClick={() => onChangeMode("albums")}>
<ModeButton
active={mode == "albums"}
onClick={() => onChangeMode("albums")}
>
<Typography>{t("albums")}</Typography>
</AlbumModeButton>
<Typography>{t("people")}</Typography>
</ModeButton>
<ModeButton
active={mode == "people"}
onClick={() => onChangeMode("people")}
>
<Typography>{t("people")}</Typography>
</ModeButton>
</Stack>
);
};
const AlbumModeButton = styled(UnstyledButton)(
({ theme }) => `
p { color: ${theme.colors.text.muted} }
const ModeButton = styled(UnstyledButton, {
shouldForwardProp: (propName) => propName != "active",
})<{ active: boolean }>(
({ active, theme }) => `
p { color: ${active ? theme.colors.text.base : theme.colors.text.muted} }
p:hover { color: ${theme.colors.text.base} }
`,
);
@@ -403,7 +425,7 @@ type ItemData =
| {
type: "people";
people: Person[];
activePerson: Person;
activePerson: Person | undefined;
onSelectPerson: (person: Person) => void;
};
@@ -569,7 +591,7 @@ const ActiveIndicator = styled("div")`
interface PersonCardProps {
person: Person;
activePerson: Person;
activePerson: Person | undefined;
onSelectPerson: (person: Person) => void;
}
@@ -582,10 +604,11 @@ const PersonCard: React.FC<PersonCardProps> = ({
<ItemCard
TileComponent={BarItemTile}
coverFile={person.displayFaceFile}
coverFaceID={person.displayFaceID}
onClick={() => onSelectPerson(person)}
>
{person.name && <CardText text={person.name} />}
</ItemCard>
{activePerson.id === person.id && <ActiveIndicator />}
{activePerson?.id === person.id && <ActiveIndicator />}
</Box>
);

View File

@@ -0,0 +1,242 @@
/**
* @file code that really belongs to pages/gallery.tsx itself (or related
* files), but it written here in a separate file so that we can write in this
* package that has TypeScript strict mode enabled.
*
* Once the original gallery.tsx is strict mode, this code can be inlined back
* there.
*/
import { pt } from "@/base/i18n";
import {
addPerson,
deletePerson,
renamePerson,
} from "@/new/photos/services/ml/";
import { type Person } from "@/new/photos/services/ml/people";
import OverflowMenu from "@ente/shared/components/OverflowMenu/menu";
import { OverflowMenuOption } from "@ente/shared/components/OverflowMenu/option";
import AddIcon from "@mui/icons-material/Add";
import EditIcon from "@mui/icons-material/Edit";
import MoreHoriz from "@mui/icons-material/MoreHoriz";
import { IconButton, Stack, Tooltip } from "@mui/material";
import { ClearIcon } from "@mui/x-date-pickers";
import { t } from "i18next";
import React, { useState } from "react";
import type { FaceCluster } from "../../services/ml/cluster";
import type { CGroup } from "../../services/user-entity";
import type { NewAppContextPhotos } from "../../types/context";
import { SpaceBetweenFlex } from "../mui-custom";
import { NameInputDialog } from "../NameInputDialog";
import type { GalleryBarImplProps } from "./BarImpl";
import { GalleryItemsHeaderAdapter, GalleryItemsSummary } from "./ListHeader";
/**
* Derived UI state backing the gallery when it is in "people" mode.
*
* This may be different from the actual underlying state since there might be
* unsynced data (hidden or deleted that have not yet been synced with remote)
* that should be taken into account for the UI state.
*/
export interface GalleryPeopleState {
/**
* The currently selected person, if any.
*
* Whenever this is present, it is guaranteed to be one of the items from
* within {@link people}.
*/
activePerson: Person | undefined;
/**
* The list of people to show.
*/
people: Person[];
}
type PeopleHeaderProps = Pick<GalleryBarImplProps, "onSelectPerson"> & {
person: Person;
appContext: NewAppContextPhotos;
};
export const PeopleHeader: React.FC<PeopleHeaderProps> = ({
person,
appContext,
onSelectPerson,
}) => {
return (
<GalleryItemsHeaderAdapter>
<SpaceBetweenFlex>
<GalleryItemsSummary
name={
person.name ?? pt("Unnamed person") /* TODO-Cluster */
}
nameProps={person.name ? {} : { color: "text.muted" }}
fileCount={person.fileIDs.length}
/>
{person.type == "cgroup" ? (
<CGroupPersonOptions
cgroup={person.cgroup}
{...{ onSelectPerson, appContext }}
/>
) : (
<ClusterPersonOptions
cluster={person.cluster}
appContext={appContext}
/>
)}
</SpaceBetweenFlex>
</GalleryItemsHeaderAdapter>
);
};
type CGroupPersonOptionsProps = Pick<
PeopleHeaderProps,
"appContext" | "onSelectPerson"
> & {
cgroup: CGroup;
};
const CGroupPersonOptions: React.FC<CGroupPersonOptionsProps> = ({
cgroup,
appContext,
onSelectPerson,
}) => {
const {
startLoading,
finishLoading,
onGenericError,
setDialogBoxAttributesV2,
} = appContext;
const [openAddNameInput, setOpenAddNameInput] = useState(false);
const handleRenamePerson = () => setOpenAddNameInput(true);
const renamePersonUsingName = async (name: string) => {
startLoading();
try {
await renamePerson(name, cgroup);
} finally {
finishLoading();
}
};
const handleDeletePerson = () =>
setDialogBoxAttributesV2({
title: pt("Reset person?"),
content: pt(
"The name, face groupings and suggestions for this person will be reset",
),
close: { text: t("cancel") },
proceed: {
text: t("reset"),
action: doDeletePerson,
},
buttonDirection: "row",
});
const doDeletePerson = async () => {
startLoading();
try {
await deletePerson(cgroup);
// Reset the selection to the default state.
onSelectPerson(undefined);
} catch (e) {
onGenericError(e);
} finally {
finishLoading();
}
};
return (
<>
<OverflowMenu
ariaControls={"person-options"}
triggerButtonIcon={<MoreHoriz />}
>
<OverflowMenuOption
startIcon={<EditIcon />}
centerAlign
onClick={handleRenamePerson}
>
{t("rename")}
</OverflowMenuOption>
<OverflowMenuOption
startIcon={<ClearIcon />}
centerAlign
onClick={handleDeletePerson}
>
{pt("Reset")}
</OverflowMenuOption>
</OverflowMenu>
<NameInputDialog
open={openAddNameInput}
onClose={() => setOpenAddNameInput(false)}
title={pt("Rename person") /* TODO-Cluster pt()'s */}
placeholder={t("enter_name")}
initialValue={cgroup.data.name ?? ""}
submitButtonTitle={t("rename")}
onSubmit={renamePersonUsingName}
/>
</>
);
};
type ClusterPersonOptionsProps = Pick<PeopleHeaderProps, "appContext"> & {
cluster: FaceCluster;
};
const ClusterPersonOptions: React.FC<ClusterPersonOptionsProps> = ({
cluster,
appContext,
}) => {
const { startLoading, finishLoading } = appContext;
const [openNameInput, setOpenNameInput] = useState(false);
const handleAddPerson = () => setOpenNameInput(true);
const addPersonWithName = async (name: string) => {
startLoading();
try {
await addPerson(name, cluster);
} finally {
finishLoading();
}
};
return (
<>
<Stack direction="row" sx={{ alignItems: "center", gap: 2 }}>
<Tooltip title={pt("Add a name")}>
<IconButton onClick={handleAddPerson}>
<AddIcon />
</IconButton>
</Tooltip>
<OverflowMenu
ariaControls={"person-options"}
triggerButtonIcon={<MoreHoriz />}
>
<OverflowMenuOption
startIcon={<AddIcon />}
centerAlign
onClick={handleAddPerson}
>
{pt("Add a name")}
</OverflowMenuOption>
</OverflowMenu>
</Stack>
<NameInputDialog
open={openNameInput}
onClose={() => setOpenNameInput(false)}
title={pt("Add person") /* TODO-Cluster */}
placeholder={t("enter_name")}
initialValue={""}
submitButtonTitle={t("add")}
onSubmit={addPersonWithName}
/>
</>
);
};

View File

@@ -7,16 +7,12 @@
* there.
*/
import type { Person } from "@/new/photos/services/ml/people";
import { pt } from "@/base/i18n";
import type { SearchOption } from "@/new/photos/services/search/types";
import OverflowMenu from "@ente/shared/components/OverflowMenu/menu";
import { OverflowMenuOption } from "@ente/shared/components/OverflowMenu/option";
import EditIcon from "@mui/icons-material/Edit";
import MoreHoriz from "@mui/icons-material/MoreHoriz";
import { VerticallyCentered } from "@ente/shared/components/Container";
import { Typography } from "@mui/material";
import { t } from "i18next";
import React from "react";
import { SpaceBetweenFlex } from "../mui-custom";
import { GalleryItemsHeaderAdapter, GalleryItemsSummary } from "./ListHeader";
/**
@@ -47,36 +43,16 @@ export const SearchResultsHeader: React.FC<SearchResultsHeaderProps> = ({
</GalleryItemsHeaderAdapter>
);
interface PeopleListHeaderProps {
person: Person;
}
export const PersonListHeader: React.FC<PeopleListHeaderProps> = ({
person,
}) => {
const hasOptions = process.env.NEXT_PUBLIC_ENTE_WIP_CL;
return (
<GalleryItemsHeaderAdapter>
<SpaceBetweenFlex>
<GalleryItemsSummary
name={person.name ?? "Unnamed person"}
nameProps={person.name ? {} : { color: "text.muted" }}
fileCount={person.fileIDs.length}
/>
{hasOptions && (
<OverflowMenu
ariaControls={"person-options"}
triggerButtonIcon={<MoreHoriz />}
>
<OverflowMenuOption
startIcon={<EditIcon />}
onClick={() => console.log("test")}
>
{t("download_album")}
</OverflowMenuOption>
</OverflowMenu>
)}
</SpaceBetweenFlex>
</GalleryItemsHeaderAdapter>
);
};
export const PeopleEmptyState: React.FC = () => (
<VerticallyCentered>
<Typography
color="text.muted"
sx={{
// Approximately compensate for the hidden section bar
paddingBlockEnd: "86px",
}}
>
{pt("People will appear here once indexing completes")}
</Typography>
</VerticallyCentered>
);

Some files were not shown because too many files have changed in this diff Show More