Compare commits
145 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6538e599dd | ||
|
|
789e3e3924 | ||
|
|
3d505e425d | ||
|
|
e7122d7a72 | ||
|
|
94d0705607 | ||
|
|
caba462703 | ||
|
|
ffe397247e | ||
|
|
e7ad622c02 | ||
|
|
bca4626708 | ||
|
|
7f0ad8e2d2 | ||
|
|
6c6c5ef651 | ||
|
|
a460940430 | ||
|
|
fc2455be80 | ||
|
|
fd4357cf23 | ||
|
|
e41e0df27e | ||
|
|
f370dc3929 | ||
|
|
1c2d83e2c7 | ||
|
|
d6756f3d81 | ||
|
|
71ef7685c5 | ||
|
|
b7516f31c6 | ||
|
|
065fb166c2 | ||
|
|
4cc6e3b966 | ||
|
|
1c293a2759 | ||
|
|
062e2eca6f | ||
|
|
bcc2c34eef | ||
|
|
1613ae9185 | ||
|
|
d827a6182b | ||
|
|
83df14d379 | ||
|
|
7c1dae918d | ||
|
|
1b54c4f8e7 | ||
|
|
49b74e9091 | ||
|
|
a1f1e5bc37 | ||
|
|
2dc8a93685 | ||
|
|
c2145cbe11 | ||
|
|
50a792a81a | ||
|
|
e2bd7e1e08 | ||
|
|
11a5a990d0 | ||
|
|
ecc894ac82 | ||
|
|
50b649cd3e | ||
|
|
99b018cd49 | ||
|
|
6aa2800275 | ||
|
|
cd7fc7e026 | ||
|
|
b4d312efb6 | ||
|
|
e9722710ac | ||
|
|
f1384fea58 | ||
|
|
feadc45e75 | ||
|
|
eefe5266a8 | ||
|
|
74353193f8 | ||
|
|
0ccb73cf2b | ||
|
|
356f4424df | ||
|
|
85c6cf4309 | ||
|
|
96fb68135e | ||
|
|
a7b9adc692 | ||
|
|
e028cf9002 | ||
|
|
f984be8ea0 | ||
|
|
3d426b55d3 | ||
|
|
02b8b2c125 | ||
|
|
dc7b0f75bb | ||
|
|
a089d9891d | ||
|
|
a1183f4b4b | ||
|
|
84cfa38510 | ||
|
|
89edbcacfa | ||
|
|
c8e649f190 | ||
|
|
790e43dd6e | ||
|
|
59f6b2ff2e | ||
|
|
829defbf61 | ||
|
|
70a0f4ae48 | ||
|
|
c7c0ef6abc | ||
|
|
2fc8a0db92 | ||
|
|
b50c621be8 | ||
|
|
126f5857c3 | ||
|
|
8b3e1764a8 | ||
|
|
b776461297 | ||
|
|
4a0052026f | ||
|
|
35c4887e4a | ||
|
|
f5b87833f8 | ||
|
|
0dde76bbbc | ||
|
|
93863b0629 | ||
|
|
115a47d4c6 | ||
|
|
308c63df16 | ||
|
|
ab86d0a18d | ||
|
|
3ec74444b0 | ||
|
|
1979c84ea8 | ||
|
|
f4aefcb18b | ||
|
|
7f2fa23179 | ||
|
|
4524aa0d06 | ||
|
|
15fa8250cb | ||
|
|
4dff129949 | ||
|
|
43951ec208 | ||
|
|
f961acdf0c | ||
|
|
2c7821e5e6 | ||
|
|
d25ddfc46b | ||
|
|
8cc9b08c06 | ||
|
|
98b9d815a6 | ||
|
|
a808b9403e | ||
|
|
c956eee919 | ||
|
|
aa97ca9ccf | ||
|
|
98bb3de8da | ||
|
|
cd43edf074 | ||
|
|
dffd992304 | ||
|
|
f1b70e13a1 | ||
|
|
d91247dc35 | ||
|
|
25f55ee6bb | ||
|
|
c2e9fe0aac | ||
|
|
5885ec8e65 | ||
|
|
053104fc50 | ||
|
|
861de7f8b3 | ||
|
|
b5a4aef829 | ||
|
|
54b4f8afbd | ||
|
|
65daf342df | ||
|
|
15a498fd60 | ||
|
|
af7da9d2c9 | ||
|
|
91ad584064 | ||
|
|
b6b9f51bd7 | ||
|
|
6acfb55dcc | ||
|
|
ce42b84430 | ||
|
|
59d93138d3 | ||
|
|
78de189d56 | ||
|
|
b21c99eb12 | ||
|
|
e22cdea485 | ||
|
|
1e97407025 | ||
|
|
c4f5dc6d01 | ||
|
|
c329a17975 | ||
|
|
2a88cc74bf | ||
|
|
7e965cb6d4 | ||
|
|
6631b286c1 | ||
|
|
b8313abfa8 | ||
|
|
aa91b946fa | ||
|
|
82af2c5717 | ||
|
|
4cdc59e51c | ||
|
|
d34585e4b0 | ||
|
|
d565a684a1 | ||
|
|
13f178dca8 | ||
|
|
f8ba33e81c | ||
|
|
3d251f51fc | ||
|
|
57704522cd | ||
|
|
787926c111 | ||
|
|
08b424b3df | ||
|
|
736a946101 | ||
|
|
6f6f847ee2 | ||
|
|
13be271df7 | ||
|
|
b423852fad | ||
|
|
fe3d6b870a | ||
|
|
14be63039f | ||
|
|
d339d4c8dd |
2
.github/workflows/build-mobile.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: "3.7.3"
|
||||
flutter-version: "3.10.0"
|
||||
cache: true
|
||||
|
||||
- name: Create the Keystore
|
||||
|
||||
79
.github/workflows/docker-cleanup.yml
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
# This workflow runs on certain conditions to check for and potentially
|
||||
# delete container images from the GHCR which no longer have an associated
|
||||
# code branch.
|
||||
# Requires a PAT with the correct scope set in the secrets.
|
||||
#
|
||||
# This workflow will not trigger runs on forked repos.
|
||||
|
||||
name: Cleanup Old Docker Images
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- "closed"
|
||||
push:
|
||||
paths:
|
||||
- ".github/workflows/docker-cleanup.yml"
|
||||
|
||||
concurrency:
|
||||
group: registry-tags-cleanup
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
cleanup-images:
|
||||
name: Cleanup Stale Images Tags for ${{ matrix.primary-name }}
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- primary-name: "immich-server"
|
||||
- primary-name: "immich-machine-learning"
|
||||
- primary-name: "immich-web"
|
||||
- primary-name: "immich-proxy"
|
||||
env:
|
||||
# Requires a personal access token with the OAuth scope delete:packages
|
||||
TOKEN: ${{ secrets.PACKAGE_DELETE_TOKEN }}
|
||||
steps:
|
||||
-
|
||||
name: Clean temporary images
|
||||
if: "${{ env.TOKEN != '' }}"
|
||||
uses: stumpylog/image-cleaner-action/ephemeral@v0.1.0
|
||||
with:
|
||||
token: "${{ env.TOKEN }}"
|
||||
owner: "immich-app"
|
||||
is_org: "true"
|
||||
do_delete: "true"
|
||||
package_name: "${{ matrix.primary-name }}"
|
||||
scheme: "pull_request"
|
||||
repo_name: "immich"
|
||||
match_regex: '^pr-(\d+)$|^(\d+)$'
|
||||
|
||||
cleanup-untagged-images:
|
||||
name: Cleanup Untagged Images Tags for ${{ matrix.primary-name }}
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- cleanup-images
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- primary-name: "immich-server"
|
||||
- primary-name: "immich-machine-learning"
|
||||
- primary-name: "immich-web"
|
||||
- primary-name: "immich-proxy"
|
||||
- primary-name: "immich-build-cache"
|
||||
env:
|
||||
# Requires a personal access token with the OAuth scope delete:packages
|
||||
TOKEN: ${{ secrets.PACKAGE_DELETE_TOKEN }}
|
||||
steps:
|
||||
-
|
||||
name: Clean untagged images
|
||||
if: "${{ env.TOKEN != '' }}"
|
||||
uses: stumpylog/image-cleaner-action/untagged@v0.1.0
|
||||
with:
|
||||
token: "${{ env.TOKEN }}"
|
||||
owner: "immich-app"
|
||||
do_delete: "true"
|
||||
is_org: "true"
|
||||
package_name: "${{ matrix.primary-name }}"
|
||||
5
.github/workflows/static_analysis.yml
vendored
@@ -22,8 +22,8 @@ jobs:
|
||||
- name: Setup Flutter SDK
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: 'stable'
|
||||
flutter-version: '3.7.3'
|
||||
channel: "stable"
|
||||
flutter-version: "3.10.0"
|
||||
|
||||
- name: Install dependencies
|
||||
run: dart pub get
|
||||
@@ -32,4 +32,3 @@ jobs:
|
||||
- name: Run dart analyze
|
||||
run: dart analyze --fatal-infos
|
||||
working-directory: ./mobile
|
||||
|
||||
|
||||
40
.github/workflows/test.yml
vendored
@@ -21,6 +21,28 @@ jobs:
|
||||
- name: Run Immich Server E2E Test
|
||||
run: docker-compose -f ./docker/docker-compose.test.yml --env-file ./docker/.env.test up --abort-on-container-exit --exit-code-from immich-server-test
|
||||
|
||||
doc-tests:
|
||||
name: Run documentation checks
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./docs
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run npm install
|
||||
run: npm ci
|
||||
|
||||
- name: Run formatter
|
||||
run: npm run format
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Run tsc
|
||||
run: npm run check
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
server-unit-tests:
|
||||
name: Run server unit test suites and checks
|
||||
runs-on: ubuntu-latest
|
||||
@@ -90,10 +112,10 @@ jobs:
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: "3.7.3"
|
||||
flutter-version: "3.10.0"
|
||||
- name: Run tests
|
||||
working-directory: ./mobile
|
||||
run: flutter test
|
||||
run: flutter test -j 1
|
||||
|
||||
generated-api-up-to-date:
|
||||
name: Check generated files are up-to-date
|
||||
@@ -101,7 +123,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run API generation
|
||||
run: cd server && npm ci && npm run api:generate
|
||||
run: npm --prefix server run api:generate
|
||||
- name: Find file changes
|
||||
uses: tj-actions/verify-changed-files@v13.1
|
||||
id: verify-changed-files
|
||||
@@ -136,18 +158,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install server dependencies
|
||||
run: |
|
||||
cd server
|
||||
npm ci
|
||||
run: npm --prefix server ci
|
||||
- name: Run existing migrations
|
||||
run: |
|
||||
cd server
|
||||
npm run typeorm:migrations:run
|
||||
run: npm --prefix server run typeorm:migrations:run
|
||||
- name: Generate new migrations
|
||||
continue-on-error: true
|
||||
run: |
|
||||
cd server
|
||||
npm run typeorm:migrations:generate ./libs/infra/src/migrations/TestMigration
|
||||
run: npm --prefix server run typeorm:migrations:generate ./libs/infra/src/migrations/TestMigration
|
||||
- name: Find file changes
|
||||
uses: tj-actions/verify-changed-files@v13.1
|
||||
id: verify-changed-files
|
||||
|
||||
9
.vscode/launch.json
vendored
@@ -9,6 +9,15 @@
|
||||
"name": "Immich Server",
|
||||
"remoteRoot": "/usr/src/app",
|
||||
"localRoot": "${workspaceFolder}/server"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"restart": true,
|
||||
"port": 9231,
|
||||
"name": "Immich Microservices",
|
||||
"remoteRoot": "/usr/src/app",
|
||||
"localRoot": "${workspaceFolder}/server"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
10
Makefile
@@ -1,17 +1,17 @@
|
||||
dev:
|
||||
rm -rf ./server/dist && docker-compose -f ./docker/docker-compose.dev.yml up --remove-orphans
|
||||
docker-compose -f ./docker/docker-compose.dev.yml up --remove-orphans
|
||||
|
||||
dev-new:
|
||||
rm -rf ./server/dist && docker compose -f ./docker/docker-compose.dev.yml up --remove-orphans
|
||||
docker compose -f ./docker/docker-compose.dev.yml up --remove-orphans
|
||||
|
||||
dev-new-update:
|
||||
rm -rf ./server/dist && docker compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
|
||||
docker compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
|
||||
|
||||
dev-update:
|
||||
rm -rf ./server/dist && docker-compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
|
||||
docker-compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
|
||||
|
||||
dev-scale:
|
||||
rm -rf ./server/dist && docker-compose -f ./docker/docker-compose.dev.yml up --build -V --scale immich-server=3 --remove-orphans
|
||||
docker-compose -f ./docker/docker-compose.dev.yml up --build -V --scale immich-server=3 --remove-orphans
|
||||
|
||||
stage:
|
||||
docker-compose -f ./docker/docker-compose.staging.yml up --build -V --remove-orphans
|
||||
|
||||
52
README.md
@@ -37,7 +37,6 @@
|
||||
- [Installation](https://immich.app/docs/install/requirements)
|
||||
- [Contribution Guidelines](https://immich.app/docs/overview/support-the-project)
|
||||
- [Support The Project](#support-the-project)
|
||||
- [Known Issues](#known-issues)
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -61,25 +60,30 @@ Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
|
||||
|
||||
# Features
|
||||
|
||||
| Features | Mobile | Web |
|
||||
| ------------------------------------------- | ------ | --- |
|
||||
| Upload and view videos and photos | Yes | Yes |
|
||||
| Auto backup when the app is opened | Yes | N/A |
|
||||
| Selective album(s) for backup | Yes | N/A |
|
||||
| Download photos and videos to local device | Yes | Yes |
|
||||
| Multi-user support | Yes | Yes |
|
||||
| Album and Shared albums | Yes | Yes |
|
||||
| Scrubbable/draggable scrollbar | Yes | Yes |
|
||||
| Support RAW (HEIC, HEIF, DNG, Apple ProRaw) | Yes | Yes |
|
||||
| Metadata view (EXIF, map) | Yes | Yes |
|
||||
| Search by metadata, objects and CLIP | Yes | No |
|
||||
| Administrative functions (user management) | N/A | Yes |
|
||||
| Background backup | Yes | N/A |
|
||||
| Virtual scroll | Yes | Yes |
|
||||
| OAuth support | Yes | Yes |
|
||||
| LivePhoto backup and playback | iOS | Yes |
|
||||
| User-defined storage structure | Yes | Yes |
|
||||
| Public Sharing | N/A | Yes |
|
||||
| Features | Mobile | Web |
|
||||
| -------------------------------------------- | ------ | --- |
|
||||
| Upload and view videos and photos | Yes | Yes |
|
||||
| Auto backup when the app is opened | Yes | N/A |
|
||||
| Selective album(s) for backup | Yes | N/A |
|
||||
| Download photos and videos to local device | Yes | Yes |
|
||||
| Multi-user support | Yes | Yes |
|
||||
| Album and Shared albums | Yes | Yes |
|
||||
| Scrubbable/draggable scrollbar | Yes | Yes |
|
||||
| Support RAW (HEIC, HEIF, DNG, Apple ProRaw) | Yes | Yes |
|
||||
| Metadata view (EXIF, map) | Yes | Yes |
|
||||
| Search by metadata, objects, faces, and CLIP | Yes | Yes |
|
||||
| Administrative functions (user management) | No | Yes |
|
||||
| Background backup | Yes | N/A |
|
||||
| Virtual scroll | Yes | Yes |
|
||||
| OAuth support | Yes | Yes |
|
||||
| API Keys | N/A | Yes |
|
||||
| LivePhoto backup and playback | iOS | Yes |
|
||||
| User-defined storage structure | Yes | Yes |
|
||||
| Public Sharing | No | Yes |
|
||||
| Archive and Favorites | Yes | Yes |
|
||||
| Global Map | No | Yes |
|
||||
| Partner Sharing | No | Yes |
|
||||
| Facial recognition and clustering | No | Yes |
|
||||
|
||||
# Support the project
|
||||
|
||||
@@ -96,11 +100,3 @@ If you feel like this is the right cause and the app is something you are seeing
|
||||
- [Librepay](https://liberapay.com/alex.tran1502/)
|
||||
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
|
||||
- Bitcoin: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX
|
||||
|
||||
# Known Issues
|
||||
|
||||
## immich-machine-learning fails to start
|
||||
|
||||
Symptoms: the container logs `illegal instruction core dump` and restarts
|
||||
|
||||
Solution: https://immich.app/docs/install/requirements#hardware
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
32
dev-setup.md
@@ -1,32 +0,0 @@
|
||||
# Development Setup
|
||||
|
||||
## Lint / format extensions
|
||||
|
||||
Setting these in the IDE give a better developer experience auto-formatting code on save and providing instant feedback on lint issues.
|
||||
|
||||
### VSCode
|
||||
Install Prettier, ESLint and Svelte extensions.
|
||||
|
||||
in User `settings.json` (`cmd + shift + p` and search for Open User Settings JSON) add the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"[javascript][typescript][css]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.tabSize": 2,
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[svelte]": {
|
||||
"editor.defaultFormatter": "svelte.svelte-vscode",
|
||||
"editor.tabSize": 2
|
||||
},
|
||||
"svelte.enable-ts-plugin": true,
|
||||
"eslint.validate": ["javascript", "svelte"]
|
||||
}
|
||||
```
|
||||
|
||||
## Running tests / checks
|
||||
|
||||
In both server and web:
|
||||
`npm run check:all`
|
||||
@@ -53,13 +53,15 @@ services:
|
||||
context: ../server
|
||||
dockerfile: Dockerfile
|
||||
target: builder
|
||||
command: npm run start:dev microservices
|
||||
command: npm run start:debug microservices
|
||||
volumes:
|
||||
- ../server:/usr/src/app
|
||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||
- /usr/src/app/node_modules
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- 9231:9230
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
depends_on:
|
||||
@@ -133,8 +135,6 @@ services:
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- 2283:8080
|
||||
logging:
|
||||
driver: none
|
||||
depends_on:
|
||||
- immich-server
|
||||
restart: always
|
||||
|
||||
@@ -4,7 +4,7 @@ services:
|
||||
immich-server:
|
||||
container_name: immich_server
|
||||
image: ghcr.io/immich-app/immich-server:release
|
||||
entrypoint: ["/bin/sh", "./start-server.sh"]
|
||||
command: ["start-server.sh"]
|
||||
volumes:
|
||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||
env_file:
|
||||
@@ -18,7 +18,7 @@ services:
|
||||
immich-microservices:
|
||||
container_name: immich_microservices
|
||||
image: ghcr.io/immich-app/immich-server:release
|
||||
entrypoint: ["/bin/sh", "./start-microservices.sh"]
|
||||
command: ["start-microservices.sh"]
|
||||
volumes:
|
||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||
env_file:
|
||||
@@ -42,7 +42,6 @@ services:
|
||||
immich-web:
|
||||
container_name: immich_web
|
||||
image: ghcr.io/immich-app/immich-web:release
|
||||
entrypoint: ["/bin/sh", "./entrypoint.sh"]
|
||||
env_file:
|
||||
- .env
|
||||
restart: always
|
||||
@@ -87,8 +86,6 @@ services:
|
||||
- IMMICH_WEB_URL
|
||||
ports:
|
||||
- 2283:8080
|
||||
logging:
|
||||
driver: none
|
||||
depends_on:
|
||||
- immich-server
|
||||
restart: always
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
# Database
|
||||
###################################################################################
|
||||
|
||||
# NOTE: The following four database variables support Docker secrets by adding a *_FILE suffix to the variable name
|
||||
# See the docker-compose documentation on secrets for additional details: https://docs.docker.com/compose/compose-file/compose-file-v3/#secrets
|
||||
DB_HOSTNAME=immich_postgres
|
||||
DB_USERNAME=postgres
|
||||
DB_PASSWORD=postgres
|
||||
|
||||
2
docs/.prettierignore
Normal file
@@ -0,0 +1,2 @@
|
||||
build/
|
||||
.docusaurus/
|
||||
6
docs/.prettierrc
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 120,
|
||||
"semi": true
|
||||
}
|
||||
@@ -33,9 +33,8 @@ The motion part will now be uploaded and can be played on the mobile app and the
|
||||
src="https://media.giphy.com/media/fTrGceZd7t1ewi8ESc/giphy.gif"
|
||||
width="100%"
|
||||
style={{
|
||||
borderRadius: "10px",
|
||||
boxShadow:
|
||||
"rgba(9, 30, 66, 0.25) 0px 1px 1px, rgba(9, 30, 66, 0.13) 0px 0px 1px 1px",
|
||||
borderRadius: '10px',
|
||||
boxShadow: 'rgba(9, 30, 66, 0.25) 0px 1px 1px, rgba(9, 30, 66, 0.13) 0px 0px 1px 1px',
|
||||
}}
|
||||
title="LivePhoto playback on the web"
|
||||
/>
|
||||
@@ -73,9 +72,8 @@ The web will have the option to sign in with OAuth.
|
||||
width="50%"
|
||||
title="Web Sign in with OAuth"
|
||||
style={{
|
||||
borderRadius: "10px",
|
||||
boxShadow:
|
||||
"rgba(9, 30, 66, 0.25) 0px 1px 1px, rgba(9, 30, 66, 0.13) 0px 0px 1px 1px",
|
||||
borderRadius: '10px',
|
||||
boxShadow: 'rgba(9, 30, 66, 0.25) 0px 1px 1px, rgba(9, 30, 66, 0.13) 0px 0px 1px 1px',
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -86,9 +84,8 @@ sign-in button.
|
||||
src="https://media.giphy.com/media/3iy3SaNkVYtlkEiw06/giphy.gif"
|
||||
title="Mobile sign in with OAuth"
|
||||
style={{
|
||||
borderRadius: "10px",
|
||||
boxShadow:
|
||||
"rgba(9, 30, 66, 0.25) 0px 1px 1px, rgba(9, 30, 66, 0.13) 0px 0px 1px 1px",
|
||||
borderRadius: '10px',
|
||||
boxShadow: 'rgba(9, 30, 66, 0.25) 0px 1px 1px, rgba(9, 30, 66, 0.13) 0px 0px 1px 1px',
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -98,9 +95,8 @@ sign-in button.
|
||||
src="https://media.giphy.com/media/LStqgGESXW8XnuCv5y/giphy.gif"
|
||||
width="300"
|
||||
style={{
|
||||
borderRadius: "10px",
|
||||
boxShadow:
|
||||
"rgba(9, 30, 66, 0.25) 0px 1px 1px, rgba(9, 30, 66, 0.13) 0px 0px 1px 1px",
|
||||
borderRadius: '10px',
|
||||
boxShadow: 'rgba(9, 30, 66, 0.25) 0px 1px 1px, rgba(9, 30, 66, 0.13) 0px 0px 1px 1px',
|
||||
}}
|
||||
title="Support the project"
|
||||
/>
|
||||
|
||||
@@ -22,11 +22,11 @@ The initial approach of Immich is to become a backup tool, primarily for mobile
|
||||
|
||||
### Why does my uploaded photo show up with the wrong date or time in Immich?
|
||||
|
||||
When a photo is initially uploaded Immich uses the create date of the file to determine where it belongs in the timeline. After that, background jobs will run that extract [exif metadata](https://en.wikipedia.org/wiki/Exif), including the CreateDate, to provide a more accurate date for the photo. If that is not available it will fallback to the modified date. If you want to ensure your photo has the right date, check the exif metadata before uploading.
|
||||
When a photo is initially uploaded Immich uses the create date of the file to determine where it belongs in the timeline. After that, background jobs will run that extract [exif metadata](https://en.wikipedia.org/wiki/Exif), including the CreateDate, to provide a more accurate date for the photo. If that is not available it will fallback to the modified date. If you want to ensure your photo has the right date, check the exif metadata before uploading.
|
||||
|
||||
If the timezone is incorrect in an uploaded photo, check the ``DateTimeOriginal`` exif field of the uploaded file. Immich uses the very competent library [exiftool-vendored.js](https://github.com/photostructure/exiftool-vendored.js#dates) to handle timezone parsing, but in some cases (like photos taken with DSLR cameras) it has to fallback on the local timezone. If you are using docker, this fallback will be UTC. (Note that even the photo backup app that can't be named [has the same bug!](https://photo.stackexchange.com/a/126978)) In Immich, it is possible to change this assumed fallback timezone system-wide by setting the timezone in the microservices docker container. You might need to run the "Extract Metadata" job after to effect the change.
|
||||
If the timezone is incorrect in an uploaded photo, check the `DateTimeOriginal` exif field of the uploaded file. Immich uses the very competent library [exiftool-vendored.js](https://github.com/photostructure/exiftool-vendored.js#dates) to handle timezone parsing, but in some cases (like photos taken with DSLR cameras) it has to fallback on the local timezone. If you are using docker, this fallback will be UTC. (Note that even the photo backup app that can't be named [has the same bug!](https://photo.stackexchange.com/a/126978)) In Immich, it is possible to change this assumed fallback timezone system-wide by setting the timezone in the microservices docker container. You might need to run the "Extract Metadata" job after to effect the change.
|
||||
|
||||
As an example, the following modification of ```docker-compose.yml``` will set the timezone of the microservices container to be ``Europe/Stockholm``
|
||||
As an example, the following modification of `docker-compose.yml` will set the timezone of the microservices container to be `Europe/Stockholm`
|
||||
|
||||
```
|
||||
environment:
|
||||
@@ -34,16 +34,27 @@ As an example, the following modification of ```docker-compose.yml``` will set t
|
||||
```
|
||||
|
||||
### Why are only photos and not videos being uploaded to Immich?
|
||||
|
||||
This often happens when using a reverse proxy or cloudflare tunnel in front of Immich. Make sure to set your reverse proxy to allow large POST requests. In `nginx`, set `client_max_body_size 50000M;` or similar. Cloudflare tunnels are limited to 100 mb file sizes.
|
||||
|
||||
### Why is Immich slow on low-memory systems like the Raspberry Pi?
|
||||
|
||||
Immich uses optional machine-learning features to enhance search results. This feature, however, can be too heavy to run on a Raspberry Pi. To disable machine learning, comment out the `immich-machine-learning` section of your docker-compose.yml and set `IMMICH_MACHINE_LEARNING_URL=false` in your .env file.
|
||||
|
||||
### How to disable machine-learning and TypeSense?
|
||||
|
||||
:::warning
|
||||
Disabling both will result in poor search experience and typesense utilizes CLIP embeddings which are generated by machine-learning.
|
||||
:::
|
||||
|
||||
These features can be disabled by commenting out `immich-typesense` and `immich-machine-learning` sections of the docker-compose.yml and setting `IMMICH_MACHINE_LEARNING_URL=false` & `TYPESENSE_ENABLED=false` in your .env file.
|
||||
|
||||
### What happens to existing files after I choose a new [Storage Template](/docs/administration/storage-template.mdx)?
|
||||
|
||||
Template changes will only apply to new assets. To retroactively apply the template to previously uploaded assets, run the Storage Migration Job, available on the [Jobs](/docs/administration/jobs.md) page.
|
||||
|
||||
### In the uploads folder, why are photos stored in the wrong date?
|
||||
|
||||
This is fixed by running the storage migration job.
|
||||
|
||||
### Why is object detection not very good?
|
||||
@@ -66,6 +77,10 @@ The non-root user/group needs read/write access to the volume mounts, including
|
||||
|
||||
The admin password can be reset by running the [reset-admin-password](/docs/administration/server-commands.md) command on the immich-server.
|
||||
|
||||
### How can I backup data from Immich?
|
||||
|
||||
See [backup and restore](/docs/administration/backup-and-restore.md).
|
||||
|
||||
### How can I **purge** data from Immich?
|
||||
|
||||
Data for Immich comes in two forms:
|
||||
@@ -80,3 +95,7 @@ docker-compose down -v
|
||||
```
|
||||
|
||||
After removing the the containers and volumes, the **Files** can be cleaned up (if necessary) from the `UPLOAD_LOCATION` by simply deleting an unwanted files or folders.
|
||||
|
||||
### Why iOS app shows duplicate photos on the timeline while the web doesn't?
|
||||
|
||||
If you are using `My Photo Stream`, the Photos app temporarily creates duplicates of photos taken in the last 30 days. These photos are included in the `Recents` album and thus shown up twice. To fix this, you can disable `My Photo Stream` in the native Photos app or choose a different album in the backup screen in Immich.
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"label": "Administration",
|
||||
"position": 4
|
||||
}
|
||||
|
||||
"label": "Administration",
|
||||
"position": 4
|
||||
}
|
||||
|
||||
55
docs/docs/administration/backup-and-restore.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Backup and Restore
|
||||
|
||||
## Database
|
||||
|
||||
:::info
|
||||
Refer to the official [postgres documentation](https://www.postgresql.org/docs/current/backup.html) for details about backing up and restoring a postgres database.
|
||||
:::
|
||||
|
||||
The recommended way to backup and restore the Immich database is to use the `pg_dumpall` command.
|
||||
|
||||
```bash title='Backup'
|
||||
docker exec -t immich_postgres pg_dumpall -c -U postgres | gzip > "/path/to/backup/dump.sql.gz"
|
||||
```
|
||||
|
||||
```bash title='Restore'
|
||||
gunzip < /path/to/backup/dump.sql.gz | docker exec -i immich_postgres psql -U postgres -d immich
|
||||
```
|
||||
|
||||
The database dumps can also be automated (using [this image](https://github.com/prodrigestivill/docker-postgres-backup-local)) by editing the docker compose file to match the following:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
...
|
||||
backup:
|
||||
container_name: immich_db_dumper
|
||||
image: prodrigestivill/postgres-backup-local
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
POSTGRES_HOST: database
|
||||
POSTGRES_DB: ${DB_DATABASE_NAME}
|
||||
POSTGRES_USER: ${DB_USERNAME}
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
SCHEDULE: "@daily"
|
||||
BACKUP_NUM_KEEP: 7
|
||||
BACKUP_DIR: /db_dumps
|
||||
volumes:
|
||||
- ./db_dumps:/db_dumps
|
||||
depends_on:
|
||||
- database
|
||||
```
|
||||
|
||||
Then you can restore with the same command but pointed at the latest dump.
|
||||
|
||||
```bash title='Automated Restore'
|
||||
gunzip < db_dumps/last/immich-latest.sql.gz | docker exec -i immich_postgres psql -U postgres -d immich
|
||||
```
|
||||
|
||||
## Filesystem
|
||||
|
||||
Immich stores two types of content in the filesystem: (1) original, unmodified content, and (2) generated content. Only the original content needs to be backed-up, which includes the following folders:
|
||||
|
||||
1. `UPLOAD_LOCATION/library`
|
||||
1. `UPLOAD_LOCATION/upload`
|
||||
1. `UPLOAD_LOCATION/profile`
|
||||
@@ -36,7 +36,6 @@ Immich is a full-stack [TypeScript](https://www.typescriptlang.org/) application
|
||||
- [Redis](https://redis.io/) for job queuing.
|
||||
- [Typesense](https://typesense.org/) for search.
|
||||
|
||||
|
||||
### Web Server
|
||||
|
||||
- [NGINX](https://www.nginx.com/) for internal communication between containers and load balancing when scaling.
|
||||
|
||||
14
docs/docs/developer/database-migrations.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Database Migrations
|
||||
|
||||
After making any changes in the `server/libs/database/src/entities`, a database migration need to run in order to register the changes in the database. Follow the steps below to create a new migration.
|
||||
|
||||
1. Run the command
|
||||
|
||||
```bash
|
||||
npm run typeorm:migrations:generate ./libs/infra/src/<migration-name>
|
||||
```
|
||||
|
||||
2. Check if the migration file makes sense.
|
||||
3. Move the migration file to folder `server/libs/database/src/migrations` in your code editor.
|
||||
|
||||
The server will automatically detect `*.ts` file changes and restart. Part of the server start-up process includes running any new migrations, so it will be applied immediately.
|
||||
@@ -1,7 +1,17 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# Open API
|
||||
|
||||
Immich uses the [Open API](https://swagger.io/specification/) standard to generate API documentation. To view the published docs see [here](/docs/api).
|
||||
|
||||
## Generator
|
||||
|
||||
OpenAPI is used to generate the client (Typescript, Dart) SDK. `openapi-generator-cli` can be installed [here](https://openapi-generator.tech/docs/installation/). When you add a new or modify an existing endpoint, you must run the command below to update the client SDK.
|
||||
|
||||
```bash
|
||||
npm run api:generate # Run from the `server/` directory
|
||||
```
|
||||
|
||||
You can find the generated client SDK in the `web/src/api` for Typescript SDK and `mobile/openapi` for Dart SDK.
|
||||
|
||||
:::tip
|
||||
This can also be run via `make api` from the project root directory (not in the `server` folder)
|
||||
:::
|
||||
|
||||
@@ -1,16 +1,8 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Contributing
|
||||
|
||||
Contributions are welcome!
|
||||
|
||||
## PR Checklist
|
||||
# PR Checklist
|
||||
|
||||
When contributing code through a pull request, please check the following:
|
||||
|
||||
### Web Checks
|
||||
## Web Checks
|
||||
|
||||
- [ ] `npm run lint` (linting via ESLint)
|
||||
- [ ] `npm run format` (formatting via Prettier)
|
||||
@@ -21,7 +13,7 @@ When contributing code through a pull request, please check the following:
|
||||
Run all web checks with `npm run check:all`
|
||||
:::
|
||||
|
||||
### Server Checks
|
||||
## Server Checks
|
||||
|
||||
- [ ] `npm run lint` (linting via ESLint)
|
||||
- [ ] `npm run format` (formatting via Prettier)
|
||||
@@ -32,12 +24,10 @@ Run all web checks with `npm run check:all`
|
||||
Run all server checks with `npm run check:all`
|
||||
:::
|
||||
|
||||
### Open API
|
||||
## Open API
|
||||
|
||||
The Open API client libraries need to be regenerated whenever there are changes to the `immich-openapi-specs.json` file.
|
||||
The Open API client libraries need to be regenerated whenever there are changes to the `immich-openapi-specs.json` file. See [Open API](/docs/developer/open-api.md) for more details.
|
||||
|
||||
- [ ] `npm run api:generate`
|
||||
## Database Migrations
|
||||
|
||||
:::tip
|
||||
This can also be run via `make api` from the project root directory (not in the `server` folder)
|
||||
:::
|
||||
A database migration needs to be generated whenever there are changes to `server/libs/infra/src/entities`. See [Database Migration](/docs/developer/database-migrations.md) for more details.
|
||||
@@ -92,27 +92,3 @@ in User `settings.json` (`cmd + shift + p` and search for `Open User Settings JS
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## OpenAPI generator
|
||||
|
||||
OpenAPI is used to generate the client (Typescript, Dart) SDK. `openapi-generator-cli` can be installed [here](https://openapi-generator.tech/docs/installation/). When you add a new or modify an existing endpoint, you must run the command below to update the client SDK.
|
||||
|
||||
```bash
|
||||
npm run api:generate # Run from the `server` directory
|
||||
```
|
||||
|
||||
You can find the generated client SDK in the `web/src/api` for Typescript SDK and `mobile/openapi` for Dart SDK.
|
||||
|
||||
## Database migrations
|
||||
|
||||
After making any changes in the `server/libs/database/src/entities`, a database migration need to run in order to register the changes in the database. Follow the steps below to create a new migration.
|
||||
|
||||
1. Attached to the server container shell.
|
||||
2. Run
|
||||
|
||||
```bash
|
||||
npm run typeorm -- migration:generate ./libs/infra/src/db/<migration-name> -d ./libs/infra/src/db/config/database.config.ts
|
||||
```
|
||||
|
||||
3. Check if the migration file makes sense.
|
||||
4. Move the migration file to folder `server/libs/database/src/migrations` in your code editor.
|
||||
|
||||
@@ -14,17 +14,19 @@ Background backup is available thanks to the contribution effort of [@zoodyy](ht
|
||||
|
||||
If background backup is enabled. The app will periodically check if there are any new photos or videos in the selected album(s) to be uploaded to the cloud. If there are, it will upload them to the cloud in the background.
|
||||
|
||||
|
||||
:::info Note
|
||||
|
||||
#### General
|
||||
|
||||
- The app must be in the background for the backup worker to start running.
|
||||
- If you reopen the app and the first page you see is the backup page, the counts will not reflect the background uploaded result. You have to navigate out of the page and come back to see the updated counts.
|
||||
|
||||
#### Android
|
||||
|
||||
- It is a well-known problem that some Android models are very strict with battery optimization settings, which can cause a problem with the background worker. Please visit [Don't kill my app](https://dontkillmyapp.com/) for a guide on disabling this setting on your phone.
|
||||
|
||||
#### iOS
|
||||
|
||||
- You must enable **Background App Refresh** for the app to work in the background. You can enable it in the Settings app under General > Background App Refresh.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||
@@ -49,7 +49,6 @@ The API key can be obtained in the user setting panel on the web interface.
|
||||
|
||||

|
||||
|
||||
|
||||
### Run via Docker
|
||||
|
||||
You can run the CLI inside of a docker container to avoid needing to install anything.
|
||||
@@ -74,6 +73,7 @@ immich upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api
|
||||
|
||||
:::tip Internal networking
|
||||
If you are running the CLI container on the same machine as your Immich server, you may not be able to reach the external address. In that case, try the following steps:
|
||||
|
||||
1. Find the internal Docker network used by Immich via `docker network ls`.
|
||||
2. Adapt the above command to pass the `--network <immich_network>` argument to `docker run`, substituting `<immich_network>` with the result from step 1.
|
||||
3. Use `--server http://immich-server:3001/` for the upload command instead of the external address.
|
||||
@@ -81,6 +81,7 @@ If you are running the CLI container on the same machine as your Immich server,
|
||||
```bash title="Upload to internal address"
|
||||
docker run --network immich_default -it --rm -v "$(pwd):/import" ghcr.io/immich-app/immich-cli:latest upload --key HFEJ38DNSDUEG --server http://immich-server:3001/
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Run from source
|
||||
|
||||
15
docs/docs/features/facial-recognition.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Facial Recognition
|
||||
|
||||
Immich recognizes faces in your photos and videos and groups them together. You can then assign names to the faces and search for them.
|
||||
|
||||
The list of people is shown in the Explore page.
|
||||
|
||||
<img src={require('./img/facial-recognition-1.png').default} title='Facial Recognition 1' />
|
||||
|
||||
Upon clicking on a person, a list of assets that contain their face will be shown.
|
||||
|
||||
<img src={require('./img/facial-recognition-2.png').default} title='Facial Recognition 2' />
|
||||
|
||||
The asset detail view will also show the faces that are recognized in the asset.
|
||||
|
||||
<img src={require('./img/facial-recognition-3.png').default} title='Facial Recognition 3' />
|
||||
BIN
docs/docs/features/img/facial-recognition-1.png
Normal file
|
After Width: | Height: | Size: 524 KiB |
BIN
docs/docs/features/img/facial-recognition-2.png
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
docs/docs/features/img/facial-recognition-3.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
docs/docs/features/img/partner-sharing-1.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
docs/docs/features/img/partner-sharing-2.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
docs/docs/features/img/partner-sharing-3.png
Normal file
|
After Width: | Height: | Size: 183 KiB |
BIN
docs/docs/features/img/sidecar-jobs.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
docs/docs/features/img/xmp-sidecars.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
17
docs/docs/features/partner-sharing.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Partner Sharing
|
||||
|
||||
Immich allows you to share your library with other users. They can then view your library and download the assets.
|
||||
|
||||
You can manage one or multiple users to have access to your library from the [User Settings](docs/features/user-settings.md) page.
|
||||
|
||||
<img src={require('./img/partner-sharing-1.png').default} title='Partner Sharing 1' />
|
||||
|
||||
<img src={require('./img/partner-sharing-2.png').default} title='Partner Sharing 2' />
|
||||
|
||||
Accessing the shared library can be done from the Sharing page.
|
||||
|
||||
<img src={require('./img/partner-sharing-3.png').default} title='Partner Sharing 3' />
|
||||
|
||||
:::tip Sharing specific assets
|
||||
For sharing a specific set of assets, you can use the shared album feature of Immich.
|
||||
:::
|
||||
@@ -4,7 +4,7 @@ Immich supports [Reverse Geocoding](https://en.wikipedia.org/wiki/Reverse_geocod
|
||||
|
||||
## Extraction
|
||||
|
||||
During Exif Extraction, assets with latitudes and longitudes are reverse geocoded to determine their City, State, and Country.
|
||||
During Exif Extraction, assets with latitudes and longitudes are reverse geocoded to determine their City, State, and Country.
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Search
|
||||
|
||||
Immich uses Typesense as the primary search database to enable high performance search mechanism.
|
||||
Immich uses Typesense as the primary search database to enable high performance search mechanism.
|
||||
|
||||
Typesense is a powerful search engine that can be integrated with popular natural language processing (NLP) models like CLIP and SBERT to provide highly accurate and relevant search results. Here are some benefits of using Typesense integrated search for CLIP and SBERT:
|
||||
|
||||
@@ -10,11 +10,11 @@ Faster Search Response Times: Typesense is optimized for lightning-fast search r
|
||||
|
||||
Enhanced Semantic Search Capabilities: CLIP and SBERT are powerful NLP models that can extract the semantic meaning from text, enabling more nuanced search queries. By integrating with Typesense, these models can help to improve the accuracy of semantic search, enabling users to find the most relevant results based on the true meaning of their query.
|
||||
|
||||
Greater Search Flexibility: Typesense provides flexible search capabilities, including fuzzy search, partial search, enabling users to find the information they need quickly and easily. When integrated with CLIP and SBERT, Typesense can offer even greater flexibility, allowing users to refine their search queries using natural language and providing more accurate and relevant results.
|
||||
Greater Search Flexibility: Typesense provides flexible search capabilities, including fuzzy search, partial search, enabling users to find the information they need quickly and easily. When integrated with CLIP and SBERT, Typesense can offer even greater flexibility, allowing users to refine their search queries using natural language and providing more accurate and relevant results.
|
||||
|
||||
(Generated by Chat-GPT4)
|
||||
|
||||
Some search examples:
|
||||
<img src={require('./img/search-ex-2.webp').default} title='Search Example 1' />
|
||||
Some search examples:
|
||||
<img src={require('./img/search-ex-2.webp').default} title='Search Example 1' />
|
||||
|
||||
<img src={require('./img/search-ex-3.webp').default} title='Search Example 2' />
|
||||
|
||||
@@ -10,7 +10,7 @@ View your User ID and email, and update your first and last name.
|
||||
|
||||
## Change Password
|
||||
|
||||
Users can change their own passwords.
|
||||
Users can change their own passwords.
|
||||
|
||||

|
||||
|
||||
|
||||
13
docs/docs/features/xmp-sidecars.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# XMP Sidecars
|
||||
|
||||
Immich can ingest XMP sidecars on file upload (via the CLI) as well as detect new sidecars that are placed in the filesystem for existing images.
|
||||
|
||||
<img src={require('./img/xmp-sidecars.png').default} title='XMP sidecars' />
|
||||
|
||||
XMP sidecars are external XML files that contain metadata related to media files. Many applications read and write these files either exclusively or in addition to the metadata written to image files. They can be a powerful tool for editing and storing metadata of a media file without modifying the mdia file itself. When Immich receives or detects an XMP sidecar for a media file, it will attempt to extract the metadata from both the sidecar as well as the media file. It will prioritize the metadata for fields in the sidecar but will fall back and use the metadata in the media file if necessary.
|
||||
|
||||
When importing files via the CLI bulk uploader, Immich will automatically detect XMP sidecar files as files that exist next to the original media file and have the exact same name with an additional `.xmp` file extension (i.e., `PXL_20230401_203352928.MP.jpg` and `PXL_20230401_203352928.MP.jpg.xmp`).
|
||||
|
||||
There are 2 administrator jobs associated with sidecar files: `SYNC` and `DISCOVER`. The sync job will re-scan all media with existing sidecar files and queue them for a metadata refresh. This is a great use case when third-party applications are used to modify the metadata of media. The discover job will attempt to scan the filesystem for new sidecar files for all media that does not currently have a sidecar file associated with it.
|
||||
|
||||
<img src={require('./img/sidecar-jobs.png').default} title='Sidecar Administrator Jobs' />
|
||||
@@ -13,37 +13,36 @@ Install Immich using Portainer's Stack feature.
|
||||
5. Replace `.env` with `stack.env` for all containers that need to use environment variables in the web editor.
|
||||
|
||||
<img
|
||||
src={require('./img/dot-env.png').default}
|
||||
width="50%"
|
||||
style={{border: '1px solid #ddd'}}
|
||||
alt="Dot Env Example"
|
||||
src={require('./img/dot-env.png').default}
|
||||
width="50%"
|
||||
style={{border: '1px solid #ddd'}}
|
||||
alt="Dot Env Example"
|
||||
/>
|
||||
|
||||
8. Click on "**Advanced Mode**" in the **Environment Variables** section.
|
||||
|
||||
<img
|
||||
src={require('./img/env-1.png').default}
|
||||
width="50%"
|
||||
style={{border: '1px solid #ddd'}}
|
||||
alt="Dot Env Example"
|
||||
src={require('./img/env-1.png').default}
|
||||
width="50%"
|
||||
style={{border: '1px solid #ddd'}}
|
||||
alt="Dot Env Example"
|
||||
/>
|
||||
|
||||
9. Copy the content of the `example.env` file from the [GitHub repository](https://github.com/immich-app/immich/releases/latest/download/example.env) and paste into the editor.
|
||||
10. Switch back to "**Simple Mode**".
|
||||
|
||||
<img
|
||||
src={require('./img/env-2.png').default}
|
||||
width="50%"
|
||||
style={{border: '1px solid #ddd'}}
|
||||
alt="Dot Env Example"
|
||||
src={require('./img/env-2.png').default}
|
||||
width="50%"
|
||||
style={{border: '1px solid #ddd'}}
|
||||
alt="Dot Env Example"
|
||||
/>
|
||||
|
||||
* Populate custom database information if necessary.
|
||||
* Populate `UPLOAD_LOCATION` with your preferred location for storing backup assets.
|
||||
- Populate custom database information if necessary.
|
||||
- Populate `UPLOAD_LOCATION` with your preferred location for storing backup assets.
|
||||
|
||||
11. Click on "**Deploy the stack**".
|
||||
|
||||
|
||||
:::tip
|
||||
For more information on how to use the application, please refer to the [Post Installation](/docs/install/post-install.mdx) guide.
|
||||
:::
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
sidebar_position: 10
|
||||
---
|
||||
|
||||
|
||||
# Requirements
|
||||
|
||||
Hardware and software requirements for Immich
|
||||
|
||||
## Software
|
||||
@@ -18,5 +18,5 @@ You can also use Podman to run the application. However, additional configuratio
|
||||
## Hardware
|
||||
|
||||
- **OS**: Preferred unix-based operating system (Ubuntu, Debian, MacOS, etc). Windows works too, with [Docker Desktop on Windows](https://docs.docker.com/desktop/install/windows-install/)
|
||||
- **RAM**: At least 2GB, preferred 4GB.
|
||||
- **RAM**: At least 4GB, preferred 6GB.
|
||||
- **CPU**: At least 2 cores, preferred 4 cores.
|
||||
|
||||
@@ -5,6 +5,7 @@ sidebar_position: 60
|
||||
# Unraid
|
||||
|
||||
Immich can easily be installed and updated on Unraid via:
|
||||
|
||||
1. [Docker Compose Manager](https://forums.unraid.net/topic/114415-plugin-docker-compose-manager/) plugin from the Unraid Community Apps
|
||||
2. Community made template on the Unraid Community Apps
|
||||
|
||||
@@ -20,7 +21,7 @@ In order to install Immich from the Unraid CA, you will need an existing Redis a
|
||||
|
||||
Once you have Redis and PostgreSQL running, search for Immich on the Unraid CA, choose either of the templates listed and fill out the example variables.
|
||||
|
||||
For more information about setting up the community image see [here](https://github.com/imagegenius/docker-immich#application-setup)
|
||||
For more information about setting up the community image see [here](https://github.com/imagegenius/docker-immich#application-setup)
|
||||
|
||||
## Docker-Compose Method (Official)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ sidebar_position: 1
|
||||
|
||||
# Introduction
|
||||
|
||||
<img src={require('./img/feature-panel.png').default} alt='Immich' />
|
||||
<img src={require('./img/feature-panel.png').default} alt="Immich" />
|
||||
|
||||
## Welcome!
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ If you feel like this is the right cause and the app is something you see yourse
|
||||
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
|
||||
- Bitcoin: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
There are lots of non-monetary ways to contribute to Immich as well.
|
||||
|
||||
@@ -7,8 +7,7 @@ const darkCodeTheme = require('prism-react-renderer/themes/dracula');
|
||||
/** @type {import('@docusaurus/types').Config} */
|
||||
const config = {
|
||||
title: 'Immich',
|
||||
tagline:
|
||||
'High performance self-hosted photo and video backup solution directly from your mobile phone',
|
||||
tagline: 'High performance self-hosted photo and video backup solution directly from your mobile phone',
|
||||
url: 'https://documentation.immich.app',
|
||||
baseUrl: '/',
|
||||
onBrokenLinks: 'throw',
|
||||
@@ -111,11 +110,6 @@ const config = {
|
||||
label: 'GitHub',
|
||||
position: 'right',
|
||||
},
|
||||
{
|
||||
href: 'https://github.com/orgs/immich-app/projects/1',
|
||||
label: 'Roadmap',
|
||||
position: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
footer: {
|
||||
@@ -154,10 +148,6 @@ const config = {
|
||||
label: 'GitHub',
|
||||
href: 'https://github.com/immich-app/immich',
|
||||
},
|
||||
{
|
||||
label: 'Roadmap',
|
||||
href: 'https://github.com/orgs/immich-app/projects/1',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
22
docs/package-lock.json
generated
@@ -25,6 +25,7 @@
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "2.1.0",
|
||||
"@tsconfig/docusaurus": "^1.0.5",
|
||||
"prettier": "^2.8.8",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -10538,6 +10539,21 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "2.8.8",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
|
||||
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-error": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz",
|
||||
@@ -22086,6 +22102,12 @@
|
||||
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
|
||||
"integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA=="
|
||||
},
|
||||
"prettier": {
|
||||
"version": "2.8.8",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
|
||||
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
|
||||
"dev": true
|
||||
},
|
||||
"pretty-error": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz",
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
"format": "prettier --check .",
|
||||
"format:fix": "prettier --write .",
|
||||
"start": "docusaurus start",
|
||||
"build": "docusaurus build",
|
||||
"swizzle": "docusaurus swizzle",
|
||||
@@ -12,7 +14,7 @@
|
||||
"serve": "docusaurus serve",
|
||||
"write-translations": "docusaurus write-translations",
|
||||
"write-heading-ids": "docusaurus write-heading-ids",
|
||||
"typecheck": "tsc"
|
||||
"check": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "2.1.0",
|
||||
@@ -32,6 +34,7 @@
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "2.1.0",
|
||||
"@tsconfig/docusaurus": "^1.0.5",
|
||||
"prettier": "^2.8.8",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"browserslist": {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
|
||||
const sidebars = {
|
||||
// By default, Docusaurus generates a sidebar from the docs folder structure
|
||||
tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
|
||||
tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }],
|
||||
|
||||
// But you can create a sidebar manually
|
||||
/*
|
||||
|
||||
@@ -14,8 +14,8 @@ const FeatureList: FeatureItem[] = [
|
||||
Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default,
|
||||
description: (
|
||||
<>
|
||||
Docusaurus was designed from the ground up to be easily installed and
|
||||
used to get your website up and running quickly.
|
||||
Docusaurus was designed from the ground up to be easily installed and used to get your website up and running
|
||||
quickly.
|
||||
</>
|
||||
),
|
||||
},
|
||||
@@ -24,8 +24,8 @@ const FeatureList: FeatureItem[] = [
|
||||
Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default,
|
||||
description: (
|
||||
<>
|
||||
Docusaurus lets you focus on your docs, and we'll do the chores. Go
|
||||
ahead and move your docs into the <code>docs</code> directory.
|
||||
Docusaurus lets you focus on your docs, and we'll do the chores. Go ahead and move your docs into the{' '}
|
||||
<code>docs</code> directory.
|
||||
</>
|
||||
),
|
||||
},
|
||||
@@ -34,14 +34,14 @@ const FeatureList: FeatureItem[] = [
|
||||
Svg: require('@site/static/img/undraw_docusaurus_react.svg').default,
|
||||
description: (
|
||||
<>
|
||||
Extend or customize your website layout by reusing React. Docusaurus can
|
||||
be extended while reusing the same header and footer.
|
||||
Extend or customize your website layout by reusing React. Docusaurus can be extended while reusing the same
|
||||
header and footer.
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
function Feature({title, Svg, description}: FeatureItem) {
|
||||
function Feature({ title, Svg, description }: FeatureItem) {
|
||||
return (
|
||||
<div className={clsx('col col--4')}>
|
||||
<div className="text--center">
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@import url("https://fonts.googleapis.com/css2?family=Overpass:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Snowburst+One&display=swap");
|
||||
@import url('https://fonts.googleapis.com/css2?family=Overpass:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Snowburst+One&display=swap');
|
||||
|
||||
html,
|
||||
button {
|
||||
font-family: "Overpass", sans-serif;
|
||||
font-family: 'Overpass', sans-serif;
|
||||
}
|
||||
|
||||
/* You can override the default Infima variables here. */
|
||||
@@ -29,7 +29,7 @@ button {
|
||||
}
|
||||
|
||||
/* For readability concerns, you should choose a lighter palette in dark mode. */
|
||||
[data-theme="dark"] {
|
||||
[data-theme='dark'] {
|
||||
--ifm-color-primary: #adcbfa;
|
||||
--ifm-color-primary-dark: #85b2f8;
|
||||
--ifm-color-primary-darker: #71a5f6;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import Link from "@docusaurus/Link";
|
||||
import Layout from "@theme/Layout";
|
||||
import React from 'react';
|
||||
import Link from '@docusaurus/Link';
|
||||
import Layout from '@theme/Layout';
|
||||
|
||||
function HomepageHeader() {
|
||||
return (
|
||||
@@ -31,7 +31,7 @@ function HomepageHeader() {
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<img src="/img/immich-screenshots.webp" alt="logo" />
|
||||
<img src="/img/immich-screenshots.png" alt="logo" />
|
||||
</section>
|
||||
</header>
|
||||
);
|
||||
|
||||
@@ -1,297 +1,256 @@
|
||||
import Hogan from "hogan.js";
|
||||
import LunrSearchAdapter from "./lunar-search";
|
||||
import autocomplete from "autocomplete.js";
|
||||
import templates from "./templates";
|
||||
import utils from "./utils";
|
||||
import $ from "autocomplete.js/zepto";
|
||||
import Hogan from 'hogan.js';
|
||||
import LunrSearchAdapter from './lunar-search';
|
||||
import autocomplete from 'autocomplete.js';
|
||||
import templates from './templates';
|
||||
import utils from './utils';
|
||||
import $ from 'autocomplete.js/zepto';
|
||||
|
||||
class DocSearch {
|
||||
constructor({
|
||||
searchDocs,
|
||||
searchIndex,
|
||||
inputSelector,
|
||||
debug = false,
|
||||
baseUrl = '/',
|
||||
queryDataCallback = null,
|
||||
autocompleteOptions = {
|
||||
debug: false,
|
||||
hint: false,
|
||||
autoselect: true
|
||||
constructor({
|
||||
searchDocs,
|
||||
searchIndex,
|
||||
inputSelector,
|
||||
debug = false,
|
||||
baseUrl = '/',
|
||||
queryDataCallback = null,
|
||||
autocompleteOptions = {
|
||||
debug: false,
|
||||
hint: false,
|
||||
autoselect: true,
|
||||
},
|
||||
transformData = false,
|
||||
queryHook = false,
|
||||
handleSelected = false,
|
||||
enhancedSearchInput = false,
|
||||
layout = 'collumns',
|
||||
}) {
|
||||
this.input = DocSearch.getInputFromSelector(inputSelector);
|
||||
this.queryDataCallback = queryDataCallback || null;
|
||||
const autocompleteOptionsDebug =
|
||||
autocompleteOptions && autocompleteOptions.debug ? autocompleteOptions.debug : false;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
autocompleteOptions.debug = debug || autocompleteOptionsDebug;
|
||||
this.autocompleteOptions = autocompleteOptions;
|
||||
this.autocompleteOptions.cssClasses = this.autocompleteOptions.cssClasses || {};
|
||||
this.autocompleteOptions.cssClasses.prefix = this.autocompleteOptions.cssClasses.prefix || 'ds';
|
||||
const inputAriaLabel = this.input && typeof this.input.attr === 'function' && this.input.attr('aria-label');
|
||||
this.autocompleteOptions.ariaLabel = this.autocompleteOptions.ariaLabel || inputAriaLabel || 'search input';
|
||||
|
||||
this.isSimpleLayout = layout === 'simple';
|
||||
|
||||
this.client = new LunrSearchAdapter(searchDocs, searchIndex, baseUrl);
|
||||
|
||||
if (enhancedSearchInput) {
|
||||
this.input = DocSearch.injectSearchBox(this.input);
|
||||
}
|
||||
this.autocomplete = autocomplete(this.input, autocompleteOptions, [
|
||||
{
|
||||
source: this.getAutocompleteSource(transformData, queryHook),
|
||||
templates: {
|
||||
suggestion: DocSearch.getSuggestionTemplate(this.isSimpleLayout),
|
||||
footer: templates.footer,
|
||||
empty: DocSearch.getEmptyTemplate(),
|
||||
},
|
||||
transformData = false,
|
||||
queryHook = false,
|
||||
handleSelected = false,
|
||||
enhancedSearchInput = false,
|
||||
layout = "collumns"
|
||||
}) {
|
||||
this.input = DocSearch.getInputFromSelector(inputSelector);
|
||||
this.queryDataCallback = queryDataCallback || null;
|
||||
const autocompleteOptionsDebug =
|
||||
autocompleteOptions && autocompleteOptions.debug
|
||||
? autocompleteOptions.debug
|
||||
: false;
|
||||
},
|
||||
]);
|
||||
|
||||
const customHandleSelected = handleSelected;
|
||||
this.handleSelected = customHandleSelected || this.handleSelected;
|
||||
|
||||
// We prevent default link clicking if a custom handleSelected is defined
|
||||
if (customHandleSelected) {
|
||||
$('.algolia-autocomplete').on('click', '.ds-suggestions a', (event) => {
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
this.autocomplete.on('autocomplete:selected', this.handleSelected.bind(null, this.autocomplete.autocomplete));
|
||||
|
||||
this.autocomplete.on('autocomplete:shown', this.handleShown.bind(null, this.input));
|
||||
|
||||
if (enhancedSearchInput) {
|
||||
DocSearch.bindSearchBoxEvent();
|
||||
}
|
||||
}
|
||||
|
||||
static injectSearchBox(input) {
|
||||
input.before(templates.searchBox);
|
||||
const newInput = input.prev().prev().find('input');
|
||||
input.remove();
|
||||
return newInput;
|
||||
}
|
||||
|
||||
static bindSearchBoxEvent() {
|
||||
$('.searchbox [type="reset"]').on('click', function () {
|
||||
$('input#docsearch').focus();
|
||||
$(this).addClass('hide');
|
||||
autocomplete.autocomplete.setVal('');
|
||||
});
|
||||
|
||||
$('input#docsearch').on('keyup', () => {
|
||||
const searchbox = document.querySelector('input#docsearch');
|
||||
const reset = document.querySelector('.searchbox [type="reset"]');
|
||||
reset.className = 'searchbox__reset';
|
||||
if (searchbox.value.length === 0) {
|
||||
reset.className += ' hide';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the matching input from a CSS selector, null if none matches
|
||||
* @function getInputFromSelector
|
||||
* @param {string} selector CSS selector that matches the search
|
||||
* input of the page
|
||||
* @returns {void}
|
||||
*/
|
||||
static getInputFromSelector(selector) {
|
||||
const input = $(selector).filter('input');
|
||||
return input.length ? $(input[0]) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the `source` method to be passed to autocomplete.js. It will query
|
||||
* the Algolia index and call the callbacks with the formatted hits.
|
||||
* @function getAutocompleteSource
|
||||
* @param {function} transformData An optional function to transform the hits
|
||||
* @param {function} queryHook An optional function to transform the query
|
||||
* @returns {function} Method to be passed as the `source` option of
|
||||
* autocomplete
|
||||
*/
|
||||
getAutocompleteSource(transformData, queryHook) {
|
||||
return (query, callback) => {
|
||||
if (queryHook) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
autocompleteOptions.debug = debug || autocompleteOptionsDebug;
|
||||
this.autocompleteOptions = autocompleteOptions;
|
||||
this.autocompleteOptions.cssClasses =
|
||||
this.autocompleteOptions.cssClasses || {};
|
||||
this.autocompleteOptions.cssClasses.prefix =
|
||||
this.autocompleteOptions.cssClasses.prefix || "ds";
|
||||
const inputAriaLabel =
|
||||
this.input &&
|
||||
typeof this.input.attr === "function" &&
|
||||
this.input.attr("aria-label");
|
||||
this.autocompleteOptions.ariaLabel =
|
||||
this.autocompleteOptions.ariaLabel || inputAriaLabel || "search input";
|
||||
|
||||
this.isSimpleLayout = layout === "simple";
|
||||
|
||||
this.client = new LunrSearchAdapter(searchDocs, searchIndex, baseUrl);
|
||||
|
||||
if (enhancedSearchInput) {
|
||||
this.input = DocSearch.injectSearchBox(this.input);
|
||||
query = queryHook(query) || query;
|
||||
}
|
||||
this.client.search(query).then((hits) => {
|
||||
if (this.queryDataCallback && typeof this.queryDataCallback == 'function') {
|
||||
this.queryDataCallback(hits);
|
||||
}
|
||||
this.autocomplete = autocomplete(this.input, autocompleteOptions, [
|
||||
{
|
||||
source: this.getAutocompleteSource(transformData, queryHook),
|
||||
templates: {
|
||||
suggestion: DocSearch.getSuggestionTemplate(this.isSimpleLayout),
|
||||
footer: templates.footer,
|
||||
empty: DocSearch.getEmptyTemplate()
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
const customHandleSelected = handleSelected;
|
||||
this.handleSelected = customHandleSelected || this.handleSelected;
|
||||
|
||||
// We prevent default link clicking if a custom handleSelected is defined
|
||||
if (customHandleSelected) {
|
||||
$(".algolia-autocomplete").on("click", ".ds-suggestions a", event => {
|
||||
event.preventDefault();
|
||||
});
|
||||
if (transformData) {
|
||||
hits = transformData(hits) || hits;
|
||||
}
|
||||
callback(DocSearch.formatHits(hits));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
this.autocomplete.on(
|
||||
"autocomplete:selected",
|
||||
this.handleSelected.bind(null, this.autocomplete.autocomplete)
|
||||
);
|
||||
// Given a list of hits returned by the API, will reformat them to be used in
|
||||
// a Hogan template
|
||||
static formatHits(receivedHits) {
|
||||
const clonedHits = utils.deepClone(receivedHits);
|
||||
const hits = clonedHits.map((hit) => {
|
||||
if (hit._highlightResult) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
hit._highlightResult = utils.mergeKeyWithParent(hit._highlightResult, 'hierarchy');
|
||||
}
|
||||
return utils.mergeKeyWithParent(hit, 'hierarchy');
|
||||
});
|
||||
|
||||
this.autocomplete.on(
|
||||
"autocomplete:shown",
|
||||
this.handleShown.bind(null, this.input)
|
||||
);
|
||||
// Group hits by category / subcategory
|
||||
let groupedHits = utils.groupBy(hits, 'lvl0');
|
||||
$.each(groupedHits, (level, collection) => {
|
||||
const groupedHitsByLvl1 = utils.groupBy(collection, 'lvl1');
|
||||
const flattenedHits = utils.flattenAndFlagFirst(groupedHitsByLvl1, 'isSubCategoryHeader');
|
||||
groupedHits[level] = flattenedHits;
|
||||
});
|
||||
groupedHits = utils.flattenAndFlagFirst(groupedHits, 'isCategoryHeader');
|
||||
|
||||
if (enhancedSearchInput) {
|
||||
DocSearch.bindSearchBoxEvent();
|
||||
}
|
||||
// Translate hits into smaller objects to be send to the template
|
||||
return groupedHits.map((hit) => {
|
||||
const url = DocSearch.formatURL(hit);
|
||||
const category = utils.getHighlightedValue(hit, 'lvl0');
|
||||
const subcategory = utils.getHighlightedValue(hit, 'lvl1') || category;
|
||||
const displayTitle = utils
|
||||
.compact([
|
||||
utils.getHighlightedValue(hit, 'lvl2') || subcategory,
|
||||
utils.getHighlightedValue(hit, 'lvl3'),
|
||||
utils.getHighlightedValue(hit, 'lvl4'),
|
||||
utils.getHighlightedValue(hit, 'lvl5'),
|
||||
utils.getHighlightedValue(hit, 'lvl6'),
|
||||
])
|
||||
.join('<span class="aa-suggestion-title-separator" aria-hidden="true"> › </span>');
|
||||
const text = utils.getSnippetedValue(hit, 'content');
|
||||
const isTextOrSubcategoryNonEmpty = (subcategory && subcategory !== '') || (displayTitle && displayTitle !== '');
|
||||
const isLvl1EmptyOrDuplicate = !subcategory || subcategory === '' || subcategory === category;
|
||||
const isLvl2 = displayTitle && displayTitle !== '' && displayTitle !== subcategory;
|
||||
const isLvl1 = !isLvl2 && subcategory && subcategory !== '' && subcategory !== category;
|
||||
const isLvl0 = !isLvl1 && !isLvl2;
|
||||
|
||||
return {
|
||||
isLvl0,
|
||||
isLvl1,
|
||||
isLvl2,
|
||||
isLvl1EmptyOrDuplicate,
|
||||
isCategoryHeader: hit.isCategoryHeader,
|
||||
isSubCategoryHeader: hit.isSubCategoryHeader,
|
||||
isTextOrSubcategoryNonEmpty,
|
||||
category,
|
||||
subcategory,
|
||||
title: displayTitle,
|
||||
text,
|
||||
url,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
static formatURL(hit) {
|
||||
const { url, anchor } = hit;
|
||||
if (url) {
|
||||
const containsAnchor = url.indexOf('#') !== -1;
|
||||
if (containsAnchor) return url;
|
||||
else if (anchor) return `${hit.url}#${hit.anchor}`;
|
||||
return url;
|
||||
} else if (anchor) return `#${hit.anchor}`;
|
||||
/* eslint-disable */
|
||||
console.warn('no anchor nor url for : ', JSON.stringify(hit));
|
||||
/* eslint-enable */
|
||||
return null;
|
||||
}
|
||||
|
||||
static getEmptyTemplate() {
|
||||
return (args) => Hogan.compile(templates.empty).render(args);
|
||||
}
|
||||
|
||||
static getSuggestionTemplate(isSimpleLayout) {
|
||||
const stringTemplate = isSimpleLayout ? templates.suggestionSimple : templates.suggestion;
|
||||
const template = Hogan.compile(stringTemplate);
|
||||
return (suggestion) => template.render(suggestion);
|
||||
}
|
||||
|
||||
handleSelected(input, event, suggestion, datasetNumber, context = {}) {
|
||||
// Do nothing if click on the suggestion, as it's already a <a href>, the
|
||||
// browser will take care of it. This allow Ctrl-Clicking on results and not
|
||||
// having the main window being redirected as well
|
||||
if (context.selectionMethod === 'click') {
|
||||
return;
|
||||
}
|
||||
|
||||
static injectSearchBox(input) {
|
||||
input.before(templates.searchBox);
|
||||
const newInput = input
|
||||
.prev()
|
||||
.prev()
|
||||
.find("input");
|
||||
input.remove();
|
||||
return newInput;
|
||||
input.setVal('');
|
||||
window.location.assign(suggestion.url);
|
||||
}
|
||||
|
||||
handleShown(input) {
|
||||
const middleOfInput = input.offset().left + input.width() / 2;
|
||||
let middleOfWindow = $(document).width() / 2;
|
||||
|
||||
if (isNaN(middleOfWindow)) {
|
||||
middleOfWindow = 900;
|
||||
}
|
||||
|
||||
static bindSearchBoxEvent() {
|
||||
$('.searchbox [type="reset"]').on("click", function () {
|
||||
$("input#docsearch").focus();
|
||||
$(this).addClass("hide");
|
||||
autocomplete.autocomplete.setVal("");
|
||||
});
|
||||
|
||||
$("input#docsearch").on("keyup", () => {
|
||||
const searchbox = document.querySelector("input#docsearch");
|
||||
const reset = document.querySelector('.searchbox [type="reset"]');
|
||||
reset.className = "searchbox__reset";
|
||||
if (searchbox.value.length === 0) {
|
||||
reset.className += " hide";
|
||||
}
|
||||
});
|
||||
const alignClass = middleOfInput - middleOfWindow >= 0 ? 'algolia-autocomplete-right' : 'algolia-autocomplete-left';
|
||||
const otherAlignClass =
|
||||
middleOfInput - middleOfWindow < 0 ? 'algolia-autocomplete-right' : 'algolia-autocomplete-left';
|
||||
const autocompleteWrapper = $('.algolia-autocomplete');
|
||||
if (!autocompleteWrapper.hasClass(alignClass)) {
|
||||
autocompleteWrapper.addClass(alignClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the matching input from a CSS selector, null if none matches
|
||||
* @function getInputFromSelector
|
||||
* @param {string} selector CSS selector that matches the search
|
||||
* input of the page
|
||||
* @returns {void}
|
||||
*/
|
||||
static getInputFromSelector(selector) {
|
||||
const input = $(selector).filter("input");
|
||||
return input.length ? $(input[0]) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the `source` method to be passed to autocomplete.js. It will query
|
||||
* the Algolia index and call the callbacks with the formatted hits.
|
||||
* @function getAutocompleteSource
|
||||
* @param {function} transformData An optional function to transform the hits
|
||||
* @param {function} queryHook An optional function to transform the query
|
||||
* @returns {function} Method to be passed as the `source` option of
|
||||
* autocomplete
|
||||
*/
|
||||
getAutocompleteSource(transformData, queryHook) {
|
||||
return (query, callback) => {
|
||||
if (queryHook) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
query = queryHook(query) || query;
|
||||
}
|
||||
this.client.search(query).then(hits => {
|
||||
if (
|
||||
this.queryDataCallback &&
|
||||
typeof this.queryDataCallback == "function"
|
||||
) {
|
||||
this.queryDataCallback(hits);
|
||||
}
|
||||
if (transformData) {
|
||||
hits = transformData(hits) || hits;
|
||||
}
|
||||
callback(DocSearch.formatHits(hits));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// Given a list of hits returned by the API, will reformat them to be used in
|
||||
// a Hogan template
|
||||
static formatHits(receivedHits) {
|
||||
const clonedHits = utils.deepClone(receivedHits);
|
||||
const hits = clonedHits.map(hit => {
|
||||
if (hit._highlightResult) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
hit._highlightResult = utils.mergeKeyWithParent(
|
||||
hit._highlightResult,
|
||||
"hierarchy"
|
||||
);
|
||||
}
|
||||
return utils.mergeKeyWithParent(hit, "hierarchy");
|
||||
});
|
||||
|
||||
// Group hits by category / subcategory
|
||||
let groupedHits = utils.groupBy(hits, "lvl0");
|
||||
$.each(groupedHits, (level, collection) => {
|
||||
const groupedHitsByLvl1 = utils.groupBy(collection, "lvl1");
|
||||
const flattenedHits = utils.flattenAndFlagFirst(
|
||||
groupedHitsByLvl1,
|
||||
"isSubCategoryHeader"
|
||||
);
|
||||
groupedHits[level] = flattenedHits;
|
||||
});
|
||||
groupedHits = utils.flattenAndFlagFirst(groupedHits, "isCategoryHeader");
|
||||
|
||||
// Translate hits into smaller objects to be send to the template
|
||||
return groupedHits.map(hit => {
|
||||
const url = DocSearch.formatURL(hit);
|
||||
const category = utils.getHighlightedValue(hit, "lvl0");
|
||||
const subcategory = utils.getHighlightedValue(hit, "lvl1") || category;
|
||||
const displayTitle = utils
|
||||
.compact([
|
||||
utils.getHighlightedValue(hit, "lvl2") || subcategory,
|
||||
utils.getHighlightedValue(hit, "lvl3"),
|
||||
utils.getHighlightedValue(hit, "lvl4"),
|
||||
utils.getHighlightedValue(hit, "lvl5"),
|
||||
utils.getHighlightedValue(hit, "lvl6")
|
||||
])
|
||||
.join(
|
||||
'<span class="aa-suggestion-title-separator" aria-hidden="true"> › </span>'
|
||||
);
|
||||
const text = utils.getSnippetedValue(hit, "content");
|
||||
const isTextOrSubcategoryNonEmpty =
|
||||
(subcategory && subcategory !== "") ||
|
||||
(displayTitle && displayTitle !== "");
|
||||
const isLvl1EmptyOrDuplicate =
|
||||
!subcategory || subcategory === "" || subcategory === category;
|
||||
const isLvl2 =
|
||||
displayTitle && displayTitle !== "" && displayTitle !== subcategory;
|
||||
const isLvl1 =
|
||||
!isLvl2 &&
|
||||
(subcategory && subcategory !== "" && subcategory !== category);
|
||||
const isLvl0 = !isLvl1 && !isLvl2;
|
||||
|
||||
return {
|
||||
isLvl0,
|
||||
isLvl1,
|
||||
isLvl2,
|
||||
isLvl1EmptyOrDuplicate,
|
||||
isCategoryHeader: hit.isCategoryHeader,
|
||||
isSubCategoryHeader: hit.isSubCategoryHeader,
|
||||
isTextOrSubcategoryNonEmpty,
|
||||
category,
|
||||
subcategory,
|
||||
title: displayTitle,
|
||||
text,
|
||||
url
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
static formatURL(hit) {
|
||||
const { url, anchor } = hit;
|
||||
if (url) {
|
||||
const containsAnchor = url.indexOf("#") !== -1;
|
||||
if (containsAnchor) return url;
|
||||
else if (anchor) return `${hit.url}#${hit.anchor}`;
|
||||
return url;
|
||||
} else if (anchor) return `#${hit.anchor}`;
|
||||
/* eslint-disable */
|
||||
console.warn("no anchor nor url for : ", JSON.stringify(hit));
|
||||
/* eslint-enable */
|
||||
return null;
|
||||
}
|
||||
|
||||
static getEmptyTemplate() {
|
||||
return args => Hogan.compile(templates.empty).render(args);
|
||||
}
|
||||
|
||||
static getSuggestionTemplate(isSimpleLayout) {
|
||||
const stringTemplate = isSimpleLayout
|
||||
? templates.suggestionSimple
|
||||
: templates.suggestion;
|
||||
const template = Hogan.compile(stringTemplate);
|
||||
return suggestion => template.render(suggestion);
|
||||
}
|
||||
|
||||
handleSelected(input, event, suggestion, datasetNumber, context = {}) {
|
||||
// Do nothing if click on the suggestion, as it's already a <a href>, the
|
||||
// browser will take care of it. This allow Ctrl-Clicking on results and not
|
||||
// having the main window being redirected as well
|
||||
if (context.selectionMethod === "click") {
|
||||
return;
|
||||
}
|
||||
|
||||
input.setVal("");
|
||||
window.location.assign(suggestion.url);
|
||||
}
|
||||
|
||||
handleShown(input) {
|
||||
const middleOfInput = input.offset().left + input.width() / 2;
|
||||
let middleOfWindow = $(document).width() / 2;
|
||||
|
||||
if (isNaN(middleOfWindow)) {
|
||||
middleOfWindow = 900;
|
||||
}
|
||||
|
||||
const alignClass =
|
||||
middleOfInput - middleOfWindow >= 0
|
||||
? "algolia-autocomplete-right"
|
||||
: "algolia-autocomplete-left";
|
||||
const otherAlignClass =
|
||||
middleOfInput - middleOfWindow < 0
|
||||
? "algolia-autocomplete-right"
|
||||
: "algolia-autocomplete-left";
|
||||
const autocompleteWrapper = $(".algolia-autocomplete");
|
||||
if (!autocompleteWrapper.hasClass(alignClass)) {
|
||||
autocompleteWrapper.addClass(alignClass);
|
||||
}
|
||||
|
||||
if (autocompleteWrapper.hasClass(otherAlignClass)) {
|
||||
autocompleteWrapper.removeClass(otherAlignClass);
|
||||
}
|
||||
if (autocompleteWrapper.hasClass(otherAlignClass)) {
|
||||
autocompleteWrapper.removeClass(otherAlignClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default DocSearch;
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
color: #3a33d1;
|
||||
}
|
||||
/* Highligted search terms in the main category headers */
|
||||
.algolia-docsearch-suggestion--category-header
|
||||
.algolia-docsearch-suggestion--highlight {
|
||||
.algolia-docsearch-suggestion--category-header .algolia-docsearch-suggestion--highlight {
|
||||
background-color: #4d47d5;
|
||||
}
|
||||
/* Currently selected suggestion */
|
||||
@@ -343,9 +342,7 @@
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
.algolia-autocomplete
|
||||
.algolia-docsearch-suggestion--text
|
||||
.algolia-docsearch-suggestion--highlight {
|
||||
.algolia-autocomplete .algolia-docsearch-suggestion--text .algolia-docsearch-suggestion--highlight {
|
||||
padding: 0 0 1px;
|
||||
background: inherit;
|
||||
box-shadow: inset 0 -2px 0 0 rgba(69, 142, 225, 0.8);
|
||||
@@ -419,14 +416,11 @@
|
||||
.algolia-autocomplete
|
||||
.algolia-docsearch-suggestion.algolia-docsearch-suggestion__main
|
||||
.algolia-docsearch-suggestion--category-header,
|
||||
.algolia-autocomplete
|
||||
.algolia-docsearch-suggestion.algolia-docsearch-suggestion__secondary {
|
||||
.algolia-autocomplete .algolia-docsearch-suggestion.algolia-docsearch-suggestion__secondary {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.algolia-autocomplete
|
||||
.algolia-docsearch-suggestion--subcategory-column
|
||||
.algolia-docsearch-suggestion--highlight {
|
||||
.algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column .algolia-docsearch-suggestion--highlight {
|
||||
background-color: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
@@ -459,9 +453,7 @@
|
||||
margin-top: -8px;
|
||||
}
|
||||
|
||||
.algolia-autocomplete
|
||||
.algolia-docsearch-suggestion--no-results
|
||||
.algolia-docsearch-suggestion--text {
|
||||
.algolia-autocomplete .algolia-docsearch-suggestion--no-results .algolia-docsearch-suggestion--text {
|
||||
color: #ffffff;
|
||||
margin-top: 4px;
|
||||
}
|
||||
@@ -477,14 +469,10 @@
|
||||
color: #222222;
|
||||
background-color: #ebebeb;
|
||||
border-radius: 3px;
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.algolia-autocomplete
|
||||
.algolia-docsearch-suggestion
|
||||
code
|
||||
.algolia-docsearch-suggestion--highlight {
|
||||
.algolia-autocomplete .algolia-docsearch-suggestion code .algolia-docsearch-suggestion--highlight {
|
||||
background: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { useRef, useCallback, useState } from "react";
|
||||
import classnames from "classnames";
|
||||
import { useHistory } from "@docusaurus/router";
|
||||
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
|
||||
import React, { useRef, useCallback, useState } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { useHistory } from '@docusaurus/router';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import { usePluginData } from '@docusaurus/useGlobalData';
|
||||
import useIsBrowser from "@docusaurus/useIsBrowser";
|
||||
const Search = props => {
|
||||
import useIsBrowser from '@docusaurus/useIsBrowser';
|
||||
const Search = (props) => {
|
||||
const initialized = useRef(false);
|
||||
const searchBarRef = useRef(null);
|
||||
const [indexReady, setIndexReady] = useState(false);
|
||||
@@ -13,65 +13,62 @@ const Search = props => {
|
||||
const isBrowser = useIsBrowser();
|
||||
const { baseUrl } = siteConfig;
|
||||
const initAlgolia = (searchDocs, searchIndex, DocSearch) => {
|
||||
new DocSearch({
|
||||
searchDocs,
|
||||
searchIndex,
|
||||
baseUrl,
|
||||
inputSelector: "#search_input_react",
|
||||
// Override algolia's default selection event, allowing us to do client-side
|
||||
// navigation and avoiding a full page refresh.
|
||||
handleSelected: (_input, _event, suggestion) => {
|
||||
const url = suggestion.url || "/";
|
||||
// Use an anchor tag to parse the absolute url into a relative url
|
||||
// Alternatively, we can use new URL(suggestion.url) but its not supported in IE
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
// Algolia use closest parent element id #__docusaurus when a h1 page title does not have an id
|
||||
// So, we can safely remove it. See https://github.com/facebook/docusaurus/issues/1828 for more details.
|
||||
new DocSearch({
|
||||
searchDocs,
|
||||
searchIndex,
|
||||
baseUrl,
|
||||
inputSelector: '#search_input_react',
|
||||
// Override algolia's default selection event, allowing us to do client-side
|
||||
// navigation and avoiding a full page refresh.
|
||||
handleSelected: (_input, _event, suggestion) => {
|
||||
const url = suggestion.url || '/';
|
||||
// Use an anchor tag to parse the absolute url into a relative url
|
||||
// Alternatively, we can use new URL(suggestion.url) but its not supported in IE
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
// Algolia use closest parent element id #__docusaurus when a h1 page title does not have an id
|
||||
// So, we can safely remove it. See https://github.com/facebook/docusaurus/issues/1828 for more details.
|
||||
|
||||
history.push(url);
|
||||
}
|
||||
});
|
||||
history.push(url);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const pluginData = usePluginData('docusaurus-lunr-search');
|
||||
const getSearchDoc = () =>
|
||||
process.env.NODE_ENV === "production"
|
||||
process.env.NODE_ENV === 'production'
|
||||
? fetch(`${baseUrl}${pluginData.fileNames.searchDoc}`).then((content) => content.json())
|
||||
: Promise.resolve([]);
|
||||
|
||||
const getLunrIndex = () =>
|
||||
process.env.NODE_ENV === "production"
|
||||
process.env.NODE_ENV === 'production'
|
||||
? fetch(`${baseUrl}${pluginData.fileNames.lunrIndex}`).then((content) => content.json())
|
||||
: Promise.resolve([]);
|
||||
|
||||
const loadAlgolia = () => {
|
||||
if (!initialized.current) {
|
||||
Promise.all([
|
||||
getSearchDoc(),
|
||||
getLunrIndex(),
|
||||
import("./DocSearch"),
|
||||
import("./algolia.css")
|
||||
]).then(([searchDocs, searchIndex, { default: DocSearch }]) => {
|
||||
if (searchDocs.length === 0) {
|
||||
return;
|
||||
}
|
||||
initAlgolia(searchDocs, searchIndex, DocSearch);
|
||||
setIndexReady(true);
|
||||
});
|
||||
Promise.all([getSearchDoc(), getLunrIndex(), import('./DocSearch'), import('./algolia.css')]).then(
|
||||
([searchDocs, searchIndex, { default: DocSearch }]) => {
|
||||
if (searchDocs.length === 0) {
|
||||
return;
|
||||
}
|
||||
initAlgolia(searchDocs, searchIndex, DocSearch);
|
||||
setIndexReady(true);
|
||||
},
|
||||
);
|
||||
initialized.current = true;
|
||||
}
|
||||
};
|
||||
|
||||
const toggleSearchIconClick = useCallback(
|
||||
e => {
|
||||
(e) => {
|
||||
if (!searchBarRef.current.contains(e.target)) {
|
||||
searchBarRef.current.focus();
|
||||
}
|
||||
|
||||
props.handleSearchBarToggle && props.handleSearchBarToggle(!props.isSearchBarExpanded);
|
||||
},
|
||||
[props.isSearchBarExpanded]
|
||||
[props.isSearchBarExpanded],
|
||||
);
|
||||
|
||||
if (isBrowser) {
|
||||
@@ -83,8 +80,8 @@ const Search = props => {
|
||||
<span
|
||||
aria-label="expand searchbar"
|
||||
role="button"
|
||||
className={classnames("search-icon", {
|
||||
"search-icon-hidden": props.isSearchBarExpanded
|
||||
className={classnames('search-icon', {
|
||||
'search-icon-hidden': props.isSearchBarExpanded,
|
||||
})}
|
||||
onClick={toggleSearchIconClick}
|
||||
onKeyDown={toggleSearchIconClick}
|
||||
@@ -96,9 +93,9 @@ const Search = props => {
|
||||
placeholder={indexReady ? 'Search' : 'Loading...'}
|
||||
aria-label="Search"
|
||||
className={classnames(
|
||||
"navbar__search-input",
|
||||
{ "search-bar-expanded": props.isSearchBarExpanded },
|
||||
{ "search-bar": !props.isSearchBarExpanded }
|
||||
'navbar__search-input',
|
||||
{ 'search-bar-expanded': props.isSearchBarExpanded },
|
||||
{ 'search-bar': !props.isSearchBarExpanded },
|
||||
)}
|
||||
onClick={loadAlgolia}
|
||||
onMouseOver={loadAlgolia}
|
||||
|
||||
@@ -1,147 +1,161 @@
|
||||
import lunr from "@generated/lunr.client";
|
||||
import lunr from '@generated/lunr.client';
|
||||
lunr.tokenizer.separator = /[\s\-/]+/;
|
||||
|
||||
class LunrSearchAdapter {
|
||||
constructor(searchDocs, searchIndex, baseUrl = '/') {
|
||||
this.searchDocs = searchDocs;
|
||||
this.lunrIndex = lunr.Index.load(searchIndex);
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
constructor(searchDocs, searchIndex, baseUrl = '/') {
|
||||
this.searchDocs = searchDocs;
|
||||
this.lunrIndex = lunr.Index.load(searchIndex);
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
getLunrResult(input) {
|
||||
return this.lunrIndex.query(function (query) {
|
||||
const tokens = lunr.tokenizer(input);
|
||||
query.term(tokens, {
|
||||
boost: 10
|
||||
});
|
||||
query.term(tokens, {
|
||||
wildcard: lunr.Query.wildcard.TRAILING
|
||||
});
|
||||
});
|
||||
}
|
||||
getLunrResult(input) {
|
||||
return this.lunrIndex.query(function (query) {
|
||||
const tokens = lunr.tokenizer(input);
|
||||
query.term(tokens, {
|
||||
boost: 10,
|
||||
});
|
||||
query.term(tokens, {
|
||||
wildcard: lunr.Query.wildcard.TRAILING,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getHit(doc, formattedTitle, formattedContent) {
|
||||
return {
|
||||
hierarchy: {
|
||||
lvl0: doc.pageTitle || doc.title,
|
||||
lvl1: doc.type === 0 ? null : doc.title
|
||||
getHit(doc, formattedTitle, formattedContent) {
|
||||
return {
|
||||
hierarchy: {
|
||||
lvl0: doc.pageTitle || doc.title,
|
||||
lvl1: doc.type === 0 ? null : doc.title,
|
||||
},
|
||||
url: doc.url,
|
||||
_snippetResult: formattedContent
|
||||
? {
|
||||
content: {
|
||||
value: formattedContent,
|
||||
matchLevel: 'full',
|
||||
},
|
||||
url: doc.url,
|
||||
_snippetResult: formattedContent ? {
|
||||
content: {
|
||||
value: formattedContent,
|
||||
matchLevel: "full"
|
||||
}
|
||||
} : null,
|
||||
_highlightResult: {
|
||||
hierarchy: {
|
||||
lvl0: {
|
||||
value: doc.type === 0 ? formattedTitle || doc.title : doc.pageTitle,
|
||||
},
|
||||
lvl1:
|
||||
doc.type === 0
|
||||
? null
|
||||
: {
|
||||
value: formattedTitle || doc.title
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
getTitleHit(doc, position, length) {
|
||||
const start = position[0];
|
||||
const end = position[0] + length;
|
||||
let formattedTitle = doc.title.substring(0, start) + '<span class="algolia-docsearch-suggestion--highlight">' + doc.title.substring(start, end) + '</span>' + doc.title.substring(end, doc.title.length);
|
||||
return this.getHit(doc, formattedTitle)
|
||||
}
|
||||
}
|
||||
: null,
|
||||
_highlightResult: {
|
||||
hierarchy: {
|
||||
lvl0: {
|
||||
value: doc.type === 0 ? formattedTitle || doc.title : doc.pageTitle,
|
||||
},
|
||||
lvl1:
|
||||
doc.type === 0
|
||||
? null
|
||||
: {
|
||||
value: formattedTitle || doc.title,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
getTitleHit(doc, position, length) {
|
||||
const start = position[0];
|
||||
const end = position[0] + length;
|
||||
let formattedTitle =
|
||||
doc.title.substring(0, start) +
|
||||
'<span class="algolia-docsearch-suggestion--highlight">' +
|
||||
doc.title.substring(start, end) +
|
||||
'</span>' +
|
||||
doc.title.substring(end, doc.title.length);
|
||||
return this.getHit(doc, formattedTitle);
|
||||
}
|
||||
|
||||
getKeywordHit(doc, position, length) {
|
||||
const start = position[0];
|
||||
const end = position[0] + length;
|
||||
let formattedTitle = doc.title + '<br /><i>Keywords: ' + doc.keywords.substring(0, start) + '<span class="algolia-docsearch-suggestion--highlight">' + doc.keywords.substring(start, end) + '</span>' + doc.keywords.substring(end, doc.keywords.length) + '</i>'
|
||||
return this.getHit(doc, formattedTitle)
|
||||
}
|
||||
|
||||
getContentHit(doc, position) {
|
||||
const start = position[0];
|
||||
const end = position[0] + position[1];
|
||||
let previewStart = start;
|
||||
let previewEnd = end;
|
||||
let ellipsesBefore = true;
|
||||
let ellipsesAfter = true;
|
||||
for (let k = 0; k < 3; k++) {
|
||||
const nextSpace = doc.content.lastIndexOf(' ', previewStart - 2);
|
||||
const nextDot = doc.content.lastIndexOf('.', previewStart - 2);
|
||||
if ((nextDot > 0) && (nextDot > nextSpace)) {
|
||||
previewStart = nextDot + 1;
|
||||
ellipsesBefore = false;
|
||||
break;
|
||||
}
|
||||
if (nextSpace < 0) {
|
||||
previewStart = 0;
|
||||
ellipsesBefore = false;
|
||||
break;
|
||||
}
|
||||
previewStart = nextSpace + 1;
|
||||
}
|
||||
for (let k = 0; k < 10; k++) {
|
||||
const nextSpace = doc.content.indexOf(' ', previewEnd + 1);
|
||||
const nextDot = doc.content.indexOf('.', previewEnd + 1);
|
||||
if ((nextDot > 0) && (nextDot < nextSpace)) {
|
||||
previewEnd = nextDot;
|
||||
ellipsesAfter = false;
|
||||
break;
|
||||
}
|
||||
if (nextSpace < 0) {
|
||||
previewEnd = doc.content.length;
|
||||
ellipsesAfter = false;
|
||||
break;
|
||||
}
|
||||
previewEnd = nextSpace;
|
||||
}
|
||||
let preview = doc.content.substring(previewStart, start);
|
||||
if (ellipsesBefore) {
|
||||
preview = '... ' + preview;
|
||||
}
|
||||
preview += '<span class="algolia-docsearch-suggestion--highlight">' + doc.content.substring(start, end) + '</span>';
|
||||
preview += doc.content.substring(end, previewEnd);
|
||||
if (ellipsesAfter) {
|
||||
preview += ' ...';
|
||||
}
|
||||
return this.getHit(doc, null, preview);
|
||||
getKeywordHit(doc, position, length) {
|
||||
const start = position[0];
|
||||
const end = position[0] + length;
|
||||
let formattedTitle =
|
||||
doc.title +
|
||||
'<br /><i>Keywords: ' +
|
||||
doc.keywords.substring(0, start) +
|
||||
'<span class="algolia-docsearch-suggestion--highlight">' +
|
||||
doc.keywords.substring(start, end) +
|
||||
'</span>' +
|
||||
doc.keywords.substring(end, doc.keywords.length) +
|
||||
'</i>';
|
||||
return this.getHit(doc, formattedTitle);
|
||||
}
|
||||
|
||||
getContentHit(doc, position) {
|
||||
const start = position[0];
|
||||
const end = position[0] + position[1];
|
||||
let previewStart = start;
|
||||
let previewEnd = end;
|
||||
let ellipsesBefore = true;
|
||||
let ellipsesAfter = true;
|
||||
for (let k = 0; k < 3; k++) {
|
||||
const nextSpace = doc.content.lastIndexOf(' ', previewStart - 2);
|
||||
const nextDot = doc.content.lastIndexOf('.', previewStart - 2);
|
||||
if (nextDot > 0 && nextDot > nextSpace) {
|
||||
previewStart = nextDot + 1;
|
||||
ellipsesBefore = false;
|
||||
break;
|
||||
}
|
||||
if (nextSpace < 0) {
|
||||
previewStart = 0;
|
||||
ellipsesBefore = false;
|
||||
break;
|
||||
}
|
||||
previewStart = nextSpace + 1;
|
||||
}
|
||||
search(input) {
|
||||
return new Promise((resolve, rej) => {
|
||||
const results = this.getLunrResult(input);
|
||||
const hits = [];
|
||||
results.length > 5 && (results.length = 5);
|
||||
this.titleHitsRes = []
|
||||
this.contentHitsRes = []
|
||||
results.forEach(result => {
|
||||
const doc = this.searchDocs[result.ref];
|
||||
const { metadata } = result.matchData;
|
||||
for (let i in metadata) {
|
||||
if (metadata[i].title) {
|
||||
if (!this.titleHitsRes.includes(result.ref)) {
|
||||
const position = metadata[i].title.position[0]
|
||||
hits.push(this.getTitleHit(doc, position, input.length));
|
||||
this.titleHitsRes.push(result.ref);
|
||||
}
|
||||
} else if (metadata[i].content) {
|
||||
const position = metadata[i].content.position[0]
|
||||
hits.push(this.getContentHit(doc, position))
|
||||
} else if (metadata[i].keywords) {
|
||||
const position = metadata[i].keywords.position[0]
|
||||
hits.push(this.getKeywordHit(doc, position, input.length));
|
||||
this.titleHitsRes.push(result.ref);
|
||||
}
|
||||
}
|
||||
});
|
||||
hits.length > 5 && (hits.length = 5);
|
||||
resolve(hits);
|
||||
});
|
||||
for (let k = 0; k < 10; k++) {
|
||||
const nextSpace = doc.content.indexOf(' ', previewEnd + 1);
|
||||
const nextDot = doc.content.indexOf('.', previewEnd + 1);
|
||||
if (nextDot > 0 && nextDot < nextSpace) {
|
||||
previewEnd = nextDot;
|
||||
ellipsesAfter = false;
|
||||
break;
|
||||
}
|
||||
if (nextSpace < 0) {
|
||||
previewEnd = doc.content.length;
|
||||
ellipsesAfter = false;
|
||||
break;
|
||||
}
|
||||
previewEnd = nextSpace;
|
||||
}
|
||||
let preview = doc.content.substring(previewStart, start);
|
||||
if (ellipsesBefore) {
|
||||
preview = '... ' + preview;
|
||||
}
|
||||
preview += '<span class="algolia-docsearch-suggestion--highlight">' + doc.content.substring(start, end) + '</span>';
|
||||
preview += doc.content.substring(end, previewEnd);
|
||||
if (ellipsesAfter) {
|
||||
preview += ' ...';
|
||||
}
|
||||
return this.getHit(doc, null, preview);
|
||||
}
|
||||
search(input) {
|
||||
return new Promise((resolve, rej) => {
|
||||
const results = this.getLunrResult(input);
|
||||
const hits = [];
|
||||
results.length > 5 && (results.length = 5);
|
||||
this.titleHitsRes = [];
|
||||
this.contentHitsRes = [];
|
||||
results.forEach((result) => {
|
||||
const doc = this.searchDocs[result.ref];
|
||||
const { metadata } = result.matchData;
|
||||
for (let i in metadata) {
|
||||
if (metadata[i].title) {
|
||||
if (!this.titleHitsRes.includes(result.ref)) {
|
||||
const position = metadata[i].title.position[0];
|
||||
hits.push(this.getTitleHit(doc, position, input.length));
|
||||
this.titleHitsRes.push(result.ref);
|
||||
}
|
||||
} else if (metadata[i].content) {
|
||||
const position = metadata[i].content.position[0];
|
||||
hits.push(this.getContentHit(doc, position));
|
||||
} else if (metadata[i].keywords) {
|
||||
const position = metadata[i].keywords.position[0];
|
||||
hits.push(this.getKeywordHit(doc, position, input.length));
|
||||
this.titleHitsRes.push(result.ref);
|
||||
}
|
||||
}
|
||||
});
|
||||
hits.length > 5 && (hits.length = 5);
|
||||
resolve(hits);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default LunrSearchAdapter;
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import $ from "autocomplete.js/zepto";
|
||||
import $ from 'autocomplete.js/zepto';
|
||||
|
||||
const utils = {
|
||||
/*
|
||||
* Move the content of an object key one level higher.
|
||||
* eg.
|
||||
* {
|
||||
* name: 'My name',
|
||||
* hierarchy: {
|
||||
* lvl0: 'Foo',
|
||||
* lvl1: 'Bar'
|
||||
* }
|
||||
* }
|
||||
* Will be converted to
|
||||
* {
|
||||
* name: 'My name',
|
||||
* lvl0: 'Foo',
|
||||
* lvl1: 'Bar'
|
||||
* }
|
||||
* @param {Object} object Main object
|
||||
* @param {String} property Main object key to move up
|
||||
* @return {Object}
|
||||
* @throws Error when key is not an attribute of Object or is not an object itself
|
||||
*/
|
||||
* Move the content of an object key one level higher.
|
||||
* eg.
|
||||
* {
|
||||
* name: 'My name',
|
||||
* hierarchy: {
|
||||
* lvl0: 'Foo',
|
||||
* lvl1: 'Bar'
|
||||
* }
|
||||
* }
|
||||
* Will be converted to
|
||||
* {
|
||||
* name: 'My name',
|
||||
* lvl0: 'Foo',
|
||||
* lvl1: 'Bar'
|
||||
* }
|
||||
* @param {Object} object Main object
|
||||
* @param {String} property Main object key to move up
|
||||
* @return {Object}
|
||||
* @throws Error when key is not an attribute of Object or is not an object itself
|
||||
*/
|
||||
mergeKeyWithParent(object, property) {
|
||||
if (object[property] === undefined) {
|
||||
return object;
|
||||
@@ -34,36 +34,36 @@ const utils = {
|
||||
return newObject;
|
||||
},
|
||||
/*
|
||||
* Group all objects of a collection by the value of the specified attribute
|
||||
* If the attribute is a string, use the lowercase form.
|
||||
*
|
||||
* eg.
|
||||
* groupBy([
|
||||
* {name: 'Tim', category: 'dev'},
|
||||
* {name: 'Vincent', category: 'dev'},
|
||||
* {name: 'Ben', category: 'sales'},
|
||||
* {name: 'Jeremy', category: 'sales'},
|
||||
* {name: 'AlexS', category: 'dev'},
|
||||
* {name: 'AlexK', category: 'sales'}
|
||||
* ], 'category');
|
||||
* =>
|
||||
* {
|
||||
* 'devs': [
|
||||
* {name: 'Tim', category: 'dev'},
|
||||
* {name: 'Vincent', category: 'dev'},
|
||||
* {name: 'AlexS', category: 'dev'}
|
||||
* ],
|
||||
* 'sales': [
|
||||
* {name: 'Ben', category: 'sales'},
|
||||
* {name: 'Jeremy', category: 'sales'},
|
||||
* {name: 'AlexK', category: 'sales'}
|
||||
* ]
|
||||
* }
|
||||
* @param {array} collection Array of objects to group
|
||||
* @param {String} property The attribute on which apply the grouping
|
||||
* @return {array}
|
||||
* @throws Error when one of the element does not have the specified property
|
||||
*/
|
||||
* Group all objects of a collection by the value of the specified attribute
|
||||
* If the attribute is a string, use the lowercase form.
|
||||
*
|
||||
* eg.
|
||||
* groupBy([
|
||||
* {name: 'Tim', category: 'dev'},
|
||||
* {name: 'Vincent', category: 'dev'},
|
||||
* {name: 'Ben', category: 'sales'},
|
||||
* {name: 'Jeremy', category: 'sales'},
|
||||
* {name: 'AlexS', category: 'dev'},
|
||||
* {name: 'AlexK', category: 'sales'}
|
||||
* ], 'category');
|
||||
* =>
|
||||
* {
|
||||
* 'devs': [
|
||||
* {name: 'Tim', category: 'dev'},
|
||||
* {name: 'Vincent', category: 'dev'},
|
||||
* {name: 'AlexS', category: 'dev'}
|
||||
* ],
|
||||
* 'sales': [
|
||||
* {name: 'Ben', category: 'sales'},
|
||||
* {name: 'Jeremy', category: 'sales'},
|
||||
* {name: 'AlexK', category: 'sales'}
|
||||
* ]
|
||||
* }
|
||||
* @param {array} collection Array of objects to group
|
||||
* @param {String} property The attribute on which apply the grouping
|
||||
* @return {array}
|
||||
* @throws Error when one of the element does not have the specified property
|
||||
*/
|
||||
groupBy(collection, property) {
|
||||
const newCollection = {};
|
||||
$.each(collection, (index, item) => {
|
||||
@@ -84,94 +84,94 @@ const utils = {
|
||||
return newCollection;
|
||||
},
|
||||
/*
|
||||
* Return an array of all the values of the specified object
|
||||
* eg.
|
||||
* values({
|
||||
* foo: 42,
|
||||
* bar: true,
|
||||
* baz: 'yep'
|
||||
* })
|
||||
* =>
|
||||
* [42, true, yep]
|
||||
* @param {object} object Object to extract values from
|
||||
* @return {array}
|
||||
*/
|
||||
* Return an array of all the values of the specified object
|
||||
* eg.
|
||||
* values({
|
||||
* foo: 42,
|
||||
* bar: true,
|
||||
* baz: 'yep'
|
||||
* })
|
||||
* =>
|
||||
* [42, true, yep]
|
||||
* @param {object} object Object to extract values from
|
||||
* @return {array}
|
||||
*/
|
||||
values(object) {
|
||||
return Object.keys(object).map(key => object[key]);
|
||||
return Object.keys(object).map((key) => object[key]);
|
||||
},
|
||||
/*
|
||||
* Flattens an array
|
||||
* eg.
|
||||
* flatten([1, 2, [3, 4], [5, 6]])
|
||||
* =>
|
||||
* [1, 2, 3, 4, 5, 6]
|
||||
* @param {array} array Array to flatten
|
||||
* @return {array}
|
||||
*/
|
||||
* Flattens an array
|
||||
* eg.
|
||||
* flatten([1, 2, [3, 4], [5, 6]])
|
||||
* =>
|
||||
* [1, 2, 3, 4, 5, 6]
|
||||
* @param {array} array Array to flatten
|
||||
* @return {array}
|
||||
*/
|
||||
flatten(array) {
|
||||
const results = [];
|
||||
array.forEach(value => {
|
||||
array.forEach((value) => {
|
||||
if (!Array.isArray(value)) {
|
||||
results.push(value);
|
||||
return;
|
||||
}
|
||||
value.forEach(subvalue => {
|
||||
value.forEach((subvalue) => {
|
||||
results.push(subvalue);
|
||||
});
|
||||
});
|
||||
return results;
|
||||
},
|
||||
/*
|
||||
* Flatten all values of an object into an array, marking each first element of
|
||||
* each group with a specific flag
|
||||
* eg.
|
||||
* flattenAndFlagFirst({
|
||||
* 'devs': [
|
||||
* {name: 'Tim', category: 'dev'},
|
||||
* {name: 'Vincent', category: 'dev'},
|
||||
* {name: 'AlexS', category: 'dev'}
|
||||
* ],
|
||||
* 'sales': [
|
||||
* {name: 'Ben', category: 'sales'},
|
||||
* {name: 'Jeremy', category: 'sales'},
|
||||
* {name: 'AlexK', category: 'sales'}
|
||||
* ]
|
||||
* , 'isTop');
|
||||
* =>
|
||||
* [
|
||||
* {name: 'Tim', category: 'dev', isTop: true},
|
||||
* {name: 'Vincent', category: 'dev', isTop: false},
|
||||
* {name: 'AlexS', category: 'dev', isTop: false},
|
||||
* {name: 'Ben', category: 'sales', isTop: true},
|
||||
* {name: 'Jeremy', category: 'sales', isTop: false},
|
||||
* {name: 'AlexK', category: 'sales', isTop: false}
|
||||
* ]
|
||||
* @param {object} object Object to flatten
|
||||
* @param {string} flag Flag to set to true on first element of each group
|
||||
* @return {array}
|
||||
*/
|
||||
* Flatten all values of an object into an array, marking each first element of
|
||||
* each group with a specific flag
|
||||
* eg.
|
||||
* flattenAndFlagFirst({
|
||||
* 'devs': [
|
||||
* {name: 'Tim', category: 'dev'},
|
||||
* {name: 'Vincent', category: 'dev'},
|
||||
* {name: 'AlexS', category: 'dev'}
|
||||
* ],
|
||||
* 'sales': [
|
||||
* {name: 'Ben', category: 'sales'},
|
||||
* {name: 'Jeremy', category: 'sales'},
|
||||
* {name: 'AlexK', category: 'sales'}
|
||||
* ]
|
||||
* , 'isTop');
|
||||
* =>
|
||||
* [
|
||||
* {name: 'Tim', category: 'dev', isTop: true},
|
||||
* {name: 'Vincent', category: 'dev', isTop: false},
|
||||
* {name: 'AlexS', category: 'dev', isTop: false},
|
||||
* {name: 'Ben', category: 'sales', isTop: true},
|
||||
* {name: 'Jeremy', category: 'sales', isTop: false},
|
||||
* {name: 'AlexK', category: 'sales', isTop: false}
|
||||
* ]
|
||||
* @param {object} object Object to flatten
|
||||
* @param {string} flag Flag to set to true on first element of each group
|
||||
* @return {array}
|
||||
*/
|
||||
flattenAndFlagFirst(object, flag) {
|
||||
const values = this.values(object).map(collection =>
|
||||
const values = this.values(object).map((collection) =>
|
||||
collection.map((item, index) => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
item[flag] = index === 0;
|
||||
return item;
|
||||
})
|
||||
}),
|
||||
);
|
||||
return this.flatten(values);
|
||||
},
|
||||
/*
|
||||
* Removes all empty strings, null, false and undefined elements array
|
||||
* eg.
|
||||
* compact([42, false, null, undefined, '', [], 'foo']);
|
||||
* =>
|
||||
* [42, [], 'foo']
|
||||
* @param {array} array Array to compact
|
||||
* @return {array}
|
||||
*/
|
||||
* Removes all empty strings, null, false and undefined elements array
|
||||
* eg.
|
||||
* compact([42, false, null, undefined, '', [], 'foo']);
|
||||
* =>
|
||||
* [42, [], 'foo']
|
||||
* @param {array} array Array to compact
|
||||
* @return {array}
|
||||
*/
|
||||
compact(array) {
|
||||
const results = [];
|
||||
array.forEach(value => {
|
||||
array.forEach((value) => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
@@ -239,11 +239,7 @@ const utils = {
|
||||
* @return {string}
|
||||
**/
|
||||
getSnippetedValue(object, property) {
|
||||
if (
|
||||
!object._snippetResult ||
|
||||
!object._snippetResult[property] ||
|
||||
!object._snippetResult[property].value
|
||||
) {
|
||||
if (!object._snippetResult || !object._snippetResult[property] || !object._snippetResult[property].value) {
|
||||
return object[property];
|
||||
}
|
||||
let snippet = object._snippetResult[property].value;
|
||||
@@ -257,11 +253,11 @@ const utils = {
|
||||
return snippet;
|
||||
},
|
||||
/*
|
||||
* Deep clone an object.
|
||||
* Note: This will not clone functions and dates
|
||||
* @param {object} object Object to clone
|
||||
* @return {object}
|
||||
*/
|
||||
* Deep clone an object.
|
||||
* Note: This will not clone functions and dates
|
||||
* @param {object} object Object to clone
|
||||
* @return {object}
|
||||
*/
|
||||
deepClone(object) {
|
||||
return JSON.parse(JSON.stringify(object));
|
||||
},
|
||||
|
||||
BIN
docs/static/img/immich-screenshots.png
vendored
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
docs/static/img/immich-screenshots.webp
vendored
|
Before Width: | Height: | Size: 162 KiB |
@@ -4,25 +4,25 @@ module.exports = {
|
||||
corePlugins: {
|
||||
preflight: false, // disable Tailwind's reset
|
||||
},
|
||||
content: ["./src/**/*.{js,jsx,ts,tsx}", "../docs/**/*.mdx"], // my markdown stuff is in ../docs, not /src
|
||||
darkMode: ["class", '[data-theme="dark"]'], // hooks into docusaurus' dark mode settigns
|
||||
content: ['./src/**/*.{js,jsx,ts,tsx}', '../docs/**/*.mdx'], // my markdown stuff is in ../docs, not /src
|
||||
darkMode: ['class', '[data-theme="dark"]'], // hooks into docusaurus' dark mode settigns
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
// Light Theme
|
||||
"immich-primary": "#4250af",
|
||||
"immich-bg": "white",
|
||||
"immich-fg": "black",
|
||||
"immich-gray": "#F6F6F4",
|
||||
'immich-primary': '#4250af',
|
||||
'immich-bg': 'white',
|
||||
'immich-fg': 'black',
|
||||
'immich-gray': '#F6F6F4',
|
||||
|
||||
// Dark Theme
|
||||
"immich-dark-primary": "#adcbfa",
|
||||
"immich-dark-bg": "black",
|
||||
"immich-dark-fg": "#e5e7eb",
|
||||
"immich-dark-gray": "#212121",
|
||||
'immich-dark-primary': '#adcbfa',
|
||||
'immich-dark-bg': 'black',
|
||||
'immich-dark-fg': '#e5e7eb',
|
||||
'immich-dark-gray': '#212121',
|
||||
},
|
||||
fontFamily: {
|
||||
"immich-title": ["Snowburst One", "cursive"],
|
||||
'immich-title': ['Snowburst One', 'cursive'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
{ "source": "/docs/features/password-login", "destination": "/docs/administration/password-login" },
|
||||
{ "source": "/docs/features/server-commands", "destination": "/docs/administration/server-commands" },
|
||||
{ "source": "/docs/features/storage-template", "destination": "/docs/administration/storage-template" },
|
||||
{ "source": "/docs/features/user-management", "destination": "/docs/administration/user-management" }
|
||||
{ "source": "/docs/features/user-management", "destination": "/docs/administration/user-management" },
|
||||
{ "source": "/docs/developer/contributing", "destination": "/docs/developer/pr-checklist" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
venv/
|
||||
venv/
|
||||
*.zip
|
||||
*.onnx
|
||||
170
machine-learning/.gitignore
vendored
@@ -1,4 +1,172 @@
|
||||
*.zip
|
||||
*.onnx
|
||||
upload/
|
||||
venv/
|
||||
__pycache__/
|
||||
model-cache/
|
||||
model-cache/
|
||||
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
.idea/
|
||||
|
||||
|
||||
*.onnx
|
||||
*.zip
|
||||
@@ -1,13 +1,15 @@
|
||||
FROM python:3.10 as builder
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PIP_NO_CACHE_DIR=true
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PIP_NO_CACHE_DIR=true
|
||||
|
||||
RUN python -m venv /opt/venv
|
||||
RUN /opt/venv/bin/pip install --pre torch -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html
|
||||
RUN /opt/venv/bin/pip install transformers tqdm numpy scikit-learn scipy nltk sentencepiece flask Pillow gunicorn
|
||||
RUN /opt/venv/bin/pip install torch --index-url https://download.pytorch.org/whl/cpu
|
||||
RUN /opt/venv/bin/pip install transformers tqdm numpy scikit-learn scipy nltk sentencepiece fastapi Pillow uvicorn[standard]
|
||||
RUN /opt/venv/bin/pip install --no-deps sentence-transformers
|
||||
# Facial Recognition Stuff
|
||||
RUN /opt/venv/bin/pip install insightface onnxruntime
|
||||
|
||||
FROM python:3.10-slim
|
||||
|
||||
@@ -16,12 +18,12 @@ ENV NODE_ENV=production
|
||||
COPY --from=builder /opt/venv /opt/venv
|
||||
|
||||
ENV TRANSFORMERS_CACHE=/cache \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PATH="/opt/venv/bin:$PATH"
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PATH="/opt/venv/bin:$PATH"
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["gunicorn", "src.main:server"]
|
||||
ENV PYTHONPATH=`pwd`
|
||||
CMD ["python", "src/main.py"]
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
"""
|
||||
Gunicorn configuration options.
|
||||
https://docs.gunicorn.org/en/stable/settings.html
|
||||
"""
|
||||
import os
|
||||
|
||||
|
||||
# Set the bind address based on the env
|
||||
port = os.getenv("MACHINE_LEARNING_PORT") or "3003"
|
||||
listen_ip = os.getenv("MACHINE_LEARNING_IP") or "0.0.0.0"
|
||||
bind = [f"{listen_ip}:{port}"]
|
||||
|
||||
# Preload the Flask app / models etc. before starting the server
|
||||
preload_app = True
|
||||
|
||||
# Logging settings - log to stdout and set log level
|
||||
accesslog = "-"
|
||||
loglevel = os.getenv("MACHINE_LEARNING_LOG_LEVEL") or "info"
|
||||
|
||||
# Worker settings
|
||||
# ----------------------
|
||||
# It is important these are chosen carefully as per
|
||||
# https://pythonspeed.com/articles/gunicorn-in-docker/
|
||||
# Otherwise we get workers failing to respond to heartbeat checks,
|
||||
# especially as requests take a long time to complete.
|
||||
workers = 2
|
||||
threads = 4
|
||||
worker_tmp_dir = "/dev/shm"
|
||||
timeout = 60
|
||||
@@ -1,73 +1,158 @@
|
||||
import os
|
||||
from flask import Flask, request
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
import uvicorn
|
||||
|
||||
from insightface.app import FaceAnalysis
|
||||
from transformers import pipeline
|
||||
from sentence_transformers import SentenceTransformer, util
|
||||
from sentence_transformers import SentenceTransformer
|
||||
from PIL import Image
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
is_dev = os.getenv('NODE_ENV') == 'development'
|
||||
server_port = os.getenv('MACHINE_LEARNING_PORT', 3003)
|
||||
server_host = os.getenv('MACHINE_LEARNING_HOST', '0.0.0.0')
|
||||
|
||||
classification_model = os.getenv('MACHINE_LEARNING_CLASSIFICATION_MODEL', 'microsoft/resnet-50')
|
||||
object_model = os.getenv('MACHINE_LEARNING_OBJECT_MODEL', 'hustvl/yolos-tiny')
|
||||
clip_image_model = os.getenv('MACHINE_LEARNING_CLIP_IMAGE_MODEL', 'clip-ViT-B-32')
|
||||
clip_text_model = os.getenv('MACHINE_LEARNING_CLIP_TEXT_MODEL', 'clip-ViT-B-32')
|
||||
class MlRequestBody(BaseModel):
|
||||
thumbnailPath: str
|
||||
|
||||
|
||||
class ClipRequestBody(BaseModel):
|
||||
text: str
|
||||
|
||||
|
||||
classification_model = os.getenv(
|
||||
"MACHINE_LEARNING_CLASSIFICATION_MODEL", "microsoft/resnet-50"
|
||||
)
|
||||
object_model = os.getenv("MACHINE_LEARNING_OBJECT_MODEL", "hustvl/yolos-tiny")
|
||||
clip_image_model = os.getenv("MACHINE_LEARNING_CLIP_IMAGE_MODEL", "clip-ViT-B-32")
|
||||
clip_text_model = os.getenv("MACHINE_LEARNING_CLIP_TEXT_MODEL", "clip-ViT-B-32")
|
||||
facial_recognition_model = os.getenv(
|
||||
"MACHINE_LEARNING_FACIAL_RECOGNITION_MODEL", "buffalo_l"
|
||||
)
|
||||
|
||||
cache_folder = os.getenv("MACHINE_LEARNING_CACHE_FOLDER", "/cache")
|
||||
|
||||
_model_cache = {}
|
||||
def _get_model(model, task=None):
|
||||
global _model_cache
|
||||
key = '|'.join([model, str(task)])
|
||||
if key not in _model_cache:
|
||||
if task:
|
||||
_model_cache[key] = pipeline(model=model, task=task)
|
||||
else:
|
||||
_model_cache[key] = SentenceTransformer(model)
|
||||
return _model_cache[key]
|
||||
|
||||
server = Flask(__name__)
|
||||
app = FastAPI()
|
||||
|
||||
@server.route("/ping")
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
# Get all models
|
||||
_get_model(object_model, "object-detection")
|
||||
_get_model(classification_model, "image-classification")
|
||||
_get_model(clip_image_model)
|
||||
_get_model(clip_text_model)
|
||||
_get_model(facial_recognition_model, "facial-recognition")
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {"message": "Immich ML"}
|
||||
|
||||
|
||||
@app.get("/ping")
|
||||
def ping():
|
||||
return "pong"
|
||||
|
||||
@server.route("/object-detection/detect-object", methods=['POST'])
|
||||
def object_detection():
|
||||
model = _get_model(object_model, 'object-detection')
|
||||
assetPath = request.json['thumbnailPath']
|
||||
return run_engine(model, assetPath), 200
|
||||
|
||||
@server.route("/image-classifier/tag-image", methods=['POST'])
|
||||
def image_classification():
|
||||
model = _get_model(classification_model, 'image-classification')
|
||||
assetPath = request.json['thumbnailPath']
|
||||
return run_engine(model, assetPath), 200
|
||||
@app.post("/object-detection/detect-object", status_code=200)
|
||||
def object_detection(payload: MlRequestBody):
|
||||
model = _get_model(object_model, "object-detection")
|
||||
assetPath = payload.thumbnailPath
|
||||
return run_engine(model, assetPath)
|
||||
|
||||
@server.route("/sentence-transformer/encode-image", methods=['POST'])
|
||||
def clip_encode_image():
|
||||
|
||||
@app.post("/image-classifier/tag-image", status_code=200)
|
||||
def image_classification(payload: MlRequestBody):
|
||||
model = _get_model(classification_model, "image-classification")
|
||||
assetPath = payload.thumbnailPath
|
||||
return run_engine(model, assetPath)
|
||||
|
||||
|
||||
@app.post("/sentence-transformer/encode-image", status_code=200)
|
||||
def clip_encode_image(payload: MlRequestBody):
|
||||
model = _get_model(clip_image_model)
|
||||
assetPath = request.json['thumbnailPath']
|
||||
return model.encode(Image.open(assetPath)).tolist(), 200
|
||||
assetPath = payload.thumbnailPath
|
||||
return model.encode(Image.open(assetPath)).tolist()
|
||||
|
||||
@server.route("/sentence-transformer/encode-text", methods=['POST'])
|
||||
def clip_encode_text():
|
||||
|
||||
@app.post("/sentence-transformer/encode-text", status_code=200)
|
||||
def clip_encode_text(payload: ClipRequestBody):
|
||||
model = _get_model(clip_text_model)
|
||||
text = request.json['text']
|
||||
return model.encode(text).tolist(), 200
|
||||
text = payload.text
|
||||
return model.encode(text).tolist()
|
||||
|
||||
|
||||
@app.post("/facial-recognition/detect-faces", status_code=200)
|
||||
def facial_recognition(payload: MlRequestBody):
|
||||
model = _get_model(facial_recognition_model, "facial-recognition")
|
||||
assetPath = payload.thumbnailPath
|
||||
img = cv.imread(assetPath)
|
||||
height, width, _ = img.shape
|
||||
results = []
|
||||
faces = model.get(img)
|
||||
|
||||
for face in faces:
|
||||
if face.det_score < 0.7:
|
||||
continue
|
||||
x1, y1, x2, y2 = face.bbox
|
||||
|
||||
results.append(
|
||||
{
|
||||
"imageWidth": width,
|
||||
"imageHeight": height,
|
||||
"boundingBox": {
|
||||
"x1": round(x1),
|
||||
"y1": round(y1),
|
||||
"x2": round(x2),
|
||||
"y2": round(y2),
|
||||
},
|
||||
"score": face.det_score.item(),
|
||||
"embedding": face.normed_embedding.tolist(),
|
||||
}
|
||||
)
|
||||
return results
|
||||
|
||||
|
||||
def run_engine(engine, path):
|
||||
result = []
|
||||
predictions = engine(path)
|
||||
|
||||
for index, pred in enumerate(predictions):
|
||||
tags = pred['label'].split(', ')
|
||||
if (pred['score'] > 0.9):
|
||||
tags = pred["label"].split(", ")
|
||||
if pred["score"] > 0.9:
|
||||
result = [*result, *tags]
|
||||
|
||||
if (len(result) > 1):
|
||||
if len(result) > 1:
|
||||
result = list(set(result))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _get_model(model, task=None):
|
||||
global _model_cache
|
||||
key = "|".join([model, str(task)])
|
||||
if key not in _model_cache:
|
||||
if task:
|
||||
if task == "facial-recognition":
|
||||
face_model = FaceAnalysis(
|
||||
name=model,
|
||||
root=cache_folder,
|
||||
allowed_modules=["detection", "recognition"],
|
||||
)
|
||||
face_model.prepare(ctx_id=0, det_size=(640, 640))
|
||||
_model_cache[key] = face_model
|
||||
else:
|
||||
_model_cache[key] = pipeline(model=model, task=task)
|
||||
else:
|
||||
_model_cache[key] = SentenceTransformer(model, cache_folder=cache_folder)
|
||||
return _model_cache[key]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
server.run(debug=is_dev, host=server_host, port=server_port)
|
||||
host = os.getenv("MACHINE_LEARNING_HOST", "0.0.0.0")
|
||||
port = int(os.getenv("MACHINE_LEARNING_PORT", 3003))
|
||||
is_dev = os.getenv("NODE_ENV") == "development"
|
||||
|
||||
uvicorn.run("main:app", host=host, port=port, reload=is_dev, workers=1)
|
||||
|
||||
@@ -61,26 +61,17 @@ fi
|
||||
|
||||
if [ "$CURRENT_SERVER" != "$NEXT_SERVER" ]; then
|
||||
echo "Pumping Server: $CURRENT_SERVER => $NEXT_SERVER"
|
||||
npm --prefix server version $SERVER_PUMP
|
||||
npm --prefix server run api:generate
|
||||
fi
|
||||
|
||||
if [ "$CURRENT_MOBILE" != "$NEXT_MOBILE" ]; then
|
||||
echo "Pumping Mobile: $CURRENT_MOBILE => $NEXT_MOBILE"
|
||||
fi
|
||||
|
||||
sed -i "s/^ \"version\": \"$CURRENT_SERVER\",$/ \"version\": \"$NEXT_SERVER\",/" server/package.json
|
||||
sed -i "s/^ \"version\": \"$CURRENT_SERVER\",$/ \"version\": \"$NEXT_SERVER\",/" server/package-lock.json
|
||||
sed -i "s/\"version\": \"$CURRENT_SERVER\",$/\"version\": \"$NEXT_SERVER\",/" server/immich-openapi-specs.json
|
||||
sed -i "s/\"android\.injected\.version\.name\" => \"$CURRENT_SERVER\",/\"android\.injected\.version\.name\" => \"$NEXT_SERVER\",/" mobile/android/fastlane/Fastfile
|
||||
sed -i "s/version_number: \"$CURRENT_SERVER\"$/version_number: \"$NEXT_SERVER\"/" mobile/ios/fastlane/Fastfile
|
||||
sed -i "s/\"android\.injected\.version\.code\" => $CURRENT_MOBILE,/\"android\.injected\.version\.code\" => $NEXT_MOBILE,/" mobile/android/fastlane/Fastfile
|
||||
sed -i "s/^version: $CURRENT_SERVER+$CURRENT_MOBILE$/version: $NEXT_SERVER+$NEXT_MOBILE/" mobile/pubspec.yaml
|
||||
|
||||
# OpenApi Generated Files
|
||||
sed -i "s/API version: $CURRENT_SERVER,$/API version: $NEXT_SERVER/" mobile/openapi/README.md
|
||||
sed -i "s/OpenAPI document: $CURRENT_SERVER,$/OpenAPI document: $NEXT_SERVER/" web/src/api/open-api/api.ts
|
||||
sed -i "s/OpenAPI document: $CURRENT_SERVER,$/OpenAPI document: $NEXT_SERVER/" web/src/api/open-api/base.ts
|
||||
sed -i "s/OpenAPI document: $CURRENT_SERVER,$/OpenAPI document: $NEXT_SERVER/" web/src/api/open-api/common.ts
|
||||
sed -i "s/OpenAPI document: $CURRENT_SERVER,$/OpenAPI document: $NEXT_SERVER/" web/src/api/open-api/configuration.ts
|
||||
sed -i "s/OpenAPI document: $CURRENT_SERVER,$/OpenAPI document: $NEXT_SERVER/" web/src/api/open-api/index.ts
|
||||
|
||||
echo "IMMICH_VERSION=v$NEXT_SERVER" >>$GITHUB_ENV
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"flutterSdkVersion": "3.7.0",
|
||||
"flutterSdkVersion": "3.10.0",
|
||||
"flavors": {}
|
||||
}
|
||||
}
|
||||
|
||||
5
mobile/.gitignore
vendored
@@ -49,3 +49,8 @@ app.*.map.json
|
||||
|
||||
# Fastlane
|
||||
ios/fastlane/report.xml
|
||||
|
||||
# Isar
|
||||
default.isar
|
||||
default.isar.lock
|
||||
libisar.so
|
||||
@@ -23,6 +23,7 @@ if (flutterVersionName == null) {
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
def keystoreProperties = new Properties()
|
||||
@@ -86,4 +87,6 @@ dependencies {
|
||||
implementation "androidx.work:work-runtime-ktx:$work_version"
|
||||
implementation "androidx.concurrent:concurrent-futures:$concurrent_version"
|
||||
implementation "com.google.guava:guava:$guava_version"
|
||||
implementation "com.github.bumptech.glide:glide:$glide_version"
|
||||
kapt "com.github.bumptech.glide:compiler:$glide_version"
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package app.alextran.immich
|
||||
|
||||
import com.bumptech.glide.annotation.GlideModule
|
||||
import com.bumptech.glide.module.AppGlideModule
|
||||
|
||||
@GlideModule
|
||||
class AppGlideModule : AppGlideModule()
|
||||
@@ -1,15 +1,16 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.6.10'
|
||||
ext.kotlin_version = '1.8.20'
|
||||
ext.work_version = '2.7.1'
|
||||
ext.concurrent_version = '1.1.0'
|
||||
ext.guava_version = '31.0.1-android'
|
||||
ext.glide_version = '4.14.2'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.1.2'
|
||||
classpath 'com.android.tools.build:gradle:7.4.2'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
@@ -29,6 +30,6 @@ subprojects {
|
||||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ platform :android do
|
||||
task: 'bundle',
|
||||
build_type: 'Release',
|
||||
properties: {
|
||||
"android.injected.version.code" => 77,
|
||||
"android.injected.version.name" => "1.54.0",
|
||||
"android.injected.version.code" => 82,
|
||||
"android.injected.version.name" => "1.59.0",
|
||||
}
|
||||
)
|
||||
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
* Fixes white navigation bar in Android 8.
|
||||
* Add bottom safe area to video player controls.
|
||||
* Fix null check operator on null value.
|
||||
* Add troubleshooting toggle.
|
||||
* Configurable log level.
|
||||
* Consistent handling of DateTime in SyncService.
|
||||
* Fix asset removal edge cases.
|
||||
* Archive feature on mobile.
|
||||
* Add AssetState and proper asset updating.
|
||||
* Video player disposes early.
|
||||
@@ -0,0 +1 @@
|
||||
* Minor UI improvement
|
||||
@@ -0,0 +1,2 @@
|
||||
* Upgrade to Flutter 3.10
|
||||
* Lazy loading of timeline
|
||||
@@ -0,0 +1 @@
|
||||
* Remove Hive box
|
||||
@@ -5,17 +5,17 @@
|
||||
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000285">
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000296">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="168.230955">
|
||||
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="64.042552">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="43.975952">
|
||||
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="29.676557">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#Fri Jun 23 08:50:38 CEST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
|
||||
distributionSha256Sum=cd5c2958a107ee7f0722004a12d0f8559b4564c34daad7df06cffd4d12a426d0
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip
|
||||
distributionSha256Sum=518a863631feb7452b8f1b3dc2aaee5f388355cc3421bbd0275fbeadd77e84b2
|
||||
@@ -1,6 +1,10 @@
|
||||
{
|
||||
"add_to_album_bottom_sheet_added": "Přidáno do {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Již v {album}",
|
||||
"advanced_settings_tile_subtitle": "Pokročilé uživatelské nastavení",
|
||||
"advanced_settings_tile_title": "Pokročilé",
|
||||
"advanced_settings_troubleshooting_subtitle": "Povolit dodatečné funkce pro řešení problémů",
|
||||
"advanced_settings_troubleshooting_title": "Řešení problémů",
|
||||
"album_info_card_backup_album_excluded": "VYLOUČENO",
|
||||
"album_info_card_backup_album_included": "ZAHRNUTO",
|
||||
"album_thumbnail_card_item": "1 položka",
|
||||
@@ -17,7 +21,10 @@
|
||||
"album_viewer_appbar_share_remove": "Odstranit z alba",
|
||||
"album_viewer_page_share_add_users": "Přidat uživatele",
|
||||
"all_videos_page_title": "Videa",
|
||||
"archive_page_no_archived_assets": "No archived assets found",
|
||||
"archive_page_title": "Archív ({})",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Dynamické rozložení",
|
||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
||||
"asset_list_layout_settings_group_by": "Seskupit položky podle",
|
||||
"asset_list_layout_settings_group_by_month": "Měsíc",
|
||||
"asset_list_layout_settings_group_by_month_day": "Měsíc + den",
|
||||
@@ -108,10 +115,12 @@
|
||||
"control_bottom_app_bar_add_to_album": "Přidat do alba",
|
||||
"control_bottom_app_bar_album_info": "{} položek",
|
||||
"control_bottom_app_bar_album_info_shared": "{} položky - sdílené",
|
||||
"control_bottom_app_bar_archive": "Archív",
|
||||
"control_bottom_app_bar_create_new_album": "Vytvořit nové album",
|
||||
"control_bottom_app_bar_delete": "Vymazat",
|
||||
"control_bottom_app_bar_favorite": "Oblíbené",
|
||||
"control_bottom_app_bar_share": "Sdílet",
|
||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
||||
"create_album_page_untitled": "Bez názvu",
|
||||
"create_shared_album_page_create": "Vytvořit",
|
||||
"create_shared_album_page_share": "Sdílet",
|
||||
@@ -126,6 +135,8 @@
|
||||
"delete_dialog_cancel": "Zrušit",
|
||||
"delete_dialog_ok": "Vymazat",
|
||||
"delete_dialog_title": "Vymazat trvale",
|
||||
"description_input_hint_text": "Přidat popis...",
|
||||
"description_input_submit_error": "Chyba aktualizace popisu, další podrobnosti najdete v logu",
|
||||
"exif_bottom_sheet_description": "Přidat popis...",
|
||||
"exif_bottom_sheet_details": "PODROBNOSTI",
|
||||
"exif_bottom_sheet_location": "LOKALITA",
|
||||
@@ -133,16 +144,19 @@
|
||||
"experimental_settings_new_asset_list_title": "Povolení experimentální mřížky fotografií",
|
||||
"experimental_settings_subtitle": "Používejte na vlastní riziko!",
|
||||
"experimental_settings_title": "Experimentální",
|
||||
"favorites_page_no_favorites": "No favorite assets found",
|
||||
"favorites_page_title": "Oblíbené",
|
||||
"home_page_add_to_album_conflicts": "Přidáno {added} položek do alba {album}. {failed} položek již je v albu.",
|
||||
"home_page_add_to_album_err_local": "Zatím není možné přidat lokální média do alb, přeskakuje se",
|
||||
"home_page_add_to_album_success": "Přidány položky {added} do alba {album}.",
|
||||
"home_page_archive_err_local": "Zatím nemohu archivovat lokální média, přeskakuji",
|
||||
"home_page_building_timeline": "Vytváření časové osy",
|
||||
"home_page_favorite_err_local": "Zatím není možné zařadit lokální média mezi oblíbená, přeskakuje se",
|
||||
"home_page_first_time_notice": "Pokud aplikaci používáte poprvé, nezapomeňte si vybrat zálohovaná alba, aby se na časové ose mohly nacházet fotografie a videa z vybraných albech.",
|
||||
"image_viewer_page_state_provider_download_error": "Chyba stahování",
|
||||
"image_viewer_page_state_provider_download_success": "Stahování bylo úspěšné",
|
||||
"library_page_albums": "Alba",
|
||||
"library_page_archive": "Archív",
|
||||
"library_page_device_albums": "Alba v zařízení",
|
||||
"library_page_favorites": "Oblíbené",
|
||||
"library_page_new_album": "Nové album",
|
||||
@@ -206,7 +220,7 @@
|
||||
"search_page_view_all_button": "Zobrazit vše",
|
||||
"search_page_your_activity": "Vaše aktivita",
|
||||
"search_result_page_new_search_hint": "Nové vyhledávání",
|
||||
"search_suggestion_list_smart_search_hint_1": "Ve výchozím nastavení je chytré vyhledávání zapnuto, pro vyhledávání metadat použijte syntaxi",
|
||||
"search_suggestion_list_smart_search_hint_1": "Ve výchozím nastavení je chytré vyhledávání zapnuto, pro vyhledávání metadat použijte syntaxi ",
|
||||
"search_suggestion_list_smart_search_hint_2": "m:vaše-vyhledávaná-fráze",
|
||||
"select_additional_user_for_sharing_page_suggestions": "Návrhy",
|
||||
"select_user_for_sharing_page_err_album": "Nepodařilo se vytvořit album",
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
{
|
||||
"add_to_album_bottom_sheet_added": "Tilføjet til {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Allerede i {album}",
|
||||
"advanced_settings_tile_subtitle": "Avancerede brugerindstillinger",
|
||||
"advanced_settings_tile_title": "Arkivér",
|
||||
"advanced_settings_troubleshooting_subtitle": "Slå ekstra funktioner for fejlsøgning til",
|
||||
"advanced_settings_troubleshooting_title": "Fejlsøgning",
|
||||
"album_info_card_backup_album_excluded": "EKSKLUDERET",
|
||||
"album_info_card_backup_album_included": "INKLUDERET",
|
||||
"album_thumbnail_card_item": "1 genstand",
|
||||
@@ -17,7 +21,10 @@
|
||||
"album_viewer_appbar_share_remove": "Fjern fra album",
|
||||
"album_viewer_page_share_add_users": "Tilføj brugere",
|
||||
"all_videos_page_title": "Videoer",
|
||||
"archive_page_no_archived_assets": "No archived assets found",
|
||||
"archive_page_title": "Arkivér ({})",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Dynamisk layout",
|
||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
||||
"asset_list_layout_settings_group_by": "Gruppér elementer pr. ",
|
||||
"asset_list_layout_settings_group_by_month": "Måned",
|
||||
"asset_list_layout_settings_group_by_month_day": "Måned + dag",
|
||||
@@ -108,10 +115,12 @@
|
||||
"control_bottom_app_bar_add_to_album": "Tilføj til album",
|
||||
"control_bottom_app_bar_album_info": "{} genstande",
|
||||
"control_bottom_app_bar_album_info_shared": "{} genstande • Delt",
|
||||
"control_bottom_app_bar_archive": "Arkiv",
|
||||
"control_bottom_app_bar_create_new_album": "Opret nyt album",
|
||||
"control_bottom_app_bar_delete": "Slet",
|
||||
"control_bottom_app_bar_favorite": "Favorit",
|
||||
"control_bottom_app_bar_share": "Del",
|
||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
||||
"create_album_page_untitled": "Uden titel",
|
||||
"create_shared_album_page_create": "Opret",
|
||||
"create_shared_album_page_share": "Del",
|
||||
@@ -126,6 +135,8 @@
|
||||
"delete_dialog_cancel": "Annuller",
|
||||
"delete_dialog_ok": "Slet",
|
||||
"delete_dialog_title": "Slet permanent",
|
||||
"description_input_hint_text": "Tilføj en beskrivelse...",
|
||||
"description_input_submit_error": "Fejl med at opdatere beskrivelsen. Tjek loggen for flere detaljer",
|
||||
"exif_bottom_sheet_description": "Tilføj beskrivelse...",
|
||||
"exif_bottom_sheet_details": "DETALJER",
|
||||
"exif_bottom_sheet_location": "LOKATION",
|
||||
@@ -133,16 +144,19 @@
|
||||
"experimental_settings_new_asset_list_title": "Aktiver eksperimentelt fotogitter",
|
||||
"experimental_settings_subtitle": "Brug på eget ansvar!",
|
||||
"experimental_settings_title": "Eksperimentelle",
|
||||
"favorites_page_no_favorites": "No favorite assets found",
|
||||
"favorites_page_title": "Favoritter",
|
||||
"home_page_add_to_album_conflicts": "Tilføjede {added} elementer til album {album}. {failed} elementer er allerede i albummet.",
|
||||
"home_page_add_to_album_err_local": "Kan endnu ikke tilføje lokale elementer til album. Springer over..",
|
||||
"home_page_add_to_album_success": "Tilføjede {added} elementer til album {album}.",
|
||||
"home_page_archive_err_local": "Kan ikke arkivere lokalt element endnu.. Springer over",
|
||||
"home_page_building_timeline": "Bygger tidslinjen",
|
||||
"home_page_favorite_err_local": "Kan endnu ikke gøre lokale elementer til favoritter. Springer over..",
|
||||
"home_page_first_time_notice": "Hvis dette er din første gang i appen, bedes du vælge en backup af albummer så tidlinjen kan blive fyldt med billeder og videoer fra albummerne.",
|
||||
"image_viewer_page_state_provider_download_error": "Fejl ved download",
|
||||
"image_viewer_page_state_provider_download_success": "Download succesfuld",
|
||||
"library_page_albums": "Albummer",
|
||||
"library_page_archive": "Arkiv",
|
||||
"library_page_device_albums": "Albummer på enhed",
|
||||
"library_page_favorites": "Favoritter",
|
||||
"library_page_new_album": "Nyt album",
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
{
|
||||
"add_to_album_bottom_sheet_added": "Added to {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
|
||||
"add_to_album_bottom_sheet_added": "Zu {album} hinzugefügt",
|
||||
"add_to_album_bottom_sheet_already_exists": "Bereits in {album}",
|
||||
"advanced_settings_tile_subtitle": "Erweiterte Benutzereinstellungen",
|
||||
"advanced_settings_tile_title": "Sonstige",
|
||||
"advanced_settings_troubleshooting_subtitle": "Aktiviere erweiterte Funktionen zur Fehlersuche",
|
||||
"advanced_settings_troubleshooting_title": "Fehlersuche",
|
||||
"album_info_card_backup_album_excluded": "AUSGESCHLOSSEN",
|
||||
"album_info_card_backup_album_included": "EINGESCHLOSSEN",
|
||||
"album_thumbnail_card_item": "1 Element",
|
||||
"album_thumbnail_card_items": "{} Elemente",
|
||||
"album_thumbnail_card_shared": " · Geteilt",
|
||||
"album_thumbnail_owned": "Owned",
|
||||
"album_thumbnail_shared_by": "Shared by {}",
|
||||
"album_thumbnail_shared_by": "Geteilt von {}",
|
||||
"album_viewer_appbar_share_delete": "Album löschen",
|
||||
"album_viewer_appbar_share_err_delete": "Album konnte nicht gelöscht werden",
|
||||
"album_viewer_appbar_share_err_leave": "Album konnte nicht verlassen werden",
|
||||
@@ -17,12 +21,15 @@
|
||||
"album_viewer_appbar_share_remove": "Entferne vom Album",
|
||||
"album_viewer_page_share_add_users": "Nutzer hinzufügen",
|
||||
"all_videos_page_title": "Videos",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Dynamic layout",
|
||||
"asset_list_layout_settings_group_by": "Group assets by",
|
||||
"asset_list_layout_settings_group_by_month": "Month",
|
||||
"asset_list_layout_settings_group_by_month_day": "Month + day",
|
||||
"asset_list_settings_subtitle": "Photo grid layout settings",
|
||||
"asset_list_settings_title": "Photo Grid",
|
||||
"archive_page_no_archived_assets": "No archived assets found",
|
||||
"archive_page_title": "Archive ({})",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Dynamisches Layout",
|
||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
||||
"asset_list_layout_settings_group_by": "Gruppiere Elemente nach",
|
||||
"asset_list_layout_settings_group_by_month": "Monat",
|
||||
"asset_list_layout_settings_group_by_month_day": "Monat + Tag",
|
||||
"asset_list_settings_subtitle": "Einstellungen für das Fotogitter-Layout",
|
||||
"asset_list_settings_title": "Fotogitter",
|
||||
"backup_album_selection_page_albums_device": "Alben auf dem Gerät ({})",
|
||||
"backup_album_selection_page_albums_tap": "Tippen um einzuschließen, doppelt tippen um zu entfernen",
|
||||
"backup_album_selection_page_assets_scatter": "Elemente können sich über mehrere Alben verteilen. Daher können diese vor der Sicherung eingeschlossen oder ausgeschlossen werden",
|
||||
@@ -31,21 +38,21 @@
|
||||
"backup_album_selection_page_total_assets": "Elemente",
|
||||
"backup_all": "Alle",
|
||||
"backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…",
|
||||
"backup_background_service_connection_failed_message": "Failed to connect to the server. Retrying…",
|
||||
"backup_background_service_current_upload_notification": "Uploading {}",
|
||||
"backup_background_service_default_notification": "Suche nach neuen assets…",
|
||||
"backup_background_service_error_title": "Backup error",
|
||||
"backup_background_service_in_progress_notification": "Backing up your assets…",
|
||||
"backup_background_service_upload_failure_notification": "Failed to upload {}",
|
||||
"backup_background_service_connection_failed_message": "Konnte keine Verbindung zum Server herstellen. Neuer Versuch...",
|
||||
"backup_background_service_current_upload_notification": "Lädt {} hoch",
|
||||
"backup_background_service_default_notification": "Suche nach neuen Elementen…",
|
||||
"backup_background_service_error_title": "Fehler bei der Sicherung",
|
||||
"backup_background_service_in_progress_notification": "Elemente werden gesichert...",
|
||||
"backup_background_service_upload_failure_notification": "Konnte {} nicht hochladen",
|
||||
"backup_controller_page_albums": "Gesicherte Alben",
|
||||
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
|
||||
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
|
||||
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings",
|
||||
"backup_controller_page_background_battery_info_link": "Show me how",
|
||||
"backup_controller_page_background_app_refresh_disabled_content": "Aktiviere Hintergrundaktualisierungen in Einstellungen -> Allgemein -> Hintergrundaktualisierungen um Sicherungen im Hintergrund zu ermöglichen. ",
|
||||
"backup_controller_page_background_app_refresh_disabled_title": "Hintergrundaktualisierungen sind deaktiviert.",
|
||||
"backup_controller_page_background_app_refresh_enable_button_text": "Gehe zu Einstellungen",
|
||||
"backup_controller_page_background_battery_info_link": "Zeige mir wie",
|
||||
"backup_controller_page_background_battery_info_message": "For the best background backup experience, please disable any battery optimizations restricting background activity for Immich.\n\nSince this is device-specific, please lookup the required information for your device manufacturer.",
|
||||
"backup_controller_page_background_battery_info_ok": "OK",
|
||||
"backup_controller_page_background_battery_info_title": "Battery optimizations",
|
||||
"backup_controller_page_background_charging": "Only while charging",
|
||||
"backup_controller_page_background_battery_info_title": "Batterieoptimierungen",
|
||||
"backup_controller_page_background_charging": "Nur während des Ladens",
|
||||
"backup_controller_page_background_configure_error": "Failed to configure the background service",
|
||||
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
||||
"backup_controller_page_background_description": "Turn on the background service to automatically backup any new assets without needing to open the app",
|
||||
@@ -53,7 +60,7 @@
|
||||
"backup_controller_page_background_is_on": "Automatic background backup is on",
|
||||
"backup_controller_page_background_turn_off": "Turn off background service",
|
||||
"backup_controller_page_background_turn_on": "Turn on background service",
|
||||
"backup_controller_page_background_wifi": "Only on WiFi",
|
||||
"backup_controller_page_background_wifi": "Nur im WLAN",
|
||||
"backup_controller_page_backup": "Sicherung",
|
||||
"backup_controller_page_backup_selected": "Ausgewählt: ",
|
||||
"backup_controller_page_backup_sub": "Gesicherte Fotos und Videos",
|
||||
@@ -83,42 +90,44 @@
|
||||
"backup_err_only_album": "Das einzige Album kann nicht entfernt werden",
|
||||
"backup_info_card_assets": "Elemente",
|
||||
"cache_settings_album_thumbnails": "Library page thumbnails ({} assets)",
|
||||
"cache_settings_clear_cache_button": "Clear cache",
|
||||
"cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.",
|
||||
"cache_settings_clear_cache_button": "Zwischenspeicher löschen",
|
||||
"cache_settings_clear_cache_button_title": "Löscht den Zwischenspeicher der App. Dies wird die Leistungsfähigkeit der App deutlich einschränken, bis der Zwischenspeicher wieder aufgebaut wurde.",
|
||||
"cache_settings_image_cache_size": "Image cache size ({} assets)",
|
||||
"cache_settings_statistics_album": "Library thumbnails",
|
||||
"cache_settings_statistics_assets": "{} assets ({})",
|
||||
"cache_settings_statistics_full": "Full images",
|
||||
"cache_settings_statistics_shared": "Shared album thumbnails",
|
||||
"cache_settings_statistics_thumbnail": "Vorschaubilder",
|
||||
"cache_settings_statistics_title": "Cache usage",
|
||||
"cache_settings_statistics_title": "Zwischenspeicher Nutzung",
|
||||
"cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application",
|
||||
"cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)",
|
||||
"cache_settings_title": "Caching Settings",
|
||||
"change_password_form_confirm_password": "Confirm Password",
|
||||
"change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
|
||||
"change_password_form_new_password": "New Password",
|
||||
"change_password_form_password_mismatch": "Passwords do not match",
|
||||
"change_password_form_reenter_new_password": "Re-enter New Password",
|
||||
"common_add_to_album": "Add to album",
|
||||
"common_change_password": "Change Password",
|
||||
"common_create_new_album": "Create new album",
|
||||
"common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.",
|
||||
"common_shared": "Shared",
|
||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"cache_settings_title": "Zwischenspeicher Einstellungen",
|
||||
"change_password_form_confirm_password": "Passwort bestätigen",
|
||||
"change_password_form_description": "Hallo {firstName} {lastName}\n\nDas ist entweder das erste Mal dass du dich einloggst oder eine Anfrage zur Änderung deines Passwortes wurde gestellt. Bitte gebe das neue Passwort ein.",
|
||||
"change_password_form_new_password": "Neues Passwort",
|
||||
"change_password_form_password_mismatch": "Passwörter stimmen nicht überein",
|
||||
"change_password_form_reenter_new_password": "Passwort erneut eingeben",
|
||||
"common_add_to_album": "Zu Album hinzufügen",
|
||||
"common_change_password": "Passwort ändern",
|
||||
"common_create_new_album": "Erstelle ein neues Album",
|
||||
"common_server_error": "Bitte überprüfe Deine Netzwerkverbindung und stelle sicher, dass die App und Server Versionen kompatibel sind.",
|
||||
"common_shared": "Geteilt",
|
||||
"control_bottom_app_bar_add_to_album": "Zu Album hinzufügen",
|
||||
"control_bottom_app_bar_album_info": "{} Elemente",
|
||||
"control_bottom_app_bar_album_info_shared": "{} Elemente · geteilt",
|
||||
"control_bottom_app_bar_archive": "Archiv",
|
||||
"control_bottom_app_bar_create_new_album": "Neues Album erstellen",
|
||||
"control_bottom_app_bar_delete": "Löschen",
|
||||
"control_bottom_app_bar_favorite": "Favorite",
|
||||
"control_bottom_app_bar_favorite": "Favorit",
|
||||
"control_bottom_app_bar_share": "Teilen",
|
||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
||||
"create_album_page_untitled": "Unbenannt",
|
||||
"create_shared_album_page_create": "Erstellen",
|
||||
"create_shared_album_page_share": "Teilen",
|
||||
"create_shared_album_page_share_add_assets": "ELEMENTE HINZUFÜGEN",
|
||||
"create_shared_album_page_share_select_photos": "Fotos auswählen",
|
||||
"curated_location_page_title": "Places",
|
||||
"curated_object_page_title": "Things",
|
||||
"curated_location_page_title": "Orte",
|
||||
"curated_object_page_title": "Dinge",
|
||||
"daily_title_text_date": "E, dd MMM",
|
||||
"daily_title_text_date_year": "E, dd MMM, yyyy",
|
||||
"date_format": "E d. LLL y • hh:mm",
|
||||
@@ -126,110 +135,115 @@
|
||||
"delete_dialog_cancel": "Abbrechen",
|
||||
"delete_dialog_ok": "Löschen",
|
||||
"delete_dialog_title": "Für immer löschen",
|
||||
"description_input_hint_text": "Beschreibung hinzufügen...",
|
||||
"description_input_submit_error": "Beschreibung konnte nicht geändert werden, bitte im Log für mehr Details nachsehen.",
|
||||
"exif_bottom_sheet_description": "Beschreibung hinzufügen...",
|
||||
"exif_bottom_sheet_details": "DETAILS",
|
||||
"exif_bottom_sheet_location": "STANDORT",
|
||||
"experimental_settings_new_asset_list_subtitle": "In Arbeit",
|
||||
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
|
||||
"experimental_settings_subtitle": "Use at your own risk!",
|
||||
"experimental_settings_new_asset_list_title": "Experimentelle Fotogitter aktivieren",
|
||||
"experimental_settings_subtitle": "Benutzung auf eigene Gefahr!",
|
||||
"experimental_settings_title": "Experimentell",
|
||||
"favorites_page_title": "Favorites",
|
||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"home_page_building_timeline": "Building the timeline",
|
||||
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
|
||||
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
|
||||
"image_viewer_page_state_provider_download_error": "Download Error",
|
||||
"image_viewer_page_state_provider_download_success": "Download Success",
|
||||
"favorites_page_no_favorites": "No favorite assets found",
|
||||
"favorites_page_title": "Favoriten",
|
||||
"home_page_add_to_album_conflicts": "{added} Elemente zu {album} hinzugefügt. {failed} Elemente sind bereits vorhanden.",
|
||||
"home_page_add_to_album_err_local": "Kann lokale Elemente noch nicht zu Alben hinzufügen, überspringe",
|
||||
"home_page_add_to_album_success": "{added} Elemente zu {album} hinzugefügt.",
|
||||
"home_page_archive_err_local": "Kann lokale Elemente nicht archvieren, überspringe",
|
||||
"home_page_building_timeline": "Zeitachse wird erstellt.",
|
||||
"home_page_favorite_err_local": "Kann lokale Elemente noch nicht favorisieren, überspringe",
|
||||
"home_page_first_time_notice": "Wenn dies das erste Mal ist dass Du Immich nutzt, stelle bitte sicher, dass mindestens ein Album zur Sicherung ausgewählt ist, sodass die Zeitachse mit Fotos und Videos gefüllt werden kann.",
|
||||
"image_viewer_page_state_provider_download_error": "Fehler beim Herunterladen",
|
||||
"image_viewer_page_state_provider_download_success": "Erfolgreich heruntergeladen",
|
||||
"library_page_albums": "Alben",
|
||||
"library_page_device_albums": "Albums on Device",
|
||||
"library_page_favorites": "Favorites",
|
||||
"library_page_archive": "Archiv",
|
||||
"library_page_device_albums": "Alben auf dem Gerät.",
|
||||
"library_page_favorites": "Favoriten",
|
||||
"library_page_new_album": "Neues Album",
|
||||
"library_page_sharing": "Sharing",
|
||||
"library_page_sort_created": "Most recently created",
|
||||
"library_page_sort_title": "Album title",
|
||||
"login_form_api_exception": "API exception. Please check the server URL and try again.",
|
||||
"library_page_sharing": "Teilen",
|
||||
"library_page_sort_created": "Zuletzt erstellt",
|
||||
"library_page_sort_title": "Albumtitel",
|
||||
"login_form_api_exception": "API Fehler. Bitte die Serveradresse überprüfen und erneut versuchen.",
|
||||
"login_form_button_text": "Anmelden",
|
||||
"login_form_email_hint": "deine@email.de",
|
||||
"login_form_endpoint_hint": "http://deine-server-ip:port/api",
|
||||
"login_form_endpoint_url": "Server URL",
|
||||
"login_form_err_http": "Bitte gebe http:// oder https:// an",
|
||||
"login_form_err_invalid_email": "Ungültige E-Mail",
|
||||
"login_form_err_invalid_url": "Invalid URL",
|
||||
"login_form_err_invalid_url": "Ungültige URL",
|
||||
"login_form_err_leading_whitespace": "Führendes Leerzichen",
|
||||
"login_form_err_trailing_whitespace": "Folgendes Leerzeichen",
|
||||
"login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
|
||||
"login_form_failed_get_oauth_server_config": "Fehler beim Login per OAuth, Server-URL überprüfen",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth-Funktion nicht verfügbar auf diesem Server.",
|
||||
"login_form_failed_login": "Error logging you in, check server url, email and password",
|
||||
"login_form_label_email": "E-Mail",
|
||||
"login_form_label_password": "Passwort",
|
||||
"login_form_next_button": "Next",
|
||||
"login_form_next_button": "Weiter",
|
||||
"login_form_password_hint": "password",
|
||||
"login_form_save_login": "Angemeldet bleiben",
|
||||
"login_form_server_empty": "Enter a server URL.",
|
||||
"login_form_server_error": "Could not connect to server.",
|
||||
"login_form_server_empty": "Serveradresse eingeben.",
|
||||
"login_form_server_error": "Konnte nicht mit Server verbinden.",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"motion_photos_page_title": "Motion Photos",
|
||||
"notification_permission_dialog_cancel": "Cancel",
|
||||
"notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.",
|
||||
"notification_permission_dialog_settings": "Settings",
|
||||
"notification_permission_list_tile_content": "Grant permission to enable notifications.",
|
||||
"notification_permission_list_tile_enable_button": "Enable Notifications",
|
||||
"notification_permission_list_tile_title": "Notification Permission",
|
||||
"permission_onboarding_continue_anyway": "Continue anyway",
|
||||
"motion_photos_page_title": "Live Photos",
|
||||
"notification_permission_dialog_cancel": "Abbrechen",
|
||||
"notification_permission_dialog_content": "Um Benachrichtigungen zu aktivieren, navigiere zu Einstellungen und klicke \"Erlauben\"",
|
||||
"notification_permission_dialog_settings": "Einstellungen",
|
||||
"notification_permission_list_tile_content": "Erlaube Berechtigung für Benachrichtigungen",
|
||||
"notification_permission_list_tile_enable_button": "Aktiviere Benachrichtigungen",
|
||||
"notification_permission_list_tile_title": "Benachrichtigungs-Berechtigung",
|
||||
"permission_onboarding_continue_anyway": "Trotzdem fortfahren",
|
||||
"permission_onboarding_get_started": "Get started",
|
||||
"permission_onboarding_go_to_settings": "Go to settings",
|
||||
"permission_onboarding_grant_permission": "Grant permission",
|
||||
"permission_onboarding_log_out": "Log out",
|
||||
"permission_onboarding_permission_denied": "Permission denied. To use Immich, grant photo and video permissions in Settings.",
|
||||
"permission_onboarding_permission_granted": "Permission granted! You are all set.",
|
||||
"permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.",
|
||||
"permission_onboarding_request": "Immich requires permission to view your photos and videos.",
|
||||
"permission_onboarding_go_to_settings": "Gehe zu Einstellungen",
|
||||
"permission_onboarding_grant_permission": "Berechtigung erteilen",
|
||||
"permission_onboarding_log_out": "Abmelden",
|
||||
"permission_onboarding_permission_denied": "Berechtigungen verweigert. Um Immich zu benutzen, Zugriff auf Fotos und Videos in Einstellungen erlauben.",
|
||||
"permission_onboarding_permission_granted": "Berechtigung erteilt! Du bist startklar.",
|
||||
"permission_onboarding_permission_limited": "Berechtigungen unzureichend. Um Immich das Sichern von ganzen Sammlungen zu ermöglichen, muss der Zugriff auf alle Fotos und Videos in den Einstellungen erlaubt werden.",
|
||||
"permission_onboarding_request": "Immich benötigt Berechtigung um auf deine Fotos und Videos zuzugreifen.",
|
||||
"profile_drawer_app_logs": "Logs",
|
||||
"profile_drawer_client_server_up_to_date": "App und Server sind aktuell",
|
||||
"profile_drawer_settings": "Einstellungen",
|
||||
"profile_drawer_sign_out": "Abmelden",
|
||||
"recently_added_page_title": "Recently Added",
|
||||
"recently_added_page_title": "Zuletzt hinzugefügt",
|
||||
"search_bar_hint": "Durchsuche deine Fotos",
|
||||
"search_page_categories": "Categories",
|
||||
"search_page_favorites": "Favorites",
|
||||
"search_page_motion_photos": "Motion Photos",
|
||||
"search_page_categories": "Kategorien",
|
||||
"search_page_favorites": "Favoriten",
|
||||
"search_page_motion_photos": "Live Photos",
|
||||
"search_page_no_objects": "Keine Objektinformationen verfügbar",
|
||||
"search_page_no_places": "Keine Informationen über Orte verfügbar",
|
||||
"search_page_places": "Orte",
|
||||
"search_page_recently_added": "Recently added",
|
||||
"search_page_screenshots": "Screenshots",
|
||||
"search_page_recently_added": "Zuletzt hinzugefügt",
|
||||
"search_page_screenshots": "Bildschirmfotos",
|
||||
"search_page_selfies": "Selfies",
|
||||
"search_page_things": "Dinge",
|
||||
"search_page_videos": "Videos",
|
||||
"search_page_view_all_button": "View all",
|
||||
"search_page_your_activity": "Your activity",
|
||||
"search_page_view_all_button": "Alle anzeigen",
|
||||
"search_page_your_activity": "Deine Aktivität",
|
||||
"search_result_page_new_search_hint": "Neue Suche",
|
||||
"search_suggestion_list_smart_search_hint_1": "Smart search is enabled by default, to search for metadata use the syntax ",
|
||||
"search_suggestion_list_smart_search_hint_2": "m:your-search-term",
|
||||
"search_suggestion_list_smart_search_hint_1": "Intelligente Suche ist standardmäßig aktiviert; um nach Metadaten zu suchen Syntax benutzen",
|
||||
"search_suggestion_list_smart_search_hint_2": "m:dein-suchbegriff",
|
||||
"select_additional_user_for_sharing_page_suggestions": "Vorschläge",
|
||||
"select_user_for_sharing_page_err_album": "Album konnte nicht erstellt werden",
|
||||
"select_user_for_sharing_page_share_suggestions": "Suggestions",
|
||||
"server_info_box_app_version": "App Version",
|
||||
"server_info_box_server_version": "Server Version",
|
||||
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
|
||||
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_image_viewer_help": "Der Detailviewer lädt zuerst die kleine Miniaturansicht, dann die Vorschau in mittlerer Größe (falls aktiviert) und schließlich das Original (falls aktiviert).",
|
||||
"setting_image_viewer_original_subtitle": "Aktivieren, um das Originalbild in voller Auflösung (groß!) zu laden. Deaktivieren, um den Datenverbrauch zu reduzieren (sowohl im Netzwerk als auch im Gerätespeicher).",
|
||||
"setting_image_viewer_original_title": "Original laden",
|
||||
"setting_image_viewer_preview_subtitle": "Aktivieren, um ein Bild mit mittlerer Auflösung zu laden. Deaktivieren, um entweder das Original direkt zu laden oder nur die Miniaturansicht zu verwenden.",
|
||||
"setting_image_viewer_preview_title": "Vorschaubild laden",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||
"setting_notifications_notify_hours": "{} hours",
|
||||
"setting_notifications_notify_immediately": "immediately",
|
||||
"setting_notifications_notify_minutes": "{} minutes",
|
||||
"setting_notifications_notify_never": "never",
|
||||
"setting_notifications_notify_seconds": "{} seconds",
|
||||
"setting_notifications_notify_hours": "{} Stunden",
|
||||
"setting_notifications_notify_immediately": "sofort",
|
||||
"setting_notifications_notify_minutes": "{} Minuten",
|
||||
"setting_notifications_notify_never": "niemals",
|
||||
"setting_notifications_notify_seconds": "{} Sekunden",
|
||||
"setting_notifications_single_progress_subtitle": "Detaillierte Upload Informationen für jedes Element.",
|
||||
"setting_notifications_single_progress_title": "Show background backup detail progress",
|
||||
"setting_notifications_single_progress_title": "Zeige Hintergrund-Sicherungs Detailfortschritt",
|
||||
"setting_notifications_subtitle": "Adjust your notification preferences",
|
||||
"setting_notifications_title": "Notifications",
|
||||
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_notifications_title": "Benachrichtigungen",
|
||||
"setting_notifications_total_progress_subtitle": "Gesamter Upload-Fortschritt (abgeschlossen/Anzahl Elemente)",
|
||||
"setting_notifications_total_progress_title": "Zeige Hintergrundsicherungsfortschritt",
|
||||
"setting_pages_app_bar_settings": "Einstellungen",
|
||||
"settings_require_restart": "Bitte starte Immich neu, um diese Einstellung anzuwenden.",
|
||||
"share_add": "Hinzufügen",
|
||||
@@ -247,8 +261,8 @@
|
||||
"tab_controller_nav_photos": "Fotos",
|
||||
"tab_controller_nav_search": "Suche",
|
||||
"tab_controller_nav_sharing": "Teilen",
|
||||
"theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})",
|
||||
"theme_setting_asset_list_storage_indicator_title": "Zeige Sicherungsstatus auf Miniaturbild",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "Anzahl der Elemente pro Reihe ({})",
|
||||
"theme_setting_dark_mode_switch": "Dunkler Modus",
|
||||
"theme_setting_image_viewer_quality_subtitle": "Einstellen der Qualität des Detailbildbetrachters",
|
||||
"theme_setting_image_viewer_quality_title": "Qualität des Bildbetrachters",
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
{
|
||||
"add_to_album_bottom_sheet_added": "Added to {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
|
||||
"advanced_settings_tile_subtitle": "Advanced user's settings",
|
||||
"advanced_settings_tile_title": "Advanced",
|
||||
"advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
|
||||
"advanced_settings_troubleshooting_title": "Troubleshooting",
|
||||
"album_info_card_backup_album_excluded": "EXCLUDED",
|
||||
"album_info_card_backup_album_included": "INCLUDED",
|
||||
"album_thumbnail_card_item": "1 item",
|
||||
@@ -17,7 +21,10 @@
|
||||
"album_viewer_appbar_share_remove": "Remove from album",
|
||||
"album_viewer_page_share_add_users": "Add users",
|
||||
"all_videos_page_title": "Videos",
|
||||
"archive_page_no_archived_assets": "No archived assets found",
|
||||
"archive_page_title": "Archive ({})",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Dynamic layout",
|
||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
||||
"asset_list_layout_settings_group_by": "Group assets by",
|
||||
"asset_list_layout_settings_group_by_month": "Month",
|
||||
"asset_list_layout_settings_group_by_month_day": "Month + day",
|
||||
@@ -108,11 +115,12 @@
|
||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"control_bottom_app_bar_archive": "Archive",
|
||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
||||
"control_bottom_app_bar_delete": "Delete",
|
||||
"control_bottom_app_bar_favorite": "Favorite",
|
||||
"control_bottom_app_bar_archive": "Archive",
|
||||
"control_bottom_app_bar_share": "Share",
|
||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
||||
"create_album_page_untitled": "Untitled",
|
||||
"create_shared_album_page_create": "Create",
|
||||
"create_shared_album_page_share": "Share",
|
||||
@@ -127,6 +135,8 @@
|
||||
"delete_dialog_cancel": "Cancel",
|
||||
"delete_dialog_ok": "Delete",
|
||||
"delete_dialog_title": "Delete Permanently",
|
||||
"description_input_hint_text": "Add description...",
|
||||
"description_input_submit_error": "Error updating description, check the log for more details",
|
||||
"exif_bottom_sheet_description": "Add Description...",
|
||||
"exif_bottom_sheet_details": "DETAILS",
|
||||
"exif_bottom_sheet_location": "LOCATION",
|
||||
@@ -134,22 +144,23 @@
|
||||
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
|
||||
"experimental_settings_subtitle": "Use at your own risk!",
|
||||
"experimental_settings_title": "Experimental",
|
||||
"favorites_page_no_favorites": "No favorite assets found",
|
||||
"favorites_page_title": "Favorites",
|
||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_building_timeline": "Building the timeline",
|
||||
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
|
||||
"image_viewer_page_state_provider_download_error": "Download Error",
|
||||
"image_viewer_page_state_provider_download_success": "Download Success",
|
||||
"library_page_albums": "Albums",
|
||||
"library_page_archive": "Archive",
|
||||
"library_page_device_albums": "Albums on Device",
|
||||
"library_page_favorites": "Favorites",
|
||||
"library_page_new_album": "New album",
|
||||
"library_page_sharing": "Sharing",
|
||||
"library_page_archive": "Archive",
|
||||
"library_page_sort_created": "Most recently created",
|
||||
"library_page_sort_title": "Album title",
|
||||
"login_form_api_exception": "API exception. Please check the server URL and try again.",
|
||||
@@ -246,6 +257,15 @@
|
||||
"sharing_page_empty_list": "EMPTY LIST",
|
||||
"sharing_silver_appbar_create_shared_album": "Create shared album",
|
||||
"sharing_silver_appbar_share_partner": "Share with partner",
|
||||
"partner_page_title": "Partner",
|
||||
"partner_page_no_more_users": "No more users to add",
|
||||
"partner_page_empty_message": "Your photos are not yet shared with any partner.",
|
||||
"partner_page_shared_to_title": "Shared to",
|
||||
"partner_page_select_partner": "Select partner",
|
||||
"partner_page_add_partner": "Add partner",
|
||||
"partner_page_partner_add_failed": "Failed to add partner",
|
||||
"partner_page_stop_sharing_title": "Stop sharing your photos?",
|
||||
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
|
||||
"tab_controller_nav_library": "Library",
|
||||
"tab_controller_nav_photos": "Photos",
|
||||
"tab_controller_nav_search": "Search",
|
||||
@@ -265,12 +285,5 @@
|
||||
"version_announcement_overlay_text_1": "Hi friend, there is a new release of",
|
||||
"version_announcement_overlay_text_2": "please take your time to visit the ",
|
||||
"version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.",
|
||||
"version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89",
|
||||
"advanced_settings_tile_title": "Advanced",
|
||||
"advanced_settings_tile_subtitle": "Advanced user's settings",
|
||||
"advanced_settings_troubleshooting_title": "Troubleshooting",
|
||||
"advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
|
||||
"description_input_submit_error": "Error updating description, check the log for more details",
|
||||
"description_input_hint_text": "Add description...",
|
||||
"archive_page_title": "Archive ({})"
|
||||
}
|
||||
"version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89"
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
{
|
||||
"add_to_album_bottom_sheet_added": "Added to {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
|
||||
"advanced_settings_tile_subtitle": "Advanced user's settings",
|
||||
"advanced_settings_tile_title": "Advanced",
|
||||
"advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
|
||||
"advanced_settings_troubleshooting_title": "Troubleshooting",
|
||||
"album_info_card_backup_album_excluded": "EXCLUIDOS",
|
||||
"album_info_card_backup_album_included": "INCLUIDOS",
|
||||
"album_thumbnail_card_item": "1 item",
|
||||
@@ -17,7 +21,10 @@
|
||||
"album_viewer_appbar_share_remove": "Eliminar del álbum ",
|
||||
"album_viewer_page_share_add_users": "Añadir usuarios",
|
||||
"all_videos_page_title": "Videos",
|
||||
"archive_page_no_archived_assets": "No archived assets found",
|
||||
"archive_page_title": "Archive ({})",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Disposición dinámica",
|
||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
||||
"asset_list_layout_settings_group_by": "Agrupar recursos por",
|
||||
"asset_list_layout_settings_group_by_month": "Mes",
|
||||
"asset_list_layout_settings_group_by_month_day": "Mes + día",
|
||||
@@ -108,10 +115,12 @@
|
||||
"control_bottom_app_bar_add_to_album": "Añadir al álbum",
|
||||
"control_bottom_app_bar_album_info": "{} elementos",
|
||||
"control_bottom_app_bar_album_info_shared": "{} elementos · Compartido",
|
||||
"control_bottom_app_bar_archive": "Archive",
|
||||
"control_bottom_app_bar_create_new_album": "Crear nuevo álbum",
|
||||
"control_bottom_app_bar_delete": "Eliminar",
|
||||
"control_bottom_app_bar_favorite": "Favorite",
|
||||
"control_bottom_app_bar_share": "Share",
|
||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
||||
"create_album_page_untitled": "Untitled",
|
||||
"create_shared_album_page_create": "Create",
|
||||
"create_shared_album_page_share": "Compartir",
|
||||
@@ -126,6 +135,8 @@
|
||||
"delete_dialog_cancel": "Cancelar",
|
||||
"delete_dialog_ok": "Eliminar",
|
||||
"delete_dialog_title": "Eliminar Permanentemente",
|
||||
"description_input_hint_text": "Add description...",
|
||||
"description_input_submit_error": "Error updating description, check the log for more details",
|
||||
"exif_bottom_sheet_description": "Añadir Descripción...",
|
||||
"exif_bottom_sheet_details": "DETALLES",
|
||||
"exif_bottom_sheet_location": "LOCALZACIÓN",
|
||||
@@ -133,16 +144,19 @@
|
||||
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
|
||||
"experimental_settings_subtitle": "Use at your own risk!",
|
||||
"experimental_settings_title": "Experimental",
|
||||
"favorites_page_no_favorites": "No favorite assets found",
|
||||
"favorites_page_title": "Favoritos",
|
||||
"home_page_add_to_album_conflicts": "Añadidos {added} elementos al álbum {album}. {failed} elementos ya están añadidos.",
|
||||
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
|
||||
"home_page_add_to_album_success": "Añadidos {added} elementos al álbum {album}.",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_building_timeline": "Construyendo la línea de tiempo",
|
||||
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
|
||||
"home_page_first_time_notice": "Si esta es la primera vez que usas la app, por favor, asegúrate de elegir un álbum de respaldo para que la línea de tiempo pueda cargar fotos y videos en los álbumes.",
|
||||
"image_viewer_page_state_provider_download_error": "Download Error",
|
||||
"image_viewer_page_state_provider_download_success": "Download Success",
|
||||
"library_page_albums": "Albums",
|
||||
"library_page_archive": "Archive",
|
||||
"library_page_device_albums": "Albums on Device",
|
||||
"library_page_favorites": "Favoritos",
|
||||
"library_page_new_album": "New album",
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
{
|
||||
"add_to_album_bottom_sheet_added": "Added to {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
|
||||
"add_to_album_bottom_sheet_added": "Lisätty albumiin {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Kohde on jo albumissa {album}",
|
||||
"advanced_settings_tile_subtitle": "Edistyneen käyttäjän asetukset",
|
||||
"advanced_settings_tile_title": "Edistyneet",
|
||||
"advanced_settings_troubleshooting_subtitle": "Kytke vianetsinnän lisäominaisuudet päälle",
|
||||
"advanced_settings_troubleshooting_title": "Vianetsintä",
|
||||
"album_info_card_backup_album_excluded": "JÄTETTY POIS",
|
||||
"album_info_card_backup_album_included": "SISÄLLYTETTY",
|
||||
"album_thumbnail_card_item": "1 kohde",
|
||||
"album_thumbnail_card_items": "{} kohdetta",
|
||||
"album_thumbnail_card_shared": "Jaettu",
|
||||
"album_thumbnail_owned": "Owned",
|
||||
"album_thumbnail_shared_by": "Shared by {}",
|
||||
"album_thumbnail_owned": "Omistettu",
|
||||
"album_thumbnail_shared_by": "Jakanut {}",
|
||||
"album_viewer_appbar_share_delete": "Poista albumi",
|
||||
"album_viewer_appbar_share_err_delete": "Albumin poistaminen epäonnistui",
|
||||
"album_viewer_appbar_share_err_leave": "Albumista poistuminen epäonnistui",
|
||||
@@ -16,8 +20,11 @@
|
||||
"album_viewer_appbar_share_leave": "Poistu albumista",
|
||||
"album_viewer_appbar_share_remove": "Poista albumista",
|
||||
"album_viewer_page_share_add_users": "Lisää käyttäjiä",
|
||||
"all_videos_page_title": "Videos",
|
||||
"all_videos_page_title": "Videot",
|
||||
"archive_page_no_archived_assets": "No archived assets found",
|
||||
"archive_page_title": "Arkisto ({})",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Dynaaminen asetelma",
|
||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
||||
"asset_list_layout_settings_group_by": "Ryhmittele",
|
||||
"asset_list_layout_settings_group_by_month": "Kuukauden mukaan",
|
||||
"asset_list_layout_settings_group_by_month_day": "Kuukauden ja päivän mukaan",
|
||||
@@ -38,9 +45,9 @@
|
||||
"backup_background_service_in_progress_notification": "Varmuuskopioidaan kohteita...",
|
||||
"backup_background_service_upload_failure_notification": "Lähetys palvelimelle epäonnistui {}",
|
||||
"backup_controller_page_albums": "Varmuuskopioi albumit",
|
||||
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
|
||||
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
|
||||
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings",
|
||||
"backup_controller_page_background_app_refresh_disabled_content": "Salli sovelluksen päivittäminen taustalla suorittaaksesi varmuuskopiointia taustalla: Asetukset > Yleiset > Appien päivitys taustalla",
|
||||
"backup_controller_page_background_app_refresh_disabled_title": "Sovelluksen päivittäminen taustalla on pois päältä",
|
||||
"backup_controller_page_background_app_refresh_enable_button_text": "Siirry asetuksiin",
|
||||
"backup_controller_page_background_battery_info_link": "Näytä minulle miten",
|
||||
"backup_controller_page_background_battery_info_message": "Kytke pois päältä kaikki Immichin taustatyöskentelyyn liittyvät akun optimoinnit, jotta varmistat taustavarmuuskopioinnin parhaan mahdollisen toiminnan.\n\nKoska tämä on laitekohtaista, tarkista tarvittavat toimet laitevalmistajan ohjeista.",
|
||||
"backup_controller_page_background_battery_info_ok": "OK",
|
||||
@@ -95,30 +102,32 @@
|
||||
"cache_settings_subtitle": "Hallitse Immich-mobiilisovelluksen välimuistin käyttöä",
|
||||
"cache_settings_thumbnail_size": "Esikatselukuvien välimuistin koko ({} kohdetta)",
|
||||
"cache_settings_title": "Välimuistin asetukset",
|
||||
"change_password_form_confirm_password": "Confirm Password",
|
||||
"change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
|
||||
"change_password_form_new_password": "New Password",
|
||||
"change_password_form_password_mismatch": "Passwords do not match",
|
||||
"change_password_form_reenter_new_password": "Re-enter New Password",
|
||||
"common_add_to_album": "Add to album",
|
||||
"common_change_password": "Change Password",
|
||||
"common_create_new_album": "Create new album",
|
||||
"common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.",
|
||||
"common_shared": "Shared",
|
||||
"change_password_form_confirm_password": "Vahvista salasana",
|
||||
"change_password_form_description": "Hei {firstName} {lastName},\n\nTämä on joko ensimmäinen kirjautumisesi järjestelmään tai salasanan vaihtaminen vaihtaminen on pakotettu. Ole hyvä ja syötä uusi salasana alle.",
|
||||
"change_password_form_new_password": "Uusi salasana",
|
||||
"change_password_form_password_mismatch": "Salasanat eivät täsmää",
|
||||
"change_password_form_reenter_new_password": "Uusi salasana uudelleen",
|
||||
"common_add_to_album": "Lisää albumiin",
|
||||
"common_change_password": "Vaihda salasana",
|
||||
"common_create_new_album": "Luo uusi albumi",
|
||||
"common_server_error": "Tarkista internet-yhteytesi. Varmista että palvelin on saavutettavissa ja sovellus-/palvelinversiot ovat yhteensopivia.",
|
||||
"common_shared": "Jaettu",
|
||||
"control_bottom_app_bar_add_to_album": "Lisää albumiin",
|
||||
"control_bottom_app_bar_album_info": "{} kohdetta",
|
||||
"control_bottom_app_bar_album_info_shared": "{} kohdetta · Jaettu",
|
||||
"control_bottom_app_bar_archive": "Arkistoi",
|
||||
"control_bottom_app_bar_create_new_album": "Luo uusi albumi",
|
||||
"control_bottom_app_bar_delete": "Poista",
|
||||
"control_bottom_app_bar_favorite": "Favorite",
|
||||
"control_bottom_app_bar_favorite": "Suosikki",
|
||||
"control_bottom_app_bar_share": "Jaa",
|
||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
||||
"create_album_page_untitled": "Nimetön",
|
||||
"create_shared_album_page_create": "Luo",
|
||||
"create_shared_album_page_share": "Jaa",
|
||||
"create_shared_album_page_share_add_assets": "LISÄÄ KOHTEITA",
|
||||
"create_shared_album_page_share_select_photos": "Valitse kuvat",
|
||||
"curated_location_page_title": "Places",
|
||||
"curated_object_page_title": "Things",
|
||||
"curated_location_page_title": "Paikat",
|
||||
"curated_object_page_title": "Asiat",
|
||||
"daily_title_text_date": "E, MMM dd",
|
||||
"daily_title_text_date_year": "E, MMM dd, yyyy",
|
||||
"date_format": "E, LLL d, y • h:mm a",
|
||||
@@ -126,6 +135,8 @@
|
||||
"delete_dialog_cancel": "Peruuta",
|
||||
"delete_dialog_ok": "Poista",
|
||||
"delete_dialog_title": "Poista pysyvästi",
|
||||
"description_input_hint_text": "Lisää kuvaus...",
|
||||
"description_input_submit_error": "Virhe kuvauksen päivittämisessä, tarkista lisätiedot lokista",
|
||||
"exif_bottom_sheet_description": "Lisää kuvaus…",
|
||||
"exif_bottom_sheet_details": "TIEDOT",
|
||||
"exif_bottom_sheet_location": "SIJAINTI",
|
||||
@@ -133,23 +144,26 @@
|
||||
"experimental_settings_new_asset_list_title": "Ota käyttöön kokeellinen kuvaruudukko",
|
||||
"experimental_settings_subtitle": "Käyttö omalla vastuulla!",
|
||||
"experimental_settings_title": "Kokeellinen",
|
||||
"favorites_page_no_favorites": "No favorite assets found",
|
||||
"favorites_page_title": "Suosikit",
|
||||
"home_page_add_to_album_conflicts": "Lisätty {added} kohdetta albumiin {album}. {failed} kohdetta on jo albumissa.",
|
||||
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
|
||||
"home_page_add_to_album_err_local": "Paikallisten kohteiden lisääminen albumeihin ei ole mahdollista, ohitetaan",
|
||||
"home_page_add_to_album_success": "Lisätty {added} kohdetta albumiin {album}.",
|
||||
"home_page_archive_err_local": "Paikallisten kohteiden arkistointi ei ole mahdollista, ohitetaan",
|
||||
"home_page_building_timeline": "Rakennetaan aikajanaa",
|
||||
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
|
||||
"home_page_favorite_err_local": "Paikallisten kohteiden lisääminen suosikkeihin ei ole mahdollista, ohitetaan",
|
||||
"home_page_first_time_notice": "Jos käytät sovellusta ensimmäistä kertaa, muista valita varmuuskopioitavat albumi(t), jotta aikajanalla voi olla kuvia ja videoita.",
|
||||
"image_viewer_page_state_provider_download_error": "Download Error",
|
||||
"image_viewer_page_state_provider_download_success": "Download Success",
|
||||
"image_viewer_page_state_provider_download_error": "Lataus epäonnistui",
|
||||
"image_viewer_page_state_provider_download_success": "Lataus onnistui",
|
||||
"library_page_albums": "Albumit",
|
||||
"library_page_device_albums": "Albums on Device",
|
||||
"library_page_archive": "Arkisto",
|
||||
"library_page_device_albums": "Laitteen albumit",
|
||||
"library_page_favorites": "Suosikit",
|
||||
"library_page_new_album": "Uusi albumi",
|
||||
"library_page_sharing": "Jakaminen",
|
||||
"library_page_sort_created": "Viimeisin luotu",
|
||||
"library_page_sort_title": "Albumin otsikko",
|
||||
"login_form_api_exception": "API exception. Please check the server URL and try again.",
|
||||
"login_form_api_exception": "API-virhe. Tarkista palvelimen URL-osoite ja yritä uudelleen.",
|
||||
"login_form_button_text": "Kirjaudu",
|
||||
"login_form_email_hint": "sahkopostisi@esimerkki.fi",
|
||||
"login_form_endpoint_hint": "http://palvelimesi-osoite:portti/api",
|
||||
@@ -164,55 +178,55 @@
|
||||
"login_form_failed_login": "Virhe kirjautumisessa. Tarkista palvelimen URL, sähköpostiosoite ja salasana.",
|
||||
"login_form_label_email": "Sähköposti",
|
||||
"login_form_label_password": "Salasana",
|
||||
"login_form_next_button": "Next",
|
||||
"login_form_next_button": "Seuraava",
|
||||
"login_form_password_hint": "salasana",
|
||||
"login_form_save_login": "Pysy kirjautuneena",
|
||||
"login_form_server_empty": "Enter a server URL.",
|
||||
"login_form_server_error": "Could not connect to server.",
|
||||
"login_form_server_empty": "Syötä palvelimen URL-osoite.",
|
||||
"login_form_server_error": "Palvelimeen ei saatu yhteyttä.",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"motion_photos_page_title": "Motion Photos",
|
||||
"notification_permission_dialog_cancel": "Cancel",
|
||||
"notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.",
|
||||
"notification_permission_dialog_settings": "Settings",
|
||||
"notification_permission_list_tile_content": "Grant permission to enable notifications.",
|
||||
"notification_permission_list_tile_enable_button": "Enable Notifications",
|
||||
"notification_permission_list_tile_title": "Notification Permission",
|
||||
"permission_onboarding_continue_anyway": "Continue anyway",
|
||||
"permission_onboarding_get_started": "Get started",
|
||||
"permission_onboarding_go_to_settings": "Go to settings",
|
||||
"permission_onboarding_grant_permission": "Grant permission",
|
||||
"permission_onboarding_log_out": "Log out",
|
||||
"permission_onboarding_permission_denied": "Permission denied. To use Immich, grant photo and video permissions in Settings.",
|
||||
"permission_onboarding_permission_granted": "Permission granted! You are all set.",
|
||||
"permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.",
|
||||
"permission_onboarding_request": "Immich requires permission to view your photos and videos.",
|
||||
"motion_photos_page_title": "Liikekuvat",
|
||||
"notification_permission_dialog_cancel": "Peruuta",
|
||||
"notification_permission_dialog_content": "Ottaaksesi ilmoitukset käyttöön, siirry asetuksiin ja valitse 'salli'.",
|
||||
"notification_permission_dialog_settings": "Asetukset",
|
||||
"notification_permission_list_tile_content": "Myönnä käyttöoikeus ottaaksesi ilmoitukset käyttöön.",
|
||||
"notification_permission_list_tile_enable_button": "Ota ilmoitukset käyttöön",
|
||||
"notification_permission_list_tile_title": "Ilmoitusten käyttöoikeus",
|
||||
"permission_onboarding_continue_anyway": "Jatka silti",
|
||||
"permission_onboarding_get_started": "Aloittaminen",
|
||||
"permission_onboarding_go_to_settings": "Siirry asetuksiin",
|
||||
"permission_onboarding_grant_permission": "Käyttöoikeuden myöntäminen",
|
||||
"permission_onboarding_log_out": "Kirjaudu ulos",
|
||||
"permission_onboarding_permission_denied": "Kielletty käyttöoikeus. Käyttääksesi Immichiä, myönnä oikeus kuviin ja videoihin asetuksista.",
|
||||
"permission_onboarding_permission_granted": "Käyttöoikeus myönnetty! Kaikki valmista.",
|
||||
"permission_onboarding_permission_limited": "Rajoitettu käyttöoikeus. Salliaksesi Immichin varmuuskopioida ja hallita koko kuvakirjastoasi, myönnä oikeus kuviin ja videoihin asetuksista.",
|
||||
"permission_onboarding_request": "Immich vaatii käyttöoikeuden kuvien ja videoiden käyttämiseen.",
|
||||
"profile_drawer_app_logs": "Lokit",
|
||||
"profile_drawer_client_server_up_to_date": "Asiakassovellus ja palvelin ovat ajan tasalla",
|
||||
"profile_drawer_settings": "Asetukset",
|
||||
"profile_drawer_sign_out": "Kirjaudu ulos",
|
||||
"recently_added_page_title": "Recently Added",
|
||||
"recently_added_page_title": "Viimeksi lisätyt",
|
||||
"search_bar_hint": "Etsi kuvia",
|
||||
"search_page_categories": "Categories",
|
||||
"search_page_favorites": "Favorites",
|
||||
"search_page_motion_photos": "Motion Photos",
|
||||
"search_page_categories": "Kategoriat",
|
||||
"search_page_favorites": "Suosikit",
|
||||
"search_page_motion_photos": "Liikekuvat",
|
||||
"search_page_no_objects": "Objektitietoja ei ole saatavilla",
|
||||
"search_page_no_places": "Paikkatietoja ei ole saatavilla",
|
||||
"search_page_places": "Paikat",
|
||||
"search_page_recently_added": "Recently added",
|
||||
"search_page_screenshots": "Screenshots",
|
||||
"search_page_selfies": "Selfies",
|
||||
"search_page_recently_added": "Viimeksi lisätyt",
|
||||
"search_page_screenshots": "Näyttökuvat",
|
||||
"search_page_selfies": "Selfiet",
|
||||
"search_page_things": "Asiat",
|
||||
"search_page_videos": "Videos",
|
||||
"search_page_view_all_button": "View all",
|
||||
"search_page_your_activity": "Your activity",
|
||||
"search_page_videos": "Videot",
|
||||
"search_page_view_all_button": "Näytä kaikki",
|
||||
"search_page_your_activity": "Toimintasi",
|
||||
"search_result_page_new_search_hint": "Uusi haku",
|
||||
"search_suggestion_list_smart_search_hint_1": "Smart search is enabled by default, to search for metadata use the syntax ",
|
||||
"search_suggestion_list_smart_search_hint_2": "m:your-search-term",
|
||||
"search_suggestion_list_smart_search_hint_1": "Älykäs haku on oletuksena käytössä. Käytä metatietojen etsimiseen syntaksia",
|
||||
"search_suggestion_list_smart_search_hint_2": "m:hakusana",
|
||||
"select_additional_user_for_sharing_page_suggestions": "Ehdotukset",
|
||||
"select_user_for_sharing_page_err_album": "Albumin luonti epäonnistui",
|
||||
"select_user_for_sharing_page_share_suggestions": "Ehdotukset",
|
||||
"server_info_box_app_version": "App Version",
|
||||
"server_info_box_server_version": "Server Version",
|
||||
"server_info_box_app_version": "Sovelluksen versio",
|
||||
"server_info_box_server_version": "Palvelimen versio",
|
||||
"setting_image_viewer_help": "Sovellus lataa ensin pienen esikatselukuvan, toisena keskitarkkuuksisen kuvan (jos käytössä) ja kolmantena alkuperäisen täysitarkkuuksisen kuvan (jos käytössä)",
|
||||
"setting_image_viewer_original_subtitle": "Ota käyttöön ladataksesi alkuperäinen täysitarkkuuksinen kuva (suuri!). Poista käytöstä vähentääksesi datan käyttöä (sekä verkossa että laitteen välimuistissa).",
|
||||
"setting_image_viewer_original_title": "Lataa alkuperäinen kuva",
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
{
|
||||
"add_to_album_bottom_sheet_added": "Ajouté à {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Déjà dans {album}",
|
||||
"advanced_settings_tile_subtitle": "Advanced user's settings",
|
||||
"advanced_settings_tile_title": "Advanced",
|
||||
"advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
|
||||
"advanced_settings_troubleshooting_title": "Troubleshooting",
|
||||
"album_info_card_backup_album_excluded": "EXCLU",
|
||||
"album_info_card_backup_album_included": "INCLUS",
|
||||
"album_thumbnail_card_item": "1 élément",
|
||||
@@ -17,7 +21,10 @@
|
||||
"album_viewer_appbar_share_remove": "Retirer de l'album",
|
||||
"album_viewer_page_share_add_users": "Ajouter des utilisateurs",
|
||||
"all_videos_page_title": "Videos",
|
||||
"archive_page_no_archived_assets": "No archived assets found",
|
||||
"archive_page_title": "Archive ({})",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Affichage dynamique",
|
||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
||||
"asset_list_layout_settings_group_by": "Grouper les éléments par",
|
||||
"asset_list_layout_settings_group_by_month": "Mois",
|
||||
"asset_list_layout_settings_group_by_month_day": "Mois + jour",
|
||||
@@ -108,10 +115,12 @@
|
||||
"control_bottom_app_bar_add_to_album": "Ajouter à l'album",
|
||||
"control_bottom_app_bar_album_info": "{} éléments",
|
||||
"control_bottom_app_bar_album_info_shared": "{} éléments - Partagés",
|
||||
"control_bottom_app_bar_archive": "Archive",
|
||||
"control_bottom_app_bar_create_new_album": "Créer un nouvel album",
|
||||
"control_bottom_app_bar_delete": "Supprimer",
|
||||
"control_bottom_app_bar_favorite": "Favoris",
|
||||
"control_bottom_app_bar_share": "Partager",
|
||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
||||
"create_album_page_untitled": "Sans titre",
|
||||
"create_shared_album_page_create": "Créer",
|
||||
"create_shared_album_page_share": "Partager",
|
||||
@@ -126,6 +135,8 @@
|
||||
"delete_dialog_cancel": "Annuler",
|
||||
"delete_dialog_ok": "Supprimer",
|
||||
"delete_dialog_title": "Supprimer définitivement",
|
||||
"description_input_hint_text": "Add description...",
|
||||
"description_input_submit_error": "Error updating description, check the log for more details",
|
||||
"exif_bottom_sheet_description": "Ajouter une description...",
|
||||
"exif_bottom_sheet_details": "DÉTAILS",
|
||||
"exif_bottom_sheet_location": "LOCALISATION",
|
||||
@@ -133,16 +144,19 @@
|
||||
"experimental_settings_new_asset_list_title": "Activer la grille de photos expérimentale",
|
||||
"experimental_settings_subtitle": "Utilisez à vos dépends !",
|
||||
"experimental_settings_title": "Expérimental",
|
||||
"favorites_page_no_favorites": "No favorite assets found",
|
||||
"favorites_page_title": "Favoris",
|
||||
"home_page_add_to_album_conflicts": "{added} éléments ajoutés à l'album {album}. Les éléments {failed} sont déjà dans l'album.",
|
||||
"home_page_add_to_album_err_local": "Impossible d'ajouter des éléments locaux aux albums pour le moment, étape ignorée",
|
||||
"home_page_add_to_album_success": "{added} éléments ajoutés à l'album {album}.",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_building_timeline": "Construction de la chronologie",
|
||||
"home_page_favorite_err_local": "Impossible d'ajouter des éléments locaux aux favoris pour le moment, étape ignorée",
|
||||
"home_page_first_time_notice": "Si c'est la première fois que vous utilisez l'application, veillez à choisir un ou plusieurs albums de sauvegarde afin que la chronologie puisse alimenter les photos et les vidéos de cet ou ces albums.",
|
||||
"image_viewer_page_state_provider_download_error": "Erreur de téléchargement",
|
||||
"image_viewer_page_state_provider_download_success": "Téléchargement réussi",
|
||||
"library_page_albums": "Albums",
|
||||
"library_page_archive": "Archive",
|
||||
"library_page_device_albums": "Albums on Device",
|
||||
"library_page_favorites": "Favoris",
|
||||
"library_page_new_album": "Nouvel album",
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
{
|
||||
"add_to_album_bottom_sheet_added": "Aggiunto in {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Già presente in {album}",
|
||||
"advanced_settings_tile_subtitle": "Advanced user's settings",
|
||||
"advanced_settings_tile_title": "Advanced",
|
||||
"advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
|
||||
"advanced_settings_troubleshooting_title": "Troubleshooting",
|
||||
"album_info_card_backup_album_excluded": "ESCLUSI",
|
||||
"album_info_card_backup_album_included": "INCLUSI",
|
||||
"album_thumbnail_card_item": "1 elemento ",
|
||||
@@ -17,7 +21,10 @@
|
||||
"album_viewer_appbar_share_remove": "Rimuovere dall'album ",
|
||||
"album_viewer_page_share_add_users": "Aggiungi utenti",
|
||||
"all_videos_page_title": "Videos",
|
||||
"archive_page_no_archived_assets": "No archived assets found",
|
||||
"archive_page_title": "Archive ({})",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Layout dinamico",
|
||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
||||
"asset_list_layout_settings_group_by": "Raggruppa le immagini per",
|
||||
"asset_list_layout_settings_group_by_month": "Mese",
|
||||
"asset_list_layout_settings_group_by_month_day": "Mese + giorno",
|
||||
@@ -108,10 +115,12 @@
|
||||
"control_bottom_app_bar_add_to_album": "Aggiungi all'album",
|
||||
"control_bottom_app_bar_album_info": "{} elementi",
|
||||
"control_bottom_app_bar_album_info_shared": "{} elementi · Condivisi",
|
||||
"control_bottom_app_bar_archive": "Archive",
|
||||
"control_bottom_app_bar_create_new_album": "Crea nuovo album",
|
||||
"control_bottom_app_bar_delete": "Elimina",
|
||||
"control_bottom_app_bar_favorite": "Preferiti",
|
||||
"control_bottom_app_bar_share": "Condividi",
|
||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
||||
"create_album_page_untitled": "Senza titolo",
|
||||
"create_shared_album_page_create": "Crea",
|
||||
"create_shared_album_page_share": "Condividi",
|
||||
@@ -126,6 +135,8 @@
|
||||
"delete_dialog_cancel": "Annulla",
|
||||
"delete_dialog_ok": "Elimina",
|
||||
"delete_dialog_title": "Cancella definitivamente",
|
||||
"description_input_hint_text": "Add description...",
|
||||
"description_input_submit_error": "Error updating description, check the log for more details",
|
||||
"exif_bottom_sheet_description": "Aggiungi una descrizione...",
|
||||
"exif_bottom_sheet_details": "DETTAGLI",
|
||||
"exif_bottom_sheet_location": "POSIZIONE",
|
||||
@@ -133,16 +144,19 @@
|
||||
"experimental_settings_new_asset_list_title": "Attiva griglia di foto sperimentale",
|
||||
"experimental_settings_subtitle": "Usalo a tuo rischio!",
|
||||
"experimental_settings_title": "Sperimentale",
|
||||
"favorites_page_no_favorites": "No favorite assets found",
|
||||
"favorites_page_title": "Preferiti",
|
||||
"home_page_add_to_album_conflicts": "Aggiunti {added} elementi all'album {album}. {failed} elementi erano già presenti nell'album.",
|
||||
"home_page_add_to_album_err_local": "Non puoi aggiungere negli album foto ancora non caricate",
|
||||
"home_page_add_to_album_success": "Aggiunti {added} elementi all'album {album}",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_building_timeline": "Costruendo il Timeline",
|
||||
"home_page_favorite_err_local": "Non puoi aggiungere tra i preferiti le foto ancora non caricate",
|
||||
"home_page_first_time_notice": "Se è la prima volta che usi l'app, assicurati di scegliere gli album per avere il Timeline con immagini e video",
|
||||
"image_viewer_page_state_provider_download_error": "Errore nel Download",
|
||||
"image_viewer_page_state_provider_download_success": "Download con successo",
|
||||
"library_page_albums": "Album",
|
||||
"library_page_archive": "Archive",
|
||||
"library_page_device_albums": "Albums on Device",
|
||||
"library_page_favorites": "Preferiti",
|
||||
"library_page_new_album": "Nuovo Album",
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
{
|
||||
"add_to_album_bottom_sheet_added": "{album}に追加しました",
|
||||
"add_to_album_bottom_sheet_already_exists": "{album}にもう存在してます",
|
||||
"advanced_settings_tile_subtitle": "Advanced user's settings",
|
||||
"advanced_settings_tile_title": "Advanced",
|
||||
"advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
|
||||
"advanced_settings_troubleshooting_title": "Troubleshooting",
|
||||
"album_info_card_backup_album_excluded": "除外中",
|
||||
"album_info_card_backup_album_included": "選択中",
|
||||
"album_thumbnail_card_item": "項目数: 1",
|
||||
@@ -17,7 +21,10 @@
|
||||
"album_viewer_appbar_share_remove": "アルバムから除外",
|
||||
"album_viewer_page_share_add_users": "ユーザーを追加",
|
||||
"all_videos_page_title": "Videos",
|
||||
"archive_page_no_archived_assets": "No archived assets found",
|
||||
"archive_page_title": "Archive ({})",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "ダイナミックレイアウト",
|
||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
||||
"asset_list_layout_settings_group_by": "写真をグループ分けする方法:",
|
||||
"asset_list_layout_settings_group_by_month": "月",
|
||||
"asset_list_layout_settings_group_by_month_day": "月+日",
|
||||
@@ -108,10 +115,12 @@
|
||||
"control_bottom_app_bar_add_to_album": "アルバムに追加",
|
||||
"control_bottom_app_bar_album_info": "{}枚の写真",
|
||||
"control_bottom_app_bar_album_info_shared": "{}枚の共有中の写真",
|
||||
"control_bottom_app_bar_archive": "Archive",
|
||||
"control_bottom_app_bar_create_new_album": "新しいアルバムを作成",
|
||||
"control_bottom_app_bar_delete": "削除",
|
||||
"control_bottom_app_bar_favorite": "お気に入り",
|
||||
"control_bottom_app_bar_share": "共有",
|
||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
||||
"create_album_page_untitled": "タイトル無し",
|
||||
"create_shared_album_page_create": "作成",
|
||||
"create_shared_album_page_share": "共有",
|
||||
@@ -126,6 +135,8 @@
|
||||
"delete_dialog_cancel": "キャンセル",
|
||||
"delete_dialog_ok": "削除",
|
||||
"delete_dialog_title": "永久的に削除",
|
||||
"description_input_hint_text": "Add description...",
|
||||
"description_input_submit_error": "Error updating description, check the log for more details",
|
||||
"exif_bottom_sheet_description": "概要を追加",
|
||||
"exif_bottom_sheet_details": "詳細な情報",
|
||||
"exif_bottom_sheet_location": "撮影地",
|
||||
@@ -133,16 +144,19 @@
|
||||
"experimental_settings_new_asset_list_title": "試験的なグリッドを有効",
|
||||
"experimental_settings_subtitle": "試験的だから自己責任でね",
|
||||
"experimental_settings_title": "試験的",
|
||||
"favorites_page_no_favorites": "No favorite assets found",
|
||||
"favorites_page_title": "お気に入り",
|
||||
"home_page_add_to_album_conflicts": "{album}に{added}枚写真を追加しました。{failed}枚の写真は常に存在してたよ",
|
||||
"home_page_add_to_album_err_local": "まだアップロードされてない写真はアルバムに登録できないよ",
|
||||
"home_page_add_to_album_success": "{album}に{added}枚写真を追加しました",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_building_timeline": "タイムラインを構築中",
|
||||
"home_page_favorite_err_local": "まだアップロードされてない写真はお気に入り登録できないよ",
|
||||
"home_page_first_time_notice": "アプリを使うのがはじめての場合タイムラインに写真を表示するためにアルバムを選択してね",
|
||||
"image_viewer_page_state_provider_download_error": "ダウンロードエラー",
|
||||
"image_viewer_page_state_provider_download_success": "ダウンロードできました",
|
||||
"library_page_albums": "アルバム",
|
||||
"library_page_archive": "Archive",
|
||||
"library_page_device_albums": "Albums on Device",
|
||||
"library_page_favorites": "お気に入り",
|
||||
"library_page_new_album": "新しいアルバム",
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
{
|
||||
"add_to_album_bottom_sheet_added": "{album}에 추가",
|
||||
"add_to_album_bottom_sheet_already_exists": "{album}에 이미 포함되어 있습니다",
|
||||
"advanced_settings_tile_subtitle": "Advanced user's settings",
|
||||
"advanced_settings_tile_title": "Advanced",
|
||||
"advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
|
||||
"advanced_settings_troubleshooting_title": "Troubleshooting",
|
||||
"album_info_card_backup_album_excluded": "제외됨",
|
||||
"album_info_card_backup_album_included": "포함됨",
|
||||
"album_thumbnail_card_item": "1개 항목",
|
||||
@@ -17,7 +21,10 @@
|
||||
"album_viewer_appbar_share_remove": "앨범에서 제거",
|
||||
"album_viewer_page_share_add_users": "사용자 추가",
|
||||
"all_videos_page_title": "Videos",
|
||||
"archive_page_no_archived_assets": "No archived assets found",
|
||||
"archive_page_title": "Archive ({})",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "다이나믹 레이아웃",
|
||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
||||
"asset_list_layout_settings_group_by": "다음으로 그룹화",
|
||||
"asset_list_layout_settings_group_by_month": "월",
|
||||
"asset_list_layout_settings_group_by_month_day": "월 + 일",
|
||||
@@ -108,10 +115,12 @@
|
||||
"control_bottom_app_bar_add_to_album": "앨범에 추가",
|
||||
"control_bottom_app_bar_album_info": "{} 항목",
|
||||
"control_bottom_app_bar_album_info_shared": "{} 항목 · 공유됨",
|
||||
"control_bottom_app_bar_archive": "Archive",
|
||||
"control_bottom_app_bar_create_new_album": "앨범 생성",
|
||||
"control_bottom_app_bar_delete": "삭제",
|
||||
"control_bottom_app_bar_favorite": "즐겨찾기",
|
||||
"control_bottom_app_bar_share": "공유",
|
||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
||||
"create_album_page_untitled": "제목없음",
|
||||
"create_shared_album_page_create": "만들기",
|
||||
"create_shared_album_page_share": "공유",
|
||||
@@ -126,6 +135,8 @@
|
||||
"delete_dialog_cancel": "취소",
|
||||
"delete_dialog_ok": "삭제",
|
||||
"delete_dialog_title": "영구적으로 삭제",
|
||||
"description_input_hint_text": "Add description...",
|
||||
"description_input_submit_error": "Error updating description, check the log for more details",
|
||||
"exif_bottom_sheet_description": "설명 추가...",
|
||||
"exif_bottom_sheet_details": "상세정보",
|
||||
"exif_bottom_sheet_location": "위치",
|
||||
@@ -133,16 +144,19 @@
|
||||
"experimental_settings_new_asset_list_title": "실험적 사진 그리드 적용",
|
||||
"experimental_settings_subtitle": "문제시 책임지지 않습니다!",
|
||||
"experimental_settings_title": "실험적기능",
|
||||
"favorites_page_no_favorites": "No favorite assets found",
|
||||
"favorites_page_title": "즐겨찾기",
|
||||
"home_page_add_to_album_conflicts": "{album} 앨범에 {added} 미디어를 추가했습니다. {failed} 이미 앨범에 있는 항목입니다.",
|
||||
"home_page_add_to_album_err_local": "앨범에 미디어파일을 추가할 수 없어, 건너뜁니다.",
|
||||
"home_page_add_to_album_success": "{album} 앨범에 {added} 미디어를 추가했습니다. ",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_building_timeline": "타임라인 생성",
|
||||
"home_page_favorite_err_local": "미디어파일을 즐겨찾기에 추가할 수 없어, 건너뜁니다.",
|
||||
"home_page_first_time_notice": "앱을 처음 사용하는 경우 타임라인이 앨범의 사진과 비디오를 채울 수 있도록 백업대상 앨범을 선택해야 합니다.",
|
||||
"image_viewer_page_state_provider_download_error": "다운로드 에러",
|
||||
"image_viewer_page_state_provider_download_success": "다운로드 완료",
|
||||
"library_page_albums": "앨범",
|
||||
"library_page_archive": "Archive",
|
||||
"library_page_device_albums": "Albums on Device",
|
||||
"library_page_favorites": "즐겨찾기",
|
||||
"library_page_new_album": "새 앨범",
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
{
|
||||
"add_to_album_bottom_sheet_added": "Lagt til i {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Allerede i {album}",
|
||||
"advanced_settings_tile_subtitle": "Avanserte brukerinnstillinger",
|
||||
"advanced_settings_tile_title": "Avansert",
|
||||
"advanced_settings_troubleshooting_subtitle": "Aktiver ekstra funksjoner for feilsøking",
|
||||
"advanced_settings_troubleshooting_title": "Feilsøking",
|
||||
"album_info_card_backup_album_excluded": "EKSKLUDERT",
|
||||
"album_info_card_backup_album_included": "INKLUDERT",
|
||||
"album_thumbnail_card_item": "1 objekt",
|
||||
"album_thumbnail_card_items": "{} objekter",
|
||||
"album_thumbnail_card_shared": "Delt",
|
||||
"album_thumbnail_owned": "Owned",
|
||||
"album_thumbnail_shared_by": "Shared by {}",
|
||||
"album_thumbnail_owned": "Eid",
|
||||
"album_thumbnail_shared_by": "Delt av {}",
|
||||
"album_viewer_appbar_share_delete": "Slett album",
|
||||
"album_viewer_appbar_share_err_delete": "Feilet ved sletting av album",
|
||||
"album_viewer_appbar_share_err_leave": "Kunne ikke forlate albumet",
|
||||
@@ -16,8 +20,11 @@
|
||||
"album_viewer_appbar_share_leave": "Forlat album",
|
||||
"album_viewer_appbar_share_remove": "Fjern fra album",
|
||||
"album_viewer_page_share_add_users": "Legg til brukere",
|
||||
"all_videos_page_title": "Videos",
|
||||
"all_videos_page_title": "Videoer",
|
||||
"archive_page_no_archived_assets": "No archived assets found",
|
||||
"archive_page_title": "Arkiv ({})",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Dynamisk layout",
|
||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
||||
"asset_list_layout_settings_group_by": "Grupper bilder etter",
|
||||
"asset_list_layout_settings_group_by_month": "Måned",
|
||||
"asset_list_layout_settings_group_by_month_day": "Måned + dag",
|
||||
@@ -103,22 +110,24 @@
|
||||
"common_add_to_album": "Legg til i album",
|
||||
"common_change_password": "Endre passord",
|
||||
"common_create_new_album": "Lag nytt album",
|
||||
"common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.",
|
||||
"common_server_error": "Sjekk nettverkstilkobling, vær sikker på at serveren er mulig å nå og at app/server-versjoner er kompatible.",
|
||||
"common_shared": "Delt",
|
||||
"control_bottom_app_bar_add_to_album": "Legg til i album",
|
||||
"control_bottom_app_bar_album_info": "{} objekter",
|
||||
"control_bottom_app_bar_album_info_shared": "{} objekter · Delt",
|
||||
"control_bottom_app_bar_archive": "Arkiver",
|
||||
"control_bottom_app_bar_create_new_album": "Lag nytt album",
|
||||
"control_bottom_app_bar_delete": "Slett",
|
||||
"control_bottom_app_bar_favorite": "Favoritt",
|
||||
"control_bottom_app_bar_share": "Del",
|
||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
||||
"create_album_page_untitled": "Uten navn",
|
||||
"create_shared_album_page_create": "Opprett",
|
||||
"create_shared_album_page_share": "Del",
|
||||
"create_shared_album_page_share_add_assets": "LEGG TIL OBJEKTER",
|
||||
"create_shared_album_page_share_select_photos": "Velg bilder",
|
||||
"curated_location_page_title": "Places",
|
||||
"curated_object_page_title": "Things",
|
||||
"curated_location_page_title": "Plasseringer",
|
||||
"curated_object_page_title": "Ting",
|
||||
"daily_title_text_date": "E, MMM dd",
|
||||
"daily_title_text_date_year": "E, MMM dd, yyyy",
|
||||
"date_format": "E, LLL d, y • h:mm a",
|
||||
@@ -126,6 +135,8 @@
|
||||
"delete_dialog_cancel": "Avbryt",
|
||||
"delete_dialog_ok": "Slett",
|
||||
"delete_dialog_title": "Slett permanent",
|
||||
"description_input_hint_text": "Legg til beskrivelse...",
|
||||
"description_input_submit_error": "Feil ved oppdatering av beskrivelse, sjekk loggen for flere detaljer",
|
||||
"exif_bottom_sheet_description": "Legg til beskrivelse...",
|
||||
"exif_bottom_sheet_details": "DETALJER",
|
||||
"exif_bottom_sheet_location": "PLASSERING",
|
||||
@@ -133,23 +144,26 @@
|
||||
"experimental_settings_new_asset_list_title": "Aktiver eksperimentell grid-visning",
|
||||
"experimental_settings_subtitle": "Bruk på egen risiko!",
|
||||
"experimental_settings_title": "Eksperimentelt",
|
||||
"favorites_page_no_favorites": "No favorite assets found",
|
||||
"favorites_page_title": "Favoritter",
|
||||
"home_page_add_to_album_conflicts": "Lagt til {added} objekter til album {album}. {failed} objekter er allerede i albumet.",
|
||||
"home_page_add_to_album_err_local": "Kan ikke legge til lokale objekter til album enda, hopper over",
|
||||
"home_page_add_to_album_success": "Lagt til {added} objekter til album {album}.",
|
||||
"home_page_archive_err_local": "Kan ikke arkivere lokale objekter enda, hopper over",
|
||||
"home_page_building_timeline": "Genererer tidslinjen",
|
||||
"home_page_favorite_err_local": "Kan ikke sette favoritt på lokale objekter enda, hopper over",
|
||||
"home_page_first_time_notice": "Hvis dette er første gangen du benytter appen, så velg ett album(eller flere) slik at tidslinjen kan genereres med dine bilder og videoer.",
|
||||
"image_viewer_page_state_provider_download_error": "Nedlasting feilet",
|
||||
"image_viewer_page_state_provider_download_success": "Nedlasting vellykket",
|
||||
"library_page_albums": "Albumer",
|
||||
"library_page_device_albums": "Albums on Device",
|
||||
"library_page_archive": "Arkiv",
|
||||
"library_page_device_albums": "Albumer på enheten",
|
||||
"library_page_favorites": "Favoritter",
|
||||
"library_page_new_album": "Nytt album",
|
||||
"library_page_sharing": "Deling",
|
||||
"library_page_sort_created": "Nylig opplastet",
|
||||
"library_page_sort_title": "Album tittel",
|
||||
"login_form_api_exception": "API exception. Please check the server URL and try again.",
|
||||
"login_form_api_exception": "API feil. Sjekk server URL og prøv igjen.",
|
||||
"login_form_button_text": "Logg inn",
|
||||
"login_form_email_hint": "dinepost@epost.no",
|
||||
"login_form_endpoint_hint": "http://din-server-ip:port/api",
|
||||
@@ -164,59 +178,59 @@
|
||||
"login_form_failed_login": "Feil ved innlogging, sjekk server URL, epost og passord",
|
||||
"login_form_label_email": "Epostadresse",
|
||||
"login_form_label_password": "Passord",
|
||||
"login_form_next_button": "Next",
|
||||
"login_form_next_button": "Neste",
|
||||
"login_form_password_hint": "passord",
|
||||
"login_form_save_login": "Forbli innlogget",
|
||||
"login_form_server_empty": "Enter a server URL.",
|
||||
"login_form_server_error": "Could not connect to server.",
|
||||
"login_form_server_empty": "Skriv inn en server URL.",
|
||||
"login_form_server_error": "Kan ikke koble til server.",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"motion_photos_page_title": "Motion Photos",
|
||||
"motion_photos_page_title": "Bevegelige bilder",
|
||||
"notification_permission_dialog_cancel": "Avbryt",
|
||||
"notification_permission_dialog_content": "For å aktivere notifikasjoner, gå til Innstillinger og velg tillat.",
|
||||
"notification_permission_dialog_settings": "Innstillinger",
|
||||
"notification_permission_list_tile_content": "Tillat tilgang for å aktivere notifikasjoner",
|
||||
"notification_permission_list_tile_enable_button": "Aktiver notifikasjoner",
|
||||
"notification_permission_list_tile_title": "Notifikasjonstilgang",
|
||||
"permission_onboarding_continue_anyway": "Continue anyway",
|
||||
"permission_onboarding_get_started": "Get started",
|
||||
"permission_onboarding_go_to_settings": "Go to settings",
|
||||
"permission_onboarding_grant_permission": "Grant permission",
|
||||
"permission_onboarding_log_out": "Log out",
|
||||
"permission_onboarding_permission_denied": "Permission denied. To use Immich, grant photo and video permissions in Settings.",
|
||||
"permission_onboarding_permission_granted": "Permission granted! You are all set.",
|
||||
"permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.",
|
||||
"permission_onboarding_request": "Immich requires permission to view your photos and videos.",
|
||||
"permission_onboarding_continue_anyway": "Fortsett uansett",
|
||||
"permission_onboarding_get_started": "Kom i gang",
|
||||
"permission_onboarding_go_to_settings": "Gå til innstillinger",
|
||||
"permission_onboarding_grant_permission": "Gi tilgang",
|
||||
"permission_onboarding_log_out": "Logg ut",
|
||||
"permission_onboarding_permission_denied": "Tilgang avvist. For å bruke Immich, tillat å vise bilde og videoer i Innstillinger.",
|
||||
"permission_onboarding_permission_granted": "Tilgang gitt! Du er i gang.",
|
||||
"permission_onboarding_permission_limited": "Tilgang begrenset. For å la Immich ta backup og håndtere galleriet, tillatt bilde og video-tilgang i Innstillinger.",
|
||||
"permission_onboarding_request": "Immich trenger tilgang til å se dine bilder og videoer",
|
||||
"profile_drawer_app_logs": "Logg",
|
||||
"profile_drawer_client_server_up_to_date": "Klient og Server er oppdatert",
|
||||
"profile_drawer_settings": "Innstillinger",
|
||||
"profile_drawer_sign_out": "Logg ut",
|
||||
"recently_added_page_title": "Recently Added",
|
||||
"recently_added_page_title": "Nylig lagt til",
|
||||
"search_bar_hint": "Søk i dine bilder",
|
||||
"search_page_categories": "Categories",
|
||||
"search_page_favorites": "Favorites",
|
||||
"search_page_motion_photos": "Motion Photos",
|
||||
"search_page_categories": "Kategorier",
|
||||
"search_page_favorites": "Favoritter",
|
||||
"search_page_motion_photos": "Bevegelige bilder",
|
||||
"search_page_no_objects": "Ingen objektinfo tilgjengelig",
|
||||
"search_page_no_places": "Ingen plasseringsinfo tilgjengelig",
|
||||
"search_page_places": "Plasser",
|
||||
"search_page_recently_added": "Recently added",
|
||||
"search_page_screenshots": "Screenshots",
|
||||
"search_page_selfies": "Selfies",
|
||||
"search_page_recently_added": "Nylig lagt til",
|
||||
"search_page_screenshots": "Skjermbilder",
|
||||
"search_page_selfies": "Selfier",
|
||||
"search_page_things": "Ting",
|
||||
"search_page_videos": "Videos",
|
||||
"search_page_view_all_button": "View all",
|
||||
"search_page_your_activity": "Your activity",
|
||||
"search_page_videos": "Videoer",
|
||||
"search_page_view_all_button": "Vis alle",
|
||||
"search_page_your_activity": "Din aktivitet",
|
||||
"search_result_page_new_search_hint": "Nytt søk",
|
||||
"search_suggestion_list_smart_search_hint_1": "Smart search is enabled by default, to search for metadata use the syntax ",
|
||||
"search_suggestion_list_smart_search_hint_2": "m:your-search-term",
|
||||
"search_suggestion_list_smart_search_hint_1": "Smartsøk er aktivert som standard, for å søke etter metadata bruk syntaks ",
|
||||
"search_suggestion_list_smart_search_hint_2": "m:ditt-søkeord",
|
||||
"select_additional_user_for_sharing_page_suggestions": "Forslag",
|
||||
"select_user_for_sharing_page_err_album": "Feilet ved oppretting av album",
|
||||
"select_user_for_sharing_page_share_suggestions": "Forslag",
|
||||
"server_info_box_app_version": "App versjon",
|
||||
"server_info_box_server_version": "Server versjon",
|
||||
"setting_image_viewer_help": "Først lastes mikrobilder, deretter middels-oppløsningbildet (hvis aktivert), til slutt lastes original (hvis aktivert).",
|
||||
"setting_image_viewer_help": "Først lastes mikrobilder, deretter forhåndsvisningsbildet (hvis aktivert), til slutt lastes original (hvis aktivert).",
|
||||
"setting_image_viewer_original_subtitle": "Aktiver for å laste originalbildet i full oppløsning (Stort!). Deaktiver for å spare databruk (både nettverksbruk og bufferdata på enheten).",
|
||||
"setting_image_viewer_original_title": "Last originalbildet",
|
||||
"setting_image_viewer_preview_subtitle": "Aktiver for å laste ett bilde av middels-oppløsning. Deaktiver for å enten direkte laste inn originalen eller kun benytte miniatyrbilde.",
|
||||
"setting_image_viewer_preview_subtitle": "Aktiver for å laste ett bilde av medium oppløsning. Deaktiver for å enten direkte laste inn originalen eller kun benytte miniatyrbilde.",
|
||||
"setting_image_viewer_preview_title": "Last forhåndsvisningsbilde",
|
||||
"setting_notifications_notify_failures_grace_period": "Varsle om sikkerhetskopieringsfeil i bakgrunnen: {}",
|
||||
"setting_notifications_notify_hours": "{} timer",
|
||||
@@ -251,7 +265,7 @@
|
||||
"theme_setting_asset_list_tiles_per_row_title": "Antall ressurser per rad ({})",
|
||||
"theme_setting_dark_mode_switch": "Mørk modus",
|
||||
"theme_setting_image_viewer_quality_subtitle": "Juster kvaliteten på detaljer med bildeviser",
|
||||
"theme_setting_image_viewer_quality_title": "Bilderviser-kvalitet",
|
||||
"theme_setting_image_viewer_quality_title": "Kvalitet på bildevisning",
|
||||
"theme_setting_system_theme_switch": "Automatisk (følg system)",
|
||||
"theme_setting_theme_subtitle": "Velg app'ens temainnstilling",
|
||||
"theme_setting_theme_title": "Tema",
|
||||
@@ -261,6 +275,6 @@
|
||||
"version_announcement_overlay_release_notes": "Endringslogg",
|
||||
"version_announcement_overlay_text_1": "Hei, det er en ny versjon av",
|
||||
"version_announcement_overlay_text_2": "vennligst ta deg tid til å besøke",
|
||||
"version_announcement_overlay_text_3": "og verifiser at din docker-compose og .env oppsett er oppdatert for å forhindre en eventuell miskonfigurasjon. Spesielt hvis du benytter WatchTower eller en annen tjeneste som håndterer oppdatering av applikasjoner på serveren automatisk.",
|
||||
"version_announcement_overlay_text_3": " og verifiser at din docker-compose og .env oppsett er oppdatert for å forhindre en eventuell miskonfigurasjon. Spesielt hvis du benytter WatchTower eller en annen tjeneste som håndterer oppdatering av applikasjoner på serveren automatisk.",
|
||||
"version_announcement_overlay_title": "Ny serverversjon tilgjengelig"
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
{
|
||||
"add_to_album_bottom_sheet_added": "Toegevoegd aan {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Staat al in {album}",
|
||||
"advanced_settings_tile_subtitle": "Geavanceerde gebruikersinstellingen",
|
||||
"advanced_settings_tile_title": "Geavanceerd",
|
||||
"advanced_settings_troubleshooting_subtitle": "Schakel extra functies in voor probleemoplossing",
|
||||
"advanced_settings_troubleshooting_title": "Probleemoplossing",
|
||||
"album_info_card_backup_album_excluded": "UITGESLOTEN",
|
||||
"album_info_card_backup_album_included": "INGESLOTEN",
|
||||
"album_thumbnail_card_item": "1 item",
|
||||
@@ -17,7 +21,10 @@
|
||||
"album_viewer_appbar_share_remove": "Verwijder uit album",
|
||||
"album_viewer_page_share_add_users": "Gebruikers toevoegen",
|
||||
"all_videos_page_title": "Video's",
|
||||
"archive_page_no_archived_assets": "No archived assets found",
|
||||
"archive_page_title": "Archief ({})",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Dynamische layout",
|
||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
||||
"asset_list_layout_settings_group_by": "Groupeer items per",
|
||||
"asset_list_layout_settings_group_by_month": "Maand",
|
||||
"asset_list_layout_settings_group_by_month_day": "Maand + dag",
|
||||
@@ -108,10 +115,12 @@
|
||||
"control_bottom_app_bar_add_to_album": "Toevoegen aan album",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Gedeeld",
|
||||
"control_bottom_app_bar_archive": "Archiveren",
|
||||
"control_bottom_app_bar_create_new_album": "Maak nieuw album",
|
||||
"control_bottom_app_bar_delete": "Verwijderen",
|
||||
"control_bottom_app_bar_favorite": "Favoriet",
|
||||
"control_bottom_app_bar_share": "Delen",
|
||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
||||
"create_album_page_untitled": "Naamloos",
|
||||
"create_shared_album_page_create": "Aanmaken",
|
||||
"create_shared_album_page_share": "Delen",
|
||||
@@ -126,6 +135,8 @@
|
||||
"delete_dialog_cancel": "Annuleren",
|
||||
"delete_dialog_ok": "Verwijderen",
|
||||
"delete_dialog_title": "Permanent verwijderen",
|
||||
"description_input_hint_text": "Beschrijving toevoegen...",
|
||||
"description_input_submit_error": "Beschrijving bijwerken mislukt, controleer het logboek voor meer details",
|
||||
"exif_bottom_sheet_description": "Beschrijving toevoegen...",
|
||||
"exif_bottom_sheet_details": "DETAILS",
|
||||
"exif_bottom_sheet_location": "LOCATIE",
|
||||
@@ -133,16 +144,19 @@
|
||||
"experimental_settings_new_asset_list_title": "Experimenteel foto grid inschakelen",
|
||||
"experimental_settings_subtitle": "Gebruik op eigen risico!",
|
||||
"experimental_settings_title": "Experimenteel",
|
||||
"favorites_page_no_favorites": "No favorite assets found",
|
||||
"favorites_page_title": "Favorieten",
|
||||
"home_page_add_to_album_conflicts": "{added} items toegevoegd aan album {album}. {failed} items staan al in het album.",
|
||||
"home_page_add_to_album_err_local": "Lokale items kunnen nog niet aan albums worden toegevoegd, overslaan",
|
||||
"home_page_add_to_album_success": "{added} items toegevoegd aan album {album}.",
|
||||
"home_page_archive_err_local": "Lokale items kunnen nog niet gearchiveerd worden, overslaan",
|
||||
"home_page_building_timeline": "Tijdlijn opbouwen",
|
||||
"home_page_favorite_err_local": "Lokale items kunnen nog niet als favoriet worden aangemerkt, overslaan",
|
||||
"home_page_first_time_notice": "Als dit de eerste keer is dat je de app gebruikt, zorg er dan voor dat je een back-up album kiest, zodat de tijdlijn gevuld kan worden met foto's en video's uit het album.",
|
||||
"image_viewer_page_state_provider_download_error": "Download mislukt",
|
||||
"image_viewer_page_state_provider_download_success": "Download succesvol",
|
||||
"library_page_albums": "Albums",
|
||||
"library_page_archive": "Archief",
|
||||
"library_page_device_albums": "Albums op apparaat",
|
||||
"library_page_favorites": "Favorieten",
|
||||
"library_page_new_album": "Nieuw album",
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
{
|
||||
"add_to_album_bottom_sheet_added": "Added to {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
|
||||
"advanced_settings_tile_subtitle": "Advanced user's settings",
|
||||
"advanced_settings_tile_title": "Advanced",
|
||||
"advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
|
||||
"advanced_settings_troubleshooting_title": "Troubleshooting",
|
||||
"album_info_card_backup_album_excluded": "WYKLUCZONE",
|
||||
"album_info_card_backup_album_included": "WŁĄCZONE",
|
||||
"album_thumbnail_card_item": "1 pozycja",
|
||||
@@ -17,7 +21,10 @@
|
||||
"album_viewer_appbar_share_remove": "Usuń z albumu",
|
||||
"album_viewer_page_share_add_users": "Dodaj użytkowników",
|
||||
"all_videos_page_title": "Videos",
|
||||
"archive_page_no_archived_assets": "No archived assets found",
|
||||
"archive_page_title": "Archive ({})",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Dynamic layout",
|
||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
||||
"asset_list_layout_settings_group_by": "Group assets by",
|
||||
"asset_list_layout_settings_group_by_month": "Month",
|
||||
"asset_list_layout_settings_group_by_month_day": "Month + day",
|
||||
@@ -108,10 +115,12 @@
|
||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"control_bottom_app_bar_archive": "Archive",
|
||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
||||
"control_bottom_app_bar_delete": "Usuń",
|
||||
"control_bottom_app_bar_favorite": "Favorite",
|
||||
"control_bottom_app_bar_share": "Udostępnij",
|
||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
||||
"create_album_page_untitled": "Bez tytułu",
|
||||
"create_shared_album_page_create": "Utwórz",
|
||||
"create_shared_album_page_share": "Udostępnij",
|
||||
@@ -126,6 +135,8 @@
|
||||
"delete_dialog_cancel": "Anuluj",
|
||||
"delete_dialog_ok": "Usuń",
|
||||
"delete_dialog_title": "Usuń trwale",
|
||||
"description_input_hint_text": "Add description...",
|
||||
"description_input_submit_error": "Error updating description, check the log for more details",
|
||||
"exif_bottom_sheet_description": "Dodaj Opis...",
|
||||
"exif_bottom_sheet_details": "SZCZEGÓŁY",
|
||||
"exif_bottom_sheet_location": "LOKALIZACJA",
|
||||
@@ -133,16 +144,19 @@
|
||||
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
|
||||
"experimental_settings_subtitle": "Use at your own risk!",
|
||||
"experimental_settings_title": "Experimental",
|
||||
"favorites_page_no_favorites": "No favorite assets found",
|
||||
"favorites_page_title": "Favorites",
|
||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_building_timeline": "Building the timeline",
|
||||
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
|
||||
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
|
||||
"image_viewer_page_state_provider_download_error": "Download Error",
|
||||
"image_viewer_page_state_provider_download_success": "Download Success",
|
||||
"library_page_albums": "Albumy",
|
||||
"library_page_archive": "Archive",
|
||||
"library_page_device_albums": "Albums on Device",
|
||||
"library_page_favorites": "Favorites",
|
||||
"library_page_new_album": "Nowy album",
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
{
|
||||
"add_to_album_bottom_sheet_added": "Добавлено в {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Уже в {album}",
|
||||
"advanced_settings_tile_subtitle": "Advanced user's settings",
|
||||
"advanced_settings_tile_title": "Advanced",
|
||||
"advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
|
||||
"advanced_settings_troubleshooting_title": "Troubleshooting",
|
||||
"album_info_card_backup_album_excluded": "ИСКЛЮЧЕН",
|
||||
"album_info_card_backup_album_included": "ВКЛЮЧЕН",
|
||||
"album_thumbnail_card_item": "1 объект",
|
||||
@@ -17,7 +21,10 @@
|
||||
"album_viewer_appbar_share_remove": "Удалить из альбома",
|
||||
"album_viewer_page_share_add_users": "Добавить пользователей",
|
||||
"all_videos_page_title": "Videos",
|
||||
"archive_page_no_archived_assets": "No archived assets found",
|
||||
"archive_page_title": "Archive ({})",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Динамическое расположение",
|
||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
||||
"asset_list_layout_settings_group_by": "Группировать объекты по",
|
||||
"asset_list_layout_settings_group_by_month": "месяцу",
|
||||
"asset_list_layout_settings_group_by_month_day": "месяцу и дню",
|
||||
@@ -108,10 +115,12 @@
|
||||
"control_bottom_app_bar_add_to_album": "Добавить в альбом",
|
||||
"control_bottom_app_bar_album_info": "{} файлов",
|
||||
"control_bottom_app_bar_album_info_shared": "{} файлов · Общий",
|
||||
"control_bottom_app_bar_archive": "Archive",
|
||||
"control_bottom_app_bar_create_new_album": "\nСоздать новый альбом",
|
||||
"control_bottom_app_bar_delete": "Удалить",
|
||||
"control_bottom_app_bar_favorite": "Избранное",
|
||||
"control_bottom_app_bar_share": "Поделиться",
|
||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
||||
"create_album_page_untitled": "Без названия",
|
||||
"create_shared_album_page_create": "Создать",
|
||||
"create_shared_album_page_share": "Поделиться",
|
||||
@@ -126,6 +135,8 @@
|
||||
"delete_dialog_cancel": "Отменить",
|
||||
"delete_dialog_ok": "Удалить",
|
||||
"delete_dialog_title": "Удалить навсегда",
|
||||
"description_input_hint_text": "Add description...",
|
||||
"description_input_submit_error": "Error updating description, check the log for more details",
|
||||
"exif_bottom_sheet_description": "Добавить описание...",
|
||||
"exif_bottom_sheet_details": "ПОДРОБНОСТИ",
|
||||
"exif_bottom_sheet_location": "МЕСТОПОЛОЖЕНИЕ",
|
||||
@@ -133,16 +144,19 @@
|
||||
"experimental_settings_new_asset_list_title": "Включить экспериментальную сетку фотографий",
|
||||
"experimental_settings_subtitle": "Используйте на свой страх и риск!",
|
||||
"experimental_settings_title": "Экспериментальные функции",
|
||||
"favorites_page_no_favorites": "No favorite assets found",
|
||||
"favorites_page_title": "Избранное",
|
||||
"home_page_add_to_album_conflicts": "Добавлено {added} объектов в альбом {album}. Объекты {failed} уже есть в альбоме.",
|
||||
"home_page_add_to_album_err_local": "Пока нельзя добавлять локальные объекты в альбомы, пропускаем",
|
||||
"home_page_add_to_album_success": "Добавлено {added} объектов в альбом {album}.",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_building_timeline": "Построение временной шкалы",
|
||||
"home_page_favorite_err_local": "Пока не удается добавить в избранное локальные объекты, пропускаем",
|
||||
"home_page_first_time_notice": "Если вы используете приложение впервые, убедитесь, что вы выбрали резервный(е) альбом(ы), чтобы временная шкала могла заполнить фотографии и видео в альбоме(ах).",
|
||||
"image_viewer_page_state_provider_download_error": "Ошибка загрузки",
|
||||
"image_viewer_page_state_provider_download_success": "Успешно загружено",
|
||||
"library_page_albums": "Альбомы",
|
||||
"library_page_archive": "Archive",
|
||||
"library_page_device_albums": "Albums on Device",
|
||||
"library_page_favorites": "Избранное",
|
||||
"library_page_new_album": "Новый альбом",
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
{
|
||||
"add_to_album_bottom_sheet_added": "Pridané do {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Už v {album}",
|
||||
"advanced_settings_tile_subtitle": "Advanced user's settings",
|
||||
"advanced_settings_tile_title": "Advanced",
|
||||
"advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
|
||||
"advanced_settings_troubleshooting_title": "Troubleshooting",
|
||||
"album_info_card_backup_album_excluded": "VYLÚČENÉ",
|
||||
"album_info_card_backup_album_included": "ZAHRNUTÉ",
|
||||
"album_thumbnail_card_item": "1 položka",
|
||||
@@ -17,7 +21,10 @@
|
||||
"album_viewer_appbar_share_remove": "Odstrániť z albumu",
|
||||
"album_viewer_page_share_add_users": "Pridať používateľov",
|
||||
"all_videos_page_title": "Videos",
|
||||
"archive_page_no_archived_assets": "No archived assets found",
|
||||
"archive_page_title": "Archive ({})",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Dynamické rozloženie",
|
||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
||||
"asset_list_layout_settings_group_by": "Zoskupiť položky podľa",
|
||||
"asset_list_layout_settings_group_by_month": "Mesiac",
|
||||
"asset_list_layout_settings_group_by_month_day": "Mesiac + deň",
|
||||
@@ -108,10 +115,12 @@
|
||||
"control_bottom_app_bar_add_to_album": "Pridať do albumu",
|
||||
"control_bottom_app_bar_album_info": "{} položiek",
|
||||
"control_bottom_app_bar_album_info_shared": "{} položky - zdieľané",
|
||||
"control_bottom_app_bar_archive": "Archive",
|
||||
"control_bottom_app_bar_create_new_album": "Vytvoriť nový album",
|
||||
"control_bottom_app_bar_delete": "Vymazať",
|
||||
"control_bottom_app_bar_favorite": "Obľúbené",
|
||||
"control_bottom_app_bar_share": "Zdieľať",
|
||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
||||
"create_album_page_untitled": "Bez názvu",
|
||||
"create_shared_album_page_create": "Vytvoriť",
|
||||
"create_shared_album_page_share": "Zdieľať",
|
||||
@@ -126,6 +135,8 @@
|
||||
"delete_dialog_cancel": "Zrušiť",
|
||||
"delete_dialog_ok": "Vymazať",
|
||||
"delete_dialog_title": "Vymazať natrvalo",
|
||||
"description_input_hint_text": "Add description...",
|
||||
"description_input_submit_error": "Error updating description, check the log for more details",
|
||||
"exif_bottom_sheet_description": "Pridať popis...",
|
||||
"exif_bottom_sheet_details": "PODROBNOSTI",
|
||||
"exif_bottom_sheet_location": "LOKALITA",
|
||||
@@ -133,16 +144,19 @@
|
||||
"experimental_settings_new_asset_list_title": "Povolenie experimentálnej mriežky fotografií",
|
||||
"experimental_settings_subtitle": "Používajte na vlastné riziko!",
|
||||
"experimental_settings_title": "Experimentálne",
|
||||
"favorites_page_no_favorites": "No favorite assets found",
|
||||
"favorites_page_title": "Obľúbené",
|
||||
"home_page_add_to_album_conflicts": "Pridané {added} položiek do albumu {album}. {failed} položiek už je v albume.",
|
||||
"home_page_add_to_album_err_local": "Zatiaľ nie je možné pridať lokálne média do albumov, preskakuje sa",
|
||||
"home_page_add_to_album_success": "Pridané {added} položky do albumu {album}.",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_building_timeline": "Vytváranie časovej osi",
|
||||
"home_page_favorite_err_local": "Zatiaľ nie je možné zaradiť lokálne média medzi obľúbené, preskakuje sa",
|
||||
"home_page_first_time_notice": "Ak aplikáciu používate prvý krát, nezabudnite si vybrať zálohované albumy, aby sa na časovej osi mohli nachádzať fotografie a videá z vybraných albumoch.",
|
||||
"image_viewer_page_state_provider_download_error": "Chyba sťahovania",
|
||||
"image_viewer_page_state_provider_download_success": "Sťahovanie bolo úspešné",
|
||||
"library_page_albums": "Albumy",
|
||||
"library_page_archive": "Archive",
|
||||
"library_page_device_albums": "Albums on Device",
|
||||
"library_page_favorites": "Obľúbené",
|
||||
"library_page_new_album": "Nový album",
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
{
|
||||
"add_to_album_bottom_sheet_added": "Added to {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
|
||||
"advanced_settings_tile_subtitle": "Advanced user's settings",
|
||||
"advanced_settings_tile_title": "Advanced",
|
||||
"advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
|
||||
"advanced_settings_troubleshooting_title": "Troubleshooting",
|
||||
"album_info_card_backup_album_excluded": "EXKLUDERAD",
|
||||
"album_info_card_backup_album_included": "INKLUDERAD",
|
||||
"album_thumbnail_card_item": "1 objekt",
|
||||
@@ -17,7 +21,10 @@
|
||||
"album_viewer_appbar_share_remove": "Ta bort från album",
|
||||
"album_viewer_page_share_add_users": "Lägg till användare",
|
||||
"all_videos_page_title": "Videos",
|
||||
"archive_page_no_archived_assets": "No archived assets found",
|
||||
"archive_page_title": "Archive ({})",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Dynamisk layout",
|
||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
||||
"asset_list_layout_settings_group_by": "Gruppera bilder efter",
|
||||
"asset_list_layout_settings_group_by_month": "Månad",
|
||||
"asset_list_layout_settings_group_by_month_day": "Månad + dag",
|
||||
@@ -108,10 +115,12 @@
|
||||
"control_bottom_app_bar_add_to_album": "Lägg till i album",
|
||||
"control_bottom_app_bar_album_info": "{} objekt",
|
||||
"control_bottom_app_bar_album_info_shared": "{} objekt • Delat",
|
||||
"control_bottom_app_bar_archive": "Archive",
|
||||
"control_bottom_app_bar_create_new_album": "Skapa nytt album",
|
||||
"control_bottom_app_bar_delete": "Radera",
|
||||
"control_bottom_app_bar_favorite": "Favorit",
|
||||
"control_bottom_app_bar_share": "Dela",
|
||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
||||
"create_album_page_untitled": "Namnlös",
|
||||
"create_shared_album_page_create": "Skapa",
|
||||
"create_shared_album_page_share": "Dela",
|
||||
@@ -126,6 +135,8 @@
|
||||
"delete_dialog_cancel": "Avbryt",
|
||||
"delete_dialog_ok": "Radera",
|
||||
"delete_dialog_title": "Radera permanent",
|
||||
"description_input_hint_text": "Add description...",
|
||||
"description_input_submit_error": "Error updating description, check the log for more details",
|
||||
"exif_bottom_sheet_description": "Lägg till beskrivning...",
|
||||
"exif_bottom_sheet_details": "DETALJER",
|
||||
"exif_bottom_sheet_location": "PLATS",
|
||||
@@ -133,16 +144,19 @@
|
||||
"experimental_settings_new_asset_list_title": "Aktivera experimentellt fotorutnät",
|
||||
"experimental_settings_subtitle": "Använd på egen risk!",
|
||||
"experimental_settings_title": "Experimentellt",
|
||||
"favorites_page_no_favorites": "No favorite assets found",
|
||||
"favorites_page_title": "Favoriter",
|
||||
"home_page_add_to_album_conflicts": "Lade till {added} foton och videor i albumet {album}. {failed} foton och videor finns redan i albumet.",
|
||||
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
|
||||
"home_page_add_to_album_success": "Lade till {added} foton och videor i albumet {album}.",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_building_timeline": "Bygger tidslinjen",
|
||||
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
|
||||
"home_page_first_time_notice": "Om det här är första gången du använder appen, välj ett eller flera backup-album så att tidslinjen kan fyllas med foton och videor från albumen.",
|
||||
"image_viewer_page_state_provider_download_error": "Download Error",
|
||||
"image_viewer_page_state_provider_download_success": "Download Success",
|
||||
"library_page_albums": "Album",
|
||||
"library_page_archive": "Archive",
|
||||
"library_page_device_albums": "Albums on Device",
|
||||
"library_page_favorites": "Favoriter",
|
||||
"library_page_new_album": "Nytt album",
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
{
|
||||
"add_to_album_bottom_sheet_added": "添加到{album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "已经在{album}中了",
|
||||
"advanced_settings_tile_subtitle": "Advanced user's settings",
|
||||
"advanced_settings_tile_title": "Advanced",
|
||||
"advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
|
||||
"advanced_settings_troubleshooting_title": "Troubleshooting",
|
||||
"album_info_card_backup_album_excluded": "排除",
|
||||
"album_info_card_backup_album_included": "已选",
|
||||
"album_thumbnail_card_item": "1张",
|
||||
@@ -17,7 +21,10 @@
|
||||
"album_viewer_appbar_share_remove": "从相册中移除",
|
||||
"album_viewer_page_share_add_users": "新增用户",
|
||||
"all_videos_page_title": "Videos",
|
||||
"archive_page_no_archived_assets": "No archived assets found",
|
||||
"archive_page_title": "Archive ({})",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "动态布局",
|
||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
||||
"asset_list_layout_settings_group_by": "分组照片或视频由",
|
||||
"asset_list_layout_settings_group_by_month": "月",
|
||||
"asset_list_layout_settings_group_by_month_day": "月和日",
|
||||
@@ -108,10 +115,12 @@
|
||||
"control_bottom_app_bar_add_to_album": "添加到相册",
|
||||
"control_bottom_app_bar_album_info": "{}张",
|
||||
"control_bottom_app_bar_album_info_shared": "{} 张已分享",
|
||||
"control_bottom_app_bar_archive": "Archive",
|
||||
"control_bottom_app_bar_create_new_album": "新建相册",
|
||||
"control_bottom_app_bar_delete": "删除",
|
||||
"control_bottom_app_bar_favorite": "收藏",
|
||||
"control_bottom_app_bar_share": "分享",
|
||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
||||
"create_album_page_untitled": "未命名",
|
||||
"create_shared_album_page_create": "新建",
|
||||
"create_shared_album_page_share": "分享",
|
||||
@@ -126,6 +135,8 @@
|
||||
"delete_dialog_cancel": "取消",
|
||||
"delete_dialog_ok": "删除",
|
||||
"delete_dialog_title": "永久删除",
|
||||
"description_input_hint_text": "Add description...",
|
||||
"description_input_submit_error": "Error updating description, check the log for more details",
|
||||
"exif_bottom_sheet_description": "增加描述...",
|
||||
"exif_bottom_sheet_details": "详情",
|
||||
"exif_bottom_sheet_location": "位置",
|
||||
@@ -133,16 +144,19 @@
|
||||
"experimental_settings_new_asset_list_title": "启用实验性的照片宫格",
|
||||
"experimental_settings_subtitle": "使用风险自负!",
|
||||
"experimental_settings_title": "实验功能",
|
||||
"favorites_page_no_favorites": "No favorite assets found",
|
||||
"favorites_page_title": "收藏",
|
||||
"home_page_add_to_album_conflicts": "添加{added}张到相册{album}。{failed} 项已经处于该相册中。",
|
||||
"home_page_add_to_album_err_local": "无法在相册中收藏本地的照片或视频,跳过",
|
||||
"home_page_add_to_album_success": "添加了{added}张到相册{album}。",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_building_timeline": "生成时间线",
|
||||
"home_page_favorite_err_local": "还不能收藏本地的照片或视频,跳过",
|
||||
"home_page_first_time_notice": "如果这是您第一次使用该应用程序,请确保选择一个想要备份的本地相册,以便可以在时间线中预览该相册中的照片和视频。",
|
||||
"image_viewer_page_state_provider_download_error": "下载出现错误",
|
||||
"image_viewer_page_state_provider_download_success": "下载成功",
|
||||
"library_page_albums": "相册",
|
||||
"library_page_archive": "Archive",
|
||||
"library_page_device_albums": "Albums on Device",
|
||||
"library_page_favorites": "收藏",
|
||||
"library_page_new_album": "新建相册",
|
||||
|
||||
@@ -35,6 +35,14 @@ target 'Runner' do
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.generated_projects.each do |project|
|
||||
project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_ios_build_settings(target)
|
||||
|
||||
|
||||
@@ -61,12 +61,12 @@ DEPENDENCIES:
|
||||
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
||||
- isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`)
|
||||
@@ -100,7 +100,7 @@ EXTERNAL SOURCES:
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/ios"
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
path_provider_ios:
|
||||
:path: ".symlinks/plugins/path_provider_ios/ios"
|
||||
permission_handler_apple:
|
||||
@@ -110,7 +110,7 @@ EXTERNAL SOURCES:
|
||||
share_plus:
|
||||
:path: ".symlinks/plugins/share_plus/ios"
|
||||
shared_preferences_foundation:
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/ios"
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||
sqflite:
|
||||
:path: ".symlinks/plugins/sqflite/ios"
|
||||
url_launcher_ios:
|
||||
@@ -129,7 +129,7 @@ SPEC CHECKSUMS:
|
||||
fluttertoast: eb263d302cc92e04176c053d2385237e9f43fad0
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
image_picker_ios: 58b9c4269cb176f89acea5e5d043c9358f2d25f8
|
||||
integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5
|
||||
integration_test: 13825b8a9334a850581300559b8839134b124670
|
||||
isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073
|
||||
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
|
||||
path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9
|
||||
@@ -145,6 +145,6 @@ SPEC CHECKSUMS:
|
||||
video_player_avfoundation: 6d971a232d72e6ee25368378d48a079dea01f1cf
|
||||
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
|
||||
|
||||
PODFILE CHECKSUM: 0606648e8a9ecd5a59eafa5ab3187b45a9004a28
|
||||
PODFILE CHECKSUM: 599d8aeb73728400c15364e734525722250a5382
|
||||
|
||||
COCOAPODS: 1.11.3
|
||||
|
||||
@@ -220,6 +220,7 @@
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||
);
|
||||
name = "Thin Binary";
|
||||
outputPaths = (
|
||||
@@ -378,7 +379,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 88;
|
||||
CURRENT_PROJECT_VERSION = 97;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -514,7 +515,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 88;
|
||||
CURRENT_PROJECT_VERSION = 97;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -542,7 +543,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 88;
|
||||
CURRENT_PROJECT_VERSION = 97;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
|
||||