Compare commits

..

2 Commits

Author SHA1 Message Date
midzelis 206a07d8db refactor(web): improve CancellableTask naming and add state machine docs
- Rename 'LOADED' return status → 'SUCCESS' (domain-agnostic)
- Rename loaded → succeeded, loadedCallback → succeededCallback
- Rename waitUntilLoaded → waitUntilSucceeded
- Rename cancelToken → abortController (matches AbortController API)
- Rename executed → succeeded, loading → running
- Simplify cancellable downgrade logic
- Add state machine documentation comment

Change-Id: I701e0065d355fca4328d64b7ce42a6f06a6a6964
2026-04-06 18:22:24 +00:00
midzelis 0a93963041 fix(web): handle unhandled promise rejection in CancellableTask
When a concurrent caller awaits `this.complete` inside `execute()` and
`cancel()` is called, the promise rejects with `undefined` outside of any
try/catch, causing "Uncaught (in promise) undefined" console spam during
rapid timeline scrolling.

- Wrap the `await this.complete` path in try/catch, returning 'CANCELED'
- Guard the `finally` block to only null `cancelToken` if it still belongs
  to this call, preventing a race condition with `cancel()` to `init()`

Change-Id: I65764dd664eb408433fc6e5fc2be4df56a6a6964
2026-04-06 17:43:56 +00:00
74 changed files with 1058 additions and 1462 deletions
-1
View File
@@ -28,4 +28,3 @@ vite.config.js.timestamp-*
.pnpm-store .pnpm-store
.devcontainer/library .devcontainer/library
.devcontainer/.env* .devcontainer/.env*
*.tsbuildInfo
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.7.1", "version": "2.6.3",
"description": "Command Line Interface (CLI) for Immich", "description": "Command Line Interface (CLI) for Immich",
"type": "module", "type": "module",
"exports": "./dist/index.js", "exports": "./dist/index.js",
@@ -33,8 +33,8 @@
"mock-fs": "^5.2.0", "mock-fs": "^5.2.0",
"prettier": "^3.7.4", "prettier": "^3.7.4",
"prettier-plugin-organize-imports": "^4.0.0", "prettier-plugin-organize-imports": "^4.0.0",
"typescript": "^6.0.0", "typescript": "^5.3.3",
"typescript-eslint": "^8.58.0", "typescript-eslint": "^8.28.0",
"vite": "^8.0.0", "vite": "^8.0.0",
"vitest": "^4.0.0", "vitest": "^4.0.0",
"vitest-fetch-mock": "^0.4.0", "vitest-fetch-mock": "^0.4.0",
+2 -5
View File
@@ -15,11 +15,8 @@
"incremental": true, "incremental": true,
"skipLibCheck": true, "skipLibCheck": true,
"esModuleInterop": true, "esModuleInterop": true,
"rootDir": "./src", "baseUrl": "./",
"paths": {
"src/*": ["./src/*"],
},
"types": ["vitest/globals"] "types": ["vitest/globals"]
}, },
"exclude": ["dist", "node_modules", "vite.config.ts"] "exclude": ["dist", "node_modules"]
} }
+1 -1
View File
@@ -1,5 +1,5 @@
[tools] [tools]
terragrunt = "0.99.5" terragrunt = "0.99.4"
opentofu = "1.11.5" opentofu = "1.11.5"
[tasks."tg:fmt"] [tasks."tg:fmt"]
+1 -1
View File
@@ -156,7 +156,7 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9 image: docker.io/valkey/valkey:9@sha256:3eeb09785cd61ec8e3be35f8804c8892080f3ca21934d628abc24ee4ed1698f6
healthcheck: healthcheck:
test: redis-cli ping || exit 1 test: redis-cli ping || exit 1
+2 -2
View File
@@ -56,7 +56,7 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9 image: docker.io/valkey/valkey:9@sha256:3eeb09785cd61ec8e3be35f8804c8892080f3ca21934d628abc24ee4ed1698f6
healthcheck: healthcheck:
test: redis-cli ping || exit 1 test: redis-cli ping || exit 1
restart: always restart: always
@@ -85,7 +85,7 @@ services:
container_name: immich_prometheus container_name: immich_prometheus
ports: ports:
- 9090:9090 - 9090:9090
image: prom/prometheus@sha256:dda13e28bf95a5e5ca5b8ed56852006094c1c8e8871d9c9dbeed30aa6e55271f image: prom/prometheus@sha256:4a61322ac1103a0e3aea2a61ef1718422a48fa046441f299d71e660a3bc71ae9
volumes: volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml - ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus - prometheus-data:/prometheus
+1 -1
View File
@@ -61,7 +61,7 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9 image: docker.io/valkey/valkey:9@sha256:3eeb09785cd61ec8e3be35f8804c8892080f3ca21934d628abc24ee4ed1698f6
user: '1000:1000' user: '1000:1000'
security_opt: security_opt:
- no-new-privileges:true - no-new-privileges:true
+1 -1
View File
@@ -49,7 +49,7 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9 image: docker.io/valkey/valkey:9@sha256:3eeb09785cd61ec8e3be35f8804c8892080f3ca21934d628abc24ee4ed1698f6
healthcheck: healthcheck:
test: redis-cli ping || exit 1 test: redis-cli ping || exit 1
restart: always restart: always
+4 -4
View File
@@ -30,17 +30,17 @@
"postcss": "^8.4.25", "postcss": "^8.4.25",
"prism-react-renderer": "^2.3.1", "prism-react-renderer": "^2.3.1",
"raw-loader": "^4.0.2", "raw-loader": "^4.0.2",
"react": "^19.0.0", "react": "^18.0.0",
"react-dom": "^19.0.0", "react-dom": "^18.0.0",
"tailwindcss": "^3.2.4", "tailwindcss": "^3.2.4",
"url": "^0.11.0" "url": "^0.11.0"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "~3.9.0", "@docusaurus/module-type-aliases": "~3.9.0",
"@docusaurus/tsconfig": "^3.10.0", "@docusaurus/tsconfig": "^3.7.0",
"@docusaurus/types": "^3.7.0", "@docusaurus/types": "^3.7.0",
"prettier": "^3.7.4", "prettier": "^3.7.4",
"typescript": "^6.0.0" "typescript": "^5.1.6"
}, },
"browserslist": { "browserslist": {
"production": [ "production": [
-4
View File
@@ -1,8 +1,4 @@
[ [
{
"label": "v2.7.1",
"url": "https://docs.v2.7.1.archive.immich.app"
},
{ {
"label": "v2.6.3", "label": "v2.6.3",
"url": "https://docs.v2.6.3.archive.immich.app" "url": "https://docs.v2.6.3.archive.immich.app"
+5 -1
View File
@@ -1,4 +1,8 @@
{ {
// This file is not used in compilation. It is here just for a nice editor experience. // This file is not used in compilation. It is here just for a nice editor experience.
"extends": "@docusaurus/tsconfig" "extends": "@docusaurus/tsconfig",
"compilerOptions": {
"baseUrl": "."
}
} }
+1 -1
View File
@@ -44,7 +44,7 @@ services:
redis: redis:
container_name: immich-e2e-redis container_name: immich-e2e-redis
image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9 image: docker.io/valkey/valkey:9@sha256:3eeb09785cd61ec8e3be35f8804c8892080f3ca21934d628abc24ee4ed1698f6
healthcheck: healthcheck:
test: redis-cli ping || exit 1 test: redis-cli ping || exit 1
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich-e2e", "name": "immich-e2e",
"version": "2.7.1", "version": "2.6.3",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
@@ -35,7 +35,7 @@
"@types/node": "^24.12.0", "@types/node": "^24.12.0",
"@types/pg": "^8.15.1", "@types/pg": "^8.15.1",
"@types/pngjs": "^6.0.4", "@types/pngjs": "^6.0.4",
"@types/supertest": "^7.0.0", "@types/supertest": "^6.0.2",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"eslint": "^10.0.0", "eslint": "^10.0.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
@@ -51,7 +51,7 @@
"sharp": "^0.34.5", "sharp": "^0.34.5",
"socket.io-client": "^4.7.4", "socket.io-client": "^4.7.4",
"supertest": "^7.0.0", "supertest": "^7.0.0",
"typescript": "^6.0.0", "typescript": "^5.3.3",
"typescript-eslint": "^8.28.0", "typescript-eslint": "^8.28.0",
"utimes": "^5.2.1", "utimes": "^5.2.1",
"vite-tsconfig-paths": "^6.1.1", "vite-tsconfig-paths": "^6.1.1",
+1 -1
View File
@@ -1,5 +1,5 @@
import { BrowserContext } from '@playwright/test'; import { BrowserContext } from '@playwright/test';
import { playwrightHost } from 'src/../playwright.config'; import { playwrightHost } from 'playwright.config';
export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserId: string) => { export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserId: string) => {
await context.addCookies([ await context.addCookies([
+1 -3
View File
@@ -14,10 +14,8 @@
"outDir": "./dist", "outDir": "./dist",
"incremental": true, "incremental": true,
"skipLibCheck": true, "skipLibCheck": true,
"paths": {
"src/*": ["./src/*"]
},
"esModuleInterop": true, "esModuleInterop": true,
"baseUrl": "./"
}, },
"include": ["src/**/*.ts", "vitest*.config.ts"], "include": ["src/**/*.ts", "vitest*.config.ts"],
"exclude": ["dist", "node_modules"] "exclude": ["dist", "node_modules"]
-9
View File
@@ -849,12 +849,9 @@
"create_link_to_share": "إنشاء رابط للمشاركة", "create_link_to_share": "إنشاء رابط للمشاركة",
"create_link_to_share_description": "السماح لأي شخص لديه الرابط بمشاهدة الصورة (الصور) المحددة", "create_link_to_share_description": "السماح لأي شخص لديه الرابط بمشاهدة الصورة (الصور) المحددة",
"create_new": "انشاء جديد", "create_new": "انشاء جديد",
"create_new_face": "إنشاء وجه جديد",
"create_new_person": "إنشاء شخص جديد", "create_new_person": "إنشاء شخص جديد",
"create_new_person_hint": "تعيين المحتويات المحددة لشخص جديد", "create_new_person_hint": "تعيين المحتويات المحددة لشخص جديد",
"create_new_user": "إنشاء مستخدم جديد", "create_new_user": "إنشاء مستخدم جديد",
"create_person": "إنشاء شخص",
"create_person_subtitle": "أضف اسماً للوجه المحدد لإنشاء الشخص الجديد والإشارة إليه",
"create_shared_album_page_share_add_assets": "إضافة الأصول", "create_shared_album_page_share_add_assets": "إضافة الأصول",
"create_shared_album_page_share_select_photos": "حدد الصور", "create_shared_album_page_share_select_photos": "حدد الصور",
"create_shared_link": "انشاء رابط مشترك", "create_shared_link": "انشاء رابط مشترك",
@@ -895,8 +892,6 @@
"day": "يوم", "day": "يوم",
"days": "ايام", "days": "ايام",
"deduplicate_all": "إلغاء تكرار الكل", "deduplicate_all": "إلغاء تكرار الكل",
"default_locale": "الإعدادات المحلية الافتراضية",
"default_locale_description": "تنسيق التواريخ والأرقام بناءً على الإعدادات المحلية للمتصفح",
"delete": "حذف", "delete": "حذف",
"delete_action_confirmation_message": "هل انت متأكد من حذف هذا الملف؟ هذا سؤدي الى نقل الملف الى سلة مهملات الخادم وسيتم اشعارك ان كنت تريد حذفه على الجهاز", "delete_action_confirmation_message": "هل انت متأكد من حذف هذا الملف؟ هذا سؤدي الى نقل الملف الى سلة مهملات الخادم وسيتم اشعارك ان كنت تريد حذفه على الجهاز",
"delete_action_prompt": "تم حذف {count}", "delete_action_prompt": "تم حذف {count}",
@@ -1389,11 +1384,9 @@
"library_page_sort_title": "عنوان الألبوم", "library_page_sort_title": "عنوان الألبوم",
"licenses": "رُخَص", "licenses": "رُخَص",
"light": "المضيئ", "light": "المضيئ",
"light_theme": "التبديل إلى المظهر الفاتح",
"like": "اعجاب", "like": "اعجاب",
"like_deleted": "تم حذف الإعجاب", "like_deleted": "تم حذف الإعجاب",
"link_motion_video": "رابط فيديو الحركة", "link_motion_video": "رابط فيديو الحركة",
"link_to_docs": "لمزيد من المعلومات، يُرجى الرجوع إلى <link>الوثائق</link>.",
"link_to_oauth": "الربط مع OAuth", "link_to_oauth": "الربط مع OAuth",
"linked_oauth_account": "حساب مرتبط بـ OAuth", "linked_oauth_account": "حساب مرتبط بـ OAuth",
"list": "قائمة", "list": "قائمة",
@@ -2217,7 +2210,6 @@
"tag": "العلامة", "tag": "العلامة",
"tag_assets": "أصول العلامة", "tag_assets": "أصول العلامة",
"tag_created": "تم إنشاء العلامة: {tag}", "tag_created": "تم إنشاء العلامة: {tag}",
"tag_face": "علِّم الوجه",
"tag_feature_description": "تصفح الصور ومقاطع الفيديو المجمعة حسب مواضيع العلامات المنطقية", "tag_feature_description": "تصفح الصور ومقاطع الفيديو المجمعة حسب مواضيع العلامات المنطقية",
"tag_not_found_question": "لا يمكن العثور على علامة؟ <link>قم بإنشاء علامة جديدة.</link>", "tag_not_found_question": "لا يمكن العثور على علامة؟ <link>قم بإنشاء علامة جديدة.</link>",
"tag_people": "علِّم الأشخاص", "tag_people": "علِّم الأشخاص",
@@ -2399,7 +2391,6 @@
"viewer_remove_from_stack": "حذف من الكومه أو المجموعة", "viewer_remove_from_stack": "حذف من الكومه أو المجموعة",
"viewer_stack_use_as_main_asset": "استخدم كأصل رئيسي", "viewer_stack_use_as_main_asset": "استخدم كأصل رئيسي",
"viewer_unstack": "فك الكومه", "viewer_unstack": "فك الكومه",
"visibility": "إمكانية الرؤية",
"visibility_changed": "الرؤية تغيرت لـ {count, plural, one {شخص واحد} other {# عدة أشخاص}}", "visibility_changed": "الرؤية تغيرت لـ {count, plural, one {شخص واحد} other {# عدة أشخاص}}",
"visual": "مرئي", "visual": "مرئي",
"visual_builder": "اداة نشاء مرئية", "visual_builder": "اداة نشاء مرئية",
-4
View File
@@ -849,12 +849,9 @@
"create_link_to_share": "Създаване на линк за споделяне", "create_link_to_share": "Създаване на линк за споделяне",
"create_link_to_share_description": "Позволете на всеки, който има линк, да види избраната(ите) снимка(и)", "create_link_to_share_description": "Позволете на всеки, който има линк, да види избраната(ите) снимка(и)",
"create_new": "СЪЗДАЙ НОВ", "create_new": "СЪЗДАЙ НОВ",
"create_new_face": "Създай ново лице",
"create_new_person": "Създаване на ново лице", "create_new_person": "Създаване на ново лице",
"create_new_person_hint": "Присвойте избраните файлове на нов човек", "create_new_person_hint": "Присвойте избраните файлове на нов човек",
"create_new_user": "Създаване на нов потребител", "create_new_user": "Създаване на нов потребител",
"create_person": "Създай човек",
"create_person_subtitle": "Добави име към избраното лице за да създадеш и да сложиш етикет на новия човек",
"create_shared_album_page_share_add_assets": "ДОБАВИ ОБЕКТИ", "create_shared_album_page_share_add_assets": "ДОБАВИ ОБЕКТИ",
"create_shared_album_page_share_select_photos": "Избери снимки", "create_shared_album_page_share_select_photos": "Избери снимки",
"create_shared_link": "Създай линк за споделяне", "create_shared_link": "Създай линк за споделяне",
@@ -2217,7 +2214,6 @@
"tag": "Таг", "tag": "Таг",
"tag_assets": "Тагни елементи", "tag_assets": "Тагни елементи",
"tag_created": "Създаден етикет: {tag}", "tag_created": "Създаден етикет: {tag}",
"tag_face": "Отбележи лице",
"tag_feature_description": "Разглеждане на снимки и видеоклипове, групирани по теми с логически тагове", "tag_feature_description": "Разглеждане на снимки и видеоклипове, групирани по теми с логически тагове",
"tag_not_found_question": "Не можете да намерите етикет? <link>Създайте нов етикет.</link>", "tag_not_found_question": "Не можете да намерите етикет? <link>Създайте нов етикет.</link>",
"tag_people": "Отбележи Хора", "tag_people": "Отбележи Хора",
-4
View File
@@ -849,12 +849,9 @@
"create_link_to_share": "Crear enllaç per compartir", "create_link_to_share": "Crear enllaç per compartir",
"create_link_to_share_description": "Deixa que qualsevol persona amb l'enllaç vegi les fotos seleccionades", "create_link_to_share_description": "Deixa que qualsevol persona amb l'enllaç vegi les fotos seleccionades",
"create_new": "CREAR NOU", "create_new": "CREAR NOU",
"create_new_face": "Crea una nova cara",
"create_new_person": "Crea una nova persona", "create_new_person": "Crea una nova persona",
"create_new_person_hint": "Assigna els elements seleccionats a una persona nova", "create_new_person_hint": "Assigna els elements seleccionats a una persona nova",
"create_new_user": "Crea un usuari nou", "create_new_user": "Crea un usuari nou",
"create_person": "Crea una persona",
"create_person_subtitle": "Afegeix un nom a la cara seleccionada per crear i etiquetar la nova persona",
"create_shared_album_page_share_add_assets": "AFEGEIX ELEMENTS", "create_shared_album_page_share_add_assets": "AFEGEIX ELEMENTS",
"create_shared_album_page_share_select_photos": "Escull fotografies", "create_shared_album_page_share_select_photos": "Escull fotografies",
"create_shared_link": "Crea un enllaç compartit", "create_shared_link": "Crea un enllaç compartit",
@@ -2217,7 +2214,6 @@
"tag": "Etiqueta", "tag": "Etiqueta",
"tag_assets": "Etiquetar actius", "tag_assets": "Etiquetar actius",
"tag_created": "Etiqueta creada: {tag}", "tag_created": "Etiqueta creada: {tag}",
"tag_face": "Etiqueta una cara",
"tag_feature_description": "Exploreu fotos i vídeos agrupats per temes d'etiquetes lògiques", "tag_feature_description": "Exploreu fotos i vídeos agrupats per temes d'etiquetes lògiques",
"tag_not_found_question": "No trobeu una etiqueta? <link>Crear una nova etiqueta.</link>", "tag_not_found_question": "No trobeu una etiqueta? <link>Crear una nova etiqueta.</link>",
"tag_people": "Etiquetar personas", "tag_people": "Etiquetar personas",
+4 -8
View File
@@ -541,7 +541,7 @@
"app_settings": "App-Einstellungen", "app_settings": "App-Einstellungen",
"app_stores": "App Stores", "app_stores": "App Stores",
"app_update_available": "App Update verfügbar", "app_update_available": "App Update verfügbar",
"appears_in": "Enthalten in", "appears_in": "Erscheint in",
"apply_count": "Anwenden ({count, number})", "apply_count": "Anwenden ({count, number})",
"archive": "Archiv", "archive": "Archiv",
"archive_action_prompt": "{count} zum Archiv hinzugefügt", "archive_action_prompt": "{count} zum Archiv hinzugefügt",
@@ -812,8 +812,8 @@
"confirm_keep_this_delete_others": "Alle anderen Dateien im Stapel bis auf diese werden gelöscht. Bist du sicher, dass du fortfahren möchten?", "confirm_keep_this_delete_others": "Alle anderen Dateien im Stapel bis auf diese werden gelöscht. Bist du sicher, dass du fortfahren möchten?",
"confirm_new_pin_code": "Neuen PIN-Code bestätigen", "confirm_new_pin_code": "Neuen PIN-Code bestätigen",
"confirm_password": "Passwort bestätigen", "confirm_password": "Passwort bestätigen",
"confirm_tag_face": "Wollen Sie dieses Gesicht mit {name} taggen?", "confirm_tag_face": "Wollen Sie dieses Gesicht mit {name} markieren?",
"confirm_tag_face_unnamed": "Möchten Sie dieses Gesicht taggen?", "confirm_tag_face_unnamed": "Möchten Sie dieses Gesicht markieren?",
"connected_device": "Verbundenes Gerät", "connected_device": "Verbundenes Gerät",
"connected_to": "Verbunden mit", "connected_to": "Verbunden mit",
"contain": "Vollständig", "contain": "Vollständig",
@@ -849,12 +849,9 @@
"create_link_to_share": "Link zum Teilen erstellen", "create_link_to_share": "Link zum Teilen erstellen",
"create_link_to_share_description": "Lass jeden mit dem Link die ausgewählten Fotos sehen", "create_link_to_share_description": "Lass jeden mit dem Link die ausgewählten Fotos sehen",
"create_new": "NEUES ERSTELLEN", "create_new": "NEUES ERSTELLEN",
"create_new_face": "Neues Gesicht erstellen",
"create_new_person": "Neue Person anlegen", "create_new_person": "Neue Person anlegen",
"create_new_person_hint": "Ausgewählte Dateien einer neuen Person zuweisen", "create_new_person_hint": "Ausgewählte Dateien einer neuen Person zuweisen",
"create_new_user": "Neuen Nutzer erstellen", "create_new_user": "Neuen Nutzer erstellen",
"create_person": "Person anlegen",
"create_person_subtitle": "Gib dem gewählten Gesicht einen Namen um die neue Person zu erstellen und zu taggen",
"create_shared_album_page_share_add_assets": "INHALTE HINZUFÜGEN", "create_shared_album_page_share_add_assets": "INHALTE HINZUFÜGEN",
"create_shared_album_page_share_select_photos": "Fotos auswählen", "create_shared_album_page_share_select_photos": "Fotos auswählen",
"create_shared_link": "Geteilten Link erstellen", "create_shared_link": "Geteilten Link erstellen",
@@ -1038,7 +1035,7 @@
"error_loading_partners": "Fehler beim Laden der Partner: {error}", "error_loading_partners": "Fehler beim Laden der Partner: {error}",
"error_retrieving_asset_information": "Fehler beim Abruf der Dateiinformationen", "error_retrieving_asset_information": "Fehler beim Abruf der Dateiinformationen",
"error_saving_image": "Fehler: {error}", "error_saving_image": "Fehler: {error}",
"error_tag_face_bounding_box": "Fehler beim Taggen des Gesichts - Begrenzungen können nicht abgerufen werden", "error_tag_face_bounding_box": "Fehler beim Markieren des Gesichts - Begrenzungen können nicht abgerufen werden",
"error_title": "Fehler - Etwas ist schief gelaufen", "error_title": "Fehler - Etwas ist schief gelaufen",
"error_while_navigating": "Fehler beim Navigieren zur Datei", "error_while_navigating": "Fehler beim Navigieren zur Datei",
"errors": { "errors": {
@@ -2217,7 +2214,6 @@
"tag": "Tag", "tag": "Tag",
"tag_assets": "Dateien taggen", "tag_assets": "Dateien taggen",
"tag_created": "Tag erstellt: {tag}", "tag_created": "Tag erstellt: {tag}",
"tag_face": "Gesicht taggen",
"tag_feature_description": "Durchsuchen von Fotos und Videos, gruppiert nach logischen Tag-Themen", "tag_feature_description": "Durchsuchen von Fotos und Videos, gruppiert nach logischen Tag-Themen",
"tag_not_found_question": "Kein Tag vorhanden? <link>Erstelle einen neuen Tag.</link>", "tag_not_found_question": "Kein Tag vorhanden? <link>Erstelle einen neuen Tag.</link>",
"tag_people": "Personen taggen", "tag_people": "Personen taggen",
+1 -5
View File
@@ -849,12 +849,9 @@
"create_link_to_share": "Δημιουργία συνδέσμου για διαμοιρασμό", "create_link_to_share": "Δημιουργία συνδέσμου για διαμοιρασμό",
"create_link_to_share_description": "Επιτρέψτε σε οποιονδήποτε έχει τον σύνδεσμο να δει τη/τις επιλεγμένη/ες φωτογραφία/ες", "create_link_to_share_description": "Επιτρέψτε σε οποιονδήποτε έχει τον σύνδεσμο να δει τη/τις επιλεγμένη/ες φωτογραφία/ες",
"create_new": "ΔΗΜΙΟΥΡΓΙΑ ΝΕΟΥ", "create_new": "ΔΗΜΙΟΥΡΓΙΑ ΝΕΟΥ",
"create_new_face": "Δημιουργία νέου προσώπου", "create_new_person": "Δημιουργία νέου προσώπου",
"create_new_person": "Δημιουργία νέου ατόμου",
"create_new_person_hint": "Αντιστοίχιση των επιλεγμένων αρχείων σε ένα νέο πρόσωπο", "create_new_person_hint": "Αντιστοίχιση των επιλεγμένων αρχείων σε ένα νέο πρόσωπο",
"create_new_user": "Δημιουργία νέου χρήστη", "create_new_user": "Δημιουργία νέου χρήστη",
"create_person": "Δημιουργία ατόμου",
"create_person_subtitle": "Προσθέστε ένα όνομα στο επιλεγμένο πρόσωπο για να δημιουργηθεί και να επισημανθεί το νέο άτομο",
"create_shared_album_page_share_add_assets": "ΠΡΟΣΘΗΚΗ ΣΤΟΙΧΕΙΩΝ", "create_shared_album_page_share_add_assets": "ΠΡΟΣΘΗΚΗ ΣΤΟΙΧΕΙΩΝ",
"create_shared_album_page_share_select_photos": "Επιλέξτε Φωτογραφίες", "create_shared_album_page_share_select_photos": "Επιλέξτε Φωτογραφίες",
"create_shared_link": "Δημιουργία κοινόχρηστου συνδέσμου", "create_shared_link": "Δημιουργία κοινόχρηστου συνδέσμου",
@@ -2217,7 +2214,6 @@
"tag": "Ετικέτα", "tag": "Ετικέτα",
"tag_assets": "Ετικετοποίηση στοιχείων", "tag_assets": "Ετικετοποίηση στοιχείων",
"tag_created": "Δημιουργήθηκε ετικέτα: {tag}", "tag_created": "Δημιουργήθηκε ετικέτα: {tag}",
"tag_face": "Επισήμανση προσώπου",
"tag_feature_description": "Περιήγηση σε φωτογραφίες και βίντεο που είναι οργανωμένα σύμφωνα με λογικά θέματα ετικετών", "tag_feature_description": "Περιήγηση σε φωτογραφίες και βίντεο που είναι οργανωμένα σύμφωνα με λογικά θέματα ετικετών",
"tag_not_found_question": "Δεν μπορείτε να βρείτε μια ετικέτα; <link>Δημιουργήστε μια νέα ετικέτα.</link>", "tag_not_found_question": "Δεν μπορείτε να βρείτε μια ετικέτα; <link>Δημιουργήστε μια νέα ετικέτα.</link>",
"tag_people": "Επισήμανση ατόμων", "tag_people": "Επισήμανση ατόμων",
+2 -6
View File
@@ -849,12 +849,9 @@
"create_link_to_share": "Crear enlace compartido", "create_link_to_share": "Crear enlace compartido",
"create_link_to_share_description": "Permitir que cualquier persona con el enlace vea la(s) foto(s) seleccionada(s)", "create_link_to_share_description": "Permitir que cualquier persona con el enlace vea la(s) foto(s) seleccionada(s)",
"create_new": "CREAR NUEVO", "create_new": "CREAR NUEVO",
"create_new_face": "Crear nueva cara",
"create_new_person": "Crear nueva persona", "create_new_person": "Crear nueva persona",
"create_new_person_hint": "Asignar los recursos seleccionados a una nueva persona", "create_new_person_hint": "Asignar los recursos seleccionados a una nueva persona",
"create_new_user": "Crear nuevo usuario", "create_new_user": "Crear nuevo usuario",
"create_person": "Crear persona",
"create_person_subtitle": "Añade un nombre a la cara seleccionada para crear y etiquetar a la nueva persona",
"create_shared_album_page_share_add_assets": "AÑADIR RECURSOS", "create_shared_album_page_share_add_assets": "AÑADIR RECURSOS",
"create_shared_album_page_share_select_photos": "Seleccionar fotos", "create_shared_album_page_share_select_photos": "Seleccionar fotos",
"create_shared_link": "Crear un enlace compartido", "create_shared_link": "Crear un enlace compartido",
@@ -1025,7 +1022,7 @@
"enable_biometric_auth_description": "Introduce tu código PIN para habilitar la autentificación biométrica", "enable_biometric_auth_description": "Introduce tu código PIN para habilitar la autentificación biométrica",
"enabled": "Habilitado", "enabled": "Habilitado",
"end_date": "Fecha final", "end_date": "Fecha final",
"enqueued": "Añadido a la cola", "enqueued": "Agregado a la cola",
"enter_wifi_name": "Introduce el nombre Wi-Fi", "enter_wifi_name": "Introduce el nombre Wi-Fi",
"enter_your_pin_code": "Introduce tu código PIN", "enter_your_pin_code": "Introduce tu código PIN",
"enter_your_pin_code_subtitle": "Introduce tu código PIN para acceder a la carpeta protegida", "enter_your_pin_code_subtitle": "Introduce tu código PIN para acceder a la carpeta protegida",
@@ -1088,7 +1085,7 @@
"unable_to_add_partners": "No se pueden añadir miembros", "unable_to_add_partners": "No se pueden añadir miembros",
"unable_to_add_remove_archive": "No se pudo {archived, select, true {eliminar el recurso del} other {añadir el recurso al}} archivo", "unable_to_add_remove_archive": "No se pudo {archived, select, true {eliminar el recurso del} other {añadir el recurso al}} archivo",
"unable_to_add_remove_favorites": "No se pudo {favorite, select, true {añadir el recuso a} other {eliminar el recurso de}} los favoritos", "unable_to_add_remove_favorites": "No se pudo {favorite, select, true {añadir el recuso a} other {eliminar el recurso de}} los favoritos",
"unable_to_archive_unarchive": "No se pudo {archived, select, true {añadir el elemento al} other {quitar el elemento del}} archivo", "unable_to_archive_unarchive": "No se pudo {archived, select, true {agregar el elemento al} other {quitar el elemento del}} archivo",
"unable_to_change_album_user_role": "No se puede cambiar la función del usuario del álbum", "unable_to_change_album_user_role": "No se puede cambiar la función del usuario del álbum",
"unable_to_change_date": "No se puede cambiar la fecha", "unable_to_change_date": "No se puede cambiar la fecha",
"unable_to_change_description": "Imposible cambiar la descripción", "unable_to_change_description": "Imposible cambiar la descripción",
@@ -2217,7 +2214,6 @@
"tag": "Etiqueta", "tag": "Etiqueta",
"tag_assets": "Etiquetar recursos", "tag_assets": "Etiquetar recursos",
"tag_created": "Etiqueta creada: {tag}", "tag_created": "Etiqueta creada: {tag}",
"tag_face": "Etiquetar cara",
"tag_feature_description": "Explore fotos y videos agrupados por temas de etiquetas lógicas", "tag_feature_description": "Explore fotos y videos agrupados por temas de etiquetas lógicas",
"tag_not_found_question": "¿No encuentra una etiqueta? <link>Crea una nueva etiqueta.</link>", "tag_not_found_question": "¿No encuentra una etiqueta? <link>Crea una nueva etiqueta.</link>",
"tag_people": "Etiquetar personas", "tag_people": "Etiquetar personas",
+2 -14
View File
@@ -849,12 +849,9 @@
"create_link_to_share": "Luo linkki jaettavaksi", "create_link_to_share": "Luo linkki jaettavaksi",
"create_link_to_share_description": "Salli kaikkien linkin saaneiden nähdä valitut kuvat", "create_link_to_share_description": "Salli kaikkien linkin saaneiden nähdä valitut kuvat",
"create_new": "LUO UUSI", "create_new": "LUO UUSI",
"create_new_face": "Luo uudet kasvot",
"create_new_person": "Luo uusi henkilö", "create_new_person": "Luo uusi henkilö",
"create_new_person_hint": "Määritä valitut mediat uudelle henkilölle", "create_new_person_hint": "Määritä valitut mediat uudelle henkilölle",
"create_new_user": "Luo uusi käyttäjä", "create_new_user": "Luo uusi käyttäjä",
"create_person": "Luo henkilö",
"create_person_subtitle": "Lisää nimi valituille kasvoille luodaksesi uudelle henkilölle tunnisteen",
"create_shared_album_page_share_add_assets": "LISÄÄ KOHTEITA", "create_shared_album_page_share_add_assets": "LISÄÄ KOHTEITA",
"create_shared_album_page_share_select_photos": "Valitse kuvat", "create_shared_album_page_share_select_photos": "Valitse kuvat",
"create_shared_link": "Luo jakolinkki", "create_shared_link": "Luo jakolinkki",
@@ -869,7 +866,6 @@
"crop_aspect_ratio_fixed": "Kiinteä", "crop_aspect_ratio_fixed": "Kiinteä",
"crop_aspect_ratio_free": "Vapaa", "crop_aspect_ratio_free": "Vapaa",
"crop_aspect_ratio_original": "Alkuperäinen", "crop_aspect_ratio_original": "Alkuperäinen",
"crop_aspect_ratio_square": "Neliö",
"curated_object_page_title": "Asiat", "curated_object_page_title": "Asiat",
"current_device": "Nykyinen laite", "current_device": "Nykyinen laite",
"current_pin_code": "Nykyinen PIN-koodi", "current_pin_code": "Nykyinen PIN-koodi",
@@ -884,7 +880,7 @@
"daily_title_text_date": "E, dd MMM", "daily_title_text_date": "E, dd MMM",
"daily_title_text_date_year": "E, dd MMM, yyyy", "daily_title_text_date_year": "E, dd MMM, yyyy",
"dark": "Tumma", "dark": "Tumma",
"dark_theme": "Vaihda tummaan teemaan", "dark_theme": "Vaihda tumma teema",
"date": "Päivämäärä", "date": "Päivämäärä",
"date_after": "Päivämäärän jälkeen", "date_after": "Päivämäärän jälkeen",
"date_and_time": "Päivämäärä ja aika", "date_and_time": "Päivämäärä ja aika",
@@ -895,8 +891,6 @@
"day": "Päivä", "day": "Päivä",
"days": "Päivää", "days": "Päivää",
"deduplicate_all": "Poista kaikkien kaksoiskappaleet", "deduplicate_all": "Poista kaikkien kaksoiskappaleet",
"default_locale": "Oletuskieli",
"default_locale_description": "Muotoile päivämäärät ja luvut selaimesi kieliasetusten mukaan",
"delete": "Poista", "delete": "Poista",
"delete_action_confirmation_message": "Haluatko varmasti poistaa tämän aineiston? Tämä toiminto siirtää aineiston palvelimen roskakoriin ja kysyy, haluatko poistaa sen myös paikallisesti", "delete_action_confirmation_message": "Haluatko varmasti poistaa tämän aineiston? Tämä toiminto siirtää aineiston palvelimen roskakoriin ja kysyy, haluatko poistaa sen myös paikallisesti",
"delete_action_prompt": "{count} poistettu", "delete_action_prompt": "{count} poistettu",
@@ -972,7 +966,7 @@
"downloading_media": "Median lataaminen", "downloading_media": "Median lataaminen",
"drop_files_to_upload": "Pudota tiedostot mihin tahansa ladataksesi ne", "drop_files_to_upload": "Pudota tiedostot mihin tahansa ladataksesi ne",
"duplicates": "Kaksoiskappaleet", "duplicates": "Kaksoiskappaleet",
"duplicates_description": "Selvitä jokaisen kohdalla mitkä (jos mitkään) ovat kaksoiskappaleita.", "duplicates_description": "Selvitä jokaisen kohdalla mitkä (jos mitkään) ovat kaksoiskappaleita",
"duration": "Kesto", "duration": "Kesto",
"edit": "Muokkaa", "edit": "Muokkaa",
"edit_album": "Muokkaa albumia", "edit_album": "Muokkaa albumia",
@@ -1009,8 +1003,6 @@
"editor_edits_applied_success": "Muutokset otettu käyttöön", "editor_edits_applied_success": "Muutokset otettu käyttöön",
"editor_flip_horizontal": "Käännä vaakatasossa", "editor_flip_horizontal": "Käännä vaakatasossa",
"editor_flip_vertical": "Käännä pystytasossa", "editor_flip_vertical": "Käännä pystytasossa",
"editor_handle_corner": "{corner, select, top_left {Vasen yläkulma} top_right {Oikea yläkulma} bottom_left {Vasen alakulma} bottom_right {Oikea alakulma} other {A}} kulman kahva",
"editor_handle_edge": "{edge, select, top {Yläreuna} bottom {Alareuna} left {Vasen reuna} right {Oikea reuna} other {En}} reunan kahva",
"editor_orientation": "Suunta", "editor_orientation": "Suunta",
"editor_reset_all_changes": "Nollaa muutokset", "editor_reset_all_changes": "Nollaa muutokset",
"editor_rotate_left": "Kierrä 90° vastapäivään", "editor_rotate_left": "Kierrä 90° vastapäivään",
@@ -1389,11 +1381,9 @@
"library_page_sort_title": "Albumin otsikko", "library_page_sort_title": "Albumin otsikko",
"licenses": "Lisenssit", "licenses": "Lisenssit",
"light": "Vaalea", "light": "Vaalea",
"light_theme": "Vaihda vaaleaan teemaan",
"like": "Tykkää", "like": "Tykkää",
"like_deleted": "Tykkäys poistettu", "like_deleted": "Tykkäys poistettu",
"link_motion_video": "Linkitä liikevideo", "link_motion_video": "Linkitä liikevideo",
"link_to_docs": "Lisätietoja löytyy <link>dokumentaatiosta</link>.",
"link_to_oauth": "Linkki OAuth", "link_to_oauth": "Linkki OAuth",
"linked_oauth_account": "Linkitetty OAuth-tili", "linked_oauth_account": "Linkitetty OAuth-tili",
"list": "Lista", "list": "Lista",
@@ -2217,7 +2207,6 @@
"tag": "Tunniste", "tag": "Tunniste",
"tag_assets": "Lisää tunnisteita", "tag_assets": "Lisää tunnisteita",
"tag_created": "Luotu tunniste: {tag}", "tag_created": "Luotu tunniste: {tag}",
"tag_face": "Merkitse kasvot",
"tag_feature_description": "Selaa valokuvia ja videoita, jotka on ryhmitelty loogisten tunnisteotsikoiden mukaan", "tag_feature_description": "Selaa valokuvia ja videoita, jotka on ryhmitelty loogisten tunnisteotsikoiden mukaan",
"tag_not_found_question": "Etkö löydä tunnistetta? <link>Luo uusi tunniste.</link>", "tag_not_found_question": "Etkö löydä tunnistetta? <link>Luo uusi tunniste.</link>",
"tag_people": "Merkitse henkilö tunnisteella", "tag_people": "Merkitse henkilö tunnisteella",
@@ -2399,7 +2388,6 @@
"viewer_remove_from_stack": "Poista pinosta", "viewer_remove_from_stack": "Poista pinosta",
"viewer_stack_use_as_main_asset": "Käytä pääkohteena", "viewer_stack_use_as_main_asset": "Käytä pääkohteena",
"viewer_unstack": "Pura pino", "viewer_unstack": "Pura pino",
"visibility": "Näkyvyys",
"visibility_changed": "{count, plural, one {# henkilön} other {# henkilöiden}} näkyvyys vaihdettu", "visibility_changed": "{count, plural, one {# henkilön} other {# henkilöiden}} näkyvyys vaihdettu",
"visual": "Visuaalinen", "visual": "Visuaalinen",
"visual_builder": "Visuaalinen koostaja", "visual_builder": "Visuaalinen koostaja",
-4
View File
@@ -849,12 +849,9 @@
"create_link_to_share": "Créer un lien pour partager", "create_link_to_share": "Créer un lien pour partager",
"create_link_to_share_description": "Permettre à n'importe qui ayant le lien de voir la(es) photo(s) sélectionnée(s)", "create_link_to_share_description": "Permettre à n'importe qui ayant le lien de voir la(es) photo(s) sélectionnée(s)",
"create_new": "NOUVEAU", "create_new": "NOUVEAU",
"create_new_face": "Créer un nouveau visage",
"create_new_person": "Créer une nouvelle personne", "create_new_person": "Créer une nouvelle personne",
"create_new_person_hint": "Attribuer les médias sélectionnés à une nouvelle personne", "create_new_person_hint": "Attribuer les médias sélectionnés à une nouvelle personne",
"create_new_user": "Créer un nouvel utilisateur", "create_new_user": "Créer un nouvel utilisateur",
"create_person": "Créer une personne",
"create_person_subtitle": "Ajouter un nom au visage sélectionné pour créer et étiqueter la nouvelle personne",
"create_shared_album_page_share_add_assets": "AJOUTER DES ÉLÉMENTS", "create_shared_album_page_share_add_assets": "AJOUTER DES ÉLÉMENTS",
"create_shared_album_page_share_select_photos": "Sélectionner les photos", "create_shared_album_page_share_select_photos": "Sélectionner les photos",
"create_shared_link": "Créer un lien partagé", "create_shared_link": "Créer un lien partagé",
@@ -2217,7 +2214,6 @@
"tag": "Étiquette", "tag": "Étiquette",
"tag_assets": "Étiqueter les médias", "tag_assets": "Étiqueter les médias",
"tag_created": "Étiquette créée: {tag}", "tag_created": "Étiquette créée: {tag}",
"tag_face": "Étiqueter le visage",
"tag_feature_description": "Parcourir les photos et vidéos groupées par thèmes logiques", "tag_feature_description": "Parcourir les photos et vidéos groupées par thèmes logiques",
"tag_not_found_question": "Vous ne trouvez pas une étiquette? <link>Créer une nouvelle étiquette.</link>", "tag_not_found_question": "Vous ne trouvez pas une étiquette? <link>Créer une nouvelle étiquette.</link>",
"tag_people": "Étiqueter les personnes", "tag_people": "Étiqueter les personnes",
-4
View File
@@ -849,12 +849,9 @@
"create_link_to_share": "Crear ligazón para compartir", "create_link_to_share": "Crear ligazón para compartir",
"create_link_to_share_description": "Permitir que calquera persoa coa ligazón vexa a(s) foto(s) seleccionada(s)", "create_link_to_share_description": "Permitir que calquera persoa coa ligazón vexa a(s) foto(s) seleccionada(s)",
"create_new": "CREAR NOVO", "create_new": "CREAR NOVO",
"create_new_face": "Crear nova cara",
"create_new_person": "Crear nova persoa", "create_new_person": "Crear nova persoa",
"create_new_person_hint": "Asignar activos seleccionados a unha nova persoa", "create_new_person_hint": "Asignar activos seleccionados a unha nova persoa",
"create_new_user": "Crear novo usuario", "create_new_user": "Crear novo usuario",
"create_person": "Crear persona",
"create_person_subtitle": "Engade un nome á cara seleccionada para crear e etiquetar á nova persona",
"create_shared_album_page_share_add_assets": "ENGADIR ACTIVOS", "create_shared_album_page_share_add_assets": "ENGADIR ACTIVOS",
"create_shared_album_page_share_select_photos": "Seleccionar Fotos", "create_shared_album_page_share_select_photos": "Seleccionar Fotos",
"create_shared_link": "Crear ligazón compartida", "create_shared_link": "Crear ligazón compartida",
@@ -2217,7 +2214,6 @@
"tag": "Etiqueta", "tag": "Etiqueta",
"tag_assets": "Etiquetar activos", "tag_assets": "Etiquetar activos",
"tag_created": "Etiqueta creada: {tag}", "tag_created": "Etiqueta creada: {tag}",
"tag_face": "Etiquetar cara",
"tag_feature_description": "Navegar por fotos e vídeos agrupados por temas de etiquetas lóxicas", "tag_feature_description": "Navegar por fotos e vídeos agrupados por temas de etiquetas lóxicas",
"tag_not_found_question": "Non atopa unha etiqueta? <link>Crear unha nova etiqueta.</link>", "tag_not_found_question": "Non atopa unha etiqueta? <link>Crear unha nova etiqueta.</link>",
"tag_people": "Etiquetar Persoas", "tag_people": "Etiquetar Persoas",
-7
View File
@@ -849,12 +849,9 @@
"create_link_to_share": "Crea link da condividere", "create_link_to_share": "Crea link da condividere",
"create_link_to_share_description": "Permetti a chiunque con il link di vedere le foto selezionate", "create_link_to_share_description": "Permetti a chiunque con il link di vedere le foto selezionate",
"create_new": "CREA NUOVO", "create_new": "CREA NUOVO",
"create_new_face": "Crea nuova faccia",
"create_new_person": "Crea nuova persona", "create_new_person": "Crea nuova persona",
"create_new_person_hint": "Assegna le risorse selezionate a una nuova persona", "create_new_person_hint": "Assegna le risorse selezionate a una nuova persona",
"create_new_user": "Crea nuovo utente", "create_new_user": "Crea nuovo utente",
"create_person": "Crea persona",
"create_person_subtitle": "Aggiungi un nome alla faccia selezionata per creare e taggare la nuova persona",
"create_shared_album_page_share_add_assets": "AGGIUNGI RISORSE", "create_shared_album_page_share_add_assets": "AGGIUNGI RISORSE",
"create_shared_album_page_share_select_photos": "Seleziona foto", "create_shared_album_page_share_select_photos": "Seleziona foto",
"create_shared_link": "Crea link condiviso", "create_shared_link": "Crea link condiviso",
@@ -895,8 +892,6 @@
"day": "Giorno", "day": "Giorno",
"days": "Giorni", "days": "Giorni",
"deduplicate_all": "Elimina tutti i doppioni", "deduplicate_all": "Elimina tutti i doppioni",
"default_locale": "Predefinito Locale",
"default_locale_description": "Formatta le date e i numeri sulla base del tuo browser locale",
"delete": "Elimina", "delete": "Elimina",
"delete_action_confirmation_message": "Vuoi davvero eliminare questa risorsa? Questa azione sposterà la risorsa nel cestino del server e ti chiederà se desideri eliminarla dal dispositivo", "delete_action_confirmation_message": "Vuoi davvero eliminare questa risorsa? Questa azione sposterà la risorsa nel cestino del server e ti chiederà se desideri eliminarla dal dispositivo",
"delete_action_prompt": "{count} elementi eliminati", "delete_action_prompt": "{count} elementi eliminati",
@@ -1393,7 +1388,6 @@
"like": "Mi piace", "like": "Mi piace",
"like_deleted": "Mi piace rimosso", "like_deleted": "Mi piace rimosso",
"link_motion_video": "Collega video in movimento", "link_motion_video": "Collega video in movimento",
"link_to_docs": "Per maggiori informazioni, riferirsi al <link>documentazione</link>.",
"link_to_oauth": "Collegamento a OAuth", "link_to_oauth": "Collegamento a OAuth",
"linked_oauth_account": "Account OAuth collegato", "linked_oauth_account": "Account OAuth collegato",
"list": "Lista", "list": "Lista",
@@ -2217,7 +2211,6 @@
"tag": "Tag", "tag": "Tag",
"tag_assets": "Tagga risorse", "tag_assets": "Tagga risorse",
"tag_created": "Tag creato: {tag}", "tag_created": "Tag creato: {tag}",
"tag_face": "Tagga la faccia",
"tag_feature_description": "Navigazione foto e video raggruppati per argomenti tag logici", "tag_feature_description": "Navigazione foto e video raggruppati per argomenti tag logici",
"tag_not_found_question": "Non riesci a trovare un tag? <link>Creane uno nuovo.</link>", "tag_not_found_question": "Non riesci a trovare un tag? <link>Creane uno nuovo.</link>",
"tag_people": "Tagga persone", "tag_people": "Tagga persone",
+24 -36
View File
@@ -798,7 +798,7 @@
"command_palette_to_close": "닫기", "command_palette_to_close": "닫기",
"command_palette_to_navigate": "들어가기", "command_palette_to_navigate": "들어가기",
"command_palette_to_select": "선택하기", "command_palette_to_select": "선택하기",
"command_palette_to_show_all": "모두 보기", "command_palette_to_show_all": "다 보여주기",
"comment_deleted": "댓글이 삭제되었습니다.", "comment_deleted": "댓글이 삭제되었습니다.",
"comment_options": "댓글 옵션", "comment_options": "댓글 옵션",
"comments_and_likes": "댓글 및 좋아요", "comments_and_likes": "댓글 및 좋아요",
@@ -849,12 +849,9 @@
"create_link_to_share": "공유 링크 생성", "create_link_to_share": "공유 링크 생성",
"create_link_to_share_description": "링크가 있는 경우 누구나 선택한 사진을 볼 수 있습니다.", "create_link_to_share_description": "링크가 있는 경우 누구나 선택한 사진을 볼 수 있습니다.",
"create_new": "새로 만들기", "create_new": "새로 만들기",
"create_new_face": "새 얼굴 생성",
"create_new_person": "인물 생성", "create_new_person": "인물 생성",
"create_new_person_hint": "선택한 항목의 인물을 새 인물로 변경", "create_new_person_hint": "선택한 항목의 인물을 새 인물로 변경",
"create_new_user": "새 사용자 생성", "create_new_user": "새 사용자 생성",
"create_person": "인물 생성",
"create_person_subtitle": "선택한 얼굴에 이름을 추가해 신규 인물을 생성하고 태그 지정",
"create_shared_album_page_share_add_assets": "항목 추가", "create_shared_album_page_share_add_assets": "항목 추가",
"create_shared_album_page_share_select_photos": "사진 선택", "create_shared_album_page_share_select_photos": "사진 선택",
"create_shared_link": "공유 링크 생성", "create_shared_link": "공유 링크 생성",
@@ -869,7 +866,6 @@
"crop_aspect_ratio_fixed": "고정", "crop_aspect_ratio_fixed": "고정",
"crop_aspect_ratio_free": "직접 조절", "crop_aspect_ratio_free": "직접 조절",
"crop_aspect_ratio_original": "원본", "crop_aspect_ratio_original": "원본",
"crop_aspect_ratio_square": "정사각형",
"curated_object_page_title": "사물", "curated_object_page_title": "사물",
"current_device": "현재 기기", "current_device": "현재 기기",
"current_pin_code": "현재 PIN 코드", "current_pin_code": "현재 PIN 코드",
@@ -884,7 +880,7 @@
"daily_title_text_date": "M월 d일 EEEE", "daily_title_text_date": "M월 d일 EEEE",
"daily_title_text_date_year": "yyyy년 M월 d일 EEEE", "daily_title_text_date_year": "yyyy년 M월 d일 EEEE",
"dark": "다크", "dark": "다크",
"dark_theme": "다크 테마 전환", "dark_theme": "다크 테마 토글",
"date": "날짜", "date": "날짜",
"date_after": "다음 날짜 이후", "date_after": "다음 날짜 이후",
"date_and_time": "날짜 및 시간", "date_and_time": "날짜 및 시간",
@@ -895,8 +891,6 @@
"day": "일", "day": "일",
"days": "일", "days": "일",
"deduplicate_all": "모두 삭제", "deduplicate_all": "모두 삭제",
"default_locale": "기본 로케일",
"default_locale_description": "브라우저 로케일 설정에 따라 날짜 및 숫자 형식을 지정합니다",
"delete": "삭제", "delete": "삭제",
"delete_action_confirmation_message": "이 항목을 삭제하시겠습니까? 서버에서는 항목을 휴지통으로 이동시키며, 로컬에서도 삭제할 것인지 확인 메시지가 표시됩니다.", "delete_action_confirmation_message": "이 항목을 삭제하시겠습니까? 서버에서는 항목을 휴지통으로 이동시키며, 로컬에서도 삭제할 것인지 확인 메시지가 표시됩니다.",
"delete_action_prompt": "{count}개 항목 삭제됨", "delete_action_prompt": "{count}개 항목 삭제됨",
@@ -1009,8 +1003,6 @@
"editor_edits_applied_success": "편집이 적용되었습니다.", "editor_edits_applied_success": "편집이 적용되었습니다.",
"editor_flip_horizontal": "좌우반전", "editor_flip_horizontal": "좌우반전",
"editor_flip_vertical": "상하반전", "editor_flip_vertical": "상하반전",
"editor_handle_corner": "{corner, select, top_left {좌상단} top_right {우상단} bottom_left {좌하단} bottom_right {우하단} other {A}} 코너 핸들",
"editor_handle_edge": "{edge, select, top {위} bottom {아래} left {왼쪽} right {오른쪽} other {An}} 모서리 핸들",
"editor_orientation": "방향", "editor_orientation": "방향",
"editor_reset_all_changes": "편집내용 초기화", "editor_reset_all_changes": "편집내용 초기화",
"editor_rotate_left": "반시계 방향으로 90° 회전", "editor_rotate_left": "반시계 방향으로 90° 회전",
@@ -1069,26 +1061,26 @@
"failed_to_load_assets": "항목 로드 실패", "failed_to_load_assets": "항목 로드 실패",
"failed_to_load_notifications": "알림 로드 실패", "failed_to_load_notifications": "알림 로드 실패",
"failed_to_load_people": "인물 로드 실패", "failed_to_load_people": "인물 로드 실패",
"failed_to_remove_product_key": "제품 키 제거에 실패했습니다.", "failed_to_remove_product_key": "제품 키 제거에 실패",
"failed_to_reset_pin_code": "PIN 코드 초기화 실패", "failed_to_reset_pin_code": "PIN 코드 초기화 실패",
"failed_to_stack_assets": "항목 스택에 실패했습니다.", "failed_to_stack_assets": "항목 스택에 실패",
"failed_to_unstack_assets": "항목 스택 풀기에 실패했습니다.", "failed_to_unstack_assets": "항목 스택 풀기에 실패",
"failed_to_update_notification_status": "알림 상태 업데이트 실패", "failed_to_update_notification_status": "알림 상태 업데이트 실패",
"incorrect_email_or_password": "잘못된 이메일 또는 비밀번호", "incorrect_email_or_password": "잘못된 이메일 또는 비밀번호",
"library_folder_already_exists": "가져올 경로가 이미 존재합니다.", "library_folder_already_exists": "가져올 경로가 이미 존재합니다.",
"page_not_found": "페이지를 찾을 수 없음", "page_not_found": "페이지를 찾을 수 없음 :/",
"paths_validation_failed": "{paths, plural, one {경로 #개} other {경로 #개}}가 유효성 검사에 실패했습니다.", "paths_validation_failed": "{paths, plural, one {경로 #개} other {경로 #개}}가 유효성 검사에 실패했습니다.",
"profile_picture_transparent_pixels": "프로필 사진에 투명 픽셀을 사용할 수 없습니다. 사진을 확대하거나 이동하세요.", "profile_picture_transparent_pixels": "프로필 사진에 투명 픽셀을 사용할 수 없습니다. 사진을 확대하거나 이동하세요.",
"quota_higher_than_disk_size": "할당량은 디스크 크기보다 작아야 합니다.", "quota_higher_than_disk_size": "할당량은 디스크 크기보다 작아야 합니다.",
"something_went_wrong": "문제가 발생했습니다.", "something_went_wrong": "문제가 발생했습니다.",
"unable_to_add_album_users": "앨범에 사용자를 추가할 수 없습니다.", "unable_to_add_album_users": "앨범에 사용자를 추가할 수 없",
"unable_to_add_assets_to_shared_link": "항목을 공유 링크에 추가할 수 없습니다.", "unable_to_add_assets_to_shared_link": "항목을 공유 링크에 추가할 수 없",
"unable_to_add_comment": "댓글을 추가할 수 없습니다.", "unable_to_add_comment": "댓글을 추가할 수 없",
"unable_to_add_exclusion_pattern": "제외 규칙을 추가할 수 없습니다.", "unable_to_add_exclusion_pattern": "제외 규칙을 추가할 수 없",
"unable_to_add_partners": "파트너를 추가할 수 없습니다.", "unable_to_add_partners": "파트너를 추가할 수 없",
"unable_to_add_remove_archive": "{archived, select, true {보관함에서 항목을 제거할} other {보관함으로 항목을 이동할}} 수 없습니다.", "unable_to_add_remove_archive": "{archived, select, true {보관함에서 항목을 제거할} other {보관함으로 항목을 이동할}} 수 없",
"unable_to_add_remove_favorites": "즐겨찾기에 항목을 {favorite, select, true {추가} other {제거}}할 수 없습니다", "unable_to_add_remove_favorites": "즐겨찾기에 항목을 {favorite, select, true {추가} other {제거}}할 수 없",
"unable_to_archive_unarchive": "항목을 {archived, select, true {보관} other {보관 해제}}할 수 없습니다", "unable_to_archive_unarchive": "항목을 {archived, select, true {보관} other {보관 해제}}할 수 없",
"unable_to_change_album_user_role": "앨범 사용자의 역할을 변경할 수 없습니다.", "unable_to_change_album_user_role": "앨범 사용자의 역할을 변경할 수 없습니다.",
"unable_to_change_date": "날짜를 변경할 수 없습니다.", "unable_to_change_date": "날짜를 변경할 수 없습니다.",
"unable_to_change_description": "설명을 변경할 수 없습니다.", "unable_to_change_description": "설명을 변경할 수 없습니다.",
@@ -1134,10 +1126,10 @@
"unable_to_remove_library": "라이브러리를 제거할 수 없습니다.", "unable_to_remove_library": "라이브러리를 제거할 수 없습니다.",
"unable_to_remove_partner": "파트너를 제거할 수 없습니다.", "unable_to_remove_partner": "파트너를 제거할 수 없습니다.",
"unable_to_remove_reaction": "반응을 제거할 수 없습니다.", "unable_to_remove_reaction": "반응을 제거할 수 없습니다.",
"unable_to_reset_password": "비밀번호를 초기화할 수 없습니다.", "unable_to_reset_password": "비밀번호를 초기화할 수 없",
"unable_to_reset_pin_code": "PIN 코드를 초기화할 수 없음", "unable_to_reset_pin_code": "PIN 코드를 초기화할 수 없음",
"unable_to_resolve_duplicate": "비슷한 항목을 처리할 수 없음", "unable_to_resolve_duplicate": "비슷한 항목을 처리할 수 없음",
"unable_to_restore_assets": "항목을 복원할 수 없습니다.", "unable_to_restore_assets": "항목을 복원할 수 없",
"unable_to_restore_trash": "휴지통을 복원할 수 없습니다.", "unable_to_restore_trash": "휴지통을 복원할 수 없습니다.",
"unable_to_restore_user": "사용자를 복원할 수 없습니다.", "unable_to_restore_user": "사용자를 복원할 수 없습니다.",
"unable_to_save_album": "앨범을 저장할 수 없습니다.", "unable_to_save_album": "앨범을 저장할 수 없습니다.",
@@ -1150,7 +1142,7 @@
"unable_to_scan_library": "라이브러리를 스캔할 수 없습니다.", "unable_to_scan_library": "라이브러리를 스캔할 수 없습니다.",
"unable_to_set_feature_photo": "대표 사진을 설정할 수 없습니다.", "unable_to_set_feature_photo": "대표 사진을 설정할 수 없습니다.",
"unable_to_set_profile_picture": "프로필 사진을 설정할 수 없습니다.", "unable_to_set_profile_picture": "프로필 사진을 설정할 수 없습니다.",
"unable_to_set_rating": "점을 정할 수 없습니다.", "unable_to_set_rating": "점을 정할 수 없",
"unable_to_submit_job": "작업을 수행할 수 없습니다.", "unable_to_submit_job": "작업을 수행할 수 없습니다.",
"unable_to_trash_asset": "휴지통으로 이동할 수 없습니다.", "unable_to_trash_asset": "휴지통으로 이동할 수 없습니다.",
"unable_to_unlink_account": "계정 연결을 해제할 수 없습니다.", "unable_to_unlink_account": "계정 연결을 해제할 수 없습니다.",
@@ -1389,11 +1381,9 @@
"library_page_sort_title": "앨범명", "library_page_sort_title": "앨범명",
"licenses": "라이선스", "licenses": "라이선스",
"light": "라이트", "light": "라이트",
"light_theme": "라이트 테마로 전환",
"like": "좋아요", "like": "좋아요",
"like_deleted": "좋아요가 삭제되었습니다.", "like_deleted": "좋아요가 삭제되었습니다.",
"link_motion_video": "모션 비디오 링크", "link_motion_video": "모션 비디오 링크",
"link_to_docs": "자세한 내용은 <link>문서</link>를 참조하십시오.",
"link_to_oauth": "OAuth에 연결", "link_to_oauth": "OAuth에 연결",
"linked_oauth_account": "OAuth 계정이 연결되었습니다.", "linked_oauth_account": "OAuth 계정이 연결되었습니다.",
"list": "목록", "list": "목록",
@@ -1655,7 +1645,6 @@
"only_favorites": "즐겨찾기만", "only_favorites": "즐겨찾기만",
"open": "열기", "open": "열기",
"open_calendar": "캘린더 열기", "open_calendar": "캘린더 열기",
"open_in_browser": "브라우저에서 열기",
"open_in_map_view": "지도 보기에서 열기", "open_in_map_view": "지도 보기에서 열기",
"open_in_openstreetmap": "OpenStreetMap에서 열기", "open_in_openstreetmap": "OpenStreetMap에서 열기",
"open_the_search_filters": "검색 필터 열기", "open_the_search_filters": "검색 필터 열기",
@@ -1812,11 +1801,11 @@
"purchase_settings_server_activated": "서버 제품 키는 관리자가 제어합니다.", "purchase_settings_server_activated": "서버 제품 키는 관리자가 제어합니다.",
"query_asset_id": "쿼리 항목 ID", "query_asset_id": "쿼리 항목 ID",
"queue_status": "전체 {total}, {count} 대기 중", "queue_status": "전체 {total}, {count} 대기 중",
"rate_asset": "항목 점", "rate_asset": "항목 점",
"rating": "별점", "rating": "별점",
"rating_clear": "점 초기화", "rating_clear": "점 초기화",
"rating_count": "{count, plural, =0 {점 없음} one {#점} other {#점}}", "rating_count": "{count, plural, =0 {점 없음} one {#점} other {#점}}",
"rating_description": "상세 정보 패널에 EXIF 별점 태그 표시", "rating_description": "상세 정보 패널에 EXIF 등급 태그 표시",
"reaction_options": "반응 옵션", "reaction_options": "반응 옵션",
"read_changelog": "변경 내역 보기", "read_changelog": "변경 내역 보기",
"readonly_mode_disabled": "읽기 전용 모드 비활성화", "readonly_mode_disabled": "읽기 전용 모드 비활성화",
@@ -1953,7 +1942,7 @@
"search_filter_media_type_title": "미디어 종류 선택", "search_filter_media_type_title": "미디어 종류 선택",
"search_filter_ocr": "OCR 검색", "search_filter_ocr": "OCR 검색",
"search_filter_people_title": "인물 선택", "search_filter_people_title": "인물 선택",
"search_filter_star_rating": "점", "search_filter_star_rating": "점",
"search_filter_tags_title": "태그 선택", "search_filter_tags_title": "태그 선택",
"search_for": "검색", "search_for": "검색",
"search_for_existing_person": "존재하는 인물 검색", "search_for_existing_person": "존재하는 인물 검색",
@@ -1975,7 +1964,7 @@
"search_page_your_map": "나의 지도", "search_page_your_map": "나의 지도",
"search_people": "인물 검색", "search_people": "인물 검색",
"search_places": "장소 검색", "search_places": "장소 검색",
"search_rating": "별점으로 검색...", "search_rating": "등급으로 검색...",
"search_result_page_new_search_hint": "새 검색", "search_result_page_new_search_hint": "새 검색",
"search_settings": "설정 검색", "search_settings": "설정 검색",
"search_state": "지역 검색...", "search_state": "지역 검색...",
@@ -2395,7 +2384,6 @@
"viewer_remove_from_stack": "스택에서 제거", "viewer_remove_from_stack": "스택에서 제거",
"viewer_stack_use_as_main_asset": "대표 항목으로 설정", "viewer_stack_use_as_main_asset": "대표 항목으로 설정",
"viewer_unstack": "스택 풀기", "viewer_unstack": "스택 풀기",
"visibility": "표시 설정",
"visibility_changed": "인물 {count, plural, one {#명} other {#명}}의 표시 여부가 변경됨", "visibility_changed": "인물 {count, plural, one {#명} other {#명}}의 표시 여부가 변경됨",
"visual": "비주얼", "visual": "비주얼",
"visual_builder": "비주얼 빌더", "visual_builder": "비주얼 빌더",
@@ -2426,7 +2414,7 @@
"yes": "네", "yes": "네",
"you_dont_have_any_shared_links": "공유 링크가 없습니다.", "you_dont_have_any_shared_links": "공유 링크가 없습니다.",
"your_wifi_name": "Wi-Fi 네트워크 이름", "your_wifi_name": "Wi-Fi 네트워크 이름",
"zero_to_clear_rating": "0을 눌러 항목 점 초기화", "zero_to_clear_rating": "0을 눌러 항목 점 초기화",
"zoom_image": "이미지 확대", "zoom_image": "이미지 확대",
"zoom_to_bounds": "화면에 맞춰 확대" "zoom_to_bounds": "화면에 맞춰 확대"
} }
-4
View File
@@ -849,12 +849,9 @@
"create_link_to_share": "Sukurti bendrinimo nuorodą", "create_link_to_share": "Sukurti bendrinimo nuorodą",
"create_link_to_share_description": "Leisti bet kam su nuoroda matyti pažymėtą(-as) nuotrauką(-as)", "create_link_to_share_description": "Leisti bet kam su nuoroda matyti pažymėtą(-as) nuotrauką(-as)",
"create_new": "SUKURTI NAUJĄ", "create_new": "SUKURTI NAUJĄ",
"create_new_face": "Sukurti naują veidą",
"create_new_person": "Sukurti naują žmogų", "create_new_person": "Sukurti naują žmogų",
"create_new_person_hint": "Priskirti pasirinktus elementus naujam žmogui", "create_new_person_hint": "Priskirti pasirinktus elementus naujam žmogui",
"create_new_user": "Sukurti naują varotoją", "create_new_user": "Sukurti naują varotoją",
"create_person": "Sukurti asmenį",
"create_person_subtitle": "Pridėkite vardą prie pasirinkto veido, kad sukurtumėte ir pažymėtumėte naują asmenį",
"create_shared_album_page_share_add_assets": "PRIDĖTI ELEMENTŲ", "create_shared_album_page_share_add_assets": "PRIDĖTI ELEMENTŲ",
"create_shared_album_page_share_select_photos": "Pažymėti nuotraukas", "create_shared_album_page_share_select_photos": "Pažymėti nuotraukas",
"create_shared_link": "Sukurti dalijimosi nuorodą", "create_shared_link": "Sukurti dalijimosi nuorodą",
@@ -2217,7 +2214,6 @@
"tag": "Žyma", "tag": "Žyma",
"tag_assets": "Pažymėti", "tag_assets": "Pažymėti",
"tag_created": "Sukurta žyma: {tag}", "tag_created": "Sukurta žyma: {tag}",
"tag_face": "Pažymėti veidą",
"tag_feature_description": "Peržiūrėkite nuotraukas ir vaizdo įrašus sugrupuotus pagal sužymėtas temas", "tag_feature_description": "Peržiūrėkite nuotraukas ir vaizdo įrašus sugrupuotus pagal sužymėtas temas",
"tag_not_found_question": "Nerandate žymos? <link>Sukurti naują žymą.</link>", "tag_not_found_question": "Nerandate žymos? <link>Sukurti naują žymą.</link>",
"tag_people": "Pažymėti Žmones", "tag_people": "Pažymėti Žmones",
+6 -10
View File
@@ -5,7 +5,7 @@
"acknowledge": "Bekreft", "acknowledge": "Bekreft",
"action": "Handling", "action": "Handling",
"action_common_update": "Oppdater", "action_common_update": "Oppdater",
"action_description": "Ett sett handlinger som skal utføres på de filtrerte mediefilene", "action_description": "Ett sett med handlinger som skal utføres på de filtrerede objekter",
"actions": "Handlinger", "actions": "Handlinger",
"active": "Aktiv", "active": "Aktiv",
"active_count": "Aktiv: {count}", "active_count": "Aktiv: {count}",
@@ -18,7 +18,7 @@
"add_a_title": "Legg til tittel", "add_a_title": "Legg til tittel",
"add_action": "Legg til hendelse", "add_action": "Legg til hendelse",
"add_action_description": "Trykk for å legge til en hendelse å utføre", "add_action_description": "Trykk for å legge til en hendelse å utføre",
"add_assets": "Legg til mediefiler", "add_assets": "Legg til objekter",
"add_birthday": "Legg til bursdag", "add_birthday": "Legg til bursdag",
"add_endpoint": "Legg til endepunkt", "add_endpoint": "Legg til endepunkt",
"add_exclusion_pattern": "Legg til ekskluderingsmønster", "add_exclusion_pattern": "Legg til ekskluderingsmønster",
@@ -34,7 +34,7 @@
"add_to_album": "Legg til album", "add_to_album": "Legg til album",
"add_to_album_bottom_sheet_added": "Lagt til i {album}", "add_to_album_bottom_sheet_added": "Lagt til i {album}",
"add_to_album_bottom_sheet_already_exists": "Allerede i {album}", "add_to_album_bottom_sheet_already_exists": "Allerede i {album}",
"add_to_album_bottom_sheet_some_local_assets": "Noen lokale filer kunne ikke legges til i albumet", "add_to_album_bottom_sheet_some_local_assets": "Noen lokale elementer kunne ikke legges til i albumet",
"add_to_album_toggle": "Avhuking for {album}", "add_to_album_toggle": "Avhuking for {album}",
"add_to_albums": "Legg til i album", "add_to_albums": "Legg til i album",
"add_to_albums_count": "Legg til i album ({count})", "add_to_albums_count": "Legg til i album ({count})",
@@ -50,8 +50,8 @@
"add_exclusion_pattern_description": "Legg til ekskluderingsmønstre. Globbing med *, ** og ? støttes. For å ignorere alle filer i en hvilken som helst mappe som heter \"Raw\", bruk \"**/Raw/**\". For å ignorere alle filer som slutter på \".tif\", bruk \"**/*.tif\". For å ignorere en absolutt filplassering, bruk \"/filsti/til/ignorer/**\".", "add_exclusion_pattern_description": "Legg til ekskluderingsmønstre. Globbing med *, ** og ? støttes. For å ignorere alle filer i en hvilken som helst mappe som heter \"Raw\", bruk \"**/Raw/**\". For å ignorere alle filer som slutter på \".tif\", bruk \"**/*.tif\". For å ignorere en absolutt filplassering, bruk \"/filsti/til/ignorer/**\".",
"admin_user": "Administrasjonsbruker", "admin_user": "Administrasjonsbruker",
"asset_offline_description": "Dette eksterne bibliotekselementet finnes ikke lenger på disk og har blitt flyttet til papirkurven. Hvis filen ble flyttet innad i biblioteket, se etter det tilsvarende elementet i tidslinjen din. For å gjenopprette elementet, vennligst sørg for at filstien under er tilgjengelig for Immich og skann biblioteket.", "asset_offline_description": "Dette eksterne bibliotekselementet finnes ikke lenger på disk og har blitt flyttet til papirkurven. Hvis filen ble flyttet innad i biblioteket, se etter det tilsvarende elementet i tidslinjen din. For å gjenopprette elementet, vennligst sørg for at filstien under er tilgjengelig for Immich og skann biblioteket.",
"authentication_settings": "Godkjenings Instillinger", "authentication_settings": "Godkjenninger",
"authentication_settings_description": "Administrer passord, OAuth, og andre innstillinger for autentiserings Instilinger", "authentication_settings_description": "Administrer passord, OAuth, og andre innstillinger for autentisering",
"authentication_settings_disable_all": "Er du sikker på at du ønsker å deaktivere alle innloggingsmetoder? Innlogging vil bli fullstendig deaktivert.", "authentication_settings_disable_all": "Er du sikker på at du ønsker å deaktivere alle innloggingsmetoder? Innlogging vil bli fullstendig deaktivert.",
"authentication_settings_reenable": "For å aktivere på nytt, bruk en <link>Server Command</link>.", "authentication_settings_reenable": "For å aktivere på nytt, bruk en <link>Server Command</link>.",
"background_task_job": "Bakgrunnsjobber", "background_task_job": "Bakgrunnsjobber",
@@ -81,7 +81,7 @@
"cron_expression_description": "Still inn skanneintervallet med cron-formatet. For mer informasjon henvises til f.eks. <link>Crontab Guru</link>", "cron_expression_description": "Still inn skanneintervallet med cron-formatet. For mer informasjon henvises til f.eks. <link>Crontab Guru</link>",
"cron_expression_presets": "Forhåndsinnstillinger for Cron-uttrykk", "cron_expression_presets": "Forhåndsinnstillinger for Cron-uttrykk",
"disable_login": "Deaktiver innlogging", "disable_login": "Deaktiver innlogging",
"duplicate_detection_job_description": "Kjør maskinlæring på filer for å oppdage lignende bilder. Krever bruk av Smart Søk", "duplicate_detection_job_description": "Kjør maskinlæring på filer for å oppdage lignende bilder. Krever bruk av Smart Search",
"exclusion_pattern_description": "Ekskluderingsmønstre lar deg ignorere filer og mapper når du skanner biblioteket ditt. Dette er nyttig hvis du har mapper som inneholder filer du ikke vil importere, for eksempel RAW-filer.", "exclusion_pattern_description": "Ekskluderingsmønstre lar deg ignorere filer og mapper når du skanner biblioteket ditt. Dette er nyttig hvis du har mapper som inneholder filer du ikke vil importere, for eksempel RAW-filer.",
"export_config_as_json_description": "Last ned nåværende systemkonfigurasjon som en JSON fil", "export_config_as_json_description": "Last ned nåværende systemkonfigurasjon som en JSON fil",
"external_libraries_page_description": "Administrering for eksterne bibliotek", "external_libraries_page_description": "Administrering for eksterne bibliotek",
@@ -849,12 +849,9 @@
"create_link_to_share": "Opprett delelink", "create_link_to_share": "Opprett delelink",
"create_link_to_share_description": "La alle med lenken se de(t) valgte bildet/bildene", "create_link_to_share_description": "La alle med lenken se de(t) valgte bildet/bildene",
"create_new": "LAG NY", "create_new": "LAG NY",
"create_new_face": "Opprett nytt ansikt",
"create_new_person": "Opprett ny person", "create_new_person": "Opprett ny person",
"create_new_person_hint": "Tildel valgte eiendeler til en ny person", "create_new_person_hint": "Tildel valgte eiendeler til en ny person",
"create_new_user": "Opprett ny bruker", "create_new_user": "Opprett ny bruker",
"create_person": "Opprett person",
"create_person_subtitle": "Gi det valgte ansiktet et navn for å opprette og tagge den nye personen",
"create_shared_album_page_share_add_assets": "LEGG TIL OBJEKTER", "create_shared_album_page_share_add_assets": "LEGG TIL OBJEKTER",
"create_shared_album_page_share_select_photos": "Velg bilder", "create_shared_album_page_share_select_photos": "Velg bilder",
"create_shared_link": "Opprett delt lenke", "create_shared_link": "Opprett delt lenke",
@@ -2217,7 +2214,6 @@
"tag": "Tagg", "tag": "Tagg",
"tag_assets": "Merk ressurser", "tag_assets": "Merk ressurser",
"tag_created": "Lag merke: {tag}", "tag_created": "Lag merke: {tag}",
"tag_face": "Tagg ansikt",
"tag_feature_description": "Bla gjennom bilder og videoer gruppert etter logiske merke-emner", "tag_feature_description": "Bla gjennom bilder og videoer gruppert etter logiske merke-emner",
"tag_not_found_question": "Finner du ikke en merke? <link>Opprett en nytt merke.</link>", "tag_not_found_question": "Finner du ikke en merke? <link>Opprett en nytt merke.</link>",
"tag_people": "Tag personer", "tag_people": "Tag personer",
-4
View File
@@ -849,12 +849,9 @@
"create_link_to_share": "Gedeelde link maken", "create_link_to_share": "Gedeelde link maken",
"create_link_to_share_description": "Laat iedereen met de link de geselecteerde foto(s) zien", "create_link_to_share_description": "Laat iedereen met de link de geselecteerde foto(s) zien",
"create_new": "MAAK NIEUW", "create_new": "MAAK NIEUW",
"create_new_face": "Nieuw gezicht aanmaken",
"create_new_person": "Nieuwe persoon aanmaken", "create_new_person": "Nieuwe persoon aanmaken",
"create_new_person_hint": "Geselecteerde items toewijzen aan een nieuwe persoon", "create_new_person_hint": "Geselecteerde items toewijzen aan een nieuwe persoon",
"create_new_user": "Nieuwe gebruiker aanmaken", "create_new_user": "Nieuwe gebruiker aanmaken",
"create_person": "Persoon aanmaken",
"create_person_subtitle": "Voeg een naam toe aan het geselecteerde gezicht om de nieuwe persoon aan te maken en te taggen",
"create_shared_album_page_share_add_assets": "ITEMS TOEVOEGEN", "create_shared_album_page_share_add_assets": "ITEMS TOEVOEGEN",
"create_shared_album_page_share_select_photos": "Selecteer foto's", "create_shared_album_page_share_select_photos": "Selecteer foto's",
"create_shared_link": "Gedeelde link maken", "create_shared_link": "Gedeelde link maken",
@@ -2217,7 +2214,6 @@
"tag": "Tag", "tag": "Tag",
"tag_assets": "Items taggen", "tag_assets": "Items taggen",
"tag_created": "Tag aangemaakt: {tag}", "tag_created": "Tag aangemaakt: {tag}",
"tag_face": "Gezicht labelen",
"tag_feature_description": "Bladeren door foto's en video's gegroepeerd op tags", "tag_feature_description": "Bladeren door foto's en video's gegroepeerd op tags",
"tag_not_found_question": "Kun je een tag niet vinden? <link>Maak een nieuwe tag.</link>", "tag_not_found_question": "Kun je een tag niet vinden? <link>Maak een nieuwe tag.</link>",
"tag_people": "Mensen taggen", "tag_people": "Mensen taggen",
-18
View File
@@ -37,10 +37,8 @@
"add_to_album_bottom_sheet_some_local_assets": "Somme lokale eigedelar kunne ikkje leggjast til i album", "add_to_album_bottom_sheet_some_local_assets": "Somme lokale eigedelar kunne ikkje leggjast til i album",
"add_to_albums": "Legg til i album", "add_to_albums": "Legg til i album",
"add_to_albums_count": "Legg til i album ({count})", "add_to_albums_count": "Legg til i album ({count})",
"add_to_bottom_bar": "Legg til i",
"add_to_shared_album": "Legg til i delt album", "add_to_shared_album": "Legg til i delt album",
"add_url": "Legg til URL", "add_url": "Legg til URL",
"add_workflow_step": "Legg til steg i arbeidsflyt",
"added_to_archive": "Lagt til i arkiv", "added_to_archive": "Lagt til i arkiv",
"added_to_favorites": "Lagt til i favorittar", "added_to_favorites": "Lagt til i favorittar",
"added_to_favorites_count": "La til {count, number} i favorittar", "added_to_favorites_count": "La til {count, number} i favorittar",
@@ -73,7 +71,6 @@
"confirm_reprocess_all_faces": "Er du sikker på at du vil behandle alle ansikt på nytt? Det vil òg fjerne namngjevne personar.", "confirm_reprocess_all_faces": "Er du sikker på at du vil behandle alle ansikt på nytt? Det vil òg fjerne namngjevne personar.",
"confirm_user_password_reset": "Er du sikker at du vil tilbakestille passordet til {user}?", "confirm_user_password_reset": "Er du sikker at du vil tilbakestille passordet til {user}?",
"confirm_user_pin_code_reset": "Er du sikker på at du vil tilbakestille {user} sin PIN-kode?", "confirm_user_pin_code_reset": "Er du sikker på at du vil tilbakestille {user} sin PIN-kode?",
"copy_config_to_clipboard_description": "Kopier systemkonfigurasjonen som eit JSON-objekt til utklippstavla",
"create_job": "Lag jobb", "create_job": "Lag jobb",
"cron_expression": "Cron uttrykk", "cron_expression": "Cron uttrykk",
"cron_expression_description": "Set inn skanningsintervall med cron-formatet. For meir informasjon sjå t.d. <link>Crontab Guru</link>", "cron_expression_description": "Set inn skanningsintervall med cron-formatet. For meir informasjon sjå t.d. <link>Crontab Guru</link>",
@@ -81,7 +78,6 @@
"disable_login": "Deaktiver innlogging", "disable_login": "Deaktiver innlogging",
"duplicate_detection_job_description": "Kjør maskinlæring på filer for å oppdage liknande bilete. Krev bruk av Smart Search", "duplicate_detection_job_description": "Kjør maskinlæring på filer for å oppdage liknande bilete. Krev bruk av Smart Search",
"exclusion_pattern_description": "Utelatingsmønster let deg utelate filer og mapper når du skannar biblioteket ditt. Det er nyttig om du har mapper som inneheld filer du ikkje ynskjer å importere, til dømes RAW-filer.", "exclusion_pattern_description": "Utelatingsmønster let deg utelate filer og mapper når du skannar biblioteket ditt. Det er nyttig om du har mapper som inneheld filer du ikkje ynskjer å importere, til dømes RAW-filer.",
"export_config_as_json_description": "Last ned nåverande systemkonfigurasjon som ei JSON-fil",
"face_detection": "Ansiktssøk", "face_detection": "Ansiktssøk",
"face_detection_description": "Finn ansikt i bilete ved hjelp av maskinlæring. For videoar vert berre miniatyrbilete bruka. \"Alle\" søkjer (opp att) gjennom alle bilete. \"Tilbakestill\" fjernar all gjeldande ansiktsdata. \"Manglande\" legg filer som ikkje vert behandla til i køa for ansiktssøk. Oppdaga ansikt vert lagt i køa for ansiktsattkjenning, og kopla til eksisterande eller nye personar.", "face_detection_description": "Finn ansikt i bilete ved hjelp av maskinlæring. For videoar vert berre miniatyrbilete bruka. \"Alle\" søkjer (opp att) gjennom alle bilete. \"Tilbakestill\" fjernar all gjeldande ansiktsdata. \"Manglande\" legg filer som ikkje vert behandla til i køa for ansiktssøk. Oppdaga ansikt vert lagt i køa for ansiktsattkjenning, og kopla til eksisterande eller nye personar.",
"facial_recognition_job_description": "Koplar attkjende ansikt til personar. Det skjer fyrst når anskiktssøkjet er ferdig. \"Tilbakestill\" fjernar alle koplingar til personar, og tilbakestiller ansiktsgrupper. \"Manglande\" legg ansikt som ikkje er oppkopla til i køa.", "facial_recognition_job_description": "Koplar attkjende ansikt til personar. Det skjer fyrst når anskiktssøkjet er ferdig. \"Tilbakestill\" fjernar alle koplingar til personar, og tilbakestiller ansiktsgrupper. \"Manglande\" legg ansikt som ikkje er oppkopla til i køa.",
@@ -109,7 +105,6 @@
"image_thumbnail_description": "Lite miniatyrbilete med fjerna metadata, brukt når ein ser på grupper av bilete som hovudtidslinja", "image_thumbnail_description": "Lite miniatyrbilete med fjerna metadata, brukt når ein ser på grupper av bilete som hovudtidslinja",
"image_thumbnail_quality_description": "Kvalitet på miniatyrbilete frå 1-100. Høgare er betre, men gjev større filstorleik, og kan senkje appresposen.", "image_thumbnail_quality_description": "Kvalitet på miniatyrbilete frå 1-100. Høgare er betre, men gjev større filstorleik, og kan senkje appresposen.",
"image_thumbnail_title": "Innstillingar for miniatyrbilete", "image_thumbnail_title": "Innstillingar for miniatyrbilete",
"import_config_from_json_description": "Importer systemkonfigurasjon ved å laste opp ei JSON konfigurasjonsfil",
"job_concurrency": "{job} samstundes utføring", "job_concurrency": "{job} samstundes utføring",
"job_created": "Jobb laga", "job_created": "Jobb laga",
"job_not_concurrency_safe": "Kan ikke trygt utføre jobben samstundes.", "job_not_concurrency_safe": "Kan ikke trygt utføre jobben samstundes.",
@@ -117,30 +112,22 @@
"job_settings_description": "Handsam samstundes utføring av jobber", "job_settings_description": "Handsam samstundes utføring av jobber",
"jobs_delayed": "{jobCount, plural, other {# forsinka}}", "jobs_delayed": "{jobCount, plural, other {# forsinka}}",
"jobs_failed": "{jobCount, plural, other {# mislykkast}}", "jobs_failed": "{jobCount, plural, other {# mislykkast}}",
"jobs_over_time": "Jobbar over tid",
"library_created": "Opprett bibliotek: {library}", "library_created": "Opprett bibliotek: {library}",
"library_deleted": "Bibliotek sletta", "library_deleted": "Bibliotek sletta",
"library_details": "Bibliotekdetaljar",
"library_folder_description": "Vel ei mappe å importere. Denne mappa, inkludert undermappar, vil bli skanna for biletar og videoar.",
"library_remove_exclusion_pattern_prompt": "Er du sikker på at du vil fjerne dette unntaksmønsteret?",
"library_scanning": "Regelbunden skanning", "library_scanning": "Regelbunden skanning",
"library_scanning_description": "Sett opp regelbunden skanning av biblioteket", "library_scanning_description": "Sett opp regelbunden skanning av biblioteket",
"library_scanning_enable_description": "Aktiver regelbunden skanning av biblioteket", "library_scanning_enable_description": "Aktiver regelbunden skanning av biblioteket",
"library_settings": "Eksternt Bibliotek", "library_settings": "Eksternt Bibliotek",
"library_settings_description": "Handsam eksterne biblioteksinnstillingar", "library_settings_description": "Handsam eksterne biblioteksinnstillingar",
"library_tasks_description": "Utfør bibliotekstoppgåver", "library_tasks_description": "Utfør bibliotekstoppgåver",
"library_updated": "Oppdatert bibliotek",
"library_watching_enable_description": "Sjekk eksterne bibliotek for forandringar", "library_watching_enable_description": "Sjekk eksterne bibliotek for forandringar",
"library_watching_settings": "Biblioteksovervåking (EKSPERIMENTELL)", "library_watching_settings": "Biblioteksovervåking (EKSPERIMENTELL)",
"library_watching_settings_description": "Sjekk automatisk for forandringar", "library_watching_settings_description": "Sjekk automatisk for forandringar",
"logging_enable_description": "Aktiver loggføring", "logging_enable_description": "Aktiver loggføring",
"logging_level_description": "Når aktivert, kva loggnivå å bruke.", "logging_level_description": "Når aktivert, kva loggnivå å bruke.",
"logging_settings": "Logging", "logging_settings": "Logging",
"machine_learning_availability_checks": "Tilgjengelegheitssjekkar",
"machine_learning_availability_checks_description": "Automatiser oppdaging og prioritet av tilgjengelege maskinlærings-serverar", "machine_learning_availability_checks_description": "Automatiser oppdaging og prioritet av tilgjengelege maskinlærings-serverar",
"machine_learning_availability_checks_enabled": "Slå på tilgjengelegheitssjekkar",
"machine_learning_availability_checks_interval": "Sjekk intervall", "machine_learning_availability_checks_interval": "Sjekk intervall",
"machine_learning_availability_checks_timeout": "Tidsavbrot på forespørsel",
"machine_learning_availability_checks_timeout_description": "Utløpstid i millisekund for tilgjengelegheitssjekk", "machine_learning_availability_checks_timeout_description": "Utløpstid i millisekund for tilgjengelegheitssjekk",
"machine_learning_clip_model": "CLIP modell", "machine_learning_clip_model": "CLIP modell",
"machine_learning_clip_model_description": "Namnet på ein CLIP modell finst <link>her</link>. Merk at du må køyre 'Smart Søk'-jobben på nytt for alle bilete etter du har forandra modell.", "machine_learning_clip_model_description": "Namnet på ein CLIP modell finst <link>her</link>. Merk at du må køyre 'Smart Søk'-jobben på nytt for alle bilete etter du har forandra modell.",
@@ -164,11 +151,6 @@
"machine_learning_min_detection_score_description": "Minimum tillitspoeng for at eit ansikt skal bli oppdaga, på ein skala frå 0 til 1. Lågare verdiar vil oppdage fleire ansikt, men kan føre til feilaktige treff.", "machine_learning_min_detection_score_description": "Minimum tillitspoeng for at eit ansikt skal bli oppdaga, på ein skala frå 0 til 1. Lågare verdiar vil oppdage fleire ansikt, men kan føre til feilaktige treff.",
"machine_learning_min_recognized_faces": "Minimum gjenkjende ansikt", "machine_learning_min_recognized_faces": "Minimum gjenkjende ansikt",
"machine_learning_min_recognized_faces_description": "Minste tal på gjenkjende fjes for å opprette ein person. Aukar ein dette, vert ansiktsgjenkjenninga meir presis, på bekostning av auka sjanse for at ansikt ikkje vert tileigna ein person.", "machine_learning_min_recognized_faces_description": "Minste tal på gjenkjende fjes for å opprette ein person. Aukar ein dette, vert ansiktsgjenkjenninga meir presis, på bekostning av auka sjanse for at ansikt ikkje vert tileigna ein person.",
"machine_learning_ocr": "OCR",
"machine_learning_ocr_description": "Bruk maskinlæring for å gjenkjenne tekst i bilete",
"machine_learning_ocr_enabled": "Slå på OCR",
"machine_learning_ocr_max_resolution": "Maksimal oppløysing",
"machine_learning_ocr_model": "OCR-modell",
"machine_learning_settings": "Innstillingar for maskinlæring", "machine_learning_settings": "Innstillingar for maskinlæring",
"machine_learning_settings_description": "Administrer maskinlæringsfunksjonar og innstillingar", "machine_learning_settings_description": "Administrer maskinlæringsfunksjonar og innstillingar",
"machine_learning_smart_search": "Smart Søk", "machine_learning_smart_search": "Smart Søk",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich-i18n", "name": "immich-i18n",
"version": "2.7.1", "version": "2.6.3",
"private": true, "private": true,
"scripts": { "scripts": {
"format": "prettier --cache --check .", "format": "prettier --cache --check .",
+2 -2
View File
@@ -2224,12 +2224,12 @@
"tag_updated": "Uaktualniono etykietę: {tag}", "tag_updated": "Uaktualniono etykietę: {tag}",
"tagged_assets": "Przypisano etykietę {count, plural, one {# zasobowi} other {# zasobom}}", "tagged_assets": "Przypisano etykietę {count, plural, one {# zasobowi} other {# zasobom}}",
"tags": "Etykiety", "tags": "Etykiety",
"tap_to_run_job": "Naciśnij, żeby uruchom zadanie", "tap_to_run_job": "Uruchom zadanie",
"template": "Szablon", "template": "Szablon",
"text_recognition": "Rozpoznawanie tekstu", "text_recognition": "Rozpoznawanie tekstu",
"theme": "Motyw", "theme": "Motyw",
"theme_selection": "Wybór motywu", "theme_selection": "Wybór motywu",
"theme_selection_description": "Automatycznie zmień motyw na jasny lub ciemny zależnie od ustawień systemu", "theme_selection_description": "Automatycznie zmień motyw na jasny lub ciemny zależnie od ustawień przeglądarki",
"theme_setting_asset_list_storage_indicator_title": "Pokaż wskaźnik przechowywania na kafelkach zasobów", "theme_setting_asset_list_storage_indicator_title": "Pokaż wskaźnik przechowywania na kafelkach zasobów",
"theme_setting_asset_list_tiles_per_row_title": "Liczba zasobów w wierszu ({count})", "theme_setting_asset_list_tiles_per_row_title": "Liczba zasobów w wierszu ({count})",
"theme_setting_colorful_interface_subtitle": "Zastosuj kolor podstawowy do powierzchni tła.", "theme_setting_colorful_interface_subtitle": "Zastosuj kolor podstawowy do powierzchni tła.",
-4
View File
@@ -849,12 +849,9 @@
"create_link_to_share": "Criar link para partilhar", "create_link_to_share": "Criar link para partilhar",
"create_link_to_share_description": "Permitir a visualização desta(s) imagem(s) a qualquer pessoa com o link", "create_link_to_share_description": "Permitir a visualização desta(s) imagem(s) a qualquer pessoa com o link",
"create_new": "CRIAR NOVO", "create_new": "CRIAR NOVO",
"create_new_face": "Criar novo rosto",
"create_new_person": "Criar nova pessoa", "create_new_person": "Criar nova pessoa",
"create_new_person_hint": "Associe os ficheiros a uma nova pessoa", "create_new_person_hint": "Associe os ficheiros a uma nova pessoa",
"create_new_user": "Criar novo utilizador", "create_new_user": "Criar novo utilizador",
"create_person": "Criar pessoa",
"create_person_subtitle": "Adicione um nome ao rosto selecionado para criar e etiquetar a nova pessoa",
"create_shared_album_page_share_add_assets": "ADICIONAR FICHEIROS", "create_shared_album_page_share_add_assets": "ADICIONAR FICHEIROS",
"create_shared_album_page_share_select_photos": "Selecionar Fotos", "create_shared_album_page_share_select_photos": "Selecionar Fotos",
"create_shared_link": "Criar link partilhado", "create_shared_link": "Criar link partilhado",
@@ -2217,7 +2214,6 @@
"tag": "Etiqueta", "tag": "Etiqueta",
"tag_assets": "Etiquetar ficheiros", "tag_assets": "Etiquetar ficheiros",
"tag_created": "Criada a etiqueta {tag}", "tag_created": "Criada a etiqueta {tag}",
"tag_face": "Etiquetar rosto",
"tag_feature_description": "A mostrar fotos e videos agrupados por tópicos lógicos de etiquetas", "tag_feature_description": "A mostrar fotos e videos agrupados por tópicos lógicos de etiquetas",
"tag_not_found_question": "Não consegue encontrar a etiqueta? <link>Crie uma nova etiqueta.</link>", "tag_not_found_question": "Não consegue encontrar a etiqueta? <link>Crie uma nova etiqueta.</link>",
"tag_people": "Etiquetar Pessoas", "tag_people": "Etiquetar Pessoas",
-4
View File
@@ -849,12 +849,9 @@
"create_link_to_share": "Создать ссылку общего доступа", "create_link_to_share": "Создать ссылку общего доступа",
"create_link_to_share_description": "Разрешить всем, у кого есть ссылка, просматривать выбранные фотографии", "create_link_to_share_description": "Разрешить всем, у кого есть ссылка, просматривать выбранные фотографии",
"create_new": "СОЗДАТЬ НОВЫЙ", "create_new": "СОЗДАТЬ НОВЫЙ",
"create_new_face": "Создать новое лицо",
"create_new_person": "Добавить нового человека", "create_new_person": "Добавить нового человека",
"create_new_person_hint": "Назначить выбранные объекты на нового человека", "create_new_person_hint": "Назначить выбранные объекты на нового человека",
"create_new_user": "Создать нового пользователя", "create_new_user": "Создать нового пользователя",
"create_person": "Создать человека",
"create_person_subtitle": "Укажите имя для создания нового человека",
"create_shared_album_page_share_add_assets": "ДОБАВИТЬ ОБЪЕКТЫ", "create_shared_album_page_share_add_assets": "ДОБАВИТЬ ОБЪЕКТЫ",
"create_shared_album_page_share_select_photos": "Выбрать фотографии", "create_shared_album_page_share_select_photos": "Выбрать фотографии",
"create_shared_link": "Создать общую ссылку", "create_shared_link": "Создать общую ссылку",
@@ -2217,7 +2214,6 @@
"tag": "Тег", "tag": "Тег",
"tag_assets": "Добавить теги", "tag_assets": "Добавить теги",
"tag_created": "Тег {tag} создан", "tag_created": "Тег {tag} создан",
"tag_face": "Отметить человека",
"tag_feature_description": "Просмотр фотографий и видео, сгруппированных по тегам", "tag_feature_description": "Просмотр фотографий и видео, сгруппированных по тегам",
"tag_not_found_question": "Не удается найти тег? <link>Создайте новый тег.</link>", "tag_not_found_question": "Не удается найти тег? <link>Создайте новый тег.</link>",
"tag_people": "Отметить человека", "tag_people": "Отметить человека",
-4
View File
@@ -849,12 +849,9 @@
"create_link_to_share": "Ustvari povezavo za skupno rabo", "create_link_to_share": "Ustvari povezavo za skupno rabo",
"create_link_to_share_description": "Omogoči vsem s povezavo ogled izbranih fotografij", "create_link_to_share_description": "Omogoči vsem s povezavo ogled izbranih fotografij",
"create_new": "USTVARI NOVEGA", "create_new": "USTVARI NOVEGA",
"create_new_face": "Ustvari nov obraz",
"create_new_person": "Ustvari novo osebo", "create_new_person": "Ustvari novo osebo",
"create_new_person_hint": "Dodeli izbrana sredstva novi osebi", "create_new_person_hint": "Dodeli izbrana sredstva novi osebi",
"create_new_user": "Ustvari novega uporabnika", "create_new_user": "Ustvari novega uporabnika",
"create_person": "Ustvari osebo",
"create_person_subtitle": "Dodajte ime izbranemu obrazu, da ustvarite in označite novo osebo",
"create_shared_album_page_share_add_assets": "DODAJ SREDSTVA", "create_shared_album_page_share_add_assets": "DODAJ SREDSTVA",
"create_shared_album_page_share_select_photos": "Izberi fotografije", "create_shared_album_page_share_select_photos": "Izberi fotografije",
"create_shared_link": "Ustvari deljeno povezavo", "create_shared_link": "Ustvari deljeno povezavo",
@@ -2217,7 +2214,6 @@
"tag": "Oznaka", "tag": "Oznaka",
"tag_assets": "Označi sredstva", "tag_assets": "Označi sredstva",
"tag_created": "Ustvarjena oznaka: {tag}", "tag_created": "Ustvarjena oznaka: {tag}",
"tag_face": "Označi obraz",
"tag_feature_description": "Brskanje po fotografijah in videoposnetkih, razvrščenih po temah logičnih oznak", "tag_feature_description": "Brskanje po fotografijah in videoposnetkih, razvrščenih po temah logičnih oznak",
"tag_not_found_question": "Ne najdete oznake? <link>Ustvarite novo oznako.</link>", "tag_not_found_question": "Ne najdete oznake? <link>Ustvarite novo oznako.</link>",
"tag_people": "Označi osebe", "tag_people": "Označi osebe",
-4
View File
@@ -849,12 +849,9 @@
"create_link_to_share": "Skapa länk att dela", "create_link_to_share": "Skapa länk att dela",
"create_link_to_share_description": "Låt alla med länken se de valda fotona", "create_link_to_share_description": "Låt alla med länken se de valda fotona",
"create_new": "SKAPA NY", "create_new": "SKAPA NY",
"create_new_face": "Skapa nytt ansikte",
"create_new_person": "Skapa ny person", "create_new_person": "Skapa ny person",
"create_new_person_hint": "Tilldela valda objekt till en ny person", "create_new_person_hint": "Tilldela valda objekt till en ny person",
"create_new_user": "Skapa en ny användare", "create_new_user": "Skapa en ny användare",
"create_person": "Skapa person",
"create_person_subtitle": "Lägg till ett namn till det valda ansiktet för att skapa och tagga den nya personen",
"create_shared_album_page_share_add_assets": "LÄGG TILL OBJEKT", "create_shared_album_page_share_add_assets": "LÄGG TILL OBJEKT",
"create_shared_album_page_share_select_photos": "Välj bilder", "create_shared_album_page_share_select_photos": "Välj bilder",
"create_shared_link": "Skapa delad länk", "create_shared_link": "Skapa delad länk",
@@ -2217,7 +2214,6 @@
"tag": "Tagg", "tag": "Tagg",
"tag_assets": "Tagga objekt", "tag_assets": "Tagga objekt",
"tag_created": "Skapade tagg: {tag}", "tag_created": "Skapade tagg: {tag}",
"tag_face": "Tagga ansikte",
"tag_feature_description": "Bläddra bland foton och videor grupperade efter logiska taggar", "tag_feature_description": "Bläddra bland foton och videor grupperade efter logiska taggar",
"tag_not_found_question": "Kan du inte hitta en tagg? <link>Skapa en ny tagg.</link>", "tag_not_found_question": "Kan du inte hitta en tagg? <link>Skapa en ny tagg.</link>",
"tag_people": "Tagga Personer", "tag_people": "Tagga Personer",
-3
View File
@@ -2123,12 +2123,9 @@
"sync_upload_album_setting_subtitle": "สร้างและอัปโหลดรูปภาพและวิดีโอของคุณไปยังอัลบั้มที่เลือกบน Immich", "sync_upload_album_setting_subtitle": "สร้างและอัปโหลดรูปภาพและวิดีโอของคุณไปยังอัลบั้มที่เลือกบน Immich",
"tag": "แท็ก", "tag": "แท็ก",
"tag_created": "สร้างแท็ก: {tag}", "tag_created": "สร้างแท็ก: {tag}",
"tag_face": "แท็กใบหน้า",
"tag_feature_description": "ดูรูปถ่ายและวีดีโอที่สร้างกลุ่มตามหัวข้อแท็ก",
"tag_not_found_question": "ไม่สามารถหาแท็กได้ใช่หรือไม่?<link>สร้างแท็กใหม่</link>", "tag_not_found_question": "ไม่สามารถหาแท็กได้ใช่หรือไม่?<link>สร้างแท็กใหม่</link>",
"tag_people": "แท็กผู้คน", "tag_people": "แท็กผู้คน",
"tag_updated": "แท็กที่ถูกอัพเดต: {tag}", "tag_updated": "แท็กที่ถูกอัพเดต: {tag}",
"tagged_assets": "ที่ถูกแท็ก",
"tags": "แท็ก", "tags": "แท็ก",
"tap_to_run_job": "แตะเพื่อรันงาน", "tap_to_run_job": "แตะเพื่อรันงาน",
"template": "เท็มเพลต", "template": "เท็มเพลต",
-4
View File
@@ -849,12 +849,9 @@
"create_link_to_share": "Paylaşmak için link oluştur", "create_link_to_share": "Paylaşmak için link oluştur",
"create_link_to_share_description": "Bağlantıya sahip olan herkesin seçilen fotoğrafları görmesine izin ver", "create_link_to_share_description": "Bağlantıya sahip olan herkesin seçilen fotoğrafları görmesine izin ver",
"create_new": "YENİ OLUŞTUR", "create_new": "YENİ OLUŞTUR",
"create_new_face": "Yeni yüz oluştur",
"create_new_person": "Yeni kişi oluştur", "create_new_person": "Yeni kişi oluştur",
"create_new_person_hint": "Seçili öğeleri yeni bir kişiye atayın", "create_new_person_hint": "Seçili öğeleri yeni bir kişiye atayın",
"create_new_user": "Yeni kullanıcı oluştur", "create_new_user": "Yeni kullanıcı oluştur",
"create_person": "Kişi oluştur",
"create_person_subtitle": "Seçilen yüze bir isim ekleyerek yeni kişiyi oluşturun ve etiketleyin",
"create_shared_album_page_share_add_assets": "ÖĞELER EKLE", "create_shared_album_page_share_add_assets": "ÖĞELER EKLE",
"create_shared_album_page_share_select_photos": "Fotoğrafları Seç", "create_shared_album_page_share_select_photos": "Fotoğrafları Seç",
"create_shared_link": "Paylaşılan bağlantı oluştur", "create_shared_link": "Paylaşılan bağlantı oluştur",
@@ -2217,7 +2214,6 @@
"tag": "Etiket", "tag": "Etiket",
"tag_assets": "Öğeleri etiketle", "tag_assets": "Öğeleri etiketle",
"tag_created": "Etiket oluşturuldu: {tag}", "tag_created": "Etiket oluşturuldu: {tag}",
"tag_face": "Yüzü etiketle",
"tag_feature_description": "Etiket temalarına göre gruplandırılmış fotoğraf ve videoları keşfedin", "tag_feature_description": "Etiket temalarına göre gruplandırılmış fotoğraf ve videoları keşfedin",
"tag_not_found_question": "Etiket bulunamadı mı? <link>Yeni bir etiket oluşturun.</link>", "tag_not_found_question": "Etiket bulunamadı mı? <link>Yeni bir etiket oluşturun.</link>",
"tag_people": "İnsanları etiketle", "tag_people": "İnsanları etiketle",
+3 -7
View File
@@ -40,7 +40,7 @@
"add_to_albums_count": "添加到相册 ({count})", "add_to_albums_count": "添加到相册 ({count})",
"add_to_bottom_bar": "添加到", "add_to_bottom_bar": "添加到",
"add_to_shared_album": "添加到共享相册", "add_to_shared_album": "添加到共享相册",
"add_upload_to_stack": "添加上传至堆", "add_upload_to_stack": "添加上传至堆",
"add_url": "添加 URL", "add_url": "添加 URL",
"add_workflow_step": "添加工作流步骤", "add_workflow_step": "添加工作流步骤",
"added_to_archive": "添加至存档", "added_to_archive": "添加至存档",
@@ -622,7 +622,7 @@
"backup": "备份", "backup": "备份",
"backup_album_selection_page_albums_device": "设备上的相簿({count}", "backup_album_selection_page_albums_device": "设备上的相簿({count}",
"backup_album_selection_page_albums_tap": "单击包含,双击排除", "backup_album_selection_page_albums_tap": "单击包含,双击排除",
"backup_album_selection_page_assets_scatter": "资源可以分散在多个相册中。因此,在备份过程中,可以包含或排除某些相册。", "backup_album_selection_page_assets_scatter": "选择包含或排除特定的相簿。(因为文件可能分散在多个相簿中)",
"backup_album_selection_page_select_albums": "选择相簿", "backup_album_selection_page_select_albums": "选择相簿",
"backup_album_selection_page_selection_info": "选择信息", "backup_album_selection_page_selection_info": "选择信息",
"backup_album_selection_page_total_assets": "选中的照片或视频总数", "backup_album_selection_page_total_assets": "选中的照片或视频总数",
@@ -809,7 +809,7 @@
"confirm_admin_password": "确认管理员密码", "confirm_admin_password": "确认管理员密码",
"confirm_delete_face": "确定要从此文件中删除 {name} 的面部信息吗?", "confirm_delete_face": "确定要从此文件中删除 {name} 的面部信息吗?",
"confirm_delete_shared_link": "确定要删除此共享链接吗?", "confirm_delete_shared_link": "确定要删除此共享链接吗?",
"confirm_keep_this_delete_others": "堆中除此项目外的所有其他项目都将被删除。确定要继续吗?", "confirm_keep_this_delete_others": "堆中除此项目外的所有其他项目都将被删除。确定要继续吗?",
"confirm_new_pin_code": "确认新 PIN 码", "confirm_new_pin_code": "确认新 PIN 码",
"confirm_password": "确认密码", "confirm_password": "确认密码",
"confirm_tag_face": "是否将此人脸标记为 {name}", "confirm_tag_face": "是否将此人脸标记为 {name}",
@@ -849,12 +849,9 @@
"create_link_to_share": "创建共享链接", "create_link_to_share": "创建共享链接",
"create_link_to_share_description": "允许任何拥有链接的人查看所选照片", "create_link_to_share_description": "允许任何拥有链接的人查看所选照片",
"create_new": "新建", "create_new": "新建",
"create_new_face": "创建新人脸",
"create_new_person": "创建新人物", "create_new_person": "创建新人物",
"create_new_person_hint": "将所选照片/视频分配给新人物", "create_new_person_hint": "将所选照片/视频分配给新人物",
"create_new_user": "新建用户", "create_new_user": "新建用户",
"create_person": "创建人物",
"create_person_subtitle": "为所选人脸添加姓名,以创建并标记新人物",
"create_shared_album_page_share_add_assets": "添加照片/视频", "create_shared_album_page_share_add_assets": "添加照片/视频",
"create_shared_album_page_share_select_photos": "选择照片", "create_shared_album_page_share_select_photos": "选择照片",
"create_shared_link": "创建共享链接", "create_shared_link": "创建共享链接",
@@ -2217,7 +2214,6 @@
"tag": "标签", "tag": "标签",
"tag_assets": "标记项目", "tag_assets": "标记项目",
"tag_created": "已创建标签:{tag}", "tag_created": "已创建标签:{tag}",
"tag_face": "标记人脸",
"tag_feature_description": "按逻辑标签分组并浏览照片和视频", "tag_feature_description": "按逻辑标签分组并浏览照片和视频",
"tag_not_found_question": "找不到标签吗?<link>创建新标签。</link>", "tag_not_found_question": "找不到标签吗?<link>创建新标签。</link>",
"tag_people": "命名人物", "tag_people": "命名人物",
+2 -2
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "immich-ml" name = "immich-ml"
version = "2.7.1" version = "2.6.3"
description = "" description = ""
authors = [{ name = "Hau Tran", email = "alex.tran1502@gmail.com" }] authors = [{ name = "Hau Tran", email = "alex.tran1502@gmail.com" }]
requires-python = ">=3.11,<4.0" requires-python = ">=3.11,<4.0"
@@ -11,7 +11,7 @@ dependencies = [
"gunicorn>=21.1.0", "gunicorn>=21.1.0",
"huggingface-hub>=0.20.1,<1.0", "huggingface-hub>=0.20.1,<1.0",
"insightface>=0.7.3,<1.0", "insightface>=0.7.3,<1.0",
"numpy<2.4.0", "numpy>=2.3.4",
"opencv-python-headless>=4.7.0.72,<5.0", "opencv-python-headless>=4.7.0.72,<5.0",
"orjson>=3.9.5", "orjson>=3.9.5",
"pillow>=12.1.1,<12.2", "pillow>=12.1.1,<12.2",
+75 -77
View File
@@ -898,7 +898,7 @@ wheels = [
[[package]] [[package]]
name = "immich-ml" name = "immich-ml"
version = "2.7.1" version = "2.6.3"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "aiocache" }, { name = "aiocache" },
@@ -987,7 +987,7 @@ requires-dist = [
{ name = "gunicorn", specifier = ">=21.1.0" }, { name = "gunicorn", specifier = ">=21.1.0" },
{ name = "huggingface-hub", specifier = ">=0.20.1,<1.0" }, { name = "huggingface-hub", specifier = ">=0.20.1,<1.0" },
{ name = "insightface", specifier = ">=0.7.3,<1.0" }, { name = "insightface", specifier = ">=0.7.3,<1.0" },
{ name = "numpy", specifier = "<2.4.0" }, { name = "numpy", specifier = ">=2.3.4" },
{ name = "onnxruntime", marker = "extra == 'armnn'", specifier = ">=1.23.2,<2" }, { name = "onnxruntime", marker = "extra == 'armnn'", specifier = ">=1.23.2,<2" },
{ name = "onnxruntime", marker = "extra == 'cpu'", specifier = ">=1.23.2,<2" }, { name = "onnxruntime", marker = "extra == 'cpu'", specifier = ">=1.23.2,<2" },
{ name = "onnxruntime", marker = "extra == 'rknn'", specifier = ">=1.23.2,<2" }, { name = "onnxruntime", marker = "extra == 'rknn'", specifier = ">=1.23.2,<2" },
@@ -1519,83 +1519,81 @@ wheels = [
[[package]] [[package]]
name = "numpy" name = "numpy"
version = "2.3.5" version = "2.4.2"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" } sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/43/77/84dd1d2e34d7e2792a236ba180b5e8fcc1e3e414e761ce0253f63d7f572e/numpy-2.3.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de5672f4a7b200c15a4127042170a694d4df43c992948f5e1af57f0174beed10", size = 17034641, upload-time = "2025-11-16T22:49:19.336Z" }, { url = "https://files.pythonhosted.org/packages/d3/44/71852273146957899753e69986246d6a176061ea183407e95418c2aa4d9a/numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7e88598032542bd49af7c4747541422884219056c268823ef6e5e89851c8825", size = 16955478, upload-time = "2026-01-31T23:10:25.623Z" },
{ url = "https://files.pythonhosted.org/packages/2a/ea/25e26fa5837106cde46ae7d0b667e20f69cbbc0efd64cba8221411ab26ae/numpy-2.3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:acfd89508504a19ed06ef963ad544ec6664518c863436306153e13e94605c218", size = 12528324, upload-time = "2025-11-16T22:49:22.582Z" }, { url = "https://files.pythonhosted.org/packages/74/41/5d17d4058bd0cd96bcbd4d9ff0fb2e21f52702aab9a72e4a594efa18692f/numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7edc794af8b36ca37ef5fcb5e0d128c7e0595c7b96a2318d1badb6fcd8ee86b1", size = 14965467, upload-time = "2026-01-31T23:10:28.186Z" },
{ url = "https://files.pythonhosted.org/packages/4d/1a/e85f0eea4cf03d6a0228f5c0256b53f2df4bc794706e7df019fc622e47f1/numpy-2.3.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ffe22d2b05504f786c867c8395de703937f934272eb67586817b46188b4ded6d", size = 5356872, upload-time = "2025-11-16T22:49:25.408Z" }, { url = "https://files.pythonhosted.org/packages/49/48/fb1ce8136c19452ed15f033f8aee91d5defe515094e330ce368a0647846f/numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6e9f61981ace1360e42737e2bae58b27bf28a1b27e781721047d84bd754d32e7", size = 5475172, upload-time = "2026-01-31T23:10:30.848Z" },
{ url = "https://files.pythonhosted.org/packages/5c/bb/35ef04afd567f4c989c2060cde39211e4ac5357155c1833bcd1166055c61/numpy-2.3.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:872a5cf366aec6bb1147336480fef14c9164b154aeb6542327de4970282cd2f5", size = 6893148, upload-time = "2025-11-16T22:49:27.549Z" }, { url = "https://files.pythonhosted.org/packages/40/a9/3feb49f17bbd1300dd2570432961f5c8a4ffeff1db6f02c7273bd020a4c9/numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cb7bbb88aa74908950d979eeaa24dbdf1a865e3c7e45ff0121d8f70387b55f73", size = 6805145, upload-time = "2026-01-31T23:10:32.352Z" },
{ url = "https://files.pythonhosted.org/packages/f2/2b/05bbeb06e2dff5eab512dfc678b1cc5ee94d8ac5956a0885c64b6b26252b/numpy-2.3.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3095bdb8dd297e5920b010e96134ed91d852d81d490e787beca7e35ae1d89cf7", size = 14557282, upload-time = "2025-11-16T22:49:30.964Z" }, { url = "https://files.pythonhosted.org/packages/3f/39/fdf35cbd6d6e2fcad42fcf85ac04a85a0d0fbfbf34b30721c98d602fd70a/numpy-2.4.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f069069931240b3fc703f1e23df63443dbd6390614c8c44a87d96cd0ec81eb1", size = 15966084, upload-time = "2026-01-31T23:10:34.502Z" },
{ url = "https://files.pythonhosted.org/packages/65/fb/2b23769462b34398d9326081fad5655198fcf18966fcb1f1e49db44fbf31/numpy-2.3.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cba086a43d54ca804ce711b2a940b16e452807acebe7852ff327f1ecd49b0d4", size = 16897903, upload-time = "2025-11-16T22:49:34.191Z" }, { url = "https://files.pythonhosted.org/packages/1b/46/6fa4ea94f1ddf969b2ee941290cca6f1bfac92b53c76ae5f44afe17ceb69/numpy-2.4.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c02ef4401a506fb60b411467ad501e1429a3487abca4664871d9ae0b46c8ba32", size = 16899477, upload-time = "2026-01-31T23:10:37.075Z" },
{ url = "https://files.pythonhosted.org/packages/ac/14/085f4cf05fc3f1e8aa95e85404e984ffca9b2275a5dc2b1aae18a67538b8/numpy-2.3.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6cf9b429b21df6b99f4dee7a1218b8b7ffbbe7df8764dc0bd60ce8a0708fed1e", size = 16341672, upload-time = "2025-11-16T22:49:37.2Z" }, { url = "https://files.pythonhosted.org/packages/09/a1/2a424e162b1a14a5bd860a464ab4e07513916a64ab1683fae262f735ccd2/numpy-2.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2653de5c24910e49c2b106499803124dde62a5a1fe0eedeaecf4309a5f639390", size = 17323429, upload-time = "2026-01-31T23:10:39.704Z" },
{ url = "https://files.pythonhosted.org/packages/6f/3b/1f73994904142b2aa290449b3bb99772477b5fd94d787093e4f24f5af763/numpy-2.3.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:396084a36abdb603546b119d96528c2f6263921c50df3c8fd7cb28873a237748", size = 18838896, upload-time = "2025-11-16T22:49:39.727Z" }, { url = "https://files.pythonhosted.org/packages/ce/a2/73014149ff250628df72c58204822ac01d768697913881aacf839ff78680/numpy-2.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ae241bbfc6ae276f94a170b14785e561cb5e7f626b6688cf076af4110887413", size = 18635109, upload-time = "2026-01-31T23:10:41.924Z" },
{ url = "https://files.pythonhosted.org/packages/cd/b9/cf6649b2124f288309ffc353070792caf42ad69047dcc60da85ee85fea58/numpy-2.3.5-cp311-cp311-win32.whl", hash = "sha256:b0c7088a73aef3d687c4deef8452a3ac7c1be4e29ed8bf3b366c8111128ac60c", size = 6563608, upload-time = "2025-11-16T22:49:42.079Z" }, { url = "https://files.pythonhosted.org/packages/6c/0c/73e8be2f1accd56df74abc1c5e18527822067dced5ec0861b5bb882c2ce0/numpy-2.4.2-cp311-cp311-win32.whl", hash = "sha256:df1b10187212b198dd45fa943d8985a3c8cf854aed4923796e0e019e113a1bda", size = 6237915, upload-time = "2026-01-31T23:10:45.26Z" },
{ url = "https://files.pythonhosted.org/packages/aa/44/9fe81ae1dcc29c531843852e2874080dc441338574ccc4306b39e2ff6e59/numpy-2.3.5-cp311-cp311-win_amd64.whl", hash = "sha256:a414504bef8945eae5f2d7cb7be2d4af77c5d1cb5e20b296c2c25b61dff2900c", size = 13078442, upload-time = "2025-11-16T22:49:43.99Z" }, { url = "https://files.pythonhosted.org/packages/76/ae/e0265e0163cf127c24c3969d29f1c4c64551a1e375d95a13d32eab25d364/numpy-2.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:b9c618d56a29c9cb1c4da979e9899be7578d2e0b3c24d52079c166324c9e8695", size = 12607972, upload-time = "2026-01-31T23:10:47.021Z" },
{ url = "https://files.pythonhosted.org/packages/6d/a7/f99a41553d2da82a20a2f22e93c94f928e4490bb447c9ff3c4ff230581d3/numpy-2.3.5-cp311-cp311-win_arm64.whl", hash = "sha256:0cd00b7b36e35398fa2d16af7b907b65304ef8bb4817a550e06e5012929830fa", size = 10458555, upload-time = "2025-11-16T22:49:47.092Z" }, { url = "https://files.pythonhosted.org/packages/29/a5/c43029af9b8014d6ea157f192652c50042e8911f4300f8f6ed3336bf437f/numpy-2.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:47c5a6ed21d9452b10227e5e8a0e1c22979811cad7dcc19d8e3e2fb8fa03f1a3", size = 10485763, upload-time = "2026-01-31T23:10:50.087Z" },
{ url = "https://files.pythonhosted.org/packages/44/37/e669fe6cbb2b96c62f6bbedc6a81c0f3b7362f6a59230b23caa673a85721/numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e", size = 16733873, upload-time = "2025-11-16T22:49:49.84Z" }, { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" },
{ url = "https://files.pythonhosted.org/packages/c5/65/df0db6c097892c9380851ab9e44b52d4f7ba576b833996e0080181c0c439/numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769", size = 12259838, upload-time = "2025-11-16T22:49:52.863Z" }, { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" },
{ url = "https://files.pythonhosted.org/packages/5b/e1/1ee06e70eb2136797abe847d386e7c0e830b67ad1d43f364dd04fa50d338/numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5", size = 5088378, upload-time = "2025-11-16T22:49:55.055Z" }, { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" },
{ url = "https://files.pythonhosted.org/packages/6d/9c/1ca85fb86708724275103b81ec4cf1ac1d08f465368acfc8da7ab545bdae/numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4", size = 6628559, upload-time = "2025-11-16T22:49:57.371Z" }, { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" },
{ url = "https://files.pythonhosted.org/packages/74/78/fcd41e5a0ce4f3f7b003da85825acddae6d7ecb60cf25194741b036ca7d6/numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d", size = 14250702, upload-time = "2025-11-16T22:49:59.632Z" }, { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" },
{ url = "https://files.pythonhosted.org/packages/b6/23/2a1b231b8ff672b4c450dac27164a8b2ca7d9b7144f9c02d2396518352eb/numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28", size = 16606086, upload-time = "2025-11-16T22:50:02.127Z" }, { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" },
{ url = "https://files.pythonhosted.org/packages/a0/c5/5ad26fbfbe2012e190cc7d5003e4d874b88bb18861d0829edc140a713021/numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b", size = 16025985, upload-time = "2025-11-16T22:50:04.536Z" }, { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" },
{ url = "https://files.pythonhosted.org/packages/d2/fa/dd48e225c46c819288148d9d060b047fd2a6fb1eb37eae25112ee4cb4453/numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c", size = 18542976, upload-time = "2025-11-16T22:50:07.557Z" }, { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" },
{ url = "https://files.pythonhosted.org/packages/05/79/ccbd23a75862d95af03d28b5c6901a1b7da4803181513d52f3b86ed9446e/numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952", size = 6285274, upload-time = "2025-11-16T22:50:10.746Z" }, { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" },
{ url = "https://files.pythonhosted.org/packages/2d/57/8aeaf160312f7f489dea47ab61e430b5cb051f59a98ae68b7133ce8fa06a/numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa", size = 12782922, upload-time = "2025-11-16T22:50:12.811Z" }, { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" },
{ url = "https://files.pythonhosted.org/packages/78/a6/aae5cc2ca78c45e64b9ef22f089141d661516856cf7c8a54ba434576900d/numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013", size = 10194667, upload-time = "2025-11-16T22:50:16.16Z" }, { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" },
{ url = "https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff", size = 16728251, upload-time = "2025-11-16T22:50:19.013Z" }, { url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z" },
{ url = "https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188", size = 12254652, upload-time = "2025-11-16T22:50:21.487Z" }, { url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z" },
{ url = "https://files.pythonhosted.org/packages/78/da/8c7738060ca9c31b30e9301ee0cf6c5ffdbf889d9593285a1cead337f9a5/numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0", size = 5083172, upload-time = "2025-11-16T22:50:24.562Z" }, { url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z" },
{ url = "https://files.pythonhosted.org/packages/a4/b4/ee5bb2537fb9430fd2ef30a616c3672b991a4129bb1c7dcc42aa0abbe5d7/numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903", size = 6622990, upload-time = "2025-11-16T22:50:26.47Z" }, { url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z" },
{ url = "https://files.pythonhosted.org/packages/95/03/dc0723a013c7d7c19de5ef29e932c3081df1c14ba582b8b86b5de9db7f0f/numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d", size = 14248902, upload-time = "2025-11-16T22:50:28.861Z" }, { url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z" },
{ url = "https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017", size = 16597430, upload-time = "2025-11-16T22:50:31.56Z" }, { url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z" },
{ url = "https://files.pythonhosted.org/packages/2a/51/c1e29be863588db58175175f057286900b4b3327a1351e706d5e0f8dd679/numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf", size = 16024551, upload-time = "2025-11-16T22:50:34.242Z" }, { url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z" },
{ url = "https://files.pythonhosted.org/packages/83/68/8236589d4dbb87253d28259d04d9b814ec0ecce7cb1c7fed29729f4c3a78/numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce", size = 18533275, upload-time = "2025-11-16T22:50:37.651Z" }, { url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z" },
{ url = "https://files.pythonhosted.org/packages/40/56/2932d75b6f13465239e3b7b7e511be27f1b8161ca2510854f0b6e521c395/numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e", size = 6277637, upload-time = "2025-11-16T22:50:40.11Z" }, { url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z" },
{ url = "https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b", size = 12779090, upload-time = "2025-11-16T22:50:42.503Z" }, { url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z" },
{ url = "https://files.pythonhosted.org/packages/8f/88/3f41e13a44ebd4034ee17baa384acac29ba6a4fcc2aca95f6f08ca0447d1/numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae", size = 10194710, upload-time = "2025-11-16T22:50:44.971Z" }, { url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z" },
{ url = "https://files.pythonhosted.org/packages/13/cb/71744144e13389d577f867f745b7df2d8489463654a918eea2eeb166dfc9/numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd", size = 16827292, upload-time = "2025-11-16T22:50:47.715Z" }, { url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z" },
{ url = "https://files.pythonhosted.org/packages/71/80/ba9dc6f2a4398e7f42b708a7fdc841bb638d353be255655498edbf9a15a8/numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f", size = 12378897, upload-time = "2025-11-16T22:50:51.327Z" }, { url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z" },
{ url = "https://files.pythonhosted.org/packages/2e/6d/db2151b9f64264bcceccd51741aa39b50150de9b602d98ecfe7e0c4bff39/numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a", size = 5207391, upload-time = "2025-11-16T22:50:54.542Z" }, { url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z" },
{ url = "https://files.pythonhosted.org/packages/80/ae/429bacace5ccad48a14c4ae5332f6aa8ab9f69524193511d60ccdfdc65fa/numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139", size = 6721275, upload-time = "2025-11-16T22:50:56.794Z" }, { url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z" },
{ url = "https://files.pythonhosted.org/packages/74/5b/1919abf32d8722646a38cd527bc3771eb229a32724ee6ba340ead9b92249/numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e", size = 14306855, upload-time = "2025-11-16T22:50:59.208Z" }, { url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z" },
{ url = "https://files.pythonhosted.org/packages/a5/87/6831980559434973bebc30cd9c1f21e541a0f2b0c280d43d3afd909b66d0/numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9", size = 16657359, upload-time = "2025-11-16T22:51:01.991Z" }, { url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z" },
{ url = "https://files.pythonhosted.org/packages/dd/91/c797f544491ee99fd00495f12ebb7802c440c1915811d72ac5b4479a3356/numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946", size = 16093374, upload-time = "2025-11-16T22:51:05.291Z" }, { url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z" },
{ url = "https://files.pythonhosted.org/packages/74/a6/54da03253afcbe7a72785ec4da9c69fb7a17710141ff9ac5fcb2e32dbe64/numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1", size = 18594587, upload-time = "2025-11-16T22:51:08.585Z" }, { url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z" },
{ url = "https://files.pythonhosted.org/packages/80/e9/aff53abbdd41b0ecca94285f325aff42357c6b5abc482a3fcb4994290b18/numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3", size = 6405940, upload-time = "2025-11-16T22:51:11.541Z" }, { url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z" },
{ url = "https://files.pythonhosted.org/packages/d5/81/50613fec9d4de5480de18d4f8ef59ad7e344d497edbef3cfd80f24f98461/numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234", size = 12920341, upload-time = "2025-11-16T22:51:14.312Z" }, { url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z" },
{ url = "https://files.pythonhosted.org/packages/bb/ab/08fd63b9a74303947f34f0bd7c5903b9c5532c2d287bead5bdf4c556c486/numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7", size = 10262507, upload-time = "2025-11-16T22:51:16.846Z" }, { url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092, upload-time = "2026-01-31T23:12:04.521Z" },
{ url = "https://files.pythonhosted.org/packages/ba/97/1a914559c19e32d6b2e233cf9a6a114e67c856d35b1d6babca571a3e880f/numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82", size = 16735706, upload-time = "2025-11-16T22:51:19.558Z" }, { url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770, upload-time = "2026-01-31T23:12:06.96Z" },
{ url = "https://files.pythonhosted.org/packages/57/d4/51233b1c1b13ecd796311216ae417796b88b0616cfd8a33ae4536330748a/numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0", size = 12264507, upload-time = "2025-11-16T22:51:22.492Z" }, { url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562, upload-time = "2026-01-31T23:12:09.632Z" },
{ url = "https://files.pythonhosted.org/packages/45/98/2fe46c5c2675b8306d0b4a3ec3494273e93e1226a490f766e84298576956/numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63", size = 5093049, upload-time = "2025-11-16T22:51:25.171Z" }, { url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710, upload-time = "2026-01-31T23:12:11.969Z" },
{ url = "https://files.pythonhosted.org/packages/ce/0e/0698378989bb0ac5f1660c81c78ab1fe5476c1a521ca9ee9d0710ce54099/numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9", size = 6626603, upload-time = "2025-11-16T22:51:27Z" }, { url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205, upload-time = "2026-01-31T23:12:14.33Z" },
{ url = "https://files.pythonhosted.org/packages/5e/a6/9ca0eecc489640615642a6cbc0ca9e10df70df38c4d43f5a928ff18d8827/numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b", size = 14262696, upload-time = "2025-11-16T22:51:29.402Z" }, { url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738, upload-time = "2026-01-31T23:12:16.525Z" },
{ url = "https://files.pythonhosted.org/packages/c8/f6/07ec185b90ec9d7217a00eeeed7383b73d7e709dae2a9a021b051542a708/numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520", size = 16597350, upload-time = "2025-11-16T22:51:32.167Z" }, { url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888, upload-time = "2026-01-31T23:12:19.306Z" },
{ url = "https://files.pythonhosted.org/packages/75/37/164071d1dde6a1a84c9b8e5b414fa127981bad47adf3a6b7e23917e52190/numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c", size = 16040190, upload-time = "2025-11-16T22:51:35.403Z" }, { url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556, upload-time = "2026-01-31T23:12:21.816Z" },
{ url = "https://files.pythonhosted.org/packages/08/3c/f18b82a406b04859eb026d204e4e1773eb41c5be58410f41ffa511d114ae/numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8", size = 18536749, upload-time = "2025-11-16T22:51:39.698Z" }, { url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899, upload-time = "2026-01-31T23:12:24.14Z" },
{ url = "https://files.pythonhosted.org/packages/40/79/f82f572bf44cf0023a2fe8588768e23e1592585020d638999f15158609e1/numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248", size = 6335432, upload-time = "2025-11-16T22:51:42.476Z" }, { url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072, upload-time = "2026-01-31T23:12:26.33Z" },
{ url = "https://files.pythonhosted.org/packages/a3/2e/235b4d96619931192c91660805e5e49242389742a7a82c27665021db690c/numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e", size = 12919388, upload-time = "2025-11-16T22:51:45.275Z" }, { url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886, upload-time = "2026-01-31T23:12:28.488Z" },
{ url = "https://files.pythonhosted.org/packages/07/2b/29fd75ce45d22a39c61aad74f3d718e7ab67ccf839ca8b60866054eb15f8/numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2", size = 10476651, upload-time = "2025-11-16T22:51:47.749Z" }, { url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567, upload-time = "2026-01-31T23:12:30.709Z" },
{ url = "https://files.pythonhosted.org/packages/17/e1/f6a721234ebd4d87084cfa68d081bcba2f5cfe1974f7de4e0e8b9b2a2ba1/numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41", size = 16834503, upload-time = "2025-11-16T22:51:50.443Z" }, { url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372, upload-time = "2026-01-31T23:12:32.962Z" },
{ url = "https://files.pythonhosted.org/packages/5c/1c/baf7ffdc3af9c356e1c135e57ab7cf8d247931b9554f55c467efe2c69eff/numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad", size = 12381612, upload-time = "2025-11-16T22:51:53.609Z" }, { url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306, upload-time = "2026-01-31T23:12:34.797Z" },
{ url = "https://files.pythonhosted.org/packages/74/91/f7f0295151407ddc9ba34e699013c32c3c91944f9b35fcf9281163dc1468/numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39", size = 5210042, upload-time = "2025-11-16T22:51:56.213Z" }, { url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394, upload-time = "2026-01-31T23:12:36.565Z" },
{ url = "https://files.pythonhosted.org/packages/2e/3b/78aebf345104ec50dd50a4d06ddeb46a9ff5261c33bcc58b1c4f12f85ec2/numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20", size = 6724502, upload-time = "2025-11-16T22:51:58.584Z" }, { url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343, upload-time = "2026-01-31T23:12:39.188Z" },
{ url = "https://files.pythonhosted.org/packages/02/c6/7c34b528740512e57ef1b7c8337ab0b4f0bddf34c723b8996c675bc2bc91/numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52", size = 14308962, upload-time = "2025-11-16T22:52:01.698Z" }, { url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045, upload-time = "2026-01-31T23:12:42.041Z" },
{ url = "https://files.pythonhosted.org/packages/80/35/09d433c5262bc32d725bafc619e095b6a6651caf94027a03da624146f655/numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b", size = 16655054, upload-time = "2025-11-16T22:52:04.267Z" }, { url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024, upload-time = "2026-01-31T23:12:44.331Z" },
{ url = "https://files.pythonhosted.org/packages/7a/ab/6a7b259703c09a88804fa2430b43d6457b692378f6b74b356155283566ac/numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3", size = 16091613, upload-time = "2025-11-16T22:52:08.651Z" }, { url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z" },
{ url = "https://files.pythonhosted.org/packages/c2/88/330da2071e8771e60d1038166ff9d73f29da37b01ec3eb43cb1427464e10/numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227", size = 18591147, upload-time = "2025-11-16T22:52:11.453Z" }, { url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z" },
{ url = "https://files.pythonhosted.org/packages/51/41/851c4b4082402d9ea860c3626db5d5df47164a712cb23b54be028b184c1c/numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5", size = 6479806, upload-time = "2025-11-16T22:52:14.641Z" }, { url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" },
{ url = "https://files.pythonhosted.org/packages/90/30/d48bde1dfd93332fa557cff1972fbc039e055a52021fbef4c2c4b1eefd17/numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf", size = 13105760, upload-time = "2025-11-16T22:52:17.975Z" }, { url = "https://files.pythonhosted.org/packages/f4/f8/50e14d36d915ef64d8f8bc4a087fc8264d82c785eda6711f80ab7e620335/numpy-2.4.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:89f7268c009bc492f506abd6f5265defa7cb3f7487dc21d357c3d290add45082", size = 16833179, upload-time = "2026-01-31T23:12:53.5Z" },
{ url = "https://files.pythonhosted.org/packages/2d/fd/4b5eb0b3e888d86aee4d198c23acec7d214baaf17ea93c1adec94c9518b9/numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42", size = 10545459, upload-time = "2025-11-16T22:52:20.55Z" }, { url = "https://files.pythonhosted.org/packages/17/17/809b5cad63812058a8189e91a1e2d55a5a18fd04611dbad244e8aeae465c/numpy-2.4.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6dee3bb76aa4009d5a912180bf5b2de012532998d094acee25d9cb8dee3e44a", size = 14889755, upload-time = "2026-01-31T23:12:55.933Z" },
{ url = "https://files.pythonhosted.org/packages/c6/65/f9dea8e109371ade9c782b4e4756a82edf9d3366bca495d84d79859a0b79/numpy-2.3.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f0963b55cdd70fad460fa4c1341f12f976bb26cb66021a5580329bd498988310", size = 16910689, upload-time = "2025-11-16T22:52:23.247Z" }, { url = "https://files.pythonhosted.org/packages/3e/ea/181b9bcf7627fc8371720316c24db888dcb9829b1c0270abf3d288b2e29b/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:cd2bd2bbed13e213d6b55dc1d035a4f91748a7d3edc9480c13898b0353708920", size = 5399500, upload-time = "2026-01-31T23:12:58.671Z" },
{ url = "https://files.pythonhosted.org/packages/00/4f/edb00032a8fb92ec0a679d3830368355da91a69cab6f3e9c21b64d0bb986/numpy-2.3.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f4255143f5160d0de972d28c8f9665d882b5f61309d8362fdd3e103cf7bf010c", size = 12457053, upload-time = "2025-11-16T22:52:26.367Z" }, { url = "https://files.pythonhosted.org/packages/33/9f/413adf3fc955541ff5536b78fcf0754680b3c6d95103230252a2c9408d23/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cf28c0c1d4c4bf00f509fa7eb02c58d7caf221b50b467bcb0d9bbf1584d5c821", size = 6714252, upload-time = "2026-01-31T23:13:00.518Z" },
{ url = "https://files.pythonhosted.org/packages/16/a4/e8a53b5abd500a63836a29ebe145fc1ab1f2eefe1cfe59276020373ae0aa/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:a4b9159734b326535f4dd01d947f919c6eefd2d9827466a696c44ced82dfbc18", size = 5285635, upload-time = "2025-11-16T22:52:29.266Z" }, { url = "https://files.pythonhosted.org/packages/91/da/643aad274e29ccbdf42ecd94dafe524b81c87bcb56b83872d54827f10543/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e04ae107ac591763a47398bb45b568fc38f02dbc4aa44c063f67a131f99346cb", size = 15797142, upload-time = "2026-01-31T23:13:02.219Z" },
{ url = "https://files.pythonhosted.org/packages/a3/2f/37eeb9014d9c8b3e9c55bc599c68263ca44fdbc12a93e45a21d1d56df737/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2feae0d2c91d46e59fcd62784a3a83b3fb677fead592ce51b5a6fbb4f95965ff", size = 6801770, upload-time = "2025-11-16T22:52:31.421Z" }, { url = "https://files.pythonhosted.org/packages/66/27/965b8525e9cb5dc16481b30a1b3c21e50c7ebf6e9dbd48d0c4d0d5089c7e/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:602f65afdef699cda27ec0b9224ae5dc43e328f4c24c689deaf77133dbee74d0", size = 16727979, upload-time = "2026-01-31T23:13:04.62Z" },
{ url = "https://files.pythonhosted.org/packages/7d/e4/68d2f474df2cb671b2b6c2986a02e520671295647dad82484cde80ca427b/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffac52f28a7849ad7576293c0cb7b9f08304e8f7d738a8cb8a90ec4c55a998eb", size = 14391768, upload-time = "2025-11-16T22:52:33.593Z" }, { url = "https://files.pythonhosted.org/packages/de/e5/b7d20451657664b07986c2f6e3be564433f5dcaf3482d68eaecd79afaf03/numpy-2.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be71bf1edb48ebbbf7f6337b5bfd2f895d1902f6335a5830b20141fc126ffba0", size = 12502577, upload-time = "2026-01-31T23:13:07.08Z" },
{ url = "https://files.pythonhosted.org/packages/b8/50/94ccd8a2b141cb50651fddd4f6a48874acb3c91c8f0842b08a6afc4b0b21/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63c0e9e7eea69588479ebf4a8a270d5ac22763cc5854e9a7eae952a3908103f7", size = 16729263, upload-time = "2025-11-16T22:52:36.369Z" },
{ url = "https://files.pythonhosted.org/packages/2d/ee/346fa473e666fe14c52fcdd19ec2424157290a032d4c41f98127bfb31ac7/numpy-2.3.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f16417ec91f12f814b10bafe79ef77e70113a2f5f7018640e7425ff979253425", size = 12967213, upload-time = "2025-11-16T22:52:39.38Z" },
] ]
[[package]] [[package]]
+1 -1
View File
@@ -17,7 +17,7 @@ config_roots = [
node = "24.14.1" node = "24.14.1"
flutter = "3.35.7" flutter = "3.35.7"
pnpm = "10.32.1" pnpm = "10.32.1"
terragrunt = "0.99.5" terragrunt = "0.99.4"
opentofu = "1.11.5" opentofu = "1.11.5"
java = "21.0.2" java = "21.0.2"
+2 -2
View File
@@ -35,8 +35,8 @@ platform :android do
task: 'bundle', task: 'bundle',
build_type: 'Release', build_type: 'Release',
properties: { properties: {
"android.injected.version.code" => 3042, "android.injected.version.code" => 3041,
"android.injected.version.name" => "2.7.1", "android.injected.version.name" => "2.6.3",
} }
) )
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab') upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
+1 -1
View File
@@ -80,7 +80,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>2.7.1</string> <string>2.6.3</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>
+1 -1
View File
@@ -3,7 +3,7 @@ Immich API
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 2.7.1 - API version: 2.6.3
- Generator version: 7.8.0 - Generator version: 7.8.0
- Build package: org.openapitools.codegen.languages.DartClientCodegen - Build package: org.openapitools.codegen.languages.DartClientCodegen
+1 -1
View File
@@ -2,7 +2,7 @@ name: immich_mobile
description: Immich - selfhosted backup media file on mobile phone description: Immich - selfhosted backup media file on mobile phone
publish_to: 'none' publish_to: 'none'
version: 2.7.1+3042 version: 2.6.3+3041
environment: environment:
sdk: '>=3.8.0 <4.0.0' sdk: '>=3.8.0 <4.0.0'
+1 -1
View File
@@ -15225,7 +15225,7 @@
"info": { "info": {
"title": "Immich", "title": "Immich",
"description": "Immich API", "description": "Immich API",
"version": "2.7.1", "version": "2.6.3",
"contact": {} "contact": {}
}, },
"tags": [ "tags": [
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "2.7.1", "version": "2.6.3",
"description": "Auto-generated TypeScript SDK for the Immich API", "description": "Auto-generated TypeScript SDK for the Immich API",
"type": "module", "type": "module",
"main": "./build/index.js", "main": "./build/index.js",
@@ -20,7 +20,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^24.12.0", "@types/node": "^24.12.0",
"typescript": "^6.0.0" "typescript": "^5.3.3"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
+1 -1
View File
@@ -1,6 +1,6 @@
/** /**
* Immich * Immich
* 2.7.1 * 2.6.3
* DO NOT MODIFY - This file has been generated using oazapfts. * DO NOT MODIFY - This file has been generated using oazapfts.
* See https://www.npmjs.com/package/oazapfts * See https://www.npmjs.com/package/oazapfts
*/ */
-1
View File
@@ -7,7 +7,6 @@
"outDir": "build", "outDir": "build",
"module": "Node16", "module": "Node16",
"moduleResolution": "Node16", "moduleResolution": "Node16",
"rootDir": "./src",
"lib": ["esnext", "dom"] "lib": ["esnext", "dom"]
}, },
"include": ["src/**/*.ts"] "include": ["src/**/*.ts"]
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich-monorepo", "name": "immich-monorepo",
"version": "2.7.1", "version": "2.6.3",
"description": "Monorepo for Immich", "description": "Monorepo for Immich",
"private": true, "private": true,
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be", "packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be",
+4 -4
View File
@@ -11,7 +11,7 @@
"devDependencies": { "devDependencies": {
"@extism/js-pdk": "^1.0.1", "@extism/js-pdk": "^1.0.1",
"esbuild": "^0.27.0", "esbuild": "^0.27.0",
"typescript": "^6.0.0" "typescript": "^5.3.2"
} }
}, },
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
@@ -509,9 +509,9 @@
} }
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "6.0.2", "version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
+1 -1
View File
@@ -14,6 +14,6 @@
"devDependencies": { "devDependencies": {
"@extism/js-pdk": "^1.0.1", "@extism/js-pdk": "^1.0.1",
"esbuild": "^0.27.0", "esbuild": "^0.27.0",
"typescript": "^6.0.0" "typescript": "^5.3.2"
} }
} }
+2 -2
View File
@@ -44,7 +44,7 @@ export function actionAddToAlbum() {
authToken, authToken,
assetId: data.asset.id, assetId: data.asset.id,
albumId: albumId, albumId: albumId,
}), })
); );
addAssetToAlbum(ptr.offset); addAssetToAlbum(ptr.offset);
@@ -61,7 +61,7 @@ export function actionArchive() {
authToken, authToken,
id: data.asset.id, id: data.asset.id,
visibility: 'archive', visibility: 'archive',
}), })
); );
updateAsset(ptr.offset); updateAsset(ptr.offset);
+7 -3
View File
@@ -2,13 +2,17 @@
"compilerOptions": { "compilerOptions": {
"target": "es2020", // Specify ECMAScript target version "target": "es2020", // Specify ECMAScript target version
"module": "commonjs", // Specify module code generation "module": "commonjs", // Specify module code generation
"lib": ["es2020"], // Specify a list of library files to be included in the compilation "lib": [
"types": ["./src/index.d.ts", "./node_modules/@extism/js-pdk"], // Specify a list of type definition files to be included in the compilation "es2020"
], // Specify a list of library files to be included in the compilation
"types": [
"@extism/js-pdk",
"./src/index.d.ts"
], // Specify a list of type definition files to be included in the compilation
"strict": true, // Enable all strict type-checking options "strict": true, // Enable all strict type-checking options
"esModuleInterop": true, // Enables compatibility with Babel-style module imports "esModuleInterop": true, // Enables compatibility with Babel-style module imports
"skipLibCheck": true, // Skip type checking of declaration files "skipLibCheck": true, // Skip type checking of declaration files
"allowJs": true, // Allow JavaScript files to be compiled "allowJs": true, // Allow JavaScript files to be compiled
"rootDir": "./src",
"noEmit": true // Do not emit outputs (no .js or .d.ts files) "noEmit": true // Do not emit outputs (no .js or .d.ts files)
}, },
"include": [ "include": [
+681 -938
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -2,9 +2,9 @@
"contentSecurityPolicy": { "contentSecurityPolicy": {
"directives": { "directives": {
"default-src": ["'self'"], "default-src": ["'self'"],
"script-src": ["'self'", "'wasm-unsafe-eval'", "'unsafe-inline'", "https://www.gstatic.com"], "script-src": ["'self'", "'wasm-unsafe-eval", "'unsafe-inline'", "https://www.gstatic.com"],
"style-src": ["'self'", "'unsafe-inline'"], "style-src": ["'self'", "'unsafe-inline'"],
"img-src": ["'self'", "data:", "blob:"], "img-src": ["'self'", "'data:'", "'blob:'"],
"connect-src": [ "connect-src": [
"'self'", "'self'",
"blob:", "blob:",
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich", "name": "immich",
"version": "2.7.1", "version": "2.6.3",
"description": "", "description": "",
"author": "", "author": "",
"private": true, "private": true,
@@ -149,7 +149,7 @@
"@types/react": "^19.0.0", "@types/react": "^19.0.0",
"@types/sanitize-html": "^2.13.0", "@types/sanitize-html": "^2.13.0",
"@types/semver": "^7.5.8", "@types/semver": "^7.5.8",
"@types/supertest": "^7.0.0", "@types/supertest": "^6.0.0",
"@types/ua-parser-js": "^0.7.36", "@types/ua-parser-js": "^0.7.36",
"@types/validator": "^13.15.2", "@types/validator": "^13.15.2",
"@vitest/coverage-v8": "^3.0.0", "@vitest/coverage-v8": "^3.0.0",
@@ -167,7 +167,7 @@
"supertest": "^7.1.0", "supertest": "^7.1.0",
"tailwindcss": "^3.4.0", "tailwindcss": "^3.4.0",
"testcontainers": "^11.0.0", "testcontainers": "^11.0.0",
"typescript": "^6.0.0", "typescript": "^5.9.2",
"typescript-eslint": "^8.28.0", "typescript-eslint": "^8.28.0",
"unplugin-swc": "^1.4.5", "unplugin-swc": "^1.4.5",
"vite-tsconfig-paths": "^6.0.0", "vite-tsconfig-paths": "^6.0.0",
+7 -8
View File
@@ -73,8 +73,7 @@ export function Chunked(
const originalMethod = descriptor.value; const originalMethod = descriptor.value;
const parameterIndex = options.paramIndex ?? 0; const parameterIndex = options.paramIndex ?? 0;
const chunkSize = options.chunkSize || DATABASE_PARAMETER_CHUNK_SIZE; const chunkSize = options.chunkSize || DATABASE_PARAMETER_CHUNK_SIZE;
const mergeFn = options.mergeFn; descriptor.value = async function (...arguments_: any[]) {
descriptor.value = function (...arguments_: any[]) {
const argument = arguments_[parameterIndex]; const argument = arguments_[parameterIndex];
// Early return if argument length is less than or equal to the chunk size. // Early return if argument length is less than or equal to the chunk size.
@@ -82,27 +81,27 @@ export function Chunked(
(Array.isArray(argument) && argument.length <= chunkSize) || (Array.isArray(argument) && argument.length <= chunkSize) ||
(argument instanceof Set && argument.size <= chunkSize) (argument instanceof Set && argument.size <= chunkSize)
) { ) {
return originalMethod.apply(this, arguments_); return await originalMethod.apply(this, arguments_);
} }
return Promise.all( return Promise.all(
chunks(argument, chunkSize).map((chunk) => { chunks(argument, chunkSize).map(async (chunk) => {
return Reflect.apply(originalMethod, this, [ return await Reflect.apply(originalMethod, this, [
...arguments_.slice(0, parameterIndex), ...arguments_.slice(0, parameterIndex),
chunk, chunk,
...arguments_.slice(parameterIndex + 1), ...arguments_.slice(parameterIndex + 1),
]); ]);
}), }),
).then((results) => (mergeFn ? mergeFn(results) : results)); ).then((results) => (options.mergeFn ? options.mergeFn(results) : results));
}; };
}; };
} }
export function ChunkedArray(options?: { paramIndex?: number; chunkSize?: number }): MethodDecorator { export function ChunkedArray(options?: { paramIndex?: number }): MethodDecorator {
return Chunked({ ...options, mergeFn: _.flatten }); return Chunked({ ...options, mergeFn: _.flatten });
} }
export function ChunkedSet(options?: { paramIndex?: number; chunkSize?: number }): MethodDecorator { export function ChunkedSet(options?: { paramIndex?: number }): MethodDecorator {
return Chunked({ ...options, mergeFn: (args: Set<any>[]) => setUnion(...args) }); return Chunked({ ...options, mergeFn: (args: Set<any>[]) => setUnion(...args) });
} }
+2 -4
View File
@@ -380,10 +380,8 @@ export class AssetRepository {
return this.db.insertInto('asset').values(asset).returningAll().executeTakeFirstOrThrow(); return this.db.insertInto('asset').values(asset).returningAll().executeTakeFirstOrThrow();
} }
@ChunkedArray({ chunkSize: 4000 }) createAll(assets: Insertable<AssetTable>[]) {
async createAll(assets: Insertable<AssetTable>[]) { return this.db.insertInto('asset').values(assets).returningAll().execute();
const ids = await this.db.insertInto('asset').values(assets).returning('id').execute();
return ids.map(({ id }) => id);
} }
@GenerateSql({ params: [DummyValue.UUID, { year: 2000, day: 1, month: 1 }] }) @GenerateSql({ params: [DummyValue.UUID, { year: 2000, day: 1, month: 1 }] })
+1 -1
View File
@@ -560,7 +560,7 @@ describe(LibraryService.name, () => {
paths: ['/data/user1/photo.jpg'], paths: ['/data/user1/photo.jpg'],
}; };
mocks.asset.createAll.mockResolvedValue([asset.id]); mocks.asset.createAll.mockResolvedValue([asset]);
mocks.library.get.mockResolvedValue(library); mocks.library.get.mockResolvedValue(library);
await expect(sut.handleSyncFiles(mockLibraryJob)).resolves.toBe(JobStatus.Success); await expect(sut.handleSyncFiles(mockLibraryJob)).resolves.toBe(JobStatus.Success);
+7 -1
View File
@@ -266,7 +266,13 @@ export class LibraryService extends BaseService {
), ),
); );
const assetIds = await this.assetRepository.createAll(assetImports); const assetIds: string[] = [];
for (let i = 0; i < assetImports.length; i += 5000) {
// Chunk the imports to avoid the postgres limit of max parameters at once
const chunk = assetImports.slice(i, i + 5000);
await this.assetRepository.createAll(chunk).then((assets) => assetIds.push(...assets.map((asset) => asset.id)));
}
const progressMessage = const progressMessage =
job.progressCounter && job.totalAssets job.progressCounter && job.totalAssets
-3
View File
@@ -1,7 +1,4 @@
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
},
"exclude": ["dist", "node_modules", "upload", "test", "e2e", "**/*spec.ts"] "exclude": ["dist", "node_modules", "upload", "test", "e2e", "**/*spec.ts"]
} }
File diff suppressed because one or more lines are too long
+1 -2
View File
@@ -19,9 +19,8 @@
"preserveWatchOutput": true, "preserveWatchOutput": true,
"paths": { "paths": {
"src/*": ["./src/*"], "src/*": ["./src/*"],
"test/*": ["./test/*"]
}, },
"rootDir": ".", "baseUrl": "./",
"jsx": "react", "jsx": "react",
"types": ["vitest/globals"], "types": ["vitest/globals"],
"noErrorTruncation": true "noErrorTruncation": true
-1
View File
@@ -73,7 +73,6 @@ export default typescriptEslint.config(
'eslint.config.js', 'eslint.config.js',
'tailwind.config.js', 'tailwind.config.js',
'coverage', 'coverage',
'vite.config.ts',
], ],
}, },
typescriptEslint.configs.recommended, typescriptEslint.configs.recommended,
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich-web", "name": "immich-web",
"version": "2.7.1", "version": "2.6.3",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -89,7 +89,7 @@
"dotenv": "^17.0.0", "dotenv": "^17.0.0",
"eslint": "^10.0.0", "eslint": "^10.0.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-compat": "^7.0.0", "eslint-plugin-compat": "^6.0.2",
"eslint-plugin-svelte": "^3.12.4", "eslint-plugin-svelte": "^3.12.4",
"eslint-plugin-unicorn": "^63.0.0", "eslint-plugin-unicorn": "^63.0.0",
"factory.ts": "^1.4.1", "factory.ts": "^1.4.1",
@@ -99,12 +99,12 @@
"prettier-plugin-organize-imports": "^4.0.0", "prettier-plugin-organize-imports": "^4.0.0",
"prettier-plugin-sort-json": "^4.1.1", "prettier-plugin-sort-json": "^4.1.1",
"prettier-plugin-svelte": "^3.3.3", "prettier-plugin-svelte": "^3.3.3",
"rollup-plugin-visualizer": "^7.0.0", "rollup-plugin-visualizer": "^6.0.0",
"svelte": "5.54.1", "svelte": "5.54.1",
"svelte-check": "^4.1.5", "svelte-check": "^4.1.5",
"svelte-eslint-parser": "^1.3.3", "svelte-eslint-parser": "^1.3.3",
"tailwindcss": "^4.2.2", "tailwindcss": "^4.2.2",
"typescript": "^6.0.0", "typescript": "^5.8.3",
"typescript-eslint": "^8.45.0", "typescript-eslint": "^8.45.0",
"vite": "^8.0.0", "vite": "^8.0.0",
"vitest": "^4.0.0" "vitest": "^4.0.0"
@@ -203,7 +203,7 @@ class TransformManager implements EditToolManager {
passive: true, passive: true,
}); });
globalThis.addEventListener('mousemove', (e: MouseEvent) => transformManager.handleMouseMove(e), { passive: true }); globalThis.addEventListener('mousemove', (e) => transformManager.handleMouseMove(e), { passive: true });
const transformEdits = edits.filter((e) => e.action === 'rotate' || e.action === 'mirror'); const transformEdits = edits.filter((e) => e.action === 'rotate' || e.action === 'mirror');
@@ -145,7 +145,7 @@ describe('TimelineManager', () => {
it('cancels month loading', async () => { it('cancels month loading', async () => {
const month = getTimelineMonthByDate(timelineManager, { year: 2024, month: 1 })!; const month = getTimelineMonthByDate(timelineManager, { year: 2024, month: 1 })!;
void timelineManager.loadTimelineMonth({ year: 2024, month: 1 }); void timelineManager.loadTimelineMonth({ year: 2024, month: 1 });
const abortSpy = vi.spyOn(month!.loader!.cancelToken!, 'abort'); const abortSpy = vi.spyOn(month!.loader!.abortController!, 'abort');
month?.cancel(); month?.cancel();
expect(abortSpy).toBeCalledTimes(1); expect(abortSpy).toBeCalledTimes(1);
await timelineManager.loadTimelineMonth({ year: 2024, month: 1 }); await timelineManager.loadTimelineMonth({ year: 2024, month: 1 });
@@ -638,12 +638,8 @@ describe('TimelineManager', () => {
const previousMonth = getTimelineMonthByDate(timelineManager, { year: 2024, month: 3 }); const previousMonth = getTimelineMonthByDate(timelineManager, { year: 2024, month: 3 });
const a = month!.getFirstAsset(); const a = month!.getFirstAsset();
const b = previousMonth!.getFirstAsset(); const b = previousMonth!.getFirstAsset();
const loadTimelineMonthSpy = vi.spyOn(month!.loader!, 'execute');
const previousMonthSpy = vi.spyOn(previousMonth!.loader!, 'execute');
const previous = await timelineManager.getLaterAsset(a); const previous = await timelineManager.getLaterAsset(a);
expect(previous).toEqual(b); expect(previous).toEqual(b);
expect(loadTimelineMonthSpy).toBeCalledTimes(0);
expect(previousMonthSpy).toBeCalledTimes(0);
}); });
it('skips removed assets', async () => { it('skips removed assets', async () => {
@@ -307,8 +307,8 @@ export class TimelineManager extends VirtualScrollManager {
return; return;
} }
if (!this.initTask.executed) { if (!this.initTask.succeeded) {
await (this.initTask.loading ? this.initTask.waitUntilCompletion() : this.#init(this.#options)); await (this.initTask.running ? this.initTask.waitUntilCompletion() : this.#init(this.#options));
} }
const changedWidth = viewport.width !== this.viewportWidth; const changedWidth = viewport.width !== this.viewportWidth;
@@ -351,14 +351,10 @@ export class TimelineManager extends VirtualScrollManager {
return; return;
} }
if (timelineMonth.loader?.executed) {
return;
}
const executionStatus = await timelineMonth.loader?.execute(async (signal: AbortSignal) => { const executionStatus = await timelineMonth.loader?.execute(async (signal: AbortSignal) => {
await loadFromTimeBuckets(this, timelineMonth, this.#options, signal); await loadFromTimeBuckets(this, timelineMonth, this.#options, signal);
}, cancelable); }, cancelable);
if (executionStatus === 'LOADED') { if (executionStatus === 'SUCCESS') {
updateGeometry(this, timelineMonth, { invalidateHeight: false }); updateGeometry(this, timelineMonth, { invalidateHeight: false });
this.updateViewportProximities(); this.updateViewportProximities();
} }
@@ -372,7 +368,7 @@ export class TimelineManager extends VirtualScrollManager {
async findTimelineMonthForAsset(asset: AssetDescriptor | AssetResponseDto) { async findTimelineMonthForAsset(asset: AssetDescriptor | AssetResponseDto) {
if (!this.isInitialized) { if (!this.isInitialized) {
await this.initTask.waitUntilExecution(); await this.initTask.waitUntilSucceeded();
} }
const { id } = asset; const { id } = asset;
+117 -127
View File
@@ -2,39 +2,39 @@ import { CancellableTask } from '$lib/utils/cancellable-task';
describe('CancellableTask', () => { describe('CancellableTask', () => {
describe('execute', () => { describe('execute', () => {
it('should execute task successfully and return LOADED', async () => { it('should execute task successfully and return SUCCESS', async () => {
const task = new CancellableTask(); const task = new CancellableTask();
const taskFn = vi.fn(async (_: AbortSignal) => { const taskFunction = vi.fn(async (_: AbortSignal) => {
await new Promise((resolve) => setTimeout(resolve, 10)); await new Promise((resolve) => setTimeout(resolve, 10));
}); });
const result = await task.execute(taskFn, true); const result = await task.execute(taskFunction, true);
expect(result).toBe('LOADED'); expect(result).toBe('SUCCESS');
expect(task.executed).toBe(true); expect(task.succeeded).toBe(true);
expect(task.loading).toBe(false); expect(task.running).toBe(false);
expect(taskFn).toHaveBeenCalledTimes(1); expect(taskFunction).toHaveBeenCalledTimes(1);
}); });
it('should call loadedCallback when task completes successfully', async () => { it('should call succeededCallback when task completes successfully', async () => {
const loadedCallback = vi.fn(); const succeededCallback = vi.fn();
const task = new CancellableTask(loadedCallback); const task = new CancellableTask(succeededCallback);
const taskFn = vi.fn(async () => {}); const taskFunction = vi.fn(async () => {});
await task.execute(taskFn, true); await task.execute(taskFunction, true);
expect(loadedCallback).toHaveBeenCalledTimes(1); expect(succeededCallback).toHaveBeenCalledTimes(1);
}); });
it('should return DONE if task is already executed', async () => { it('should return DONE if task is already executed', async () => {
const task = new CancellableTask(); const task = new CancellableTask();
const taskFn = vi.fn(async () => {}); const taskFunction = vi.fn(async () => {});
await task.execute(taskFn, true); await task.execute(taskFunction, true);
const result = await task.execute(taskFn, true); const result = await task.execute(taskFunction, true);
expect(result).toBe('DONE'); expect(result).toBe('DONE');
expect(taskFn).toHaveBeenCalledTimes(1); expect(taskFunction).toHaveBeenCalledTimes(1);
}); });
it('should wait if task is already running', async () => { it('should wait if task is already running', async () => {
@@ -43,42 +43,42 @@ describe('CancellableTask', () => {
const taskPromise = new Promise<void>((resolve) => { const taskPromise = new Promise<void>((resolve) => {
resolveTask = resolve; resolveTask = resolve;
}); });
const taskFn = vi.fn(async () => { const taskFunction = vi.fn(async () => {
await taskPromise; await taskPromise;
}); });
const promise1 = task.execute(taskFn, true); const promise1 = task.execute(taskFunction, true);
const promise2 = task.execute(taskFn, true); const promise2 = task.execute(taskFunction, true);
expect(task.loading).toBe(true); expect(task.running).toBe(true);
resolveTask!(); resolveTask!();
const [result1, result2] = await Promise.all([promise1, promise2]); const [result1, result2] = await Promise.all([promise1, promise2]);
expect(result1).toBe('LOADED'); expect(result1).toBe('SUCCESS');
expect(result2).toBe('WAITED'); expect(result2).toBe('WAITED');
expect(taskFn).toHaveBeenCalledTimes(1); expect(taskFunction).toHaveBeenCalledTimes(1);
}); });
it('should pass AbortSignal to task function', async () => { it('should pass AbortSignal to task function', async () => {
const task = new CancellableTask(); const task = new CancellableTask();
let capturedSignal: AbortSignal | null = null; let capturedSignal: AbortSignal | null = null;
const taskFn = async (signal: AbortSignal) => { const taskFunction = async (signal: AbortSignal) => {
await Promise.resolve(); await Promise.resolve();
capturedSignal = signal; capturedSignal = signal;
}; };
await task.execute(taskFn, true); await task.execute(taskFunction, true);
expect(capturedSignal).toBeInstanceOf(AbortSignal); expect(capturedSignal).toBeInstanceOf(AbortSignal);
}); });
it('should set cancellable flag correctly', async () => { it('should set cancellable flag correctly', async () => {
const task = new CancellableTask(); const task = new CancellableTask();
const taskFn = vi.fn(async () => {}); const taskFunction = vi.fn(async () => {});
expect(task.cancellable).toBe(true); expect(task.cancellable).toBe(true);
const promise = task.execute(taskFn, false); const promise = task.execute(taskFunction, false);
expect(task.cancellable).toBe(false); expect(task.cancellable).toBe(false);
await promise; await promise;
}); });
@@ -89,14 +89,14 @@ describe('CancellableTask', () => {
const taskPromise = new Promise<void>((resolve) => { const taskPromise = new Promise<void>((resolve) => {
resolveTask = resolve; resolveTask = resolve;
}); });
const taskFn = vi.fn(async () => { const taskFunction = vi.fn(async () => {
await taskPromise; await taskPromise;
}); });
const promise1 = task.execute(taskFn, false); const promise1 = task.execute(taskFunction, false);
expect(task.cancellable).toBe(false); expect(task.cancellable).toBe(false);
const promise2 = task.execute(taskFn, true); const promise2 = task.execute(taskFunction, true);
expect(task.cancellable).toBe(false); expect(task.cancellable).toBe(false);
resolveTask!(); resolveTask!();
@@ -108,7 +108,7 @@ describe('CancellableTask', () => {
it('should cancel a running task', async () => { it('should cancel a running task', async () => {
const task = new CancellableTask(); const task = new CancellableTask();
let taskStarted = false; let taskStarted = false;
const taskFn = async (signal: AbortSignal) => { const taskFunction = async (signal: AbortSignal) => {
taskStarted = true; taskStarted = true;
await new Promise((resolve) => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100));
if (signal.aborted) { if (signal.aborted) {
@@ -116,9 +116,7 @@ describe('CancellableTask', () => {
} }
}; };
const promise = task.execute(taskFn, true); const promise = task.execute(taskFunction, true);
// Wait a bit to ensure task has started
await new Promise((resolve) => setTimeout(resolve, 10)); await new Promise((resolve) => setTimeout(resolve, 10));
expect(taskStarted).toBe(true); expect(taskStarted).toBe(true);
@@ -126,20 +124,20 @@ describe('CancellableTask', () => {
const result = await promise; const result = await promise;
expect(result).toBe('CANCELED'); expect(result).toBe('CANCELED');
expect(task.executed).toBe(false); expect(task.succeeded).toBe(false);
}); });
it('should call canceledCallback when task is canceled', async () => { it('should call canceledCallback when task is canceled', async () => {
const canceledCallback = vi.fn(); const canceledCallback = vi.fn();
const task = new CancellableTask(undefined, canceledCallback); const task = new CancellableTask(undefined, canceledCallback);
const taskFn = async (signal: AbortSignal) => { const taskFunction = async (signal: AbortSignal) => {
await new Promise((resolve) => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100));
if (signal.aborted) { if (signal.aborted) {
throw new DOMException('Aborted', 'AbortError'); throw new DOMException('Aborted', 'AbortError');
} }
}; };
const promise = task.execute(taskFn, true); const promise = task.execute(taskFunction, true);
await new Promise((resolve) => setTimeout(resolve, 10)); await new Promise((resolve) => setTimeout(resolve, 10));
task.cancel(); task.cancel();
await promise; await promise;
@@ -149,16 +147,16 @@ describe('CancellableTask', () => {
it('should not cancel if task is not cancellable', async () => { it('should not cancel if task is not cancellable', async () => {
const task = new CancellableTask(); const task = new CancellableTask();
const taskFn = vi.fn(async () => { const taskFunction = vi.fn(async () => {
await new Promise((resolve) => setTimeout(resolve, 50)); await new Promise((resolve) => setTimeout(resolve, 50));
}); });
const promise = task.execute(taskFn, false); const promise = task.execute(taskFunction, false);
task.cancel(); task.cancel();
const result = await promise; const result = await promise;
expect(result).toBe('LOADED'); expect(result).toBe('SUCCESS');
expect(task.executed).toBe(true); expect(task.succeeded).toBe(true);
}); });
it('should return CANCELED when concurrent caller is waiting and task is canceled', async () => { it('should return CANCELED when concurrent caller is waiting and task is canceled', async () => {
@@ -167,15 +165,15 @@ describe('CancellableTask', () => {
const taskPromise = new Promise<void>((resolve) => { const taskPromise = new Promise<void>((resolve) => {
resolveTask = resolve; resolveTask = resolve;
}); });
const taskFn = async (signal: AbortSignal) => { const taskFunction = async (signal: AbortSignal) => {
await taskPromise; await taskPromise;
if (signal.aborted) { if (signal.aborted) {
throw new DOMException('Aborted', 'AbortError'); throw new DOMException('Aborted', 'AbortError');
} }
}; };
const promise1 = task.execute(taskFn, true); const promise1 = task.execute(taskFunction, true);
const promise2 = task.execute(taskFn, true); const promise2 = task.execute(taskFunction, true);
task.cancel(); task.cancel();
resolveTask!(); resolveTask!();
@@ -187,41 +185,41 @@ describe('CancellableTask', () => {
it('should not cancel if task is already executed', async () => { it('should not cancel if task is already executed', async () => {
const task = new CancellableTask(); const task = new CancellableTask();
const taskFn = vi.fn(async () => {}); const taskFunction = vi.fn(async () => {});
await task.execute(taskFn, true); await task.execute(taskFunction, true);
expect(task.executed).toBe(true); expect(task.succeeded).toBe(true);
task.cancel(); task.cancel();
expect(task.executed).toBe(true); expect(task.succeeded).toBe(true);
}); });
}); });
describe('reset', () => { describe('reset', () => {
it('should reset task to initial state', async () => { it('should reset task to initial state', async () => {
const task = new CancellableTask(); const task = new CancellableTask();
const taskFn = vi.fn(async () => {}); const taskFunction = vi.fn(async () => {});
await task.execute(taskFn, true); await task.execute(taskFunction, true);
expect(task.executed).toBe(true); expect(task.succeeded).toBe(true);
await task.reset(); await task.reset();
expect(task.executed).toBe(false); expect(task.succeeded).toBe(false);
expect(task.cancelToken).toBe(null); expect(task.abortController).toBe(null);
expect(task.loading).toBe(false); expect(task.running).toBe(false);
}); });
it('should cancel running task before resetting', async () => { it('should cancel running task before resetting', async () => {
const task = new CancellableTask(); const task = new CancellableTask();
const taskFn = async (signal: AbortSignal) => { const taskFunction = async (signal: AbortSignal) => {
await new Promise((resolve) => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100));
if (signal.aborted) { if (signal.aborted) {
throw new DOMException('Aborted', 'AbortError'); throw new DOMException('Aborted', 'AbortError');
} }
}; };
const promise = task.execute(taskFn, true); const promise = task.execute(taskFunction, true);
await new Promise((resolve) => setTimeout(resolve, 10)); await new Promise((resolve) => setTimeout(resolve, 10));
const resetPromise = task.reset(); const resetPromise = task.reset();
@@ -229,30 +227,30 @@ describe('CancellableTask', () => {
await promise; await promise;
await resetPromise; await resetPromise;
expect(task.executed).toBe(false); expect(task.succeeded).toBe(false);
expect(task.loading).toBe(false); expect(task.running).toBe(false);
}); });
it('should allow re-execution after reset', async () => { it('should allow re-execution after reset', async () => {
const task = new CancellableTask(); const task = new CancellableTask();
const taskFn = vi.fn(async () => {}); const taskFunction = vi.fn(async () => {});
await task.execute(taskFn, true); await task.execute(taskFunction, true);
await task.reset(); await task.reset();
const result = await task.execute(taskFn, true); const result = await task.execute(taskFunction, true);
expect(result).toBe('LOADED'); expect(result).toBe('SUCCESS');
expect(task.executed).toBe(true); expect(task.succeeded).toBe(true);
expect(taskFn).toHaveBeenCalledTimes(2); expect(taskFunction).toHaveBeenCalledTimes(2);
}); });
}); });
describe('waitUntilCompletion', () => { describe('waitUntilCompletion', () => {
it('should return DONE if task is already executed', async () => { it('should return DONE if task is already executed', async () => {
const task = new CancellableTask(); const task = new CancellableTask();
const taskFn = vi.fn(async () => {}); const taskFunction = vi.fn(async () => {});
await task.execute(taskFn, true); await task.execute(taskFunction, true);
const result = await task.waitUntilCompletion(); const result = await task.waitUntilCompletion();
expect(result).toBe('DONE'); expect(result).toBe('DONE');
@@ -264,11 +262,11 @@ describe('CancellableTask', () => {
const taskPromise = new Promise<void>((resolve) => { const taskPromise = new Promise<void>((resolve) => {
resolveTask = resolve; resolveTask = resolve;
}); });
const taskFn = async () => { const taskFunction = async () => {
await taskPromise; await taskPromise;
}; };
const executePromise = task.execute(taskFn, true); const executePromise = task.execute(taskFunction, true);
const waitPromise = task.waitUntilCompletion(); const waitPromise = task.waitUntilCompletion();
resolveTask!(); resolveTask!();
@@ -280,14 +278,14 @@ describe('CancellableTask', () => {
it('should return CANCELED if task is canceled', async () => { it('should return CANCELED if task is canceled', async () => {
const task = new CancellableTask(); const task = new CancellableTask();
const taskFn = async (signal: AbortSignal) => { const taskFunction = async (signal: AbortSignal) => {
await new Promise((resolve) => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100));
if (signal.aborted) { if (signal.aborted) {
throw new DOMException('Aborted', 'AbortError'); throw new DOMException('Aborted', 'AbortError');
} }
}; };
const executePromise = task.execute(taskFn, true); const executePromise = task.execute(taskFunction, true);
const waitPromise = task.waitUntilCompletion(); const waitPromise = task.waitUntilCompletion();
await new Promise((resolve) => setTimeout(resolve, 10)); await new Promise((resolve) => setTimeout(resolve, 10));
@@ -299,13 +297,13 @@ describe('CancellableTask', () => {
}); });
}); });
describe('waitUntilExecution', () => { describe('waitUntilSucceeded', () => {
it('should return DONE if task is already executed', async () => { it('should return DONE if task is already executed', async () => {
const task = new CancellableTask(); const task = new CancellableTask();
const taskFn = vi.fn(async () => {}); const taskFunction = vi.fn(async () => {});
await task.execute(taskFn, true); await task.execute(taskFunction, true);
const result = await task.waitUntilExecution(); const result = await task.waitUntilSucceeded();
expect(result).toBe('DONE'); expect(result).toBe('DONE');
}); });
@@ -316,12 +314,12 @@ describe('CancellableTask', () => {
const taskPromise = new Promise<void>((resolve) => { const taskPromise = new Promise<void>((resolve) => {
resolveTask = resolve; resolveTask = resolve;
}); });
const taskFn = async () => { const taskFunction = async () => {
await taskPromise; await taskPromise;
}; };
const executePromise = task.execute(taskFn, true); const executePromise = task.execute(taskFunction, true);
const waitPromise = task.waitUntilExecution(); const waitPromise = task.waitUntilSucceeded();
resolveTask!(); resolveTask!();
@@ -335,7 +333,7 @@ describe('CancellableTask', () => {
const task = new CancellableTask(); const task = new CancellableTask();
let attempt = 0; let attempt = 0;
const taskFn = async (signal: AbortSignal) => { const taskFunction = async (signal: AbortSignal) => {
attempt++; attempt++;
await new Promise((resolve) => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100));
if (signal.aborted && attempt === 1) { if (signal.aborted && attempt === 1) {
@@ -344,8 +342,8 @@ describe('CancellableTask', () => {
}; };
// Start first execution // Start first execution
const executePromise1 = task.execute(taskFn, true); const executePromise1 = task.execute(taskFunction, true);
const waitPromise = task.waitUntilExecution(); const waitPromise = task.waitUntilSucceeded();
// Cancel the first execution // Cancel the first execution
vi.advanceTimersByTime(10); vi.advanceTimersByTime(10);
@@ -354,12 +352,12 @@ describe('CancellableTask', () => {
await executePromise1; await executePromise1;
// Start second execution // Start second execution
const executePromise2 = task.execute(taskFn, true); const executePromise2 = task.execute(taskFunction, true);
vi.advanceTimersByTime(100); vi.advanceTimersByTime(100);
const [executeResult, waitResult] = await Promise.all([executePromise2, waitPromise]); const [executeResult, waitResult] = await Promise.all([executePromise2, waitPromise]);
expect(executeResult).toBe('LOADED'); expect(executeResult).toBe('SUCCESS');
expect(waitResult).toBe('WAITED'); expect(waitResult).toBe('WAITED');
expect(attempt).toBe(2); expect(attempt).toBe(2);
@@ -371,98 +369,98 @@ describe('CancellableTask', () => {
it('should return ERRORED when task throws non-abort error', async () => { it('should return ERRORED when task throws non-abort error', async () => {
const task = new CancellableTask(); const task = new CancellableTask();
const error = new Error('Task failed'); const error = new Error('Task failed');
const taskFn = async () => { const taskFunction = async () => {
await Promise.resolve(); await Promise.resolve();
throw error; throw error;
}; };
const result = await task.execute(taskFn, true); const result = await task.execute(taskFunction, true);
expect(result).toBe('ERRORED'); expect(result).toBe('ERRORED');
expect(task.executed).toBe(false); expect(task.succeeded).toBe(false);
}); });
it('should call errorCallback when task throws non-abort error', async () => { it('should call errorCallback when task throws non-abort error', async () => {
const errorCallback = vi.fn(); const errorCallback = vi.fn();
const task = new CancellableTask(undefined, undefined, errorCallback); const task = new CancellableTask(undefined, undefined, errorCallback);
const error = new Error('Task failed'); const error = new Error('Task failed');
const taskFn = async () => { const taskFunction = async () => {
await Promise.resolve(); await Promise.resolve();
throw error; throw error;
}; };
await task.execute(taskFn, true); await task.execute(taskFunction, true);
expect(errorCallback).toHaveBeenCalledTimes(1); expect(errorCallback).toHaveBeenCalledTimes(1);
expect(errorCallback).toHaveBeenCalledWith(error); expect(errorCallback).toHaveBeenCalledWith(error);
}); });
it('should return CANCELED when task throws AbortError', async () => { it('should return ERRORED when task throws AbortError without signal being aborted', async () => {
const task = new CancellableTask(); const task = new CancellableTask();
const taskFn = async () => { const taskFunction = async () => {
await Promise.resolve(); await Promise.resolve();
throw new DOMException('Aborted', 'AbortError'); throw new DOMException('Aborted', 'AbortError');
}; };
const result = await task.execute(taskFn, true); const result = await task.execute(taskFunction, true);
expect(result).toBe('CANCELED'); expect(result).toBe('ERRORED');
expect(task.executed).toBe(false); expect(task.succeeded).toBe(false);
}); });
it('should allow re-execution after error', async () => { it('should allow re-execution after error', async () => {
const task = new CancellableTask(); const task = new CancellableTask();
const taskFn1 = async () => { const taskFunction1 = async () => {
await Promise.resolve(); await Promise.resolve();
throw new Error('Failed'); throw new Error('Failed');
}; };
const taskFn2 = vi.fn(async () => {}); const taskFunction2 = vi.fn(async () => {});
const result1 = await task.execute(taskFn1, true); const result1 = await task.execute(taskFunction1, true);
expect(result1).toBe('ERRORED'); expect(result1).toBe('ERRORED');
const result2 = await task.execute(taskFn2, true); const result2 = await task.execute(taskFunction2, true);
expect(result2).toBe('LOADED'); expect(result2).toBe('SUCCESS');
expect(task.executed).toBe(true); expect(task.succeeded).toBe(true);
}); });
}); });
describe('loading property', () => { describe('running property', () => {
it('should return true when task is running', async () => { it('should return true when task is running', async () => {
const task = new CancellableTask(); const task = new CancellableTask();
let resolveTask: () => void; let resolveTask: () => void;
const taskPromise = new Promise<void>((resolve) => { const taskPromise = new Promise<void>((resolve) => {
resolveTask = resolve; resolveTask = resolve;
}); });
const taskFn = async () => { const taskFunction = async () => {
await taskPromise; await taskPromise;
}; };
expect(task.loading).toBe(false); expect(task.running).toBe(false);
const promise = task.execute(taskFn, true); const promise = task.execute(taskFunction, true);
expect(task.loading).toBe(true); expect(task.running).toBe(true);
resolveTask!(); resolveTask!();
await promise; await promise;
expect(task.loading).toBe(false); expect(task.running).toBe(false);
}); });
}); });
describe('complete promise', () => { describe('complete promise', () => {
it('should resolve when task completes successfully', async () => { it('should resolve when task completes successfully', async () => {
const task = new CancellableTask(); const task = new CancellableTask();
const taskFn = vi.fn(async () => {}); const taskFunction = vi.fn(async () => {});
const completePromise = task.complete; const completePromise = task.complete;
await task.execute(taskFn, true); await task.execute(taskFunction, true);
await expect(completePromise).resolves.toBeUndefined(); await expect(completePromise).resolves.toBeUndefined();
}); });
it('should reject when task is canceled', async () => { it('should reject when task is canceled', async () => {
const task = new CancellableTask(); const task = new CancellableTask();
const taskFn = async (signal: AbortSignal) => { const taskFunction = async (signal: AbortSignal) => {
await new Promise((resolve) => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100));
if (signal.aborted) { if (signal.aborted) {
throw new DOMException('Aborted', 'AbortError'); throw new DOMException('Aborted', 'AbortError');
@@ -470,7 +468,7 @@ describe('CancellableTask', () => {
}; };
const completePromise = task.complete; const completePromise = task.complete;
const promise = task.execute(taskFn, true); const promise = task.execute(taskFunction, true);
await new Promise((resolve) => setTimeout(resolve, 10)); await new Promise((resolve) => setTimeout(resolve, 10));
task.cancel(); task.cancel();
await promise; await promise;
@@ -480,13 +478,13 @@ describe('CancellableTask', () => {
it('should reject when task errors', async () => { it('should reject when task errors', async () => {
const task = new CancellableTask(); const task = new CancellableTask();
const taskFn = async () => { const taskFunction = async () => {
await Promise.resolve(); await Promise.resolve();
throw new Error('Failed'); throw new Error('Failed');
}; };
const completePromise = task.complete; const completePromise = task.complete;
await task.execute(taskFn, true); await task.execute(taskFunction, true);
await expect(completePromise).rejects.toBeUndefined(); await expect(completePromise).rejects.toBeUndefined();
}); });
@@ -496,27 +494,22 @@ describe('CancellableTask', () => {
it('should automatically call abort() on signal when task is canceled', async () => { it('should automatically call abort() on signal when task is canceled', async () => {
const task = new CancellableTask(); const task = new CancellableTask();
let capturedSignal: AbortSignal | null = null; let capturedSignal: AbortSignal | null = null;
const taskFn = async (signal: AbortSignal) => { const taskFunction = async (signal: AbortSignal) => {
capturedSignal = signal; capturedSignal = signal;
// Simulate a long-running task
await new Promise((resolve) => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100));
if (signal.aborted) { if (signal.aborted) {
throw new DOMException('Aborted', 'AbortError'); throw new DOMException('Aborted', 'AbortError');
} }
}; };
const promise = task.execute(taskFn, true); const promise = task.execute(taskFunction, true);
// Wait a bit to ensure task has started
await new Promise((resolve) => setTimeout(resolve, 10)); await new Promise((resolve) => setTimeout(resolve, 10));
expect(capturedSignal).not.toBeNull(); expect(capturedSignal).not.toBeNull();
expect(capturedSignal!.aborted).toBe(false); expect(capturedSignal!.aborted).toBe(false);
// Cancel the task
task.cancel(); task.cancel();
// Verify the signal was aborted
expect(capturedSignal!.aborted).toBe(true); expect(capturedSignal!.aborted).toBe(true);
const result = await promise; const result = await promise;
@@ -526,25 +519,22 @@ describe('CancellableTask', () => {
it('should detect if signal was aborted after task completes', async () => { it('should detect if signal was aborted after task completes', async () => {
const task = new CancellableTask(); const task = new CancellableTask();
let controller: AbortController | null = null; let controller: AbortController | null = null;
const taskFn = async (_: AbortSignal) => { const taskFunction = async (_: AbortSignal) => {
// Capture the controller to abort it externally // Capture the controller to abort it externally before the function returns
controller = task.cancelToken; controller = task.abortController;
// Simulate some work
await new Promise((resolve) => setTimeout(resolve, 10)); await new Promise((resolve) => setTimeout(resolve, 10));
// Now abort before the function returns
controller?.abort(); controller?.abort();
}; };
const result = await task.execute(taskFn, true); const result = await task.execute(taskFunction, true);
expect(result).toBe('CANCELED'); expect(result).toBe('CANCELED');
expect(task.executed).toBe(false); expect(task.succeeded).toBe(false);
}); });
it('should handle abort signal in async operations', async () => { it('should handle abort signal in async operations', async () => {
const task = new CancellableTask(); const task = new CancellableTask();
const taskFn = async (signal: AbortSignal) => { const taskFunction = async (signal: AbortSignal) => {
// Simulate listening to abort signal during async operation
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
signal.addEventListener('abort', () => { signal.addEventListener('abort', () => {
reject(new DOMException('Aborted', 'AbortError')); reject(new DOMException('Aborted', 'AbortError'));
@@ -553,7 +543,7 @@ describe('CancellableTask', () => {
}); });
}; };
const promise = task.execute(taskFn, true); const promise = task.execute(taskFunction, true);
await new Promise((resolve) => setTimeout(resolve, 10)); await new Promise((resolve) => setTimeout(resolve, 10));
task.cancel(); task.cancel();
+54 -47
View File
@@ -1,47 +1,60 @@
/**
* A one-shot async task with cancellation support via AbortController/AbortSignal.
*
* State machine:
*
* IDLE execute() RUNNING task succeeds SUCCEEDED (terminal)
*
* cancel()/abort CANCELED IDLE
* task throws ERRORED IDLE
*
* SUCCEEDED is terminal further execute() calls return 'DONE'.
* Call reset() to move from SUCCEEDED back to IDLE for re-execution.
*
* execute() return values: 'SUCCESS' | 'DONE' | 'WAITED' | 'CANCELED' | 'ERRORED'
*/
export class CancellableTask { export class CancellableTask {
cancelToken: AbortController | null = null; abortController: AbortController | null = null;
cancellable: boolean = true; cancellable: boolean = true;
/** /**
* A promise that resolves once the bucket is loaded, and rejects if bucket is canceled. * A promise that resolves once the task completes, and rejects if the task is canceled or errored.
*/ */
complete!: Promise<unknown>; complete!: Promise<unknown>;
executed: boolean = false; succeeded: boolean = false;
private loadedSignal: (() => void) | undefined; private completeResolve: (() => void) | undefined;
private canceledSignal: (() => void) | undefined; private completeReject: (() => void) | undefined;
constructor( constructor(
private loadedCallback?: () => void, private succeededCallback?: () => void,
private canceledCallback?: () => void, private canceledCallback?: () => void,
private errorCallback?: (error: unknown) => void, private errorCallback?: (error: unknown) => void,
) { ) {
this.init(); this.init();
} }
get loading() { get running() {
return !!this.cancelToken; return !!this.abortController;
} }
async waitUntilCompletion() { async waitUntilCompletion() {
if (this.executed) { if (this.succeeded) {
return 'DONE'; return 'DONE';
} }
// The `complete` promise resolves when executed, rejects when canceled/errored.
try { try {
const complete = this.complete; await this.complete;
await complete;
return 'WAITED'; return 'WAITED';
} catch { } catch {
// ignore // expected when canceled
} }
return 'CANCELED'; return 'CANCELED';
} }
async waitUntilExecution() { async waitUntilSucceeded() {
// Keep retrying until the task completes successfully (not canceled) // Keep retrying until the task completes successfully (not canceled)
for (;;) { for (;;) {
try { try {
if (this.executed) { if (this.succeeded) {
return 'DONE'; return 'DONE';
} }
await this.complete; await this.complete;
@@ -52,17 +65,15 @@ export class CancellableTask {
} }
} }
async execute<F extends (abortSignal: AbortSignal) => Promise<void>>(f: F, cancellable: boolean) { async execute(task: (abortSignal: AbortSignal) => Promise<void>, cancellable: boolean) {
if (this.executed) { if (this.succeeded) {
return 'DONE'; return 'DONE';
} }
// if promise is pending, wait on previous request instead. // if promise is pending, wait on previous request instead.
if (this.cancelToken) { if (this.abortController) {
// if promise is pending, and preventCancel is requested, if (!cancellable) {
// do not allow transition from prevent cancel to allow cancel. this.cancellable = false;
if (this.cancellable && !cancellable) {
this.cancellable = cancellable;
} }
try { try {
await this.complete; await this.complete;
@@ -72,45 +83,42 @@ export class CancellableTask {
} }
} }
this.cancellable = cancellable; this.cancellable = cancellable;
const cancelToken = (this.cancelToken = new AbortController()); const abortController = (this.abortController = new AbortController());
try { try {
await f(cancelToken.signal); await task(abortController.signal);
if (cancelToken.signal.aborted) { if (abortController.signal.aborted) {
return 'CANCELED'; return 'CANCELED';
} }
this.#transitionToExecuted(); this.#transitionToSucceeded();
return 'LOADED'; return 'SUCCESS';
} catch (error) { } catch (error) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any if (abortController.signal.aborted) {
if ((error as any).name === 'AbortError') {
// abort error is not treated as an error, but as a cancellation.
return 'CANCELED'; return 'CANCELED';
} }
this.#transitionToErrored(error); this.#transitionToErrored(error);
return 'ERRORED'; return 'ERRORED';
} finally { } finally {
if (this.cancelToken === cancelToken) { if (this.abortController === abortController) {
this.cancelToken = null; this.abortController = null;
} }
} }
} }
private init() { private init() {
this.abortController = null;
this.succeeded = false;
this.complete = new Promise<void>((resolve, reject) => { this.complete = new Promise<void>((resolve, reject) => {
this.cancelToken = null; this.completeResolve = resolve;
this.executed = false; this.completeReject = reject;
this.loadedSignal = resolve;
this.canceledSignal = reject;
}); });
// Suppress unhandled rejection warning // Suppress unhandled rejection warning
this.complete.catch(() => {}); this.complete.catch(() => {});
} }
// will reset this job back to the initial state (isLoaded=false, no errors, etc)
async reset() { async reset() {
this.#transitionToCancelled(); this.#transitionToCancelled();
if (this.cancelToken) { if (this.abortController) {
await this.waitUntilCompletion(); await this.waitUntilCompletion();
} }
this.init(); this.init();
@@ -121,27 +129,26 @@ export class CancellableTask {
} }
#transitionToCancelled() { #transitionToCancelled() {
if (this.executed) { if (this.succeeded) {
return; return;
} }
if (!this.cancellable) { if (!this.cancellable) {
return; return;
} }
this.cancelToken?.abort(); this.abortController?.abort();
this.canceledSignal?.(); this.completeReject?.();
this.init(); this.init();
this.canceledCallback?.(); this.canceledCallback?.();
} }
#transitionToExecuted() { #transitionToSucceeded() {
this.executed = true; this.succeeded = true;
this.loadedSignal?.(); this.completeResolve?.();
this.loadedCallback?.(); this.succeededCallback?.();
} }
#transitionToErrored(error: unknown) { #transitionToErrored(error: unknown) {
this.cancelToken = null; this.completeReject?.();
this.canceledSignal?.();
this.init(); this.init();
this.errorCallback?.(error); this.errorCallback?.(error);
} }
-1
View File
@@ -1,7 +1,6 @@
# Allow social media access og Tags # Allow social media access og Tags
User-agent: * User-agent: *
Allow: /share/ Allow: /share/
Allow: /s/
Allow: /api/assets/ Allow: /api/assets/
# https://www.robotstxt.org/robotstxt.html # https://www.robotstxt.org/robotstxt.html
-2
View File
@@ -8,13 +8,11 @@
"moduleResolution": "bundler", "moduleResolution": "bundler",
"noImplicitOverride": true, "noImplicitOverride": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"rootDir": ".",
"skipLibCheck": true, "skipLibCheck": true,
"sourceMap": true, "sourceMap": true,
"strict": true, "strict": true,
"target": "es2022", "target": "es2022",
"types": ["vitest/globals"] "types": ["vitest/globals"]
}, },
"exclude": ["vite.config.ts"],
"extends": "./.svelte-kit/tsconfig.json" "extends": "./.svelte-kit/tsconfig.json"
} }