Compare commits
35 Commits
v1.110.0
...
fix-mobile
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
675c64a22f | ||
|
|
d3aacbe74b | ||
|
|
41aefffe09 | ||
|
|
090762f9dd | ||
|
|
3225e33fc1 | ||
|
|
85ab916ecf | ||
|
|
7445dad0dd | ||
|
|
0237f9baa3 | ||
|
|
2e059bfbfd | ||
|
|
7bb7f63d57 | ||
|
|
66a5a5718f | ||
|
|
ddc4d2f927 | ||
|
|
0beeb61f5c | ||
|
|
a321db9f48 | ||
|
|
827136fc8b | ||
|
|
088eea88e0 | ||
|
|
15503784c8 | ||
|
|
bc8e236598 | ||
|
|
909bd43e65 | ||
|
|
3330885bcc | ||
|
|
e1ac73718c | ||
|
|
a78eeb9b9c | ||
|
|
86b3e3ee13 | ||
|
|
4b2bc8e4ce | ||
|
|
f92aee204e | ||
|
|
7fd2b7965c | ||
|
|
32ba6e3e3f | ||
|
|
0a6e5e0ec1 | ||
|
|
65a4f86154 | ||
|
|
147c6e3600 | ||
|
|
ee6f1a010c | ||
|
|
a444ea7361 | ||
|
|
59b809012f | ||
|
|
c037a8b8fa | ||
|
|
ce15cf6065 |
@@ -1 +1 @@
|
|||||||
20.15.1
|
20.16.0
|
||||||
|
|||||||
9
cli/package-lock.json
generated
9
cli/package-lock.json
generated
@@ -26,7 +26,7 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||||
"@typescript-eslint/parser": "^7.0.0",
|
"@typescript-eslint/parser": "^7.0.0",
|
||||||
"@vitest/coverage-v8": "^1.2.2",
|
"@vitest/coverage-v8": "^1.2.2",
|
||||||
"byte-size": "^8.1.1",
|
"byte-size": "^9.0.0",
|
||||||
"cli-progress": "^3.12.0",
|
"cli-progress": "^3.12.0",
|
||||||
"commander": "^12.0.0",
|
"commander": "^12.0.0",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.56.0",
|
||||||
@@ -1669,10 +1669,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/byte-size": {
|
"node_modules/byte-size": {
|
||||||
"version": "8.1.1",
|
"version": "9.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/byte-size/-/byte-size-8.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/byte-size/-/byte-size-9.0.0.tgz",
|
||||||
"integrity": "sha512-tUkzZWK0M/qdoLEqikxBWe4kumyuwjl3HO6zHTr4yEI23EojPtLYXdG1+AQY7MN0cGyNDvEaJ8wiYQm6P2bPxg==",
|
"integrity": "sha512-xrJ8Hki7eQ6xew55mM6TG9zHI852OoAHcPfduWWtR6yxk2upTuIZy13VioRBDyHReHDdbeDPifUboeNkK/sXXA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.17"
|
"node": ">=12.17"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||||
"@typescript-eslint/parser": "^7.0.0",
|
"@typescript-eslint/parser": "^7.0.0",
|
||||||
"@vitest/coverage-v8": "^1.2.2",
|
"@vitest/coverage-v8": "^1.2.2",
|
||||||
"byte-size": "^8.1.1",
|
"byte-size": "^9.0.0",
|
||||||
"cli-progress": "^3.12.0",
|
"cli-progress": "^3.12.0",
|
||||||
"commander": "^12.0.0",
|
"commander": "^12.0.0",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.56.0",
|
||||||
@@ -64,6 +64,6 @@
|
|||||||
"lodash-es": "^4.17.21"
|
"lodash-es": "^4.17.21"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "20.15.1"
|
"node": "20.16.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,37 +2,37 @@
|
|||||||
# Manual edits may be lost in future updates.
|
# Manual edits may be lost in future updates.
|
||||||
|
|
||||||
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
||||||
version = "4.37.0"
|
version = "4.38.0"
|
||||||
constraints = "4.37.0"
|
constraints = "4.38.0"
|
||||||
hashes = [
|
hashes = [
|
||||||
"h1:0gOI8arnh2CTcHfGH8iwAe6qz2BRSytmbOiNXZjnrHc=",
|
"h1:+27KAHKHBDvv3dqyJv5vhtdKQZJzoZXoMqIyronlHNw=",
|
||||||
"h1:0h0qRJYPHL92Dx3NYZO2WJ21cxyZGEoldzw9aYhPnew=",
|
"h1:/uV9RgOUhkxElkHhWs8fs5ZbX9vj6RCBfP0oJO0JF30=",
|
||||||
"h1:6ri7vZ1MLtQbooicIO4catyIuRq4LHAsIcgd3vGq3AE=",
|
"h1:1DNAdMugJJOAWD/XYiZenYYZLy7fw2ctjT4YZmkRCVQ=",
|
||||||
"h1:7BwVaqxSD9VsmLzs6jDJBJvHPq0dz4I8rCeJAK63Dc4=",
|
"h1:1wn4PmCLdT7mvd74JkCGmJDJxTQDkcxc+1jNbmwnMHA=",
|
||||||
"h1:8tVm+BJvzI14pRbEyt00AvH6oIyqiLRZQ9KxcBeSDhE=",
|
"h1:BIHB4fBxHg2bA9KbL92njhyctxKC8b6hNDp60y5QBss=",
|
||||||
"h1:FTll1M9rPA7RxEyLB6etQqaqynWWl3WkiwJtHMjPr3Y=",
|
"h1:HCQpvKPsMsR4HO5eDqt+Kao7T7CYeEH7KZIO7xMcC6M=",
|
||||||
"h1:L7ysGftn0fstXMjCt3/XEz2giRdEwBsGrdvi4Zw8uzM=",
|
"h1:HTomuzocukpNLwtWzeSF3yteCVsyVKbwKmN66u9iPac=",
|
||||||
"h1:PsbAKy7LdSpwZMJZ7bO3lI04hLDTlXke/LCkrKXYwwE=",
|
"h1:YDxsUBhBAwHSXLzVwrSlSBOwv1NvLyry7s5SfCV7VqQ=",
|
||||||
"h1:Sjkpr8CKs0rXGcdis5q4Kbqmo5mmosgirnQi65G4sM8=",
|
"h1:dchVhxo+Acd1l2RuZ88tW9lWj4422QMfgtxKvKCjYrw=",
|
||||||
"h1:YxJRQdVSzMZR5Ce5M3Gs1SPutXpednxuRwtSSiReHDY=",
|
"h1:eypa+P4ZpsEGMPFuCE+6VkRefu0TZRFmVBOpK+PDOPY=",
|
||||||
"h1:bJrJeBKWEwt4hGQ+3VJR69dsqHORovE8LzuQt9+NTug=",
|
"h1:f3yjse2OsRZj7ZhR7BLintJMlI4fpyt8HyDP/zcEavw=",
|
||||||
"h1:hPC7Vk0ZGXCDJ1y5dOepVo1c0PoUulnJUarrMv4gQIQ=",
|
"h1:mSJ7xj8K+xcnEmGg7lH0jjzyQb157wH94ULTAlIV+HQ=",
|
||||||
"h1:joMURZCLUJ2eSlj645xqHWKYbRBYqvajCkhaz7qzi8g=",
|
"h1:tt+2J2Ze8VIdDq2Hr6uHlTJzAMBRpErBwTYx0uD5ilE=",
|
||||||
"h1:uqo0WgG5lCcG8+gf99VnsKKbJMM1urNZq1FbAT6u3S0=",
|
"h1:uQW8SKxmulqrAisO+365mIf2FueINAp5PY28bqCPCug=",
|
||||||
"zh:012a6c3e8bf4aca0ebe0884e15bd42fd018659193f2159d5d2bf9948a9be1bc4",
|
"zh:171ab67cccceead4514fafb2d39e4e708a90cce79000aaf3c29aab7ed4457071",
|
||||||
"zh:079666c0a079237af46ed19ffc4143655ee0e8920a274868e44fbc3db88f346d",
|
"zh:18aa7228447baaaefc49a43e8eff970817a7491a63d8937e796357a3829dd979",
|
||||||
"zh:08e7ff86f6848f3109d59ad46f8c0987178eff2f70c8ef03f2d44ae68e42dfb3",
|
"zh:2cbaab6092e81ba6f41fa60a50f14e980c8ec327ee11d0b21f16a478be4b7567",
|
||||||
"zh:1ce8a499fdf8f484f7d18ec91566bc0759b07d0ca710990cd60d32b222e416b1",
|
"zh:53b8e49c06f5b31a8c681f8c0669cf43e78abe71657b8182a221d096bb514965",
|
||||||
"zh:348e72338095bffccf7c46c7e6b9d0e063a22d9ae761061b0b31dea1aad22cd9",
|
"zh:6037cfc60b4b647aabae155fcb46d649ed7c650e0287f05db52b2068f1e27c8a",
|
||||||
"zh:47d39343dea1ef469a2c8e51c8d5993687af427a132da5379796fec27acb5710",
|
"zh:62460982ce1a869eebfca675603fbbd50416cf6b69459fb855bfbe5ae2b97607",
|
||||||
"zh:4cdf8e9579f9af3c72270088fc6e22208f0f91fd4382bc4a860d16040c86917b",
|
"zh:65f6f3a8470917b6398baa5eb4f74b3932b213eac7c0202798bfad6fd1ee17df",
|
||||||
"zh:4fbebb21ecebc7e5ac0ea9e341c5dbea3094fc0579e4dc5b40bfe693164e022e",
|
|
||||||
"zh:778578dda7dd98576a3fe228132c8b60f646f4cf113638c94f1c40e2b11c027c",
|
|
||||||
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
||||||
"zh:894071f0f42571f820918d1a4316704923e29c5b2392704c1cbd063a04a641b8",
|
"zh:8b5cebe64bf04105a49178a165b6a8800a9a33bae6767143a47fe4977755f805",
|
||||||
"zh:8d11dd73dd499c74d89f77a7e1b3d4a077ac88b0c9c3412e9a6a1b4efe17d107",
|
"zh:a5596635db0993ee3c3060fbc2227d91b239466e96d2d82642625a5aa2486988",
|
||||||
"zh:991e088be8381a73872cd33bb659e9dd69d7ab1f1f8d89b3cd17ffe59dffc65f",
|
"zh:b3a9c63038441f13c311fd4b2c7e69e571445e5a7365a20c7cc9046b7e6c8aba",
|
||||||
"zh:9c0848b9c7e6799c9ffcf3afa70ad94a027f3e15a94679d56790714de0b072c5",
|
"zh:b585e7e4d7648a540b14b9182819214896ca9337729eeb1f2034833b17db754d",
|
||||||
"zh:ad71ae800065ffc24b94d994250136ae8a9f6da704cf91b0dc9e14989e947369",
|
"zh:d2c3c545318ac8542369e9fc8228e29ee585febdf203a450fad3e0eded71ce02",
|
||||||
|
"zh:e95dd2d6c3525073af47d47b763cb81b6a51b20cabf76f789c69328922da9ecf",
|
||||||
|
"zh:eee6e590b36d6c6168a7daae8afa74a8721fd7aa9f62a710f04a311975100722",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
cloudflare = {
|
cloudflare = {
|
||||||
source = "cloudflare/cloudflare"
|
source = "cloudflare/cloudflare"
|
||||||
version = "4.37.0"
|
version = "4.38.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,37 +2,37 @@
|
|||||||
# Manual edits may be lost in future updates.
|
# Manual edits may be lost in future updates.
|
||||||
|
|
||||||
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
||||||
version = "4.37.0"
|
version = "4.38.0"
|
||||||
constraints = "4.37.0"
|
constraints = "4.38.0"
|
||||||
hashes = [
|
hashes = [
|
||||||
"h1:0gOI8arnh2CTcHfGH8iwAe6qz2BRSytmbOiNXZjnrHc=",
|
"h1:+27KAHKHBDvv3dqyJv5vhtdKQZJzoZXoMqIyronlHNw=",
|
||||||
"h1:0h0qRJYPHL92Dx3NYZO2WJ21cxyZGEoldzw9aYhPnew=",
|
"h1:/uV9RgOUhkxElkHhWs8fs5ZbX9vj6RCBfP0oJO0JF30=",
|
||||||
"h1:6ri7vZ1MLtQbooicIO4catyIuRq4LHAsIcgd3vGq3AE=",
|
"h1:1DNAdMugJJOAWD/XYiZenYYZLy7fw2ctjT4YZmkRCVQ=",
|
||||||
"h1:7BwVaqxSD9VsmLzs6jDJBJvHPq0dz4I8rCeJAK63Dc4=",
|
"h1:1wn4PmCLdT7mvd74JkCGmJDJxTQDkcxc+1jNbmwnMHA=",
|
||||||
"h1:8tVm+BJvzI14pRbEyt00AvH6oIyqiLRZQ9KxcBeSDhE=",
|
"h1:BIHB4fBxHg2bA9KbL92njhyctxKC8b6hNDp60y5QBss=",
|
||||||
"h1:FTll1M9rPA7RxEyLB6etQqaqynWWl3WkiwJtHMjPr3Y=",
|
"h1:HCQpvKPsMsR4HO5eDqt+Kao7T7CYeEH7KZIO7xMcC6M=",
|
||||||
"h1:L7ysGftn0fstXMjCt3/XEz2giRdEwBsGrdvi4Zw8uzM=",
|
"h1:HTomuzocukpNLwtWzeSF3yteCVsyVKbwKmN66u9iPac=",
|
||||||
"h1:PsbAKy7LdSpwZMJZ7bO3lI04hLDTlXke/LCkrKXYwwE=",
|
"h1:YDxsUBhBAwHSXLzVwrSlSBOwv1NvLyry7s5SfCV7VqQ=",
|
||||||
"h1:Sjkpr8CKs0rXGcdis5q4Kbqmo5mmosgirnQi65G4sM8=",
|
"h1:dchVhxo+Acd1l2RuZ88tW9lWj4422QMfgtxKvKCjYrw=",
|
||||||
"h1:YxJRQdVSzMZR5Ce5M3Gs1SPutXpednxuRwtSSiReHDY=",
|
"h1:eypa+P4ZpsEGMPFuCE+6VkRefu0TZRFmVBOpK+PDOPY=",
|
||||||
"h1:bJrJeBKWEwt4hGQ+3VJR69dsqHORovE8LzuQt9+NTug=",
|
"h1:f3yjse2OsRZj7ZhR7BLintJMlI4fpyt8HyDP/zcEavw=",
|
||||||
"h1:hPC7Vk0ZGXCDJ1y5dOepVo1c0PoUulnJUarrMv4gQIQ=",
|
"h1:mSJ7xj8K+xcnEmGg7lH0jjzyQb157wH94ULTAlIV+HQ=",
|
||||||
"h1:joMURZCLUJ2eSlj645xqHWKYbRBYqvajCkhaz7qzi8g=",
|
"h1:tt+2J2Ze8VIdDq2Hr6uHlTJzAMBRpErBwTYx0uD5ilE=",
|
||||||
"h1:uqo0WgG5lCcG8+gf99VnsKKbJMM1urNZq1FbAT6u3S0=",
|
"h1:uQW8SKxmulqrAisO+365mIf2FueINAp5PY28bqCPCug=",
|
||||||
"zh:012a6c3e8bf4aca0ebe0884e15bd42fd018659193f2159d5d2bf9948a9be1bc4",
|
"zh:171ab67cccceead4514fafb2d39e4e708a90cce79000aaf3c29aab7ed4457071",
|
||||||
"zh:079666c0a079237af46ed19ffc4143655ee0e8920a274868e44fbc3db88f346d",
|
"zh:18aa7228447baaaefc49a43e8eff970817a7491a63d8937e796357a3829dd979",
|
||||||
"zh:08e7ff86f6848f3109d59ad46f8c0987178eff2f70c8ef03f2d44ae68e42dfb3",
|
"zh:2cbaab6092e81ba6f41fa60a50f14e980c8ec327ee11d0b21f16a478be4b7567",
|
||||||
"zh:1ce8a499fdf8f484f7d18ec91566bc0759b07d0ca710990cd60d32b222e416b1",
|
"zh:53b8e49c06f5b31a8c681f8c0669cf43e78abe71657b8182a221d096bb514965",
|
||||||
"zh:348e72338095bffccf7c46c7e6b9d0e063a22d9ae761061b0b31dea1aad22cd9",
|
"zh:6037cfc60b4b647aabae155fcb46d649ed7c650e0287f05db52b2068f1e27c8a",
|
||||||
"zh:47d39343dea1ef469a2c8e51c8d5993687af427a132da5379796fec27acb5710",
|
"zh:62460982ce1a869eebfca675603fbbd50416cf6b69459fb855bfbe5ae2b97607",
|
||||||
"zh:4cdf8e9579f9af3c72270088fc6e22208f0f91fd4382bc4a860d16040c86917b",
|
"zh:65f6f3a8470917b6398baa5eb4f74b3932b213eac7c0202798bfad6fd1ee17df",
|
||||||
"zh:4fbebb21ecebc7e5ac0ea9e341c5dbea3094fc0579e4dc5b40bfe693164e022e",
|
|
||||||
"zh:778578dda7dd98576a3fe228132c8b60f646f4cf113638c94f1c40e2b11c027c",
|
|
||||||
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
||||||
"zh:894071f0f42571f820918d1a4316704923e29c5b2392704c1cbd063a04a641b8",
|
"zh:8b5cebe64bf04105a49178a165b6a8800a9a33bae6767143a47fe4977755f805",
|
||||||
"zh:8d11dd73dd499c74d89f77a7e1b3d4a077ac88b0c9c3412e9a6a1b4efe17d107",
|
"zh:a5596635db0993ee3c3060fbc2227d91b239466e96d2d82642625a5aa2486988",
|
||||||
"zh:991e088be8381a73872cd33bb659e9dd69d7ab1f1f8d89b3cd17ffe59dffc65f",
|
"zh:b3a9c63038441f13c311fd4b2c7e69e571445e5a7365a20c7cc9046b7e6c8aba",
|
||||||
"zh:9c0848b9c7e6799c9ffcf3afa70ad94a027f3e15a94679d56790714de0b072c5",
|
"zh:b585e7e4d7648a540b14b9182819214896ca9337729eeb1f2034833b17db754d",
|
||||||
"zh:ad71ae800065ffc24b94d994250136ae8a9f6da704cf91b0dc9e14989e947369",
|
"zh:d2c3c545318ac8542369e9fc8228e29ee585febdf203a450fad3e0eded71ce02",
|
||||||
|
"zh:e95dd2d6c3525073af47d47b763cb81b6a51b20cabf76f789c69328922da9ecf",
|
||||||
|
"zh:eee6e590b36d6c6168a7daae8afa74a8721fd7aa9f62a710f04a311975100722",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
cloudflare = {
|
cloudflare = {
|
||||||
source = "cloudflare/cloudflare"
|
source = "cloudflare/cloudflare"
|
||||||
version = "4.37.0"
|
version = "4.38.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ services:
|
|||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
image: docker.io/redis:6.2-alpine@sha256:328fe6a5822256d065debb36617a8169dbfbd77b797c525288e465f56c1d392b
|
image: docker.io/redis:6.2-alpine@sha256:e3b17ba9479deec4b7d1eeec1548a253acc5374d68d3b27937fcfe4df8d18c7e
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: redis-cli ping || exit 1
|
test: redis-cli ping || exit 1
|
||||||
restart: always
|
restart: always
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
20.15.1
|
20.16.0
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ In our `.env` file, we will define variables that will help us in the future whe
|
|||||||
|
|
||||||
# Custom location where your uploaded, thumbnails, and transcoded video files are stored
|
# Custom location where your uploaded, thumbnails, and transcoded video files are stored
|
||||||
- UPLOAD_LOCATION=./library
|
- UPLOAD_LOCATION=./library
|
||||||
+ UPLOAD_LOCATION=/custom/location/on/your/system/immich/immich_files
|
+ UPLOAD_LOCATION=/custom/path/immich/immich_files
|
||||||
+ THUMB_LOCATION=/custom/location/on/your/system/immich/thumbs
|
+ THUMB_LOCATION=/custom/path/immich/thumbs
|
||||||
+ ENCODED_VIDEO_LOCATION=/custom/location/on/your/system/immich/encoded-video
|
+ ENCODED_VIDEO_LOCATION=/custom/path/immich/encoded-video
|
||||||
+ PROFILE_LOCATION=/custom/location/on/your/system/immich/profile
|
+ PROFILE_LOCATION=/custom/path/immich/profile
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
After defining the locations for these files, we will edit the `docker-compose.yml` file accordingly and add the new variables to the `immich-server` and `immich-microservices` containers.
|
After defining the locations for these files, we will edit the `docker-compose.yml` file accordingly and add the new variables to the `immich-server` container.
|
||||||
|
|
||||||
```diff title="docker-compose.yml"
|
```diff title="docker-compose.yml"
|
||||||
services:
|
services:
|
||||||
@@ -29,16 +29,6 @@ services:
|
|||||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||||
+ - ${THUMB_LOCATION}:/usr/src/app/upload/thumbs
|
+ - ${THUMB_LOCATION}:/usr/src/app/upload/thumbs
|
||||||
+ - ${ENCODED_VIDEO_LOCATION}:/usr/src/app/upload/encoded-video
|
+ - ${ENCODED_VIDEO_LOCATION}:/usr/src/app/upload/encoded-video
|
||||||
+ - ${PROFILE_LOCATION}:/usr/src/app/upload/profile
|
|
||||||
- /etc/localtime:/etc/localtime:ro
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
immich-microservices:
|
|
||||||
volumes:
|
|
||||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
|
||||||
+ - ${THUMB_LOCATION}:/usr/src/app/upload/thumbs
|
|
||||||
+ - ${ENCODED_VIDEO_LOCATION}:/usr/src/app/upload/encoded-video
|
|
||||||
+ - ${PROFILE_LOCATION}:/usr/src/app/upload/profile
|
+ - ${PROFILE_LOCATION}:/usr/src/app/upload/profile
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
```
|
```
|
||||||
@@ -46,7 +36,6 @@ services:
|
|||||||
Restart Immich to register the changes.
|
Restart Immich to register the changes.
|
||||||
|
|
||||||
```
|
```
|
||||||
docker compose down
|
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Keep in mind that mucking around in the database might set the moon on fire. Avo
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
Run `docker exec -it immich_postgres psql immich <DB_USERNAME>` to connect to the database via the container directly.
|
Run `docker exec -it immich_postgres psql --dbname=immich --username=<DB_USERNAME>` to connect to the database via the container directly.
|
||||||
|
|
||||||
(Replace `<DB_USERNAME>` with the value from your [`.env` file](/docs/install/environment-variables#database)).
|
(Replace `<DB_USERNAME>` with the value from your [`.env` file](/docs/install/environment-variables#database)).
|
||||||
:::
|
:::
|
||||||
@@ -106,3 +106,9 @@ SELECT "key", "value" FROM "system_metadata" WHERE "key" = 'system-config';
|
|||||||
```sql title="Delete person and unset it for the faces it was associated with"
|
```sql title="Delete person and unset it for the faces it was associated with"
|
||||||
DELETE FROM "person" WHERE "name" = 'PersonNameHere';
|
DELETE FROM "person" WHERE "name" = 'PersonNameHere';
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Postgres internal
|
||||||
|
|
||||||
|
```sql title="Change DB_PASSWORD"
|
||||||
|
ALTER USER <DB_USERNAME> WITH ENCRYPTED PASSWORD 'newpasswordhere';
|
||||||
|
```
|
||||||
|
|||||||
@@ -56,6 +56,6 @@
|
|||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "20.15.1"
|
"node": "20.16.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
20.15.1
|
20.16.0
|
||||||
|
|||||||
4
e2e/package-lock.json
generated
4
e2e/package-lock.json
generated
@@ -37,7 +37,7 @@
|
|||||||
"supertest": "^7.0.0",
|
"supertest": "^7.0.0",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"utimes": "^5.2.1",
|
"utimes": "^5.2.1",
|
||||||
"vitest": "^1.3.0"
|
"vitest": "^1.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"../cli": {
|
"../cli": {
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||||
"@typescript-eslint/parser": "^7.0.0",
|
"@typescript-eslint/parser": "^7.0.0",
|
||||||
"@vitest/coverage-v8": "^1.2.2",
|
"@vitest/coverage-v8": "^1.2.2",
|
||||||
"byte-size": "^8.1.1",
|
"byte-size": "^9.0.0",
|
||||||
"cli-progress": "^3.12.0",
|
"cli-progress": "^3.12.0",
|
||||||
"commander": "^12.0.0",
|
"commander": "^12.0.0",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.56.0",
|
||||||
|
|||||||
@@ -47,9 +47,9 @@
|
|||||||
"supertest": "^7.0.0",
|
"supertest": "^7.0.0",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"utimes": "^5.2.1",
|
"utimes": "^5.2.1",
|
||||||
"vitest": "^1.3.0"
|
"vitest": "^1.6.0"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "20.15.1"
|
"node": "20.16.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ test.describe('Registration', () => {
|
|||||||
test('admin registration', async ({ page }) => {
|
test('admin registration', async ({ page }) => {
|
||||||
// welcome
|
// welcome
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
await page.getByRole('button', { name: 'Getting Started' }).click();
|
await page.getByRole('link', { name: 'Getting Started' }).click();
|
||||||
|
|
||||||
// register
|
// register
|
||||||
await expect(page).toHaveTitle(/Admin Registration/);
|
await expect(page).toHaveTitle(/Admin Registration/);
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"flutter": "3.22.2"
|
"flutter": "3.22.3"
|
||||||
}
|
}
|
||||||
2
mobile/.vscode/settings.json
vendored
2
mobile/.vscode/settings.json
vendored
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"dart.flutterSdkPath": ".fvm/versions/3.22.1",
|
"dart.flutterSdkPath": ".fvm/versions/3.22.3",
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"**/.fvm": true
|
"**/.fvm": true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
"action_common_cancel": "Cancel",
|
"action_common_cancel": "Cancel",
|
||||||
"action_common_clear": "Clear",
|
"action_common_clear": "Clear",
|
||||||
"action_common_confirm": "Confirm",
|
"action_common_confirm": "Confirm",
|
||||||
|
"action_common_save": "Save",
|
||||||
|
"action_common_select": "Select",
|
||||||
"action_common_update": "Update",
|
"action_common_update": "Update",
|
||||||
"add_to_album_bottom_sheet_added": "Added to {album}",
|
"add_to_album_bottom_sheet_added": "Added to {album}",
|
||||||
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
|
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
|
||||||
@@ -146,6 +148,7 @@
|
|||||||
"common_create_new_album": "Create new album",
|
"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_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.",
|
||||||
"common_shared": "Shared",
|
"common_shared": "Shared",
|
||||||
|
"contextual_search": "Sunrise on the beach",
|
||||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
"control_bottom_app_bar_add_to_album": "Add to album",
|
||||||
"control_bottom_app_bar_album_info": "{} items",
|
"control_bottom_app_bar_album_info": "{} items",
|
||||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||||
@@ -203,6 +206,7 @@
|
|||||||
"experimental_settings_title": "Experimental",
|
"experimental_settings_title": "Experimental",
|
||||||
"favorites_page_no_favorites": "No favorite assets found",
|
"favorites_page_no_favorites": "No favorite assets found",
|
||||||
"favorites_page_title": "Favorites",
|
"favorites_page_title": "Favorites",
|
||||||
|
"filename_search": "File name or extension",
|
||||||
"haptic_feedback_switch": "Enable haptic feedback",
|
"haptic_feedback_switch": "Enable haptic feedback",
|
||||||
"haptic_feedback_title": "Haptic Feedback",
|
"haptic_feedback_title": "Haptic Feedback",
|
||||||
"header_settings_add_header_tip": "Add Header",
|
"header_settings_add_header_tip": "Add Header",
|
||||||
@@ -230,6 +234,8 @@
|
|||||||
"image_viewer_page_state_provider_download_started": "Download Started",
|
"image_viewer_page_state_provider_download_started": "Download Started",
|
||||||
"image_viewer_page_state_provider_download_success": "Download Success",
|
"image_viewer_page_state_provider_download_success": "Download Success",
|
||||||
"image_viewer_page_state_provider_share_error": "Share Error",
|
"image_viewer_page_state_provider_share_error": "Share Error",
|
||||||
|
"invalid_date": "Invalid date",
|
||||||
|
"invalid_date_format": "Invalid date format",
|
||||||
"library_page_albums": "Albums",
|
"library_page_albums": "Albums",
|
||||||
"library_page_archive": "Archive",
|
"library_page_archive": "Archive",
|
||||||
"library_page_device_albums": "Albums on Device",
|
"library_page_device_albums": "Albums on Device",
|
||||||
@@ -311,6 +317,7 @@
|
|||||||
"multiselect_grid_edit_date_time_err_read_only": "Cannot edit date of read only asset(s), skipping",
|
"multiselect_grid_edit_date_time_err_read_only": "Cannot edit date of read only asset(s), skipping",
|
||||||
"multiselect_grid_edit_gps_err_read_only": "Cannot edit location of read only asset(s), skipping",
|
"multiselect_grid_edit_gps_err_read_only": "Cannot edit location of read only asset(s), skipping",
|
||||||
"no_assets_to_show": "No assets to show",
|
"no_assets_to_show": "No assets to show",
|
||||||
|
"no_name": "No name",
|
||||||
"notification_permission_dialog_cancel": "Cancel",
|
"notification_permission_dialog_cancel": "Cancel",
|
||||||
"notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.",
|
"notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.",
|
||||||
"notification_permission_dialog_settings": "Settings",
|
"notification_permission_dialog_settings": "Settings",
|
||||||
@@ -354,17 +361,30 @@
|
|||||||
"scaffold_body_error_occurred": "Error occurred",
|
"scaffold_body_error_occurred": "Error occurred",
|
||||||
"search_bar_hint": "Search your photos",
|
"search_bar_hint": "Search your photos",
|
||||||
"search_filter_apply": "Apply filter",
|
"search_filter_apply": "Apply filter",
|
||||||
|
"search_filter_camera": "Camera",
|
||||||
"search_filter_camera_make": "Make",
|
"search_filter_camera_make": "Make",
|
||||||
"search_filter_camera_model": "Model",
|
"search_filter_camera_model": "Model",
|
||||||
|
"search_filter_camera_title": "Select camera type",
|
||||||
|
"search_filter_date": "Date",
|
||||||
|
"search_filter_date_interval": "{start} to {end}",
|
||||||
|
"search_filter_date_title": "Select a date range",
|
||||||
"search_filter_display_option_archive": "Archive",
|
"search_filter_display_option_archive": "Archive",
|
||||||
"search_filter_display_option_favorite": "Favorite",
|
"search_filter_display_option_favorite": "Favorite",
|
||||||
"search_filter_display_option_not_in_album": "Not in album",
|
"search_filter_display_option_not_in_album": "Not in album",
|
||||||
|
"search_filter_display_options": "Display Options",
|
||||||
|
"search_filter_display_options_title": "Display options",
|
||||||
|
"search_filter_location": "Location",
|
||||||
"search_filter_location_city": "City",
|
"search_filter_location_city": "City",
|
||||||
"search_filter_location_country": "Country",
|
"search_filter_location_country": "Country",
|
||||||
"search_filter_location_state": "State",
|
"search_filter_location_state": "State",
|
||||||
|
"search_filter_location_title": "Select location",
|
||||||
|
"search_filter_media_type": "Media Type",
|
||||||
"search_filter_media_type_all": "All",
|
"search_filter_media_type_all": "All",
|
||||||
"search_filter_media_type_image": "Image",
|
"search_filter_media_type_image": "Image",
|
||||||
|
"search_filter_media_type_title": "Select media type",
|
||||||
"search_filter_media_type_video": "Video",
|
"search_filter_media_type_video": "Video",
|
||||||
|
"search_filter_people": "People",
|
||||||
|
"search_filter_people_title": "Select people",
|
||||||
"search_page_categories": "Categories",
|
"search_page_categories": "Categories",
|
||||||
"search_page_favorites": "Favorites",
|
"search_page_favorites": "Favorites",
|
||||||
"search_page_motion_photos": "Motion Photos",
|
"search_page_motion_photos": "Motion Photos",
|
||||||
@@ -418,15 +438,18 @@
|
|||||||
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
|
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
|
||||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||||
"setting_pages_app_bar_settings": "Settings",
|
"setting_pages_app_bar_settings": "Settings",
|
||||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
|
||||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||||
"setting_video_viewer_looping_title": "Looping",
|
"setting_video_viewer_looping_title": "Looping",
|
||||||
"setting_video_viewer_title": "Videos",
|
"setting_video_viewer_title": "Videos",
|
||||||
|
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||||
"share_add": "Add",
|
"share_add": "Add",
|
||||||
"share_add_photos": "Add photos",
|
"share_add_photos": "Add photos",
|
||||||
"share_add_title": "Add a title",
|
"share_add_title": "Add a title",
|
||||||
"share_assets_selected": "{} selected",
|
"share_assets_selected": "{} selected",
|
||||||
"share_create_album": "Create album",
|
"share_create_album": "Create album",
|
||||||
|
"share_dialog_preparing": "Preparing...",
|
||||||
|
"share_done": "Done",
|
||||||
|
"share_invite": "Invite to album",
|
||||||
"shared_album_activities_input_disable": "Comment is disabled",
|
"shared_album_activities_input_disable": "Comment is disabled",
|
||||||
"shared_album_activities_input_hint": "Say something",
|
"shared_album_activities_input_hint": "Say something",
|
||||||
"shared_album_activity_remove_content": "Do you want to delete this activity?",
|
"shared_album_activity_remove_content": "Do you want to delete this activity?",
|
||||||
@@ -438,7 +461,6 @@
|
|||||||
"shared_album_section_people_action_remove_user": "Remove user from album",
|
"shared_album_section_people_action_remove_user": "Remove user from album",
|
||||||
"shared_album_section_people_owner_label": "Owner",
|
"shared_album_section_people_owner_label": "Owner",
|
||||||
"shared_album_section_people_title": "PEOPLE",
|
"shared_album_section_people_title": "PEOPLE",
|
||||||
"share_dialog_preparing": "Preparing...",
|
|
||||||
"shared_link_app_bar_title": "Shared Links",
|
"shared_link_app_bar_title": "Shared Links",
|
||||||
"shared_link_clipboard_copied_massage": "Copied to clipboard",
|
"shared_link_clipboard_copied_massage": "Copied to clipboard",
|
||||||
"shared_link_clipboard_text": "Link: {}\nPassword: {}",
|
"shared_link_clipboard_text": "Link: {}\nPassword: {}",
|
||||||
@@ -484,14 +506,12 @@
|
|||||||
"shared_link_info_chip_upload": "Upload",
|
"shared_link_info_chip_upload": "Upload",
|
||||||
"shared_link_manage_links": "Manage Shared links",
|
"shared_link_manage_links": "Manage Shared links",
|
||||||
"shared_link_public_album": "Public album",
|
"shared_link_public_album": "Public album",
|
||||||
"share_done": "Done",
|
|
||||||
"share_invite": "Invite to album",
|
|
||||||
"sharing_page_album": "Shared albums",
|
"sharing_page_album": "Shared albums",
|
||||||
"sharing_page_description": "Create shared albums to share photos and videos with people in your network.",
|
"sharing_page_description": "Create shared albums to share photos and videos with people in your network.",
|
||||||
"sharing_page_empty_list": "EMPTY LIST",
|
"sharing_page_empty_list": "EMPTY LIST",
|
||||||
"sharing_silver_appbar_create_shared_album": "New shared album",
|
"sharing_silver_appbar_create_shared_album": "New shared album",
|
||||||
"sharing_silver_appbar_shared_links": "Shared links",
|
|
||||||
"sharing_silver_appbar_share_partner": "Share with partner",
|
"sharing_silver_appbar_share_partner": "Share with partner",
|
||||||
|
"sharing_silver_appbar_shared_links": "Shared links",
|
||||||
"tab_controller_nav_library": "Library",
|
"tab_controller_nav_library": "Library",
|
||||||
"tab_controller_nav_photos": "Photos",
|
"tab_controller_nav_photos": "Photos",
|
||||||
"tab_controller_nav_search": "Search",
|
"tab_controller_nav_search": "Search",
|
||||||
|
|||||||
@@ -383,7 +383,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 164;
|
CURRENT_PROJECT_VERSION = 165;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -525,7 +525,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 164;
|
CURRENT_PROJECT_VERSION = 165;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -553,7 +553,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 164;
|
CURRENT_PROJECT_VERSION = 165;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
|||||||
@@ -58,11 +58,11 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.109.0</string>
|
<string>1.110.0</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>164</string>
|
<string>165</string>
|
||||||
<key>FLTEnableImpeller</key>
|
<key>FLTEnableImpeller</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class Store {
|
|||||||
|
|
||||||
/// Initializes the store (call exactly once per app start)
|
/// Initializes the store (call exactly once per app start)
|
||||||
static void init(Isar db) {
|
static void init(Isar db) {
|
||||||
|
print("Initializing store");
|
||||||
_db = db;
|
_db = db;
|
||||||
_populateCache();
|
_populateCache();
|
||||||
_db.storeValues.where().build().watch().listen(_onChangeListener);
|
_db.storeValues.where().build().watch().listen(_onChangeListener);
|
||||||
@@ -59,6 +60,9 @@ class Store {
|
|||||||
/// Removes the value synchronously from the cache and asynchronously from the DB
|
/// Removes the value synchronously from the cache and asynchronously from the DB
|
||||||
static Future<void> delete<T>(StoreKey<T> key) {
|
static Future<void> delete<T>(StoreKey<T> key) {
|
||||||
if (_cache[key.id] == null) return Future.value();
|
if (_cache[key.id] == null) return Future.value();
|
||||||
|
if(key.id == StoreKey.serverEndpoint.id) {
|
||||||
|
_log.info("Server endpoint changed to null");
|
||||||
|
}
|
||||||
_cache[key.id] = null;
|
_cache[key.id] = null;
|
||||||
return _db.writeTxn(() => _db.storeValues.delete(key.id));
|
return _db.writeTxn(() => _db.storeValues.delete(key.id));
|
||||||
}
|
}
|
||||||
@@ -76,12 +80,12 @@ class Store {
|
|||||||
/// updates the state if a value is updated in any isolate
|
/// updates the state if a value is updated in any isolate
|
||||||
static void _onChangeListener(List<StoreValue>? data) {
|
static void _onChangeListener(List<StoreValue>? data) {
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
|
final dbValues = _db.txnSync(() => _db.storeValues.getAllSync(data.map((e) => e.id).toList()));
|
||||||
for (StoreValue value in data) {
|
for (StoreValue value in data) {
|
||||||
final key = StoreKey.values.firstWhereOrNull((e) => e.id == value.id);
|
final dbValue = dbValues.firstWhere((e) => e?.id == value.id, orElse: () => null)?._extract(StoreKey.values[value.id]);
|
||||||
if (key != null) {
|
_cache[value.id] = dbValue;
|
||||||
_cache[value.id] = value._extract(key);
|
if(value.id == StoreKey.serverEndpoint.id) {
|
||||||
} else {
|
_log.info("Server endpoint changed to ${value.strValue}");
|
||||||
_log.warning("No key available for value id - ${value.id}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,7 +100,8 @@ class StoreValue {
|
|||||||
int? intValue;
|
int? intValue;
|
||||||
String? strValue;
|
String? strValue;
|
||||||
|
|
||||||
T? _extract<T>(StoreKey<T> key) {
|
T? _extract<T>(StoreKey<T>? key) {
|
||||||
|
if (key == null) return null;
|
||||||
switch (key.type) {
|
switch (key.type) {
|
||||||
case const (int):
|
case const (int):
|
||||||
return intValue as T?;
|
return intValue as T?;
|
||||||
|
|||||||
@@ -19,45 +19,22 @@ class SplashScreenPage extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final apiService = ref.watch(apiServiceProvider);
|
final apiService = ref.watch(apiServiceProvider);
|
||||||
final serverUrl = Store.tryGet(StoreKey.serverUrl);
|
final serverUrl = Store.tryGet(StoreKey.serverUrl);
|
||||||
|
final endpoint = Store.tryGet(StoreKey.serverEndpoint);
|
||||||
final accessToken = Store.tryGet(StoreKey.accessToken);
|
final accessToken = Store.tryGet(StoreKey.accessToken);
|
||||||
final log = Logger("SplashScreenPage");
|
final log = Logger("SplashScreenPage");
|
||||||
|
|
||||||
void performLoggingIn() async {
|
void performLoggingIn() async {
|
||||||
bool isSuccess = false;
|
bool isAuthSuccess = false;
|
||||||
bool deviceIsOffline = false;
|
|
||||||
|
|
||||||
if (accessToken != null && serverUrl != null) {
|
if (accessToken != null && serverUrl != null && endpoint != null) {
|
||||||
try {
|
apiService.setEndpoint(endpoint);
|
||||||
// Resolve API server endpoint from user provided serverUrl
|
|
||||||
await apiService.resolveAndSetEndpoint(serverUrl);
|
|
||||||
} on ApiException catch (error, stackTrace) {
|
|
||||||
log.severe(
|
|
||||||
"Failed to resolve endpoint [ApiException]",
|
|
||||||
error,
|
|
||||||
stackTrace,
|
|
||||||
);
|
|
||||||
// okay, try to continue anyway if offline
|
|
||||||
if (error.code == 503) {
|
|
||||||
deviceIsOffline = true;
|
|
||||||
log.warning("Device seems to be offline upon launch");
|
|
||||||
} else {
|
|
||||||
log.severe("Failed to resolve endpoint", error);
|
|
||||||
}
|
|
||||||
} catch (error, stackTrace) {
|
|
||||||
log.severe(
|
|
||||||
"Failed to resolve endpoint [Catch All]",
|
|
||||||
error,
|
|
||||||
stackTrace,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isSuccess = await ref
|
isAuthSuccess = await ref
|
||||||
.read(authenticationProvider.notifier)
|
.read(authenticationProvider.notifier)
|
||||||
.setSuccessLoginInfo(
|
.setSuccessLoginInfo(
|
||||||
accessToken: accessToken,
|
accessToken: accessToken,
|
||||||
serverUrl: serverUrl,
|
serverUrl: serverUrl,
|
||||||
offlineLogin: deviceIsOffline,
|
|
||||||
);
|
);
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
log.severe(
|
log.severe(
|
||||||
@@ -66,29 +43,29 @@ class SplashScreenPage extends HookConsumerWidget {
|
|||||||
stackTrace,
|
stackTrace,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
isAuthSuccess = false;
|
||||||
|
log.severe(
|
||||||
|
'Missing authentication, server, or endpoint info from the local store',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the device is offline and there is a currentUser stored locallly
|
if (!isAuthSuccess) {
|
||||||
// Proceed into the app
|
|
||||||
if (deviceIsOffline && Store.tryGet(StoreKey.currentUser) != null) {
|
|
||||||
context.replaceRoute(const TabControllerRoute());
|
|
||||||
} else if (isSuccess) {
|
|
||||||
// If device was able to login through the internet successfully
|
|
||||||
final hasPermission =
|
|
||||||
await ref.read(galleryPermissionNotifier.notifier).hasPermission;
|
|
||||||
if (hasPermission) {
|
|
||||||
// Resume backup (if enable) then navigate
|
|
||||||
ref.watch(backupProvider.notifier).resumeBackup();
|
|
||||||
}
|
|
||||||
context.replaceRoute(const TabControllerRoute());
|
|
||||||
} else {
|
|
||||||
log.severe(
|
log.severe(
|
||||||
'Unable to login through offline or online methods - logging out completely',
|
'Unable to login using offline or online methods - Logging out completely',
|
||||||
);
|
);
|
||||||
|
|
||||||
ref.read(authenticationProvider.notifier).logout();
|
ref.read(authenticationProvider.notifier).logout();
|
||||||
// User was unable to login through either offline or online methods
|
|
||||||
context.replaceRoute(const LoginRoute());
|
context.replaceRoute(const LoginRoute());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.replaceRoute(const TabControllerRoute());
|
||||||
|
|
||||||
|
final hasPermission =
|
||||||
|
await ref.read(galleryPermissionNotifier.notifier).hasPermission;
|
||||||
|
if (hasPermission) {
|
||||||
|
// Resume backup (if enable) then navigate
|
||||||
|
ref.watch(backupProvider.notifier).resumeBackup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
203
mobile/lib/pages/editing/crop.page.dart
Normal file
203
mobile/lib/pages/editing/crop.page.dart
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:crop_image/crop_image.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
import 'package:immich_mobile/utils/hooks/crop_controller_hook.dart';
|
||||||
|
import 'edit.page.dart';
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
|
||||||
|
/// A widget for cropping an image.
|
||||||
|
/// This widget uses [HookWidget] to manage its lifecycle and state. It allows
|
||||||
|
/// users to crop an image and then navigate to the [EditImagePage] with the
|
||||||
|
/// cropped image.
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
|
class CropImagePage extends HookWidget {
|
||||||
|
final Image image;
|
||||||
|
const CropImagePage({super.key, required this.image});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final cropController = useCropController();
|
||||||
|
final aspectRatio = useState<double?>(null);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Theme.of(context).bottomAppBarTheme.color,
|
||||||
|
leading: CloseButton(color: Theme.of(context).iconTheme.color),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.done_rounded,
|
||||||
|
color: Theme.of(context).iconTheme.color,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
final croppedImage = await cropController.croppedImage();
|
||||||
|
context.pushRoute(EditImageRoute(image: croppedImage));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: LayoutBuilder(
|
||||||
|
builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.only(top: 20),
|
||||||
|
width: double.infinity,
|
||||||
|
height: constraints.maxHeight * 0.6,
|
||||||
|
child: CropImage(
|
||||||
|
controller: cropController,
|
||||||
|
image: image,
|
||||||
|
gridColor: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).bottomAppBarTheme.color,
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(20),
|
||||||
|
topRight: Radius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 20,
|
||||||
|
right: 20,
|
||||||
|
bottom: 10,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.rotate_left,
|
||||||
|
color: Theme.of(context).iconTheme.color,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
cropController.rotateLeft();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.rotate_right,
|
||||||
|
color: Theme.of(context).iconTheme.color,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
cropController.rotateRight();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: <Widget>[
|
||||||
|
_AspectRatioButton(
|
||||||
|
cropController: cropController,
|
||||||
|
aspectRatio: aspectRatio,
|
||||||
|
ratio: null,
|
||||||
|
label: 'Free',
|
||||||
|
),
|
||||||
|
_AspectRatioButton(
|
||||||
|
cropController: cropController,
|
||||||
|
aspectRatio: aspectRatio,
|
||||||
|
ratio: 1.0,
|
||||||
|
label: '1:1',
|
||||||
|
),
|
||||||
|
_AspectRatioButton(
|
||||||
|
cropController: cropController,
|
||||||
|
aspectRatio: aspectRatio,
|
||||||
|
ratio: 16.0 / 9.0,
|
||||||
|
label: '16:9',
|
||||||
|
),
|
||||||
|
_AspectRatioButton(
|
||||||
|
cropController: cropController,
|
||||||
|
aspectRatio: aspectRatio,
|
||||||
|
ratio: 3.0 / 2.0,
|
||||||
|
label: '3:2',
|
||||||
|
),
|
||||||
|
_AspectRatioButton(
|
||||||
|
cropController: cropController,
|
||||||
|
aspectRatio: aspectRatio,
|
||||||
|
ratio: 7.0 / 5.0,
|
||||||
|
label: '7:5',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AspectRatioButton extends StatelessWidget {
|
||||||
|
final CropController cropController;
|
||||||
|
final ValueNotifier<double?> aspectRatio;
|
||||||
|
final double? ratio;
|
||||||
|
final String label;
|
||||||
|
|
||||||
|
const _AspectRatioButton({
|
||||||
|
required this.cropController,
|
||||||
|
required this.aspectRatio,
|
||||||
|
required this.ratio,
|
||||||
|
required this.label,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
IconData iconData;
|
||||||
|
switch (label) {
|
||||||
|
case 'Free':
|
||||||
|
iconData = Icons.crop_free_rounded;
|
||||||
|
break;
|
||||||
|
case '1:1':
|
||||||
|
iconData = Icons.crop_square_rounded;
|
||||||
|
break;
|
||||||
|
case '16:9':
|
||||||
|
iconData = Icons.crop_16_9_rounded;
|
||||||
|
break;
|
||||||
|
case '3:2':
|
||||||
|
iconData = Icons.crop_3_2_rounded;
|
||||||
|
break;
|
||||||
|
case '7:5':
|
||||||
|
iconData = Icons.crop_7_5_rounded;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
iconData = Icons.crop_free_rounded;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
iconData,
|
||||||
|
color: aspectRatio.value == ratio
|
||||||
|
? Colors.indigo
|
||||||
|
: Theme.of(context).iconTheme.color,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
aspectRatio.value = ratio;
|
||||||
|
cropController.aspectRatio = ratio;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Text(label, style: Theme.of(context).textTheme.bodyMedium),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
158
mobile/lib/pages/editing/edit.page.dart
Normal file
158
mobile/lib/pages/editing/edit.page.dart
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
|
import 'package:immich_mobile/widgets/common/immich_image.dart';
|
||||||
|
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
|
import 'package:immich_mobile/providers/album/album.provider.dart';
|
||||||
|
|
||||||
|
/// A stateless widget that provides functionality for editing an image.
|
||||||
|
///
|
||||||
|
/// This widget allows users to edit an image provided either as an [Asset] or
|
||||||
|
/// directly as an [Image]. It ensures that exactly one of these is provided.
|
||||||
|
///
|
||||||
|
/// It also includes a conversion method to convert an [Image] to a [Uint8List] to save the image on the user's phone
|
||||||
|
/// They automatically navigate to the [HomePage] with the edited image saved and they eventually get backed up to the server.
|
||||||
|
@immutable
|
||||||
|
@RoutePage()
|
||||||
|
class EditImagePage extends ConsumerWidget {
|
||||||
|
final Asset? asset;
|
||||||
|
final Image? image;
|
||||||
|
|
||||||
|
const EditImagePage({
|
||||||
|
super.key,
|
||||||
|
this.image,
|
||||||
|
this.asset,
|
||||||
|
}) : assert(
|
||||||
|
(image != null && asset == null) || (image == null && asset != null),
|
||||||
|
'Must supply one of asset or image',
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<Uint8List> _imageToUint8List(Image image) async {
|
||||||
|
final Completer<Uint8List> completer = Completer();
|
||||||
|
image.image.resolve(const ImageConfiguration()).addListener(
|
||||||
|
ImageStreamListener(
|
||||||
|
(ImageInfo info, bool _) {
|
||||||
|
info.image
|
||||||
|
.toByteData(format: ImageByteFormat.png)
|
||||||
|
.then((byteData) {
|
||||||
|
if (byteData != null) {
|
||||||
|
completer.complete(byteData.buffer.asUint8List());
|
||||||
|
} else {
|
||||||
|
completer.completeError('Failed to convert image to bytes');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (exception, stackTrace) =>
|
||||||
|
completer.completeError(exception),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final ImageProvider provider = (asset != null)
|
||||||
|
? ImmichImage.imageProvider(asset: asset!)
|
||||||
|
: (image != null)
|
||||||
|
? image!.image
|
||||||
|
: throw Exception('Invalid image source type');
|
||||||
|
|
||||||
|
final Image imageWidget = (asset != null)
|
||||||
|
? Image(image: ImmichImage.imageProvider(asset: asset!))
|
||||||
|
: (image != null)
|
||||||
|
? image!
|
||||||
|
: throw Exception('Invalid image source type');
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Theme.of(context).appBarTheme.backgroundColor,
|
||||||
|
leading: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.close_rounded,
|
||||||
|
color: Theme.of(context).iconTheme.color,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
onPressed: () =>
|
||||||
|
Navigator.of(context).popUntil((route) => route.isFirst),
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
if (image != null)
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
try {
|
||||||
|
final Uint8List imageData = await _imageToUint8List(image!);
|
||||||
|
ImmichToast.show(
|
||||||
|
durationInSecond: 3,
|
||||||
|
context: context,
|
||||||
|
msg: 'Image Saved!',
|
||||||
|
gravity: ToastGravity.CENTER,
|
||||||
|
);
|
||||||
|
|
||||||
|
await PhotoManager.editor
|
||||||
|
.saveImage(imageData, title: "_edited.jpg");
|
||||||
|
await ref.read(albumProvider.notifier).getDeviceAlbums();
|
||||||
|
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||||
|
} catch (e) {
|
||||||
|
ImmichToast.show(
|
||||||
|
durationInSecond: 6,
|
||||||
|
context: context,
|
||||||
|
msg: 'Error: ${e.toString()}',
|
||||||
|
gravity: ToastGravity.BOTTOM,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'Save to gallery',
|
||||||
|
style: Theme.of(context).textTheme.displayMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: Image(image: provider),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
height: 80,
|
||||||
|
color: Theme.of(context).bottomAppBarTheme.color,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
bottomNavigationBar: Container(
|
||||||
|
height: 80,
|
||||||
|
margin: const EdgeInsets.only(bottom: 20, right: 10, left: 10, top: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).bottomAppBarTheme.color,
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Platform.isAndroid
|
||||||
|
? Icons.crop_rotate_rounded
|
||||||
|
: Icons.crop_rotate_rounded,
|
||||||
|
color: Theme.of(context).iconTheme.color,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
context.pushRoute(CropImageRoute(image: imageWidget));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Text('Crop', style: Theme.of(context).textTheme.displayMedium),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
@@ -114,7 +115,7 @@ class SearchInputPage extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
peopleCurrentFilterWidget.value = Text(
|
peopleCurrentFilterWidget.value = Text(
|
||||||
value.map((e) => e.name != '' ? e.name : "No name").join(', '),
|
value.map((e) => e.name != '' ? e.name : 'no_name'.tr()).join(', '),
|
||||||
style: context.textTheme.labelLarge,
|
style: context.textTheme.labelLarge,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -134,7 +135,7 @@ class SearchInputPage extends HookConsumerWidget {
|
|||||||
child: FractionallySizedBox(
|
child: FractionallySizedBox(
|
||||||
heightFactor: 0.8,
|
heightFactor: 0.8,
|
||||||
child: FilterBottomSheetScaffold(
|
child: FilterBottomSheetScaffold(
|
||||||
title: 'Select people',
|
title: 'search_filter_people_title'.tr(),
|
||||||
expanded: true,
|
expanded: true,
|
||||||
onSearch: search,
|
onSearch: search,
|
||||||
onClear: handleClear,
|
onClear: handleClear,
|
||||||
@@ -190,7 +191,7 @@ class SearchInputPage extends HookConsumerWidget {
|
|||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
isDismissible: false,
|
isDismissible: false,
|
||||||
child: FilterBottomSheetScaffold(
|
child: FilterBottomSheetScaffold(
|
||||||
title: 'Select location',
|
title: 'search_filter_location_title'.tr(),
|
||||||
onSearch: search,
|
onSearch: search,
|
||||||
onClear: handleClear,
|
onClear: handleClear,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -241,7 +242,7 @@ class SearchInputPage extends HookConsumerWidget {
|
|||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
isDismissible: false,
|
isDismissible: false,
|
||||||
child: FilterBottomSheetScaffold(
|
child: FilterBottomSheetScaffold(
|
||||||
title: 'Select camera type',
|
title: 'search_filter_camera_title'.tr(),
|
||||||
onSearch: search,
|
onSearch: search,
|
||||||
onClear: handleClear,
|
onClear: handleClear,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -268,14 +269,14 @@ class SearchInputPage extends HookConsumerWidget {
|
|||||||
start: filter.value.date.takenAfter ?? lastDate,
|
start: filter.value.date.takenAfter ?? lastDate,
|
||||||
end: filter.value.date.takenBefore ?? lastDate,
|
end: filter.value.date.takenBefore ?? lastDate,
|
||||||
),
|
),
|
||||||
helpText: 'Select a date range',
|
helpText: 'search_filter_date_title'.tr(),
|
||||||
cancelText: 'Cancel',
|
cancelText: 'action_common_cancel'.tr(),
|
||||||
confirmText: 'Select',
|
confirmText: 'action_common_select'.tr(),
|
||||||
saveText: 'Save',
|
saveText: 'action_common_save'.tr(),
|
||||||
errorFormatText: 'Invalid date format',
|
errorFormatText: 'invalid_date_format'.tr(),
|
||||||
errorInvalidText: 'Invalid date',
|
errorInvalidText: 'invalid_date'.tr(),
|
||||||
fieldStartHintText: 'Start date',
|
fieldStartHintText: 'start_date'.tr(),
|
||||||
fieldEndHintText: 'End date',
|
fieldEndHintText: 'end_date'.tr(),
|
||||||
initialEntryMode: DatePickerEntryMode.input,
|
initialEntryMode: DatePickerEntryMode.input,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -305,12 +306,17 @@ class SearchInputPage extends HookConsumerWidget {
|
|||||||
// If date range is less than 24 hours, set the end date to the end of the day
|
// If date range is less than 24 hours, set the end date to the end of the day
|
||||||
if (date.end.difference(date.start).inHours < 24) {
|
if (date.end.difference(date.start).inHours < 24) {
|
||||||
dateRangeCurrentFilterWidget.value = Text(
|
dateRangeCurrentFilterWidget.value = Text(
|
||||||
date.start.toLocal().toIso8601String().split('T').first,
|
DateFormat.yMMMd().format(date.start.toLocal()),
|
||||||
style: context.textTheme.labelLarge,
|
style: context.textTheme.labelLarge,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
dateRangeCurrentFilterWidget.value = Text(
|
dateRangeCurrentFilterWidget.value = Text(
|
||||||
'${date.start.toLocal().toIso8601String().split('T').first} to ${date.end.toLocal().toIso8601String().split('T').first}',
|
'search_filter_date_interval'.tr(
|
||||||
|
namedArgs: {
|
||||||
|
"start": DateFormat.yMMMd().format(date.start.toLocal()),
|
||||||
|
"end": DateFormat.yMMMd().format(date.end.toLocal()),
|
||||||
|
},
|
||||||
|
),
|
||||||
style: context.textTheme.labelLarge,
|
style: context.textTheme.labelLarge,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -326,7 +332,11 @@ class SearchInputPage extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
mediaTypeCurrentFilterWidget.value = Text(
|
mediaTypeCurrentFilterWidget.value = Text(
|
||||||
assetType == AssetType.image ? 'Image' : 'Video',
|
assetType == AssetType.image
|
||||||
|
? 'search_filter_media_type_image'.tr()
|
||||||
|
: assetType == AssetType.video
|
||||||
|
? 'search_filter_media_type_video'.tr()
|
||||||
|
: 'search_filter_media_type_all'.tr(),
|
||||||
style: context.textTheme.labelLarge,
|
style: context.textTheme.labelLarge,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -343,7 +353,7 @@ class SearchInputPage extends HookConsumerWidget {
|
|||||||
showFilterBottomSheet(
|
showFilterBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
child: FilterBottomSheetScaffold(
|
child: FilterBottomSheetScaffold(
|
||||||
title: 'Select media type',
|
title: 'search_filter_media_type_title'.tr(),
|
||||||
onSearch: search,
|
onSearch: search,
|
||||||
onClear: handleClear,
|
onClear: handleClear,
|
||||||
child: MediaTypePicker(
|
child: MediaTypePicker(
|
||||||
@@ -367,7 +377,10 @@ class SearchInputPage extends HookConsumerWidget {
|
|||||||
isNotInAlbum: value,
|
isNotInAlbum: value,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (value) filterText.add('Not in album');
|
if (value) {
|
||||||
|
filterText
|
||||||
|
.add('search_filter_display_option_not_in_album'.tr());
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case DisplayOption.archive:
|
case DisplayOption.archive:
|
||||||
filter.value = filter.value.copyWith(
|
filter.value = filter.value.copyWith(
|
||||||
@@ -375,7 +388,9 @@ class SearchInputPage extends HookConsumerWidget {
|
|||||||
isArchive: value,
|
isArchive: value,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (value) filterText.add('Archive');
|
if (value) {
|
||||||
|
filterText.add('search_filter_display_option_archive'.tr());
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case DisplayOption.favorite:
|
case DisplayOption.favorite:
|
||||||
filter.value = filter.value.copyWith(
|
filter.value = filter.value.copyWith(
|
||||||
@@ -383,7 +398,9 @@ class SearchInputPage extends HookConsumerWidget {
|
|||||||
isFavorite: value,
|
isFavorite: value,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (value) filterText.add('Favorite');
|
if (value) {
|
||||||
|
filterText.add('search_filter_display_option_favorite'.tr());
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -410,7 +427,7 @@ class SearchInputPage extends HookConsumerWidget {
|
|||||||
showFilterBottomSheet(
|
showFilterBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
child: FilterBottomSheetScaffold(
|
child: FilterBottomSheetScaffold(
|
||||||
title: 'Display options',
|
title: 'search_filter_display_options_title'.tr(),
|
||||||
onSearch: search,
|
onSearch: search,
|
||||||
onClear: handleClear,
|
onClear: handleClear,
|
||||||
child: DisplayOptionPicker(
|
child: DisplayOptionPicker(
|
||||||
@@ -489,8 +506,8 @@ class SearchInputPage extends HookConsumerWidget {
|
|||||||
controller: textSearchController,
|
controller: textSearchController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: isContextualSearch.value
|
hintText: isContextualSearch.value
|
||||||
? 'Sunrise on the beach'
|
? 'contextual_search'.tr()
|
||||||
: 'File name or extension',
|
: 'filename_search'.tr(),
|
||||||
hintStyle: context.textTheme.bodyLarge?.copyWith(
|
hintStyle: context.textTheme.bodyLarge?.copyWith(
|
||||||
color: context.themeData.colorScheme.onSurface.withOpacity(0.75),
|
color: context.themeData.colorScheme.onSurface.withOpacity(0.75),
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
@@ -519,37 +536,37 @@ class SearchInputPage extends HookConsumerWidget {
|
|||||||
SearchFilterChip(
|
SearchFilterChip(
|
||||||
icon: Icons.people_alt_rounded,
|
icon: Icons.people_alt_rounded,
|
||||||
onTap: showPeoplePicker,
|
onTap: showPeoplePicker,
|
||||||
label: 'People',
|
label: 'search_filter_people'.tr(),
|
||||||
currentFilter: peopleCurrentFilterWidget.value,
|
currentFilter: peopleCurrentFilterWidget.value,
|
||||||
),
|
),
|
||||||
SearchFilterChip(
|
SearchFilterChip(
|
||||||
icon: Icons.location_pin,
|
icon: Icons.location_pin,
|
||||||
onTap: showLocationPicker,
|
onTap: showLocationPicker,
|
||||||
label: 'Location',
|
label: 'search_filter_location'.tr(),
|
||||||
currentFilter: locationCurrentFilterWidget.value,
|
currentFilter: locationCurrentFilterWidget.value,
|
||||||
),
|
),
|
||||||
SearchFilterChip(
|
SearchFilterChip(
|
||||||
icon: Icons.camera_alt_rounded,
|
icon: Icons.camera_alt_rounded,
|
||||||
onTap: showCameraPicker,
|
onTap: showCameraPicker,
|
||||||
label: 'Camera',
|
label: 'search_filter_camera'.tr(),
|
||||||
currentFilter: cameraCurrentFilterWidget.value,
|
currentFilter: cameraCurrentFilterWidget.value,
|
||||||
),
|
),
|
||||||
SearchFilterChip(
|
SearchFilterChip(
|
||||||
icon: Icons.date_range_rounded,
|
icon: Icons.date_range_rounded,
|
||||||
onTap: showDatePicker,
|
onTap: showDatePicker,
|
||||||
label: 'Date',
|
label: 'search_filter_date'.tr(),
|
||||||
currentFilter: dateRangeCurrentFilterWidget.value,
|
currentFilter: dateRangeCurrentFilterWidget.value,
|
||||||
),
|
),
|
||||||
SearchFilterChip(
|
SearchFilterChip(
|
||||||
icon: Icons.video_collection_outlined,
|
icon: Icons.video_collection_outlined,
|
||||||
onTap: showMediaTypePicker,
|
onTap: showMediaTypePicker,
|
||||||
label: 'Media Type',
|
label: 'search_filter_media_type'.tr(),
|
||||||
currentFilter: mediaTypeCurrentFilterWidget.value,
|
currentFilter: mediaTypeCurrentFilterWidget.value,
|
||||||
),
|
),
|
||||||
SearchFilterChip(
|
SearchFilterChip(
|
||||||
icon: Icons.display_settings_outlined,
|
icon: Icons.display_settings_outlined,
|
||||||
onTap: showDisplayOptionPicker,
|
onTap: showDisplayOptionPicker,
|
||||||
label: 'Display Options',
|
label: 'search_filter_display_options'.tr(),
|
||||||
currentFilter: displayOptionCurrentFilterWidget.value,
|
currentFilter: displayOptionCurrentFilterWidget.value,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -156,7 +156,6 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
|||||||
Future<bool> setSuccessLoginInfo({
|
Future<bool> setSuccessLoginInfo({
|
||||||
required String accessToken,
|
required String accessToken,
|
||||||
required String serverUrl,
|
required String serverUrl,
|
||||||
bool offlineLogin = false,
|
|
||||||
}) async {
|
}) async {
|
||||||
_apiService.setAccessToken(accessToken);
|
_apiService.setAccessToken(accessToken);
|
||||||
|
|
||||||
@@ -165,57 +164,56 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
|||||||
Store.tryGet(StoreKey.deviceId) ?? await FlutterUdid.consistentUdid;
|
Store.tryGet(StoreKey.deviceId) ?? await FlutterUdid.consistentUdid;
|
||||||
|
|
||||||
bool shouldChangePassword = false;
|
bool shouldChangePassword = false;
|
||||||
User? user;
|
User? user = Store.tryGet(StoreKey.currentUser);
|
||||||
|
|
||||||
bool retResult = false;
|
UserAdminResponseDto? userResponse;
|
||||||
User? offlineUser = Store.tryGet(StoreKey.currentUser);
|
UserPreferencesResponseDto? userPreferences;
|
||||||
|
try {
|
||||||
// If the user is offline and there is a user saved on the device,
|
var responses = await Future.wait([
|
||||||
// if not try an online login
|
_apiService.usersApi.getMyUser(),
|
||||||
if (offlineLogin && offlineUser != null) {
|
_apiService.usersApi.getMyPreferences(),
|
||||||
user = offlineUser;
|
]);
|
||||||
retResult = false;
|
userResponse = responses[0] as UserAdminResponseDto;
|
||||||
} else {
|
userPreferences = responses[1] as UserPreferencesResponseDto;
|
||||||
UserAdminResponseDto? userResponseDto;
|
} on ApiException catch (error, stackTrace) {
|
||||||
UserPreferencesResponseDto? userPreferences;
|
if (error.code == 401) {
|
||||||
try {
|
_log.severe("Unauthorized access, token likely expired. Logging out.");
|
||||||
userResponseDto = await _apiService.usersApi.getMyUser();
|
|
||||||
userPreferences = await _apiService.usersApi.getMyPreferences();
|
|
||||||
} on ApiException catch (error, stackTrace) {
|
|
||||||
_log.severe(
|
|
||||||
"Error getting user information from the server [API EXCEPTION]",
|
|
||||||
error,
|
|
||||||
stackTrace,
|
|
||||||
);
|
|
||||||
if (error.innerException is SocketException) {
|
|
||||||
state = state.copyWith(isAuthenticated: true);
|
|
||||||
}
|
|
||||||
} catch (error, stackTrace) {
|
|
||||||
_log.severe(
|
|
||||||
"Error getting user information from the server [CATCH ALL]",
|
|
||||||
error,
|
|
||||||
stackTrace,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userResponseDto != null) {
|
|
||||||
Store.put(StoreKey.deviceId, deviceId);
|
|
||||||
Store.put(StoreKey.deviceIdHash, fastHash(deviceId));
|
|
||||||
Store.put(
|
|
||||||
StoreKey.currentUser,
|
|
||||||
User.fromUserDto(userResponseDto, userPreferences),
|
|
||||||
);
|
|
||||||
Store.put(StoreKey.serverUrl, serverUrl);
|
|
||||||
Store.put(StoreKey.accessToken, accessToken);
|
|
||||||
|
|
||||||
shouldChangePassword = userResponseDto.shouldChangePassword;
|
|
||||||
user = User.fromUserDto(userResponseDto, userPreferences);
|
|
||||||
|
|
||||||
retResult = true;
|
|
||||||
} else {
|
|
||||||
_log.severe("Unable to get user information from the server.");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
_log.severe(
|
||||||
|
"Error getting user information from the server [API EXCEPTION]",
|
||||||
|
stackTrace,
|
||||||
|
);
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
_log.severe(
|
||||||
|
"Error getting user information from the server [CATCH ALL]",
|
||||||
|
error,
|
||||||
|
stackTrace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user information is successfully retrieved, update the store
|
||||||
|
// Due to the flow of the code, this will always happen on first login
|
||||||
|
if (userResponse != null) {
|
||||||
|
Store.put(StoreKey.deviceId, deviceId);
|
||||||
|
Store.put(StoreKey.deviceIdHash, fastHash(deviceId));
|
||||||
|
Store.put(
|
||||||
|
StoreKey.currentUser,
|
||||||
|
User.fromUserDto(userResponse, userPreferences),
|
||||||
|
);
|
||||||
|
Store.put(StoreKey.serverUrl, serverUrl);
|
||||||
|
Store.put(StoreKey.accessToken, accessToken);
|
||||||
|
|
||||||
|
shouldChangePassword = userResponse.shouldChangePassword;
|
||||||
|
user = User.fromUserDto(userResponse, userPreferences);
|
||||||
|
} else {
|
||||||
|
_log.severe("Unable to get user information from the server.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user is null, the login was not successful
|
||||||
|
// and we don't have a local copy of the user from a prior successful login
|
||||||
|
if (user == null) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
@@ -229,7 +227,7 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
|||||||
deviceId: deviceId,
|
deviceId: deviceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
return retResult;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ import 'package:immich_mobile/pages/common/headers_settings.page.dart';
|
|||||||
import 'package:immich_mobile/pages/common/settings.page.dart';
|
import 'package:immich_mobile/pages/common/settings.page.dart';
|
||||||
import 'package:immich_mobile/pages/common/splash_screen.page.dart';
|
import 'package:immich_mobile/pages/common/splash_screen.page.dart';
|
||||||
import 'package:immich_mobile/pages/common/tab_controller.page.dart';
|
import 'package:immich_mobile/pages/common/tab_controller.page.dart';
|
||||||
|
import 'package:immich_mobile/pages/editing/edit.page.dart';
|
||||||
|
import 'package:immich_mobile/pages/editing/crop.page.dart';
|
||||||
import 'package:immich_mobile/pages/library/archive.page.dart';
|
import 'package:immich_mobile/pages/library/archive.page.dart';
|
||||||
import 'package:immich_mobile/pages/library/favorite.page.dart';
|
import 'package:immich_mobile/pages/library/favorite.page.dart';
|
||||||
import 'package:immich_mobile/pages/library/library.page.dart';
|
import 'package:immich_mobile/pages/library/library.page.dart';
|
||||||
@@ -133,6 +135,8 @@ class AppRouter extends _$AppRouter {
|
|||||||
page: CreateAlbumRoute.page,
|
page: CreateAlbumRoute.page,
|
||||||
guards: [_authGuard, _duplicateGuard],
|
guards: [_authGuard, _duplicateGuard],
|
||||||
),
|
),
|
||||||
|
AutoRoute(page: EditImageRoute.page),
|
||||||
|
AutoRoute(page: CropImageRoute.page),
|
||||||
AutoRoute(page: FavoritesRoute.page, guards: [_authGuard, _duplicateGuard]),
|
AutoRoute(page: FavoritesRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||||
AutoRoute(page: AllVideosRoute.page, guards: [_authGuard, _duplicateGuard]),
|
AutoRoute(page: AllVideosRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||||
AutoRoute(
|
AutoRoute(
|
||||||
|
|||||||
@@ -165,6 +165,28 @@ abstract class _$AppRouter extends RootStackRouter {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
CropImageRoute.name: (routeData) {
|
||||||
|
final args = routeData.argsAs<CropImageRouteArgs>();
|
||||||
|
return AutoRoutePage<dynamic>(
|
||||||
|
routeData: routeData,
|
||||||
|
child: CropImagePage(
|
||||||
|
key: args.key,
|
||||||
|
image: args.image,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
EditImageRoute.name: (routeData) {
|
||||||
|
final args = routeData.argsAs<EditImageRouteArgs>(
|
||||||
|
orElse: () => const EditImageRouteArgs());
|
||||||
|
return AutoRoutePage<dynamic>(
|
||||||
|
routeData: routeData,
|
||||||
|
child: EditImagePage(
|
||||||
|
key: args.key,
|
||||||
|
image: args.image,
|
||||||
|
asset: args.asset,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
FailedBackupStatusRoute.name: (routeData) {
|
FailedBackupStatusRoute.name: (routeData) {
|
||||||
return AutoRoutePage<dynamic>(
|
return AutoRoutePage<dynamic>(
|
||||||
routeData: routeData,
|
routeData: routeData,
|
||||||
@@ -836,6 +858,87 @@ class CreateAlbumRouteArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// generated route for
|
||||||
|
/// [CropImagePage]
|
||||||
|
class CropImageRoute extends PageRouteInfo<CropImageRouteArgs> {
|
||||||
|
CropImageRoute({
|
||||||
|
Key? key,
|
||||||
|
required Image image,
|
||||||
|
List<PageRouteInfo>? children,
|
||||||
|
}) : super(
|
||||||
|
CropImageRoute.name,
|
||||||
|
args: CropImageRouteArgs(
|
||||||
|
key: key,
|
||||||
|
image: image,
|
||||||
|
),
|
||||||
|
initialChildren: children,
|
||||||
|
);
|
||||||
|
|
||||||
|
static const String name = 'CropImageRoute';
|
||||||
|
|
||||||
|
static const PageInfo<CropImageRouteArgs> page =
|
||||||
|
PageInfo<CropImageRouteArgs>(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
class CropImageRouteArgs {
|
||||||
|
const CropImageRouteArgs({
|
||||||
|
this.key,
|
||||||
|
required this.image,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Key? key;
|
||||||
|
|
||||||
|
final Image image;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'CropImageRouteArgs{key: $key, image: $image}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// generated route for
|
||||||
|
/// [EditImagePage]
|
||||||
|
class EditImageRoute extends PageRouteInfo<EditImageRouteArgs> {
|
||||||
|
EditImageRoute({
|
||||||
|
Key? key,
|
||||||
|
Image? image,
|
||||||
|
Asset? asset,
|
||||||
|
List<PageRouteInfo>? children,
|
||||||
|
}) : super(
|
||||||
|
EditImageRoute.name,
|
||||||
|
args: EditImageRouteArgs(
|
||||||
|
key: key,
|
||||||
|
image: image,
|
||||||
|
asset: asset,
|
||||||
|
),
|
||||||
|
initialChildren: children,
|
||||||
|
);
|
||||||
|
|
||||||
|
static const String name = 'EditImageRoute';
|
||||||
|
|
||||||
|
static const PageInfo<EditImageRouteArgs> page =
|
||||||
|
PageInfo<EditImageRouteArgs>(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
class EditImageRouteArgs {
|
||||||
|
const EditImageRouteArgs({
|
||||||
|
this.key,
|
||||||
|
this.image,
|
||||||
|
this.asset,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Key? key;
|
||||||
|
|
||||||
|
final Image? image;
|
||||||
|
|
||||||
|
final Asset? asset;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'EditImageRouteArgs{key: $key, image: $image, asset: $asset}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [FailedBackupStatusPage]
|
/// [FailedBackupStatusPage]
|
||||||
class FailedBackupStatusRoute extends PageRouteInfo<void> {
|
class FailedBackupStatusRoute extends PageRouteInfo<void> {
|
||||||
|
|||||||
@@ -64,11 +64,13 @@ class ApiService implements Authentication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<String> resolveAndSetEndpoint(String serverUrl) async {
|
Future<String> resolveAndSetEndpoint(String serverUrl) async {
|
||||||
final endpoint = await _resolveEndpoint(serverUrl);
|
var endpoint = Store.tryGet(StoreKey.serverEndpoint);
|
||||||
|
|
||||||
|
endpoint ??= await _resolveEndpoint(serverUrl);
|
||||||
setEndpoint(endpoint);
|
setEndpoint(endpoint);
|
||||||
|
|
||||||
// Save in hivebox for next startup
|
// Save in hivebox for next startup
|
||||||
Store.put(StoreKey.serverEndpoint, endpoint);
|
await Store.put(StoreKey.serverEndpoint, endpoint);
|
||||||
return endpoint;
|
return endpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
12
mobile/lib/utils/hooks/crop_controller_hook.dart
Normal file
12
mobile/lib/utils/hooks/crop_controller_hook.dart
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:crop_image/crop_image.dart';
|
||||||
|
import 'dart:ui'; // Import the dart:ui library for Rect
|
||||||
|
|
||||||
|
/// A hook that provides a [CropController] instance.
|
||||||
|
CropController useCropController() {
|
||||||
|
return useMemoized(
|
||||||
|
() => CropController(
|
||||||
|
defaultCrop: const Rect.fromLTRB(0.1, 0.1, 0.9, 0.9),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -25,9 +25,7 @@ class HttpSSLCertOverride extends HttpOverrides {
|
|||||||
try {
|
try {
|
||||||
_log.info("Setting client certificate");
|
_log.info("Setting client certificate");
|
||||||
ctx.usePrivateKeyBytes(cert.data, password: cert.password);
|
ctx.usePrivateKeyBytes(cert.data, password: cert.password);
|
||||||
if (!Platform.isIOS) {
|
ctx.useCertificateChainBytes(cert.data, password: cert.password);
|
||||||
ctx.useCertificateChainBytes(cert.data, password: cert.password);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_log.severe("Failed to set SSL client cert: $e");
|
_log.severe("Failed to set SSL client cert: $e");
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import 'package:immich_mobile/providers/asset.provider.dart';
|
|||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||||
|
import 'package:immich_mobile/pages/editing/edit.page.dart';
|
||||||
|
|
||||||
class BottomGalleryBar extends ConsumerWidget {
|
class BottomGalleryBar extends ConsumerWidget {
|
||||||
final Asset asset;
|
final Asset asset;
|
||||||
@@ -69,6 +70,12 @@ class BottomGalleryBar extends ConsumerWidget {
|
|||||||
label: 'control_bottom_app_bar_share'.tr(),
|
label: 'control_bottom_app_bar_share'.tr(),
|
||||||
tooltip: 'control_bottom_app_bar_share'.tr(),
|
tooltip: 'control_bottom_app_bar_share'.tr(),
|
||||||
),
|
),
|
||||||
|
if (asset.isImage)
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: const Icon(Icons.edit_outlined),
|
||||||
|
label: 'control_bottom_app_bar_edit'.tr(),
|
||||||
|
tooltip: 'control_bottom_app_bar_edit'.tr(),
|
||||||
|
),
|
||||||
if (isOwner)
|
if (isOwner)
|
||||||
asset.isArchived
|
asset.isArchived
|
||||||
? BottomNavigationBarItem(
|
? BottomNavigationBarItem(
|
||||||
@@ -280,6 +287,24 @@ class BottomGalleryBar extends ConsumerWidget {
|
|||||||
ref.read(imageViewerStateProvider.notifier).shareAsset(asset, context);
|
ref.read(imageViewerStateProvider.notifier).shareAsset(asset, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void handleEdit() async {
|
||||||
|
if (asset.isOffline) {
|
||||||
|
ImmichToast.show(
|
||||||
|
durationInSecond: 1,
|
||||||
|
context: context,
|
||||||
|
msg: 'asset_action_edit_err_offline'.tr(),
|
||||||
|
gravity: ToastGravity.BOTTOM,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) =>
|
||||||
|
EditImagePage(asset: asset), // Send the Asset object
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
handleArchive() {
|
handleArchive() {
|
||||||
ref.read(assetProvider.notifier).toggleArchive([asset]);
|
ref.read(assetProvider.notifier).toggleArchive([asset]);
|
||||||
if (isParent) {
|
if (isParent) {
|
||||||
@@ -343,6 +368,7 @@ class BottomGalleryBar extends ConsumerWidget {
|
|||||||
|
|
||||||
List<Function(int)> actionslist = [
|
List<Function(int)> actionslist = [
|
||||||
(_) => shareAsset(),
|
(_) => shareAsset(),
|
||||||
|
if (asset.isImage) (_) => handleEdit(),
|
||||||
if (isOwner) (_) => handleArchive(),
|
if (isOwner) (_) => handleArchive(),
|
||||||
if (isOwner && stack.isNotEmpty) (_) => showStackActionItems(),
|
if (isOwner && stack.isNotEmpty) (_) => showStackActionItems(),
|
||||||
if (isOwner) (_) => handleDelete(),
|
if (isOwner) (_) => handleDelete(),
|
||||||
|
|||||||
@@ -273,6 +273,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.1"
|
version: "3.1.1"
|
||||||
|
crop_image:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: crop_image
|
||||||
|
sha256: "6cf20655ecbfba99c369d43ec7adcfa49bf135af88fb75642173d6224a95d3f1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.13"
|
||||||
cross_file:
|
cross_file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1813,4 +1821,4 @@ packages:
|
|||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.4.0 <4.0.0"
|
dart: ">=3.4.0 <4.0.0"
|
||||||
flutter: ">=3.22.2"
|
flutter: ">=3.22.3"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ version: 1.110.0+151
|
|||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.3.0 <4.0.0'
|
sdk: '>=3.3.0 <4.0.0'
|
||||||
flutter: 3.22.2
|
flutter: 3.22.3
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
@@ -62,6 +62,8 @@ dependencies:
|
|||||||
thumbhash: 0.1.0+1
|
thumbhash: 0.1.0+1
|
||||||
async: ^2.11.0
|
async: ^2.11.0
|
||||||
|
|
||||||
|
#image editing packages
|
||||||
|
crop_image: ^1.0.13
|
||||||
openapi:
|
openapi:
|
||||||
path: openapi
|
path: openapi
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
20.15.1
|
20.16.0
|
||||||
|
|||||||
@@ -28,6 +28,6 @@
|
|||||||
"directory": "open-api/typescript-sdk"
|
"directory": "open-api/typescript-sdk"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "20.15.1"
|
"node": "20.16.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
20.15.1
|
20.16.0
|
||||||
|
|||||||
261
server/package-lock.json
generated
261
server/package-lock.json
generated
@@ -60,6 +60,7 @@
|
|||||||
"semver": "^7.6.2",
|
"semver": "^7.6.2",
|
||||||
"sharp": "^0.33.0",
|
"sharp": "^0.33.0",
|
||||||
"sirv": "^2.0.4",
|
"sirv": "^2.0.4",
|
||||||
|
"tailwindcss-preset-email": "^1.3.2",
|
||||||
"thumbhash": "^0.1.1",
|
"thumbhash": "^0.1.1",
|
||||||
"typeorm": "^0.3.17",
|
"typeorm": "^0.3.17",
|
||||||
"ua-parser-js": "^1.0.35"
|
"ua-parser-js": "^1.0.35"
|
||||||
@@ -82,7 +83,7 @@
|
|||||||
"@types/multer": "^1.4.7",
|
"@types/multer": "^1.4.7",
|
||||||
"@types/node": "^20.14.12",
|
"@types/node": "^20.14.12",
|
||||||
"@types/nodemailer": "^6.4.14",
|
"@types/nodemailer": "^6.4.14",
|
||||||
"@types/picomatch": "^2.3.3",
|
"@types/picomatch": "^3.0.0",
|
||||||
"@types/semver": "^7.5.8",
|
"@types/semver": "^7.5.8",
|
||||||
"@types/supertest": "^6.0.0",
|
"@types/supertest": "^6.0.0",
|
||||||
"@types/ua-parser-js": "^0.7.36",
|
"@types/ua-parser-js": "^0.7.36",
|
||||||
@@ -103,7 +104,8 @@
|
|||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"unplugin-swc": "^1.4.5",
|
"unplugin-swc": "^1.4.5",
|
||||||
"utimes": "^5.2.1",
|
"utimes": "^5.2.1",
|
||||||
"vitest": "^1.5.0"
|
"vite-tsconfig-paths": "^4.3.2",
|
||||||
|
"vitest": "^1.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@aashutoshrathi/word-wrap": {
|
"node_modules/@aashutoshrathi/word-wrap": {
|
||||||
@@ -6309,9 +6311,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/picomatch": {
|
"node_modules/@types/picomatch": {
|
||||||
"version": "2.3.4",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-2.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-3.0.0.tgz",
|
||||||
"integrity": "sha512-0so8lU8O5zatZS/2Fi4zrwks+vZv7e0dygrgEZXljODXBig97l4cPQD+9LabXfGJOWwoRkTVz6Q4edZvD12UOA==",
|
"integrity": "sha512-iX/Qwk9vU17N/5Q7QrV46wzciloTdCqTRt6z8A7uFFADM2+Sy5oQh9ldZhAiTXH+l0sM/EkXatEhJIs8FUyOBQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/prismjs": {
|
"node_modules/@types/prismjs": {
|
||||||
@@ -10373,6 +10375,12 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/globrex": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz",
|
||||||
|
"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/gopd": {
|
"node_modules/gopd": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||||
@@ -13831,6 +13839,11 @@
|
|||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-email/node_modules/arg": {
|
||||||
|
"version": "5.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||||
|
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="
|
||||||
|
},
|
||||||
"node_modules/react-email/node_modules/brace-expansion": {
|
"node_modules/react-email/node_modules/brace-expansion": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||||
@@ -13981,6 +13994,53 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-email/node_modules/tailwindcss": {
|
||||||
|
"version": "3.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.0.tgz",
|
||||||
|
"integrity": "sha512-VigzymniH77knD1dryXbyxR+ePHihHociZbXnLZHUyzf2MMs2ZVqlUrZ3FvpXP8pno9JzmILt1sZPD19M3IxtA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@alloc/quick-lru": "^5.2.0",
|
||||||
|
"arg": "^5.0.2",
|
||||||
|
"chokidar": "^3.5.3",
|
||||||
|
"didyoumean": "^1.2.2",
|
||||||
|
"dlv": "^1.1.3",
|
||||||
|
"fast-glob": "^3.3.0",
|
||||||
|
"glob-parent": "^6.0.2",
|
||||||
|
"is-glob": "^4.0.3",
|
||||||
|
"jiti": "^1.19.1",
|
||||||
|
"lilconfig": "^2.1.0",
|
||||||
|
"micromatch": "^4.0.5",
|
||||||
|
"normalize-path": "^3.0.0",
|
||||||
|
"object-hash": "^3.0.0",
|
||||||
|
"picocolors": "^1.0.0",
|
||||||
|
"postcss": "^8.4.23",
|
||||||
|
"postcss-import": "^15.1.0",
|
||||||
|
"postcss-js": "^4.0.1",
|
||||||
|
"postcss-load-config": "^4.0.1",
|
||||||
|
"postcss-nested": "^6.0.1",
|
||||||
|
"postcss-selector-parser": "^6.0.11",
|
||||||
|
"resolve": "^1.22.2",
|
||||||
|
"sucrase": "^3.32.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"tailwind": "lib/cli.js",
|
||||||
|
"tailwindcss": "lib/cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-email/node_modules/tailwindcss/node_modules/glob-parent": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
||||||
|
"dependencies": {
|
||||||
|
"is-glob": "^4.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-email/node_modules/typescript": {
|
"node_modules/react-email/node_modules/typescript": {
|
||||||
"version": "5.1.6",
|
"version": "5.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
|
||||||
@@ -15507,9 +15567,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "3.4.0",
|
"version": "3.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.6.tgz",
|
||||||
"integrity": "sha512-VigzymniH77knD1dryXbyxR+ePHihHociZbXnLZHUyzf2MMs2ZVqlUrZ3FvpXP8pno9JzmILt1sZPD19M3IxtA==",
|
"integrity": "sha512-1uRHzPB+Vzu57ocybfZ4jh5Q3SdlH7XW23J5sQoM9LhE9eIOlzxer/3XPSsycvih3rboRsvt0QCmzSrqyOYUIA==",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alloc/quick-lru": "^5.2.0",
|
"@alloc/quick-lru": "^5.2.0",
|
||||||
"arg": "^5.0.2",
|
"arg": "^5.0.2",
|
||||||
@@ -15519,7 +15580,7 @@
|
|||||||
"fast-glob": "^3.3.0",
|
"fast-glob": "^3.3.0",
|
||||||
"glob-parent": "^6.0.2",
|
"glob-parent": "^6.0.2",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
"jiti": "^1.19.1",
|
"jiti": "^1.21.0",
|
||||||
"lilconfig": "^2.1.0",
|
"lilconfig": "^2.1.0",
|
||||||
"micromatch": "^4.0.5",
|
"micromatch": "^4.0.5",
|
||||||
"normalize-path": "^3.0.0",
|
"normalize-path": "^3.0.0",
|
||||||
@@ -15542,15 +15603,48 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tailwindcss-email-variants": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tailwindcss-email-variants/-/tailwindcss-email-variants-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-bRk4R2jnfaW7BBaL2kDgOdBl0SpVP/JPDE/yCkZb1n3YrPK9ZQyQGZoVX3OX06GxjMOrNO3wZACVdHJce7dm8w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"tailwindcss": ">=3.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tailwindcss-mso": {
|
||||||
|
"version": "1.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tailwindcss-mso/-/tailwindcss-mso-1.4.3.tgz",
|
||||||
|
"integrity": "sha512-8YfZ4xnIComDrhoSr8FUwm7EGz1FkxsZy07Fs4Jm/JxHrFiubdiZjyxLuHMc3S8o02+U4fjRGHPOzoVXRus10A==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"tailwindcss": ">=3.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tailwindcss-preset-email": {
|
||||||
|
"version": "1.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/tailwindcss-preset-email/-/tailwindcss-preset-email-1.3.2.tgz",
|
||||||
|
"integrity": "sha512-kSPNZM5+tSi+uhCb4rk1XF9Q6zp8lhoNLCa3GQqe6gKmfI/nTqY8Y+5/DYNpwqhmUPCSHULlyI/LUCaF/q8sLg==",
|
||||||
|
"dependencies": {
|
||||||
|
"tailwindcss-email-variants": "^3.0.0",
|
||||||
|
"tailwindcss-mso": "^1.4.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"tailwindcss": ">=3.4.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tailwindcss/node_modules/arg": {
|
"node_modules/tailwindcss/node_modules/arg": {
|
||||||
"version": "5.0.2",
|
"version": "5.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||||
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="
|
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss/node_modules/glob-parent": {
|
"node_modules/tailwindcss/node_modules/glob-parent": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||||
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-glob": "^4.0.3"
|
"is-glob": "^4.0.3"
|
||||||
},
|
},
|
||||||
@@ -16147,6 +16241,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tsconfck": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-00eoI6WY57SvZEVjm13stEVE90VkEdJAFGgpFLTsZbJyW/LwFQ7uQxJHWpZ2hzSWgCPKc9AnBnNP+0X7o3hAmQ==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"tsconfck": "bin/tsconfck.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18 || >=20"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tsconfig-paths": {
|
"node_modules/tsconfig-paths": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz",
|
||||||
@@ -16792,6 +16906,25 @@
|
|||||||
"url": "https://opencollective.com/vitest"
|
"url": "https://opencollective.com/vitest"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vite-tsconfig-paths": {
|
||||||
|
"version": "4.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz",
|
||||||
|
"integrity": "sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "^4.1.1",
|
||||||
|
"globrex": "^0.1.2",
|
||||||
|
"tsconfck": "^3.0.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vite": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"vite": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vitest": {
|
"node_modules/vitest": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz",
|
||||||
@@ -21055,9 +21188,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/picomatch": {
|
"@types/picomatch": {
|
||||||
"version": "2.3.4",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-2.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-3.0.0.tgz",
|
||||||
"integrity": "sha512-0so8lU8O5zatZS/2Fi4zrwks+vZv7e0dygrgEZXljODXBig97l4cPQD+9LabXfGJOWwoRkTVz6Q4edZvD12UOA==",
|
"integrity": "sha512-iX/Qwk9vU17N/5Q7QrV46wzciloTdCqTRt6z8A7uFFADM2+Sy5oQh9ldZhAiTXH+l0sM/EkXatEhJIs8FUyOBQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/prismjs": {
|
"@types/prismjs": {
|
||||||
@@ -24050,6 +24183,12 @@
|
|||||||
"slash": "^3.0.0"
|
"slash": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"globrex": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz",
|
||||||
|
"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"gopd": {
|
"gopd": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||||
@@ -26373,6 +26512,11 @@
|
|||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"arg": {
|
||||||
|
"version": "5.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||||
|
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="
|
||||||
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||||
@@ -26476,6 +26620,45 @@
|
|||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||||
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
|
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
|
||||||
},
|
},
|
||||||
|
"tailwindcss": {
|
||||||
|
"version": "3.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.0.tgz",
|
||||||
|
"integrity": "sha512-VigzymniH77knD1dryXbyxR+ePHihHociZbXnLZHUyzf2MMs2ZVqlUrZ3FvpXP8pno9JzmILt1sZPD19M3IxtA==",
|
||||||
|
"requires": {
|
||||||
|
"@alloc/quick-lru": "^5.2.0",
|
||||||
|
"arg": "^5.0.2",
|
||||||
|
"chokidar": "^3.5.3",
|
||||||
|
"didyoumean": "^1.2.2",
|
||||||
|
"dlv": "^1.1.3",
|
||||||
|
"fast-glob": "^3.3.0",
|
||||||
|
"glob-parent": "^6.0.2",
|
||||||
|
"is-glob": "^4.0.3",
|
||||||
|
"jiti": "^1.19.1",
|
||||||
|
"lilconfig": "^2.1.0",
|
||||||
|
"micromatch": "^4.0.5",
|
||||||
|
"normalize-path": "^3.0.0",
|
||||||
|
"object-hash": "^3.0.0",
|
||||||
|
"picocolors": "^1.0.0",
|
||||||
|
"postcss": "^8.4.23",
|
||||||
|
"postcss-import": "^15.1.0",
|
||||||
|
"postcss-js": "^4.0.1",
|
||||||
|
"postcss-load-config": "^4.0.1",
|
||||||
|
"postcss-nested": "^6.0.1",
|
||||||
|
"postcss-selector-parser": "^6.0.11",
|
||||||
|
"resolve": "^1.22.2",
|
||||||
|
"sucrase": "^3.32.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"glob-parent": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
||||||
|
"requires": {
|
||||||
|
"is-glob": "^4.0.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"typescript": {
|
"typescript": {
|
||||||
"version": "5.1.6",
|
"version": "5.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
|
||||||
@@ -27591,9 +27774,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tailwindcss": {
|
"tailwindcss": {
|
||||||
"version": "3.4.0",
|
"version": "3.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.6.tgz",
|
||||||
"integrity": "sha512-VigzymniH77knD1dryXbyxR+ePHihHociZbXnLZHUyzf2MMs2ZVqlUrZ3FvpXP8pno9JzmILt1sZPD19M3IxtA==",
|
"integrity": "sha512-1uRHzPB+Vzu57ocybfZ4jh5Q3SdlH7XW23J5sQoM9LhE9eIOlzxer/3XPSsycvih3rboRsvt0QCmzSrqyOYUIA==",
|
||||||
|
"peer": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@alloc/quick-lru": "^5.2.0",
|
"@alloc/quick-lru": "^5.2.0",
|
||||||
"arg": "^5.0.2",
|
"arg": "^5.0.2",
|
||||||
@@ -27603,7 +27787,7 @@
|
|||||||
"fast-glob": "^3.3.0",
|
"fast-glob": "^3.3.0",
|
||||||
"glob-parent": "^6.0.2",
|
"glob-parent": "^6.0.2",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
"jiti": "^1.19.1",
|
"jiti": "^1.21.0",
|
||||||
"lilconfig": "^2.1.0",
|
"lilconfig": "^2.1.0",
|
||||||
"micromatch": "^4.0.5",
|
"micromatch": "^4.0.5",
|
||||||
"normalize-path": "^3.0.0",
|
"normalize-path": "^3.0.0",
|
||||||
@@ -27622,18 +27806,41 @@
|
|||||||
"arg": {
|
"arg": {
|
||||||
"version": "5.0.2",
|
"version": "5.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||||
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="
|
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"glob-parent": {
|
"glob-parent": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||||
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
||||||
|
"peer": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-glob": "^4.0.3"
|
"is-glob": "^4.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"tailwindcss-email-variants": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tailwindcss-email-variants/-/tailwindcss-email-variants-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-bRk4R2jnfaW7BBaL2kDgOdBl0SpVP/JPDE/yCkZb1n3YrPK9ZQyQGZoVX3OX06GxjMOrNO3wZACVdHJce7dm8w==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
|
"tailwindcss-mso": {
|
||||||
|
"version": "1.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tailwindcss-mso/-/tailwindcss-mso-1.4.3.tgz",
|
||||||
|
"integrity": "sha512-8YfZ4xnIComDrhoSr8FUwm7EGz1FkxsZy07Fs4Jm/JxHrFiubdiZjyxLuHMc3S8o02+U4fjRGHPOzoVXRus10A==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
|
"tailwindcss-preset-email": {
|
||||||
|
"version": "1.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/tailwindcss-preset-email/-/tailwindcss-preset-email-1.3.2.tgz",
|
||||||
|
"integrity": "sha512-kSPNZM5+tSi+uhCb4rk1XF9Q6zp8lhoNLCa3GQqe6gKmfI/nTqY8Y+5/DYNpwqhmUPCSHULlyI/LUCaF/q8sLg==",
|
||||||
|
"requires": {
|
||||||
|
"tailwindcss-email-variants": "^3.0.0",
|
||||||
|
"tailwindcss-mso": "^1.4.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"tapable": {
|
"tapable": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
|
||||||
@@ -28090,6 +28297,13 @@
|
|||||||
"yn": "3.1.1"
|
"yn": "3.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"tsconfck": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-00eoI6WY57SvZEVjm13stEVE90VkEdJAFGgpFLTsZbJyW/LwFQ7uQxJHWpZ2hzSWgCPKc9AnBnNP+0X7o3hAmQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"tsconfig-paths": {
|
"tsconfig-paths": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz",
|
||||||
@@ -28435,6 +28649,17 @@
|
|||||||
"vite": "^5.0.0"
|
"vite": "^5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"vite-tsconfig-paths": {
|
||||||
|
"version": "4.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz",
|
||||||
|
"integrity": "sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"debug": "^4.1.1",
|
||||||
|
"globrex": "^0.1.2",
|
||||||
|
"tsconfck": "^3.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"vitest": {
|
"vitest": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz",
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
"semver": "^7.6.2",
|
"semver": "^7.6.2",
|
||||||
"sharp": "^0.33.0",
|
"sharp": "^0.33.0",
|
||||||
"sirv": "^2.0.4",
|
"sirv": "^2.0.4",
|
||||||
|
"tailwindcss-preset-email": "^1.3.2",
|
||||||
"thumbhash": "^0.1.1",
|
"thumbhash": "^0.1.1",
|
||||||
"typeorm": "^0.3.17",
|
"typeorm": "^0.3.17",
|
||||||
"ua-parser-js": "^1.0.35"
|
"ua-parser-js": "^1.0.35"
|
||||||
@@ -108,7 +109,7 @@
|
|||||||
"@types/multer": "^1.4.7",
|
"@types/multer": "^1.4.7",
|
||||||
"@types/node": "^20.14.12",
|
"@types/node": "^20.14.12",
|
||||||
"@types/nodemailer": "^6.4.14",
|
"@types/nodemailer": "^6.4.14",
|
||||||
"@types/picomatch": "^2.3.3",
|
"@types/picomatch": "^3.0.0",
|
||||||
"@types/semver": "^7.5.8",
|
"@types/semver": "^7.5.8",
|
||||||
"@types/supertest": "^6.0.0",
|
"@types/supertest": "^6.0.0",
|
||||||
"@types/ua-parser-js": "^0.7.36",
|
"@types/ua-parser-js": "^0.7.36",
|
||||||
@@ -129,9 +130,10 @@
|
|||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"unplugin-swc": "^1.4.5",
|
"unplugin-swc": "^1.4.5",
|
||||||
"utimes": "^5.2.1",
|
"utimes": "^5.2.1",
|
||||||
"vitest": "^1.5.0"
|
"vitest": "^1.6.0",
|
||||||
|
"vite-tsconfig-paths": "^4.3.2"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "20.15.1"
|
"node": "20.16.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,8 @@
|
|||||||
import {
|
import { Img, Link, Section, Text } from '@react-email/components';
|
||||||
Body,
|
|
||||||
Button,
|
|
||||||
Column,
|
|
||||||
Container,
|
|
||||||
Head,
|
|
||||||
Hr,
|
|
||||||
Html,
|
|
||||||
Img,
|
|
||||||
Link,
|
|
||||||
Preview,
|
|
||||||
Row,
|
|
||||||
Section,
|
|
||||||
Text,
|
|
||||||
} from '@react-email/components';
|
|
||||||
import * as CSS from 'csstype';
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { AlbumInviteEmailProps } from 'src/interfaces/notification.interface';
|
import { AlbumInviteEmailProps } from 'src/interfaces/notification.interface';
|
||||||
|
import { ImmichButton } from './components/button.component';
|
||||||
|
import ImmichLayout from './components/immich.layout';
|
||||||
|
|
||||||
export const AlbumInviteEmail = ({
|
export const AlbumInviteEmail = ({
|
||||||
baseUrl,
|
baseUrl,
|
||||||
@@ -25,122 +12,37 @@ export const AlbumInviteEmail = ({
|
|||||||
albumId,
|
albumId,
|
||||||
cid,
|
cid,
|
||||||
}: AlbumInviteEmailProps) => (
|
}: AlbumInviteEmailProps) => (
|
||||||
<Html>
|
<ImmichLayout preview="You have been added to a shared album.">
|
||||||
<Head />
|
<Text className="m-0">
|
||||||
<Preview>You have been added to a shared album.</Preview>
|
Hey <strong>{recipientName}</strong>!
|
||||||
<Body
|
</Text>
|
||||||
style={{
|
|
||||||
margin: 0,
|
<Text>
|
||||||
padding: 0,
|
{senderName} has added you to the album <strong>{albumName}</strong>.
|
||||||
backgroundColor: '#ffffff',
|
</Text>
|
||||||
color: 'rgb(28,28,28)',
|
|
||||||
fontFamily: 'Overpass, sans-serif',
|
{cid && (
|
||||||
fontSize: '18px',
|
<Section className="flex justify-center my-0">
|
||||||
lineHeight: '24px',
|
<Img
|
||||||
}}
|
className="max-w-[300px] w-full rounded-lg"
|
||||||
>
|
src={`cid:${cid}`}
|
||||||
<Container
|
|
||||||
style={{
|
|
||||||
width: '540px',
|
|
||||||
maxWidth: '100%',
|
|
||||||
padding: '10px',
|
|
||||||
margin: '0 auto',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Section
|
|
||||||
style={{
|
style={{
|
||||||
padding: '36px',
|
boxShadow: 'rgba(50, 50, 93, 0.25) 0px 13px 27px -5px, rgba(0, 0, 0, 0.3) 0px 8px 16px -8px',
|
||||||
tableLayout: 'fixed',
|
|
||||||
backgroundColor: 'rgb(226, 232, 240)',
|
|
||||||
border: 'solid 0px rgb(248 113 113)',
|
|
||||||
borderRadius: '50px',
|
|
||||||
textAlign: 'center' as const,
|
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<Img
|
</Section>
|
||||||
src="https://immich.app/img/immich-logo-inline-light.png"
|
)}
|
||||||
alt="Immich"
|
|
||||||
style={{
|
|
||||||
height: 'auto',
|
|
||||||
margin: '0 auto 48px auto',
|
|
||||||
width: '50%',
|
|
||||||
alignSelf: 'center',
|
|
||||||
color: 'white',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Text style={text}>Hey {recipientName}!</Text>
|
<Section className="flex justify-center my-6">
|
||||||
|
<ImmichButton href={`${baseUrl}/albums/${albumId}`}>View Album</ImmichButton>
|
||||||
|
</Section>
|
||||||
|
|
||||||
<Text style={text}>
|
<Text className="text-xs">
|
||||||
{senderName} has added you to the album <strong>{albumName}</strong>.
|
If you cannot click the button use the link below to view the album.
|
||||||
</Text>
|
<br />
|
||||||
|
<Link href={`${baseUrl}/albums/${albumId}`}>{`${baseUrl}/albums/${albumId}`}</Link>
|
||||||
{cid && (
|
</Text>
|
||||||
<Row>
|
</ImmichLayout>
|
||||||
<Column align="center">
|
|
||||||
<Img
|
|
||||||
src={`cid:${cid}`}
|
|
||||||
width="300"
|
|
||||||
style={{
|
|
||||||
borderRadius: '20px',
|
|
||||||
boxShadow: 'rgba(50, 50, 93, 0.25) 0px 13px 27px -5px, rgba(0, 0, 0, 0.3) 0px 8px 16px -8px',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Column>
|
|
||||||
</Row>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Row style={{ marginBottom: '36px', marginTop: '36px' }}>
|
|
||||||
<Text style={{ ...text }}>To view the album, open the link in a browser, or click the button below.</Text>
|
|
||||||
</Row>
|
|
||||||
<Row>
|
|
||||||
<Column align="center">
|
|
||||||
<Link style={{ marginTop: '50px' }} href={`${baseUrl}/albums/${albumId}`}>
|
|
||||||
{baseUrl}/albums/{albumId}
|
|
||||||
</Link>
|
|
||||||
</Column>
|
|
||||||
</Row>
|
|
||||||
<Row>
|
|
||||||
<Column align="center">
|
|
||||||
<Button style={button} href={`${baseUrl}/albums/${albumId}`}>
|
|
||||||
View album
|
|
||||||
</Button>
|
|
||||||
</Column>
|
|
||||||
</Row>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Hr style={{ color: 'rgb(66, 80, 175)', marginTop: '24px' }} />
|
|
||||||
|
|
||||||
<Section style={{ textAlign: 'center' }}>
|
|
||||||
<Row>
|
|
||||||
<Column align="center">
|
|
||||||
<Link href="https://play.google.com/store/apps/details?id=app.alextran.immich">
|
|
||||||
<Img src={`https://immich.app/img/google-play-badge.png`} height="96px" alt="Immich" />
|
|
||||||
</Link>
|
|
||||||
<Link href="https://apps.apple.com/sg/app/immich/id1613945652">
|
|
||||||
<Img
|
|
||||||
src={`https://immich.app/img/ios-app-store-badge.png`}
|
|
||||||
alt="Immich"
|
|
||||||
style={{ height: '72px', padding: '14px' }}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
</Column>
|
|
||||||
</Row>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
color: '#6a737d',
|
|
||||||
fontSize: '0.8rem',
|
|
||||||
textAlign: 'center' as const,
|
|
||||||
marginTop: '12px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Link href="https://immich.app">Immich</Link> project is available under GNU AGPL v3 license.
|
|
||||||
</Text>
|
|
||||||
</Container>
|
|
||||||
</Body>
|
|
||||||
</Html>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
AlbumInviteEmail.PreviewProps = {
|
AlbumInviteEmail.PreviewProps = {
|
||||||
@@ -148,27 +50,7 @@ AlbumInviteEmail.PreviewProps = {
|
|||||||
albumName: 'Trip to Europe',
|
albumName: 'Trip to Europe',
|
||||||
albumId: 'b63f6dae-e1c9-401b-9a85-9dbbf5612539',
|
albumId: 'b63f6dae-e1c9-401b-9a85-9dbbf5612539',
|
||||||
senderName: 'Owner User',
|
senderName: 'Owner User',
|
||||||
recipientName: 'Guest User',
|
recipientName: 'Alan Turing',
|
||||||
cid: '',
|
|
||||||
} as AlbumInviteEmailProps;
|
} as AlbumInviteEmailProps;
|
||||||
|
|
||||||
export default AlbumInviteEmail;
|
export default AlbumInviteEmail;
|
||||||
|
|
||||||
const text = {
|
|
||||||
margin: '0 0 24px 0',
|
|
||||||
textAlign: 'left' as const,
|
|
||||||
fontSize: '18px',
|
|
||||||
lineHeight: '24px',
|
|
||||||
};
|
|
||||||
|
|
||||||
const button: CSS.Properties = {
|
|
||||||
backgroundColor: 'rgb(66, 80, 175)',
|
|
||||||
margin: '1em 0',
|
|
||||||
padding: '0.75em 3em',
|
|
||||||
color: '#fff',
|
|
||||||
fontSize: '1em',
|
|
||||||
fontWeight: 700,
|
|
||||||
lineHeight: 1.5,
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
borderRadius: '9999px',
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,165 +1,49 @@
|
|||||||
import {
|
import { Img, Link, Section, Text } from '@react-email/components';
|
||||||
Body,
|
|
||||||
Button,
|
|
||||||
Column,
|
|
||||||
Container,
|
|
||||||
Head,
|
|
||||||
Hr,
|
|
||||||
Html,
|
|
||||||
Img,
|
|
||||||
Link,
|
|
||||||
Preview,
|
|
||||||
Row,
|
|
||||||
Section,
|
|
||||||
Text,
|
|
||||||
} from '@react-email/components';
|
|
||||||
import * as CSS from 'csstype';
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { AlbumUpdateEmailProps } from 'src/interfaces/notification.interface';
|
import { AlbumUpdateEmailProps } from 'src/interfaces/notification.interface';
|
||||||
|
import { ImmichButton } from './components/button.component';
|
||||||
|
import ImmichLayout from './components/immich.layout';
|
||||||
|
|
||||||
export const AlbumUpdateEmail = ({ baseUrl, albumName, recipientName, albumId, cid }: AlbumUpdateEmailProps) => (
|
export const AlbumUpdateEmail = ({ baseUrl, albumName, recipientName, albumId, cid }: AlbumUpdateEmailProps) => (
|
||||||
<Html>
|
<ImmichLayout preview="New media has been added to a shared album.">
|
||||||
<Head />
|
<Text className="m-0">
|
||||||
<Preview>New media has been added to a shared album.</Preview>
|
Hey <strong>{recipientName}</strong>!
|
||||||
<Body
|
</Text>
|
||||||
style={{
|
|
||||||
margin: 0,
|
<Text>
|
||||||
padding: 0,
|
New media has been added to <strong>{albumName}</strong>,
|
||||||
backgroundColor: '#ffffff',
|
<br /> check it out!
|
||||||
color: 'rgb(28,28,28)',
|
</Text>
|
||||||
fontFamily: 'Overpass, sans-serif',
|
|
||||||
fontSize: '18px',
|
{cid && (
|
||||||
lineHeight: '24px',
|
<Section className="flex justify-center my-0">
|
||||||
}}
|
<Img
|
||||||
>
|
className="max-w-[300px] w-full rounded-lg"
|
||||||
<Container
|
src={`cid:${cid}`}
|
||||||
style={{
|
|
||||||
width: '540px',
|
|
||||||
maxWidth: '100%',
|
|
||||||
padding: '10px',
|
|
||||||
margin: '0 auto',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Section
|
|
||||||
style={{
|
style={{
|
||||||
padding: '36px',
|
boxShadow: 'rgba(50, 50, 93, 0.25) 0px 13px 27px -5px, rgba(0, 0, 0, 0.3) 0px 8px 16px -8px',
|
||||||
tableLayout: 'fixed',
|
|
||||||
backgroundColor: 'rgb(226, 232, 240)',
|
|
||||||
border: 'solid 0px rgb(248 113 113)',
|
|
||||||
borderRadius: '50px',
|
|
||||||
textAlign: 'center' as const,
|
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<Img
|
</Section>
|
||||||
src="https://immich.app/img/immich-logo-inline-light.png"
|
)}
|
||||||
alt="Immich"
|
|
||||||
style={{
|
|
||||||
height: 'auto',
|
|
||||||
margin: '0 auto 48px auto',
|
|
||||||
width: '50%',
|
|
||||||
alignSelf: 'center',
|
|
||||||
color: 'white',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Text style={text}>Hey {recipientName}!</Text>
|
<Section className="flex justify-center my-6">
|
||||||
|
<ImmichButton href={`${baseUrl}/albums/${albumId}`}>View Album</ImmichButton>
|
||||||
|
</Section>
|
||||||
|
|
||||||
<Text style={text}>
|
<Text className="text-xs">
|
||||||
New media has been added to <strong>{albumName}</strong>, check it out!
|
If you cannot click the button use the link below to view the album.
|
||||||
</Text>
|
<br />
|
||||||
|
<Link href={`${baseUrl}/albums/${albumId}`}>{`${baseUrl}/albums/${albumId}`}</Link>
|
||||||
{cid && (
|
</Text>
|
||||||
<Row>
|
</ImmichLayout>
|
||||||
<Column align="center">
|
|
||||||
<Img
|
|
||||||
src={`cid:${cid}`}
|
|
||||||
width="300"
|
|
||||||
style={{
|
|
||||||
borderRadius: '20px',
|
|
||||||
boxShadow: 'rgba(50, 50, 93, 0.25) 0px 13px 27px -5px, rgba(0, 0, 0, 0.3) 0px 8px 16px -8px',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Column>
|
|
||||||
</Row>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Row style={{ marginBottom: '36px', marginTop: '36px' }}>
|
|
||||||
<Text style={{ ...text }}>To view the album, open the link in a browser, or click the button below.</Text>
|
|
||||||
</Row>
|
|
||||||
<Row>
|
|
||||||
<Column align="center">
|
|
||||||
<Link style={{ marginTop: '50px' }} href={`${baseUrl}/albums/${albumId}`}>
|
|
||||||
{baseUrl}/albums/{albumId}
|
|
||||||
</Link>
|
|
||||||
</Column>
|
|
||||||
</Row>
|
|
||||||
<Row>
|
|
||||||
<Column align="center">
|
|
||||||
<Button style={button} href={`${baseUrl}/albums/${albumId}`}>
|
|
||||||
View album
|
|
||||||
</Button>
|
|
||||||
</Column>
|
|
||||||
</Row>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Hr style={{ color: 'rgb(66, 80, 175)', marginTop: '24px' }} />
|
|
||||||
|
|
||||||
<Section style={{ textAlign: 'center' }}>
|
|
||||||
<Row>
|
|
||||||
<Column align="center">
|
|
||||||
<Link href="https://play.google.com/store/apps/details?id=app.alextran.immich">
|
|
||||||
<Img src={`https://immich.app/img/google-play-badge.png`} height="96px" alt="Immich" />
|
|
||||||
</Link>
|
|
||||||
<Link href="https://apps.apple.com/sg/app/immich/id1613945652">
|
|
||||||
<Img
|
|
||||||
src={`https://immich.app/img/ios-app-store-badge.png`}
|
|
||||||
alt="Immich"
|
|
||||||
style={{ height: '72px', padding: '14px' }}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
</Column>
|
|
||||||
</Row>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
color: '#6a737d',
|
|
||||||
fontSize: '0.8rem',
|
|
||||||
textAlign: 'center' as const,
|
|
||||||
marginTop: '12px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Link href="https://immich.app">Immich</Link> project is available under GNU AGPL v3 license.
|
|
||||||
</Text>
|
|
||||||
</Container>
|
|
||||||
</Body>
|
|
||||||
</Html>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
AlbumUpdateEmail.PreviewProps = {
|
AlbumUpdateEmail.PreviewProps = {
|
||||||
baseUrl: 'https://demo.immich.app',
|
baseUrl: 'https://demo.immich.app',
|
||||||
albumName: 'Trip to Europe',
|
albumName: 'Trip to Europe',
|
||||||
albumId: 'b63f6dae-e1c9-401b-9a85-9dbbf5612539',
|
albumId: 'b63f6dae-e1c9-401b-9a85-9dbbf5612539',
|
||||||
recipientName: 'Alex Tran',
|
recipientName: 'Alan Turing',
|
||||||
} as AlbumUpdateEmailProps;
|
} as AlbumUpdateEmailProps;
|
||||||
|
|
||||||
export default AlbumUpdateEmail;
|
export default AlbumUpdateEmail;
|
||||||
|
|
||||||
const text = {
|
|
||||||
margin: '0 0 24px 0',
|
|
||||||
textAlign: 'left' as const,
|
|
||||||
fontSize: '18px',
|
|
||||||
lineHeight: '24px',
|
|
||||||
};
|
|
||||||
|
|
||||||
const button: CSS.Properties = {
|
|
||||||
backgroundColor: 'rgb(66, 80, 175)',
|
|
||||||
margin: '1em 0',
|
|
||||||
padding: '0.75em 3em',
|
|
||||||
color: '#fff',
|
|
||||||
fontSize: '1em',
|
|
||||||
fontWeight: 700,
|
|
||||||
lineHeight: 1.5,
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
borderRadius: '9999px',
|
|
||||||
};
|
|
||||||
|
|||||||
14
server/src/emails/components/button.component.tsx
Normal file
14
server/src/emails/components/button.component.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Button, ButtonProps } from '@react-email/components';
|
||||||
|
|
||||||
|
interface ImmichButtonProps extends ButtonProps {}
|
||||||
|
|
||||||
|
export const ImmichButton = ({ children, ...props }: ImmichButtonProps) => (
|
||||||
|
<Button
|
||||||
|
{...props}
|
||||||
|
className="py-3 px-8 border bg-immich-primary rounded-full no-underline hover:no-underline text-white hover:text-gray-50 font-bold uppercase"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
25
server/src/emails/components/footer.template.tsx
Normal file
25
server/src/emails/components/footer.template.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { Column, Img, Link, Row, Text } from '@react-email/components';
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
export const ImmichFooter = () => (
|
||||||
|
<>
|
||||||
|
<Row className="h-18 w-full">
|
||||||
|
<Column align="center" className="w-6/12 sm:w-full">
|
||||||
|
<Link href="https://play.google.com/store/apps/details?id=app.alextran.immich">
|
||||||
|
<Img className="h-full max-w-full" src={`https://immich.app/img/google-play-badge.png`} />
|
||||||
|
</Link>
|
||||||
|
</Column>
|
||||||
|
<Column align="center" className="w-6/12 sm:w-full">
|
||||||
|
<div className="h-full p-3">
|
||||||
|
<Link href="https://apps.apple.com/sg/app/immich/id1613945652">
|
||||||
|
<Img src={`https://immich.app/img/ios-app-store-badge.png`} alt="Immich" className="max-w-full" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</Column>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Text className="text-center text-sm text-immich-footer">
|
||||||
|
<Link href="https://immich.app">Immich</Link> project is available under GNU AGPL v3 license.
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
);
|
||||||
93
server/src/emails/components/futo.layout.tsx
Normal file
93
server/src/emails/components/futo.layout.tsx
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Container,
|
||||||
|
Font,
|
||||||
|
Head,
|
||||||
|
Hr,
|
||||||
|
Html,
|
||||||
|
Img,
|
||||||
|
Link,
|
||||||
|
Preview,
|
||||||
|
Section,
|
||||||
|
Tailwind,
|
||||||
|
Text,
|
||||||
|
} from '@react-email/components';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { ImmichFooter } from './footer.template';
|
||||||
|
|
||||||
|
interface FutoLayoutProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
preview: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FutoLayout = ({ children, preview }: FutoLayoutProps) => (
|
||||||
|
<Html>
|
||||||
|
<Tailwind
|
||||||
|
config={{
|
||||||
|
presets: [require('tailwindcss-preset-email')],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
// Light Theme
|
||||||
|
'immich-primary': '#4250AF',
|
||||||
|
'futo-primary': '#000000',
|
||||||
|
'futo-bg': '#F4F4f4',
|
||||||
|
'futo-gray': '#F6F6F4',
|
||||||
|
'futo-footer': '#6A737D',
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
sans: ['Overpass', 'sans-serif'],
|
||||||
|
mono: ['Overpass Mono', 'monospace'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Head>
|
||||||
|
<Font
|
||||||
|
fontFamily="Overpass"
|
||||||
|
fallbackFontFamily="sans-serif"
|
||||||
|
webFont={{
|
||||||
|
url: 'https://fonts.gstatic.com/s/overpass/v13/qFdH35WCmI96Ajtm81GrU9vyww.woff2',
|
||||||
|
format: 'woff2',
|
||||||
|
}}
|
||||||
|
fontWeight={'100 900'}
|
||||||
|
fontStyle="normal"
|
||||||
|
/>
|
||||||
|
</Head>
|
||||||
|
<Preview>{preview}</Preview>
|
||||||
|
<Body className="bg-futo-bg my-auto mx-auto px-2 font-sans text-base text-futo-primary">
|
||||||
|
<Container className="my-[40px] mx-auto max-w-[465px]">
|
||||||
|
<Section className="my-6 p-12 border border-red-400 rounded-[50px] bg-gray-50">
|
||||||
|
<Section className="flex justify-center mb-12">
|
||||||
|
<Img
|
||||||
|
src="https://immich.app/img/immich-logo-inline-light.png"
|
||||||
|
className="h-12 antialiased rounded-none"
|
||||||
|
alt="Immich"
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section className="flex justify-center my-6">
|
||||||
|
<Link href="https://futo.org">
|
||||||
|
<Img className="h-6" src="https://futo.org/images/FutoMainLogo.svg" alt="FUTO" />
|
||||||
|
</Link>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Hr className="my-2 text-futo-gray" />
|
||||||
|
|
||||||
|
<ImmichFooter />
|
||||||
|
</Container>
|
||||||
|
</Body>
|
||||||
|
</Tailwind>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
|
|
||||||
|
FutoLayout.PreviewProps = {
|
||||||
|
preview: 'This is the preview shown on some mail clients',
|
||||||
|
children: <Text>Email body goes here.</Text>,
|
||||||
|
} as FutoLayoutProps;
|
||||||
|
|
||||||
|
export default FutoLayout;
|
||||||
74
server/src/emails/components/immich.layout.tsx
Normal file
74
server/src/emails/components/immich.layout.tsx
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { Body, Container, Font, Head, Hr, Html, Img, Preview, Section, Tailwind, Text } from '@react-email/components';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { ImmichFooter } from './footer.template';
|
||||||
|
|
||||||
|
interface ImmichLayoutProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
preview: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ImmichLayout = ({ children, preview }: ImmichLayoutProps) => (
|
||||||
|
<Html>
|
||||||
|
<Tailwind
|
||||||
|
config={{
|
||||||
|
presets: [require('tailwindcss-preset-email')],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
// Light Theme
|
||||||
|
'immich-primary': '#4250AF',
|
||||||
|
'immich-bg': 'white',
|
||||||
|
'immich-fg': 'black',
|
||||||
|
'immich-gray': '#F6F6F4',
|
||||||
|
'immich-footer': '#6A737D',
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
sans: ['Overpass', 'sans-serif'],
|
||||||
|
mono: ['Overpass Mono', 'monospace'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Head>
|
||||||
|
<Font
|
||||||
|
fontFamily="Overpass"
|
||||||
|
fallbackFontFamily="sans-serif"
|
||||||
|
webFont={{
|
||||||
|
url: 'https://fonts.gstatic.com/s/overpass/v13/qFdH35WCmI96Ajtm81GrU9vyww.woff2',
|
||||||
|
format: 'woff2',
|
||||||
|
}}
|
||||||
|
fontWeight={'100 900'}
|
||||||
|
fontStyle="normal"
|
||||||
|
/>
|
||||||
|
</Head>
|
||||||
|
<Preview>{preview}</Preview>
|
||||||
|
<Body className="bg-[#F4F4f4] my-auto mx-auto px-2 font-sans text-base text-gray-800">
|
||||||
|
<Container className="my-[40px] mx-auto max-w-[465px]">
|
||||||
|
<Section className="my-6 p-12 border border-red-400 rounded-[50px] bg-gray-50">
|
||||||
|
<Section className="flex justify-center mb-12">
|
||||||
|
<Img
|
||||||
|
src="https://immich.app/img/immich-logo-inline-light.png"
|
||||||
|
className="h-12 antialiased rounded-none"
|
||||||
|
alt="Immich"
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Hr className="my-2 text-immich-gray" />
|
||||||
|
|
||||||
|
<ImmichFooter />
|
||||||
|
</Container>
|
||||||
|
</Body>
|
||||||
|
</Tailwind>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
|
|
||||||
|
ImmichLayout.PreviewProps = {
|
||||||
|
preview: 'This is the preview shown on some mail clients',
|
||||||
|
children: <Text>Email body goes here.</Text>,
|
||||||
|
} as ImmichLayoutProps;
|
||||||
|
|
||||||
|
export default ImmichLayout;
|
||||||
@@ -15,172 +15,50 @@ import {
|
|||||||
} from '@react-email/components';
|
} from '@react-email/components';
|
||||||
import * as CSS from 'csstype';
|
import * as CSS from 'csstype';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { ImmichButton } from './components/button.component';
|
||||||
|
import FutoLayout from './components/futo.layout';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Template to be used for FUTOPay project
|
* Template to be used for FUTOPay project
|
||||||
* Variable is {{LICENSEKEY}}
|
* Variable is {{LICENSEKEY}}
|
||||||
* */
|
* */
|
||||||
export const LicenseEmail = () => (
|
export const LicenseEmail = () => (
|
||||||
<Html>
|
<FutoLayout preview="Your Immich Server License">
|
||||||
<Head />
|
<Text>Thank you for supporting Immich and open-source software</Text>
|
||||||
<Preview>Your Immich Server License</Preview>
|
|
||||||
<Body
|
<Text>
|
||||||
style={{
|
Your <strong>Immich</strong> key is
|
||||||
margin: 0,
|
</Text>
|
||||||
padding: 0,
|
|
||||||
backgroundColor: '#f4f4f4',
|
<Section className="my-2 bg-gray-200 rounded-2xl text-center p-4">
|
||||||
color: 'rgb(28,28,28)',
|
<Text className="m-0 text-monospace font-bold text-immich-primary">{'{{LICENSEKEY}}'}</Text>
|
||||||
fontFamily: 'Overpass, sans-serif',
|
</Section>
|
||||||
fontSize: '18px',
|
|
||||||
lineHeight: '24px',
|
<Text>
|
||||||
}}
|
To activate your instance, you can click the following button or copy and paste the link below to your browser.
|
||||||
>
|
</Text>
|
||||||
<Container
|
|
||||||
style={{
|
<Section className="flex justify-center my-6">
|
||||||
width: '540px',
|
<ImmichButton
|
||||||
maxWidth: '100%',
|
href={`https://my.immich.app/link?target=activate_license&licenseKey={{LICENSEKEY}}&activationKey={{ACTIVATIONKEY}}`}
|
||||||
padding: '10px',
|
|
||||||
margin: '0 auto',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Section
|
Activate
|
||||||
style={{
|
</ImmichButton>
|
||||||
padding: '36px',
|
</Section>
|
||||||
tableLayout: 'fixed',
|
|
||||||
backgroundColor: '#fefefe',
|
|
||||||
borderRadius: '16px',
|
|
||||||
textAlign: 'center' as const,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Img
|
|
||||||
src="https://immich.app/img/immich-logo-inline-light.png"
|
|
||||||
alt="Immich"
|
|
||||||
style={{
|
|
||||||
height: 'auto',
|
|
||||||
margin: '0 auto 48px auto',
|
|
||||||
width: '50%',
|
|
||||||
alignSelf: 'center',
|
|
||||||
color: 'white',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Text style={text}>Thank you for supporting Immich and open-source software</Text>
|
<Text className="text-center">
|
||||||
|
<Link
|
||||||
<Text style={text}>
|
className="text-immich-primary text-sm"
|
||||||
Your <strong>Immich</strong> license key is
|
// style={{ marginTop: '50px', color: 'rgb(66, 80, 175)', fontSize: '0.9rem' }}
|
||||||
</Text>
|
href={`https://my.immich.app/link?target=activate_license&licenseKey={{LICENSEKEY}}&activationKey={{ACTIVATIONKEY}}`}
|
||||||
|
>
|
||||||
<Section
|
https://my.immich.app/link?target=activate_license&licenseKey={'{{LICENSEKEY}}'}&activationKey=
|
||||||
style={{
|
{'{{ACTIVATIONKEY}}'}
|
||||||
textAlign: 'center',
|
</Link>
|
||||||
background: 'rgb(225, 225, 225)',
|
</Text>
|
||||||
borderRadius: '16px',
|
</FutoLayout>
|
||||||
marginBottom: '25px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text style={{ fontFamily: 'monospace', fontWeight: 600, color: 'rgb(66, 80, 175)' }}>
|
|
||||||
{'{{LICENSEKEY}}'}
|
|
||||||
</Text>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
{/* <Text style={text}>
|
|
||||||
To activate your instance, you can click the following button or copy and paste the link below to your
|
|
||||||
browser
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Row>
|
|
||||||
<Column align="center">
|
|
||||||
<Button
|
|
||||||
style={button}
|
|
||||||
href={`https://my.immich.app/link?target=activate_license&licenseKey={{LICENSEKEY}}&activationKey={{ACTIVATIONKEY}}`}
|
|
||||||
>
|
|
||||||
Activate
|
|
||||||
</Button>
|
|
||||||
</Column>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
<Row>
|
|
||||||
<Column align="center">
|
|
||||||
<a
|
|
||||||
style={{ marginTop: '50px', color: 'rgb(66, 80, 175)', fontSize: '0.9rem' }}
|
|
||||||
href={`https://my.immich.app/link?target=activate_license&licenseKey={{LICENSEKEY}}&activationKey={{ACTIVATIONKEY}}`}
|
|
||||||
>
|
|
||||||
https://my.immich.app/link?target=activate_license&licenseKey={'{{LICENSEKEY}}'}&activationKey=
|
|
||||||
{'{{ACTIVATIONKEY}}'}
|
|
||||||
</a>
|
|
||||||
</Column>
|
|
||||||
</Row> */}
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Section style={{ textAlign: 'center' }}>
|
|
||||||
<Row>
|
|
||||||
<Column align="center">
|
|
||||||
<Link href="https://futo.org">
|
|
||||||
<Img
|
|
||||||
src="https://futo.org/images/FutoMainLogo.svg"
|
|
||||||
alt="FUTO"
|
|
||||||
style={{
|
|
||||||
height: '24px',
|
|
||||||
marginTop: '25px',
|
|
||||||
marginBottom: '25px',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
</Column>
|
|
||||||
</Row>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Hr style={{ color: 'rgb(66, 80, 175)', marginTop: '0' }} />
|
|
||||||
|
|
||||||
<Section style={{ textAlign: 'center' }}>
|
|
||||||
<Column align="center">
|
|
||||||
<Link href="https://apps.apple.com/sg/app/immich/id1613945652">
|
|
||||||
<Img
|
|
||||||
src={`https://immich.app/img/ios-app-store-badge.png`}
|
|
||||||
alt="Immich"
|
|
||||||
style={{ height: '72px', padding: '14px' }}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
<Link href="https://play.google.com/store/apps/details?id=app.alextran.immich">
|
|
||||||
<Img src={`https://immich.app/img/google-play-badge.png`} height="96px" alt="Immich" />
|
|
||||||
</Link>
|
|
||||||
</Column>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
color: '#6a737d',
|
|
||||||
fontSize: '0.8rem',
|
|
||||||
textAlign: 'center' as const,
|
|
||||||
marginTop: '14px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Link href="https://immich.app">Immich</Link> project is available under GNU AGPL v3 license.
|
|
||||||
</Text>
|
|
||||||
</Container>
|
|
||||||
</Body>
|
|
||||||
</Html>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
LicenseEmail.PreviewProps = {};
|
LicenseEmail.PreviewProps = {};
|
||||||
|
|
||||||
export default LicenseEmail;
|
export default LicenseEmail;
|
||||||
|
|
||||||
const text = {
|
|
||||||
margin: '0 0 24px 0',
|
|
||||||
textAlign: 'left' as const,
|
|
||||||
fontSize: '16px',
|
|
||||||
lineHeight: '24px',
|
|
||||||
};
|
|
||||||
|
|
||||||
const button: CSS.Properties = {
|
|
||||||
backgroundColor: 'rgb(66, 80, 175)',
|
|
||||||
margin: '1em 0',
|
|
||||||
padding: '0.75em 3em',
|
|
||||||
color: '#fff',
|
|
||||||
fontSize: '1em',
|
|
||||||
fontWeight: 600,
|
|
||||||
lineHeight: 1.5,
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
borderRadius: '9999px',
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,134 +1,25 @@
|
|||||||
import {
|
import { Link, Row, Text } from '@react-email/components';
|
||||||
Body,
|
|
||||||
Button,
|
|
||||||
Column,
|
|
||||||
Container,
|
|
||||||
Head,
|
|
||||||
Hr,
|
|
||||||
Html,
|
|
||||||
Img,
|
|
||||||
Link,
|
|
||||||
Preview,
|
|
||||||
Row,
|
|
||||||
Section,
|
|
||||||
Text,
|
|
||||||
} from '@react-email/components';
|
|
||||||
import * as CSS from 'csstype';
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { TestEmailProps } from 'src/interfaces/notification.interface';
|
import { TestEmailProps } from 'src/interfaces/notification.interface';
|
||||||
|
import ImmichLayout from './components/immich.layout';
|
||||||
|
|
||||||
export const TestEmail = ({ baseUrl, displayName }: TestEmailProps) => (
|
export const TestEmail = ({ baseUrl, displayName }: TestEmailProps) => (
|
||||||
<Html>
|
<ImmichLayout preview="This is a test email from Immich.">
|
||||||
<Head />
|
<Text className="m-0">
|
||||||
<Preview>This is a test email from Immich</Preview>
|
Hey <strong>{displayName}</strong>!
|
||||||
<Body
|
</Text>
|
||||||
style={{
|
|
||||||
margin: 0,
|
|
||||||
padding: 0,
|
|
||||||
backgroundColor: '#ffffff',
|
|
||||||
color: 'rgb(66, 80, 175)',
|
|
||||||
fontFamily: 'Overpass, sans-serif',
|
|
||||||
fontSize: '18px',
|
|
||||||
lineHeight: '24px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Container
|
|
||||||
style={{
|
|
||||||
width: '480px',
|
|
||||||
maxWidth: '100%',
|
|
||||||
padding: '10px',
|
|
||||||
margin: '0 auto',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Section
|
|
||||||
style={{
|
|
||||||
padding: '36px',
|
|
||||||
tableLayout: 'fixed',
|
|
||||||
backgroundColor: 'rgb(226, 232, 240)',
|
|
||||||
border: 'solid 0px rgb(248 113 113)',
|
|
||||||
borderRadius: '50px',
|
|
||||||
textAlign: 'center' as const,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Img
|
|
||||||
src="https://immich.app/img/immich-logo-inline-light.png"
|
|
||||||
alt="Immich"
|
|
||||||
style={{
|
|
||||||
height: 'auto',
|
|
||||||
margin: '0 auto 48px auto',
|
|
||||||
width: '50%',
|
|
||||||
alignSelf: 'center',
|
|
||||||
color: 'white',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Text style={text}>
|
<Text>This is a test email from your Immich Instance!</Text>
|
||||||
Hey <strong>{displayName}</strong>, this is the test email from your Immich Instance
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Row>
|
<Row>
|
||||||
<Link style={{ marginTop: '50px' }} href={baseUrl}>
|
<Link href={baseUrl}>{baseUrl}</Link>
|
||||||
{baseUrl}
|
</Row>
|
||||||
</Link>
|
</ImmichLayout>
|
||||||
</Row>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Hr style={{ color: 'rgb(66, 80, 175)', marginTop: '24px' }} />
|
|
||||||
|
|
||||||
<Section style={{ textAlign: 'center' }}>
|
|
||||||
<Row>
|
|
||||||
<Column align="center">
|
|
||||||
<Link href="https://play.google.com/store/apps/details?id=app.alextran.immich">
|
|
||||||
<Img src={`https://immich.app/img/google-play-badge.png`} height="96px" alt="Immich" />
|
|
||||||
</Link>
|
|
||||||
<Link href="https://apps.apple.com/sg/app/immich/id1613945652">
|
|
||||||
<Img
|
|
||||||
src={`https://immich.app/img/ios-app-store-badge.png`}
|
|
||||||
alt="Immich"
|
|
||||||
style={{ height: '72px', padding: '14px' }}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
</Column>
|
|
||||||
</Row>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
color: '#6a737d',
|
|
||||||
fontSize: '0.8rem',
|
|
||||||
textAlign: 'center' as const,
|
|
||||||
marginTop: '12px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Link href="https://immich.app">Immich</Link> project is available under GNU AGPL v3 license.
|
|
||||||
</Text>
|
|
||||||
</Container>
|
|
||||||
</Body>
|
|
||||||
</Html>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
TestEmail.PreviewProps = {
|
TestEmail.PreviewProps = {
|
||||||
baseUrl: 'https://demo.immich.app/auth/login',
|
baseUrl: 'https://demo.immich.app',
|
||||||
displayName: 'Alan Turing',
|
displayName: 'Alan Turing',
|
||||||
} as TestEmailProps;
|
} as TestEmailProps;
|
||||||
|
|
||||||
export default TestEmail;
|
export default TestEmail;
|
||||||
|
|
||||||
const text = {
|
|
||||||
margin: '0 0 24px 0',
|
|
||||||
textAlign: 'left' as const,
|
|
||||||
fontSize: '18px',
|
|
||||||
lineHeight: '24px',
|
|
||||||
};
|
|
||||||
|
|
||||||
const button: CSS.Properties = {
|
|
||||||
backgroundColor: 'rgb(66, 80, 175)',
|
|
||||||
margin: '1em 0',
|
|
||||||
padding: '0.75em 3em',
|
|
||||||
color: '#fff',
|
|
||||||
fontSize: '1em',
|
|
||||||
fontWeight: 700,
|
|
||||||
lineHeight: 1.5,
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
borderRadius: '9999px',
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,132 +1,37 @@
|
|||||||
import {
|
import { Link, Section, Text } from '@react-email/components';
|
||||||
Body,
|
|
||||||
Button,
|
|
||||||
Column,
|
|
||||||
Container,
|
|
||||||
Head,
|
|
||||||
Hr,
|
|
||||||
Html,
|
|
||||||
Img,
|
|
||||||
Link,
|
|
||||||
Preview,
|
|
||||||
Row,
|
|
||||||
Section,
|
|
||||||
Text,
|
|
||||||
} from '@react-email/components';
|
|
||||||
import * as CSS from 'csstype';
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { WelcomeEmailProps } from 'src/interfaces/notification.interface';
|
import { WelcomeEmailProps } from 'src/interfaces/notification.interface';
|
||||||
|
import { ImmichButton } from './components/button.component';
|
||||||
|
import ImmichLayout from './components/immich.layout';
|
||||||
|
|
||||||
export const WelcomeEmail = ({ baseUrl, displayName, username, password }: WelcomeEmailProps) => (
|
export const WelcomeEmail = ({ baseUrl, displayName, username, password }: WelcomeEmailProps) => (
|
||||||
<Html>
|
<ImmichLayout preview="You have been invited to a new Immich instance.">
|
||||||
<Head />
|
<Text className="m-0">
|
||||||
<Preview>You have been invited to a new Immich instance.</Preview>
|
Hey <strong>{displayName}</strong>!
|
||||||
<Body
|
</Text>
|
||||||
style={{
|
|
||||||
margin: 0,
|
|
||||||
padding: 0,
|
|
||||||
backgroundColor: '#ffffff',
|
|
||||||
color: 'rgb(66, 80, 175)',
|
|
||||||
fontFamily: 'Overpass, sans-serif',
|
|
||||||
fontSize: '18px',
|
|
||||||
lineHeight: '24px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Container
|
|
||||||
style={{
|
|
||||||
width: '480px',
|
|
||||||
maxWidth: '100%',
|
|
||||||
padding: '10px',
|
|
||||||
margin: '0 auto',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Section
|
|
||||||
style={{
|
|
||||||
padding: '36px',
|
|
||||||
tableLayout: 'fixed',
|
|
||||||
backgroundColor: 'rgb(226, 232, 240)',
|
|
||||||
border: 'solid 0px rgb(248 113 113)',
|
|
||||||
borderRadius: '50px',
|
|
||||||
textAlign: 'center' as const,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Img
|
|
||||||
src="https://immich.app/img/immich-logo-inline-light.png"
|
|
||||||
alt="Immich"
|
|
||||||
style={{
|
|
||||||
height: 'auto',
|
|
||||||
margin: '0 auto 48px auto',
|
|
||||||
width: '50%',
|
|
||||||
alignSelf: 'center',
|
|
||||||
color: 'white',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Text style={text}>
|
<Text>A new account has been created for you.</Text>
|
||||||
Hey <strong>{displayName}</strong>!
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Text style={text}>A new account has been created for you.</Text>
|
<Text>
|
||||||
|
<strong>Username</strong>: {username}
|
||||||
|
{password && (
|
||||||
|
<>
|
||||||
|
<br />
|
||||||
|
<strong>Password</strong>: {password}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
|
||||||
<Text style={text}>
|
<Section className="flex justify-center my-6">
|
||||||
<strong>Username</strong>: {username}
|
<ImmichButton href={`${baseUrl}/auth/login`}>Login</ImmichButton>
|
||||||
{password && (
|
</Section>
|
||||||
<>
|
|
||||||
<br />
|
|
||||||
<strong>Password</strong>: {password}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Row>
|
<Text className="text-xs">
|
||||||
<Text style={{ ...text, marginBottom: '36px' }}>
|
If you cannot click the button use the link below to proceed with first login.
|
||||||
To login, open the link in a browser, or click the button below.
|
<br />
|
||||||
</Text>
|
<Link href={baseUrl}>{baseUrl}</Link>
|
||||||
</Row>
|
</Text>
|
||||||
<Row>
|
</ImmichLayout>
|
||||||
<Link style={{ marginTop: '50px' }} href={baseUrl}>
|
|
||||||
{baseUrl}
|
|
||||||
</Link>
|
|
||||||
</Row>
|
|
||||||
<Row>
|
|
||||||
<Button style={button} href={`${baseUrl}/auth/login`}>
|
|
||||||
Login
|
|
||||||
</Button>
|
|
||||||
</Row>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Hr style={{ color: 'rgb(66, 80, 175)', marginTop: '24px' }} />
|
|
||||||
|
|
||||||
<Section style={{ textAlign: 'center' }}>
|
|
||||||
<Row>
|
|
||||||
<Column align="center">
|
|
||||||
<Link href="https://play.google.com/store/apps/details?id=app.alextran.immich">
|
|
||||||
<Img src={`https://immich.app/img/google-play-badge.png`} height="96px" alt="Immich" />
|
|
||||||
</Link>
|
|
||||||
<Link href="https://apps.apple.com/sg/app/immich/id1613945652">
|
|
||||||
<Img
|
|
||||||
src={`https://immich.app/img/ios-app-store-badge.png`}
|
|
||||||
alt="Immich"
|
|
||||||
style={{ height: '72px', padding: '14px' }}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
</Column>
|
|
||||||
</Row>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
color: '#6a737d',
|
|
||||||
fontSize: '0.8rem',
|
|
||||||
textAlign: 'center' as const,
|
|
||||||
marginTop: '12px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Link href="https://immich.app">Immich</Link> project is available under GNU AGPL v3 license.
|
|
||||||
</Text>
|
|
||||||
</Container>
|
|
||||||
</Body>
|
|
||||||
</Html>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
WelcomeEmail.PreviewProps = {
|
WelcomeEmail.PreviewProps = {
|
||||||
@@ -137,22 +42,3 @@ WelcomeEmail.PreviewProps = {
|
|||||||
} as WelcomeEmailProps;
|
} as WelcomeEmailProps;
|
||||||
|
|
||||||
export default WelcomeEmail;
|
export default WelcomeEmail;
|
||||||
|
|
||||||
const text = {
|
|
||||||
margin: '0 0 24px 0',
|
|
||||||
textAlign: 'left' as const,
|
|
||||||
fontSize: '18px',
|
|
||||||
lineHeight: '24px',
|
|
||||||
};
|
|
||||||
|
|
||||||
const button: CSS.Properties = {
|
|
||||||
backgroundColor: 'rgb(66, 80, 175)',
|
|
||||||
margin: '1em 0',
|
|
||||||
padding: '0.75em 3em',
|
|
||||||
color: '#fff',
|
|
||||||
fontSize: '1em',
|
|
||||||
fontWeight: 700,
|
|
||||||
lineHeight: 1.5,
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
borderRadius: '9999px',
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -297,7 +297,16 @@ export class MapRepository implements IMapRepository {
|
|||||||
admin2Name: admin2Map.get(`${lineSplit[8]}.${lineSplit[10]}.${lineSplit[11]}`),
|
admin2Name: admin2Map.get(`${lineSplit[8]}.${lineSplit[10]}.${lineSplit[11]}`),
|
||||||
}),
|
}),
|
||||||
resourcePaths.geodata.cities500,
|
resourcePaths.geodata.cities500,
|
||||||
{ entityFilter: (lineSplit) => lineSplit[7] != 'PPLX' },
|
{
|
||||||
|
entityFilter: (lineSplit) => {
|
||||||
|
if (lineSplit[7] === 'PPLX') {
|
||||||
|
// Exclude populated subsections of cities that are not in Australia.
|
||||||
|
// Australia has a lot of PPLX areas, so we include them.
|
||||||
|
return lineSplit[8] === 'AU';
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import swc from 'unplugin-swc';
|
import swc from 'unplugin-swc';
|
||||||
|
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||||
import { defineConfig } from 'vitest/config';
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
@@ -11,5 +12,5 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [swc.vite()],
|
plugins: [swc.vite(), tsconfigPaths()],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
20.15.1
|
20.16.0
|
||||||
|
|||||||
2
web/package-lock.json
generated
2
web/package-lock.json
generated
@@ -65,7 +65,7 @@
|
|||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.1.4",
|
"vite": "^5.1.4",
|
||||||
"vitest": "^1.3.1"
|
"vitest": "^1.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"../open-api/typescript-sdk": {
|
"../open-api/typescript-sdk": {
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.1.4",
|
"vite": "^5.1.4",
|
||||||
"vitest": "^1.3.1"
|
"vitest": "^1.6.0"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -83,6 +83,6 @@
|
|||||||
"thumbhash": "^0.1.1"
|
"thumbhash": "^0.1.1"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "20.15.1"
|
"node": "20.16.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,7 +108,10 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<span class="text-immich-primary dark:text-immich-dark-primary">
|
<span class="text-immich-primary dark:text-immich-dark-primary">
|
||||||
{#if user.quotaSizeInBytes}
|
{#if user.quotaSizeInBytes}
|
||||||
({((user.usage / user.quotaSizeInBytes) * 100).toFixed(0)}%)
|
({(user.usage / user.quotaSizeInBytes).toLocaleString($locale, {
|
||||||
|
style: 'percent',
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
})})
|
||||||
{:else}
|
{:else}
|
||||||
({$t('unlimited')})
|
({$t('unlimited')})
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<input
|
<input
|
||||||
use:shortcut={{ shortcut: { key: 'Enter' }, onShortcut: (e) => e.currentTarget.blur() }}
|
use:shortcut={{ shortcut: { key: 'Enter' }, onShortcut: (e) => e.currentTarget.blur() }}
|
||||||
on:blur={handleUpdateName}
|
on:blur={handleUpdateName}
|
||||||
class="w-[99%] mb-2 border-b-2 border-transparent text-6xl text-immich-primary outline-none transition-all dark:text-immich-dark-primary {isOwned
|
class="w-[99%] mb-2 border-b-2 border-transparent text-2xl md:text-4xl lg:text-6xl text-immich-primary outline-none transition-all dark:text-immich-dark-primary {isOwned
|
||||||
? 'hover:border-gray-400'
|
? 'hover:border-gray-400'
|
||||||
: 'hover:border-transparent'} bg-immich-bg focus:border-b-2 focus:border-immich-primary focus:outline-none dark:bg-immich-dark-bg dark:focus:border-immich-dark-primary dark:focus:bg-immich-dark-gray"
|
: 'hover:border-transparent'} bg-immich-bg focus:border-b-2 focus:border-immich-primary focus:outline-none dark:bg-immich-dark-bg dark:focus:border-immich-dark-primary dark:focus:bg-immich-dark-gray"
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
@@ -95,10 +95,10 @@
|
|||||||
|
|
||||||
<main class="relative h-screen overflow-hidden bg-immich-bg px-6 pt-[var(--navbar-height)] dark:bg-immich-dark-bg">
|
<main class="relative h-screen overflow-hidden bg-immich-bg px-6 pt-[var(--navbar-height)] dark:bg-immich-dark-bg">
|
||||||
<AssetGrid {album} {assetStore} {assetInteractionStore}>
|
<AssetGrid {album} {assetStore} {assetInteractionStore}>
|
||||||
<section class="pt-24">
|
<section class="pt-8 md:pt-24">
|
||||||
<!-- ALBUM TITLE -->
|
<!-- ALBUM TITLE -->
|
||||||
<h1
|
<h1
|
||||||
class="bg-immich-bg text-6xl text-immich-primary outline-none transition-all dark:bg-immich-dark-bg dark:text-immich-dark-primary"
|
class="bg-immich-bg text-2xl md:text-4xl lg:text-6xl text-immich-primary outline-none transition-all dark:bg-immich-dark-bg dark:text-immich-dark-primary"
|
||||||
>
|
>
|
||||||
{album.albumName}
|
{album.albumName}
|
||||||
</h1>
|
</h1>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import type { ActivityResponseDto } from '@immich/sdk';
|
import type { ActivityResponseDto } from '@immich/sdk';
|
||||||
import { mdiCommentOutline, mdiHeart, mdiHeartOutline } from '@mdi/js';
|
import { mdiCommentOutline, mdiHeart, mdiHeartOutline } from '@mdi/js';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
@@ -24,7 +25,7 @@
|
|||||||
<div class="flex gap-2 items-center justify-center">
|
<div class="flex gap-2 items-center justify-center">
|
||||||
<Icon path={mdiCommentOutline} class="scale-x-[-1]" size={24} />
|
<Icon path={mdiCommentOutline} class="scale-x-[-1]" size={24} />
|
||||||
{#if numberOfComments}
|
{#if numberOfComments}
|
||||||
<div class="text-xl">{numberOfComments}</div>
|
<div class="text-xl">{numberOfComments.toLocaleString($locale)}</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -35,7 +35,9 @@
|
|||||||
<div class="h-[7px] rounded-full bg-immich-primary" style={`width: ${download.percentage}%`} />
|
<div class="h-[7px] rounded-full bg-immich-primary" style={`width: ${download.percentage}%`} />
|
||||||
</div>
|
</div>
|
||||||
<p class="min-w-[4em] whitespace-nowrap text-right">
|
<p class="min-w-[4em] whitespace-nowrap text-right">
|
||||||
<span class="text-immich-primary">{download.percentage}%</span>
|
<span class="text-immich-primary">
|
||||||
|
{(download.percentage / 100).toLocaleString($locale, { style: 'percent' })}
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -109,7 +109,7 @@
|
|||||||
buttonSize="50"
|
buttonSize="50"
|
||||||
icon={mdiCog}
|
icon={mdiCog}
|
||||||
on:click={() => (showSettings = !showSettings)}
|
on:click={() => (showSettings = !showSettings)}
|
||||||
title={$t('next')}
|
title={$t('slideshow_settings')}
|
||||||
/>
|
/>
|
||||||
{#if !isFullScreen}
|
{#if !isFullScreen}
|
||||||
<CircleIconButton
|
<CircleIconButton
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import { getAltText } from '$lib/utils/thumbnail-util';
|
import { getAltText } from '$lib/utils/thumbnail-util';
|
||||||
import { timeToSeconds } from '$lib/utils/date-time';
|
import { timeToSeconds } from '$lib/utils/date-time';
|
||||||
import { AssetMediaSize, AssetTypeEnum, type AssetResponseDto } from '@immich/sdk';
|
import { AssetMediaSize, AssetTypeEnum, type AssetResponseDto } from '@immich/sdk';
|
||||||
import { playVideoThumbnailOnHover } from '$lib/stores/preferences.store';
|
import { locale, playVideoThumbnailOnHover } from '$lib/stores/preferences.store';
|
||||||
import { getAssetPlaybackUrl } from '$lib/utils';
|
import { getAssetPlaybackUrl } from '$lib/utils';
|
||||||
import {
|
import {
|
||||||
mdiArchiveArrowDownOutline,
|
mdiArchiveArrowDownOutline,
|
||||||
@@ -177,7 +177,7 @@
|
|||||||
: 'top-7 right-1'} z-20 flex place-items-center gap-1 text-xs font-medium text-white"
|
: 'top-7 right-1'} z-20 flex place-items-center gap-1 text-xs font-medium text-white"
|
||||||
>
|
>
|
||||||
<span class="pr-2 pt-2 flex place-items-center gap-1">
|
<span class="pr-2 pt-2 flex place-items-center gap-1">
|
||||||
<p>{asset.stackCount}</p>
|
<p>{asset.stackCount.toLocaleString($locale)}</p>
|
||||||
<Icon path={mdiCameraBurst} size="24" />
|
<Icon path={mdiCameraBurst} size="24" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import Button from '$lib/components/elements/buttons/button.svelte';
|
||||||
|
import { render, screen } from '@testing-library/svelte';
|
||||||
|
|
||||||
|
describe('Button component', () => {
|
||||||
|
it('should render as a button', () => {
|
||||||
|
render(Button);
|
||||||
|
const button = screen.getByRole('button');
|
||||||
|
expect(button).toBeInTheDocument();
|
||||||
|
expect(button).toHaveAttribute('type', 'button');
|
||||||
|
expect(button).not.toHaveAttribute('href');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render as a link if href prop is set', () => {
|
||||||
|
render(Button, { props: { href: '/test' } });
|
||||||
|
const link = screen.getByRole('link');
|
||||||
|
expect(link).toBeInTheDocument();
|
||||||
|
expect(link).toHaveAttribute('href', '/test');
|
||||||
|
expect(link).not.toHaveAttribute('type');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||||
|
import { render, screen } from '@testing-library/svelte';
|
||||||
|
|
||||||
|
describe('CircleIconButton component', () => {
|
||||||
|
it('should render as a button', () => {
|
||||||
|
render(CircleIconButton, { icon: '', title: 'test' });
|
||||||
|
const button = screen.getByRole('button');
|
||||||
|
expect(button).toBeInTheDocument();
|
||||||
|
expect(button).toHaveAttribute('type', 'button');
|
||||||
|
expect(button).not.toHaveAttribute('href');
|
||||||
|
expect(button).toHaveAttribute('title', 'test');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render as a link if href prop is set', () => {
|
||||||
|
render(CircleIconButton, { props: { href: '/test', icon: '', title: 'test' } });
|
||||||
|
const link = screen.getByRole('link');
|
||||||
|
expect(link).toBeInTheDocument();
|
||||||
|
expect(link).toHaveAttribute('href', '/test');
|
||||||
|
expect(link).not.toHaveAttribute('type');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render icon inside button', () => {
|
||||||
|
render(CircleIconButton, { icon: '', title: 'test' });
|
||||||
|
const button = screen.getByRole('button');
|
||||||
|
const icon = button.querySelector('svg');
|
||||||
|
expect(icon).toBeInTheDocument();
|
||||||
|
expect(icon).toHaveAttribute('aria-label', 'test');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
export type Type = 'button' | 'submit' | 'reset';
|
import type { HTMLButtonAttributes, HTMLLinkAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
export type Color =
|
export type Color =
|
||||||
| 'primary'
|
| 'primary'
|
||||||
| 'primary-inversed'
|
| 'primary-inversed'
|
||||||
@@ -14,45 +15,66 @@
|
|||||||
| 'dark-gray'
|
| 'dark-gray'
|
||||||
| 'overlay-primary';
|
| 'overlay-primary';
|
||||||
export type Size = 'tiny' | 'icon' | 'link' | 'sm' | 'base' | 'lg';
|
export type Size = 'tiny' | 'icon' | 'link' | 'sm' | 'base' | 'lg';
|
||||||
export type Rounded = 'lg' | '3xl' | 'full' | false;
|
export type Rounded = 'lg' | '3xl' | 'full' | 'none';
|
||||||
export type Shadow = 'md' | false;
|
export type Shadow = 'md' | false;
|
||||||
|
|
||||||
|
type BaseProps = {
|
||||||
|
class?: string;
|
||||||
|
color?: Color;
|
||||||
|
size?: Size;
|
||||||
|
rounded?: Rounded;
|
||||||
|
shadow?: Shadow;
|
||||||
|
fullwidth?: boolean;
|
||||||
|
border?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ButtonProps = HTMLButtonAttributes &
|
||||||
|
BaseProps & {
|
||||||
|
href?: never;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LinkProps = HTMLLinkAttributes &
|
||||||
|
BaseProps & {
|
||||||
|
type?: never;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Props = ButtonProps | LinkProps;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let type: Type = 'button';
|
type $$Props = Props;
|
||||||
|
|
||||||
|
export let type: $$Props['type'] = 'button';
|
||||||
|
export let href: $$Props['href'] = undefined;
|
||||||
export let color: Color = 'primary';
|
export let color: Color = 'primary';
|
||||||
export let size: Size = 'base';
|
export let size: Size = 'base';
|
||||||
export let rounded: Rounded = '3xl';
|
export let rounded: Rounded = '3xl';
|
||||||
export let shadow: Shadow = 'md';
|
export let shadow: Shadow = 'md';
|
||||||
export let disabled = false;
|
|
||||||
export let fullwidth = false;
|
export let fullwidth = false;
|
||||||
export let border = false;
|
export let border = false;
|
||||||
export let title: string | undefined = '';
|
|
||||||
export let form: string | undefined = undefined;
|
|
||||||
|
|
||||||
let className = '';
|
let className = '';
|
||||||
export { className as class };
|
export { className as class };
|
||||||
|
|
||||||
const colorClasses: Record<Color, string> = {
|
const colorClasses: Record<Color, string> = {
|
||||||
primary:
|
primary:
|
||||||
'bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-gray enabled:dark:hover:bg-immich-dark-primary/80 enabled:hover:bg-immich-primary/90',
|
'bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-gray dark:hover:bg-immich-dark-primary/80 hover:bg-immich-primary/90',
|
||||||
secondary:
|
secondary:
|
||||||
'bg-gray-500 dark:bg-gray-200 text-white dark:text-immich-dark-gray enabled:hover:bg-gray-500/90 enabled:dark:hover:bg-gray-200/90',
|
'bg-gray-500 dark:bg-gray-200 text-white dark:text-immich-dark-gray hover:bg-gray-500/90 dark:hover:bg-gray-200/90',
|
||||||
'transparent-primary':
|
'transparent-primary': 'text-gray-500 dark:text-immich-dark-primary hover:bg-gray-100 dark:hover:bg-gray-700',
|
||||||
'text-gray-500 dark:text-immich-dark-primary enabled:hover:bg-gray-100 enabled:dark:hover:bg-gray-700',
|
|
||||||
'text-primary':
|
'text-primary':
|
||||||
'text-immich-primary dark:text-immich-dark-primary enabled:dark:hover:bg-immich-dark-primary/10 enabled:hover:bg-immich-primary/10',
|
'text-immich-primary dark:text-immich-dark-primary dark:hover:bg-immich-dark-primary/10 hover:bg-immich-primary/10',
|
||||||
'light-red': 'bg-[#F9DEDC] text-[#410E0B] enabled:hover:bg-red-50',
|
'light-red': 'bg-[#F9DEDC] text-[#410E0B] hover:bg-red-50',
|
||||||
red: 'bg-red-500 text-white enabled:hover:bg-red-400',
|
red: 'bg-red-500 text-white hover:bg-red-400',
|
||||||
green: 'bg-green-400 text-gray-800 enabled:hover:bg-green-400/90',
|
green: 'bg-green-400 text-gray-800 hover:bg-green-400/90',
|
||||||
gray: 'bg-gray-500 dark:bg-gray-200 enabled:hover:bg-gray-500/75 enabled:dark:hover:bg-gray-200/80 text-white dark:text-immich-dark-gray',
|
gray: 'bg-gray-500 dark:bg-gray-200 hover:bg-gray-500/75 dark:hover:bg-gray-200/80 text-white dark:text-immich-dark-gray',
|
||||||
'transparent-gray':
|
'transparent-gray':
|
||||||
'dark:text-immich-dark-fg enabled:hover:bg-immich-primary/5 enabled:hover:text-gray-700 enabled:hover:dark:text-immich-dark-fg enabled:dark:hover:bg-immich-dark-primary/25',
|
'dark:text-immich-dark-fg hover:bg-immich-primary/5 hover:text-gray-700 hover:dark:text-immich-dark-fg dark:hover:bg-immich-dark-primary/25',
|
||||||
'dark-gray':
|
'dark-gray':
|
||||||
'dark:border-immich-dark-gray dark:bg-gray-500 enabled:dark:hover:bg-immich-dark-primary/50 enabled:hover:bg-immich-primary/10 dark:text-white',
|
'dark:border-immich-dark-gray dark:bg-gray-500 dark:hover:bg-immich-dark-primary/50 hover:bg-immich-primary/10 dark:text-white',
|
||||||
'overlay-primary': 'text-gray-500 enabled:hover:bg-gray-100',
|
'overlay-primary': 'text-gray-500 hover:bg-gray-100',
|
||||||
'primary-inversed':
|
'primary-inversed':
|
||||||
'bg-immich-dark-primary dark:bg-immich-primary text-black dark:text-white enabled:hover:bg-immich-dark-primary/80 enabled:dark:hover:bg-immich-primary/90',
|
'bg-immich-dark-primary dark:bg-immich-primary text-black dark:text-white hover:bg-immich-dark-primary/80 dark:hover:bg-immich-primary/90',
|
||||||
};
|
};
|
||||||
|
|
||||||
const sizeClasses: Record<Size, string> = {
|
const sizeClasses: Record<Size, string> = {
|
||||||
@@ -63,25 +85,37 @@
|
|||||||
base: 'px-6 py-3 font-medium',
|
base: 'px-6 py-3 font-medium',
|
||||||
lg: 'px-6 py-4 font-semibold',
|
lg: 'px-6 py-4 font-semibold',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const roundedClasses: Record<Rounded, string> = {
|
||||||
|
none: '',
|
||||||
|
lg: 'rounded-lg',
|
||||||
|
'3xl': 'rounded-3xl',
|
||||||
|
full: 'rounded-full',
|
||||||
|
};
|
||||||
|
|
||||||
|
$: computedClass = [
|
||||||
|
className,
|
||||||
|
colorClasses[color],
|
||||||
|
sizeClasses[size],
|
||||||
|
roundedClasses[rounded],
|
||||||
|
shadow === 'md' && 'shadow-md',
|
||||||
|
fullwidth && 'w-full',
|
||||||
|
border && 'border',
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
{type}
|
<svelte:element
|
||||||
{disabled}
|
this={href ? 'a' : 'button'}
|
||||||
{title}
|
type={href ? undefined : type}
|
||||||
{form}
|
{href}
|
||||||
on:click
|
on:click
|
||||||
on:focus
|
on:focus
|
||||||
on:blur
|
on:blur
|
||||||
class="{className} inline-flex items-center justify-center transition-colors disabled:cursor-not-allowed disabled:opacity-60 {colorClasses[
|
class="inline-flex items-center justify-center transition-colors disabled:cursor-not-allowed disabled:opacity-60 disabled:pointer-events-none {computedClass}"
|
||||||
color
|
{...$$restProps}
|
||||||
]} {sizeClasses[size]}"
|
|
||||||
class:rounded-lg={rounded === 'lg'}
|
|
||||||
class:rounded-3xl={rounded === '3xl'}
|
|
||||||
class:rounded-full={rounded === 'full'}
|
|
||||||
class:shadow-md={shadow === 'md'}
|
|
||||||
class:w-full={fullwidth}
|
|
||||||
class:border
|
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</button>
|
</svelte:element>
|
||||||
|
|||||||
@@ -1,18 +1,48 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
|
import type { HTMLButtonAttributes, HTMLLinkAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
export type Color = 'transparent' | 'light' | 'dark' | 'gray' | 'primary' | 'opaque';
|
export type Color = 'transparent' | 'light' | 'dark' | 'gray' | 'primary' | 'opaque';
|
||||||
|
export type Padding = '1' | '2' | '3';
|
||||||
|
|
||||||
|
type BaseProps = {
|
||||||
|
icon: string;
|
||||||
|
title: string;
|
||||||
|
class?: string;
|
||||||
|
color?: Color;
|
||||||
|
padding?: Padding;
|
||||||
|
size?: string;
|
||||||
|
hideMobile?: true;
|
||||||
|
buttonSize?: string;
|
||||||
|
viewBox?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ButtonProps = HTMLButtonAttributes &
|
||||||
|
BaseProps & {
|
||||||
|
href?: never;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LinkProps = HTMLLinkAttributes &
|
||||||
|
BaseProps & {
|
||||||
|
type?: never;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Props = ButtonProps | LinkProps;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
|
|
||||||
export let type: 'button' | 'submit' | 'reset' = 'button';
|
type $$Props = Props;
|
||||||
|
|
||||||
|
export let type: $$Props['type'] = 'button';
|
||||||
|
export let href: $$Props['href'] = undefined;
|
||||||
export let icon: string;
|
export let icon: string;
|
||||||
export let color: Color = 'transparent';
|
export let color: Color = 'transparent';
|
||||||
export let title: string;
|
export let title: string;
|
||||||
/**
|
/**
|
||||||
* The padding of the button, used by the `p-{padding}` Tailwind CSS class.
|
* The padding of the button, used by the `p-{padding}` Tailwind CSS class.
|
||||||
*/
|
*/
|
||||||
export let padding = '3';
|
export let padding: Padding = '3';
|
||||||
/**
|
/**
|
||||||
* Size of the button, used for a CSS value.
|
* Size of the button, used for a CSS value.
|
||||||
*/
|
*/
|
||||||
@@ -23,10 +53,6 @@
|
|||||||
* viewBox attribute for the SVG icon.
|
* viewBox attribute for the SVG icon.
|
||||||
*/
|
*/
|
||||||
export let viewBox: string | undefined = undefined;
|
export let viewBox: string | undefined = undefined;
|
||||||
export let id: string | undefined = undefined;
|
|
||||||
export let ariaHasPopup: boolean | undefined = undefined;
|
|
||||||
export let ariaExpanded: boolean | undefined = undefined;
|
|
||||||
export let ariaControls: string | undefined = undefined;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override the default styling of the button for specific use cases, such as the icon color.
|
* Override the default styling of the button for specific use cases, such as the icon color.
|
||||||
@@ -44,22 +70,28 @@
|
|||||||
'bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 hover:dark:bg-immich-dark-primary/80 text-white dark:text-immich-dark-gray',
|
'bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 hover:dark:bg-immich-dark-primary/80 text-white dark:text-immich-dark-gray',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const paddingClasses: Record<Padding, string> = {
|
||||||
|
'1': 'p-1',
|
||||||
|
'2': 'p-2',
|
||||||
|
'3': 'p-3',
|
||||||
|
};
|
||||||
|
|
||||||
$: colorClass = colorClasses[color];
|
$: colorClass = colorClasses[color];
|
||||||
$: mobileClass = hideMobile ? 'hidden sm:flex' : '';
|
$: mobileClass = hideMobile ? 'hidden sm:flex' : '';
|
||||||
$: paddingClass = `p-${padding}`;
|
$: paddingClass = paddingClasses[padding];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
{id}
|
<svelte:element
|
||||||
|
this={href ? 'a' : 'button'}
|
||||||
|
type={href ? undefined : type}
|
||||||
{title}
|
{title}
|
||||||
{type}
|
{href}
|
||||||
style:width={buttonSize ? buttonSize + 'px' : ''}
|
style:width={buttonSize ? buttonSize + 'px' : ''}
|
||||||
style:height={buttonSize ? buttonSize + 'px' : ''}
|
style:height={buttonSize ? buttonSize + 'px' : ''}
|
||||||
class="flex place-content-center place-items-center rounded-full {colorClass} {paddingClass} transition-all hover:dark:text-immich-dark-gray {className} {mobileClass}"
|
class="flex place-content-center place-items-center rounded-full {colorClass} {paddingClass} transition-all disabled:cursor-default hover:dark:text-immich-dark-gray {className} {mobileClass}"
|
||||||
aria-haspopup={ariaHasPopup}
|
|
||||||
aria-expanded={ariaExpanded}
|
|
||||||
aria-controls={ariaControls}
|
|
||||||
on:click
|
on:click
|
||||||
|
{...$$restProps}
|
||||||
>
|
>
|
||||||
<Icon path={icon} {size} ariaLabel={title} {viewBox} color="currentColor" />
|
<Icon path={icon} {size} ariaLabel={title} {viewBox} color="currentColor" />
|
||||||
</button>
|
</svelte:element>
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
export type Color = 'transparent-primary' | 'transparent-gray';
|
export type Color = 'transparent-primary' | 'transparent-gray';
|
||||||
|
|
||||||
|
type BaseProps = {
|
||||||
|
color?: Color;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Props = (LinkProps & BaseProps) | (ButtonProps & BaseProps);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Button from './button.svelte';
|
import Button, { type ButtonProps, type LinkProps } from '$lib/components/elements/buttons/button.svelte';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
type $$Props = Props;
|
||||||
|
|
||||||
export let color: Color = 'transparent-gray';
|
export let color: Color = 'transparent-gray';
|
||||||
export let disabled = false;
|
|
||||||
export let fullwidth = false;
|
|
||||||
export let title: string | undefined = undefined;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button {title} size="link" {color} shadow={false} rounded="lg" {disabled} on:click {fullwidth}>
|
<Button size="link" {color} shadow={false} rounded="lg" on:click {...$$restProps}>
|
||||||
<slot />
|
<slot />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<div class="absolute z-50 top-2 left-2 transition-transform {isFocused ? 'translate-y-0' : '-translate-y-10 sr-only'}">
|
<div class="absolute z-50 top-2 left-2 transition-transform {isFocused ? 'translate-y-0' : '-translate-y-10 sr-only'}">
|
||||||
<Button
|
<Button
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
rounded={false}
|
rounded="none"
|
||||||
on:click={moveFocus}
|
on:click={moveFocus}
|
||||||
on:focus={() => (isFocused = true)}
|
on:focus={() => (isFocused = true)}
|
||||||
on:blur={() => (isFocused = false)}
|
on:blur={() => (isFocused = false)}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
import { tweened } from 'svelte/motion';
|
import { tweened } from 'svelte/motion';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
|
|
||||||
const parseIndex = (s: string | null, max: number | null) =>
|
const parseIndex = (s: string | null, max: number | null) =>
|
||||||
Math.max(Math.min(Number.parseInt(s ?? '') || 0, max ?? 0), 0);
|
Math.max(Math.min(Number.parseInt(s ?? '') || 0, max ?? 0), 0);
|
||||||
@@ -201,7 +202,7 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p class="text-small">
|
<p class="text-small">
|
||||||
{assetIndex + 1}/{currentMemory.assets.length}
|
{(assetIndex + 1).toLocaleString($locale)}/{currentMemory.assets.length.toLocaleString($locale)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -427,7 +427,9 @@
|
|||||||
<!-- Right margin MUST be equal to the width of immich-scrubbable-scrollbar -->
|
<!-- Right margin MUST be equal to the width of immich-scrubbable-scrollbar -->
|
||||||
<section
|
<section
|
||||||
id="asset-grid"
|
id="asset-grid"
|
||||||
class="scrollbar-hidden h-full overflow-y-auto outline-none pb-[60px] {isEmpty ? 'm-0' : 'ml-4 tall:ml-0 mr-[60px]'}"
|
class="scrollbar-hidden h-full overflow-y-auto outline-none pb-[60px] {isEmpty
|
||||||
|
? 'm-0'
|
||||||
|
: 'ml-4 tall:ml-0 md:mr-[60px]'}"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
bind:clientHeight={viewport.height}
|
bind:clientHeight={viewport.height}
|
||||||
bind:clientWidth={viewport.width}
|
bind:clientWidth={viewport.width}
|
||||||
|
|||||||
@@ -31,8 +31,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ControlAppBar on:close={clearSelect} backIcon={mdiClose} tailwindClasses="bg-white shadow-md">
|
<ControlAppBar on:close={clearSelect} backIcon={mdiClose} tailwindClasses="bg-white shadow-md">
|
||||||
<p class="font-medium text-immich-primary dark:text-immich-dark-primary" slot="leading">
|
<div class="font-medium text-immich-primary dark:text-immich-dark-primary" slot="leading">
|
||||||
{$t('selected_count', { values: { count: assets.size } })}
|
<p class="block sm:hidden">{assets.size}</p>
|
||||||
</p>
|
<p class="hidden sm:block">{$t('selected_count', { values: { count: assets.size } })}</p>
|
||||||
|
</div>
|
||||||
<slot slot="trailing" />
|
<slot slot="trailing" />
|
||||||
</ControlAppBar>
|
</ControlAppBar>
|
||||||
|
|||||||
@@ -86,7 +86,8 @@
|
|||||||
<Icon path={mdiPlus} size="30" />
|
<Icon path={mdiPlus} size="30" />
|
||||||
</div>
|
</div>
|
||||||
<p class="">
|
<p class="">
|
||||||
New Album {#if search.length > 0}<b>{search}</b>{/if}
|
{$t('new_album')}
|
||||||
|
{#if search.length > 0}<b>{search}</b>{/if}
|
||||||
</p>
|
</p>
|
||||||
</button>
|
</button>
|
||||||
{#if filteredAlbums.length > 0}
|
{#if filteredAlbums.length > 0}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import CircleIconButton, { type Color } from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
import CircleIconButton, {
|
||||||
|
type Color,
|
||||||
|
type Padding,
|
||||||
|
} from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||||
import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte';
|
import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte';
|
||||||
import {
|
import {
|
||||||
getContextMenuPositionFromBoundingRect,
|
getContextMenuPositionFromBoundingRect,
|
||||||
@@ -24,7 +27,7 @@
|
|||||||
export let direction: 'left' | 'right' = 'right';
|
export let direction: 'left' | 'right' = 'right';
|
||||||
export let color: Color = 'transparent';
|
export let color: Color = 'transparent';
|
||||||
export let size: string | undefined = undefined;
|
export let size: string | undefined = undefined;
|
||||||
export let padding: string | undefined = undefined;
|
export let padding: Padding | undefined = undefined;
|
||||||
/**
|
/**
|
||||||
* Additional classes to apply to the button.
|
* Additional classes to apply to the button.
|
||||||
*/
|
*/
|
||||||
@@ -114,9 +117,9 @@
|
|||||||
{padding}
|
{padding}
|
||||||
{size}
|
{size}
|
||||||
{title}
|
{title}
|
||||||
ariaControls={menuId}
|
aria-controls={menuId}
|
||||||
ariaExpanded={isOpen}
|
aria-expanded={isOpen}
|
||||||
ariaHasPopup={true}
|
aria-haspopup={true}
|
||||||
class={buttonClass}
|
class={buttonClass}
|
||||||
id={buttonId}
|
id={buttonId}
|
||||||
on:click={handleClick}
|
on:click={handleClick}
|
||||||
|
|||||||
@@ -54,11 +54,11 @@
|
|||||||
<div in:fly={{ y: 10, duration: 200 }} class="absolute top-0 w-full z-[100] bg-transparent">
|
<div in:fly={{ y: 10, duration: 200 }} class="absolute top-0 w-full z-[100] bg-transparent">
|
||||||
<div
|
<div
|
||||||
id="asset-selection-app-bar"
|
id="asset-selection-app-bar"
|
||||||
class={`grid grid-cols-[10%_80%_10%] justify-between md:grid-cols-[15%_70%_15%] lg:grid-cols-[25%_50%_25%] ${appBarBorder} mx-2 mt-2 place-items-center rounded-lg p-2 transition-all ${tailwindClasses} dark:bg-immich-dark-gray ${
|
class={`grid grid-cols-[10%_80%_10%] justify-between sm:grid-cols-[25%_50%_25%] lg:grid-cols-[25%_50%_25%] ${appBarBorder} mx-2 mt-2 place-items-center rounded-lg p-2 transition-all ${tailwindClasses} dark:bg-immich-dark-gray ${
|
||||||
forceDark && 'bg-immich-dark-gray text-white'
|
forceDark && 'bg-immich-dark-gray text-white'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div class="flex place-items-center gap-6 justify-self-start dark:text-immich-dark-fg">
|
<div class="flex place-items-center sm:gap-6 justify-self-start dark:text-immich-dark-fg">
|
||||||
{#if showBackButton}
|
{#if showBackButton}
|
||||||
<CircleIconButton title={$t('close')} on:click={handleClose} icon={backIcon} size={'24'} class={buttonClass} />
|
<CircleIconButton title={$t('close')} on:click={handleClose} icon={backIcon} size={'24'} class={buttonClass} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -73,14 +73,19 @@
|
|||||||
<p class="text-sm text-gray-500 dark:text-immich-dark-fg">{$user.email}</p>
|
<p class="text-sm text-gray-500 dark:text-immich-dark-fg">{$user.email}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a href={AppRoute.USER_SETTINGS} on:click={() => dispatch('close')}>
|
<Button
|
||||||
<Button color="dark-gray" size="sm" shadow={false} border>
|
href={AppRoute.USER_SETTINGS}
|
||||||
<div class="flex place-content-center place-items-center gap-2 px-2">
|
on:click={() => dispatch('close')}
|
||||||
<Icon path={mdiCog} size="18" />
|
color="dark-gray"
|
||||||
{$t('account_settings')}
|
size="sm"
|
||||||
</div>
|
shadow={false}
|
||||||
</Button>
|
border
|
||||||
</a>
|
>
|
||||||
|
<div class="flex place-content-center place-items-center gap-2 px-2">
|
||||||
|
<Icon path={mdiCog} size="18" />
|
||||||
|
{$t('account_settings')}
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4 flex flex-col">
|
<div class="mb-4 flex flex-col">
|
||||||
|
|||||||
@@ -60,9 +60,13 @@
|
|||||||
|
|
||||||
<section class="flex place-items-center justify-end gap-4 max-sm:w-full">
|
<section class="flex place-items-center justify-end gap-4 max-sm:w-full">
|
||||||
{#if $featureFlags.search}
|
{#if $featureFlags.search}
|
||||||
<a href={AppRoute.SEARCH} id="search-button" class="ml-4 sm:hidden">
|
<CircleIconButton
|
||||||
<CircleIconButton title={$t('go_to_search')} icon={mdiMagnify} />
|
href={AppRoute.SEARCH}
|
||||||
</a>
|
id="search-button"
|
||||||
|
class="ml-4 sm:hidden"
|
||||||
|
title={$t('go_to_search')}
|
||||||
|
icon={mdiMagnify}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<ThemeButton />
|
<ThemeButton />
|
||||||
|
|||||||
@@ -37,8 +37,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a href={getProductLink(ImmichProduct.Client)}>
|
<Button href={getProductLink(ImmichProduct.Client)} fullwidth>{$t('purchase_button_select')}</Button>
|
||||||
<Button fullwidth>{$t('purchase_button_select')}</Button>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,6 +25,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-6 w-full">
|
<div class="mt-6 w-full">
|
||||||
<Button fullwidth on:click={onDone}>OK</Button>
|
<Button fullwidth on:click={onDone}>{$t('ok')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -37,8 +37,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a href={getLicenseLink(ImmichProduct.Server)}>
|
<Button href={getLicenseLink(ImmichProduct.Server)} fullwidth>{$t('purchase_button_select')}</Button>
|
||||||
<Button fullwidth>{$t('purchase_button_select')}</Button>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,21 +13,31 @@
|
|||||||
import { focusOutside } from '$lib/actions/focus-outside';
|
import { focusOutside } from '$lib/actions/focus-outside';
|
||||||
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
import { generateId } from '$lib/utils/generate-id';
|
||||||
|
import { tick } from 'svelte';
|
||||||
|
|
||||||
export let value = '';
|
export let value = '';
|
||||||
export let grayTheme: boolean;
|
export let grayTheme: boolean;
|
||||||
export let searchQuery: MetadataSearchDto | SmartSearchDto = {};
|
export let searchQuery: MetadataSearchDto | SmartSearchDto = {};
|
||||||
|
|
||||||
|
$: showClearIcon = value.length > 0;
|
||||||
|
|
||||||
let input: HTMLInputElement;
|
let input: HTMLInputElement;
|
||||||
|
|
||||||
let showHistory = false;
|
let showSuggestions = false;
|
||||||
let showFilter = false;
|
let showFilter = false;
|
||||||
$: showClearIcon = value.length > 0;
|
let isSearchSuggestions = false;
|
||||||
|
let selectedId: string | undefined;
|
||||||
|
let moveSelection: (direction: 1 | -1) => void;
|
||||||
|
let clearSelection: () => void;
|
||||||
|
let selectActiveOption: () => void;
|
||||||
|
|
||||||
|
const listboxId = generateId();
|
||||||
|
|
||||||
const onSearch = async (payload: SmartSearchDto | MetadataSearchDto) => {
|
const onSearch = async (payload: SmartSearchDto | MetadataSearchDto) => {
|
||||||
const params = getMetadataSearchQuery(payload);
|
const params = getMetadataSearchQuery(payload);
|
||||||
|
|
||||||
showHistory = false;
|
closeDropdown();
|
||||||
showFilter = false;
|
showFilter = false;
|
||||||
$isSearchEnabled = false;
|
$isSearchEnabled = false;
|
||||||
await goto(`${AppRoute.SEARCH}?${params}`);
|
await goto(`${AppRoute.SEARCH}?${params}`);
|
||||||
@@ -39,7 +49,8 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const saveSearchTerm = (saveValue: string) => {
|
const saveSearchTerm = (saveValue: string) => {
|
||||||
$savedSearchTerms = [saveValue, ...$savedSearchTerms];
|
const filteredSearchTerms = $savedSearchTerms.filter((item) => item.toLowerCase() !== saveValue.toLowerCase());
|
||||||
|
$savedSearchTerms = [saveValue, ...filteredSearchTerms];
|
||||||
|
|
||||||
if ($savedSearchTerms.length > 5) {
|
if ($savedSearchTerms.length > 5) {
|
||||||
$savedSearchTerms = $savedSearchTerms.slice(0, 5);
|
$savedSearchTerms = $savedSearchTerms.slice(0, 5);
|
||||||
@@ -52,7 +63,6 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onFocusIn = () => {
|
const onFocusIn = () => {
|
||||||
showHistory = true;
|
|
||||||
$isSearchEnabled = true;
|
$isSearchEnabled = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -61,12 +71,13 @@
|
|||||||
$preventRaceConditionSearchBar = true;
|
$preventRaceConditionSearchBar = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
showHistory = false;
|
closeDropdown();
|
||||||
$isSearchEnabled = false;
|
$isSearchEnabled = false;
|
||||||
showFilter = false;
|
showFilter = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onHistoryTermClick = async (searchTerm: string) => {
|
const onHistoryTermClick = async (searchTerm: string) => {
|
||||||
|
value = searchTerm;
|
||||||
const searchPayload = { query: searchTerm };
|
const searchPayload = { query: searchTerm };
|
||||||
await onSearch(searchPayload);
|
await onSearch(searchPayload);
|
||||||
};
|
};
|
||||||
@@ -76,7 +87,7 @@
|
|||||||
value = '';
|
value = '';
|
||||||
|
|
||||||
if (showFilter) {
|
if (showFilter) {
|
||||||
showHistory = false;
|
closeDropdown();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -84,12 +95,49 @@
|
|||||||
handlePromiseError(onSearch({ query: value }));
|
handlePromiseError(onSearch({ query: value }));
|
||||||
saveSearchTerm(value);
|
saveSearchTerm(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onClear = () => {
|
||||||
|
value = '';
|
||||||
|
input.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEscape = () => {
|
||||||
|
closeDropdown();
|
||||||
|
showFilter = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onArrow = async (direction: 1 | -1) => {
|
||||||
|
openDropdown();
|
||||||
|
await tick();
|
||||||
|
moveSelection(direction);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEnter = (event: KeyboardEvent) => {
|
||||||
|
if (selectedId) {
|
||||||
|
event.preventDefault();
|
||||||
|
selectActiveOption();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onInput = () => {
|
||||||
|
openDropdown();
|
||||||
|
clearSelection();
|
||||||
|
};
|
||||||
|
|
||||||
|
const openDropdown = () => {
|
||||||
|
showSuggestions = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDropdown = () => {
|
||||||
|
showSuggestions = false;
|
||||||
|
clearSelection();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window
|
<svelte:window
|
||||||
use:shortcuts={[
|
use:shortcuts={[
|
||||||
{ shortcut: { key: 'Escape' }, onShortcut: onFocusOut },
|
{ shortcut: { key: 'Escape' }, onShortcut: onEscape },
|
||||||
{ shortcut: { ctrl: true, key: 'k' }, onShortcut: () => input.focus() },
|
{ shortcut: { ctrl: true, key: 'k' }, onShortcut: () => input.select() },
|
||||||
{ shortcut: { ctrl: true, shift: true, key: 'k' }, onShortcut: onFilterClick },
|
{ shortcut: { ctrl: true, shift: true, key: 'k' }, onShortcut: onFilterClick },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@@ -102,53 +150,69 @@
|
|||||||
action={AppRoute.SEARCH}
|
action={AppRoute.SEARCH}
|
||||||
on:reset={() => (value = '')}
|
on:reset={() => (value = '')}
|
||||||
on:submit|preventDefault={onSubmit}
|
on:submit|preventDefault={onSubmit}
|
||||||
|
on:focusin={onFocusIn}
|
||||||
|
role="search"
|
||||||
>
|
>
|
||||||
<div class="absolute inset-y-0 left-0 flex items-center pl-2">
|
<div use:focusOutside={{ onFocusOut: closeDropdown }}>
|
||||||
<CircleIconButton type="submit" title={$t('search')} icon={mdiMagnify} size="20" />
|
<label for="main-search-bar" class="sr-only">{$t('search_your_photos')}</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="q"
|
||||||
|
id="main-search-bar"
|
||||||
|
class="w-full transition-all border-2 px-14 py-4 text-immich-fg/75 dark:text-immich-dark-fg
|
||||||
|
{grayTheme ? 'dark:bg-immich-dark-gray' : 'dark:bg-immich-dark-bg'}
|
||||||
|
{(showSuggestions && isSearchSuggestions) || showFilter ? 'rounded-t-3xl' : 'rounded-3xl bg-gray-200'}
|
||||||
|
{$isSearchEnabled ? 'border-gray-200 dark:border-gray-700 bg-white' : 'border-transparent'}"
|
||||||
|
placeholder={$t('search_your_photos')}
|
||||||
|
required
|
||||||
|
pattern="^(?!m:$).*$"
|
||||||
|
bind:value
|
||||||
|
bind:this={input}
|
||||||
|
on:focus={openDropdown}
|
||||||
|
on:input={onInput}
|
||||||
|
disabled={showFilter}
|
||||||
|
role="combobox"
|
||||||
|
aria-controls={listboxId}
|
||||||
|
aria-activedescendant={selectedId ?? ''}
|
||||||
|
aria-expanded={showSuggestions && isSearchSuggestions}
|
||||||
|
aria-autocomplete="list"
|
||||||
|
use:shortcuts={[
|
||||||
|
{ shortcut: { key: 'Escape' }, onShortcut: onEscape },
|
||||||
|
{ shortcut: { ctrl: true, shift: true, key: 'k' }, onShortcut: onFilterClick },
|
||||||
|
{ shortcut: { key: 'ArrowUp' }, onShortcut: () => onArrow(-1) },
|
||||||
|
{ shortcut: { key: 'ArrowDown' }, onShortcut: () => onArrow(1) },
|
||||||
|
{ shortcut: { key: 'Enter' }, onShortcut: onEnter, preventDefault: false },
|
||||||
|
{ shortcut: { key: 'ArrowDown', alt: true }, onShortcut: openDropdown },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- SEARCH HISTORY BOX -->
|
||||||
|
<SearchHistoryBox
|
||||||
|
id={listboxId}
|
||||||
|
searchQuery={value}
|
||||||
|
isOpen={showSuggestions}
|
||||||
|
bind:isSearchSuggestions
|
||||||
|
bind:moveSelection
|
||||||
|
bind:clearSelection
|
||||||
|
bind:selectActiveOption
|
||||||
|
onClearAllSearchTerms={clearAllSearchTerms}
|
||||||
|
onClearSearchTerm={(searchTerm) => clearSearchTerm(searchTerm)}
|
||||||
|
onSelectSearchTerm={(searchTerm) => handlePromiseError(onHistoryTermClick(searchTerm))}
|
||||||
|
onActiveSelectionChange={(id) => (selectedId = id)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<label for="main-search-bar" class="sr-only">{$t('search_your_photos')}</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="q"
|
|
||||||
id="main-search-bar"
|
|
||||||
class="w-full {grayTheme
|
|
||||||
? 'dark:bg-immich-dark-gray'
|
|
||||||
: 'dark:bg-immich-dark-bg'} px-14 py-4 text-immich-fg/75 dark:text-immich-dark-fg {(showHistory &&
|
|
||||||
$savedSearchTerms.length > 0) ||
|
|
||||||
showFilter
|
|
||||||
? 'rounded-t-3xl border border-gray-200 bg-white dark:border-gray-800'
|
|
||||||
: 'rounded-3xl border border-transparent bg-gray-200'}"
|
|
||||||
placeholder={$t('search_your_photos')}
|
|
||||||
required
|
|
||||||
pattern="^(?!m:$).*$"
|
|
||||||
bind:value
|
|
||||||
bind:this={input}
|
|
||||||
on:click={onFocusIn}
|
|
||||||
on:focus={onFocusIn}
|
|
||||||
disabled={showFilter}
|
|
||||||
use:shortcuts={[
|
|
||||||
{ shortcut: { key: 'Escape' }, onShortcut: onFocusOut },
|
|
||||||
{ shortcut: { ctrl: true, shift: true, key: 'k' }, onShortcut: onFilterClick },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="absolute inset-y-0 {showClearIcon ? 'right-14' : 'right-2'} flex items-center pl-6 transition-all">
|
<div class="absolute inset-y-0 {showClearIcon ? 'right-14' : 'right-2'} flex items-center pl-6 transition-all">
|
||||||
<CircleIconButton title={$t('show_search_options')} icon={mdiTune} on:click={onFilterClick} size="20" />
|
<CircleIconButton title={$t('show_search_options')} icon={mdiTune} on:click={onFilterClick} size="20" />
|
||||||
</div>
|
</div>
|
||||||
{#if showClearIcon}
|
{#if showClearIcon}
|
||||||
<div class="absolute inset-y-0 right-0 flex items-center pr-2">
|
<div class="absolute inset-y-0 right-0 flex items-center pr-2">
|
||||||
<CircleIconButton type="reset" icon={mdiClose} title={$t('clear')} size="20" />
|
<CircleIconButton on:click={onClear} icon={mdiClose} title={$t('clear')} size="20" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
<div class="absolute inset-y-0 left-0 flex items-center pl-2">
|
||||||
<!-- SEARCH HISTORY BOX -->
|
<CircleIconButton type="submit" disabled={showFilter} title={$t('search')} icon={mdiMagnify} size="20" />
|
||||||
{#if showHistory && $savedSearchTerms.length > 0}
|
</div>
|
||||||
<SearchHistoryBox
|
|
||||||
on:clearAllSearchTerms={clearAllSearchTerms}
|
|
||||||
on:clearSearchTerm={({ detail: searchTerm }) => clearSearchTerm(searchTerm)}
|
|
||||||
on:selectSearchTerm={({ detail: searchTerm }) => handlePromiseError(onHistoryTermClick(searchTerm))}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{#if showFilter}
|
{#if showFilter}
|
||||||
|
|||||||
@@ -117,7 +117,7 @@
|
|||||||
<div
|
<div
|
||||||
bind:clientWidth={filterBoxWidth}
|
bind:clientWidth={filterBoxWidth}
|
||||||
transition:fly={{ y: 25, duration: 250 }}
|
transition:fly={{ y: 25, duration: 250 }}
|
||||||
class="absolute w-full rounded-b-3xl border border-t-0 border-gray-200 bg-white shadow-2xl dark:border-gray-800 dark:bg-immich-dark-gray dark:text-gray-300"
|
class="absolute w-full rounded-b-3xl border-2 border-t-0 border-gray-200 bg-white shadow-2xl dark:border-gray-700 dark:bg-immich-dark-gray dark:text-gray-300"
|
||||||
>
|
>
|
||||||
<form
|
<form
|
||||||
id="search-filter-form"
|
id="search-filter-form"
|
||||||
|
|||||||
@@ -2,51 +2,130 @@
|
|||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
import { savedSearchTerms } from '$lib/stores/search.store';
|
import { savedSearchTerms } from '$lib/stores/search.store';
|
||||||
import { mdiMagnify, mdiClose } from '@mdi/js';
|
import { mdiMagnify, mdiClose } from '@mdi/js';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
export let id: string;
|
||||||
selectSearchTerm: string;
|
export let searchQuery: string = '';
|
||||||
clearSearchTerm: string;
|
export let isSearchSuggestions: boolean = false;
|
||||||
clearAllSearchTerms: void;
|
export let isOpen: boolean = false;
|
||||||
}>();
|
export let onSelectSearchTerm: (searchTerm: string) => void;
|
||||||
|
export let onClearSearchTerm: (searchTerm: string) => void;
|
||||||
|
export let onClearAllSearchTerms: () => void;
|
||||||
|
export let onActiveSelectionChange: (selectedId: string | undefined) => void;
|
||||||
|
|
||||||
|
$: filteredSearchTerms = $savedSearchTerms.filter((term) => term.toLowerCase().includes(searchQuery.toLowerCase()));
|
||||||
|
$: isSearchSuggestions = filteredSearchTerms.length > 0;
|
||||||
|
$: showClearAll = searchQuery === '';
|
||||||
|
$: suggestionCount = showClearAll ? filteredSearchTerms.length + 1 : filteredSearchTerms.length;
|
||||||
|
|
||||||
|
let selectedIndex: number | undefined = undefined;
|
||||||
|
let element: HTMLDivElement;
|
||||||
|
|
||||||
|
export function moveSelection(increment: 1 | -1) {
|
||||||
|
if (!isSearchSuggestions) {
|
||||||
|
return;
|
||||||
|
} else if (selectedIndex === undefined) {
|
||||||
|
selectedIndex = increment === 1 ? 0 : suggestionCount - 1;
|
||||||
|
} else if (selectedIndex + increment < 0 || selectedIndex + increment >= suggestionCount) {
|
||||||
|
clearSelection();
|
||||||
|
} else {
|
||||||
|
selectedIndex = (selectedIndex + increment + suggestionCount) % suggestionCount;
|
||||||
|
}
|
||||||
|
onActiveSelectionChange(getId(selectedIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearSelection() {
|
||||||
|
selectedIndex = undefined;
|
||||||
|
onActiveSelectionChange(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selectActiveOption() {
|
||||||
|
if (selectedIndex === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const selectedElement = element.querySelector(`#${getId(selectedIndex)}`) as HTMLElement;
|
||||||
|
selectedElement?.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClearAll = () => {
|
||||||
|
clearSelection();
|
||||||
|
onClearAllSearchTerms();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClearSingle = (searchTerm: string) => {
|
||||||
|
clearSelection();
|
||||||
|
onClearSearchTerm(searchTerm);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelect = (searchTerm: string) => {
|
||||||
|
clearSelection();
|
||||||
|
onSelectSearchTerm(searchTerm);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getId = (index: number | undefined) => {
|
||||||
|
if (index === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return `${id}-${index}`;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div role="listbox" {id} aria-label={$t('recent_searches')} bind:this={element}>
|
||||||
transition:fly={{ y: 25, duration: 250 }}
|
{#if isOpen && isSearchSuggestions}
|
||||||
class="absolute w-full rounded-b-3xl border border-gray-200 bg-white pb-5 shadow-2xl transition-all dark:border-gray-800 dark:bg-immich-dark-gray dark:text-gray-300"
|
<div
|
||||||
>
|
transition:fly={{ y: 25, duration: 150 }}
|
||||||
{#if $savedSearchTerms.length > 0}
|
class="absolute w-full rounded-b-3xl border-2 border-t-0 border-gray-200 bg-white pb-5 shadow-2xl transition-all dark:border-gray-700 dark:bg-immich-dark-gray dark:text-gray-300"
|
||||||
<div class="flex items-center justify-between px-5 pt-5 text-xs">
|
>
|
||||||
<p>{$t('recent_searches').toUpperCase()}</p>
|
<div class="flex items-center justify-between px-5 pt-5 text-xs">
|
||||||
<button
|
<p class="py-2" aria-hidden={true}>{$t('recent_searches').toUpperCase()}</p>
|
||||||
type="button"
|
{#if showClearAll}
|
||||||
class="rounded-lg p-2 font-semibold text-immich-primary hover:bg-immich-primary/25 dark:text-immich-dark-primary"
|
<button
|
||||||
on:click={() => dispatch('clearAllSearchTerms')}>{$t('clear_all')}</button
|
id={getId(0)}
|
||||||
>
|
type="button"
|
||||||
|
class="rounded-lg p-2 font-semibold text-immich-primary aria-selected:bg-immich-primary/25 hover:bg-immich-primary/25 dark:text-immich-dark-primary"
|
||||||
|
role="option"
|
||||||
|
on:click={() => handleClearAll()}
|
||||||
|
tabindex="-1"
|
||||||
|
aria-selected={selectedIndex === 0}
|
||||||
|
aria-label={$t('clear_all_recent_searches')}
|
||||||
|
>
|
||||||
|
{$t('clear_all')}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#each filteredSearchTerms as savedSearchTerm, i (i)}
|
||||||
|
{@const index = showClearAll ? i + 1 : i}
|
||||||
|
<div class="flex w-full items-center justify-between text-sm text-black dark:text-gray-300">
|
||||||
|
<div class="relative w-full items-center">
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<div
|
||||||
|
id={getId(index)}
|
||||||
|
class="relative flex w-full cursor-pointer gap-3 py-3 pl-5 hover:bg-gray-100 aria-selected:bg-gray-100 dark:aria-selected:bg-gray-500/30 dark:hover:bg-gray-500/30"
|
||||||
|
on:click={() => handleSelect(savedSearchTerm)}
|
||||||
|
role="option"
|
||||||
|
tabindex="-1"
|
||||||
|
aria-selected={selectedIndex === index}
|
||||||
|
aria-label={savedSearchTerm}
|
||||||
|
>
|
||||||
|
<Icon path={mdiMagnify} size="1.5em" ariaHidden={true} />
|
||||||
|
{savedSearchTerm}
|
||||||
|
</div>
|
||||||
|
<div aria-hidden={true} class="absolute right-5 top-0 items-center justify-center py-3">
|
||||||
|
<CircleIconButton
|
||||||
|
icon={mdiClose}
|
||||||
|
title={$t('remove')}
|
||||||
|
size="18"
|
||||||
|
padding="1"
|
||||||
|
tabindex={-1}
|
||||||
|
on:click={() => handleClearSingle(savedSearchTerm)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#each $savedSearchTerms as savedSearchTerm, i (i)}
|
|
||||||
<div
|
|
||||||
class="flex w-full items-center justify-between text-sm text-black hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-500/10"
|
|
||||||
>
|
|
||||||
<div class="relative w-full items-center">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="relative flex w-full cursor-pointer gap-3 py-3 pl-5"
|
|
||||||
on:click={() => dispatch('selectSearchTerm', savedSearchTerm)}
|
|
||||||
>
|
|
||||||
<Icon path={mdiMagnify} size="1.5em" />
|
|
||||||
{savedSearchTerm}
|
|
||||||
</button>
|
|
||||||
<div class="absolute right-5 top-0 items-center justify-center py-3">
|
|
||||||
<button type="button" on:click={() => dispatch('clearSearchTerm', savedSearchTerm)}
|
|
||||||
><Icon path={mdiClose} size="18" /></button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
info?: string;
|
info?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const shortcuts: Shortcuts = {
|
export let shortcuts: Shortcuts = {
|
||||||
general: [
|
general: [
|
||||||
{ key: ['←', '→'], action: $t('previous_or_next_photo') },
|
{ key: ['←', '→'], action: $t('previous_or_next_photo') },
|
||||||
{ key: ['Esc'], action: $t('back_close_deselect') },
|
{ key: ['Esc'], action: $t('back_close_deselect') },
|
||||||
@@ -40,45 +40,48 @@
|
|||||||
|
|
||||||
<FullScreenModal title={$t('keyboard_shortcuts')} width="auto" onClose={() => dispatch('close')}>
|
<FullScreenModal title={$t('keyboard_shortcuts')} width="auto" onClose={() => dispatch('close')}>
|
||||||
<div class="grid grid-cols-1 gap-4 px-4 pb-4 md:grid-cols-2">
|
<div class="grid grid-cols-1 gap-4 px-4 pb-4 md:grid-cols-2">
|
||||||
<div class="p-4">
|
{#if shortcuts.general.length > 0}
|
||||||
<h2>{$t('general')}</h2>
|
<div class="p-4">
|
||||||
<div class="text-sm">
|
<h2>{$t('general')}</h2>
|
||||||
{#each shortcuts.general as shortcut}
|
<div class="text-sm">
|
||||||
<div class="grid grid-cols-[30%_70%] items-center gap-4 pt-4 text-sm">
|
{#each shortcuts.general as shortcut}
|
||||||
<div class="flex justify-self-end">
|
<div class="grid grid-cols-[30%_70%] items-center gap-4 pt-4 text-sm">
|
||||||
{#each shortcut.key as key}
|
<div class="flex justify-self-end">
|
||||||
<p class="mr-1 flex items-center justify-center justify-self-end rounded-lg bg-immich-primary/25 p-2">
|
{#each shortcut.key as key}
|
||||||
{key}
|
<p class="mr-1 flex items-center justify-center justify-self-end rounded-lg bg-immich-primary/25 p-2">
|
||||||
</p>
|
{key}
|
||||||
{/each}
|
</p>
|
||||||
</div>
|
{/each}
|
||||||
<p class="mb-1 mt-1 flex">{shortcut.action}</p>
|
</div>
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="p-4">
|
|
||||||
<h2>{$t('actions')}</h2>
|
|
||||||
<div class="text-sm">
|
|
||||||
{#each shortcuts.actions as shortcut}
|
|
||||||
<div class="grid grid-cols-[30%_70%] items-center gap-4 pt-4 text-sm">
|
|
||||||
<div class="flex justify-self-end">
|
|
||||||
{#each shortcut.key as key}
|
|
||||||
<p class="mr-1 flex items-center justify-center justify-self-end rounded-lg bg-immich-primary/25 p-2">
|
|
||||||
{key}
|
|
||||||
</p>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<p class="mb-1 mt-1 flex">{shortcut.action}</p>
|
<p class="mb-1 mt-1 flex">{shortcut.action}</p>
|
||||||
{#if shortcut.info}
|
|
||||||
<Icon path={mdiInformationOutline} title={shortcut.info} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/each}
|
||||||
{/each}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
|
{#if shortcuts.actions.length > 0}
|
||||||
|
<div class="p-4">
|
||||||
|
<h2>{$t('actions')}</h2>
|
||||||
|
<div class="text-sm">
|
||||||
|
{#each shortcuts.actions as shortcut}
|
||||||
|
<div class="grid grid-cols-[30%_70%] items-center gap-4 pt-4 text-sm">
|
||||||
|
<div class="flex justify-self-end">
|
||||||
|
{#each shortcut.key as key}
|
||||||
|
<p class="mr-1 flex items-center justify-center justify-self-end rounded-lg bg-immich-primary/25 p-2">
|
||||||
|
{key}
|
||||||
|
</p>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<p class="mb-1 mt-1 flex">{shortcut.action}</p>
|
||||||
|
{#if shortcut.info}
|
||||||
|
<Icon path={mdiInformationOutline} title={shortcut.info} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</FullScreenModal>
|
</FullScreenModal>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
showBuyButton = getButtonVisibility();
|
showBuyButton = getButtonVisibility();
|
||||||
showMessage = false;
|
showMessage = false;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, 'Error hiding buy button');
|
handleError(error, $t('errors.error_hiding_buy_button'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
<div class="h-6 w-6">
|
<div class="h-6 w-6">
|
||||||
<ImmichLogo noText />
|
<ImmichLogo noText />
|
||||||
</div>
|
</div>
|
||||||
<p class="dark:text-gray-100">Supporter</p>
|
<p class="dark:text-gray-100">{$t('purchase_account_info')}</p>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
{:else if !$isPurchased && showBuyButton}
|
{:else if !$isPurchased && showBuyButton}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
||||||
import { mdiCog, mdiWindowMinimize, mdiCancel, mdiCloudUploadOutline } from '@mdi/js';
|
import { mdiCog, mdiWindowMinimize, mdiCancel, mdiCloudUploadOutline } from '@mdi/js';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
|
|
||||||
let showDetail = false;
|
let showDetail = false;
|
||||||
let showOptions = false;
|
let showOptions = false;
|
||||||
@@ -73,9 +74,14 @@
|
|||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
<p class="immich-form-label text-xs">
|
<p class="immich-form-label text-xs">
|
||||||
{$t('upload_status_uploaded')} <span class="text-immich-success">{$successCounter}</span> -
|
{$t('upload_status_uploaded')}
|
||||||
{$t('upload_status_errors')} <span class="text-immich-error">{$errorCounter}</span> -
|
<span class="text-immich-success">{$successCounter.toLocaleString($locale)}</span>
|
||||||
{$t('upload_status_duplicates')} <span class="text-immich-warning">{$duplicateCounter}</span>
|
-
|
||||||
|
{$t('upload_status_errors')}
|
||||||
|
<span class="text-immich-error">{$errorCounter.toLocaleString($locale)}</span>
|
||||||
|
-
|
||||||
|
{$t('upload_status_duplicates')}
|
||||||
|
<span class="text-immich-warning">{$duplicateCounter.toLocaleString($locale)}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col items-end">
|
<div class="flex flex-col items-end">
|
||||||
@@ -139,7 +145,7 @@
|
|||||||
on:click={() => (showDetail = true)}
|
on:click={() => (showDetail = true)}
|
||||||
class="absolute -left-4 -top-4 flex h-10 w-10 place-content-center place-items-center rounded-full bg-immich-primary p-5 text-xs text-gray-200"
|
class="absolute -left-4 -top-4 flex h-10 w-10 place-content-center place-items-center rounded-full bg-immich-primary p-5 text-xs text-gray-200"
|
||||||
>
|
>
|
||||||
{$remainingUploads}
|
{$remainingUploads.toLocaleString($locale)}
|
||||||
</button>
|
</button>
|
||||||
{#if $hasError}
|
{#if $hasError}
|
||||||
<button
|
<button
|
||||||
@@ -148,7 +154,7 @@
|
|||||||
on:click={() => (showDetail = true)}
|
on:click={() => (showDetail = true)}
|
||||||
class="absolute -right-4 -top-4 flex h-10 w-10 place-content-center place-items-center rounded-full bg-immich-error p-5 text-xs text-gray-200"
|
class="absolute -right-4 -top-4 flex h-10 w-10 place-content-center place-items-center rounded-full bg-immich-error p-5 text-xs text-gray-200"
|
||||||
>
|
>
|
||||||
{$errorCounter}
|
{$errorCounter.toLocaleString($locale)}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -63,10 +63,10 @@
|
|||||||
const removeIndividualProductKey = async () => {
|
const removeIndividualProductKey = async () => {
|
||||||
try {
|
try {
|
||||||
const isConfirmed = await dialogController.show({
|
const isConfirmed = await dialogController.show({
|
||||||
title: 'Remove Product Key',
|
title: $t('purchase_remove_product_key'),
|
||||||
prompt: 'Are you sure you want to remove the product key?',
|
prompt: $t('purchase_remove_product_key_prompt'),
|
||||||
confirmText: 'Remove',
|
confirmText: $t('remove'),
|
||||||
cancelText: 'Cancel',
|
cancelText: $t('cancel'),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isConfirmed) {
|
if (!isConfirmed) {
|
||||||
@@ -76,17 +76,17 @@
|
|||||||
await deleteIndividualProductKey();
|
await deleteIndividualProductKey();
|
||||||
purchaseStore.setPurchaseStatus(false);
|
purchaseStore.setPurchaseStatus(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, 'Failed to remove product key');
|
handleError(error, $t('errors.failed_to_remove_product_key'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeServerProductKey = async () => {
|
const removeServerProductKey = async () => {
|
||||||
try {
|
try {
|
||||||
const isConfirmed = await dialogController.show({
|
const isConfirmed = await dialogController.show({
|
||||||
title: 'Remove License',
|
title: $t('purchase_remove_server_product_key'),
|
||||||
prompt: 'Are you sure you want to remove the Server product key?',
|
prompt: $t('purchase_remove_server_product_key_prompt'),
|
||||||
confirmText: 'Remove',
|
confirmText: $t('remove'),
|
||||||
cancelText: 'Cancel',
|
cancelText: $t('cancel'),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isConfirmed) {
|
if (!isConfirmed) {
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
await deleteServerProductKey();
|
await deleteServerProductKey();
|
||||||
purchaseStore.setPurchaseStatus(false);
|
purchaseStore.setPurchaseStatus(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, 'Failed to remove product key');
|
handleError(error, $t('errors.failed_to_remove_product_key'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
{#if $user.isAdmin && serverPurchaseInfo?.activatedAt}
|
{#if $user.isAdmin && serverPurchaseInfo?.activatedAt}
|
||||||
<p class="dark:text-white text-sm mt-1 col-start-2">
|
<p class="dark:text-white text-sm mt-1 col-start-2">
|
||||||
{$t('purchase_activated_time', {
|
{$t('purchase_activated_time', {
|
||||||
values: { date: new Date(serverPurchaseInfo.activatedAt).toLocaleDateString() },
|
values: { date: new Date(serverPurchaseInfo.activatedAt) },
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
{:else}
|
{:else}
|
||||||
@@ -161,7 +161,7 @@
|
|||||||
{#if $user.license?.activatedAt}
|
{#if $user.license?.activatedAt}
|
||||||
<p class="dark:text-white text-sm mt-1 col-start-2">
|
<p class="dark:text-white text-sm mt-1 col-start-2">
|
||||||
{$t('purchase_activated_time', {
|
{$t('purchase_activated_time', {
|
||||||
values: { date: new Date($user.license?.activatedAt).toLocaleDateString() },
|
values: { date: new Date($user.license?.activatedAt) },
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -64,8 +64,14 @@
|
|||||||
|
|
||||||
<svelte:window
|
<svelte:window
|
||||||
use:shortcuts={[
|
use:shortcuts={[
|
||||||
{ shortcut: { key: 'k', shift: true }, onShortcut: onSelectAll },
|
{ shortcut: { key: 'a' }, onShortcut: onSelectAll },
|
||||||
{ shortcut: { key: 't', shift: true }, onShortcut: onSelectNone },
|
{
|
||||||
|
shortcut: { key: 's' },
|
||||||
|
onShortcut: () => {
|
||||||
|
setAsset(assets[0]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ shortcut: { key: 'd' }, onShortcut: onSelectNone },
|
||||||
{ shortcut: { key: 'c', shift: true }, onShortcut: handleResolve },
|
{ shortcut: { key: 'c', shift: true }, onShortcut: handleResolve },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -366,7 +366,7 @@
|
|||||||
"api_keys": "مفاتيح واجهة برمجة التطبيقات",
|
"api_keys": "مفاتيح واجهة برمجة التطبيقات",
|
||||||
"app_settings": "إعدادات التطبيق",
|
"app_settings": "إعدادات التطبيق",
|
||||||
"appears_in": "يظهر في",
|
"appears_in": "يظهر في",
|
||||||
"archive": "أرشيف",
|
"archive": "الأرشيف",
|
||||||
"archive_or_unarchive_photo": "أرشفة الصورة أو إلغاء أرشفتها",
|
"archive_or_unarchive_photo": "أرشفة الصورة أو إلغاء أرشفتها",
|
||||||
"archive_size": "حجم الأرشيف",
|
"archive_size": "حجم الأرشيف",
|
||||||
"archive_size_description": "تكوين حجم الأرشيف للتنزيلات (بالجيجابايت)",
|
"archive_size_description": "تكوين حجم الأرشيف للتنزيلات (بالجيجابايت)",
|
||||||
@@ -1215,7 +1215,7 @@
|
|||||||
"user_usage_detail": "تفاصيل استخدام المستخدم",
|
"user_usage_detail": "تفاصيل استخدام المستخدم",
|
||||||
"username": "اسم المستخدم",
|
"username": "اسم المستخدم",
|
||||||
"users": "المستخدمين",
|
"users": "المستخدمين",
|
||||||
"utilities": "مُعدات",
|
"utilities": "أدوات",
|
||||||
"validate": "تحقْق",
|
"validate": "تحقْق",
|
||||||
"variables": "المتغيرات",
|
"variables": "المتغيرات",
|
||||||
"version": "الإصدار",
|
"version": "الإصدار",
|
||||||
|
|||||||
@@ -169,7 +169,7 @@
|
|||||||
"oauth_client_secret": "Клиентска тайна",
|
"oauth_client_secret": "Клиентска тайна",
|
||||||
"oauth_enable_description": "",
|
"oauth_enable_description": "",
|
||||||
"oauth_issuer_url": "",
|
"oauth_issuer_url": "",
|
||||||
"oauth_mobile_redirect_uri": "",
|
"oauth_mobile_redirect_uri": "URI за мобилно пренасочване",
|
||||||
"oauth_mobile_redirect_uri_override": "",
|
"oauth_mobile_redirect_uri_override": "",
|
||||||
"oauth_mobile_redirect_uri_override_description": "Разреши когато 'app.immich:/' е невалиден пренасочвар адрес/URI.",
|
"oauth_mobile_redirect_uri_override_description": "Разреши когато 'app.immich:/' е невалиден пренасочвар адрес/URI.",
|
||||||
"oauth_profile_signing_algorithm": "Алгоритъм за създаване на профили",
|
"oauth_profile_signing_algorithm": "Алгоритъм за създаване на профили",
|
||||||
|
|||||||
@@ -128,6 +128,7 @@
|
|||||||
"map_dark_style": "Tema fosc",
|
"map_dark_style": "Tema fosc",
|
||||||
"map_enable_description": "Habilita característiques del mapa",
|
"map_enable_description": "Habilita característiques del mapa",
|
||||||
"map_gps_settings": "Configuració de mapa i GPS",
|
"map_gps_settings": "Configuració de mapa i GPS",
|
||||||
|
"map_gps_settings_description": "Gestiona la configuració de mapa i GPS (Geocodificació inversa)",
|
||||||
"map_light_style": "Tema clar",
|
"map_light_style": "Tema clar",
|
||||||
"map_manage_reverse_geocoding_settings": "Gestiona els paràmetres de <link>geocodificació inversa</link>",
|
"map_manage_reverse_geocoding_settings": "Gestiona els paràmetres de <link>geocodificació inversa</link>",
|
||||||
"map_reverse_geocoding": "Geocodificació inversa",
|
"map_reverse_geocoding": "Geocodificació inversa",
|
||||||
@@ -173,6 +174,7 @@
|
|||||||
"oauth_mobile_redirect_uri": "URI de redirecció mòbil",
|
"oauth_mobile_redirect_uri": "URI de redirecció mòbil",
|
||||||
"oauth_mobile_redirect_uri_override": "Sobreescriu l'URI de redirecció mòbil",
|
"oauth_mobile_redirect_uri_override": "Sobreescriu l'URI de redirecció mòbil",
|
||||||
"oauth_mobile_redirect_uri_override_description": "Habilita quan 'app.immich:/' és una URI de redirecció invàlida.",
|
"oauth_mobile_redirect_uri_override_description": "Habilita quan 'app.immich:/' és una URI de redirecció invàlida.",
|
||||||
|
"oauth_profile_signing_algorithm": "Algoritme de signatura del perfil",
|
||||||
"oauth_profile_signing_algorithm_description": "Algoritme utilitzat per signar el perfil d’usuari.",
|
"oauth_profile_signing_algorithm_description": "Algoritme utilitzat per signar el perfil d’usuari.",
|
||||||
"oauth_scope": "Abast",
|
"oauth_scope": "Abast",
|
||||||
"oauth_settings": "OAuth",
|
"oauth_settings": "OAuth",
|
||||||
@@ -224,82 +226,97 @@
|
|||||||
"storage_template_migration_description": "Aplica la <link>{template}</link> actual als elements pujats prèviament",
|
"storage_template_migration_description": "Aplica la <link>{template}</link> actual als elements pujats prèviament",
|
||||||
"storage_template_migration_info": "Els canvis de plantilla només s'aplicaran a nous elements. Per aplicar la plantilla rectroactivament a elements pujats prèviament, executeu la <link>{job}</link>.",
|
"storage_template_migration_info": "Els canvis de plantilla només s'aplicaran a nous elements. Per aplicar la plantilla rectroactivament a elements pujats prèviament, executeu la <link>{job}</link>.",
|
||||||
"storage_template_migration_job": "Tasca de migració de la plantilla d'emmagatzematge",
|
"storage_template_migration_job": "Tasca de migració de la plantilla d'emmagatzematge",
|
||||||
|
"storage_template_more_details": "Per obtenir més detalls sobre aquesta funció, consulteu la <template-link>Storage Template</template-link> i les seves <implications-link>implications</implications-link>",
|
||||||
|
"storage_template_onboarding_description": "Quan està activada, aquesta funció organitzarà automàticament els fitxers en funció d'una plantilla definida per l'usuari. A causa de problemes d'estabilitat, la funció s'ha desactivat de manera predeterminada. Per obtenir més informació, consulteu la <link>documentation</link>.",
|
||||||
|
"storage_template_path_length": "Límit aproximat de longitud de la ruta: <b>{length, number}</b>/{limit, number}",
|
||||||
"storage_template_settings": "Plantilla d'emmagatzematge",
|
"storage_template_settings": "Plantilla d'emmagatzematge",
|
||||||
"storage_template_settings_description": "Gestiona l'estructura de les carpetes i el nom del fitxers dels elements pujats",
|
"storage_template_settings_description": "Gestiona l'estructura de les carpetes i el nom del fitxers dels elements pujats",
|
||||||
|
"storage_template_user_label": "<code>{label}</code> és l'etiqueta d'emmagatzematge de l'usuari",
|
||||||
"system_settings": "Configuració del sistema",
|
"system_settings": "Configuració del sistema",
|
||||||
"theme_custom_css_settings": "CSS personalitzat",
|
"theme_custom_css_settings": "CSS personalitzat",
|
||||||
"theme_custom_css_settings_description": "",
|
"theme_custom_css_settings_description": "Els Fulls d'Estil en Cascada permeten personalitzar el disseny d'Immich.",
|
||||||
"theme_settings": "Configuració del tema",
|
"theme_settings": "Configuració del tema",
|
||||||
"theme_settings_description": "Gestiona la personalització de la interfície web Immich",
|
"theme_settings_description": "Gestiona la personalització de la interfície web Immich",
|
||||||
|
"these_files_matched_by_checksum": "Aquests fitxers coincideixen amb els seus checksums",
|
||||||
"thumbnail_generation_job": "Generar miniatures",
|
"thumbnail_generation_job": "Generar miniatures",
|
||||||
"thumbnail_generation_job_description": "Genera miniatures grans, petites i borroses per a cada element, així com miniatures per a cada persona",
|
"thumbnail_generation_job_description": "Genera miniatures grans, petites i borroses per a cada element, així com miniatures per a cada persona",
|
||||||
"transcode_policy_description": "",
|
"transcode_policy_description": "",
|
||||||
"transcoding_acceleration_api": "API d'acceleració",
|
"transcoding_acceleration_api": "API d'acceleració",
|
||||||
"transcoding_acceleration_api_description": "",
|
"transcoding_acceleration_api_description": "L'API que interactuarà amb el vostre dispositiu per accelerar la transcodificació. Aquesta configuració és \"millor esforç\": tornarà a la transcodificació del programari en cas d'error. VP9 pot funcionar o no depenent del vostre maquinari.",
|
||||||
"transcoding_acceleration_nvenc": "NVENC (requereix GPU d'NVIDIA)",
|
"transcoding_acceleration_nvenc": "NVENC (requereix GPU d'NVIDIA)",
|
||||||
"transcoding_acceleration_qsv": "Quick Sync (requereix GPU d'Intel de 7a generació o posterior)",
|
"transcoding_acceleration_qsv": "Quick Sync (requereix GPU d'Intel de 7a generació o posterior)",
|
||||||
"transcoding_acceleration_rkmpp": "RKMPP (requereix SoC de Rockchip)",
|
"transcoding_acceleration_rkmpp": "RKMPP (requereix SoC de Rockchip)",
|
||||||
"transcoding_acceleration_vaapi": "VAAPI",
|
"transcoding_acceleration_vaapi": "VAAPI",
|
||||||
"transcoding_accepted_audio_codecs": "Còdecs d'àudio acceptats",
|
"transcoding_accepted_audio_codecs": "Còdecs d'àudio acceptats",
|
||||||
"transcoding_accepted_audio_codecs_description": "",
|
"transcoding_accepted_audio_codecs_description": "Seleccioneu quins còdecs d'àudio no s'han de transcodificar. Només s'utilitza per a determinades polítiques de transcodificació.",
|
||||||
|
"transcoding_accepted_containers": "Contenidors acceptats",
|
||||||
|
"transcoding_accepted_containers_description": "Seleccioneu quins formats de contenidor no s'han de redistribuir a MP4. Només s'utilitza per a determinades polítiques de transcodificació.",
|
||||||
"transcoding_accepted_video_codecs": "Còdecs de vídeo acceptats",
|
"transcoding_accepted_video_codecs": "Còdecs de vídeo acceptats",
|
||||||
"transcoding_accepted_video_codecs_description": "",
|
"transcoding_accepted_video_codecs_description": "Seleccioneu quins còdecs de vídeo no s'han de transcodificar. Només s'utilitza per a determinades polítiques de transcodificació.",
|
||||||
"transcoding_advanced_options_description": "",
|
"transcoding_advanced_options_description": "Opcions que la majoria dels usuaris no haurien de canviar",
|
||||||
"transcoding_audio_codec": "Còdec d'àudio",
|
"transcoding_audio_codec": "Còdec d'àudio",
|
||||||
"transcoding_audio_codec_description": "",
|
"transcoding_audio_codec_description": "Opus és l'opció de màxima qualitat, però té menor compatibilitat amb dispositius o programari antics.",
|
||||||
"transcoding_bitrate_description": "",
|
"transcoding_bitrate_description": "Vídeos superiors a la taxa de bits màxima o que no tenen un format acceptat",
|
||||||
"transcoding_codecs_learn_more": "Per obtenir més informació sobre la terminologia utilitzada, consulteu la documentació de FFmpeg per al <h264-link> còdec H.264</h264-link>, <hevc-link> còdec HEVC</hevc-link> i <vp9-link> còdec VP9</vp9-link>.",
|
"transcoding_codecs_learn_more": "Per obtenir més informació sobre la terminologia utilitzada, consulteu la documentació de FFmpeg per al <h264-link> còdec H.264</h264-link>, <hevc-link> còdec HEVC</hevc-link> i <vp9-link> còdec VP9</vp9-link>.",
|
||||||
"transcoding_constant_quality_mode": "Mode de qualitat constant",
|
"transcoding_constant_quality_mode": "Mode de qualitat constant",
|
||||||
"transcoding_constant_quality_mode_description": "",
|
"transcoding_constant_quality_mode_description": "ICQ és millor que CQP, però alguns dispositius d'acceleració de maquinari no admeten aquest mode. Establir aquesta opció preferirà el mode especificat quan utilitzeu la codificació basada en la qualitat. Ignorat per NVENC perquè no és compatible amb ICQ.",
|
||||||
"transcoding_constant_rate_factor": "",
|
"transcoding_constant_rate_factor": "Factor de taxa constant (-crf)",
|
||||||
"transcoding_constant_rate_factor_description": "",
|
"transcoding_constant_rate_factor_description": "Nivell de qualitat del vídeo. Els valors típics són 23 per a H.264, 28 per a HEVC, 31 per a VP9 i 35 per a AV1. Més baix és millor, però produeix fitxers més grans.",
|
||||||
"transcoding_disabled_description": "",
|
"transcoding_disabled_description": "No transcodifiqueu cap vídeo, pot interrompre la reproducció en alguns clients",
|
||||||
"transcoding_hardware_acceleration": "Acceleració de maquinari",
|
"transcoding_hardware_acceleration": "Acceleració de maquinari",
|
||||||
"transcoding_hardware_acceleration_description": "Experimental. Molt més ràpid, però tindrà una qualitat més baixa amb la mateixa taxa de bits",
|
"transcoding_hardware_acceleration_description": "Experimental. Molt més ràpid, però tindrà una qualitat més baixa amb la mateixa taxa de bits",
|
||||||
"transcoding_hardware_decoding": "Descodificació de maquinari",
|
"transcoding_hardware_decoding": "Descodificació de maquinari",
|
||||||
"transcoding_hardware_decoding_setting_description": "",
|
"transcoding_hardware_decoding_setting_description": "S'aplica només a NVENC, QSV i RKMPP. Permet l'acceleració d'extrem a extrem en lloc d'accelerar només la codificació. És possible que no funcioni en tots els vídeos.",
|
||||||
"transcoding_hevc_codec": "Còdec HEVC",
|
"transcoding_hevc_codec": "Còdec HEVC",
|
||||||
"transcoding_max_b_frames": "",
|
"transcoding_max_b_frames": "Nombre màxim de B-frames",
|
||||||
"transcoding_max_b_frames_description": "",
|
"transcoding_max_b_frames_description": "Els valors més alts milloren l'eficiència de la compressió, però alenteixen la codificació. És possible que no sigui compatible amb l'acceleració de maquinari en dispositius antics. 0 desactiva els B-frames, mentre que -1 estableix aquest valor automàticament.",
|
||||||
"transcoding_max_bitrate": "",
|
"transcoding_max_bitrate": "Taxa de bits màxima",
|
||||||
"transcoding_max_bitrate_description": "",
|
"transcoding_max_bitrate_description": "Establir una taxa de bits màxima pot fer que les mides dels fitxers siguin més previsibles amb un cost menor per a la qualitat. A 720p, els valors típics són 2600k per a VP9 o HEVC, o 4500k per a H.264. Desactivat si s'estableix a 0.",
|
||||||
"transcoding_max_keyframe_interval": "",
|
"transcoding_max_keyframe_interval": "Interval màxim de fotogrames clau",
|
||||||
"transcoding_max_keyframe_interval_description": "",
|
"transcoding_max_keyframe_interval_description": "Estableix la distància màxima entre fotogrames clau. Els valors més baixos empitjoren l'eficiència de la compressió, però milloren els temps de cerca i poden millorar la qualitat en escenes amb moviment ràpid. 0 estableix aquest valor automàticament.",
|
||||||
"transcoding_optimal_description": "",
|
"transcoding_optimal_description": "Vídeos superiors a la resolució objectiu o que no tenen un format acceptat",
|
||||||
"transcoding_preferred_hardware_device": "Dispositiu de maquinari preferit",
|
"transcoding_preferred_hardware_device": "Dispositiu de maquinari preferit",
|
||||||
"transcoding_preferred_hardware_device_description": "",
|
"transcoding_preferred_hardware_device_description": "S'aplica només a VAAPI i QSV. Estableix el node dri utilitzat per a la transcodificació de maquinari.",
|
||||||
"transcoding_preset_preset": "",
|
"transcoding_preset_preset": "Preestablert (-preset)",
|
||||||
"transcoding_preset_preset_description": "",
|
"transcoding_preset_preset_description": "Velocitat de compressió. Els valors predefinits més lents produeixen fitxers més petits i augmenten la qualitat quan s'orienta a una taxa de bits determinada. VP9 ignora les velocitats superiors a \"més ràpides\".",
|
||||||
"transcoding_reference_frames": "",
|
"transcoding_reference_frames": "Fotogrames de referència",
|
||||||
"transcoding_reference_frames_description": "",
|
"transcoding_reference_frames_description": "El nombre de fotogrames a fer referència en comprimir un fotograma determinat. Els valors més alts milloren l'eficiència de la compressió, però alenteixen la codificació. 0 estableix aquest valor automàticament.",
|
||||||
"transcoding_required_description": "",
|
"transcoding_required_description": "Només vídeos que no tenen un format acceptat",
|
||||||
"transcoding_settings": "Configuració de transcodificació de vídeo",
|
"transcoding_settings": "Configuració de transcodificació de vídeo",
|
||||||
"transcoding_settings_description": "Gestiona la resolució i codificació dels fitxers de vídeo",
|
"transcoding_settings_description": "Gestiona la resolució i codificació dels fitxers de vídeo",
|
||||||
"transcoding_target_resolution": "",
|
"transcoding_target_resolution": "Resolució objectiu",
|
||||||
"transcoding_target_resolution_description": "",
|
"transcoding_target_resolution_description": "Les resolucions més altes poden conservar més detalls, però triguen més temps a codificar-se, tenen mides de fitxer més grans i poden reduir la capacitat de resposta de l'aplicació.",
|
||||||
"transcoding_temporal_aq": "",
|
"transcoding_temporal_aq": "AQ temporal",
|
||||||
"transcoding_temporal_aq_description": "",
|
"transcoding_temporal_aq_description": "S'aplica només a NVENC. Augmenta la qualitat de les escenes de baix moviment i alt detall. És possible que no sigui compatible amb dispositius antics.",
|
||||||
"transcoding_threads": "Fils",
|
"transcoding_threads": "Fils",
|
||||||
"transcoding_threads_description": "",
|
"transcoding_threads_description": "Els valors més alts condueixen a una codificació més ràpida, però deixen menys espai perquè el servidor processi altres tasques mentre està actiu. Aquest valor no hauria de ser superior al nombre de nuclis de CPU. Maximitza la utilització si s'estableix a 0.",
|
||||||
"transcoding_tone_mapping": "",
|
"transcoding_tone_mapping": "Mapeig de to",
|
||||||
"transcoding_tone_mapping_description": "",
|
"transcoding_tone_mapping_description": "Intenta preservar l'aspecte dels vídeos HDR quan es converteixen a SDR. Cada algorisme fa diferents compensacions pel color, el detall i la brillantor. Hable conserva els detalls, Mobius conserva el color i Reinhard conserva la brillantor.",
|
||||||
"transcoding_tone_mapping_npl": "",
|
"transcoding_tone_mapping_npl": "NPL de mapatge de to",
|
||||||
"transcoding_tone_mapping_npl_description": "",
|
"transcoding_tone_mapping_npl_description": "Els colors s'ajustaran perquè semblin normals per a exposicions amb aquesta brillantor. Contra intuïtivament, els valors més baixos augmenten la brillantor del vídeo i viceversa, ja que compensa la brillantor de la pantalla. 0 estableix aquest valor automàticament.",
|
||||||
"transcoding_transcode_policy": "",
|
"transcoding_transcode_policy": "Política de transcodificació",
|
||||||
"transcoding_two_pass_encoding": "",
|
"transcoding_transcode_policy_description": "Política sobre quan s'ha de transcodificar un vídeo. Els vídeos HDR sempre es transcodificaran (excepte si la transcodificació està desactivada).",
|
||||||
"transcoding_two_pass_encoding_setting_description": "",
|
"transcoding_two_pass_encoding": "Codificació de dues passades",
|
||||||
|
"transcoding_two_pass_encoding_setting_description": "Transcodifica en dos passos per produir vídeos millor codificats. Quan la taxa de bits màxima està habilitada (necessari perquè funcioni amb H.264 i HEVC), aquest mode utilitza un interval de velocitat de bits basat en la taxa de bits màxima i ignora CRF. Per a VP9, es pot utilitzar CRF si la taxa de bits màxima està desactivada.",
|
||||||
"transcoding_video_codec": "Còdec de video",
|
"transcoding_video_codec": "Còdec de video",
|
||||||
"transcoding_video_codec_description": "",
|
"transcoding_video_codec_description": "VP9 té una alta eficiència i compatibilitat web, però triga més a transcodificar-se. HEVC funciona de manera similar, però té una compatibilitat web inferior. H.264 és àmpliament compatible i de transcodificació ràpida, però produeix fitxers molt més grans. AV1 és el còdec més eficient, però no té suport en dispositius antics.",
|
||||||
"trash_enabled_description": "",
|
"trash_enabled_description": "Activa les funcions de la paperera",
|
||||||
"trash_number_of_days": "Nombre de dies",
|
"trash_number_of_days": "Nombre de dies",
|
||||||
"trash_number_of_days_description": "",
|
"trash_number_of_days_description": "Nombre de dies per mantenir els recursos a la paperera abans de suprimir-los permanentment",
|
||||||
"trash_settings": "Configuració de la paperera",
|
"trash_settings": "Configuració de la paperera",
|
||||||
"trash_settings_description": "Gestiona la configuració de la paperera",
|
"trash_settings_description": "Gestiona la configuració de la paperera",
|
||||||
"user_delete_delay_settings": "",
|
"untracked_files": "Fitxers sense seguiment",
|
||||||
"user_delete_delay_settings_description": "",
|
"untracked_files_description": "L'aplicació no fa un seguiment d'aquests fitxers. Poden ser el resultat de moviments fallits, càrregues interrompudes o deixades enrere a causa d'un error",
|
||||||
|
"user_delete_delay": "El compte i els recursos de <b>{user}</b> es programaran per a la supressió permanent en {delay, plural, one {# day} other {# days}}.",
|
||||||
|
"user_delete_delay_settings": "Retard de la supressió",
|
||||||
|
"user_delete_delay_settings_description": "Nombre de dies després de la supressió per eliminar permanentment el compte i els elements d'un usuari. El treball de supressió d'usuaris s'executa a mitjanit per comprovar si hi ha usuaris preparats per eliminar. Els canvis en aquesta configuració s'avaluaran en la propera execució.",
|
||||||
|
"user_delete_immediately": "El compte i els recursos de <b>{user}</b> es posaran a la cua per suprimir-los permanentment <b>immediatament</b>.",
|
||||||
|
"user_delete_immediately_checkbox": "Posa en cua l'usuari i els recursos per suprimir-los immediatament",
|
||||||
"user_management": "Gestió d'usuaris",
|
"user_management": "Gestió d'usuaris",
|
||||||
"user_password_has_been_reset": "La contrasenya de l'usuari ha estat restablida:",
|
"user_password_has_been_reset": "La contrasenya de l'usuari ha estat restablida:",
|
||||||
|
"user_password_reset_description": "Si us plau, proporcioneu la contrasenya temporal a l'usuari i informeu-los que haurà de canviar la contrasenya en el proper inici de sessió.",
|
||||||
"user_restore_description": "Es restaurarà el compte <b>{user}</b> .",
|
"user_restore_description": "Es restaurarà el compte <b>{user}</b> .",
|
||||||
|
"user_restore_scheduled_removal": "Restaura l'usuari - eliminació programada el {date, date, long}",
|
||||||
"user_settings": "Configuració d'usuaris",
|
"user_settings": "Configuració d'usuaris",
|
||||||
"user_settings_description": "Gestiona la configuració dels usuaris",
|
"user_settings_description": "Gestiona la configuració dels usuaris",
|
||||||
"user_successfully_removed": "L'usuari {email} s'ha eliminat correctament.",
|
"user_successfully_removed": "L'usuari {email} s'ha eliminat correctament.",
|
||||||
@@ -327,10 +344,12 @@
|
|||||||
"album_options": "Opcions de l'àlbum",
|
"album_options": "Opcions de l'àlbum",
|
||||||
"album_remove_user": "Eliminar l'usuari?",
|
"album_remove_user": "Eliminar l'usuari?",
|
||||||
"album_remove_user_confirmation": "Esteu segurs que voleu eliminar {user}?",
|
"album_remove_user_confirmation": "Esteu segurs que voleu eliminar {user}?",
|
||||||
|
"album_share_no_users": "Sembla que has compartit aquest àlbum amb tots els usuaris o no tens cap usuari amb qui compartir-ho.",
|
||||||
"album_updated": "Àlbum actualitzat",
|
"album_updated": "Àlbum actualitzat",
|
||||||
"album_updated_setting_description": "",
|
"album_updated_setting_description": "Rep una notificació per correu electrònic quan un àlbum compartit tingui recursos nous",
|
||||||
"album_user_left": "Surt de {album}",
|
"album_user_left": "Surt de {album}",
|
||||||
"album_user_removed": "{user} eliminat",
|
"album_user_removed": "{user} eliminat",
|
||||||
|
"album_with_link_access": "Permet que qualsevol persona que tingui l'enllaç vegi fotos i persones d'aquest àlbum.",
|
||||||
"albums": "Àlbums",
|
"albums": "Àlbums",
|
||||||
"albums_count": "{count, plural, one {{count, number} àlbum} other {{count, number} àlbums}}",
|
"albums_count": "{count, plural, one {{count, number} àlbum} other {{count, number} àlbums}}",
|
||||||
"all": "Tots",
|
"all": "Tots",
|
||||||
@@ -339,7 +358,11 @@
|
|||||||
"all_videos": "Tots els vídeos",
|
"all_videos": "Tots els vídeos",
|
||||||
"allow_dark_mode": "Permet el tema fosc",
|
"allow_dark_mode": "Permet el tema fosc",
|
||||||
"allow_edits": "Permet editar",
|
"allow_edits": "Permet editar",
|
||||||
|
"allow_public_user_to_download": "Permet que l'usuari públic pugui descarregar",
|
||||||
|
"allow_public_user_to_upload": "Permet que l'usuari públic pugui carregar",
|
||||||
"api_key": "Clau API",
|
"api_key": "Clau API",
|
||||||
|
"api_key_description": "Aquest valor només es mostrarà una vegada. Assegureu-vos de copiar-lo abans de tancar la finestra.",
|
||||||
|
"api_key_empty": "El nom de la clau de l'API no pot estar buit",
|
||||||
"api_keys": "Claus API",
|
"api_keys": "Claus API",
|
||||||
"app_settings": "Configuració de l'app",
|
"app_settings": "Configuració de l'app",
|
||||||
"appears_in": "Apareix a",
|
"appears_in": "Apareix a",
|
||||||
@@ -348,17 +371,23 @@
|
|||||||
"archive_size": "Mida de l'arxiu",
|
"archive_size": "Mida de l'arxiu",
|
||||||
"archive_size_description": "Configureu la mida de l'arxiu de les descàrregues (en GiB)",
|
"archive_size_description": "Configureu la mida de l'arxiu de les descàrregues (en GiB)",
|
||||||
"archived": "Arxivat",
|
"archived": "Arxivat",
|
||||||
|
"archived_count": "",
|
||||||
"are_these_the_same_person": "Són la mateixa persona?",
|
"are_these_the_same_person": "Són la mateixa persona?",
|
||||||
"are_you_sure_to_do_this": "Esteu segurs que voleu fer-ho?",
|
"are_you_sure_to_do_this": "Esteu segurs que voleu fer-ho?",
|
||||||
"asset_added_to_album": "Afegit a l'àlbum",
|
"asset_added_to_album": "Afegit a l'àlbum",
|
||||||
"asset_adding_to_album": "Afegint a l'àlbum...",
|
"asset_adding_to_album": "Afegint a l'àlbum...",
|
||||||
|
"asset_description_updated": "La descripció del recurs s'ha actualitzat",
|
||||||
"asset_filename_is_offline": "L'element {filename} està fora de línia",
|
"asset_filename_is_offline": "L'element {filename} està fora de línia",
|
||||||
"asset_has_unassigned_faces": "L'element té cares no assignades",
|
"asset_has_unassigned_faces": "L'element té cares no assignades",
|
||||||
"asset_offline": "Element fora de línia",
|
"asset_offline": "Element fora de línia",
|
||||||
"asset_offline_description": "Aquest element està fora de línia. L'Immich no pot accedir a la seva ubicació. Si us plau, assegureu-vos que l'actiu està disponible i després torneu la llibreria.",
|
"asset_offline_description": "Aquest element està fora de línia. L'Immich no pot accedir a la seva ubicació. Si us plau, assegureu-vos que l'actiu està disponible i després torneu la llibreria.",
|
||||||
|
"asset_skipped": "Saltat",
|
||||||
|
"asset_uploaded": "Carregat",
|
||||||
|
"asset_uploading": "S'està carregant...",
|
||||||
"assets": "Elements",
|
"assets": "Elements",
|
||||||
"assets_added_count": "{count, plural, one {Afegit un element} other {Afegits # elements}}",
|
"assets_added_count": "{count, plural, one {Afegit un element} other {Afegits # elements}}",
|
||||||
"assets_added_to_album_count": "{count, plural, one {Afegit un element} other {Afegits # elements}} a l'àlbum",
|
"assets_added_to_album_count": "{count, plural, one {Afegit un element} other {Afegits # elements}} a l'àlbum",
|
||||||
|
"assets_added_to_name_count": "S'ha afegit {count, plural, one {# asset} other {# assets}} a {hasName, select, true {<b>{name}</b>} other {new album}}",
|
||||||
"assets_count": "{count, plural, one {Un element} other {# elements}}",
|
"assets_count": "{count, plural, one {Un element} other {# elements}}",
|
||||||
"assets_moved_to_trash_count": "{count, plural, one {Un element mogut} other {# elements moguts}} a la paperera",
|
"assets_moved_to_trash_count": "{count, plural, one {Un element mogut} other {# elements moguts}} a la paperera",
|
||||||
"assets_permanently_deleted_count": "{count, plural, one {Un element esborrat} other {# elements esborrats}} permanentment",
|
"assets_permanently_deleted_count": "{count, plural, one {Un element esborrat} other {# elements esborrats}} permanentment",
|
||||||
@@ -369,11 +398,17 @@
|
|||||||
"assets_were_part_of_album_count": "{count, plural, one {L'element ja és} other {Els elements ja són}} part de l'àlbum",
|
"assets_were_part_of_album_count": "{count, plural, one {L'element ja és} other {Els elements ja són}} part de l'àlbum",
|
||||||
"authorized_devices": "Dispositius autoritzats",
|
"authorized_devices": "Dispositius autoritzats",
|
||||||
"back": "Enrere",
|
"back": "Enrere",
|
||||||
|
"back_close_deselect": "Tornar, tancar o anul·lar la selecció",
|
||||||
"backward": "Enrere",
|
"backward": "Enrere",
|
||||||
"birthdate_saved": "Data de naixement guardada amb èxit",
|
"birthdate_saved": "Data de naixement guardada amb èxit",
|
||||||
"birthdate_set_description": "La data de naixement s'utilitza per calcular l'edat d'aquesta persona en el moment d'una foto.",
|
"birthdate_set_description": "La data de naixement s'utilitza per calcular l'edat d'aquesta persona en el moment d'una foto.",
|
||||||
"blurred_background": "Fons difuminat",
|
"blurred_background": "Fons difuminat",
|
||||||
"buy": "Comprar llicència",
|
"build": "Construeix",
|
||||||
|
"build_image": "Construeix la imatge",
|
||||||
|
"bulk_delete_duplicates_confirmation": "Esteu segur que voleu suprimir de manera massiva {count, plural, one {# duplicate asset} other {# duplicate asset}}? Això mantindrà el recurs més gran de cada grup i esborrarà permanentment tots els altres duplicats. No podeu desfer aquesta acció!",
|
||||||
|
"bulk_keep_duplicates_confirmation": "Esteu segur que voleu mantenir {count, plural, one {# duplicate asset} other {# duplicate asset}}? Això resoldrà tots els grups duplicats sense eliminar res.",
|
||||||
|
"bulk_trash_duplicates_confirmation": "Esteu segur que voleu enviar a les escombraries {count, plural, one {# duplicate asset} other {# duplicate asset}}? Això mantindrà el recurs més gran de cada grup i eliminarà la resta de duplicats.",
|
||||||
|
"buy": "Comprar Immich",
|
||||||
"camera": "Càmera",
|
"camera": "Càmera",
|
||||||
"camera_brand": "Marca de la càmera",
|
"camera_brand": "Marca de la càmera",
|
||||||
"camera_model": "Model de càmera",
|
"camera_model": "Model de càmera",
|
||||||
@@ -392,12 +427,16 @@
|
|||||||
"change_name": "Canvia el nom",
|
"change_name": "Canvia el nom",
|
||||||
"change_name_successfully": "Nom canviat amb èxit",
|
"change_name_successfully": "Nom canviat amb èxit",
|
||||||
"change_password": "Canvia la contrasenya",
|
"change_password": "Canvia la contrasenya",
|
||||||
|
"change_password_description": "Aquesta és la primera vegada que inicieu la sessió al sistema o s'ha fet una sol·licitud per canviar la contrasenya. Introduïu la nova contrasenya a continuació.",
|
||||||
"change_your_password": "Canvia la teva contrasenya",
|
"change_your_password": "Canvia la teva contrasenya",
|
||||||
"changed_visibility_successfully": "Visibilitat canviada amb èxit",
|
"changed_visibility_successfully": "Visibilitat canviada amb èxit",
|
||||||
"check_logs": "",
|
"check_all": "Marqueu-ho tot",
|
||||||
|
"check_logs": "Comprovar els registres",
|
||||||
|
"choose_matching_people_to_merge": "Trieu les persones que coincideixin per combinar-les",
|
||||||
"city": "Ciutat",
|
"city": "Ciutat",
|
||||||
"clear": "Neteja",
|
"clear": "Neteja",
|
||||||
"clear_all": "Neteja-ho tot",
|
"clear_all": "Neteja-ho tot",
|
||||||
|
"clear_all_recent_searches": "Esborra totes les cerques recents",
|
||||||
"clear_message": "Neteja el missatge",
|
"clear_message": "Neteja el missatge",
|
||||||
"clear_value": "Neteja el valor",
|
"clear_value": "Neteja el valor",
|
||||||
"close": "Tanca",
|
"close": "Tanca",
|
||||||
@@ -411,13 +450,13 @@
|
|||||||
"confirm_admin_password": "Confirmeu la contrasenya d'administrador",
|
"confirm_admin_password": "Confirmeu la contrasenya d'administrador",
|
||||||
"confirm_delete_shared_link": "Esteu segurs que voleu eliminar aquest enllaç compartit?",
|
"confirm_delete_shared_link": "Esteu segurs que voleu eliminar aquest enllaç compartit?",
|
||||||
"confirm_password": "Confirmació de contrasenya",
|
"confirm_password": "Confirmació de contrasenya",
|
||||||
"contain": "",
|
"contain": "Contingut",
|
||||||
"context": "",
|
"context": "Context",
|
||||||
"continue": "",
|
"continue": "Continuar",
|
||||||
"copied_image_to_clipboard": "Imatge copiada a porta-retalls.",
|
"copied_image_to_clipboard": "Imatge copiada a porta-retalls.",
|
||||||
"copied_to_clipboard": "Copiada a porta-retalls!",
|
"copied_to_clipboard": "Copiada a porta-retalls!",
|
||||||
"copy_error": "Error de còpia",
|
"copy_error": "Error de còpia",
|
||||||
"copy_file_path": "",
|
"copy_file_path": "Copia la ruta del fitxer",
|
||||||
"copy_image": "Còpia imatge",
|
"copy_image": "Còpia imatge",
|
||||||
"copy_link": "Còpia l'enllaç",
|
"copy_link": "Còpia l'enllaç",
|
||||||
"copy_link_to_clipboard": "Còpia l'enllaç al porta-retalls",
|
"copy_link_to_clipboard": "Còpia l'enllaç al porta-retalls",
|
||||||
@@ -438,7 +477,7 @@
|
|||||||
"create_user": "Crea un usuari",
|
"create_user": "Crea un usuari",
|
||||||
"created": "Creat",
|
"created": "Creat",
|
||||||
"current_device": "Dispositiu actual",
|
"current_device": "Dispositiu actual",
|
||||||
"custom_locale": "",
|
"custom_locale": "Localització personalitzada",
|
||||||
"custom_locale_description": "Format de dates i números segons la llengua i regió",
|
"custom_locale_description": "Format de dates i números segons la llengua i regió",
|
||||||
"dark": "Fosc",
|
"dark": "Fosc",
|
||||||
"date_after": "Data posterior a",
|
"date_after": "Data posterior a",
|
||||||
@@ -447,13 +486,14 @@
|
|||||||
"date_of_birth_saved": "Data de naixement guardada amb èxit",
|
"date_of_birth_saved": "Data de naixement guardada amb èxit",
|
||||||
"date_range": "Interval de dates",
|
"date_range": "Interval de dates",
|
||||||
"day": "Dia",
|
"day": "Dia",
|
||||||
"default_locale": "",
|
"deduplicate_all": "Desduplica-ho tot",
|
||||||
|
"default_locale": "Localització predeterminada",
|
||||||
"default_locale_description": "Format de dates i números segons la configuració del navegador",
|
"default_locale_description": "Format de dates i números segons la configuració del navegador",
|
||||||
"delete": "Esborra",
|
"delete": "Esborra",
|
||||||
"delete_album": "Esborra l'àlbum",
|
"delete_album": "Esborra l'àlbum",
|
||||||
"delete_api_key_prompt": "Esteu segurs que voleu eliminar aquesta clau API?",
|
"delete_api_key_prompt": "Esteu segurs que voleu eliminar aquesta clau API?",
|
||||||
"delete_duplicates_confirmation": "Esteu segurs que voleu eliminar aquests duplicats permanentment?",
|
"delete_duplicates_confirmation": "Esteu segurs que voleu eliminar aquests duplicats permanentment?",
|
||||||
"delete_key": "",
|
"delete_key": "Suprimeix la clau",
|
||||||
"delete_library": "Suprimeix la llibreria",
|
"delete_library": "Suprimeix la llibreria",
|
||||||
"delete_link": "Esborra l'enllaç",
|
"delete_link": "Esborra l'enllaç",
|
||||||
"delete_shared_link": "Odstranit sdílený odkaz",
|
"delete_shared_link": "Odstranit sdílený odkaz",
|
||||||
@@ -463,20 +503,23 @@
|
|||||||
"details": "Detalls",
|
"details": "Detalls",
|
||||||
"direction": "Direcció",
|
"direction": "Direcció",
|
||||||
"disabled": "Desactivat",
|
"disabled": "Desactivat",
|
||||||
"disallow_edits": "",
|
"disallow_edits": "No permetre les edicions",
|
||||||
"discover": "Descobreix",
|
"discover": "Descobreix",
|
||||||
"dismiss_all_errors": "Descarta tots els errors",
|
"dismiss_all_errors": "Descarta tots els errors",
|
||||||
"dismiss_error": "Descarta l'error",
|
"dismiss_error": "Descarta l'error",
|
||||||
"display_options": "Opcions de visualització",
|
"display_options": "Opcions de visualització",
|
||||||
"display_order": "Ordre de visualització",
|
"display_order": "Ordre de visualització",
|
||||||
"display_original_photos": "Mostra les fotografies originals",
|
"display_original_photos": "Mostra les fotografies originals",
|
||||||
"display_original_photos_setting_description": "",
|
"display_original_photos_setting_description": "Preferiu mostrar la foto original quan visualitzeu un recurs en lloc de miniatures quan el recurs original és compatible amb el web. Això pot provocar una velocitat de visualització de fotos més lenta.",
|
||||||
|
"do_not_show_again": "No tornis a mostrar aquest missatge",
|
||||||
"done": "Fet",
|
"done": "Fet",
|
||||||
"download": "Baixar",
|
"download": "Baixar",
|
||||||
"download_settings": "Baixar",
|
"download_settings": "Baixar",
|
||||||
|
"download_settings_description": "Gestioneu la configuració relacionada amb la descàrrega de recursos",
|
||||||
"downloading": "Baixant",
|
"downloading": "Baixant",
|
||||||
"downloading_asset_filename": "Descarregant l'element {filename}",
|
"downloading_asset_filename": "Descarregant l'element {filename}",
|
||||||
"duplicates": "Duplicats",
|
"duplicates": "Duplicats",
|
||||||
|
"duplicates_description": "Resol cada grup indicant quins, si n'hi ha, són duplicats",
|
||||||
"duration": "Duració",
|
"duration": "Duració",
|
||||||
"durations": {
|
"durations": {
|
||||||
"days": "",
|
"days": "",
|
||||||
@@ -507,6 +550,7 @@
|
|||||||
"empty": "",
|
"empty": "",
|
||||||
"empty_album": "",
|
"empty_album": "",
|
||||||
"empty_trash": "Buidar la paperera",
|
"empty_trash": "Buidar la paperera",
|
||||||
|
"empty_trash_confirmation": "Esteu segur que voleu buidar la paperera? Això eliminarà tots els recursos a la paperera permanentment d'Immich.\nNo podeu desfer aquesta acció!",
|
||||||
"enable": "Activar",
|
"enable": "Activar",
|
||||||
"enabled": "Activat",
|
"enabled": "Activat",
|
||||||
"end_date": "Data final",
|
"end_date": "Data final",
|
||||||
@@ -518,28 +562,37 @@
|
|||||||
"cannot_navigate_previous_asset": "No es pot navegar a l'element anterior",
|
"cannot_navigate_previous_asset": "No es pot navegar a l'element anterior",
|
||||||
"cant_apply_changes": "No es poden aplicar els canvis",
|
"cant_apply_changes": "No es poden aplicar els canvis",
|
||||||
"cant_change_activity": "No es pot {enabled, select, true {desactivar} other {activar}} aquesta activitat",
|
"cant_change_activity": "No es pot {enabled, select, true {desactivar} other {activar}} aquesta activitat",
|
||||||
|
"cant_change_asset_favorite": "No es pot canviar el favorit per a aquest recurs",
|
||||||
"cant_change_metadata_assets_count": "No es poden canviar les metadades {count, plural, one {de l'element} other {dels # elements}}",
|
"cant_change_metadata_assets_count": "No es poden canviar les metadades {count, plural, one {de l'element} other {dels # elements}}",
|
||||||
"cant_get_faces": "No es poden obtenir les cares",
|
"cant_get_faces": "No es poden obtenir les cares",
|
||||||
"cant_get_number_of_comments": "No es pot obtenir el nombre de comentaris",
|
"cant_get_number_of_comments": "No es pot obtenir el nombre de comentaris",
|
||||||
"cant_search_people": "No es poden cercar persones",
|
"cant_search_people": "No es poden cercar persones",
|
||||||
"cant_search_places": "No es poden cercar llocs",
|
"cant_search_places": "No es poden cercar llocs",
|
||||||
|
"cleared_jobs": "Tasques buides per a: {job}",
|
||||||
"error_adding_assets_to_album": "Error afegint elements a l'àlbum",
|
"error_adding_assets_to_album": "Error afegint elements a l'àlbum",
|
||||||
"error_adding_users_to_album": "Error afegint usuaris a l'àlbum",
|
"error_adding_users_to_album": "Error afegint usuaris a l'àlbum",
|
||||||
|
"error_deleting_shared_user": "S'ha produït un error en suprimir l'usuari compartit",
|
||||||
"error_downloading": "Error descarregant {filename}",
|
"error_downloading": "Error descarregant {filename}",
|
||||||
|
"error_hiding_buy_button": "S'ha produït un error en amagar el botó de compra",
|
||||||
"error_removing_assets_from_album": "Error eliminant els elements de l'àlbum, consulteu la consola per obtenir més detalls",
|
"error_removing_assets_from_album": "Error eliminant els elements de l'àlbum, consulteu la consola per obtenir més detalls",
|
||||||
"error_selecting_all_assets": "Error seleccionant tots els elements",
|
"error_selecting_all_assets": "Error seleccionant tots els elements",
|
||||||
"exclusion_pattern_already_exists": "Aquest patró d’exclusió ja existeix.",
|
"exclusion_pattern_already_exists": "Aquest patró d’exclusió ja existeix.",
|
||||||
|
"failed_job_command": "L'ordre {command} ha fallat per a la tasca: {job}",
|
||||||
"failed_to_create_album": "No s'ha pogut crear l'àlbum",
|
"failed_to_create_album": "No s'ha pogut crear l'àlbum",
|
||||||
"failed_to_create_shared_link": "No s'ha pogut crear l'enllaç compartit",
|
"failed_to_create_shared_link": "No s'ha pogut crear l'enllaç compartit",
|
||||||
"failed_to_edit_shared_link": "No s'ha pogut editar l'enllaç compartit",
|
"failed_to_edit_shared_link": "No s'ha pogut editar l'enllaç compartit",
|
||||||
"failed_to_load_asset": "No s'ha pogut carregar l'element",
|
"failed_to_load_asset": "No s'ha pogut carregar l'element",
|
||||||
"failed_to_load_assets": "No s'han pogut carregar els elements",
|
"failed_to_load_assets": "No s'han pogut carregar els elements",
|
||||||
|
"failed_to_load_people": "No s'han pogut carregar les persones",
|
||||||
|
"failed_to_remove_product_key": "No s'ha pogut eliminar la clau del producte",
|
||||||
"failed_to_stack_assets": "No s'han pogut apilar els elements",
|
"failed_to_stack_assets": "No s'han pogut apilar els elements",
|
||||||
"failed_to_unstack_assets": "No s'han pogut desapilar els elements",
|
"failed_to_unstack_assets": "No s'han pogut desapilar els elements",
|
||||||
"import_path_already_exists": "Aquest camí d'importació ja existeix.",
|
"import_path_already_exists": "Aquest camí d'importació ja existeix.",
|
||||||
"incorrect_email_or_password": "Correu electrònic o contrasenya incorrectes",
|
"incorrect_email_or_password": "Correu electrònic o contrasenya incorrectes",
|
||||||
|
"paths_validation_failed": "{paths, plural, one {# path} other {# paths}} no ha pogut validar",
|
||||||
"profile_picture_transparent_pixels": "Les fotos de perfil no poden tenir píxels transparents. Per favor, feu zoom in, mogueu la imatge o ambdues.",
|
"profile_picture_transparent_pixels": "Les fotos de perfil no poden tenir píxels transparents. Per favor, feu zoom in, mogueu la imatge o ambdues.",
|
||||||
"quota_higher_than_disk_size": "Heu establert una quota més gran que la mida de disc",
|
"quota_higher_than_disk_size": "Heu establert una quota més gran que la mida de disc",
|
||||||
|
"repair_unable_to_check_items": "No es pot comprovar {count, select, one {item} other {items}}",
|
||||||
"unable_to_add_album_users": "No es poden afegir usuaris a l'àlbum",
|
"unable_to_add_album_users": "No es poden afegir usuaris a l'àlbum",
|
||||||
"unable_to_add_assets_to_shared_link": "No s'han pogut afegir els elements a l'enllaç compartit",
|
"unable_to_add_assets_to_shared_link": "No s'han pogut afegir els elements a l'enllaç compartit",
|
||||||
"unable_to_add_comment": "No es pot afegir el comentari",
|
"unable_to_add_comment": "No es pot afegir el comentari",
|
||||||
@@ -548,131 +601,186 @@
|
|||||||
"unable_to_add_partners": "No es poden afegir companys",
|
"unable_to_add_partners": "No es poden afegir companys",
|
||||||
"unable_to_add_remove_archive": "No s'ha pogut {archived, select, true {eliminar l'element de} other {afegir l'element a}} l'arxiu",
|
"unable_to_add_remove_archive": "No s'ha pogut {archived, select, true {eliminar l'element de} other {afegir l'element a}} l'arxiu",
|
||||||
"unable_to_add_remove_favorites": "No s'ha pogut {favorite, select, true {afegir l'element als} other {eliminar l'element dels}} preferits",
|
"unable_to_add_remove_favorites": "No s'ha pogut {favorite, select, true {afegir l'element als} other {eliminar l'element dels}} preferits",
|
||||||
"unable_to_change_album_user_role": "",
|
"unable_to_archive_unarchive": "No es pot {archived, select, true {archive} other {unarchive}}",
|
||||||
|
"unable_to_change_album_user_role": "No es pot canviar el rol d'usuari de l'àlbum",
|
||||||
"unable_to_change_date": "No es pot canviar la data",
|
"unable_to_change_date": "No es pot canviar la data",
|
||||||
|
"unable_to_change_favorite": "No es pot canviar el favorit per a aquest recurs",
|
||||||
"unable_to_change_location": "No es pot canviar la ubicació",
|
"unable_to_change_location": "No es pot canviar la ubicació",
|
||||||
|
"unable_to_change_password": "No es pot canviar la contrasenya",
|
||||||
|
"unable_to_change_visibility": "No es pot canviar la visibilitat de {count, plural, one {# person} other {# people}}",
|
||||||
"unable_to_check_item": "",
|
"unable_to_check_item": "",
|
||||||
"unable_to_check_items": "",
|
"unable_to_check_items": "",
|
||||||
|
"unable_to_complete_oauth_login": "No es pot completar l'inici de sessió OAuth",
|
||||||
|
"unable_to_connect": "No pot connectar",
|
||||||
"unable_to_connect_to_server": "No es pot connectar al servidor",
|
"unable_to_connect_to_server": "No es pot connectar al servidor",
|
||||||
"unable_to_create_admin_account": "",
|
"unable_to_copy_to_clipboard": "No es pot copiar al porta-retalls, assegureu-vos que esteu accedint a la pàgina mitjançant https",
|
||||||
|
"unable_to_create_admin_account": "No es pot crear un compte d'administrador",
|
||||||
|
"unable_to_create_api_key": "No es pot crear una clau d'API nova",
|
||||||
"unable_to_create_library": "No es pot crear la llibreria",
|
"unable_to_create_library": "No es pot crear la llibreria",
|
||||||
"unable_to_create_user": "No es pot crear l'usuari",
|
"unable_to_create_user": "No es pot crear l'usuari",
|
||||||
"unable_to_delete_album": "No es pot eliminar l'àlbum",
|
"unable_to_delete_album": "No es pot eliminar l'àlbum",
|
||||||
"unable_to_delete_asset": "",
|
"unable_to_delete_asset": "No es pot suprimir el recurs",
|
||||||
|
"unable_to_delete_assets": "S'ha produït un error en suprimir recursos",
|
||||||
|
"unable_to_delete_exclusion_pattern": "No es pot suprimir el patró d'exclusió",
|
||||||
|
"unable_to_delete_import_path": "No es pot suprimir la ruta d'importació",
|
||||||
|
"unable_to_delete_shared_link": "No es pot suprimir l'enllaç compartit",
|
||||||
"unable_to_delete_user": "No es pot eliminar l'usuari",
|
"unable_to_delete_user": "No es pot eliminar l'usuari",
|
||||||
|
"unable_to_download_files": "No es poden descarregar fitxers",
|
||||||
|
"unable_to_edit_exclusion_pattern": "No es pot editar el patró d'exclusió",
|
||||||
|
"unable_to_edit_import_path": "No es pot editar la ruta d'importació",
|
||||||
"unable_to_empty_trash": "No es pot buidar la paperera",
|
"unable_to_empty_trash": "No es pot buidar la paperera",
|
||||||
"unable_to_enter_fullscreen": "No es pot entrar a la pantalla completa",
|
"unable_to_enter_fullscreen": "No es pot entrar a la pantalla completa",
|
||||||
"unable_to_exit_fullscreen": "No es pot sortir de la pantalla completa",
|
"unable_to_exit_fullscreen": "No es pot sortir de la pantalla completa",
|
||||||
|
"unable_to_get_comments_number": "No es pot obtenir el nombre de comentaris",
|
||||||
|
"unable_to_get_shared_link": "No s'ha pogut obtenir l'enllaç compartit",
|
||||||
"unable_to_hide_person": "No es pot amagar la persona",
|
"unable_to_hide_person": "No es pot amagar la persona",
|
||||||
|
"unable_to_link_oauth_account": "No es pot enllaçar el compte OAuth",
|
||||||
"unable_to_load_album": "No es pot carregar l'àlbum",
|
"unable_to_load_album": "No es pot carregar l'àlbum",
|
||||||
"unable_to_load_asset_activity": "",
|
"unable_to_load_asset_activity": "No es pot carregar l'activitat dels recursos",
|
||||||
"unable_to_load_items": "",
|
"unable_to_load_items": "No es poden carregar els elements",
|
||||||
"unable_to_load_liked_status": "",
|
"unable_to_load_liked_status": "No es pot carregar l'estat de m'agrada",
|
||||||
"unable_to_play_video": "",
|
"unable_to_log_out_all_devices": "No es poden tancar la sessió de tots els dispositius",
|
||||||
"unable_to_refresh_user": "",
|
"unable_to_log_out_device": "No es pot tancar la sessió del dispositiu",
|
||||||
"unable_to_remove_album_users": "",
|
"unable_to_login_with_oauth": "No es pot iniciar sessió amb OAuth",
|
||||||
|
"unable_to_play_video": "No es pot reproduir el vídeo",
|
||||||
|
"unable_to_reassign_assets_existing_person": "No es poden reassignar recursos a {name, select, null {an existing person} other {{name}}}",
|
||||||
|
"unable_to_reassign_assets_new_person": "No es poden reassignar recursos a una persona nova",
|
||||||
|
"unable_to_refresh_user": "No es pot actualitzar l'usuari",
|
||||||
|
"unable_to_remove_album_users": "No es poden eliminar usuaris de l'àlbum",
|
||||||
|
"unable_to_remove_api_key": "No es pot eliminar la clau de l'API",
|
||||||
|
"unable_to_remove_assets_from_shared_link": "No es poden eliminar recursos de l'enllaç compartit",
|
||||||
"unable_to_remove_comment": "",
|
"unable_to_remove_comment": "",
|
||||||
"unable_to_remove_library": "",
|
"unable_to_remove_library": "No es pot eliminar la biblioteca",
|
||||||
|
"unable_to_remove_offline_files": "No es poden eliminar els fitxers fora de línia",
|
||||||
"unable_to_remove_partner": "No es pot eliminar company/a",
|
"unable_to_remove_partner": "No es pot eliminar company/a",
|
||||||
"unable_to_remove_reaction": "",
|
"unable_to_remove_reaction": "No es pot eliminar la reacció",
|
||||||
"unable_to_remove_user": "",
|
"unable_to_remove_user": "",
|
||||||
"unable_to_repair_items": "",
|
"unable_to_repair_items": "No es poden reparar els elements",
|
||||||
"unable_to_reset_password": "",
|
"unable_to_reset_password": "No es pot restablir la contrasenya",
|
||||||
"unable_to_resolve_duplicate": "",
|
"unable_to_resolve_duplicate": "No es pot resoldre el duplicat",
|
||||||
"unable_to_restore_assets": "",
|
"unable_to_restore_assets": "No es poden restaurar els recursos",
|
||||||
"unable_to_restore_trash": "",
|
"unable_to_restore_trash": "No es pot restaurar la paperera",
|
||||||
"unable_to_restore_user": "",
|
"unable_to_restore_user": "No es pot restaurar l'usuari",
|
||||||
"unable_to_save_album": "",
|
"unable_to_save_album": "No es pot desar l'àlbum",
|
||||||
"unable_to_save_name": "",
|
"unable_to_save_api_key": "No es pot desar la clau de l'API",
|
||||||
"unable_to_save_profile": "",
|
"unable_to_save_date_of_birth": "No es pot desar la data de naixement",
|
||||||
"unable_to_save_settings": "",
|
"unable_to_save_name": "No es pot desar el nom",
|
||||||
"unable_to_scan_libraries": "",
|
"unable_to_save_profile": "No es pot desar el perfil",
|
||||||
"unable_to_scan_library": "",
|
"unable_to_save_settings": "No es pot desar la configuració",
|
||||||
"unable_to_set_profile_picture": "",
|
"unable_to_scan_libraries": "No es poden escanejar les biblioteques",
|
||||||
"unable_to_submit_job": "",
|
"unable_to_scan_library": "No es pot escanejar la biblioteca",
|
||||||
"unable_to_trash_asset": "",
|
"unable_to_set_feature_photo": "No s'ha pogut configurar la foto destacada",
|
||||||
"unable_to_unlink_account": "",
|
"unable_to_set_profile_picture": "No es pot configurar la foto de perfil",
|
||||||
|
"unable_to_submit_job": "No es pot enviar la tasca",
|
||||||
|
"unable_to_trash_asset": "No es pot eliminar el recurs a la paperera",
|
||||||
|
"unable_to_unlink_account": "No es pot desenllaçar el compte",
|
||||||
|
"unable_to_update_album_cover": "No es pot actualitzar la portada de l'àlbum",
|
||||||
"unable_to_update_album_info": "No es pot actualitzar la informació de l'àlbum",
|
"unable_to_update_album_info": "No es pot actualitzar la informació de l'àlbum",
|
||||||
"unable_to_update_library": "",
|
"unable_to_update_library": "No es pot actualitzar la biblioteca",
|
||||||
"unable_to_update_location": "",
|
"unable_to_update_location": "No es pot actualitzar la ubicació",
|
||||||
"unable_to_update_settings": "",
|
"unable_to_update_settings": "No es pot actualitzar la configuració",
|
||||||
"unable_to_update_user": ""
|
"unable_to_update_timeline_display_status": "No es pot actualitzar l'estat de visualització de la cronologia",
|
||||||
|
"unable_to_update_user": "No es pot actualitzar l'usuari",
|
||||||
|
"unable_to_upload_file": "No es pot carregar el fitxer"
|
||||||
},
|
},
|
||||||
"every_day_at_onepm": "",
|
"every_day_at_onepm": "",
|
||||||
"every_night_at_midnight": "",
|
"every_night_at_midnight": "",
|
||||||
"every_night_at_twoam": "",
|
"every_night_at_twoam": "",
|
||||||
"every_six_hours": "",
|
"every_six_hours": "",
|
||||||
"exit_slideshow": "",
|
"exit_slideshow": "Surt de la presentació de diapositives",
|
||||||
"expand_all": "",
|
"expand_all": "Ampliar-ho tot",
|
||||||
"expire_after": "Caduca després de",
|
"expire_after": "Caduca després de",
|
||||||
"expired": "Caducat",
|
"expired": "Caducat",
|
||||||
|
"expires_date": "Caduca el {date}",
|
||||||
"explore": "Explorar",
|
"explore": "Explorar",
|
||||||
|
"export": "Exporta",
|
||||||
"export_as_json": "Exportar com a JSON",
|
"export_as_json": "Exportar com a JSON",
|
||||||
"extension": "Extensió",
|
"extension": "Extensió",
|
||||||
|
"external": "Extern",
|
||||||
"external_libraries": "Llibreries externes",
|
"external_libraries": "Llibreries externes",
|
||||||
|
"face_unassigned": "Sense assignar",
|
||||||
"failed_to_get_people": "",
|
"failed_to_get_people": "",
|
||||||
"favorite": "Preferit",
|
"favorite": "Preferit",
|
||||||
"favorite_or_unfavorite_photo": "",
|
"favorite_or_unfavorite_photo": "Foto preferida o no preferida",
|
||||||
"favorites": "Preferits",
|
"favorites": "Preferits",
|
||||||
"feature": "",
|
"feature": "",
|
||||||
"feature_photo_updated": "",
|
"feature_photo_updated": "Foto destacada actualitzada",
|
||||||
"featurecollection": "",
|
"featurecollection": "",
|
||||||
"file_name": "Nom de l'arxiu",
|
"file_name": "Nom de l'arxiu",
|
||||||
"file_name_or_extension": "Nom de l'arxiu o extensió",
|
"file_name_or_extension": "Nom de l'arxiu o extensió",
|
||||||
"filename": "",
|
"filename": "",
|
||||||
"files": "",
|
"files": "",
|
||||||
"filetype": "",
|
"filetype": "",
|
||||||
"filter_people": "",
|
"filter_people": "Filtra persones",
|
||||||
"fix_incorrect_match": "",
|
"find_them_fast": "Trobeu-los ràpidament pel nom amb la cerca",
|
||||||
"force_re-scan_library_files": "",
|
"fix_incorrect_match": "Corregiu la coincidència incorrecta",
|
||||||
"forward": "",
|
"force_re-scan_library_files": "Força a tornar a escanejar tots els fitxers de la biblioteca",
|
||||||
|
"forward": "Endavant",
|
||||||
"general": "General",
|
"general": "General",
|
||||||
"get_help": "",
|
"get_help": "Aconseguir ajuda",
|
||||||
"getting_started": "",
|
"getting_started": "Començant",
|
||||||
"go_back": "",
|
"go_back": "Torna",
|
||||||
"go_to_search": "",
|
"go_to_search": "Vés a cercar",
|
||||||
"go_to_share_page": "",
|
"go_to_share_page": "Vés a la pàgina de compartir",
|
||||||
"group_albums_by": "",
|
"group_albums_by": "Agrupa àlbums per...",
|
||||||
"group_no": "Cap agrupació",
|
"group_no": "Cap agrupació",
|
||||||
"group_owner": "Agrupar per propietari",
|
"group_owner": "Agrupar per propietari",
|
||||||
"group_year": "Agrupar per any",
|
"group_year": "Agrupar per any",
|
||||||
"has_quota": "Quota",
|
"has_quota": "Quota",
|
||||||
"hide_gallery": "",
|
"hi_user": "Hola {name} ({email})",
|
||||||
"hide_password": "",
|
"hide_all_people": "Amaga totes les persones",
|
||||||
"hide_person": "",
|
"hide_gallery": "Amaga la galeria",
|
||||||
|
"hide_named_person": "Amaga la persona {name}",
|
||||||
|
"hide_password": "Amaga la contrasenya",
|
||||||
|
"hide_person": "Amaga la persona",
|
||||||
|
"hide_unnamed_people": "Amaga persones sense nom",
|
||||||
"host": "Amfitrió",
|
"host": "Amfitrió",
|
||||||
"hour": "Hora",
|
"hour": "Hora",
|
||||||
"image": "Imatge",
|
"image": "Imatge",
|
||||||
|
"image_alt_text_date": "{isVideo, select, true {Video} other {Image}} presa el {date}",
|
||||||
|
"image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} pres/a amb {person1} el {date}",
|
||||||
|
"image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} pres/a {person1} amb {person2} el {date}",
|
||||||
|
"image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} pres/a amb {person1}, {person2}, i {person3} el {date}",
|
||||||
|
"image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} pres/a amb {person1}, {person2}, i {additionalCount, number} altres el {date}",
|
||||||
|
"image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} pres/a a {city}, {country} el {date}",
|
||||||
|
"image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} pres/a a {city}, {country} amb {person1} el {date}",
|
||||||
|
"image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} pres/a a {city}, {country} amb {person1} i {person2} el {date}",
|
||||||
|
"image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} pres/a a {city}, {country} amb {person1}, {person2}, i {person3} el {date}",
|
||||||
|
"image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} pres/a a {city}, {country} amb {person1}, {person2}, i {additionalCount, number} altres el {date}",
|
||||||
"img": "",
|
"img": "",
|
||||||
"immich_logo": "",
|
"immich_logo": "",
|
||||||
|
"immich_web_interface": "Interfície web Immich",
|
||||||
"import_from_json": "Importar des de JSON",
|
"import_from_json": "Importar des de JSON",
|
||||||
"import_path": "",
|
"import_path": "Ruta d'importació",
|
||||||
"in_archive": "",
|
"in_albums": "A {count, plural, one {# album} other {# albums}}",
|
||||||
|
"in_archive": "En arxiu",
|
||||||
"include_archived": "Incloure arxivats",
|
"include_archived": "Incloure arxivats",
|
||||||
"include_shared_albums": "",
|
"include_shared_albums": "Inclou àlbums compartits",
|
||||||
"include_shared_partner_assets": "Incloure elements dels companys",
|
"include_shared_partner_assets": "Incloure elements dels companys",
|
||||||
"individual_share": "",
|
"individual_share": "",
|
||||||
"info": "Informació",
|
"info": "Informació",
|
||||||
"interval": {
|
"interval": {
|
||||||
"day_at_onepm": "",
|
"day_at_onepm": "Cada dia a les 13h",
|
||||||
"hours": "",
|
"hours": "Cada {hours, plural, one {hour} other {{hours, number} hours}}",
|
||||||
"night_at_midnight": "",
|
"night_at_midnight": "Cada mitjanit",
|
||||||
"night_at_twoam": ""
|
"night_at_twoam": "Cada nit a les 2 del matí"
|
||||||
},
|
},
|
||||||
"invite_people": "",
|
"invite_people": "Convida gent",
|
||||||
"invite_to_album": "Convida a l'àlbum",
|
"invite_to_album": "Convida a l'àlbum",
|
||||||
"job_settings_description": "",
|
"job_settings_description": "",
|
||||||
"jobs": "Tasques",
|
"jobs": "Tasques",
|
||||||
"keep": "Mantenir",
|
"keep": "Mantenir",
|
||||||
"keyboard_shortcuts": "",
|
"keyboard_shortcuts": "Dreceres de teclat",
|
||||||
"language": "Idioma",
|
"language": "Idioma",
|
||||||
"language_setting_description": "",
|
"language_setting_description": "Seleccioneu el vostre idioma",
|
||||||
"last_seen": "",
|
"last_seen": "Vist per últim cop",
|
||||||
"leave": "",
|
"latest_version": "Última versió",
|
||||||
|
"latitude": "Latitud",
|
||||||
|
"leave": "Marxar",
|
||||||
"let_others_respond": "Deixa que els altres responguin",
|
"let_others_respond": "Deixa que els altres responguin",
|
||||||
"level": "",
|
"level": "Nivell",
|
||||||
"library": "Bibilioteca",
|
"library": "Bibilioteca",
|
||||||
"library_options": "",
|
"library_options": "Opcions de biblioteca",
|
||||||
"license_activated_title": "La vostra llicència ha estat activada amb èxit",
|
"license_activated_title": "La vostra llicència ha estat activada amb èxit",
|
||||||
"license_button_activate": "Activar",
|
"license_button_activate": "Activar",
|
||||||
"license_button_buy": "Comprar",
|
"license_button_buy": "Comprar",
|
||||||
@@ -686,86 +794,114 @@
|
|||||||
"license_server_title": "Llicència de servidor",
|
"license_server_title": "Llicència de servidor",
|
||||||
"license_trial_info_2": "Heu utilitzat l'Immich durant uns",
|
"license_trial_info_2": "Heu utilitzat l'Immich durant uns",
|
||||||
"license_trial_info_3": "{accountAge, plural, one {# dia} other {# dies}}",
|
"license_trial_info_3": "{accountAge, plural, one {# dia} other {# dies}}",
|
||||||
"light": "",
|
"light": "Llum",
|
||||||
"link_options": "",
|
"like_deleted": "M'agrada suprimit",
|
||||||
"link_to_oauth": "",
|
"link_options": "Opcions d'enllaç",
|
||||||
"linked_oauth_account": "",
|
"link_to_oauth": "Enllaç a OAuth",
|
||||||
|
"linked_oauth_account": "Compte OAuth enllaçat",
|
||||||
"list": "Llista",
|
"list": "Llista",
|
||||||
"loading": "Carregant",
|
"loading": "Carregant",
|
||||||
"loading_search_results_failed": "",
|
"loading_search_results_failed": "No s'han pogut carregar els resultats de la cerca",
|
||||||
"log_out": "Tanca la sessió",
|
"log_out": "Tanca la sessió",
|
||||||
"log_out_all_devices": "",
|
"log_out_all_devices": "Tanqueu la sessió de tots els dispositius",
|
||||||
"login_has_been_disabled": "",
|
"logged_out_all_devices": "S'ha tancat la sessió de tots els dispositius",
|
||||||
"look": "",
|
"logged_out_device": "Dispositiu tancat",
|
||||||
"loop_videos": "",
|
"login": "Iniciar sessió",
|
||||||
|
"login_has_been_disabled": "L'inici de sessió s'ha desactivat.",
|
||||||
|
"logout_all_device_confirmation": "Esteu segur que voleu tancar la sessió de tots els dispositius?",
|
||||||
|
"logout_this_device_confirmation": "Esteu segur que voleu tancar la sessió d'aquest dispositiu?",
|
||||||
|
"longitude": "Longitud",
|
||||||
|
"look": "Aspecte",
|
||||||
|
"loop_videos": "Vídeos en bucle",
|
||||||
"loop_videos_description": "Habilita la reproducció en bucle del vídeo en els detalls.",
|
"loop_videos_description": "Habilita la reproducció en bucle del vídeo en els detalls.",
|
||||||
"make": "Fabricant",
|
"make": "Fabricant",
|
||||||
"manage_shared_links": "Spravovat sdílené odkazy",
|
"manage_shared_links": "Spravovat sdílené odkazy",
|
||||||
"manage_sharing_with_partners": "Gestiona la compartició amb els companys",
|
"manage_sharing_with_partners": "Gestiona la compartició amb els companys",
|
||||||
"manage_the_app_settings": "",
|
"manage_the_app_settings": "Gestioneu la configuració de l'aplicació",
|
||||||
"manage_your_account": "",
|
"manage_your_account": "Gestiona el teu compte",
|
||||||
"manage_your_api_keys": "",
|
"manage_your_api_keys": "Gestioneu les vostres claus API",
|
||||||
"manage_your_devices": "",
|
"manage_your_devices": "Gestioneu els vostres dispositius connectats",
|
||||||
"manage_your_oauth_connection": "",
|
"manage_your_oauth_connection": "Gestioneu la vostra connexió OAuth",
|
||||||
"map": "Mapa",
|
"map": "Mapa",
|
||||||
"map_marker_with_image": "",
|
"map_marker_for_images": "Marcador de mapa per a imatges fetes a {city}, {country}",
|
||||||
|
"map_marker_with_image": "Marcador de mapa amb imatge",
|
||||||
"map_settings": "Paràmetres de mapa",
|
"map_settings": "Paràmetres de mapa",
|
||||||
|
"matches": "Coincidències",
|
||||||
"media_type": "Tipus de mitjà",
|
"media_type": "Tipus de mitjà",
|
||||||
"memories": "Records",
|
"memories": "Records",
|
||||||
"memories_setting_description": "",
|
"memories_setting_description": "Gestiona el que veus als teus records",
|
||||||
|
"memory": "Record",
|
||||||
|
"memory_lane_title": "Línia de records {title}",
|
||||||
"menu": "Menú",
|
"menu": "Menú",
|
||||||
"merge": "",
|
"merge": "Combinar",
|
||||||
"merge_people": "",
|
"merge_people": "Combinar persones",
|
||||||
"merge_people_successfully": "",
|
"merge_people_limit": "Només pots combinar fins a 5 cares alhora",
|
||||||
|
"merge_people_prompt": "Vols combinar aquestes persones? Aquesta acció és irreversible.",
|
||||||
|
"merge_people_successfully": "Persones combinades amb èxit",
|
||||||
|
"merged_people_count": "Combinades {count, plural, one {# person} other {# people}}",
|
||||||
"minimize": "Minimitza",
|
"minimize": "Minimitza",
|
||||||
"minute": "Minut",
|
"minute": "Minut",
|
||||||
"missing": "Restants",
|
"missing": "Restants",
|
||||||
"model": "",
|
"model": "Model",
|
||||||
"month": "Mes",
|
"month": "Mes",
|
||||||
"more": "Més",
|
"more": "Més",
|
||||||
"moved_to_trash": "",
|
"moved_to_trash": "S'ha mogut a la paperera",
|
||||||
"my_albums": "",
|
"my_albums": "Els meus àlbums",
|
||||||
"name": "Nom",
|
"name": "Nom",
|
||||||
"name_or_nickname": "",
|
"name_or_nickname": "Nom o sobrenom",
|
||||||
"never": "Mai",
|
"never": "Mai",
|
||||||
"new_api_key": "",
|
"new_album": "Nou Àlbum",
|
||||||
|
"new_api_key": "Nova clau de l'API",
|
||||||
"new_password": "Nova contrasenya",
|
"new_password": "Nova contrasenya",
|
||||||
"new_person": "Persona nova",
|
"new_person": "Persona nova",
|
||||||
"new_user_created": "Nou usuari creat",
|
"new_user_created": "Nou usuari creat",
|
||||||
"newest_first": "",
|
"new_version_available": "NOVA VERSIÓ DISPONIBLE",
|
||||||
|
"newest_first": "El més nou primer",
|
||||||
"next": "Següent",
|
"next": "Següent",
|
||||||
"next_memory": "",
|
"next_memory": "Següent record",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
"no_albums_message": "Creeu un àlbum per organitzar les vostres fotos i vídeos",
|
"no_albums_message": "Creeu un àlbum per organitzar les vostres fotos i vídeos",
|
||||||
|
"no_albums_with_name_yet": "Sembla que encara no tens cap àlbum amb aquest nom.",
|
||||||
|
"no_albums_yet": "Sembla que encara no tens cap àlbum.",
|
||||||
"no_archived_assets_message": "Arxiveu fotos i vídeos per ocultar-los de Fotos",
|
"no_archived_assets_message": "Arxiveu fotos i vídeos per ocultar-los de Fotos",
|
||||||
"no_assets_message": "",
|
"no_assets_message": "FEU CLIC PER PUJAR LA VOSTRA PRIMERA FOTO",
|
||||||
"no_duplicates_found": "No s'han trobat duplicats.",
|
"no_duplicates_found": "No s'han trobat duplicats.",
|
||||||
"no_exif_info_available": "",
|
"no_exif_info_available": "No hi ha informació d'exif disponible",
|
||||||
"no_explore_results_message": "",
|
"no_explore_results_message": "Penja més fotos per explorar la teva col·lecció.",
|
||||||
"no_favorites_message": "Afegiu preferits per trobar les millors fotos i vídeos a l'instant",
|
"no_favorites_message": "Afegiu preferits per trobar les millors fotos i vídeos a l'instant",
|
||||||
"no_libraries_message": "Creeu una llibreria externa per veure les vostres fotos i vídeos",
|
"no_libraries_message": "Creeu una llibreria externa per veure les vostres fotos i vídeos",
|
||||||
"no_name": "",
|
"no_name": "Sense nom",
|
||||||
"no_places": "",
|
"no_places": "No hi ha llocs",
|
||||||
"no_results": "",
|
"no_results": "Sense resultats",
|
||||||
|
"no_results_description": "Proveu un sinònim o una paraula clau més general",
|
||||||
"no_shared_albums_message": "Creeu un àlbum per compartir fotos i vídeos amb persones a la vostra xarxa",
|
"no_shared_albums_message": "Creeu un àlbum per compartir fotos i vídeos amb persones a la vostra xarxa",
|
||||||
"not_in_any_album": "En cap àlbum",
|
"not_in_any_album": "En cap àlbum",
|
||||||
|
"note_apply_storage_label_to_previously_uploaded assets": "Nota: per aplicar l'etiqueta d'emmagatzematge als actius penjats anteriorment, executeu el",
|
||||||
"note_unlimited_quota": "Nota: Intruduïu 0 per a quota il·limitada",
|
"note_unlimited_quota": "Nota: Intruduïu 0 per a quota il·limitada",
|
||||||
"notes": "",
|
"notes": "Notes",
|
||||||
"notification_toggle_setting_description": "",
|
"notification_toggle_setting_description": "Activa les notificacions per correu electrònic",
|
||||||
"notifications": "Notificacions",
|
"notifications": "Notificacions",
|
||||||
"notifications_setting_description": "",
|
"notifications_setting_description": "Gestiona les notificacions",
|
||||||
"oauth": "OAuth",
|
"oauth": "OAuth",
|
||||||
"offline": "Fora de línia",
|
"offline": "Fora de línia",
|
||||||
|
"offline_paths": "Rutes fora de línia",
|
||||||
|
"offline_paths_description": "Aquests resultats poden ser deguts a la supressió manual de fitxers que no formen part d'una biblioteca externa.",
|
||||||
"ok": "D'acord",
|
"ok": "D'acord",
|
||||||
"oldest_first": "",
|
"oldest_first": "El més vell primer",
|
||||||
|
"onboarding_theme_description": "Trieu un tema de color per a la vostra instància. Podeu canviar-ho més endavant a la vostra configuració.",
|
||||||
|
"onboarding_welcome_description": "Configurem la vostra instància amb alguns paràmetres habituals.",
|
||||||
|
"onboarding_welcome_user": "Benvingut, {user}",
|
||||||
"online": "En línia",
|
"online": "En línia",
|
||||||
"only_favorites": "Només preferits",
|
"only_favorites": "Només preferits",
|
||||||
"only_refreshes_modified_files": "",
|
"only_refreshes_modified_files": "Només actualitza els fitxers modificats",
|
||||||
"open_the_search_filters": "",
|
"open_in_openstreetmap": "Obre a OpenStreetMap",
|
||||||
|
"open_the_search_filters": "Obriu els filtres de cerca",
|
||||||
"options": "Opcions",
|
"options": "Opcions",
|
||||||
|
"or": "o",
|
||||||
"organize_your_library": "Organitzeu la llibreria",
|
"organize_your_library": "Organitzeu la llibreria",
|
||||||
"other": "",
|
"original": "original",
|
||||||
"other_devices": "",
|
"other": "Altres",
|
||||||
|
"other_devices": "Altres dispositius",
|
||||||
"other_variables": "Altres variables",
|
"other_variables": "Altres variables",
|
||||||
"owned": "Propi",
|
"owned": "Propi",
|
||||||
"owner": "Propietari",
|
"owner": "Propietari",
|
||||||
@@ -778,16 +914,16 @@
|
|||||||
"password": "Contrasenya",
|
"password": "Contrasenya",
|
||||||
"password_does_not_match": "La contrasenya no coincideix",
|
"password_does_not_match": "La contrasenya no coincideix",
|
||||||
"password_required": "Contrasenya requerida",
|
"password_required": "Contrasenya requerida",
|
||||||
"password_reset_success": "",
|
"password_reset_success": "El restabliment de la contrasenya ha estat correcte",
|
||||||
"past_durations": {
|
"past_durations": {
|
||||||
"days": "{days, plural, one {El dia anterior} other {Els # dies anteriors}}",
|
"days": "{days, plural, one {El dia anterior} other {Els # dies anteriors}}",
|
||||||
"hours": "",
|
"hours": "",
|
||||||
"years": "{years, plural, one {L'any passat} other {Els passats # anys}}"
|
"years": "{years, plural, one {L'any passat} other {Els passats # anys}}"
|
||||||
},
|
},
|
||||||
"path": "",
|
"path": "Ruta",
|
||||||
"pattern": "Patró",
|
"pattern": "Patró",
|
||||||
"pause": "Pausa",
|
"pause": "Pausa",
|
||||||
"pause_memories": "",
|
"pause_memories": "Pausa els records",
|
||||||
"paused": "En pausa",
|
"paused": "En pausa",
|
||||||
"pending": "Pendent",
|
"pending": "Pendent",
|
||||||
"people": "Persones",
|
"people": "Persones",
|
||||||
@@ -802,6 +938,7 @@
|
|||||||
"permanently_deleted_assets_count": "{count, plural, one {S'ha eliminat un element} other {S'han eliminat # elements}} permanentment",
|
"permanently_deleted_assets_count": "{count, plural, one {S'ha eliminat un element} other {S'han eliminat # elements}} permanentment",
|
||||||
"person": "Persona",
|
"person": "Persona",
|
||||||
"person_hidden": "{name}{hidden, select, true { (ocultat)} other {}}",
|
"person_hidden": "{name}{hidden, select, true { (ocultat)} other {}}",
|
||||||
|
"photo_shared_all_users": "Sembla que has compartit les teves fotos amb tots els usuaris o no tens cap usuari amb qui compartir-les.",
|
||||||
"photos": "Fotos",
|
"photos": "Fotos",
|
||||||
"photos_and_videos": "Fotos i vídeos",
|
"photos_and_videos": "Fotos i vídeos",
|
||||||
"photos_count": "{count, plural, one {{count, number} foto} other {{count, number} fotos}}",
|
"photos_count": "{count, plural, one {{count, number} foto} other {{count, number} fotos}}",
|
||||||
@@ -810,36 +947,66 @@
|
|||||||
"place": "Lloc",
|
"place": "Lloc",
|
||||||
"places": "Llocs",
|
"places": "Llocs",
|
||||||
"play": "Reprodueix",
|
"play": "Reprodueix",
|
||||||
"play_memories": "",
|
"play_memories": "Reproduir records",
|
||||||
"play_motion_photo": "",
|
"play_motion_photo": "Reproduir Fotos en Moviment",
|
||||||
"play_or_pause_video": "",
|
"play_or_pause_video": "Reproduir o posar en pausa el vídeo",
|
||||||
"point": "",
|
"point": "",
|
||||||
"port": "Port",
|
"port": "Port",
|
||||||
"preset": "",
|
"preset": "",
|
||||||
"preview": "Previsualització",
|
"preview": "Previsualització",
|
||||||
"previous": "Anterior",
|
"previous": "Anterior",
|
||||||
"previous_memory": "",
|
"previous_memory": "Memòria anterior",
|
||||||
"previous_or_next_photo": "",
|
"previous_or_next_photo": "Foto anterior o següent",
|
||||||
"primary": "",
|
"primary": "Primària",
|
||||||
"profile_picture_set": "",
|
"profile_image_of_user": "Imatge de perfil de {user}",
|
||||||
|
"profile_picture_set": "Imatge de perfil configurada.",
|
||||||
"public_album": "Àlbum públic",
|
"public_album": "Àlbum públic",
|
||||||
"public_share": "",
|
"public_share": "",
|
||||||
|
"purchase_activated_subtitle": "Gràcies per donar suport a Immich i al programari de codi obert",
|
||||||
|
"purchase_activated_time": "Activat el {date, date}",
|
||||||
|
"purchase_activated_title": "La teva clau s'ha activat correctament",
|
||||||
|
"purchase_button_activate": "Activar",
|
||||||
|
"purchase_button_buy": "Comprar",
|
||||||
|
"purchase_button_buy_immich": "Compra Immich",
|
||||||
|
"purchase_button_never_show_again": "No mostrar mai més",
|
||||||
|
"purchase_button_reminder": "Recordar en 30 dies",
|
||||||
|
"purchase_button_remove_key": "Elimina la clau",
|
||||||
|
"purchase_failed_activation": "No s'ha pogut activar! Si us plau, comproveu el vostre correu electrònic per trobar la clau de producte correcta!",
|
||||||
|
"purchase_individual_description_1": "Per a un particular",
|
||||||
|
"purchase_input_suggestion": "Tens una clau de producte? Introduïu la clau a continuació",
|
||||||
|
"purchase_license_subtitle": "Compra Immich per donar suport al desenvolupament continuat del servei",
|
||||||
|
"purchase_lifetime_description": "Compra de per vida",
|
||||||
|
"purchase_option_title": "OPCIONS DE COMPRA",
|
||||||
|
"purchase_panel_info_1": "Crear Immich requereix molt de temps i esforç, tenim enginyers a temps complet treballant-hi per fer-ho tan bo com sigui possible. La nostra missió és que el programari de codi obert i les pràctiques empresarials ètiques es converteixin en una font d'ingressos sostenible per als desenvolupadors i creïn un ecosistema que respecti la privacitat amb alternatives reals als serveis cloud explotadors.",
|
||||||
|
"purchase_panel_info_2": "Com que estem compromesos a no afegir murs de pagament, aquesta compra no us atorgarà cap funció addicional a Immich. Confiem en usuaris com tu per donar suport al desenvolupament continu d'Immich.",
|
||||||
|
"purchase_panel_title": "Donar suport al projecte",
|
||||||
|
"purchase_remove_product_key": "Elimina la clau del producte",
|
||||||
|
"purchase_remove_product_key_prompt": "Esteu segur que voleu eliminar la clau del producte?",
|
||||||
|
"purchase_remove_server_product_key": "Elimina la clau de producte del servidor",
|
||||||
|
"purchase_remove_server_product_key_prompt": "Esteu segur que voleu eliminar la clau de producte del servidor?",
|
||||||
|
"purchase_server_description_1": "Per a tot el servidor",
|
||||||
|
"purchase_server_title": "Servidor",
|
||||||
|
"purchase_settings_server_activated": "La clau de producte del servidor la gestiona l'administrador",
|
||||||
"range": "",
|
"range": "",
|
||||||
"raw": "",
|
"raw": "",
|
||||||
"reaction_options": "",
|
"reaction_options": "",
|
||||||
"read_changelog": "",
|
"read_changelog": "Llegeix el registre de canvis",
|
||||||
|
"reassign": "Reassignar",
|
||||||
"reassing_hint": "Assignar els elements seleccionats a una persona existent",
|
"reassing_hint": "Assignar els elements seleccionats a una persona existent",
|
||||||
"recent": "Recent",
|
"recent": "Recent",
|
||||||
"recent_searches": "Cerques recents",
|
"recent_searches": "Cerques recents",
|
||||||
"refresh": "Actualitzar",
|
"refresh": "Actualitzar",
|
||||||
|
"refresh_encoded_videos": "Actualitza vídeos codificats",
|
||||||
"refresh_metadata": "Actualitzar les metadades",
|
"refresh_metadata": "Actualitzar les metadades",
|
||||||
"refresh_thumbnails": "Actualitzar la miniatura",
|
"refresh_thumbnails": "Actualitzar la miniatura",
|
||||||
"refreshed": "Actualitzat",
|
"refreshed": "Actualitzat",
|
||||||
"refreshes_every_file": "",
|
"refreshes_every_file": "Actualitza tots els fitxers",
|
||||||
|
"refreshing_encoded_video": "S'està actualitzant el vídeo codificat",
|
||||||
"refreshing_metadata": "Actualitzant les metadades",
|
"refreshing_metadata": "Actualitzant les metadades",
|
||||||
"regenerating_thumbnails": "Regenerant les miniatures",
|
"regenerating_thumbnails": "Regenerant les miniatures",
|
||||||
"remove": "Eliminar",
|
"remove": "Eliminar",
|
||||||
"remove_assets_title": "Eliminar els elements?",
|
"remove_assets_title": "Eliminar els elements?",
|
||||||
|
"remove_custom_date_range": "Elimina l'interval de dates personalitzat",
|
||||||
"remove_from_album": "Treu de l'àlbum",
|
"remove_from_album": "Treu de l'àlbum",
|
||||||
"remove_from_favorites": "Eliminar dels preferits",
|
"remove_from_favorites": "Eliminar dels preferits",
|
||||||
"remove_from_shared_link": "Eliminar de l'enllaç compartit",
|
"remove_from_shared_link": "Eliminar de l'enllaç compartit",
|
||||||
@@ -848,17 +1015,18 @@
|
|||||||
"removed_api_key": "Eliminada la clau d'API: {name}",
|
"removed_api_key": "Eliminada la clau d'API: {name}",
|
||||||
"removed_from_archive": "Eliminat de l'arxiu",
|
"removed_from_archive": "Eliminat de l'arxiu",
|
||||||
"removed_from_favorites": "Eliminat dels preferits",
|
"removed_from_favorites": "Eliminat dels preferits",
|
||||||
"removed_from_favorites_count": "{count, plural {S'han eliminat # elements}, other {S'ha eliminat un element}} dels preferits",
|
"removed_from_favorites_count": "{count, plural, other {Removed #}} dels preferits",
|
||||||
"repair": "Reparació",
|
"repair": "Reparació",
|
||||||
"repair_no_results_message": "",
|
"repair_no_results_message": "Els fitxers sense seguiment i que falten es mostraran aquí",
|
||||||
"replace_with_upload": "Substituir amb una pujada",
|
"replace_with_upload": "Substituir amb una pujada",
|
||||||
"repository": "Repositori",
|
"repository": "Repositori",
|
||||||
"require_password": "",
|
"require_password": "Requereix contrasenya",
|
||||||
"require_user_to_change_password_on_first_login": "Requerir que l'usuari canviï la contrasenya en el primer inici de sessió",
|
"require_user_to_change_password_on_first_login": "Requerir que l'usuari canviï la contrasenya en el primer inici de sessió",
|
||||||
"reset": "Restablir",
|
"reset": "Restablir",
|
||||||
"reset_password": "Restablir contrasenya",
|
"reset_password": "Restablir contrasenya",
|
||||||
"reset_people_visibility": "Restablir la visibilitat de les persones",
|
"reset_people_visibility": "Restablir la visibilitat de les persones",
|
||||||
"reset_settings_to_default": "",
|
"reset_settings_to_default": "",
|
||||||
|
"reset_to_default": "Restableix els valors predeterminats",
|
||||||
"resolved_all_duplicates": "Tots els duplicats resolts",
|
"resolved_all_duplicates": "Tots els duplicats resolts",
|
||||||
"restore": "Recupera",
|
"restore": "Recupera",
|
||||||
"restore_all": "Restaurar-ho tot",
|
"restore_all": "Restaurar-ho tot",
|
||||||
@@ -907,7 +1075,7 @@
|
|||||||
"select_photos": "Tria fotografies",
|
"select_photos": "Tria fotografies",
|
||||||
"select_trash_all": "Envia la selecció a la paperera",
|
"select_trash_all": "Envia la selecció a la paperera",
|
||||||
"selected": "Seleccionat",
|
"selected": "Seleccionat",
|
||||||
"selected_count": "{count, plural {# seleccionats}, other {Un seleccionat}}",
|
"selected_count": "",
|
||||||
"send_message": "Envia missatge",
|
"send_message": "Envia missatge",
|
||||||
"send_welcome_email": "Envia correu de benvinguda",
|
"send_welcome_email": "Envia correu de benvinguda",
|
||||||
"server": "Servidor",
|
"server": "Servidor",
|
||||||
@@ -1045,6 +1213,7 @@
|
|||||||
"validate": "Valida",
|
"validate": "Valida",
|
||||||
"variables": "Variables",
|
"variables": "Variables",
|
||||||
"version": "Versió",
|
"version": "Versió",
|
||||||
|
"version_announcement_message": "Hola amic, hi ha una nova versió de l'aplicació, si us plau, preneu-vos el temps per visitar les <link>release notes</link> i assegureu-vos que el vostre <code>docker-compose.yml</code> i <code>.env</code> estàn actualitzats per evitar qualsevol configuració incorrecta, especialment si utilitzeu WatchTower o qualsevol mecanisme que gestioni l'actualització automàtica de la vostra aplicació.",
|
||||||
"video": "Vídeo",
|
"video": "Vídeo",
|
||||||
"video_hover_setting": "Reprodueix la miniatura en passar el ratolí",
|
"video_hover_setting": "Reprodueix la miniatura en passar el ratolí",
|
||||||
"video_hover_setting_description": "Reprodueix la miniatura quan el ratolí plana sobre l'element. Fins i tot quan estigui deshabilitat, la reproducció s'iniciarà planant sobre el botó de reproducció.",
|
"video_hover_setting_description": "Reprodueix la miniatura quan el ratolí plana sobre l'element. Fins i tot quan estigui deshabilitat, la reproducció s'iniciarà planant sobre el botó de reproducció.",
|
||||||
|
|||||||
@@ -410,7 +410,7 @@
|
|||||||
"bulk_delete_duplicates_confirmation": "Opravdu chcete hromadně odstranit {count, plural, one {# duplicitní položku} few {# duplicitní položky} other {# duplicitních položek}}? Tím se zachová největší položka z každé skupiny a všechny ostatní duplicity se trvale odstraní. Tuto akci nelze vrátit zpět!",
|
"bulk_delete_duplicates_confirmation": "Opravdu chcete hromadně odstranit {count, plural, one {# duplicitní položku} few {# duplicitní položky} other {# duplicitních položek}}? Tím se zachová největší položka z každé skupiny a všechny ostatní duplicity se trvale odstraní. Tuto akci nelze vrátit zpět!",
|
||||||
"bulk_keep_duplicates_confirmation": "Opravdu si chcete ponechat {count, plural, one {# duplicitní položku} few {# duplicitní položky} other {# duplicitních položek}}? Tím se vyřeší všechny duplicitní skupiny, aniž by se cokoli odstranilo.",
|
"bulk_keep_duplicates_confirmation": "Opravdu si chcete ponechat {count, plural, one {# duplicitní položku} few {# duplicitní položky} other {# duplicitních položek}}? Tím se vyřeší všechny duplicitní skupiny, aniž by se cokoli odstranilo.",
|
||||||
"bulk_trash_duplicates_confirmation": "Opravdu chcete hromadně vyhodit {count, plural, one {# duplicitní položku} few {# duplicitní položky} other {# duplicitních položek}}? Tím se zachová největší položka z každé skupiny a všechny ostatní duplikáty se vyhodí.",
|
"bulk_trash_duplicates_confirmation": "Opravdu chcete hromadně vyhodit {count, plural, one {# duplicitní položku} few {# duplicitní položky} other {# duplicitních položek}}? Tím se zachová největší položka z každé skupiny a všechny ostatní duplikáty se vyhodí.",
|
||||||
"buy": "Zakoupit licenci",
|
"buy": "Zakoupit Immich",
|
||||||
"camera": "Fotoaparát",
|
"camera": "Fotoaparát",
|
||||||
"camera_brand": "Značka fotoaparátu",
|
"camera_brand": "Značka fotoaparátu",
|
||||||
"camera_model": "Model fotoaparátu",
|
"camera_model": "Model fotoaparátu",
|
||||||
@@ -438,6 +438,7 @@
|
|||||||
"city": "Město",
|
"city": "Město",
|
||||||
"clear": "Vyčistit",
|
"clear": "Vyčistit",
|
||||||
"clear_all": "Vymazat vše",
|
"clear_all": "Vymazat vše",
|
||||||
|
"clear_all_recent_searches": "Vymazat všechna nedávná vyhledávání",
|
||||||
"clear_message": "Vyčistit zprávu",
|
"clear_message": "Vyčistit zprávu",
|
||||||
"clear_value": "Vyčistit hodnotu",
|
"clear_value": "Vyčistit hodnotu",
|
||||||
"close": "Zavřít",
|
"close": "Zavřít",
|
||||||
@@ -576,6 +577,7 @@
|
|||||||
"error_adding_users_to_album": "Chyba při přidávání uživatelů do alba",
|
"error_adding_users_to_album": "Chyba při přidávání uživatelů do alba",
|
||||||
"error_deleting_shared_user": "Chyba při odstraňování sdíleného uživatele",
|
"error_deleting_shared_user": "Chyba při odstraňování sdíleného uživatele",
|
||||||
"error_downloading": "Chyba při stahování {filename}",
|
"error_downloading": "Chyba při stahování {filename}",
|
||||||
|
"error_hiding_buy_button": "Chyba při skrývání tlačítka koupit",
|
||||||
"error_removing_assets_from_album": "Chyba při odstraňování položek z alba, další podrobnosti najdete v konzoli",
|
"error_removing_assets_from_album": "Chyba při odstraňování položek z alba, další podrobnosti najdete v konzoli",
|
||||||
"error_selecting_all_assets": "Chyba při výběru všech položek",
|
"error_selecting_all_assets": "Chyba při výběru všech položek",
|
||||||
"exclusion_pattern_already_exists": "Tento vzor vyloučení již existuje.",
|
"exclusion_pattern_already_exists": "Tento vzor vyloučení již existuje.",
|
||||||
@@ -586,6 +588,8 @@
|
|||||||
"failed_to_get_people": "Nepodařilo se načíst lidi",
|
"failed_to_get_people": "Nepodařilo se načíst lidi",
|
||||||
"failed_to_load_asset": "Nepodařilo se načíst položku",
|
"failed_to_load_asset": "Nepodařilo se načíst položku",
|
||||||
"failed_to_load_assets": "Nepodařilo se načíst položky",
|
"failed_to_load_assets": "Nepodařilo se načíst položky",
|
||||||
|
"failed_to_load_people": "Chyba načítání osob",
|
||||||
|
"failed_to_remove_product_key": "Nepodařilo se odebrat klíč produktu",
|
||||||
"failed_to_stack_assets": "Nepodařilo se poskládat položky",
|
"failed_to_stack_assets": "Nepodařilo se poskládat položky",
|
||||||
"failed_to_unstack_assets": "Nepodařilo se rozložit položky",
|
"failed_to_unstack_assets": "Nepodařilo se rozložit položky",
|
||||||
"import_path_already_exists": "Tato cesta importu již existuje.",
|
"import_path_already_exists": "Tato cesta importu již existuje.",
|
||||||
@@ -739,7 +743,16 @@
|
|||||||
"host": "Hostitel",
|
"host": "Hostitel",
|
||||||
"hour": "Hodina",
|
"hour": "Hodina",
|
||||||
"image": "Obrázek",
|
"image": "Obrázek",
|
||||||
"image_alt_text_date": "v {date}",
|
"image_alt_text_date": "{isVideo, select, true {Video pořízeno} other {Obrázek pořízen}} {date}",
|
||||||
|
"image_alt_text_date_1_person": "{isVideo, select, true {Video pořízeno} other {Obrázek pořízen}} {date} uživatelem {person1}",
|
||||||
|
"image_alt_text_date_2_people": "{isVideo, select, true {Video pořízeno} other {Obrázek pořízen}} {date} uživateli {person1} a {person2}",
|
||||||
|
"image_alt_text_date_3_people": "{isVideo, select, true {Video pořízeno} other {Obrázek pořízen}} {date} uživateli {person1}, {person2} a {person3}",
|
||||||
|
"image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video pořízeno} other {Obrázek pořízen}} {date} uživateli {person1}, {person2} a {additionalCount, plural, one {dalším # uživatelem} other {dalšími # uživateli}}",
|
||||||
|
"image_alt_text_date_place": "{isVideo, select, true {Video pořízeno} other {Obrázek požízen}} {date} v místě {city}, {country}",
|
||||||
|
"image_alt_text_date_place_1_person": "{isVideo, select, true {Video pořízeno} other {Obrázek požízen}} {date} v místě {city}, {country} uživatelem {person1}",
|
||||||
|
"image_alt_text_date_place_2_people": "{isVideo, select, true {Video pořízeno} other {Obrázek požízen}} {date} v místě {city}, {country} uživateli {person1} a {person2}",
|
||||||
|
"image_alt_text_date_place_3_people": "{isVideo, select, true {Video pořízeno} other {Obrázek požízen}} {date} v místě {city}, {country} uživateli {person1}, {person2} a {person3}",
|
||||||
|
"image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video pořízeno} other {Obrázek požízen}} {date} v místě {city}, {country} uživateli {person1}, {person2} a {additionalCount, plural, one {dalším # uživatelem} other {dalšími # uživateli}}",
|
||||||
"image_alt_text_people": "{count, plural, =1 {a {person1}} =2 {s {person1} a {person2}} =3 {s {person1}, {person2}, a {person3}} other {s {person1}, {person2}, a {others, number} dalšími}}",
|
"image_alt_text_people": "{count, plural, =1 {a {person1}} =2 {s {person1} a {person2}} =3 {s {person1}, {person2}, a {person3}} other {s {person1}, {person2}, a {others, number} dalšími}}",
|
||||||
"image_alt_text_place": "v {city}, {country}",
|
"image_alt_text_place": "v {city}, {country}",
|
||||||
"image_taken": "{isVideo, select, true {Video pořízeno} other {Obrázek požízen}}",
|
"image_taken": "{isVideo, select, true {Video pořízeno} other {Obrázek požízen}}",
|
||||||
@@ -860,6 +873,7 @@
|
|||||||
"name": "Jméno",
|
"name": "Jméno",
|
||||||
"name_or_nickname": "Jméno nebo přezdívka",
|
"name_or_nickname": "Jméno nebo přezdívka",
|
||||||
"never": "Nikdy",
|
"never": "Nikdy",
|
||||||
|
"new_album": "Nové album",
|
||||||
"new_api_key": "Nový API klíč",
|
"new_api_key": "Nový API klíč",
|
||||||
"new_password": "Nové heslo",
|
"new_password": "Nové heslo",
|
||||||
"new_person": "Nová osoba",
|
"new_person": "Nová osoba",
|
||||||
@@ -975,6 +989,38 @@
|
|||||||
"profile_picture_set": "Profilový obrázek nastaven.",
|
"profile_picture_set": "Profilový obrázek nastaven.",
|
||||||
"public_album": "Veřejné album",
|
"public_album": "Veřejné album",
|
||||||
"public_share": "Veřejné sdílení",
|
"public_share": "Veřejné sdílení",
|
||||||
|
"purchase_account_info": "Podporovatel",
|
||||||
|
"purchase_activated_subtitle": "Děkujeme vám za podporu aplikace Immich a softwaru s otevřeným zdrojovým kódem",
|
||||||
|
"purchase_activated_time": "Aktivováno dne {date, date}",
|
||||||
|
"purchase_activated_title": "Váš klíč byl úspěšně aktivován",
|
||||||
|
"purchase_button_activate": "Aktivovat",
|
||||||
|
"purchase_button_buy": "Koupit",
|
||||||
|
"purchase_button_buy_immich": "Koupit Immich",
|
||||||
|
"purchase_button_never_show_again": "Nikdy již nezobrazovat",
|
||||||
|
"purchase_button_reminder": "Připomenout za 30 dní",
|
||||||
|
"purchase_button_remove_key": "Odstranit klíč",
|
||||||
|
"purchase_button_select": "Vybrat",
|
||||||
|
"purchase_failed_activation": "Aktivace se nezdařila! Zkontrolujte prosím svůj e-mail pro správný produktový klíč!",
|
||||||
|
"purchase_individual_description_1": "Pro jednotlivce",
|
||||||
|
"purchase_individual_description_2": "Stav podporovatele",
|
||||||
|
"purchase_individual_title": "Individuální",
|
||||||
|
"purchase_input_suggestion": "Máte produktový klíč? Zadejte klíč níže",
|
||||||
|
"purchase_license_subtitle": "Koupit Immich na podporu dalšího rozvoje služby",
|
||||||
|
"purchase_lifetime_description": "Doživotní platnost",
|
||||||
|
"purchase_option_title": "MOŽNOSTI NÁKUPU",
|
||||||
|
"purchase_panel_info_1": "Tvorba aplikace Immich vyžaduje spoustu času a úsilí, a proto na ní pracují vývojáři na plný úvazek, aby byla co nejlepší. Naším cílem je, aby se software s otevřeným zdrojovým kódem a etické obchodní postupy staly udržitelným zdrojem příjmů pro vývojáře a aby vznikl ekosystém respektující soukromí se skutečnými alternativami k ziskuchtivým službám.",
|
||||||
|
"purchase_panel_info_2": "Protože jsme se zavázali, že nebudeme zavádět paywally, nezískáte tímto nákupem žádné další funkce v aplikaci Immich. Spoléháme na uživatele, jako jste vy, že podpoří neustálý vývoj aplikace.",
|
||||||
|
"purchase_panel_title": "Podpora projektu",
|
||||||
|
"purchase_per_server": "Na server",
|
||||||
|
"purchase_per_user": "Na uživatele",
|
||||||
|
"purchase_remove_product_key": "Odstranění produktového klíče",
|
||||||
|
"purchase_remove_product_key_prompt": "Opravdu chcete odebrat produktový klíč?",
|
||||||
|
"purchase_remove_server_product_key": "Odstranění serverového produktového klíče",
|
||||||
|
"purchase_remove_server_product_key_prompt": "Opravdu chcete odebrat serverový produktový klíč?",
|
||||||
|
"purchase_server_description_1": "Pro celý server",
|
||||||
|
"purchase_server_description_2": "Stav podporovatele",
|
||||||
|
"purchase_server_title": "Server",
|
||||||
|
"purchase_settings_server_activated": "Produktový klíč serveru spravuje správce",
|
||||||
"range": "Rozsah",
|
"range": "Rozsah",
|
||||||
"raw": "Raw",
|
"raw": "Raw",
|
||||||
"reaction_options": "Možnosti reakce",
|
"reaction_options": "Možnosti reakce",
|
||||||
@@ -1020,6 +1066,7 @@
|
|||||||
"reset_people_visibility": "Obnovit viditelnost lidí",
|
"reset_people_visibility": "Obnovit viditelnost lidí",
|
||||||
"reset_settings_to_default": "Obnovit výchozí nastavení",
|
"reset_settings_to_default": "Obnovit výchozí nastavení",
|
||||||
"reset_to_default": "Obnovit výchozí nastavení",
|
"reset_to_default": "Obnovit výchozí nastavení",
|
||||||
|
"resolve_duplicates": "Vyřešit duplicity",
|
||||||
"resolved_all_duplicates": "Vyřešeny všechny duplicity",
|
"resolved_all_duplicates": "Vyřešeny všechny duplicity",
|
||||||
"restore": "Obnovit",
|
"restore": "Obnovit",
|
||||||
"restore_all": "Obnovit vše",
|
"restore_all": "Obnovit vše",
|
||||||
@@ -1064,6 +1111,7 @@
|
|||||||
"see_all_people": "Zobrazit všechny lidi",
|
"see_all_people": "Zobrazit všechny lidi",
|
||||||
"select_album_cover": "Vybrat obal alba",
|
"select_album_cover": "Vybrat obal alba",
|
||||||
"select_all": "Vybrat vše",
|
"select_all": "Vybrat vše",
|
||||||
|
"select_all_duplicates": "Vybrat všechny duplicity",
|
||||||
"select_avatar_color": "Vyberte barvu avatara",
|
"select_avatar_color": "Vyberte barvu avatara",
|
||||||
"select_face": "Vybrat obličej",
|
"select_face": "Vybrat obličej",
|
||||||
"select_featured_photo": "Vybrat hlavní fotografii",
|
"select_featured_photo": "Vybrat hlavní fotografii",
|
||||||
@@ -1118,6 +1166,8 @@
|
|||||||
"show_person_options": "Zobrazit možnosti osoby",
|
"show_person_options": "Zobrazit možnosti osoby",
|
||||||
"show_progress_bar": "Zobrazit ukazatel průběhu",
|
"show_progress_bar": "Zobrazit ukazatel průběhu",
|
||||||
"show_search_options": "Zobrazit možnosti vyhledávání",
|
"show_search_options": "Zobrazit možnosti vyhledávání",
|
||||||
|
"show_supporter_badge": "Odznak podporovatele",
|
||||||
|
"show_supporter_badge_description": "Zobrazit odznak podporovatele",
|
||||||
"shuffle": "Náhodný výběr",
|
"shuffle": "Náhodný výběr",
|
||||||
"sign_out": "Odhlásit se",
|
"sign_out": "Odhlásit se",
|
||||||
"sign_up": "Zaregistrovat se",
|
"sign_up": "Zaregistrovat se",
|
||||||
@@ -1191,6 +1241,7 @@
|
|||||||
"unnamed_share": "Nejmenované sdílení",
|
"unnamed_share": "Nejmenované sdílení",
|
||||||
"unsaved_change": "Neuložená změna",
|
"unsaved_change": "Neuložená změna",
|
||||||
"unselect_all": "Zrušit výběr všech",
|
"unselect_all": "Zrušit výběr všech",
|
||||||
|
"unselect_all_duplicates": "Zrušit výběr všech duplicit",
|
||||||
"unstack": "Zrušit zásobník",
|
"unstack": "Zrušit zásobník",
|
||||||
"unstacked_assets_count": "{count, plural, one {Rozložena # položka} few {Rozloženy # položky} other {Rozloženo # položek}}",
|
"unstacked_assets_count": "{count, plural, one {Rozložena # položka} few {Rozloženy # položky} other {Rozloženo # položek}}",
|
||||||
"untracked_files": "Nesledované soubory",
|
"untracked_files": "Nesledované soubory",
|
||||||
@@ -1214,6 +1265,8 @@
|
|||||||
"user_license_settings": "Licence",
|
"user_license_settings": "Licence",
|
||||||
"user_license_settings_description": "Správa licence",
|
"user_license_settings_description": "Správa licence",
|
||||||
"user_liked": "Uživateli {user} se {type, select, photo {líbila tato fotka} video {líbilo toto video} asset {líbila tato položka} other {to líbilo}}",
|
"user_liked": "Uživateli {user} se {type, select, photo {líbila tato fotka} video {líbilo toto video} asset {líbila tato položka} other {to líbilo}}",
|
||||||
|
"user_purchase_settings": "Nákup",
|
||||||
|
"user_purchase_settings_description": "Správa vašeho nákupu",
|
||||||
"user_role_set": "Uživatel {user} nastaven jako {role}",
|
"user_role_set": "Uživatel {user} nastaven jako {role}",
|
||||||
"user_usage_detail": "Podrobnosti využití uživatelů",
|
"user_usage_detail": "Podrobnosti využití uživatelů",
|
||||||
"username": "Uživateleské jméno",
|
"username": "Uživateleské jméno",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"about": "Über",
|
"about": "Über Immich",
|
||||||
"account": "Konto",
|
"account": "Konto",
|
||||||
"account_settings": "Kontoeinstellungen",
|
"account_settings": "Kontoeinstellungen",
|
||||||
"acknowledge": "Bestätigen",
|
"acknowledge": "Bestätigen",
|
||||||
@@ -410,7 +410,7 @@
|
|||||||
"bulk_delete_duplicates_confirmation": "Bist du sicher, dass du {count, plural, one {# duplizierte Datei} other {# duplizierte Dateien}} gemeinsam löschen möchtest? Dabei wird die größte Datei jeder Gruppe behalten und alle anderen Duplikate dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden!",
|
"bulk_delete_duplicates_confirmation": "Bist du sicher, dass du {count, plural, one {# duplizierte Datei} other {# duplizierte Dateien}} gemeinsam löschen möchtest? Dabei wird die größte Datei jeder Gruppe behalten und alle anderen Duplikate dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden!",
|
||||||
"bulk_keep_duplicates_confirmation": "Bist du sicher, dass du {count, plural, one {# duplizierte Datei} other {# duplizierte Dateien}} behalten möchtest? Dies wird alle Duplikat-Gruppen auflösen ohne etwas zu löschen.",
|
"bulk_keep_duplicates_confirmation": "Bist du sicher, dass du {count, plural, one {# duplizierte Datei} other {# duplizierte Dateien}} behalten möchtest? Dies wird alle Duplikat-Gruppen auflösen ohne etwas zu löschen.",
|
||||||
"bulk_trash_duplicates_confirmation": "Bist du sicher, dass du {count, plural, one {# duplizierte Datei} other {# duplizierte Dateien}} gemeinsam in den Papierkorb verschieben möchtest? Dies wird die größte Datei jeder Gruppe behalten und alle anderen Duplikate in den Papierkorb verschieben.",
|
"bulk_trash_duplicates_confirmation": "Bist du sicher, dass du {count, plural, one {# duplizierte Datei} other {# duplizierte Dateien}} gemeinsam in den Papierkorb verschieben möchtest? Dies wird die größte Datei jeder Gruppe behalten und alle anderen Duplikate in den Papierkorb verschieben.",
|
||||||
"buy": "Lizenz erwerben",
|
"buy": "Immich erwerben",
|
||||||
"camera": "Kamera",
|
"camera": "Kamera",
|
||||||
"camera_brand": "Kamera-Marke",
|
"camera_brand": "Kamera-Marke",
|
||||||
"camera_model": "Kamera-Modell",
|
"camera_model": "Kamera-Modell",
|
||||||
@@ -438,6 +438,7 @@
|
|||||||
"city": "Stadt",
|
"city": "Stadt",
|
||||||
"clear": "Leeren",
|
"clear": "Leeren",
|
||||||
"clear_all": "Alles leeren",
|
"clear_all": "Alles leeren",
|
||||||
|
"clear_all_recent_searches": "Alle letzten Suchvorgänge löschen",
|
||||||
"clear_message": "Nachrichten leeren",
|
"clear_message": "Nachrichten leeren",
|
||||||
"clear_value": "Wert leeren",
|
"clear_value": "Wert leeren",
|
||||||
"close": "Schließen",
|
"close": "Schließen",
|
||||||
@@ -576,6 +577,7 @@
|
|||||||
"error_adding_users_to_album": "Fehler beim Hinzufügen von Benutzern zum Album",
|
"error_adding_users_to_album": "Fehler beim Hinzufügen von Benutzern zum Album",
|
||||||
"error_deleting_shared_user": "Fehler beim Löschen des geteilten Benutzers",
|
"error_deleting_shared_user": "Fehler beim Löschen des geteilten Benutzers",
|
||||||
"error_downloading": "Fehler beim Herunterladen von {filename}",
|
"error_downloading": "Fehler beim Herunterladen von {filename}",
|
||||||
|
"error_hiding_buy_button": "Fehler beim Ausblenden der Kaufen Schaltfläche",
|
||||||
"error_removing_assets_from_album": "Fehler beim Entfernen von Dateien aus dem Album, siehe Konsole für weitere Details",
|
"error_removing_assets_from_album": "Fehler beim Entfernen von Dateien aus dem Album, siehe Konsole für weitere Details",
|
||||||
"error_selecting_all_assets": "Fehler beim Auswählen aller Dateien",
|
"error_selecting_all_assets": "Fehler beim Auswählen aller Dateien",
|
||||||
"exclusion_pattern_already_exists": "Dieses Ausschlussmuster existiert bereits.",
|
"exclusion_pattern_already_exists": "Dieses Ausschlussmuster existiert bereits.",
|
||||||
@@ -586,6 +588,8 @@
|
|||||||
"failed_to_get_people": "Personen konnten nicht abgerufen werden",
|
"failed_to_get_people": "Personen konnten nicht abgerufen werden",
|
||||||
"failed_to_load_asset": "Fehler beim Laden der Datei",
|
"failed_to_load_asset": "Fehler beim Laden der Datei",
|
||||||
"failed_to_load_assets": "Fehler beim Laden der Dateien",
|
"failed_to_load_assets": "Fehler beim Laden der Dateien",
|
||||||
|
"failed_to_load_people": "Fehler beim Laden von Personen",
|
||||||
|
"failed_to_remove_product_key": "Fehler beim Entfernen des Produktschlüssels",
|
||||||
"failed_to_stack_assets": "Dateien konnten nicht gestapelt werden",
|
"failed_to_stack_assets": "Dateien konnten nicht gestapelt werden",
|
||||||
"failed_to_unstack_assets": "Dateien konnten nicht entstapelt werden",
|
"failed_to_unstack_assets": "Dateien konnten nicht entstapelt werden",
|
||||||
"import_path_already_exists": "Dieser Importpfad existiert bereits.",
|
"import_path_already_exists": "Dieser Importpfad existiert bereits.",
|
||||||
@@ -739,7 +743,16 @@
|
|||||||
"host": "Host",
|
"host": "Host",
|
||||||
"hour": "Stunde",
|
"hour": "Stunde",
|
||||||
"image": "Bild",
|
"image": "Bild",
|
||||||
"image_alt_text_date": "am {date}",
|
"image_alt_text_date": "{isVideo, select, true {Video} other {Bild}} aufgenommen am {date}",
|
||||||
|
"image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Bild}} aufgenommen mit {person1} am {date}",
|
||||||
|
"image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Bild}} aufgenommen mit {person1} und {person2} am {date}",
|
||||||
|
"image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Bild}} aufgenommen mit {person1}, {person2} und {person3} am {date}",
|
||||||
|
"image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Bild}} aufgenommen mit {person1}, {person2}, und {additionalCount, number} anderen am {date}",
|
||||||
|
"image_alt_text_date_place": "{isVideo, select, true {Video} other {Bild}} aufgenommen in {city}, {country} am {date}",
|
||||||
|
"image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Bild}} aufgenommen in {city}, {country} mit {person1} am {date}",
|
||||||
|
"image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Bild}} aufgenommen in {city}, {country} mit {person1} und {person2} am {date}",
|
||||||
|
"image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Bild}} aufgenommen in {city}, {country} mit {person1}, {person2}, und {person3} am {date}",
|
||||||
|
"image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Bild}} aufgenommen in {city}, {country} mit {person1}, {person2}, und {additionalCount, number} anderen am {date}",
|
||||||
"image_alt_text_people": "{count, plural, =1 {mit {person1}} =2 {mit {person1} und {person2}} =3 {mit {person1}, {person2} und {person3}} other {mit {person1}, {person2} und {others, number} anderen}}",
|
"image_alt_text_people": "{count, plural, =1 {mit {person1}} =2 {mit {person1} und {person2}} =3 {mit {person1}, {person2} und {person3}} other {mit {person1}, {person2} und {others, number} anderen}}",
|
||||||
"image_alt_text_place": "in {city}, {country}",
|
"image_alt_text_place": "in {city}, {country}",
|
||||||
"image_taken": "{isVideo, select, true {Video aufgenommen} other {Bild aufgenommen}}",
|
"image_taken": "{isVideo, select, true {Video aufgenommen} other {Bild aufgenommen}}",
|
||||||
@@ -860,6 +873,7 @@
|
|||||||
"name": "Name",
|
"name": "Name",
|
||||||
"name_or_nickname": "Name oder Nickname",
|
"name_or_nickname": "Name oder Nickname",
|
||||||
"never": "Niemals",
|
"never": "Niemals",
|
||||||
|
"new_album": "Neues Album",
|
||||||
"new_api_key": "Neuer API-Schlüssel",
|
"new_api_key": "Neuer API-Schlüssel",
|
||||||
"new_password": "Neues Passwort",
|
"new_password": "Neues Passwort",
|
||||||
"new_person": "Neue Person",
|
"new_person": "Neue Person",
|
||||||
@@ -974,6 +988,38 @@
|
|||||||
"profile_picture_set": "Profilbild gesetzt.",
|
"profile_picture_set": "Profilbild gesetzt.",
|
||||||
"public_album": "Öffentliches Album",
|
"public_album": "Öffentliches Album",
|
||||||
"public_share": "Öffentliche Teilung",
|
"public_share": "Öffentliche Teilung",
|
||||||
|
"purchase_account_info": "Unterstützer",
|
||||||
|
"purchase_activated_subtitle": "Danke für die Unterstützung von Immich und Open-Source Software",
|
||||||
|
"purchase_activated_time": "Aktiviert am {date, date}",
|
||||||
|
"purchase_activated_title": "Dein Schlüssel wurde erfolgreich aktiviert",
|
||||||
|
"purchase_button_activate": "Aktivieren",
|
||||||
|
"purchase_button_buy": "Kaufen",
|
||||||
|
"purchase_button_buy_immich": "Immich kaufen",
|
||||||
|
"purchase_button_never_show_again": "Nicht nochmal anzeigen",
|
||||||
|
"purchase_button_reminder": "Erinnere mich in 30 Tagen",
|
||||||
|
"purchase_button_remove_key": "Schlüssel entfernen",
|
||||||
|
"purchase_button_select": "Auswählen",
|
||||||
|
"purchase_failed_activation": "Aktivieren fehlgeschlagen! Überprüfe bitte den Produktschlüssel in der E-Mail!",
|
||||||
|
"purchase_individual_description_1": "Für eine Einzelperson",
|
||||||
|
"purchase_individual_description_2": "Unterstützer Status",
|
||||||
|
"purchase_individual_title": "Einzelperson",
|
||||||
|
"purchase_input_suggestion": "Besitzen Sie bereits einen Produktschlüssel? Bitte geben Sie diesen unten ein",
|
||||||
|
"purchase_license_subtitle": "Kaufe Immich um eine fortlaufende Entwicklung zu unterstützen",
|
||||||
|
"purchase_lifetime_description": "Lebenslange Gültigkeit",
|
||||||
|
"purchase_option_title": "KAUF OPTIONEN",
|
||||||
|
"purchase_panel_info_1": "Das Entwickeln von Immich ist aufwendig und nimmt viel Zeit in Anspruch, deshalb haben wir ein Team von Vollzeit-Entwickler*innen, welche ihr Bestes geben. Unser Ziel ist es, mit Open-Source Software und ethischen Unternehmenspraktiken eine nachhaltige Einkommensquelle für unsere Entwickler und ein privatsphäre-respektierendes Ökosystem für unsere Nutzenden zu schaffen. Wir wollen eine kompetitive Alternative zu ausbeuterischen Cloud-Diensten erschaffen.",
|
||||||
|
"purchase_panel_info_2": "Weil wir davon überzeugt sind keine Paywalls zu haben, wird dieser Kauf keine zusätzlichen Funktionen in Immich freischalten. Wir verlassen uns auf Nutzende wie dich, um Entwicklung von Immich zu unterstützen.",
|
||||||
|
"purchase_panel_title": "Das Projekt unterstützen",
|
||||||
|
"purchase_per_server": "Pro Server",
|
||||||
|
"purchase_per_user": "Pro Benutzer",
|
||||||
|
"purchase_remove_product_key": "Produktschlüssel entfernen",
|
||||||
|
"purchase_remove_product_key_prompt": "Sicher, dass der Produktschlüssel entfernt werden soll?",
|
||||||
|
"purchase_remove_server_product_key": "Server Produktschlüssel entfernen",
|
||||||
|
"purchase_remove_server_product_key_prompt": "Sicher, dass der Server Produktschlüssel entfernt werden soll?",
|
||||||
|
"purchase_server_description_1": "Für den gesamten Server",
|
||||||
|
"purchase_server_description_2": "Unterstützer Status",
|
||||||
|
"purchase_server_title": "Server",
|
||||||
|
"purchase_settings_server_activated": "Der Server Produktschlüssel wird durch den Administrator verwaltet",
|
||||||
"range": "Reichweite",
|
"range": "Reichweite",
|
||||||
"raw": "RAW",
|
"raw": "RAW",
|
||||||
"reaction_options": "Reaktionsmöglichkeiten",
|
"reaction_options": "Reaktionsmöglichkeiten",
|
||||||
@@ -1019,6 +1065,7 @@
|
|||||||
"reset_people_visibility": "Sichtbarkeit von Personen zurücksetzen",
|
"reset_people_visibility": "Sichtbarkeit von Personen zurücksetzen",
|
||||||
"reset_settings_to_default": "Einstellungen auf Standardwerte zurücksetzen",
|
"reset_settings_to_default": "Einstellungen auf Standardwerte zurücksetzen",
|
||||||
"reset_to_default": "Auf Standard zurücksetzen",
|
"reset_to_default": "Auf Standard zurücksetzen",
|
||||||
|
"resolve_duplicates": "Duplikate entfernen",
|
||||||
"resolved_all_duplicates": "Alle Duplikate aufgelöst",
|
"resolved_all_duplicates": "Alle Duplikate aufgelöst",
|
||||||
"restore": "Wiederherstellen",
|
"restore": "Wiederherstellen",
|
||||||
"restore_all": "Alle wiederherstellen",
|
"restore_all": "Alle wiederherstellen",
|
||||||
@@ -1063,6 +1110,7 @@
|
|||||||
"see_all_people": "Alle Personen anzeigen",
|
"see_all_people": "Alle Personen anzeigen",
|
||||||
"select_album_cover": "Album-Cover auswählen",
|
"select_album_cover": "Album-Cover auswählen",
|
||||||
"select_all": "Alles auswählen",
|
"select_all": "Alles auswählen",
|
||||||
|
"select_all_duplicates": "Alle Duplikate auswählen",
|
||||||
"select_avatar_color": "Avatar-Farbe auswählen",
|
"select_avatar_color": "Avatar-Farbe auswählen",
|
||||||
"select_face": "Gesicht auswählen",
|
"select_face": "Gesicht auswählen",
|
||||||
"select_featured_photo": "Anzeigebild auswählen",
|
"select_featured_photo": "Anzeigebild auswählen",
|
||||||
@@ -1117,6 +1165,8 @@
|
|||||||
"show_person_options": "Personen-Optionen anzeigen",
|
"show_person_options": "Personen-Optionen anzeigen",
|
||||||
"show_progress_bar": "Fortschrittsbalken anzeigen",
|
"show_progress_bar": "Fortschrittsbalken anzeigen",
|
||||||
"show_search_options": "Suchoptionen anzeigen",
|
"show_search_options": "Suchoptionen anzeigen",
|
||||||
|
"show_supporter_badge": "Unterstützer Abzeichen",
|
||||||
|
"show_supporter_badge_description": "Zeige Unterstützer Abzeichen",
|
||||||
"shuffle": "Durchmischen",
|
"shuffle": "Durchmischen",
|
||||||
"sign_out": "Abmelden",
|
"sign_out": "Abmelden",
|
||||||
"sign_up": "Registrieren",
|
"sign_up": "Registrieren",
|
||||||
@@ -1190,6 +1240,7 @@
|
|||||||
"unnamed_share": "Unbenannte Teilung",
|
"unnamed_share": "Unbenannte Teilung",
|
||||||
"unsaved_change": "Ungespeicherte Änderung",
|
"unsaved_change": "Ungespeicherte Änderung",
|
||||||
"unselect_all": "Alles abwählen",
|
"unselect_all": "Alles abwählen",
|
||||||
|
"unselect_all_duplicates": "Alle Duplikate abwählen",
|
||||||
"unstack": "Entstapeln",
|
"unstack": "Entstapeln",
|
||||||
"unstacked_assets_count": "{count, plural, one {# Datei} other {# Dateien}} entstapelt",
|
"unstacked_assets_count": "{count, plural, one {# Datei} other {# Dateien}} entstapelt",
|
||||||
"untracked_files": "Unverfolgte Dateien",
|
"untracked_files": "Unverfolgte Dateien",
|
||||||
@@ -1213,6 +1264,8 @@
|
|||||||
"user_license_settings": "Lizenz",
|
"user_license_settings": "Lizenz",
|
||||||
"user_license_settings_description": "Verwalte deine Lizenz",
|
"user_license_settings_description": "Verwalte deine Lizenz",
|
||||||
"user_liked": "{type, select, photo {Dieses Foto} video {Dieses Video} asset {Diese Datei} other {Dies}} gefällt {user}",
|
"user_liked": "{type, select, photo {Dieses Foto} video {Dieses Video} asset {Diese Datei} other {Dies}} gefällt {user}",
|
||||||
|
"user_purchase_settings": "Kauf",
|
||||||
|
"user_purchase_settings_description": "Kauf verwalten",
|
||||||
"user_role_set": "{user} als {role} festlegen",
|
"user_role_set": "{user} als {role} festlegen",
|
||||||
"user_usage_detail": "Nutzungsdetails der Nutzer",
|
"user_usage_detail": "Nutzungsdetails der Nutzer",
|
||||||
"username": "Nutzername",
|
"username": "Nutzername",
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
"add_to_shared_album": "Add to shared album",
|
"add_to_shared_album": "Add to shared album",
|
||||||
"added_to_archive": "Added to archive",
|
"added_to_archive": "Added to archive",
|
||||||
"added_to_favorites": "Added to favorites",
|
"added_to_favorites": "Added to favorites",
|
||||||
"added_to_favorites_count": "Added {count} to favorites",
|
"added_to_favorites_count": "Added {count, number} to favorites",
|
||||||
"admin": {
|
"admin": {
|
||||||
"add_exclusion_pattern_description": "Add exclusion patterns. Globbing using *, **, and ? is supported. To ignore all files in any directory named \"Raw\", use \"**/Raw/**\". To ignore all files ending in \".tif\", use \"**/*.tif\". To ignore an absolute path, use \"/path/to/ignore/**\".",
|
"add_exclusion_pattern_description": "Add exclusion patterns. Globbing using *, **, and ? is supported. To ignore all files in any directory named \"Raw\", use \"**/Raw/**\". To ignore all files ending in \".tif\", use \"**/*.tif\". To ignore an absolute path, use \"/path/to/ignore/**\".",
|
||||||
"authentication_settings": "Authentication Settings",
|
"authentication_settings": "Authentication Settings",
|
||||||
@@ -429,6 +429,7 @@
|
|||||||
"city": "City",
|
"city": "City",
|
||||||
"clear": "Clear",
|
"clear": "Clear",
|
||||||
"clear_all": "Clear all",
|
"clear_all": "Clear all",
|
||||||
|
"clear_all_recent_searches": "Clear all recent searches",
|
||||||
"clear_message": "Clear message",
|
"clear_message": "Clear message",
|
||||||
"clear_value": "Clear value",
|
"clear_value": "Clear value",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
@@ -557,6 +558,7 @@
|
|||||||
"error_adding_users_to_album": "Error adding users to album",
|
"error_adding_users_to_album": "Error adding users to album",
|
||||||
"error_deleting_shared_user": "Error deleting shared user",
|
"error_deleting_shared_user": "Error deleting shared user",
|
||||||
"error_downloading": "Error downloading {filename}",
|
"error_downloading": "Error downloading {filename}",
|
||||||
|
"error_hiding_buy_button": "Error hiding buy button",
|
||||||
"error_removing_assets_from_album": "Error removing assets from album, check console for more details",
|
"error_removing_assets_from_album": "Error removing assets from album, check console for more details",
|
||||||
"error_selecting_all_assets": "Error selecting all assets",
|
"error_selecting_all_assets": "Error selecting all assets",
|
||||||
"exclusion_pattern_already_exists": "This exclusion pattern already exists.",
|
"exclusion_pattern_already_exists": "This exclusion pattern already exists.",
|
||||||
@@ -568,6 +570,7 @@
|
|||||||
"failed_to_load_asset": "Failed to load asset",
|
"failed_to_load_asset": "Failed to load asset",
|
||||||
"failed_to_load_assets": "Failed to load assets",
|
"failed_to_load_assets": "Failed to load assets",
|
||||||
"failed_to_load_people": "Failed to load people",
|
"failed_to_load_people": "Failed to load people",
|
||||||
|
"failed_to_remove_product_key": "Failed to remove product key",
|
||||||
"failed_to_stack_assets": "Failed to stack assets",
|
"failed_to_stack_assets": "Failed to stack assets",
|
||||||
"failed_to_unstack_assets": "Failed to un-stack assets",
|
"failed_to_unstack_assets": "Failed to un-stack assets",
|
||||||
"import_path_already_exists": "This import path already exists.",
|
"import_path_already_exists": "This import path already exists.",
|
||||||
@@ -709,10 +712,16 @@
|
|||||||
"host": "Host",
|
"host": "Host",
|
||||||
"hour": "Hour",
|
"hour": "Hour",
|
||||||
"image": "Image",
|
"image": "Image",
|
||||||
"image_alt_text_date": "on {date}",
|
"image_alt_text_date": "{isVideo, select, true {Video} other {Image}} taken on {date}",
|
||||||
"image_alt_text_people": "{count, plural, =1 {with {person1}} =2 {with {person1} and {person2}} =3 {with {person1}, {person2}, and {person3}} other {with {person1}, {person2}, and {others, number} others}}",
|
"image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} taken with {person1} on {date}",
|
||||||
"image_alt_text_place": "in {city}, {country}",
|
"image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} taken with {person1} and {person2} on {date}",
|
||||||
"image_taken": "{isVideo, select, true {Video taken} other {Image taken}}",
|
"image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} taken with {person1}, {person2}, and {person3} on {date}",
|
||||||
|
"image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} taken with {person1}, {person2}, and {additionalCount, number} others on {date}",
|
||||||
|
"image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} taken in {city}, {country} on {date}",
|
||||||
|
"image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} taken in {city}, {country} with {person1} on {date}",
|
||||||
|
"image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} taken in {city}, {country} with {person1} and {person2} on {date}",
|
||||||
|
"image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} taken in {city}, {country} with {person1}, {person2}, and {person3} on {date}",
|
||||||
|
"image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} taken in {city}, {country} with {person1}, {person2}, and {additionalCount, number} others on {date}",
|
||||||
"immich_logo": "Immich Logo",
|
"immich_logo": "Immich Logo",
|
||||||
"immich_web_interface": "Immich Web Interface",
|
"immich_web_interface": "Immich Web Interface",
|
||||||
"import_from_json": "Import from JSON",
|
"import_from_json": "Import from JSON",
|
||||||
@@ -803,6 +812,7 @@
|
|||||||
"name": "Name",
|
"name": "Name",
|
||||||
"name_or_nickname": "Name or nickname",
|
"name_or_nickname": "Name or nickname",
|
||||||
"never": "Never",
|
"never": "Never",
|
||||||
|
"new_album": "New Album",
|
||||||
"new_api_key": "New API Key",
|
"new_api_key": "New API Key",
|
||||||
"new_password": "New password",
|
"new_password": "New password",
|
||||||
"new_person": "New person",
|
"new_person": "New person",
|
||||||
@@ -916,7 +926,7 @@
|
|||||||
"public_share": "Public Share",
|
"public_share": "Public Share",
|
||||||
"purchase_account_info": "Supporter",
|
"purchase_account_info": "Supporter",
|
||||||
"purchase_activated_subtitle": "Thank you for supporting Immich and open-source software",
|
"purchase_activated_subtitle": "Thank you for supporting Immich and open-source software",
|
||||||
"purchase_activated_time": "Activated on {date}",
|
"purchase_activated_time": "Activated on {date, date}",
|
||||||
"purchase_activated_title": "Your key has been successfully activated",
|
"purchase_activated_title": "Your key has been successfully activated",
|
||||||
"purchase_button_activate": "Activate",
|
"purchase_button_activate": "Activate",
|
||||||
"purchase_button_buy": "Buy",
|
"purchase_button_buy": "Buy",
|
||||||
@@ -925,7 +935,7 @@
|
|||||||
"purchase_button_reminder": "Remind me in 30 days",
|
"purchase_button_reminder": "Remind me in 30 days",
|
||||||
"purchase_button_remove_key": "Remove key",
|
"purchase_button_remove_key": "Remove key",
|
||||||
"purchase_button_select": "Select",
|
"purchase_button_select": "Select",
|
||||||
"purchase_failed_activation": "Failed to activate! Please check your email for the the correct product key!",
|
"purchase_failed_activation": "Failed to activate! Please check your email for the correct product key!",
|
||||||
"purchase_individual_description_1": "For an individual",
|
"purchase_individual_description_1": "For an individual",
|
||||||
"purchase_individual_description_2": "Supporter status",
|
"purchase_individual_description_2": "Supporter status",
|
||||||
"purchase_individual_title": "Individual",
|
"purchase_individual_title": "Individual",
|
||||||
@@ -938,6 +948,10 @@
|
|||||||
"purchase_panel_title": "Support the project",
|
"purchase_panel_title": "Support the project",
|
||||||
"purchase_per_server": "Per server",
|
"purchase_per_server": "Per server",
|
||||||
"purchase_per_user": "Per user",
|
"purchase_per_user": "Per user",
|
||||||
|
"purchase_remove_product_key": "Remove Product Key",
|
||||||
|
"purchase_remove_product_key_prompt": "Are you sure you want to remove the product key?",
|
||||||
|
"purchase_remove_server_product_key": "Remove Server product key",
|
||||||
|
"purchase_remove_server_product_key_prompt": "Are you sure you want to remove the Server product key?",
|
||||||
"purchase_server_description_1": "For the whole server",
|
"purchase_server_description_1": "For the whole server",
|
||||||
"purchase_server_description_2": "Supporter status",
|
"purchase_server_description_2": "Supporter status",
|
||||||
"purchase_server_title": "Server",
|
"purchase_server_title": "Server",
|
||||||
@@ -984,6 +998,7 @@
|
|||||||
"reset_password": "Reset password",
|
"reset_password": "Reset password",
|
||||||
"reset_people_visibility": "Reset people visibility",
|
"reset_people_visibility": "Reset people visibility",
|
||||||
"reset_to_default": "Reset to default",
|
"reset_to_default": "Reset to default",
|
||||||
|
"resolve_duplicates": "Resolve duplicates",
|
||||||
"resolved_all_duplicates": "Resolved all duplicates",
|
"resolved_all_duplicates": "Resolved all duplicates",
|
||||||
"restore": "Restore",
|
"restore": "Restore",
|
||||||
"restore_all": "Restore all",
|
"restore_all": "Restore all",
|
||||||
@@ -1028,6 +1043,7 @@
|
|||||||
"see_all_people": "See all people",
|
"see_all_people": "See all people",
|
||||||
"select_album_cover": "Select album cover",
|
"select_album_cover": "Select album cover",
|
||||||
"select_all": "Select all",
|
"select_all": "Select all",
|
||||||
|
"select_all_duplicates": "Select all duplicates",
|
||||||
"select_avatar_color": "Select avatar color",
|
"select_avatar_color": "Select avatar color",
|
||||||
"select_face": "Select face",
|
"select_face": "Select face",
|
||||||
"select_featured_photo": "Select featured photo",
|
"select_featured_photo": "Select featured photo",
|
||||||
@@ -1135,7 +1151,7 @@
|
|||||||
"total_usage": "Total usage",
|
"total_usage": "Total usage",
|
||||||
"trash": "Trash",
|
"trash": "Trash",
|
||||||
"trash_all": "Trash All",
|
"trash_all": "Trash All",
|
||||||
"trash_count": "Trash {count}",
|
"trash_count": "Trash {count, number}",
|
||||||
"trash_delete_asset": "Trash/Delete Asset",
|
"trash_delete_asset": "Trash/Delete Asset",
|
||||||
"trash_no_results_message": "Trashed photos and videos will show up here.",
|
"trash_no_results_message": "Trashed photos and videos will show up here.",
|
||||||
"trashed_items_will_be_permanently_deleted_after": "Trashed items will be permanently deleted after {days, plural, one {# day} other {# days}}.",
|
"trashed_items_will_be_permanently_deleted_after": "Trashed items will be permanently deleted after {days, plural, one {# day} other {# days}}.",
|
||||||
@@ -1153,6 +1169,7 @@
|
|||||||
"unnamed_share": "Unnamed Share",
|
"unnamed_share": "Unnamed Share",
|
||||||
"unsaved_change": "Unsaved change",
|
"unsaved_change": "Unsaved change",
|
||||||
"unselect_all": "Unselect all",
|
"unselect_all": "Unselect all",
|
||||||
|
"unselect_all_duplicates": "Unselect all duplicates",
|
||||||
"unstack": "Un-stack",
|
"unstack": "Un-stack",
|
||||||
"unstacked_assets_count": "Un-stacked {count, plural, one {# asset} other {# assets}}",
|
"unstacked_assets_count": "Un-stacked {count, plural, one {# asset} other {# assets}}",
|
||||||
"untracked_files": "Untracked files",
|
"untracked_files": "Untracked files",
|
||||||
@@ -1162,7 +1179,7 @@
|
|||||||
"upload": "Upload",
|
"upload": "Upload",
|
||||||
"upload_concurrency": "Upload concurrency",
|
"upload_concurrency": "Upload concurrency",
|
||||||
"upload_errors": "Upload completed with {count, plural, one {# error} other {# errors}}, refresh the page to see new upload assets.",
|
"upload_errors": "Upload completed with {count, plural, one {# error} other {# errors}}, refresh the page to see new upload assets.",
|
||||||
"upload_progress": "Remaining {remaining} - Processed {processed}/{total}",
|
"upload_progress": "Remaining {remaining, number} - Processed {processed, number}/{total, number}",
|
||||||
"upload_skipped_duplicates": "Skipped {count, plural, one {# duplicate asset} other {# duplicate assets}}",
|
"upload_skipped_duplicates": "Skipped {count, plural, one {# duplicate asset} other {# duplicate assets}}",
|
||||||
"upload_status_duplicates": "Duplicates",
|
"upload_status_duplicates": "Duplicates",
|
||||||
"upload_status_errors": "Errors",
|
"upload_status_errors": "Errors",
|
||||||
|
|||||||
@@ -98,7 +98,7 @@
|
|||||||
"machine_learning_clip_model_description": "El nombre de un modelo CLIP listado <link>aquí</link>. Tenga en cuenta que debe volver a ejecutar el trabajo 'Smart Search' para todas las imágenes al cambiar un modelo.",
|
"machine_learning_clip_model_description": "El nombre de un modelo CLIP listado <link>aquí</link>. Tenga en cuenta que debe volver a ejecutar el trabajo 'Smart Search' para todas las imágenes al cambiar un modelo.",
|
||||||
"machine_learning_duplicate_detection": "Detección duplicados",
|
"machine_learning_duplicate_detection": "Detección duplicados",
|
||||||
"machine_learning_duplicate_detection_enabled": "Habilitar detección de duplicados",
|
"machine_learning_duplicate_detection_enabled": "Habilitar detección de duplicados",
|
||||||
"machine_learning_duplicate_detection_enabled_description": "Si está deshabilitado, se seguirán deduplicando assets exactamente idénticos.",
|
"machine_learning_duplicate_detection_enabled_description": "Si está deshabilitado, los activos exactamente idénticos seguirán siendo eliminados.",
|
||||||
"machine_learning_duplicate_detection_setting_description": "Utilice incrustaciones de CLIP para encontrar posibles duplicados",
|
"machine_learning_duplicate_detection_setting_description": "Utilice incrustaciones de CLIP para encontrar posibles duplicados",
|
||||||
"machine_learning_enabled": "Habilitar aprendizaje automático",
|
"machine_learning_enabled": "Habilitar aprendizaje automático",
|
||||||
"machine_learning_enabled_description": "Si está deshabilitada, todas las funciones de ML se deshabilitarán independientemente de la configuración a continuación.",
|
"machine_learning_enabled_description": "Si está deshabilitada, todas las funciones de ML se deshabilitarán independientemente de la configuración a continuación.",
|
||||||
@@ -410,7 +410,7 @@
|
|||||||
"bulk_delete_duplicates_confirmation": "¿Estás seguro de que deseas eliminar de forma masiva {count, plural, one {# duplicate asset} other {# duplicate assets}}? Esto mantendrá el activo más grande de cada grupo y eliminará permanentemente todos los demás duplicados. ¡Esta acción no se puede deshacer!",
|
"bulk_delete_duplicates_confirmation": "¿Estás seguro de que deseas eliminar de forma masiva {count, plural, one {# duplicate asset} other {# duplicate assets}}? Esto mantendrá el activo más grande de cada grupo y eliminará permanentemente todos los demás duplicados. ¡Esta acción no se puede deshacer!",
|
||||||
"bulk_keep_duplicates_confirmation": "¿Estas seguro de que desea mantener {count, plural, one {# duplicate asset} other {# duplicate assets}} archivos duplicados? Esto resolverá todos los grupos duplicados sin borrar nada.",
|
"bulk_keep_duplicates_confirmation": "¿Estas seguro de que desea mantener {count, plural, one {# duplicate asset} other {# duplicate assets}} archivos duplicados? Esto resolverá todos los grupos duplicados sin borrar nada.",
|
||||||
"bulk_trash_duplicates_confirmation": "¿Estas seguro de que desea eliminar masivamente {count, plural, one {# duplicate asset} other {# duplicate assets}} archivos duplicados? Esto mantendrá el archivo más grande de cada grupo y eliminará todos los demás duplicados.",
|
"bulk_trash_duplicates_confirmation": "¿Estas seguro de que desea eliminar masivamente {count, plural, one {# duplicate asset} other {# duplicate assets}} archivos duplicados? Esto mantendrá el archivo más grande de cada grupo y eliminará todos los demás duplicados.",
|
||||||
"buy": "Comprar licencia",
|
"buy": "Comprar Immich",
|
||||||
"camera": "Cámara",
|
"camera": "Cámara",
|
||||||
"camera_brand": "Fabricante de cámara",
|
"camera_brand": "Fabricante de cámara",
|
||||||
"camera_model": "Modelo de cámara",
|
"camera_model": "Modelo de cámara",
|
||||||
@@ -438,6 +438,7 @@
|
|||||||
"city": "Ciudad",
|
"city": "Ciudad",
|
||||||
"clear": "Limpiar",
|
"clear": "Limpiar",
|
||||||
"clear_all": "Limpiar todo",
|
"clear_all": "Limpiar todo",
|
||||||
|
"clear_all_recent_searches": "Borrar búsquedas recientes",
|
||||||
"clear_message": "Limpiar mensaje",
|
"clear_message": "Limpiar mensaje",
|
||||||
"clear_value": "Limpiar valor",
|
"clear_value": "Limpiar valor",
|
||||||
"close": "Cerrar",
|
"close": "Cerrar",
|
||||||
@@ -576,6 +577,7 @@
|
|||||||
"error_adding_users_to_album": "Error al añadir usuarios al álbum",
|
"error_adding_users_to_album": "Error al añadir usuarios al álbum",
|
||||||
"error_deleting_shared_user": "Error al eliminar usuario compartido",
|
"error_deleting_shared_user": "Error al eliminar usuario compartido",
|
||||||
"error_downloading": "Error al descargar {filename}",
|
"error_downloading": "Error al descargar {filename}",
|
||||||
|
"error_hiding_buy_button": "Error al ocultar el botón de compra",
|
||||||
"error_removing_assets_from_album": "Error al eliminar archivos del álbum; consulte la consola para obtener más detalles",
|
"error_removing_assets_from_album": "Error al eliminar archivos del álbum; consulte la consola para obtener más detalles",
|
||||||
"error_selecting_all_assets": "Error al seleccionar todos los archivos",
|
"error_selecting_all_assets": "Error al seleccionar todos los archivos",
|
||||||
"exclusion_pattern_already_exists": "Este patrón de exclusión ya existe.",
|
"exclusion_pattern_already_exists": "Este patrón de exclusión ya existe.",
|
||||||
@@ -587,6 +589,7 @@
|
|||||||
"failed_to_load_asset": "Error al cargar el elemento",
|
"failed_to_load_asset": "Error al cargar el elemento",
|
||||||
"failed_to_load_assets": "Error al cargar los elementos",
|
"failed_to_load_assets": "Error al cargar los elementos",
|
||||||
"failed_to_load_people": "Error al cargar a los usuarios",
|
"failed_to_load_people": "Error al cargar a los usuarios",
|
||||||
|
"failed_to_remove_product_key": "No se pudo eliminar la clave del producto",
|
||||||
"failed_to_stack_assets": "No se pudieron agrupar los archivos",
|
"failed_to_stack_assets": "No se pudieron agrupar los archivos",
|
||||||
"failed_to_unstack_assets": "Error al desagrupar los archivos",
|
"failed_to_unstack_assets": "Error al desagrupar los archivos",
|
||||||
"import_path_already_exists": "Esta ruta de importación ya existe.",
|
"import_path_already_exists": "Esta ruta de importación ya existe.",
|
||||||
@@ -740,7 +743,16 @@
|
|||||||
"host": "Host",
|
"host": "Host",
|
||||||
"hour": "Hora",
|
"hour": "Hora",
|
||||||
"image": "Imagen",
|
"image": "Imagen",
|
||||||
"image_alt_text_date": "El {date}",
|
"image_alt_text_date": "{isVideo, select, true {Video} other {Image}} tomada el {date}",
|
||||||
|
"image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} tomada con {person1} el {date}",
|
||||||
|
"image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} tomada con {person1} y {person2} el {date}",
|
||||||
|
"image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} tomada con {person1}, {person2}, y {person3} el {date}",
|
||||||
|
"image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} tomada con {person1}, {person2}, y {additionalCount, number} más el {date}",
|
||||||
|
"image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} tomada en {city}, {country} el {date}",
|
||||||
|
"image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} tomada en {city}, {country} con {person1} el {date}",
|
||||||
|
"image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} tomada en {city}, {country} con {person1} y {person2} el {date}",
|
||||||
|
"image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} tomada en {city}, {country} con {person1}, {person2}, y {person3} el {date}",
|
||||||
|
"image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} tomada en {city}, {country} con {person1}, {person2}, y {additionalCount, number} más el {date}",
|
||||||
"image_alt_text_people": "{count, plural, =1 {with {person1}} =2 {with {person1} and {person2}} =3 {with {person1}, {person2}, and {person3}} other {with {person1}, {person2}, y {others, number} others}}",
|
"image_alt_text_people": "{count, plural, =1 {with {person1}} =2 {with {person1} and {person2}} =3 {with {person1}, {person2}, and {person3}} other {with {person1}, {person2}, y {others, number} others}}",
|
||||||
"image_alt_text_place": "En {city}, {country}",
|
"image_alt_text_place": "En {city}, {country}",
|
||||||
"image_taken": "{isVideo, select, true {Video taken} other {Image taken}}",
|
"image_taken": "{isVideo, select, true {Video taken} other {Image taken}}",
|
||||||
@@ -861,6 +873,7 @@
|
|||||||
"name": "Nombre",
|
"name": "Nombre",
|
||||||
"name_or_nickname": "Nombre o apodo",
|
"name_or_nickname": "Nombre o apodo",
|
||||||
"never": "nunca",
|
"never": "nunca",
|
||||||
|
"new_album": "Nuevo álbum",
|
||||||
"new_api_key": "Nueva clave API",
|
"new_api_key": "Nueva clave API",
|
||||||
"new_password": "Nueva contraseña",
|
"new_password": "Nueva contraseña",
|
||||||
"new_person": "Nueva persona",
|
"new_person": "Nueva persona",
|
||||||
@@ -975,6 +988,38 @@
|
|||||||
"profile_picture_set": "Conjunto de imágenes de perfil.",
|
"profile_picture_set": "Conjunto de imágenes de perfil.",
|
||||||
"public_album": "Álbum público",
|
"public_album": "Álbum público",
|
||||||
"public_share": "Compartir públicamente",
|
"public_share": "Compartir públicamente",
|
||||||
|
"purchase_account_info": "Soporte",
|
||||||
|
"purchase_activated_subtitle": "Gracias por apoyar a Immich y al software de código abierto",
|
||||||
|
"purchase_activated_time": "Activado el {date, date}",
|
||||||
|
"purchase_activated_title": "Su clave ha sido activada correctamente",
|
||||||
|
"purchase_button_activate": "Activar",
|
||||||
|
"purchase_button_buy": "Comprar",
|
||||||
|
"purchase_button_buy_immich": "Comprar Immich",
|
||||||
|
"purchase_button_never_show_again": "No volver a mostrar",
|
||||||
|
"purchase_button_reminder": "Recuérdamelo en 30 días",
|
||||||
|
"purchase_button_remove_key": "Quitar clave",
|
||||||
|
"purchase_button_select": "Seleccionar",
|
||||||
|
"purchase_failed_activation": "¡Error al activar! ¡Por favor, revisa tu correo electrónico para obtener la clave del producto correcta!",
|
||||||
|
"purchase_individual_description_1": "Para un usuario",
|
||||||
|
"purchase_individual_description_2": "Estado de soporte",
|
||||||
|
"purchase_individual_title": "Individual",
|
||||||
|
"purchase_input_suggestion": "¿Tiene una clave de producto? Introdúzcala a continuación",
|
||||||
|
"purchase_license_subtitle": "Compre Immich para apoyar el desarrollo continuo del servicio",
|
||||||
|
"purchase_lifetime_description": "Compra de por vida",
|
||||||
|
"purchase_option_title": "OPCIONES DE COMPRA",
|
||||||
|
"purchase_panel_info_1": "Desarrollar Immich requiere mucho tiempo y esfuerzo, y contamos con ingenieros a tiempo completo que trabajan en él para que sea lo mejor posible. Nuestra misión es que el software de código abierto y las prácticas comerciales éticas se conviertan en una fuente de ingresos sostenibles para los desarrolladores y crear un ecosistema que respete la privacidad con alternativas reales a los servicios en la nube de pago.",
|
||||||
|
"purchase_panel_info_2": "Como nos comprometemos a no añadir pagos, esta compra no le otorgará ninguna característica adicional en Immich. Confiamos en que los usuarios como usted apoyen el desarrollo continuo de Immich.",
|
||||||
|
"purchase_panel_title": "Apoya el proyecto",
|
||||||
|
"purchase_per_server": "Por servidor",
|
||||||
|
"purchase_per_user": "Por usuario",
|
||||||
|
"purchase_remove_product_key": "Eliminar clave de producto",
|
||||||
|
"purchase_remove_product_key_prompt": "¿Está seguro de que desea eliminar la clave del producto?",
|
||||||
|
"purchase_remove_server_product_key": "Eliminar la clave de producto del servidor",
|
||||||
|
"purchase_remove_server_product_key_prompt": "¿Está seguro de que desea eliminar la clave de producto del servidor?",
|
||||||
|
"purchase_server_description_1": "Para todo el servidor",
|
||||||
|
"purchase_server_description_2": "Estado del soporte",
|
||||||
|
"purchase_server_title": "Servidor",
|
||||||
|
"purchase_settings_server_activated": "La clave del producto del servidor la administra el administrador",
|
||||||
"range": "",
|
"range": "",
|
||||||
"raw": "",
|
"raw": "",
|
||||||
"reaction_options": "Opciones de reacción",
|
"reaction_options": "Opciones de reacción",
|
||||||
@@ -1020,6 +1065,7 @@
|
|||||||
"reset_people_visibility": "Restablecer la visibilidad de las personas",
|
"reset_people_visibility": "Restablecer la visibilidad de las personas",
|
||||||
"reset_settings_to_default": "",
|
"reset_settings_to_default": "",
|
||||||
"reset_to_default": "Restablecer los valores predeterminados",
|
"reset_to_default": "Restablecer los valores predeterminados",
|
||||||
|
"resolve_duplicates": "Resolver duplicados",
|
||||||
"resolved_all_duplicates": "Todos los duplicados resueltos",
|
"resolved_all_duplicates": "Todos los duplicados resueltos",
|
||||||
"restore": "Restaurar",
|
"restore": "Restaurar",
|
||||||
"restore_all": "Restaurar todo",
|
"restore_all": "Restaurar todo",
|
||||||
@@ -1064,6 +1110,7 @@
|
|||||||
"see_all_people": "Ver todas las personas",
|
"see_all_people": "Ver todas las personas",
|
||||||
"select_album_cover": "Seleccionar portada del álbum",
|
"select_album_cover": "Seleccionar portada del álbum",
|
||||||
"select_all": "Seleccionar todo",
|
"select_all": "Seleccionar todo",
|
||||||
|
"select_all_duplicates": "Seleccionar todos los duplicados",
|
||||||
"select_avatar_color": "Seleccionar color del avatar",
|
"select_avatar_color": "Seleccionar color del avatar",
|
||||||
"select_face": "Seleccionar cara",
|
"select_face": "Seleccionar cara",
|
||||||
"select_featured_photo": "Seleccionar foto principal",
|
"select_featured_photo": "Seleccionar foto principal",
|
||||||
@@ -1118,6 +1165,8 @@
|
|||||||
"show_person_options": "Mostrar opciones de la persona",
|
"show_person_options": "Mostrar opciones de la persona",
|
||||||
"show_progress_bar": "Mostrar barra de progreso",
|
"show_progress_bar": "Mostrar barra de progreso",
|
||||||
"show_search_options": "Mostrar opciones de búsqueda",
|
"show_search_options": "Mostrar opciones de búsqueda",
|
||||||
|
"show_supporter_badge": "Insignia de colaborador",
|
||||||
|
"show_supporter_badge_description": "Mostrar una insignia de colaborador",
|
||||||
"shuffle": "Modo aleatorio",
|
"shuffle": "Modo aleatorio",
|
||||||
"sign_out": "Salir",
|
"sign_out": "Salir",
|
||||||
"sign_up": "Registrarse",
|
"sign_up": "Registrarse",
|
||||||
@@ -1191,6 +1240,7 @@
|
|||||||
"unnamed_share": "Compartido sin nombre",
|
"unnamed_share": "Compartido sin nombre",
|
||||||
"unsaved_change": "Cambio no guardado",
|
"unsaved_change": "Cambio no guardado",
|
||||||
"unselect_all": "Limpiar selección",
|
"unselect_all": "Limpiar selección",
|
||||||
|
"unselect_all_duplicates": "Deseleccionar todos los duplicados",
|
||||||
"unstack": "Desapilar",
|
"unstack": "Desapilar",
|
||||||
"unstacked_assets_count": "Sin apilar {count, plural, one {# asset} other {# assets}}",
|
"unstacked_assets_count": "Sin apilar {count, plural, one {# asset} other {# assets}}",
|
||||||
"untracked_files": "Archivos no monitorizados",
|
"untracked_files": "Archivos no monitorizados",
|
||||||
@@ -1214,6 +1264,8 @@
|
|||||||
"user_license_settings": "Licencia",
|
"user_license_settings": "Licencia",
|
||||||
"user_license_settings_description": "Gestionar tu licencia",
|
"user_license_settings_description": "Gestionar tu licencia",
|
||||||
"user_liked": "{user} le gustó {type, select, photo {this photo} video {this video} asset {this asset} other {it}}",
|
"user_liked": "{user} le gustó {type, select, photo {this photo} video {this video} asset {this asset} other {it}}",
|
||||||
|
"user_purchase_settings": "Compra",
|
||||||
|
"user_purchase_settings_description": "Gestiona tu compra",
|
||||||
"user_role_set": "Carbiar {user} a {role}",
|
"user_role_set": "Carbiar {user} a {role}",
|
||||||
"user_usage_detail": "Detalle del uso del usuario",
|
"user_usage_detail": "Detalle del uso del usuario",
|
||||||
"username": "Nombre de usuario",
|
"username": "Nombre de usuario",
|
||||||
|
|||||||
@@ -314,7 +314,7 @@
|
|||||||
"user_delete_immediately_checkbox": "Mise en file d'attente d'un utilisateur et de médias en vue d'une suppression immédiate",
|
"user_delete_immediately_checkbox": "Mise en file d'attente d'un utilisateur et de médias en vue d'une suppression immédiate",
|
||||||
"user_management": "Gestion des utilisateurs",
|
"user_management": "Gestion des utilisateurs",
|
||||||
"user_password_has_been_reset": "Le mot de passe de l'utilisateur a été réinitialisé :",
|
"user_password_has_been_reset": "Le mot de passe de l'utilisateur a été réinitialisé :",
|
||||||
"user_password_reset_description": "Veuillez saisir un mot de passe temporaire à l'utilisateur et informez le qu'il devra le changer à sa première connexion.",
|
"user_password_reset_description": "Veuillez saisir un mot de passe temporaire à l'utilisateur et informez-le qu'il devra le changer à sa première connexion.",
|
||||||
"user_restore_description": "Le compte de <b>{user}</b> sera restauré.",
|
"user_restore_description": "Le compte de <b>{user}</b> sera restauré.",
|
||||||
"user_restore_scheduled_removal": "Restaurer l'utilisateur - suppression programmée le {date, date, long}",
|
"user_restore_scheduled_removal": "Restaurer l'utilisateur - suppression programmée le {date, date, long}",
|
||||||
"user_settings": "Paramètres utilisateur",
|
"user_settings": "Paramètres utilisateur",
|
||||||
@@ -410,7 +410,7 @@
|
|||||||
"bulk_delete_duplicates_confirmation": "Êtes-vous sûr de vouloir supprimer {count, plural, one {# doublon} other {# doublons}} ? Cette opération conservera le plus grand média de chaque groupe et supprimera définitivement tous les autres doublons. Vous ne pouvez pas annuler cette action !",
|
"bulk_delete_duplicates_confirmation": "Êtes-vous sûr de vouloir supprimer {count, plural, one {# doublon} other {# doublons}} ? Cette opération conservera le plus grand média de chaque groupe et supprimera définitivement tous les autres doublons. Vous ne pouvez pas annuler cette action !",
|
||||||
"bulk_keep_duplicates_confirmation": "Êtes-vous sûr de vouloir conserver {count, plural, one {# doublon} other {# doublons}} ? Cela résoudra tous les groupes de doublons sans rien supprimer.",
|
"bulk_keep_duplicates_confirmation": "Êtes-vous sûr de vouloir conserver {count, plural, one {# doublon} other {# doublons}} ? Cela résoudra tous les groupes de doublons sans rien supprimer.",
|
||||||
"bulk_trash_duplicates_confirmation": "Êtes-vous sûr de vouloir mettre à la corbeille {count, plural, one {# doublon} other {# doublons}} ? Cette opération permet de conserver le plus grand média de chaque groupe et de mettre à la corbeille tous les autres doublons.",
|
"bulk_trash_duplicates_confirmation": "Êtes-vous sûr de vouloir mettre à la corbeille {count, plural, one {# doublon} other {# doublons}} ? Cette opération permet de conserver le plus grand média de chaque groupe et de mettre à la corbeille tous les autres doublons.",
|
||||||
"buy": "Acheter une licence",
|
"buy": "Acheter Immich",
|
||||||
"camera": "Appareil photo",
|
"camera": "Appareil photo",
|
||||||
"camera_brand": "Marque d'appareil",
|
"camera_brand": "Marque d'appareil",
|
||||||
"camera_model": "Modèle d'appareil",
|
"camera_model": "Modèle d'appareil",
|
||||||
@@ -426,7 +426,7 @@
|
|||||||
"change_date": "Changer la date",
|
"change_date": "Changer la date",
|
||||||
"change_expiration_time": "Modifier le délai d'expiration",
|
"change_expiration_time": "Modifier le délai d'expiration",
|
||||||
"change_location": "Changer la localisation",
|
"change_location": "Changer la localisation",
|
||||||
"change_name": "Changer le nom",
|
"change_name": "Modifier/Définir le nom",
|
||||||
"change_name_successfully": "Nouveau nom enregistré",
|
"change_name_successfully": "Nouveau nom enregistré",
|
||||||
"change_password": "Modifier le mot de passe",
|
"change_password": "Modifier le mot de passe",
|
||||||
"change_password_description": "C'est la première fois que vous vous connectez ou une demande a été faite pour changer votre mot de passe. Veuillez entrer le nouveau mot de passe ci-dessous.",
|
"change_password_description": "C'est la première fois que vous vous connectez ou une demande a été faite pour changer votre mot de passe. Veuillez entrer le nouveau mot de passe ci-dessous.",
|
||||||
@@ -438,6 +438,7 @@
|
|||||||
"city": "Ville",
|
"city": "Ville",
|
||||||
"clear": "Effacer",
|
"clear": "Effacer",
|
||||||
"clear_all": "Effacer tout",
|
"clear_all": "Effacer tout",
|
||||||
|
"clear_all_recent_searches": "Supprimer les recherches récentes",
|
||||||
"clear_message": "Effacer le message",
|
"clear_message": "Effacer le message",
|
||||||
"clear_value": "Effacer la valeur",
|
"clear_value": "Effacer la valeur",
|
||||||
"close": "Fermer",
|
"close": "Fermer",
|
||||||
@@ -576,6 +577,7 @@
|
|||||||
"error_adding_users_to_album": "Erreur lors de l'ajout d'utilisateurs à l'album",
|
"error_adding_users_to_album": "Erreur lors de l'ajout d'utilisateurs à l'album",
|
||||||
"error_deleting_shared_user": "Erreur lors de la suppression l'utilisateur partagé",
|
"error_deleting_shared_user": "Erreur lors de la suppression l'utilisateur partagé",
|
||||||
"error_downloading": "Erreur lors du téléchargement de {filename}",
|
"error_downloading": "Erreur lors du téléchargement de {filename}",
|
||||||
|
"error_hiding_buy_button": "Impossible de masquer le bouton d'achat",
|
||||||
"error_removing_assets_from_album": "Erreur lors de la suppression des médias de l'album, vérifier la console pour plus de détails",
|
"error_removing_assets_from_album": "Erreur lors de la suppression des médias de l'album, vérifier la console pour plus de détails",
|
||||||
"error_selecting_all_assets": "Erreur lors de la sélection de tous les médias",
|
"error_selecting_all_assets": "Erreur lors de la sélection de tous les médias",
|
||||||
"exclusion_pattern_already_exists": "Ce modèle d'exclusion existe déjà.",
|
"exclusion_pattern_already_exists": "Ce modèle d'exclusion existe déjà.",
|
||||||
@@ -584,8 +586,10 @@
|
|||||||
"failed_to_create_shared_link": "Impossible de créer le lien partagé",
|
"failed_to_create_shared_link": "Impossible de créer le lien partagé",
|
||||||
"failed_to_edit_shared_link": "Impossible de modifier le lien partagé",
|
"failed_to_edit_shared_link": "Impossible de modifier le lien partagé",
|
||||||
"failed_to_get_people": "Impossible d'obtenir les personnes",
|
"failed_to_get_people": "Impossible d'obtenir les personnes",
|
||||||
"failed_to_load_asset": "Échec du chargement du média",
|
"failed_to_load_asset": "Impossible de charger le média",
|
||||||
"failed_to_load_assets": "Échec du chargement des médias",
|
"failed_to_load_assets": "Impossible de charger les médias",
|
||||||
|
"failed_to_load_people": "Impossible de charger les personnes",
|
||||||
|
"failed_to_remove_product_key": "Échec de suppression de la clé du produit",
|
||||||
"failed_to_stack_assets": "Impossible d'empiler les médias",
|
"failed_to_stack_assets": "Impossible d'empiler les médias",
|
||||||
"failed_to_unstack_assets": "Impossible de dépiler les médias",
|
"failed_to_unstack_assets": "Impossible de dépiler les médias",
|
||||||
"import_path_already_exists": "Ce chemin d'import existe déjà.",
|
"import_path_already_exists": "Ce chemin d'import existe déjà.",
|
||||||
@@ -739,7 +743,16 @@
|
|||||||
"host": "Hôte",
|
"host": "Hôte",
|
||||||
"hour": "Heure",
|
"hour": "Heure",
|
||||||
"image": "Image",
|
"image": "Image",
|
||||||
"image_alt_text_date": "à la {date}",
|
"image_alt_text_date": "{isVideo, select, true {Video} other {Image}} prise le {date}",
|
||||||
|
"image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} prise avec {person1} le {date}",
|
||||||
|
"image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} prise avec {person1} et {person2} le {date}",
|
||||||
|
"image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} prise avec {person1}, {person2}, et {person3} le {date}",
|
||||||
|
"image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} prise avec {person1}, {person2} et {additionalCount, number} autres personnes le {date}",
|
||||||
|
"image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} prise à {city}, {country} le {date}",
|
||||||
|
"image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} prise à {city}, {country} avec {person1} le {date}",
|
||||||
|
"image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} prise à {city}, {country} avec {person1} et {person2} le {date}",
|
||||||
|
"image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} prise à {city}, {country} avec {person1}, {person2}, et {person3} le {date}",
|
||||||
|
"image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} prise à {city}, {country} avec {person1}, {person2} et {additionalCount, number} autres personnes le {date}",
|
||||||
"image_alt_text_people": "{count, plural, =1 {with {person1}} =2 {with {person1} and {person2}} =3 {with {person1}, {person2}, and {person3}} other {with {person1}, {person2}, and {others, number} others}}",
|
"image_alt_text_people": "{count, plural, =1 {with {person1}} =2 {with {person1} and {person2}} =3 {with {person1}, {person2}, and {person3}} other {with {person1}, {person2}, and {others, number} others}}",
|
||||||
"image_alt_text_place": "à {city}, {country}",
|
"image_alt_text_place": "à {city}, {country}",
|
||||||
"image_taken": "{isVideo, select, true {Video prise} other {Image prise}}",
|
"image_taken": "{isVideo, select, true {Video prise} other {Image prise}}",
|
||||||
@@ -974,6 +987,38 @@
|
|||||||
"profile_picture_set": "Photo de profil définie.",
|
"profile_picture_set": "Photo de profil définie.",
|
||||||
"public_album": "Album public",
|
"public_album": "Album public",
|
||||||
"public_share": "Partage public",
|
"public_share": "Partage public",
|
||||||
|
"purchase_account_info": "Contributeur",
|
||||||
|
"purchase_activated_subtitle": "Merci d'avoir apporté votre soutien à Immich et les logiciels open source",
|
||||||
|
"purchase_activated_time": "Activé le {date, date}",
|
||||||
|
"purchase_activated_title": "Votre clé a été activée avec succès",
|
||||||
|
"purchase_button_activate": "Activer",
|
||||||
|
"purchase_button_buy": "Acheter",
|
||||||
|
"purchase_button_buy_immich": "Acheter Immich",
|
||||||
|
"purchase_button_never_show_again": "Ne plus l'afficher",
|
||||||
|
"purchase_button_reminder": "Me le rappeler dans 30 jours",
|
||||||
|
"purchase_button_remove_key": "Supprimer la clé",
|
||||||
|
"purchase_button_select": "Sélectionner",
|
||||||
|
"purchase_failed_activation": "Erreur à l'activation. Merci de vérifier votre courriel pour confirmer la clé du produit !",
|
||||||
|
"purchase_individual_description_1": "Pour un utilisateur",
|
||||||
|
"purchase_individual_description_2": "Statut de contributeur",
|
||||||
|
"purchase_individual_title": "Utilisateur",
|
||||||
|
"purchase_input_suggestion": "Si vous avez déjà une clé de produit, renseignez-la ci-dessous",
|
||||||
|
"purchase_license_subtitle": "Acheter Immich pour soutenir le développement de ce service",
|
||||||
|
"purchase_lifetime_description": "Achat à vie",
|
||||||
|
"purchase_option_title": "OPTIONS D'ACHAT",
|
||||||
|
"purchase_panel_info_1": "Développer Immich nécessite du temps et de l'énergie, et nous avons des ingénieurs qui travaillent à plein temps pour en faire le meilleur produit possible. Notre mission est de générer, pour les logiciels open source et les pratiques de travail éthique, une source de revenus suffisante pour les développeurs et de créer un écosystème respectueux de la vie privée grâce a des alternatives crédibles aux services cloud peu scrupuleux.",
|
||||||
|
"purchase_panel_info_2": "Comme nous sommes engagés à ne pas ajouter de fonctionnalités payantes, cet achat ne vous donnera pas accès à des éléments supplémentaires dans Immich. Nous dépendons d'utilisateurs comme vous pour soutenir le développement actif d'Immich.",
|
||||||
|
"purchase_panel_title": "Soutenir le projet",
|
||||||
|
"purchase_per_server": "Par serveur",
|
||||||
|
"purchase_per_user": "Par utilisateur",
|
||||||
|
"purchase_remove_product_key": "Supprimer la clé du produit",
|
||||||
|
"purchase_remove_product_key_prompt": "Êtes-vous sûr de vouloir supprimer la clé du produit ?",
|
||||||
|
"purchase_remove_server_product_key": "Supprimer la clé du produit pour le Serveur",
|
||||||
|
"purchase_remove_server_product_key_prompt": "Êtes-vous sûr de vouloir supprimer la clé du produit pour le serveur ?",
|
||||||
|
"purchase_server_description_1": "Pour l'ensemble du serveur",
|
||||||
|
"purchase_server_description_2": "Statut de contributeur",
|
||||||
|
"purchase_server_title": "Serveur",
|
||||||
|
"purchase_settings_server_activated": "La clé du produit pour le Serveur est gérée par l'administrateur",
|
||||||
"range": "",
|
"range": "",
|
||||||
"raw": "",
|
"raw": "",
|
||||||
"reaction_options": "Options de réaction",
|
"reaction_options": "Options de réaction",
|
||||||
@@ -1019,6 +1064,7 @@
|
|||||||
"reset_people_visibility": "Réinitialiser la visibilité des personnes",
|
"reset_people_visibility": "Réinitialiser la visibilité des personnes",
|
||||||
"reset_settings_to_default": "",
|
"reset_settings_to_default": "",
|
||||||
"reset_to_default": "Rétablir les valeurs par défaut",
|
"reset_to_default": "Rétablir les valeurs par défaut",
|
||||||
|
"resolve_duplicates": "Traiter les doublons",
|
||||||
"resolved_all_duplicates": "Résolution de tous les doublons",
|
"resolved_all_duplicates": "Résolution de tous les doublons",
|
||||||
"restore": "Restaurer",
|
"restore": "Restaurer",
|
||||||
"restore_all": "Tout restaurer",
|
"restore_all": "Tout restaurer",
|
||||||
@@ -1063,9 +1109,10 @@
|
|||||||
"see_all_people": "Voir toutes les personnes",
|
"see_all_people": "Voir toutes les personnes",
|
||||||
"select_album_cover": "Sélectionner la couverture d'album",
|
"select_album_cover": "Sélectionner la couverture d'album",
|
||||||
"select_all": "Tout sélectionner",
|
"select_all": "Tout sélectionner",
|
||||||
|
"select_all_duplicates": "Sélectionner tous les doublons",
|
||||||
"select_avatar_color": "Sélectionner la couleur de l'avatar",
|
"select_avatar_color": "Sélectionner la couleur de l'avatar",
|
||||||
"select_face": "Sélectionner le visage",
|
"select_face": "Sélectionner le visage",
|
||||||
"select_featured_photo": "Sélectionner la photo de la personne",
|
"select_featured_photo": "Sélectionner la photo de profil de cette personne",
|
||||||
"select_from_computer": "Sélectionner à partir de l'ordinateur",
|
"select_from_computer": "Sélectionner à partir de l'ordinateur",
|
||||||
"select_keep_all": "Choisir de tout garder",
|
"select_keep_all": "Choisir de tout garder",
|
||||||
"select_library_owner": "Sélectionner le propriétaire de la bibliothèque",
|
"select_library_owner": "Sélectionner le propriétaire de la bibliothèque",
|
||||||
@@ -1117,6 +1164,8 @@
|
|||||||
"show_person_options": "Afficher les options de personnes",
|
"show_person_options": "Afficher les options de personnes",
|
||||||
"show_progress_bar": "Afficher la barre de progression",
|
"show_progress_bar": "Afficher la barre de progression",
|
||||||
"show_search_options": "Afficher les options de recherche",
|
"show_search_options": "Afficher les options de recherche",
|
||||||
|
"show_supporter_badge": "Badge de contributeur",
|
||||||
|
"show_supporter_badge_description": "Afficher le badge de contributeur",
|
||||||
"shuffle": "Mélanger",
|
"shuffle": "Mélanger",
|
||||||
"sign_out": "Déconnexion",
|
"sign_out": "Déconnexion",
|
||||||
"sign_up": "S'enregistrer",
|
"sign_up": "S'enregistrer",
|
||||||
@@ -1190,6 +1239,7 @@
|
|||||||
"unnamed_share": "Partage sans nom",
|
"unnamed_share": "Partage sans nom",
|
||||||
"unsaved_change": "Modification non enregistrée",
|
"unsaved_change": "Modification non enregistrée",
|
||||||
"unselect_all": "Annuler la sélection",
|
"unselect_all": "Annuler la sélection",
|
||||||
|
"unselect_all_duplicates": "Désélectionner tous les doublons",
|
||||||
"unstack": "Désempiler",
|
"unstack": "Désempiler",
|
||||||
"unstacked_assets_count": "{count, plural, one {# média dépilé} other {# médias dépilés}}",
|
"unstacked_assets_count": "{count, plural, one {# média dépilé} other {# médias dépilés}}",
|
||||||
"untracked_files": "Fichiers non suivis",
|
"untracked_files": "Fichiers non suivis",
|
||||||
@@ -1213,6 +1263,8 @@
|
|||||||
"user_license_settings": "Licence",
|
"user_license_settings": "Licence",
|
||||||
"user_license_settings_description": "Gérer votre licence",
|
"user_license_settings_description": "Gérer votre licence",
|
||||||
"user_liked": "{user} a aimé {type, select, photo {cette photo} video {cette vidéo} asset {ce média} other {ceci}}",
|
"user_liked": "{user} a aimé {type, select, photo {cette photo} video {cette vidéo} asset {ce média} other {ceci}}",
|
||||||
|
"user_purchase_settings": "Achat",
|
||||||
|
"user_purchase_settings_description": "Gérer votre achat",
|
||||||
"user_role_set": "Définir {user} comme {role}",
|
"user_role_set": "Définir {user} comme {role}",
|
||||||
"user_usage_detail": "Détail de l'utilisation des utilisateurs",
|
"user_usage_detail": "Détail de l'utilisation des utilisateurs",
|
||||||
"username": "Nom d'utilisateur",
|
"username": "Nom d'utilisateur",
|
||||||
|
|||||||
@@ -249,6 +249,7 @@
|
|||||||
"transcoding_acceleration_vaapi": "VAAPI",
|
"transcoding_acceleration_vaapi": "VAAPI",
|
||||||
"transcoding_accepted_audio_codecs": "קודקים מקובלים של שמע",
|
"transcoding_accepted_audio_codecs": "קודקים מקובלים של שמע",
|
||||||
"transcoding_accepted_audio_codecs_description": "בחר אילו קודקים של שמע אינם צריכים לעבור המרת קידוד. משמש רק עבור פוליסות המרת קידוד מסוימות.",
|
"transcoding_accepted_audio_codecs_description": "בחר אילו קודקים של שמע אינם צריכים לעבור המרת קידוד. משמש רק עבור פוליסות המרת קידוד מסוימות.",
|
||||||
|
"transcoding_accepted_containers": "מכולות מקובלות",
|
||||||
"transcoding_accepted_video_codecs": "קודקים מקובלים של סרטונים",
|
"transcoding_accepted_video_codecs": "קודקים מקובלים של סרטונים",
|
||||||
"transcoding_accepted_video_codecs_description": "בחר אילו קודקים של סרטונים אינם צריכים לעבור המרת קידוד. משמש רק עבור פוליסות המרת קידוד מסוימות.",
|
"transcoding_accepted_video_codecs_description": "בחר אילו קודקים של סרטונים אינם צריכים לעבור המרת קידוד. משמש רק עבור פוליסות המרת קידוד מסוימות.",
|
||||||
"transcoding_advanced_options_description": "אפשרויות שרוב המשתמשים לא צריכים לשנות",
|
"transcoding_advanced_options_description": "אפשרויות שרוב המשתמשים לא צריכים לשנות",
|
||||||
@@ -408,7 +409,7 @@
|
|||||||
"bulk_delete_duplicates_confirmation": "האם את/ה בטוח/ה שברצונך למחוק בכמות גדולה {count, plural, one {נכס # כפול} other {# נכסים כפולים}}? זה ישמור על הנכס הכי גדול של כל קבוצה וימחק לצמיתות את כל שאר הכפילויות. את/ה לא יכול/ה לבטל את הפעולה הזו!",
|
"bulk_delete_duplicates_confirmation": "האם את/ה בטוח/ה שברצונך למחוק בכמות גדולה {count, plural, one {נכס # כפול} other {# נכסים כפולים}}? זה ישמור על הנכס הכי גדול של כל קבוצה וימחק לצמיתות את כל שאר הכפילויות. את/ה לא יכול/ה לבטל את הפעולה הזו!",
|
||||||
"bulk_keep_duplicates_confirmation": "האם את/ה בטוח/ה שברצונך להשאיר {count, plural, one {נכס # כפול} other {# נכסים כפולים}}? זה יפתור את כל הקבוצות הכפולות מבלי למחוק דבר.",
|
"bulk_keep_duplicates_confirmation": "האם את/ה בטוח/ה שברצונך להשאיר {count, plural, one {נכס # כפול} other {# נכסים כפולים}}? זה יפתור את כל הקבוצות הכפולות מבלי למחוק דבר.",
|
||||||
"bulk_trash_duplicates_confirmation": "האם את/ה בטוח/ה שברצונך להעביר לאשפה בכמות גדולה {count, plural, one {נכס # כפול} other {# נכסים כפולים}}? זה ישמור על הנכס הגדול ביותר של כל קבוצה ויעביר לאשפה את כל שאר הכפילויות.",
|
"bulk_trash_duplicates_confirmation": "האם את/ה בטוח/ה שברצונך להעביר לאשפה בכמות גדולה {count, plural, one {נכס # כפול} other {# נכסים כפולים}}? זה ישמור על הנכס הגדול ביותר של כל קבוצה ויעביר לאשפה את כל שאר הכפילויות.",
|
||||||
"buy": "רכוש רישיון",
|
"buy": "רכוש את Immich",
|
||||||
"camera": "מצלמה",
|
"camera": "מצלמה",
|
||||||
"camera_brand": "מותג המצלמה",
|
"camera_brand": "מותג המצלמה",
|
||||||
"camera_model": "דגם המצלמה",
|
"camera_model": "דגם המצלמה",
|
||||||
@@ -436,6 +437,7 @@
|
|||||||
"city": "עיר",
|
"city": "עיר",
|
||||||
"clear": "נקה",
|
"clear": "נקה",
|
||||||
"clear_all": "נקה הכל",
|
"clear_all": "נקה הכל",
|
||||||
|
"clear_all_recent_searches": "נקה את כל החיפושים האחרונים",
|
||||||
"clear_message": "נקה הודעה",
|
"clear_message": "נקה הודעה",
|
||||||
"clear_value": "נקה ערך",
|
"clear_value": "נקה ערך",
|
||||||
"close": "סגור",
|
"close": "סגור",
|
||||||
@@ -574,6 +576,7 @@
|
|||||||
"error_adding_users_to_album": "שגיאה בהוספת משתמשים לאלבום",
|
"error_adding_users_to_album": "שגיאה בהוספת משתמשים לאלבום",
|
||||||
"error_deleting_shared_user": "שגיאה במחיקת משתמש משותף",
|
"error_deleting_shared_user": "שגיאה במחיקת משתמש משותף",
|
||||||
"error_downloading": "שגיאה בהורדת {filename}",
|
"error_downloading": "שגיאה בהורדת {filename}",
|
||||||
|
"error_hiding_buy_button": "שגיאה בהסתרת לחצן 'קנה'",
|
||||||
"error_removing_assets_from_album": "שגיאה בהסרת נכסים מאלבום, בדוק את המסוף לפרטים נוספים",
|
"error_removing_assets_from_album": "שגיאה בהסרת נכסים מאלבום, בדוק את המסוף לפרטים נוספים",
|
||||||
"error_selecting_all_assets": "שגיאה בבחירת כל הנכסים",
|
"error_selecting_all_assets": "שגיאה בבחירת כל הנכסים",
|
||||||
"exclusion_pattern_already_exists": "דפוס החרגה זה כבר קיים.",
|
"exclusion_pattern_already_exists": "דפוס החרגה זה כבר קיים.",
|
||||||
@@ -585,6 +588,7 @@
|
|||||||
"failed_to_load_asset": "טעינת נכס נכשלה",
|
"failed_to_load_asset": "טעינת נכס נכשלה",
|
||||||
"failed_to_load_assets": "טעינת נכסים נכשלה",
|
"failed_to_load_assets": "טעינת נכסים נכשלה",
|
||||||
"failed_to_load_people": "נכשל באחזור אנשים",
|
"failed_to_load_people": "נכשל באחזור אנשים",
|
||||||
|
"failed_to_remove_product_key": "הסרת מפתח מוצר נכשלה",
|
||||||
"failed_to_stack_assets": "יצירת ערימת נכסים נכשלה",
|
"failed_to_stack_assets": "יצירת ערימת נכסים נכשלה",
|
||||||
"failed_to_unstack_assets": "ביטול ערימת נכסים נכשל",
|
"failed_to_unstack_assets": "ביטול ערימת נכסים נכשל",
|
||||||
"import_path_already_exists": "נתיב הייבוא הזה כבר קיים.",
|
"import_path_already_exists": "נתיב הייבוא הזה כבר קיים.",
|
||||||
@@ -738,7 +742,16 @@
|
|||||||
"host": "מארח",
|
"host": "מארח",
|
||||||
"hour": "שעה",
|
"hour": "שעה",
|
||||||
"image": "תמונה",
|
"image": "תמונה",
|
||||||
"image_alt_text_date": "ב {date}",
|
"image_alt_text_date": "{isVideo, select, true {סרטון שצולם} other {תמונה שצולמה}} ב-{date}",
|
||||||
|
"image_alt_text_date_1_person": "{isVideo, select, true {סרטון שצולם} other {תמונה שצולמה}} עם {person1} ב-{date}",
|
||||||
|
"image_alt_text_date_2_people": "{isVideo, select, true {סרטון שצולם} other {תמונה שצולמה}} עם {person1} ו-{person2} ב-{date}",
|
||||||
|
"image_alt_text_date_3_people": "{isVideo, select, true {סרטון שצולם} other {תמונה שצולמה}} עם {person1}, {person2}, ו-{person3} ב-{date}",
|
||||||
|
"image_alt_text_date_4_or_more_people": "{isVideo, select, true {סרטון שצולם} other {תמונה שצולמה}} עם {person1}, {person2}, ו-{additionalCount, number} אחרים ב-{date}",
|
||||||
|
"image_alt_text_date_place": "{isVideo, select, true {סרטון שצולם} other {תמונה שצולמה}} ב-{city}, {country} ב-{date}",
|
||||||
|
"image_alt_text_date_place_1_person": "{isVideo, select, true {סרטון שצולם} other {תמונה שצולמה}} ב-{city}, {country} עם {person1} ב-{date}",
|
||||||
|
"image_alt_text_date_place_2_people": "{isVideo, select, true {סרטון שצולם} other {תמונה שצולמה}} ב-{city}, {country} עם {person1} ו-{person2} ב-{date}",
|
||||||
|
"image_alt_text_date_place_3_people": "{isVideo, select, true {סרטון שצולם} other {תמונה שצולמה}} ב-{city}, {country} עם {person1}, {person2}, ו-{person3} ב-{date}",
|
||||||
|
"image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {סרטון שצולם} other {תמונה שצולמה}} ב-{city}, {country} עם {person1}, {person2}, ו-{additionalCount, number} אחרים ב-{date}",
|
||||||
"image_alt_text_people": "{count, plural, =1 {עם {person1}} =2 {עם {person1} ו{person2}} =3 {עם {person1}, {person2}, ו{person3}} other {עם {person1}, {person2}, ו{others, number} אחרים}}",
|
"image_alt_text_people": "{count, plural, =1 {עם {person1}} =2 {עם {person1} ו{person2}} =3 {עם {person1}, {person2}, ו{person3}} other {עם {person1}, {person2}, ו{others, number} אחרים}}",
|
||||||
"image_alt_text_place": "ב{city}, {country}",
|
"image_alt_text_place": "ב{city}, {country}",
|
||||||
"image_taken": "{isVideo, select, true {סרטון שצולם} other {תמונה שצולמה}}",
|
"image_taken": "{isVideo, select, true {סרטון שצולם} other {תמונה שצולמה}}",
|
||||||
@@ -772,6 +785,7 @@
|
|||||||
"language_setting_description": "בחר/י את השפה המועדפת עליך",
|
"language_setting_description": "בחר/י את השפה המועדפת עליך",
|
||||||
"last_seen": "נראה לאחרונה",
|
"last_seen": "נראה לאחרונה",
|
||||||
"latest_version": "גרסה עדכנית ביותר",
|
"latest_version": "גרסה עדכנית ביותר",
|
||||||
|
"latitude": "קו רוחב",
|
||||||
"leave": "לעזוב",
|
"leave": "לעזוב",
|
||||||
"let_others_respond": "אפשר לאחרים להגיב",
|
"let_others_respond": "אפשר לאחרים להגיב",
|
||||||
"level": "רמה",
|
"level": "רמה",
|
||||||
@@ -818,6 +832,7 @@
|
|||||||
"login_has_been_disabled": "הכניסה הושבתה.",
|
"login_has_been_disabled": "הכניסה הושבתה.",
|
||||||
"logout_all_device_confirmation": "את/ה בטוח/ה שברצונך להתנתק מכל המכשירים?",
|
"logout_all_device_confirmation": "את/ה בטוח/ה שברצונך להתנתק מכל המכשירים?",
|
||||||
"logout_this_device_confirmation": "את/ה בטוח/ה שברצונך להתנתק מהמכשיר הזה?",
|
"logout_this_device_confirmation": "את/ה בטוח/ה שברצונך להתנתק מהמכשיר הזה?",
|
||||||
|
"longitude": "קו אורך",
|
||||||
"look": "מראה",
|
"look": "מראה",
|
||||||
"loop_videos": "הפעלה חוזרת של סרטונים",
|
"loop_videos": "הפעלה חוזרת של סרטונים",
|
||||||
"loop_videos_description": "אפשר הפעלה חוזרת אוטומטית של סרטון במציג הפרטים.",
|
"loop_videos_description": "אפשר הפעלה חוזרת אוטומטית של סרטון במציג הפרטים.",
|
||||||
@@ -971,6 +986,38 @@
|
|||||||
"profile_picture_set": "תמונת פרופיל נבחרה.",
|
"profile_picture_set": "תמונת פרופיל נבחרה.",
|
||||||
"public_album": "אלבום ציבורי",
|
"public_album": "אלבום ציבורי",
|
||||||
"public_share": "שיתוף ציבורי",
|
"public_share": "שיתוף ציבורי",
|
||||||
|
"purchase_account_info": "תומך",
|
||||||
|
"purchase_activated_subtitle": "תודה לך על התמיכה ב-Immich ובתוכנות קוד-פתוח",
|
||||||
|
"purchase_activated_time": "הופעל ב-{date, date}",
|
||||||
|
"purchase_activated_title": "המפתח שלך הופעל בהצלחה",
|
||||||
|
"purchase_button_activate": "הפעל",
|
||||||
|
"purchase_button_buy": "קנה",
|
||||||
|
"purchase_button_buy_immich": "קנה Immich",
|
||||||
|
"purchase_button_never_show_again": "לעולם אל תראה שוב",
|
||||||
|
"purchase_button_reminder": "הזכר לי בעוד 30 יום",
|
||||||
|
"purchase_button_remove_key": "הסר מפתח",
|
||||||
|
"purchase_button_select": "בחר",
|
||||||
|
"purchase_failed_activation": "ההפעלה נכשלה! נא לבדוק את הדוא\"ל שלך עבור מפתח המוצר הנכון!",
|
||||||
|
"purchase_individual_description_1": "ליחיד",
|
||||||
|
"purchase_individual_description_2": "מעמד תומך",
|
||||||
|
"purchase_individual_title": "יחיד",
|
||||||
|
"purchase_input_suggestion": "יש לך מפתח מוצר? הכנס את המפתח למטה",
|
||||||
|
"purchase_license_subtitle": "קנה את Immich כדי לתמוך בפיתוח המתמשך של השירות",
|
||||||
|
"purchase_lifetime_description": "רכישה לכל החיים",
|
||||||
|
"purchase_option_title": "אפשרויות רכישה",
|
||||||
|
"purchase_panel_info_1": "בניית Immich לוקחת הרבה זמן ומאמץ, ויש לנו מהנדסים במשרה מלאה שעובדים על זה כדי לעשות את זה הכי טוב שאנחנו יכולים. המשימה שלנו היא שתוכנות קוד-פתוח ושיטות עסקיות אתיות יהיו מקור הכנסה בר-קיימא למפתחים וליצור מערכת אקולוגית שמכבדת פרטיות עם חלופות אמיתיות לשירותי ענן נצלנים.",
|
||||||
|
"purchase_panel_info_2": "מכיוון שאנחנו מחויבים לא להוסיף חומות תשלום, הרכישה הזאת לא תקנה לך תכונות נוספות כלשהן ב-Immich. אנחנו סומכים על משתמשים כמוך שיתמכו בפיתוח המתמשך של Immich.",
|
||||||
|
"purchase_panel_title": "תמוך בפרויקט",
|
||||||
|
"purchase_per_server": "עבור שרת",
|
||||||
|
"purchase_per_user": "עבור משתמש",
|
||||||
|
"purchase_remove_product_key": "הסר מפתח מוצר",
|
||||||
|
"purchase_remove_product_key_prompt": "האם את/ה בטוח/ה שאת/ה רוצה להסיר את מפתח המוצר?",
|
||||||
|
"purchase_remove_server_product_key": "הסר מפתח מוצר של שרת",
|
||||||
|
"purchase_remove_server_product_key_prompt": "האם את/ה בטוח/ה שאת/ה רוצה להסיר את מפתח המוצר של השרת?",
|
||||||
|
"purchase_server_description_1": "עבור כל השרת",
|
||||||
|
"purchase_server_description_2": "מעמד תומך",
|
||||||
|
"purchase_server_title": "שרת",
|
||||||
|
"purchase_settings_server_activated": "מפתח המוצר של השרת מנוהל על ידי מנהל המערכת",
|
||||||
"range": "",
|
"range": "",
|
||||||
"raw": "",
|
"raw": "",
|
||||||
"reaction_options": "אפשרויות הגבה",
|
"reaction_options": "אפשרויות הגבה",
|
||||||
@@ -1016,6 +1063,7 @@
|
|||||||
"reset_people_visibility": "אפס את נראות האנשים",
|
"reset_people_visibility": "אפס את נראות האנשים",
|
||||||
"reset_settings_to_default": "",
|
"reset_settings_to_default": "",
|
||||||
"reset_to_default": "אפס לברירת מחדל",
|
"reset_to_default": "אפס לברירת מחדל",
|
||||||
|
"resolve_duplicates": "פתור כפילויות",
|
||||||
"resolved_all_duplicates": "כל הכפילויות נפתרו",
|
"resolved_all_duplicates": "כל הכפילויות נפתרו",
|
||||||
"restore": "שחזר",
|
"restore": "שחזר",
|
||||||
"restore_all": "שחזר הכל",
|
"restore_all": "שחזר הכל",
|
||||||
@@ -1060,6 +1108,7 @@
|
|||||||
"see_all_people": "ראה את כל האנשים",
|
"see_all_people": "ראה את כל האנשים",
|
||||||
"select_album_cover": "בחר עטיפת אלבום",
|
"select_album_cover": "בחר עטיפת אלבום",
|
||||||
"select_all": "בחר הכל",
|
"select_all": "בחר הכל",
|
||||||
|
"select_all_duplicates": "בחר את כל הכפילויות",
|
||||||
"select_avatar_color": "בחר צבע תמונת פרופיל",
|
"select_avatar_color": "בחר צבע תמונת פרופיל",
|
||||||
"select_face": "בחר פנים",
|
"select_face": "בחר פנים",
|
||||||
"select_featured_photo": "בחר תמונה מייצגת",
|
"select_featured_photo": "בחר תמונה מייצגת",
|
||||||
@@ -1114,6 +1163,8 @@
|
|||||||
"show_person_options": "הצג אפשרויות אדם",
|
"show_person_options": "הצג אפשרויות אדם",
|
||||||
"show_progress_bar": "הצג סרגל התקדמות",
|
"show_progress_bar": "הצג סרגל התקדמות",
|
||||||
"show_search_options": "הצג אפשרויות חיפוש",
|
"show_search_options": "הצג אפשרויות חיפוש",
|
||||||
|
"show_supporter_badge": "תג תומך",
|
||||||
|
"show_supporter_badge_description": "הצג תג תומך",
|
||||||
"shuffle": "ערבוב",
|
"shuffle": "ערבוב",
|
||||||
"sign_out": "יציאה מהמערכת",
|
"sign_out": "יציאה מהמערכת",
|
||||||
"sign_up": "הרשמה",
|
"sign_up": "הרשמה",
|
||||||
@@ -1187,6 +1238,7 @@
|
|||||||
"unnamed_share": "שיתוף ללא שם",
|
"unnamed_share": "שיתוף ללא שם",
|
||||||
"unsaved_change": "שינוי לא נשמר",
|
"unsaved_change": "שינוי לא נשמר",
|
||||||
"unselect_all": "בטל בחירה בהכל",
|
"unselect_all": "בטל בחירה בהכל",
|
||||||
|
"unselect_all_duplicates": "בטל בחירת כל הכפילויות",
|
||||||
"unstack": "בטל ערימה",
|
"unstack": "בטל ערימה",
|
||||||
"unstacked_assets_count": "{count, plural, one {נכס # הוסר} other {# נכסים הוסרו}} מערימה",
|
"unstacked_assets_count": "{count, plural, one {נכס # הוסר} other {# נכסים הוסרו}} מערימה",
|
||||||
"untracked_files": "קבצים ללא מעקב",
|
"untracked_files": "קבצים ללא מעקב",
|
||||||
@@ -1210,6 +1262,8 @@
|
|||||||
"user_license_settings": "רישיון",
|
"user_license_settings": "רישיון",
|
||||||
"user_license_settings_description": "נהל את הרישיון שלך",
|
"user_license_settings_description": "נהל את הרישיון שלך",
|
||||||
"user_liked": "{user} אהב את {type, select, photo {התמונה הזאת} video {הסרטון הזה} asset {הנכס הזה} other {זה}}",
|
"user_liked": "{user} אהב את {type, select, photo {התמונה הזאת} video {הסרטון הזה} asset {הנכס הזה} other {זה}}",
|
||||||
|
"user_purchase_settings": "רכישה",
|
||||||
|
"user_purchase_settings_description": "נהל את הרכישה שלך",
|
||||||
"user_role_set": "הגדר את {user} בתור {role}",
|
"user_role_set": "הגדר את {user} בתור {role}",
|
||||||
"user_usage_detail": "פרטי השימוש של המשתמש",
|
"user_usage_detail": "פרטי השימוש של המשתמש",
|
||||||
"username": "שם משתמש",
|
"username": "שם משתמש",
|
||||||
|
|||||||
@@ -256,6 +256,7 @@
|
|||||||
"transcoding_audio_codec": "Audio kodek",
|
"transcoding_audio_codec": "Audio kodek",
|
||||||
"transcoding_audio_codec_description": "Az Opus a legjobb minőségű opció (jobb minőség ugyanannyi helyet foglalva), de kevésbé kompatibilis a régi eszközökkel vagy szoftverekkel.",
|
"transcoding_audio_codec_description": "Az Opus a legjobb minőségű opció (jobb minőség ugyanannyi helyet foglalva), de kevésbé kompatibilis a régi eszközökkel vagy szoftverekkel.",
|
||||||
"transcoding_bitrate_description": "A maximum bitrátát meghaladó vagy nem megfelelő formátumú videókat",
|
"transcoding_bitrate_description": "A maximum bitrátát meghaladó vagy nem megfelelő formátumú videókat",
|
||||||
|
"transcoding_codecs_learn_more": "Hogy többet tudjon meg az itt felhasznált kifejezésekről, látogassa meg az FFmpeg dokumentációt a <h264-link>H.264 kodekhez</h264-link>, a <hevc-link>HEVC kodekhez</hevc-link> és a <vp9-link>VP9 kodekhez</vp9-link>.",
|
||||||
"transcoding_constant_quality_mode": "Állandó minőségi mód",
|
"transcoding_constant_quality_mode": "Állandó minőségi mód",
|
||||||
"transcoding_constant_quality_mode_description": "Az ICQ jobb, mint a CQP, viszont nem minden hardver támogatja. A rendszer az itt beállított módszert részesíti előnyben. A NVENC ignorálja a beállítást, mivel nem támogatja az ICQ-t.",
|
"transcoding_constant_quality_mode_description": "Az ICQ jobb, mint a CQP, viszont nem minden hardver támogatja. A rendszer az itt beállított módszert részesíti előnyben. A NVENC ignorálja a beállítást, mivel nem támogatja az ICQ-t.",
|
||||||
"transcoding_constant_rate_factor": "",
|
"transcoding_constant_rate_factor": "",
|
||||||
|
|||||||
@@ -249,6 +249,7 @@
|
|||||||
"transcoding_acceleration_vaapi": "VAAPI",
|
"transcoding_acceleration_vaapi": "VAAPI",
|
||||||
"transcoding_accepted_audio_codecs": "Codifiche audio accettate",
|
"transcoding_accepted_audio_codecs": "Codifiche audio accettate",
|
||||||
"transcoding_accepted_audio_codecs_description": "Seleziona quali codifiche audio non devono essere trascodificate. Solo usato per alcune politiche di trascodifica.",
|
"transcoding_accepted_audio_codecs_description": "Seleziona quali codifiche audio non devono essere trascodificate. Solo usato per alcune politiche di trascodifica.",
|
||||||
|
"transcoding_accepted_containers_description": "Seleziona quali formati non hanno bisogno di essere remuxati in MP4. Usato solo per certe politiche di transcodifica.",
|
||||||
"transcoding_accepted_video_codecs": "Codifiche video accettate",
|
"transcoding_accepted_video_codecs": "Codifiche video accettate",
|
||||||
"transcoding_accepted_video_codecs_description": "Seleziona quali codifiche video non devono essere trascodificate. Usato solo per alcune politiche di trascodifica.",
|
"transcoding_accepted_video_codecs_description": "Seleziona quali codifiche video non devono essere trascodificate. Usato solo per alcune politiche di trascodifica.",
|
||||||
"transcoding_advanced_options_description": "Impostazioni che la maggior parte degli utenti non dovrebbero cambiare",
|
"transcoding_advanced_options_description": "Impostazioni che la maggior parte degli utenti non dovrebbero cambiare",
|
||||||
@@ -436,6 +437,7 @@
|
|||||||
"city": "Città",
|
"city": "Città",
|
||||||
"clear": "Pulisci",
|
"clear": "Pulisci",
|
||||||
"clear_all": "Pulisci tutto",
|
"clear_all": "Pulisci tutto",
|
||||||
|
"clear_all_recent_searches": "Rimuovi tutte le ricerche recenti",
|
||||||
"clear_message": "Pulisci messaggio",
|
"clear_message": "Pulisci messaggio",
|
||||||
"clear_value": "Pulisci valore",
|
"clear_value": "Pulisci valore",
|
||||||
"close": "Chiudi",
|
"close": "Chiudi",
|
||||||
@@ -584,6 +586,8 @@
|
|||||||
"failed_to_get_people": "Impossibile ottenere le persone",
|
"failed_to_get_people": "Impossibile ottenere le persone",
|
||||||
"failed_to_load_asset": "Errore durante il caricamento dell'asset",
|
"failed_to_load_asset": "Errore durante il caricamento dell'asset",
|
||||||
"failed_to_load_assets": "Errore durante il caricamento degli assets",
|
"failed_to_load_assets": "Errore durante il caricamento degli assets",
|
||||||
|
"failed_to_load_people": "Caricamento delle persone fallito",
|
||||||
|
"failed_to_remove_product_key": "Rimozione del codice del prodotto fallita",
|
||||||
"failed_to_stack_assets": "Errore durante il raggruppamento degli assets",
|
"failed_to_stack_assets": "Errore durante il raggruppamento degli assets",
|
||||||
"failed_to_unstack_assets": "Errore durante la separazione degli assets",
|
"failed_to_unstack_assets": "Errore durante la separazione degli assets",
|
||||||
"import_path_already_exists": "Questo percorso di importazione già esiste.",
|
"import_path_already_exists": "Questo percorso di importazione già esiste.",
|
||||||
@@ -738,6 +742,14 @@
|
|||||||
"hour": "Ora",
|
"hour": "Ora",
|
||||||
"image": "Immagine",
|
"image": "Immagine",
|
||||||
"image_alt_text_date": "il {date}",
|
"image_alt_text_date": "il {date}",
|
||||||
|
"image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} scattata con {person1} il giorno {date}",
|
||||||
|
"image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} scattata con {person1} e {person2} il giorno {date}",
|
||||||
|
"image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} scattata con {person1}, {person2}, e {person3} il giorno {date}",
|
||||||
|
"image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} scattata con {person1}, {person2}, e altre {additionalCount, number} persone il giorno {date}",
|
||||||
|
"image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} scattata a {city}, {country} il giorno {date}",
|
||||||
|
"image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} scattata a {city}, {country} con {person1} il giorno {date}",
|
||||||
|
"image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} scattata a {city}, {country} con {person1} e {person2} il giorno {date}",
|
||||||
|
"image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} scattata a {city}, {country} con {person1}, {person2}, e {person3} il giorno {date}",
|
||||||
"image_alt_text_people": "{count, plural, =1 {con {person1}} =2 {con {person1} e {person2}} =3 {con {person1}, {person2} e {person3}} other {con {person1}, {person2} e {others, number} altri}}",
|
"image_alt_text_people": "{count, plural, =1 {con {person1}} =2 {con {person1} e {person2}} =3 {con {person1}, {person2} e {person3}} other {con {person1}, {person2} e {others, number} altri}}",
|
||||||
"image_alt_text_place": "a {city}, {country}",
|
"image_alt_text_place": "a {city}, {country}",
|
||||||
"image_taken": "{isVideo, select, true {Video registrato} other {Immagine scattata}}",
|
"image_taken": "{isVideo, select, true {Video registrato} other {Immagine scattata}}",
|
||||||
|
|||||||
@@ -127,6 +127,8 @@
|
|||||||
"manage_log_settings": "ログ設定を管理します",
|
"manage_log_settings": "ログ設定を管理します",
|
||||||
"map_dark_style": "ダークモード",
|
"map_dark_style": "ダークモード",
|
||||||
"map_enable_description": "地図表示を有効にします",
|
"map_enable_description": "地図表示を有効にします",
|
||||||
|
"map_gps_settings": "地図・GPS設定",
|
||||||
|
"map_gps_settings_description": "地図とGPS(逆ジオコーディング)の設定を管理します",
|
||||||
"map_light_style": "ライトモード",
|
"map_light_style": "ライトモード",
|
||||||
"map_manage_reverse_geocoding_settings": "<link>逆ジオコーディング</link>の設定を管理します",
|
"map_manage_reverse_geocoding_settings": "<link>逆ジオコーディング</link>の設定を管理します",
|
||||||
"map_reverse_geocoding": "逆ジオコーディング",
|
"map_reverse_geocoding": "逆ジオコーディング",
|
||||||
@@ -245,6 +247,8 @@
|
|||||||
"transcoding_acceleration_vaapi": "VA-API",
|
"transcoding_acceleration_vaapi": "VA-API",
|
||||||
"transcoding_accepted_audio_codecs": "容認する音声コーデック",
|
"transcoding_accepted_audio_codecs": "容認する音声コーデック",
|
||||||
"transcoding_accepted_audio_codecs_description": "トランスコードする必要のない音声コーデックを選択します。特定のトランスコードポリシーにのみ使用されます。",
|
"transcoding_accepted_audio_codecs_description": "トランスコードする必要のない音声コーデックを選択します。特定のトランスコードポリシーにのみ使用されます。",
|
||||||
|
"transcoding_accepted_containers": "容認するコンテナ",
|
||||||
|
"transcoding_accepted_containers_description": "MP4に再多重化する必要がないコンテナを選択します。特定のトランスコードポリシーにのみ使用されます。",
|
||||||
"transcoding_accepted_video_codecs": "容認する動画コーデック",
|
"transcoding_accepted_video_codecs": "容認する動画コーデック",
|
||||||
"transcoding_accepted_video_codecs_description": "トランスコードする必要のない動画コーデックを選択します。特定のトランスコードポリシーにのみ使用されます。",
|
"transcoding_accepted_video_codecs_description": "トランスコードする必要のない動画コーデックを選択します。特定のトランスコードポリシーにのみ使用されます。",
|
||||||
"transcoding_advanced_options_description": "ほとんどのユーザーは変更する必要のないオプション",
|
"transcoding_advanced_options_description": "ほとんどのユーザーは変更する必要のないオプション",
|
||||||
@@ -379,7 +383,7 @@
|
|||||||
"assets": "アセット",
|
"assets": "アセット",
|
||||||
"assets_added_count": "{count, plural, one {#個} other {#個}}のアセットを追加しました",
|
"assets_added_count": "{count, plural, one {#個} other {#個}}のアセットを追加しました",
|
||||||
"assets_added_to_album_count": "{count, plural, one {#個} other {#個}}のアセットをアルバムに追加しました",
|
"assets_added_to_album_count": "{count, plural, one {#個} other {#個}}のアセットをアルバムに追加しました",
|
||||||
"assets_added_to_name_count": "{count, plural, one {#個} other {#個}}のアセットを{name}に追加しました",
|
"assets_added_to_name_count": "{count, plural, one {#個} other {#個}}のアセットを{hasName, select, true {<b>{name}</b>} other {新しいアルバム}}に追加しました",
|
||||||
"assets_count": "{count, plural, one {#個} other {#個}}のアセット",
|
"assets_count": "{count, plural, one {#個} other {#個}}のアセット",
|
||||||
"assets_moved_to_trash_count": "{count, plural, one {#個} other {#個}}のアセットをごみ箱に移動しました",
|
"assets_moved_to_trash_count": "{count, plural, one {#個} other {#個}}のアセットをごみ箱に移動しました",
|
||||||
"assets_permanently_deleted_count": "{count, plural, one {#個} other {#個}}のアセットを完全に削除しました",
|
"assets_permanently_deleted_count": "{count, plural, one {#個} other {#個}}のアセットを完全に削除しました",
|
||||||
@@ -400,6 +404,7 @@
|
|||||||
"bulk_delete_duplicates_confirmation": "本当に {count, plural, one {#個} other {#個}}の重複したアセットを一括削除しますか?これにより各重複中の最大のアセットが保持され、他の全ての重複が削除されます。この操作を元に戻すことはできません!",
|
"bulk_delete_duplicates_confirmation": "本当に {count, plural, one {#個} other {#個}}の重複したアセットを一括削除しますか?これにより各重複中の最大のアセットが保持され、他の全ての重複が削除されます。この操作を元に戻すことはできません!",
|
||||||
"bulk_keep_duplicates_confirmation": "本当に{count, plural, one {#個} other {#個}}の重複アセットを保持しますか?これにより何も削除されずに重複グループが解決されます。",
|
"bulk_keep_duplicates_confirmation": "本当に{count, plural, one {#個} other {#個}}の重複アセットを保持しますか?これにより何も削除されずに重複グループが解決されます。",
|
||||||
"bulk_trash_duplicates_confirmation": "本当に{count, plural, one {#個} other {#個}}の重複したアセットを一括でごみ箱に移動しますか?これにより各重複中の最大のアセットが保持され、他の全ての重複はごみ箱に移動されます。",
|
"bulk_trash_duplicates_confirmation": "本当に{count, plural, one {#個} other {#個}}の重複したアセットを一括でごみ箱に移動しますか?これにより各重複中の最大のアセットが保持され、他の全ての重複はごみ箱に移動されます。",
|
||||||
|
"buy": "Immichを購入",
|
||||||
"camera": "カメラブランド",
|
"camera": "カメラブランド",
|
||||||
"camera_brand": "カメラブランド",
|
"camera_brand": "カメラブランド",
|
||||||
"camera_model": "カメラモデル",
|
"camera_model": "カメラモデル",
|
||||||
@@ -425,6 +430,7 @@
|
|||||||
"city": "市町村",
|
"city": "市町村",
|
||||||
"clear": "クリア",
|
"clear": "クリア",
|
||||||
"clear_all": "全てクリア",
|
"clear_all": "全てクリア",
|
||||||
|
"clear_all_recent_searches": "全ての最近の検索をクリア",
|
||||||
"clear_message": "メッセージをクリア",
|
"clear_message": "メッセージをクリア",
|
||||||
"clear_value": "値をクリア",
|
"clear_value": "値をクリア",
|
||||||
"close": "閉じる",
|
"close": "閉じる",
|
||||||
@@ -480,24 +486,27 @@
|
|||||||
"default_locale_description": "ブラウザのロケールに基づいて日付と数値をフォーマットします",
|
"default_locale_description": "ブラウザのロケールに基づいて日付と数値をフォーマットします",
|
||||||
"delete": "削除",
|
"delete": "削除",
|
||||||
"delete_album": "アルバムを削除",
|
"delete_album": "アルバムを削除",
|
||||||
|
"delete_api_key_prompt": "本当にこのAPI キーを削除しますか?",
|
||||||
"delete_duplicates_confirmation": "本当にこれらの重複を完全に削除しますか?",
|
"delete_duplicates_confirmation": "本当にこれらの重複を完全に削除しますか?",
|
||||||
"delete_key": "",
|
"delete_key": "キーを削除",
|
||||||
"delete_library": "",
|
"delete_library": "ライブラリを削除",
|
||||||
"delete_link": "",
|
"delete_link": "リンクを削除",
|
||||||
"delete_shared_link": "共有リンクを消す",
|
"delete_shared_link": "共有リンクを消す",
|
||||||
"delete_user": "",
|
"delete_user": "ユーザーを削除",
|
||||||
"deleted_shared_link": "",
|
"deleted_shared_link": "共有リンクを削除",
|
||||||
"description": "概要欄",
|
"description": "概要欄",
|
||||||
"details": "詳細",
|
"details": "詳細",
|
||||||
"direction": "",
|
"direction": "方向",
|
||||||
"disallow_edits": "",
|
"disabled": "無効",
|
||||||
"discover": "",
|
"disallow_edits": "編集を許可しない",
|
||||||
"dismiss_all_errors": "",
|
"discover": "探索",
|
||||||
"dismiss_error": "",
|
"dismiss_all_errors": "全てのエラーを無視",
|
||||||
|
"dismiss_error": "エラーを無視",
|
||||||
"display_options": "表示オプション",
|
"display_options": "表示オプション",
|
||||||
"display_order": "表示順",
|
"display_order": "表示順",
|
||||||
"display_original_photos": "オリジナルの写真を表示",
|
"display_original_photos": "オリジナルの写真を表示",
|
||||||
"display_original_photos_setting_description": "オリジナルのアセットが Web 互換である場合は、アセットを表示するときにサムネイルではなく元の写真を優先して表示します。これにより写真の表示速度が遅くなる可能性があります。",
|
"display_original_photos_setting_description": "オリジナルのアセットが Web 互換である場合は、アセットを表示するときにサムネイルではなく元の写真を優先して表示します。これにより写真の表示速度が遅くなる可能性があります。",
|
||||||
|
"do_not_show_again": "このメッセージを再び表示しない",
|
||||||
"done": "完了",
|
"done": "完了",
|
||||||
"download": "ダウンロード",
|
"download": "ダウンロード",
|
||||||
"download_settings": "ダウンロード",
|
"download_settings": "ダウンロード",
|
||||||
@@ -506,7 +515,7 @@
|
|||||||
"downloading_asset_filename": "アセット {filename} をダウンロード中",
|
"downloading_asset_filename": "アセット {filename} をダウンロード中",
|
||||||
"drop_files_to_upload": "ファイルをドロップしてアップロード",
|
"drop_files_to_upload": "ファイルをドロップしてアップロード",
|
||||||
"duplicates": "重複",
|
"duplicates": "重複",
|
||||||
"duration": "",
|
"duration": "間隔",
|
||||||
"durations": {
|
"durations": {
|
||||||
"days": "",
|
"days": "",
|
||||||
"hours": "",
|
"hours": "",
|
||||||
@@ -514,7 +523,8 @@
|
|||||||
"months": "",
|
"months": "",
|
||||||
"years": ""
|
"years": ""
|
||||||
},
|
},
|
||||||
"edit_album": "",
|
"edit": "編集",
|
||||||
|
"edit_album": "アルバムを編集",
|
||||||
"edit_avatar": "アバターを編集",
|
"edit_avatar": "アバターを編集",
|
||||||
"edit_date": "日付を編集",
|
"edit_date": "日付を編集",
|
||||||
"edit_date_and_time": "日時を編集",
|
"edit_date_and_time": "日時を編集",
|
||||||
@@ -558,6 +568,7 @@
|
|||||||
"error_adding_users_to_album": "ユーザーをアルバムに追加中のエラー",
|
"error_adding_users_to_album": "ユーザーをアルバムに追加中のエラー",
|
||||||
"error_deleting_shared_user": "共有ユーザを削除中のエラー",
|
"error_deleting_shared_user": "共有ユーザを削除中のエラー",
|
||||||
"error_downloading": "{filename}をダウンロード中にエラー",
|
"error_downloading": "{filename}をダウンロード中にエラー",
|
||||||
|
"error_hiding_buy_button": "購入ボタン非表示のエラー",
|
||||||
"error_removing_assets_from_album": "アルバムからアセットを削除中のエラー、詳細についてはコンソールを確認してください",
|
"error_removing_assets_from_album": "アルバムからアセットを削除中のエラー、詳細についてはコンソールを確認してください",
|
||||||
"error_selecting_all_assets": "全アセット選択のエラー",
|
"error_selecting_all_assets": "全アセット選択のエラー",
|
||||||
"exclusion_pattern_already_exists": "この除外パターンは既に存在します。",
|
"exclusion_pattern_already_exists": "この除外パターンは既に存在します。",
|
||||||
@@ -568,6 +579,8 @@
|
|||||||
"failed_to_get_people": "人物を取得できませんでした",
|
"failed_to_get_people": "人物を取得できませんでした",
|
||||||
"failed_to_load_asset": "アセットを読み込めませんでした",
|
"failed_to_load_asset": "アセットを読み込めませんでした",
|
||||||
"failed_to_load_assets": "アセットを読み込めませんでした",
|
"failed_to_load_assets": "アセットを読み込めませんでした",
|
||||||
|
"failed_to_load_people": "人物を読み込めませんでした",
|
||||||
|
"failed_to_remove_product_key": "プロダクトキーを削除できませんでした",
|
||||||
"import_path_already_exists": "このインポートパスは既に存在します。",
|
"import_path_already_exists": "このインポートパスは既に存在します。",
|
||||||
"incorrect_email_or_password": "メールアドレスまたはパスワードが間違っています",
|
"incorrect_email_or_password": "メールアドレスまたはパスワードが間違っています",
|
||||||
"paths_validation_failed": "{paths, plural, one {#個} other {#個}}のパスの検証に失敗しました",
|
"paths_validation_failed": "{paths, plural, one {#個} other {#個}}のパスの検証に失敗しました",
|
||||||
@@ -694,7 +707,7 @@
|
|||||||
"filetype": "ファイルタイプ",
|
"filetype": "ファイルタイプ",
|
||||||
"filter_people": "人物を絞り込み",
|
"filter_people": "人物を絞り込み",
|
||||||
"find_them_fast": "名前で検索して素早く発見",
|
"find_them_fast": "名前で検索して素早く発見",
|
||||||
"fix_incorrect_match": "",
|
"fix_incorrect_match": "間違った一致を修正",
|
||||||
"force_re-scan_library_files": "強制的に全てのライブラリのファイルを再スキャン",
|
"force_re-scan_library_files": "強制的に全てのライブラリのファイルを再スキャン",
|
||||||
"forward": "前へ",
|
"forward": "前へ",
|
||||||
"general": "一般",
|
"general": "一般",
|
||||||
@@ -718,13 +731,14 @@
|
|||||||
"host": "ホスト",
|
"host": "ホスト",
|
||||||
"hour": "時間",
|
"hour": "時間",
|
||||||
"image": "写真",
|
"image": "写真",
|
||||||
"image_alt_text_date": "{date} に撮影",
|
"image_alt_text_date": "{isVideo, select, true {動画} other {写真}}は{date} に撮影",
|
||||||
"image_alt_text_place": "{country} {city}で撮影",
|
"image_alt_text_place": "{country} {city}で撮影",
|
||||||
"image_taken": "{isVideo, select, true {動画は} other {写真は}}",
|
"image_taken": "{isVideo, select, true {動画は} other {写真は}}",
|
||||||
"img": "",
|
"img": "",
|
||||||
"immich_logo": "Immich ロゴ",
|
"immich_logo": "Immich ロゴ",
|
||||||
|
"immich_web_interface": "Immich Webインターフェース",
|
||||||
"import_from_json": "JSONからインポート",
|
"import_from_json": "JSONからインポート",
|
||||||
"import_path": "",
|
"import_path": "インポートパス",
|
||||||
"in_archive": "アーカイブ済み",
|
"in_archive": "アーカイブ済み",
|
||||||
"include_archived": "アーカイブ済みを含める",
|
"include_archived": "アーカイブ済みを含める",
|
||||||
"include_shared_albums": "共有アルバムを含める",
|
"include_shared_albums": "共有アルバムを含める",
|
||||||
@@ -732,27 +746,29 @@
|
|||||||
"individual_share": "",
|
"individual_share": "",
|
||||||
"info": "情報",
|
"info": "情報",
|
||||||
"interval": {
|
"interval": {
|
||||||
"day_at_onepm": "",
|
"day_at_onepm": "毎日午後1時",
|
||||||
"hours": "",
|
"hours": "{hours, plural, one {1時間} other {{hours, number}時間}}ごと",
|
||||||
"night_at_midnight": "",
|
"night_at_midnight": "毎晩真夜中に",
|
||||||
"night_at_twoam": ""
|
"night_at_twoam": "毎晩午前2時"
|
||||||
},
|
},
|
||||||
"invite_people": "",
|
"invite_people": "人々を招待",
|
||||||
"invite_to_album": "アルバムに招待",
|
"invite_to_album": "アルバムに招待",
|
||||||
"items_count": "{count, plural, one {#個} other {#個}}の項目",
|
"items_count": "{count, plural, one {#個} other {#個}}の項目",
|
||||||
"job_settings_description": "",
|
"job_settings_description": "",
|
||||||
"jobs": "ジョブ",
|
"jobs": "ジョブ",
|
||||||
"keep": "",
|
"keep": "保持",
|
||||||
|
"keep_all": "全て保持",
|
||||||
"keyboard_shortcuts": "キーボードショートカット",
|
"keyboard_shortcuts": "キーボードショートカット",
|
||||||
"language": "言語",
|
"language": "言語",
|
||||||
"language_setting_description": "優先言語を選択してください",
|
"language_setting_description": "優先言語を選択してください",
|
||||||
"last_seen": "最新の活動",
|
"last_seen": "最新の活動",
|
||||||
"latest_version": "最新バージョン",
|
"latest_version": "最新バージョン",
|
||||||
|
"latitude": "緯度",
|
||||||
"leave": "",
|
"leave": "",
|
||||||
"let_others_respond": "他のユーザーの返信を許可する",
|
"let_others_respond": "他のユーザーの返信を許可する",
|
||||||
"level": "レベル",
|
"level": "レベル",
|
||||||
"library": "ライブラリ",
|
"library": "ライブラリ",
|
||||||
"library_options": "",
|
"library_options": "ライブラリ設定",
|
||||||
"light": "",
|
"light": "",
|
||||||
"like_deleted": "いいねが削除されました",
|
"like_deleted": "いいねが削除されました",
|
||||||
"link_options": "リンクのオプション",
|
"link_options": "リンクのオプション",
|
||||||
@@ -769,6 +785,7 @@
|
|||||||
"login_has_been_disabled": "ログインは無効化されています。",
|
"login_has_been_disabled": "ログインは無効化されています。",
|
||||||
"logout_all_device_confirmation": "本当に全てのデバイスからログアウトしますか?",
|
"logout_all_device_confirmation": "本当に全てのデバイスからログアウトしますか?",
|
||||||
"logout_this_device_confirmation": "本当にこのデバイスからログアウトしますか?",
|
"logout_this_device_confirmation": "本当にこのデバイスからログアウトしますか?",
|
||||||
|
"longitude": "経度",
|
||||||
"look": "見た目",
|
"look": "見た目",
|
||||||
"loop_videos": "動画をループ",
|
"loop_videos": "動画をループ",
|
||||||
"loop_videos_description": "有効にすると詳細表示で自動的に動画がループします。",
|
"loop_videos_description": "有効にすると詳細表示で自動的に動画がループします。",
|
||||||
@@ -798,7 +815,7 @@
|
|||||||
"merged_people_count": "{count, plural, one {#人} other {#人}}の人物をマージしました",
|
"merged_people_count": "{count, plural, one {#人} other {#人}}の人物をマージしました",
|
||||||
"minimize": "最小化",
|
"minimize": "最小化",
|
||||||
"minute": "分",
|
"minute": "分",
|
||||||
"missing": "",
|
"missing": "行方不明",
|
||||||
"model": "モデル",
|
"model": "モデル",
|
||||||
"month": "月",
|
"month": "月",
|
||||||
"more": "もっと表示",
|
"more": "もっと表示",
|
||||||
@@ -916,6 +933,31 @@
|
|||||||
"profile_picture_set": "プロフィール画像が設定されました。",
|
"profile_picture_set": "プロフィール画像が設定されました。",
|
||||||
"public_album": "公開アルバム",
|
"public_album": "公開アルバム",
|
||||||
"public_share": "公開共有",
|
"public_share": "公開共有",
|
||||||
|
"purchase_account_info": "サポーター",
|
||||||
|
"purchase_activated_subtitle": "Immich とオープンソース ソフトウェアを支援していただきありがとうございます",
|
||||||
|
"purchase_activated_time": "{date, date}にアクティベート",
|
||||||
|
"purchase_activated_title": "キーは正常にアクティベートされました",
|
||||||
|
"purchase_button_activate": "アクティベート",
|
||||||
|
"purchase_button_buy": "購入",
|
||||||
|
"purchase_button_buy_immich": "Immichを購入",
|
||||||
|
"purchase_button_never_show_again": "二度と表示しない",
|
||||||
|
"purchase_button_reminder": "30日後に通知する",
|
||||||
|
"purchase_button_remove_key": "キーを削除",
|
||||||
|
"purchase_button_select": "選択",
|
||||||
|
"purchase_failed_activation": "アクティベートに失敗しました! メールで正しいプロダクトキーを確認してください!",
|
||||||
|
"purchase_individual_description_1": "個人向け",
|
||||||
|
"purchase_individual_title": "個人",
|
||||||
|
"purchase_input_suggestion": "プロダクトキーをお持ちですか? 下に入力してください",
|
||||||
|
"purchase_license_subtitle": "Immich を購入してサービスの継続的な開発を支援してください",
|
||||||
|
"purchase_lifetime_description": "生涯の購入",
|
||||||
|
"purchase_option_title": "購入オプション",
|
||||||
|
"purchase_panel_title": "プロジェクトを支援",
|
||||||
|
"purchase_per_server": "サーバーごと",
|
||||||
|
"purchase_per_user": "ユーザーごと",
|
||||||
|
"purchase_remove_product_key": "プロダクトキーを削除",
|
||||||
|
"purchase_remove_product_key_prompt": "本当にプロダクトキーを削除しますか?",
|
||||||
|
"purchase_remove_server_product_key": "サーバープロダクトキーを削除",
|
||||||
|
"purchase_remove_server_product_key_prompt": "本当にサーバープロダクトキーを削除しますか?",
|
||||||
"range": "",
|
"range": "",
|
||||||
"raw": "",
|
"raw": "",
|
||||||
"reaction_options": "",
|
"reaction_options": "",
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
"background_task_job": "백그라운드 작업",
|
"background_task_job": "백그라운드 작업",
|
||||||
"check_all": "모두 확인",
|
"check_all": "모두 확인",
|
||||||
"cleared_jobs": "{job} 작업 중단됨",
|
"cleared_jobs": "{job} 작업 중단됨",
|
||||||
"config_set_by_file": "구성 파일의 설정이 적용되었습니다.",
|
"config_set_by_file": "업로드한 설정 파일이 현재 설정에 적용됩니다.",
|
||||||
"confirm_delete_library": "{library} 라이브러리를 삭제하시겠습니까?",
|
"confirm_delete_library": "{library} 라이브러리를 삭제하시겠습니까?",
|
||||||
"confirm_delete_library_assets": "이 라이브러리를 삭제하시겠습니까? Immich에서 항목 {count, plural, one {#개} other {#개}}가 삭제되며 되돌릴 수 없습니다. 원본 파일은 삭제되지 않습니다.",
|
"confirm_delete_library_assets": "이 라이브러리를 삭제하시겠습니까? Immich에서 항목 {count, plural, one {#개} other {#개}}가 삭제되며 되돌릴 수 없습니다. 원본 파일은 삭제되지 않습니다.",
|
||||||
"confirm_email_below": "계속 진행하려면 아래에 \"{email}\" 입력",
|
"confirm_email_below": "계속 진행하려면 아래에 \"{email}\" 입력",
|
||||||
@@ -249,7 +249,8 @@
|
|||||||
"transcoding_acceleration_vaapi": "VAAPI",
|
"transcoding_acceleration_vaapi": "VAAPI",
|
||||||
"transcoding_accepted_audio_codecs": "허용된 오디오 코덱",
|
"transcoding_accepted_audio_codecs": "허용된 오디오 코덱",
|
||||||
"transcoding_accepted_audio_codecs_description": "트랜스코딩하지 않을 오디오 코덱을 선택합니다. 이 설정은 특정 트랜스코딩 정책에만 적용됩니다.",
|
"transcoding_accepted_audio_codecs_description": "트랜스코딩하지 않을 오디오 코덱을 선택합니다. 이 설정은 특정 트랜스코딩 정책에만 적용됩니다.",
|
||||||
"transcoding_accepted_containers": "Accepted containers",
|
"transcoding_accepted_containers": "허용된 컨테이너",
|
||||||
|
"transcoding_accepted_containers_description": "MP4로 변경하지 않을 동영상 컨테이너(확장자)를 선택합니다. 이 설정은 특정 트랜스코딩 정책에만 적용됩니다.",
|
||||||
"transcoding_accepted_video_codecs": "허용된 동영상 코덱",
|
"transcoding_accepted_video_codecs": "허용된 동영상 코덱",
|
||||||
"transcoding_accepted_video_codecs_description": "트랜스코딩하지 않을 동영상 코덱을 선택합니다. 이 설정은 특정 트랜스코딩 정책에만 적용됩니다.",
|
"transcoding_accepted_video_codecs_description": "트랜스코딩하지 않을 동영상 코덱을 선택합니다. 이 설정은 특정 트랜스코딩 정책에만 적용됩니다.",
|
||||||
"transcoding_advanced_options_description": "대부분의 사용자가 변경할 필요가 없는 옵션",
|
"transcoding_advanced_options_description": "대부분의 사용자가 변경할 필요가 없는 옵션",
|
||||||
@@ -409,7 +410,7 @@
|
|||||||
"bulk_delete_duplicates_confirmation": "비슷한 항목 {count, plural, one {#개} other {#개}}를 삭제하시겠습니까? 크기가 가장 큰 항목을 제외한 나머지 항목들이 영구적으로 삭제됩니다. 이 작업은 되돌릴 수 없습니다!",
|
"bulk_delete_duplicates_confirmation": "비슷한 항목 {count, plural, one {#개} other {#개}}를 삭제하시겠습니까? 크기가 가장 큰 항목을 제외한 나머지 항목들이 영구적으로 삭제됩니다. 이 작업은 되돌릴 수 없습니다!",
|
||||||
"bulk_keep_duplicates_confirmation": "비슷한 항목 {count, plural, one {#개} other {#개}}를 유지하시겠습니까? 파일을 삭제하지 않고 확인된 것으로 판단합니다.",
|
"bulk_keep_duplicates_confirmation": "비슷한 항목 {count, plural, one {#개} other {#개}}를 유지하시겠습니까? 파일을 삭제하지 않고 확인된 것으로 판단합니다.",
|
||||||
"bulk_trash_duplicates_confirmation": "비슷한 항목 {count, plural, one {#개} other {#개}}를 휴지통으로 이동하시겠습니까? 크기가 가장 큰 항목을 제외한 나머지 항목들이 모두 휴지통으로 이동됩니다.",
|
"bulk_trash_duplicates_confirmation": "비슷한 항목 {count, plural, one {#개} other {#개}}를 휴지통으로 이동하시겠습니까? 크기가 가장 큰 항목을 제외한 나머지 항목들이 모두 휴지통으로 이동됩니다.",
|
||||||
"buy": "라이선스 구입",
|
"buy": "Immich 구매",
|
||||||
"camera": "카메라",
|
"camera": "카메라",
|
||||||
"camera_brand": "카메라 제조사",
|
"camera_brand": "카메라 제조사",
|
||||||
"camera_model": "카메라 모델",
|
"camera_model": "카메라 모델",
|
||||||
@@ -437,6 +438,7 @@
|
|||||||
"city": "도시",
|
"city": "도시",
|
||||||
"clear": "지우기",
|
"clear": "지우기",
|
||||||
"clear_all": "모두 지우기",
|
"clear_all": "모두 지우기",
|
||||||
|
"clear_all_recent_searches": "검색 기록 전체 삭제",
|
||||||
"clear_message": "메시지 지우기",
|
"clear_message": "메시지 지우기",
|
||||||
"clear_value": "값 지우기",
|
"clear_value": "값 지우기",
|
||||||
"close": "닫기",
|
"close": "닫기",
|
||||||
@@ -575,6 +577,7 @@
|
|||||||
"error_adding_users_to_album": "앨범에 사용자를 추가하는 중 문제가 발생했습니다.",
|
"error_adding_users_to_album": "앨범에 사용자를 추가하는 중 문제가 발생했습니다.",
|
||||||
"error_deleting_shared_user": "공유한 사용자를 제거하는 중 문제가 발생했습니다.",
|
"error_deleting_shared_user": "공유한 사용자를 제거하는 중 문제가 발생했습니다.",
|
||||||
"error_downloading": "{filename} 다운로드 중 문제가 발생했습니다.",
|
"error_downloading": "{filename} 다운로드 중 문제가 발생했습니다.",
|
||||||
|
"error_hiding_buy_button": "구매 버튼을 숨기는 중 문제가 발생했습니다.",
|
||||||
"error_removing_assets_from_album": "앨범에서 항목을 제거하는 중 문제가 발생했습니다. 콘솔에서 세부 정보를 확인하세요.",
|
"error_removing_assets_from_album": "앨범에서 항목을 제거하는 중 문제가 발생했습니다. 콘솔에서 세부 정보를 확인하세요.",
|
||||||
"error_selecting_all_assets": "모든 항목을 선택하는 중 문제가 발생했습니다.",
|
"error_selecting_all_assets": "모든 항목을 선택하는 중 문제가 발생했습니다.",
|
||||||
"exclusion_pattern_already_exists": "이 제외 규칙은 이미 존재합니다.",
|
"exclusion_pattern_already_exists": "이 제외 규칙은 이미 존재합니다.",
|
||||||
@@ -585,6 +588,8 @@
|
|||||||
"failed_to_get_people": "인물을 불러오지 못했습니다.",
|
"failed_to_get_people": "인물을 불러오지 못했습니다.",
|
||||||
"failed_to_load_asset": "항목을 불러오지 못했습니다.",
|
"failed_to_load_asset": "항목을 불러오지 못했습니다.",
|
||||||
"failed_to_load_assets": "항목을 불러오지 못했습니다.",
|
"failed_to_load_assets": "항목을 불러오지 못했습니다.",
|
||||||
|
"failed_to_load_people": "인물을 불러오지 못했습니다.",
|
||||||
|
"failed_to_remove_product_key": "제품 키를 제거하지 못했습니다.",
|
||||||
"failed_to_stack_assets": "스택을 만들지 못했습니다.",
|
"failed_to_stack_assets": "스택을 만들지 못했습니다.",
|
||||||
"failed_to_unstack_assets": "스택을 해제하지 못했습니다.",
|
"failed_to_unstack_assets": "스택을 해제하지 못했습니다.",
|
||||||
"import_path_already_exists": "이 가져올 경로는 이미 존재합니다.",
|
"import_path_already_exists": "이 가져올 경로는 이미 존재합니다.",
|
||||||
@@ -738,7 +743,7 @@
|
|||||||
"host": "호스트",
|
"host": "호스트",
|
||||||
"hour": "시간",
|
"hour": "시간",
|
||||||
"image": "이미지",
|
"image": "이미지",
|
||||||
"image_alt_text_date": "{date}에 촬영됨",
|
"image_alt_text_date": "{date}에 촬영된 {isVideo, select, true {동영상} other {사진}}",
|
||||||
"image_alt_text_people": "{count, plural, =1 {{person1}님과 함께,} =2 {{person1} 및 {person2}님과 함께,} =3 {{person1}, {person2} 및 {person3}님과 함께,} other {{person1}, {person2}, 및 {others, number}명과 함께,}}",
|
"image_alt_text_people": "{count, plural, =1 {{person1}님과 함께,} =2 {{person1} 및 {person2}님과 함께,} =3 {{person1}, {person2} 및 {person3}님과 함께,} other {{person1}, {person2}, 및 {others, number}명과 함께,}}",
|
||||||
"image_alt_text_place": "{country}, {city}에서",
|
"image_alt_text_place": "{country}, {city}에서",
|
||||||
"image_taken": "{isVideo, select, true {동영상} other {사진}},",
|
"image_taken": "{isVideo, select, true {동영상} other {사진}},",
|
||||||
@@ -772,6 +777,7 @@
|
|||||||
"language_setting_description": "선호하는 언어 선택",
|
"language_setting_description": "선호하는 언어 선택",
|
||||||
"last_seen": "최근 활동",
|
"last_seen": "최근 활동",
|
||||||
"latest_version": "최신 버전",
|
"latest_version": "최신 버전",
|
||||||
|
"latitude": "위도",
|
||||||
"leave": "나가기",
|
"leave": "나가기",
|
||||||
"let_others_respond": "다른 사용자의 반응 허용",
|
"let_others_respond": "다른 사용자의 반응 허용",
|
||||||
"level": "레벨",
|
"level": "레벨",
|
||||||
@@ -801,6 +807,7 @@
|
|||||||
"login_has_been_disabled": "로그인이 비활성화되었습니다.",
|
"login_has_been_disabled": "로그인이 비활성화되었습니다.",
|
||||||
"logout_all_device_confirmation": "모든 기기에서 로그아웃하시겠습니까?",
|
"logout_all_device_confirmation": "모든 기기에서 로그아웃하시겠습니까?",
|
||||||
"logout_this_device_confirmation": "이 기기에서 로그아웃하시겠습니까?",
|
"logout_this_device_confirmation": "이 기기에서 로그아웃하시겠습니까?",
|
||||||
|
"longitude": "경도",
|
||||||
"look": "보기",
|
"look": "보기",
|
||||||
"loop_videos": "동영상 반복",
|
"loop_videos": "동영상 반복",
|
||||||
"loop_videos_description": "상세 보기에서 동영상을 자동으로 반복 재생합니다.",
|
"loop_videos_description": "상세 보기에서 동영상을 자동으로 반복 재생합니다.",
|
||||||
@@ -821,7 +828,7 @@
|
|||||||
"memories": "추억",
|
"memories": "추억",
|
||||||
"memories_setting_description": "추억 표시 설정 관리",
|
"memories_setting_description": "추억 표시 설정 관리",
|
||||||
"memory": "추억",
|
"memory": "추억",
|
||||||
"memory_lane_title": "기억 {title}",
|
"memory_lane_title": "{title} 추억",
|
||||||
"menu": "메뉴",
|
"menu": "메뉴",
|
||||||
"merge": "병합",
|
"merge": "병합",
|
||||||
"merge_people": "인물 병합",
|
"merge_people": "인물 병합",
|
||||||
@@ -955,6 +962,38 @@
|
|||||||
"profile_picture_set": "프로필 사진이 설정되었습니다.",
|
"profile_picture_set": "프로필 사진이 설정되었습니다.",
|
||||||
"public_album": "공개 앨범",
|
"public_album": "공개 앨범",
|
||||||
"public_share": "모든 사용자와 공유",
|
"public_share": "모든 사용자와 공유",
|
||||||
|
"purchase_account_info": "서포터",
|
||||||
|
"purchase_activated_subtitle": "Immich와 오픈 소스 소프트웨어를 지원해주셔서 감사합니다.",
|
||||||
|
"purchase_activated_time": "{date, date}에 활성화됨",
|
||||||
|
"purchase_activated_title": "제품 키가 성공적으로 활성화되었습니다.",
|
||||||
|
"purchase_button_activate": "활성화",
|
||||||
|
"purchase_button_buy": "구매",
|
||||||
|
"purchase_button_buy_immich": "Immich 구매",
|
||||||
|
"purchase_button_never_show_again": "다시 보지 않기",
|
||||||
|
"purchase_button_reminder": "30일 후에 다시 알림",
|
||||||
|
"purchase_button_remove_key": "제품 키 제거",
|
||||||
|
"purchase_button_select": "선택",
|
||||||
|
"purchase_failed_activation": "활성화하지 못했습니다. 이메일로 전송된 키를 정확히 입력했는지 확인하세요!",
|
||||||
|
"purchase_individual_description_1": "개인 사용자용",
|
||||||
|
"purchase_individual_description_2": "서포터 현황",
|
||||||
|
"purchase_individual_title": "개인",
|
||||||
|
"purchase_input_suggestion": "제품 키를 보유 중인가요? 아래에 제품 키를 입력하세요.",
|
||||||
|
"purchase_license_subtitle": "Immich를 구매하여 지속적인 개발에 도움을 주세요.",
|
||||||
|
"purchase_lifetime_description": "일회성 구매",
|
||||||
|
"purchase_option_title": "구매 옵션",
|
||||||
|
"purchase_panel_info_1": "Immich를 개발하는 데는 많은 시간과 노력이 필요합니다. 우리는 좋은 앱을 만들기 위해 풀 타임 개발자와 함께하고 있으며, 최종적으로 오픈 소스 소프트웨어와 비즈니스 행동 윤리가 개발자에게 지속 가능한 수입원을 제공하고 착취적인 클라우드 서비스를 대체할 수 있는 개인 정보 보호 생태계를 구축하는 것을 원합니다.",
|
||||||
|
"purchase_panel_info_2": "유료 기능을 추가하지 않기로 약속했기에, 이 구매는 어떠한 추가 기능도 제공하지 않습니다. 우리는 Immich의 지속적인 개발을 지원하는 사용자 여러분에게 의존하고 있습니다.",
|
||||||
|
"purchase_panel_title": "프로젝트 지원",
|
||||||
|
"purchase_per_server": "서버당",
|
||||||
|
"purchase_per_user": "사용자당",
|
||||||
|
"purchase_remove_product_key": "제품 키 제거",
|
||||||
|
"purchase_remove_product_key_prompt": "제품 키를 제거하시겠습니까?",
|
||||||
|
"purchase_remove_server_product_key": "서버 제품 키 제거",
|
||||||
|
"purchase_remove_server_product_key_prompt": "서버 제품 키를 제거하시겠습니까?",
|
||||||
|
"purchase_server_description_1": "전체 서버용",
|
||||||
|
"purchase_server_description_2": "서포터 현황",
|
||||||
|
"purchase_server_title": "서버",
|
||||||
|
"purchase_settings_server_activated": "서버 제품 키는 관리자가 관리합니다.",
|
||||||
"range": "",
|
"range": "",
|
||||||
"raw": "",
|
"raw": "",
|
||||||
"reaction_options": "반응 옵션",
|
"reaction_options": "반응 옵션",
|
||||||
@@ -1098,6 +1137,8 @@
|
|||||||
"show_person_options": "인물 옵션 표시",
|
"show_person_options": "인물 옵션 표시",
|
||||||
"show_progress_bar": "진행 표시줄 표시",
|
"show_progress_bar": "진행 표시줄 표시",
|
||||||
"show_search_options": "검색 옵션 표시",
|
"show_search_options": "검색 옵션 표시",
|
||||||
|
"show_supporter_badge": "서포터 배지",
|
||||||
|
"show_supporter_badge_description": "서포터 배지 표시",
|
||||||
"shuffle": "셔플",
|
"shuffle": "셔플",
|
||||||
"sign_out": "로그아웃",
|
"sign_out": "로그아웃",
|
||||||
"sign_up": "로그인",
|
"sign_up": "로그인",
|
||||||
@@ -1192,6 +1233,8 @@
|
|||||||
"user": "사용자",
|
"user": "사용자",
|
||||||
"user_id": "사용자 ID",
|
"user_id": "사용자 ID",
|
||||||
"user_liked": "{user}님이 {type, select, photo {이 사진을} video {이 동영상을} asset {이 항목을} other {이 항목을}} 좋아합니다.",
|
"user_liked": "{user}님이 {type, select, photo {이 사진을} video {이 동영상을} asset {이 항목을} other {이 항목을}} 좋아합니다.",
|
||||||
|
"user_purchase_settings": "결제",
|
||||||
|
"user_purchase_settings_description": "구매한 항목 관리",
|
||||||
"user_role_set": "{user}님에게 {role} 역할을 설정했습니다.",
|
"user_role_set": "{user}님에게 {role} 역할을 설정했습니다.",
|
||||||
"user_usage_detail": "사용자 사용량 상세",
|
"user_usage_detail": "사용자 사용량 상세",
|
||||||
"username": "계정명",
|
"username": "계정명",
|
||||||
|
|||||||
@@ -410,7 +410,7 @@
|
|||||||
"bulk_delete_duplicates_confirmation": "Weet je zeker dat je {count, plural, one {# duplicate asset} other {# duplicate assets}} in bulk wilt verwijderen? Dit zal de grootste asset van elke groep behouden en alle andere duplicaten permanent verwijderen. Je kunt deze actie niet ongedaan maken!",
|
"bulk_delete_duplicates_confirmation": "Weet je zeker dat je {count, plural, one {# duplicate asset} other {# duplicate assets}} in bulk wilt verwijderen? Dit zal de grootste asset van elke groep behouden en alle andere duplicaten permanent verwijderen. Je kunt deze actie niet ongedaan maken!",
|
||||||
"bulk_keep_duplicates_confirmation": "Weet je zeker dat je {count, plural, one {# duplicate asset} other {# duplicate assets}} wilt behouden? Dit zal alle groepen met duplicaten oplossen zonder iets te verwijderen.",
|
"bulk_keep_duplicates_confirmation": "Weet je zeker dat je {count, plural, one {# duplicate asset} other {# duplicate assets}} wilt behouden? Dit zal alle groepen met duplicaten oplossen zonder iets te verwijderen.",
|
||||||
"bulk_trash_duplicates_confirmation": "Weet je zeker dat je {count, plural, one {# duplicate asset} other {# duplicate assets}} in bulk naar de prullenbak wilt verplaatsen? Dit zal de grootste asset van elke groep behouden en alle andere duplicaten naar de prullenbak verplaatsen.",
|
"bulk_trash_duplicates_confirmation": "Weet je zeker dat je {count, plural, one {# duplicate asset} other {# duplicate assets}} in bulk naar de prullenbak wilt verplaatsen? Dit zal de grootste asset van elke groep behouden en alle andere duplicaten naar de prullenbak verplaatsen.",
|
||||||
"buy": "Licentie kopen",
|
"buy": "Koop Immich",
|
||||||
"camera": "Camera",
|
"camera": "Camera",
|
||||||
"camera_brand": "Cameramerk",
|
"camera_brand": "Cameramerk",
|
||||||
"camera_model": "Cameramodel",
|
"camera_model": "Cameramodel",
|
||||||
@@ -438,6 +438,7 @@
|
|||||||
"city": "Stad",
|
"city": "Stad",
|
||||||
"clear": "Wissen",
|
"clear": "Wissen",
|
||||||
"clear_all": "Alles wissen",
|
"clear_all": "Alles wissen",
|
||||||
|
"clear_all_recent_searches": "Wis alle recente zoekopdrachten",
|
||||||
"clear_message": "Bericht wissen",
|
"clear_message": "Bericht wissen",
|
||||||
"clear_value": "Waarde wissen",
|
"clear_value": "Waarde wissen",
|
||||||
"close": "Sluiten",
|
"close": "Sluiten",
|
||||||
@@ -576,6 +577,7 @@
|
|||||||
"error_adding_users_to_album": "Fout bij toevoegen gebruikers aan album",
|
"error_adding_users_to_album": "Fout bij toevoegen gebruikers aan album",
|
||||||
"error_deleting_shared_user": "Fout bij verwijderen gedeelde gebruiker",
|
"error_deleting_shared_user": "Fout bij verwijderen gedeelde gebruiker",
|
||||||
"error_downloading": "Fout bij downloaden {filename}",
|
"error_downloading": "Fout bij downloaden {filename}",
|
||||||
|
"error_hiding_buy_button": "Fout bij het verbergen van de koop knop",
|
||||||
"error_removing_assets_from_album": "Fout bij verwijderen van assets uit album, controleer de console voor meer details",
|
"error_removing_assets_from_album": "Fout bij verwijderen van assets uit album, controleer de console voor meer details",
|
||||||
"error_selecting_all_assets": "Fout bij selecteren van alle assets",
|
"error_selecting_all_assets": "Fout bij selecteren van alle assets",
|
||||||
"exclusion_pattern_already_exists": "Dit uitsluitingspatroon bestaat al.",
|
"exclusion_pattern_already_exists": "Dit uitsluitingspatroon bestaat al.",
|
||||||
@@ -586,6 +588,8 @@
|
|||||||
"failed_to_get_people": "Fout bij ophalen van mensen",
|
"failed_to_get_people": "Fout bij ophalen van mensen",
|
||||||
"failed_to_load_asset": "Kan asset niet laden",
|
"failed_to_load_asset": "Kan asset niet laden",
|
||||||
"failed_to_load_assets": "Kan assets niet laden",
|
"failed_to_load_assets": "Kan assets niet laden",
|
||||||
|
"failed_to_load_people": "Kan mensen niet laden",
|
||||||
|
"failed_to_remove_product_key": "Er is een fout opgetreden bij het verwijderen van de product sleutel",
|
||||||
"failed_to_stack_assets": "Fout bij stapelen van assets",
|
"failed_to_stack_assets": "Fout bij stapelen van assets",
|
||||||
"failed_to_unstack_assets": "Fout bij ontstapelen van assets",
|
"failed_to_unstack_assets": "Fout bij ontstapelen van assets",
|
||||||
"import_path_already_exists": "Dit import-pad bestaat al.",
|
"import_path_already_exists": "Dit import-pad bestaat al.",
|
||||||
@@ -739,7 +743,16 @@
|
|||||||
"host": "Host",
|
"host": "Host",
|
||||||
"hour": "Uur",
|
"hour": "Uur",
|
||||||
"image": "Afbeelding",
|
"image": "Afbeelding",
|
||||||
"image_alt_text_date": "op {date}",
|
"image_alt_text_date": "{isVideo, select, true {Video} other {Image}} genomen op {date}",
|
||||||
|
"image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} genomen met {person1} op {date}",
|
||||||
|
"image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} genomen met {person1} en {person2} op {date}",
|
||||||
|
"image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} genomen met {person1}, {person2}, en {person3} op {date}",
|
||||||
|
"image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} genomen met {person1}, {person2}, en {additionalCount, number} anderen op {date}",
|
||||||
|
"image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} genomen in {city}, {country} op {date}",
|
||||||
|
"image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} genomen in {city}, {country} met {person1} op {date}",
|
||||||
|
"image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} genomen in {city}, {country} met {person1} en {person2} op {date}",
|
||||||
|
"image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} genomen in {city}, {country} met {person1}, {person2}, en {person3} op {date}",
|
||||||
|
"image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} genomen in {city}, {country} met {person1}, {person2}, en {additionalCount, number} anderen op {date}",
|
||||||
"image_alt_text_people": "{count, plural, =1 {met {person1}} =2 {met {person1} en {person2}} =3 {met {person1}, {person2} en {person3}} other {met {person1}, {person2} en {others, number} anderen}}",
|
"image_alt_text_people": "{count, plural, =1 {met {person1}} =2 {met {person1} en {person2}} =3 {met {person1}, {person2} en {person3}} other {met {person1}, {person2} en {others, number} anderen}}",
|
||||||
"image_alt_text_place": "in {city}, {country}",
|
"image_alt_text_place": "in {city}, {country}",
|
||||||
"image_taken": "{isVideo, select, true {Video gemaakt} other {Afbeelding genomen}}",
|
"image_taken": "{isVideo, select, true {Video gemaakt} other {Afbeelding genomen}}",
|
||||||
@@ -860,6 +873,7 @@
|
|||||||
"name": "Naam",
|
"name": "Naam",
|
||||||
"name_or_nickname": "Naam of gebruikersnaam",
|
"name_or_nickname": "Naam of gebruikersnaam",
|
||||||
"never": "Nooit",
|
"never": "Nooit",
|
||||||
|
"new_album": "Nieuw album",
|
||||||
"new_api_key": "Nieuwe API sleutel",
|
"new_api_key": "Nieuwe API sleutel",
|
||||||
"new_password": "Nieuw wachtwoord",
|
"new_password": "Nieuw wachtwoord",
|
||||||
"new_person": "Nieuw persoon",
|
"new_person": "Nieuw persoon",
|
||||||
@@ -975,6 +989,38 @@
|
|||||||
"profile_picture_set": "Profielfoto ingesteld.",
|
"profile_picture_set": "Profielfoto ingesteld.",
|
||||||
"public_album": "Openbaar album",
|
"public_album": "Openbaar album",
|
||||||
"public_share": "Publieke deellink",
|
"public_share": "Publieke deellink",
|
||||||
|
"purchase_account_info": "Supporter",
|
||||||
|
"purchase_activated_subtitle": "Bedankt voor het ondersteunen van Immich en open-source software",
|
||||||
|
"purchase_activated_time": "Geactiveerd op {date, date}",
|
||||||
|
"purchase_activated_title": "Je sleutel is succesvol geactiveerd",
|
||||||
|
"purchase_button_activate": "Activeren",
|
||||||
|
"purchase_button_buy": "Kopen",
|
||||||
|
"purchase_button_buy_immich": "Koop Immich",
|
||||||
|
"purchase_button_never_show_again": "Nooit meer tonen",
|
||||||
|
"purchase_button_reminder": "Herinner mij over 30 dagen",
|
||||||
|
"purchase_button_remove_key": "Sleutel verwijderen",
|
||||||
|
"purchase_button_select": "Selecteren",
|
||||||
|
"purchase_failed_activation": "Activeren mislukt! Controleer je e-mail voor de juiste productsleutel!",
|
||||||
|
"purchase_individual_description_1": "Voor een gebruiker",
|
||||||
|
"purchase_individual_description_2": "Supporter badge",
|
||||||
|
"purchase_individual_title": "Gebruiker",
|
||||||
|
"purchase_input_suggestion": "Heb je een productsleutel? Voer de sleutel hieronder in",
|
||||||
|
"purchase_license_subtitle": "Koop Immich om de verdere ontwikkeling van de service te ondersteunen",
|
||||||
|
"purchase_lifetime_description": "Levenslange aankoop",
|
||||||
|
"purchase_option_title": "AANKOOP MOGELIJKHEDEN",
|
||||||
|
"purchase_panel_info_1": "Het bouwen van Immich kost veel tijd en moeite, en we hebben fulltime engineers die eraan werken om het zo goed mogelijk te maken. Onze missie is om open-source software en ethische bedrijfspraktijken een duurzame inkomstenbron te laten worden voor ontwikkelaars en een ecosysteem te creëren dat de privacy respecteert met echte alternatieven voor uitbuitende cloudservices.",
|
||||||
|
"purchase_panel_info_2": "Omdat we ons inzetten om geen paywalls toe te voegen, krijg je met deze aankoop geen extra functies in Immich. We vertrouwen op gebruikers zoals jij om de verdere ontwikkeling van Immich te ondersteunen.",
|
||||||
|
"purchase_panel_title": "Steun het project",
|
||||||
|
"purchase_per_server": "Per server",
|
||||||
|
"purchase_per_user": "Per gebruiker",
|
||||||
|
"purchase_remove_product_key": "Verwijder product sleutel",
|
||||||
|
"purchase_remove_product_key_prompt": "Weet je zeker dat je de product sleutel wilt verwijderen?",
|
||||||
|
"purchase_remove_server_product_key": "Verwijder server product sleutel",
|
||||||
|
"purchase_remove_server_product_key_prompt": "Weet je zeker dat je de server product sleutel wilt verwijderen?",
|
||||||
|
"purchase_server_description_1": "Voor de volledige server",
|
||||||
|
"purchase_server_description_2": "Supporter badge",
|
||||||
|
"purchase_server_title": "Server",
|
||||||
|
"purchase_settings_server_activated": "De productcode van de server wordt beheerd door de beheerder",
|
||||||
"range": "",
|
"range": "",
|
||||||
"raw": "",
|
"raw": "",
|
||||||
"reaction_options": "Reactie opties",
|
"reaction_options": "Reactie opties",
|
||||||
@@ -1020,6 +1066,7 @@
|
|||||||
"reset_people_visibility": "Zichtbaarheid mensen resetten",
|
"reset_people_visibility": "Zichtbaarheid mensen resetten",
|
||||||
"reset_settings_to_default": "",
|
"reset_settings_to_default": "",
|
||||||
"reset_to_default": "Resetten naar standaard",
|
"reset_to_default": "Resetten naar standaard",
|
||||||
|
"resolve_duplicates": "Duplicaten oplossen",
|
||||||
"resolved_all_duplicates": "Alle duplicaten verwerkt",
|
"resolved_all_duplicates": "Alle duplicaten verwerkt",
|
||||||
"restore": "Herstellen",
|
"restore": "Herstellen",
|
||||||
"restore_all": "Herstel alle",
|
"restore_all": "Herstel alle",
|
||||||
@@ -1064,6 +1111,7 @@
|
|||||||
"see_all_people": "Bekijk alle mensen",
|
"see_all_people": "Bekijk alle mensen",
|
||||||
"select_album_cover": "Selecteer album cover",
|
"select_album_cover": "Selecteer album cover",
|
||||||
"select_all": "Alles selecteren",
|
"select_all": "Alles selecteren",
|
||||||
|
"select_all_duplicates": "Selecteer alle duplicaten",
|
||||||
"select_avatar_color": "Selecteer avatarkleur",
|
"select_avatar_color": "Selecteer avatarkleur",
|
||||||
"select_face": "Selecteer gezicht",
|
"select_face": "Selecteer gezicht",
|
||||||
"select_featured_photo": "Selecteer uitgelichte foto",
|
"select_featured_photo": "Selecteer uitgelichte foto",
|
||||||
@@ -1118,6 +1166,8 @@
|
|||||||
"show_person_options": "Toon persoonopties",
|
"show_person_options": "Toon persoonopties",
|
||||||
"show_progress_bar": "Toon voortgangsbalk",
|
"show_progress_bar": "Toon voortgangsbalk",
|
||||||
"show_search_options": "Zoekopties weergeven",
|
"show_search_options": "Zoekopties weergeven",
|
||||||
|
"show_supporter_badge": "Supporter badge",
|
||||||
|
"show_supporter_badge_description": "Toon een supporterbadge",
|
||||||
"shuffle": "Willekeurig",
|
"shuffle": "Willekeurig",
|
||||||
"sign_out": "Uitloggen",
|
"sign_out": "Uitloggen",
|
||||||
"sign_up": "Registreren",
|
"sign_up": "Registreren",
|
||||||
@@ -1191,6 +1241,7 @@
|
|||||||
"unnamed_share": "Naamloze deellink",
|
"unnamed_share": "Naamloze deellink",
|
||||||
"unsaved_change": "Niet-opgeslagen wijziging",
|
"unsaved_change": "Niet-opgeslagen wijziging",
|
||||||
"unselect_all": "Alles deselecteren",
|
"unselect_all": "Alles deselecteren",
|
||||||
|
"unselect_all_duplicates": "Deselecteer alle duplicaten",
|
||||||
"unstack": "Ontstapelen",
|
"unstack": "Ontstapelen",
|
||||||
"unstacked_assets_count": "{count, plural, one {# asset} other {# assets}} ontstapeld",
|
"unstacked_assets_count": "{count, plural, one {# asset} other {# assets}} ontstapeld",
|
||||||
"untracked_files": "Niet bijgehouden bestanden",
|
"untracked_files": "Niet bijgehouden bestanden",
|
||||||
@@ -1214,6 +1265,8 @@
|
|||||||
"user_license_settings": "Licentie",
|
"user_license_settings": "Licentie",
|
||||||
"user_license_settings_description": "Beheer je licentie",
|
"user_license_settings_description": "Beheer je licentie",
|
||||||
"user_liked": "{user} heeft {type, select, photo {deze foto} video {deze video} asset {deze asset} other {dit}} geliket",
|
"user_liked": "{user} heeft {type, select, photo {deze foto} video {deze video} asset {deze asset} other {dit}} geliket",
|
||||||
|
"user_purchase_settings": "Kopen",
|
||||||
|
"user_purchase_settings_description": "Beheer je aankoop",
|
||||||
"user_role_set": "{user} instellen als {role}",
|
"user_role_set": "{user} instellen als {role}",
|
||||||
"user_usage_detail": "Gedetailleerd gebruik van gebruikers",
|
"user_usage_detail": "Gedetailleerd gebruik van gebruikers",
|
||||||
"username": "Gebruikersnaam",
|
"username": "Gebruikersnaam",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user