Compare commits

..

3 Commits

Author SHA1 Message Date
Alex Tran
3acf0a87f3 revert bad changes 2025-01-24 12:52:26 -06:00
Alex Tran
0875c4c468 revert en-US changes 2025-01-24 12:49:04 -06:00
Alex
92c0c768ee chore(mobile): translation update 2025-01-24 11:50:49 -06:00
48 changed files with 313 additions and 2013 deletions

6
cli/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@immich/cli",
"version": "2.2.44",
"version": "2.2.42",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@immich/cli",
"version": "2.2.44",
"version": "2.2.42",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"fast-glob": "^3.3.2",
@@ -52,7 +52,7 @@
},
"../open-api/typescript-sdk": {
"name": "@immich/sdk",
"version": "1.125.3",
"version": "1.125.1",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@immich/cli",
"version": "2.2.44",
"version": "2.2.42",
"description": "Command Line Interface (CLI) for Immich",
"type": "module",
"exports": "./dist/index.js",

View File

@@ -1,12 +1,4 @@
[
{
"label": "v1.125.3",
"url": "https://v1.125.3.archive.immich.app"
},
{
"label": "v1.125.2",
"url": "https://v1.125.2.archive.immich.app"
},
{
"label": "v1.125.1",
"url": "https://v1.125.1.archive.immich.app"

8
e2e/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "immich-e2e",
"version": "1.125.3",
"version": "1.125.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "immich-e2e",
"version": "1.125.3",
"version": "1.125.1",
"license": "GNU Affero General Public License version 3",
"devDependencies": {
"@eslint/eslintrc": "^3.1.0",
@@ -45,7 +45,7 @@
},
"../cli": {
"name": "@immich/cli",
"version": "2.2.44",
"version": "2.2.42",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {
@@ -92,7 +92,7 @@
},
"../open-api/typescript-sdk": {
"name": "@immich/sdk",
"version": "1.125.3",
"version": "1.125.1",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "immich-e2e",
"version": "1.125.3",
"version": "1.125.1",
"description": "",
"main": "index.js",
"type": "module",

View File

@@ -52,10 +52,7 @@ describe('/albums', () => {
user1Albums = await Promise.all([
utils.createAlbum(user1.accessToken, {
albumName: user1SharedEditorUser,
albumUsers: [
{ userId: admin.userId, role: AlbumUserRole.Editor },
{ userId: user2.userId, role: AlbumUserRole.Editor },
],
albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Editor }],
assetIds: [user1Asset1.id],
}),
utils.createAlbum(user1.accessToken, {
@@ -365,26 +362,6 @@ describe('/albums', () => {
shared: true,
});
});
it('should not count trashed assets', async () => {
await utils.deleteAssets(user1.accessToken, [user1Asset2.id]);
const { status, body } = await request(app)
.get(`/albums/${user2Albums[0].id}?withoutAssets=true`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual({
...user2Albums[0],
assets: [],
assetCount: 1,
lastModifiedAssetTimestamp: expect.any(String),
endDate: expect.any(String),
startDate: expect.any(String),
albumUsers: expect.any(Array),
shared: true,
});
});
});
describe('GET /albums/statistics', () => {

View File

@@ -97,8 +97,8 @@ download:
- file: mobile/assets/i18n/id-ID.json
locale_code: id-ID
- file: mobile/assets/i18n/gl.json
locale_code: gl
locale_code: gl-ES
- file: mobile/assets/i18n/ga.json
locale_code: ga
locale_code: ga-IE
- file: mobile/assets/i18n/tr-TR.json
locale_code: tr-TR

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "machine-learning"
version = "1.125.3"
version = "1.125.1"
description = ""
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
readme = "README.md"

View File

@@ -35,8 +35,8 @@ platform :android do
task: 'bundle',
build_type: 'Release',
properties: {
"android.injected.version.code" => 179,
"android.injected.version.name" => "1.125.3",
"android.injected.version.code" => 177,
"android.injected.version.name" => "1.125.1",
}
)
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')

View File

@@ -248,7 +248,6 @@
"download_waiting_to_retry": "Waiting to retry",
"edit_date_time_dialog_date_time": "Date and Time",
"edit_date_time_dialog_timezone": "Timezone",
"edit_date_time_dialog_search_timezone": "Search timezone...",
"edit_image_title": "Edit",
"edit_location_dialog_title": "Location",
"end_date": "End date",

View File

@@ -1,673 +0,0 @@
{
"action_common_back": "Back",
"action_common_cancel": "Cancel",
"action_common_clear": "Clear",
"action_common_confirm": "Confirm",
"action_common_save": "Save",
"action_common_select": "Select",
"action_common_update": "Update",
"add_a_name": "Add a name",
"add_endpoint": "Add endpoint",
"add_to_album_bottom_sheet_added": "Added to {album}",
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
"advanced_settings_log_level_title": "Log level: {}",
"advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from assets on the device. Activate this setting to load remote images instead.",
"advanced_settings_prefer_remote_title": "Prefer remote images",
"advanced_settings_proxy_headers_subtitle": "Define proxy headers Immich should send with each network request",
"advanced_settings_proxy_headers_title": "Proxy Headers",
"advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.",
"advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates (EXPERIMENTAL)",
"advanced_settings_tile_subtitle": "Advanced user's settings",
"advanced_settings_tile_title": "Advanced",
"advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
"advanced_settings_troubleshooting_title": "Troubleshooting",
"album_info_card_backup_album_excluded": "EXCLUDED",
"album_info_card_backup_album_included": "INCLUDED",
"albums": "Albums",
"album_thumbnail_card_item": "1 item",
"album_thumbnail_card_items": "{} items",
"album_thumbnail_card_shared": " · Shared",
"album_thumbnail_owned": "Owned",
"album_thumbnail_shared_by": "Shared by {}",
"album_viewer_appbar_delete_confirm": "Are you sure you want to delete this album from your account?",
"album_viewer_appbar_share_delete": "Delete album",
"album_viewer_appbar_share_err_delete": "Failed to delete album",
"album_viewer_appbar_share_err_leave": "Failed to leave album",
"album_viewer_appbar_share_err_remove": "There are problems in removing assets from album",
"album_viewer_appbar_share_err_title": "Failed to change album title",
"album_viewer_appbar_share_leave": "Leave album",
"album_viewer_appbar_share_remove": "Remove from album",
"album_viewer_appbar_share_to": "Share To",
"album_viewer_page_share_add_users": "Add users",
"all": "All",
"all_people_page_title": "People",
"all_videos_page_title": "Videos",
"app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out",
"archived": "Archived",
"archive_page_no_archived_assets": "No archived assets found",
"archive_page_title": "Archive ({})",
"asset_action_delete_err_read_only": "Cannot delete read only asset(s), skipping",
"asset_action_share_err_offline": "Cannot fetch offline asset(s), skipping",
"asset_list_group_by_sub_title": "Group by",
"asset_list_layout_settings_dynamic_layout_title": "Dynamic layout",
"asset_list_layout_settings_group_automatically": "Automatic",
"asset_list_layout_settings_group_by": "Group assets by",
"asset_list_layout_settings_group_by_month": "Month",
"asset_list_layout_settings_group_by_month_day": "Month + day",
"asset_list_layout_sub_title": "Layout",
"asset_list_settings_subtitle": "Photo grid layout settings",
"asset_list_settings_title": "Photo Grid",
"asset_restored_successfully": "Asset restored successfully",
"assets_deleted_permanently": "{} asset(s) deleted permanently",
"assets_deleted_permanently_from_server": "{} asset(s) deleted permanently from the Immich server",
"assets_removed_permanently_from_device": "{} asset(s) removed permanently from your device",
"assets_restored_successfully": "{} asset(s) restored successfully",
"assets_trashed": "{} asset(s) trashed",
"assets_trashed_from_server": "{} asset(s) trashed from the Immich server",
"asset_viewer_settings_subtitle": "Manage your gallery viewer settings",
"asset_viewer_settings_title": "Asset Viewer",
"automatic_endpoint_switching_subtitle": "Connect locally over designated Wi-Fi when available and use alternative connections elsewhere",
"automatic_endpoint_switching_title": "Automatic URL switching",
"background_location_permission": "Background location permission",
"background_location_permission_content": "In order to switch networks when running in the background, Immich must *always* have precise location access so the app can read the Wi-Fi network's name",
"backup_album_selection_page_albums_device": "Albums on device ({})",
"backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude",
"backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.",
"backup_album_selection_page_select_albums": "Select albums",
"backup_album_selection_page_selection_info": "Selection Info",
"backup_album_selection_page_total_assets": "Total unique assets",
"backup_all": "All",
"backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…",
"backup_background_service_connection_failed_message": "Failed to connect to the server. Retrying…",
"backup_background_service_current_upload_notification": "Uploading {}",
"backup_background_service_default_notification": "Checking for new assets…",
"backup_background_service_error_title": "Backup error",
"backup_background_service_in_progress_notification": "Backing up your assets…",
"backup_background_service_upload_failure_notification": "Failed to upload {}",
"backup_controller_page_albums": "Backup Albums",
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings",
"backup_controller_page_background_battery_info_link": "Show me how",
"backup_controller_page_background_battery_info_message": "For the best background backup experience, please disable any battery optimizations restricting background activity for Immich.\n\nSince this is device-specific, please lookup the required information for your device manufacturer.",
"backup_controller_page_background_battery_info_ok": "OK",
"backup_controller_page_background_battery_info_title": "Battery optimizations",
"backup_controller_page_background_charging": "Only while charging",
"backup_controller_page_background_configure_error": "Failed to configure the background service",
"backup_controller_page_background_delay": "Delay new assets backup: {}",
"backup_controller_page_background_description": "Turn on the background service to automatically backup any new assets without needing to open the app",
"backup_controller_page_background_is_off": "Automatic background backup is off",
"backup_controller_page_background_is_on": "Automatic background backup is on",
"backup_controller_page_background_turn_off": "Turn off background service",
"backup_controller_page_background_turn_on": "Turn on background service",
"backup_controller_page_background_wifi": "Only on WiFi",
"backup_controller_page_backup": "Backup",
"backup_controller_page_backup_selected": "Selected: ",
"backup_controller_page_backup_sub": "Backed up photos and videos",
"backup_controller_page_cancel": "Cancel",
"backup_controller_page_created": "Created on: {}",
"backup_controller_page_desc_backup": "Turn on foreground backup to automatically upload new assets to the server when opening the app.",
"backup_controller_page_excluded": "Excluded: ",
"backup_controller_page_failed": "Failed ({})",
"backup_controller_page_filename": "File name: {} [{}]",
"backup_controller_page_id": "ID: {}",
"backup_controller_page_info": "Backup Information",
"backup_controller_page_none_selected": "None selected",
"backup_controller_page_remainder": "Remainder",
"backup_controller_page_remainder_sub": "Remaining photos and videos to back up from selection",
"backup_controller_page_select": "Select",
"backup_controller_page_server_storage": "Server Storage",
"backup_controller_page_start_backup": "Start Backup",
"backup_controller_page_status_off": "Automatic foreground backup is off",
"backup_controller_page_status_on": "Automatic foreground backup is on",
"backup_controller_page_storage_format": "{} of {} used",
"backup_controller_page_to_backup": "Albums to be backed up",
"backup_controller_page_total": "Total",
"backup_controller_page_total_sub": "All unique photos and videos from selected albums",
"backup_controller_page_turn_off": "Turn off foreground backup",
"backup_controller_page_turn_on": "Turn on foreground backup",
"backup_controller_page_uploading_file_info": "Uploading file info",
"backup_err_only_album": "Cannot remove the only album",
"backup_info_card_assets": "assets",
"backup_manual_cancelled": "Cancelled",
"backup_manual_failed": "Failed",
"backup_manual_in_progress": "Upload already in progress. Try after sometime",
"backup_manual_success": "Success",
"backup_manual_title": "Upload status",
"backup_options_page_title": "Backup options",
"backup_setting_subtitle": "Manage background and foreground upload settings",
"cache_settings_album_thumbnails": "Library page thumbnails ({} assets)",
"cache_settings_clear_cache_button": "Clear cache",
"cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.",
"cache_settings_duplicated_assets_clear_button": "CLEAR",
"cache_settings_duplicated_assets_subtitle": "Photos and videos that are black listed by the app",
"cache_settings_duplicated_assets_title": "Duplicated Assets ({})",
"cache_settings_image_cache_size": "Image cache size ({} assets)",
"cache_settings_statistics_album": "Library thumbnails",
"cache_settings_statistics_assets": "{} assets ({})",
"cache_settings_statistics_full": "Full images",
"cache_settings_statistics_shared": "Shared album thumbnails",
"cache_settings_statistics_thumbnail": "Thumbnails",
"cache_settings_statistics_title": "Cache usage",
"cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application",
"cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)",
"cache_settings_tile_subtitle": "Control the local storage behaviour",
"cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Caching Settings",
"cancel": "Cancel",
"canceled": "Canceled",
"change_display_order": "Change display order",
"change_password_form_confirm_password": "Confirm Password",
"change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
"change_password_form_new_password": "New Password",
"change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Re-enter New Password",
"check_corrupt_asset_backup": "Check for corrupt asset backups",
"check_corrupt_asset_backup_button": "Perform check",
"check_corrupt_asset_backup_description": "Run this check only over Wi-Fi and once all assets have been backed-up. The procedure might take a few minutes.",
"client_cert_dialog_msg_confirm": "OK",
"client_cert_enter_password": "Enter Password",
"client_cert_import": "Import",
"client_cert_import_success_msg": "Client certificate is imported",
"client_cert_invalid_msg": "Invalid certificate file or wrong password",
"client_cert_remove": "Remove",
"client_cert_remove_msg": "Client certificate is removed",
"client_cert_subtitle": "Supports PKCS12 (.p12, .pfx) format only. Certificate Import/Remove is available only before login",
"client_cert_title": "SSL Client Certificate (EXPERIMENTAL)",
"common_add_to_album": "Add to album",
"common_change_password": "Change Password",
"common_create_new_album": "Create new album",
"common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.",
"common_shared": "Shared",
"completed": "Completed",
"contextual_search": "Sunrise on the beach",
"control_bottom_app_bar_add_to_album": "Add to album",
"control_bottom_app_bar_album_info": "{} items",
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
"control_bottom_app_bar_archive": "Archive",
"control_bottom_app_bar_create_new_album": "Create new album",
"control_bottom_app_bar_delete": "Delete",
"control_bottom_app_bar_delete_from_immich": "Delete from Immich",
"control_bottom_app_bar_delete_from_local": "Delete from device",
"control_bottom_app_bar_download": "Download",
"control_bottom_app_bar_edit": "Edit",
"control_bottom_app_bar_edit_location": "Edit Location",
"control_bottom_app_bar_edit_time": "Edit Date & Time",
"control_bottom_app_bar_favorite": "Favorite",
"control_bottom_app_bar_share": "Share",
"control_bottom_app_bar_share_to": "Share To",
"control_bottom_app_bar_stack": "Stack",
"control_bottom_app_bar_trash_from_immich": "Move to Trash",
"control_bottom_app_bar_unarchive": "Unarchive",
"control_bottom_app_bar_unfavorite": "Unfavorite",
"control_bottom_app_bar_upload": "Upload",
"create_album": "Create album",
"create_album_page_untitled": "Untitled",
"create_new": "CREATE NEW",
"create_shared_album_page_create": "Create",
"create_shared_album_page_share": "Share",
"create_shared_album_page_share_add_assets": "ADD ASSETS",
"create_shared_album_page_share_select_photos": "Select Photos",
"crop": "Crop",
"curated_location_page_title": "Places",
"curated_object_page_title": "Things",
"current_server_address": "Current server address",
"daily_title_text_date": "E, MMM dd",
"daily_title_text_date_year": "E, MMM dd, yyyy",
"date_format": "E, LLL d, y • h:mm a",
"delete_dialog_alert": "These items will be permanently deleted from Immich and from your device",
"delete_dialog_alert_local": "These items will be permanently removed from your device but still be available on the Immich server",
"delete_dialog_alert_local_non_backed_up": "Some of the items aren't backed up to Immich and will be permanently removed from your device",
"delete_dialog_alert_remote": "These items will be permanently deleted from the Immich server",
"delete_dialog_cancel": "Cancel",
"delete_dialog_ok": "Delete",
"delete_dialog_ok_force": "Delete Anyway",
"delete_dialog_title": "Delete Permanently",
"delete_local_dialog_ok_backed_up_only": "Delete Backed Up Only",
"delete_local_dialog_ok_force": "Delete Anyway",
"delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?",
"delete_shared_link_dialog_title": "Delete Shared Link",
"description_input_hint_text": "Add description...",
"description_input_submit_error": "Error updating description, check the log for more details",
"download_canceled": "Download canceled",
"download_complete": "Download complete",
"download_enqueue": "Download enqueued",
"download_error": "Download Error",
"download_failed": "Download failed",
"download_filename": "file: {}",
"download_finished": "Download finished",
"downloading": "Downloading...",
"downloading_media": "Downloading media",
"download_notfound": "Download not found",
"download_paused": "Download paused",
"download_started": "Download started",
"download_sucess": "Download success",
"download_sucess_android": "The media has been downloaded to DCIM/Immich",
"download_waiting_to_retry": "Waiting to retry",
"edit_date_time_dialog_date_time": "Date and Time",
"edit_date_time_dialog_timezone": "Timezone",
"edit_image_title": "Edit",
"edit_location_dialog_title": "Location",
"end_date": "End date",
"enqueued": "Enqueued",
"enter_wifi_name": "Enter WiFi name",
"error_change_sort_album": "Failed to change album sort order",
"error_saving_image": "Error: {}",
"exif_bottom_sheet_description": "Add Description...",
"exif_bottom_sheet_details": "DETAILS",
"exif_bottom_sheet_location": "LOCATION",
"exif_bottom_sheet_location_add": "Add a location",
"exif_bottom_sheet_people": "PEOPLE",
"exif_bottom_sheet_person_add_person": "Add name",
"experimental_settings_new_asset_list_subtitle": "Work in progress",
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
"experimental_settings_subtitle": "Use at your own risk!",
"experimental_settings_title": "Experimental",
"external_network": "External network",
"external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom",
"failed": "Failed",
"favorites": "Favorites",
"favorites_page_no_favorites": "No favorite assets found",
"favorites_page_title": "Favorites",
"filename_search": "File name or extension",
"filter": "Filter",
"get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network",
"grant_permission": "Grant permission",
"haptic_feedback_switch": "Enable haptic feedback",
"haptic_feedback_title": "Haptic Feedback",
"header_settings_add_header_tip": "Add Header",
"header_settings_field_validator_msg": "Value cannot be empty",
"header_settings_header_name_input": "Header name",
"header_settings_header_value_input": "Header value",
"header_settings_page_title": "Proxy Headers (EXPERIMENTAL)",
"headers_settings_tile_subtitle": "Define proxy headers the app should send with each network request",
"headers_settings_tile_title": "Custom proxy headers",
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
"home_page_building_timeline": "Building the timeline",
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
"home_page_delete_remote_err_local": "Local assets in delete remote selection, skipping",
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
"home_page_share_err_local": "Can not share local assets via link, skipping",
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
"ignore_icloud_photos": "Ignore iCloud photos",
"ignore_icloud_photos_description": "Photos that are stored on iCloud will not be uploaded to the Immich server",
"image_saved_successfully": "Image saved",
"image_viewer_page_state_provider_download_error": "Download Error",
"image_viewer_page_state_provider_download_started": "Download Started",
"image_viewer_page_state_provider_download_success": "Download Success",
"image_viewer_page_state_provider_share_error": "Share Error",
"invalid_date": "Invalid date",
"invalid_date_format": "Invalid date format",
"library": "Library",
"library_page_albums": "Albums",
"library_page_archive": "Archive",
"library_page_device_albums": "Albums on Device",
"library_page_favorites": "Favorites",
"library_page_new_album": "New album",
"library_page_sharing": "Sharing",
"library_page_sort_asset_count": "Number of assets",
"library_page_sort_created": "Created date",
"library_page_sort_last_modified": "Last modified",
"library_page_sort_most_oldest_photo": "Oldest photo",
"library_page_sort_most_recent_photo": "Most recent photo",
"library_page_sort_title": "Album title",
"local_network": "Local network",
"local_network_sheet_info": "The app will connect to the server through this URL when using the specified Wi-Fi network",
"location_permission": "Location permission",
"location_permission_content": "In order to use the auto-switching feature, Immich needs precise location permission so it can read the current WiFi network's name",
"location_picker_choose_on_map": "Choose on map",
"location_picker_latitude": "Latitude",
"location_picker_latitude_error": "Enter a valid latitude",
"location_picker_latitude_hint": "Enter your latitude here",
"location_picker_longitude": "Longitude",
"location_picker_longitude_error": "Enter a valid longitude",
"location_picker_longitude_hint": "Enter your longitude here",
"login_disabled": "Login has been disabled",
"login_form_api_exception": "API exception. Please check the server URL and try again.",
"login_form_back_button_text": "Back",
"login_form_button_text": "Login",
"login_form_email_hint": "youremail@email.com",
"login_form_endpoint_hint": "http://your-server-ip:port",
"login_form_endpoint_url": "Server Endpoint URL",
"login_form_err_http": "Please specify http:// or https://",
"login_form_err_invalid_email": "Invalid Email",
"login_form_err_invalid_url": "Invalid URL",
"login_form_err_leading_whitespace": "Leading whitespace",
"login_form_err_trailing_whitespace": "Trailing whitespace",
"login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
"login_form_failed_login": "Error logging you in, check server URL, email and password",
"login_form_handshake_exception": "There was an Handshake Exception with the server. Enable self-signed certificate support in the settings if you are using a self-signed certificate.",
"login_form_label_email": "Email",
"login_form_label_password": "Password",
"login_form_next_button": "Next",
"login_form_password_hint": "password",
"login_form_save_login": "Stay logged in",
"login_form_server_empty": "Enter a server URL.",
"login_form_server_error": "Could not connect to server.",
"login_password_changed_error": "There was an error updating your password",
"login_password_changed_success": "Password updated successfully",
"map_assets_in_bound": "{} photo",
"map_assets_in_bounds": "{} photos",
"map_cannot_get_user_location": "Cannot get user's location",
"map_location_dialog_cancel": "Cancel",
"map_location_dialog_yes": "Yes",
"map_location_picker_page_use_location": "Use this location",
"map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?",
"map_location_service_disabled_title": "Location Service disabled",
"map_no_assets_in_bounds": "No photos in this area",
"map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?",
"map_no_location_permission_title": "Location Permission denied",
"map_settings_dark_mode": "Dark mode",
"map_settings_date_range_option_all": "All",
"map_settings_date_range_option_day": "Past 24 hours",
"map_settings_date_range_option_days": "Past {} days",
"map_settings_date_range_option_year": "Past year",
"map_settings_date_range_option_years": "Past {} years",
"map_settings_dialog_cancel": "Cancel",
"map_settings_dialog_save": "Save",
"map_settings_dialog_title": "Map Settings",
"map_settings_include_show_archived": "Include Archived",
"map_settings_include_show_partners": "Include Partners",
"map_settings_only_relative_range": "Date range",
"map_settings_only_show_favorites": "Show Favorite Only",
"map_settings_theme_settings": "Map Theme",
"map_zoom_to_see_photos": "Zoom out to see photos",
"memories_all_caught_up": "All caught up",
"memories_check_back_tomorrow": "Check back tomorrow for more memories",
"memories_start_over": "Start Over",
"memories_swipe_to_close": "Swipe up to close",
"memories_year_ago": "A year ago",
"memories_years_ago": "{} years ago",
"monthly_title_text_date_format": "MMMM y",
"motion_photos_page_title": "Motion Photos",
"multiselect_grid_edit_date_time_err_read_only": "Cannot edit date of read only asset(s), skipping",
"multiselect_grid_edit_gps_err_read_only": "Cannot edit location of read only asset(s), skipping",
"my_albums": "My albums",
"networking_settings": "Networking",
"networking_subtitle": "Manage the server endpoint settings",
"no_assets_to_show": "No assets to show",
"no_name": "No name",
"notification_permission_dialog_cancel": "Cancel",
"notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.",
"notification_permission_dialog_settings": "Settings",
"notification_permission_list_tile_content": "Grant permission to enable notifications.",
"notification_permission_list_tile_enable_button": "Enable Notifications",
"notification_permission_list_tile_title": "Notification Permission",
"not_selected": "Not selected",
"on_this_device": "On this device",
"partner_list_user_photos": "{user}'s photos",
"partner_list_view_all": "View all",
"partner_page_add_partner": "Add partner",
"partner_page_empty_message": "Your photos are not yet shared with any partner.",
"partner_page_no_more_users": "No more users to add",
"partner_page_partner_add_failed": "Failed to add partner",
"partner_page_select_partner": "Select partner",
"partner_page_shared_to_title": "Shared to",
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
"partner_page_stop_sharing_title": "Stop sharing your photos?",
"partner_page_title": "Partner",
"partners": "Partners",
"paused": "Paused",
"people": "People",
"permission_onboarding_back": "Back",
"permission_onboarding_continue_anyway": "Continue anyway",
"permission_onboarding_get_started": "Get started",
"permission_onboarding_go_to_settings": "Go to settings",
"permission_onboarding_grant_permission": "Grant permission",
"permission_onboarding_log_out": "Log out",
"permission_onboarding_permission_denied": "Permission denied. To use Immich, grant photo and video permissions in Settings.",
"permission_onboarding_permission_granted": "Permission granted! You are all set.",
"permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.",
"permission_onboarding_request": "Immich requires permission to view your photos and videos.",
"places": "Places",
"preferences_settings_subtitle": "Manage the app's preferences",
"preferences_settings_title": "Preferences",
"profile_drawer_app_logs": "Logs",
"profile_drawer_client_out_of_date_major": "Mobile App is out of date. Please update to the latest major version.",
"profile_drawer_client_out_of_date_minor": "Mobile App is out of date. Please update to the latest minor version.",
"profile_drawer_client_server_up_to_date": "Client and Server are up-to-date",
"profile_drawer_documentation": "Documentation",
"profile_drawer_github": "GitHub",
"profile_drawer_server_out_of_date_major": "Server is out of date. Please update to the latest major version.",
"profile_drawer_server_out_of_date_minor": "Server is out of date. Please update to the latest minor version.",
"profile_drawer_settings": "Settings",
"profile_drawer_sign_out": "Sign Out",
"profile_drawer_trash": "Trash",
"recently_added": "Recently added",
"recently_added_page_title": "Recently Added",
"save": "Save",
"save_to_gallery": "Save to gallery",
"scaffold_body_error_occurred": "Error occurred",
"search_albums": "Search albums",
"search_bar_hint": "Search your photos",
"search_filter_apply": "Apply filter",
"search_filter_camera": "Camera",
"search_filter_camera_make": "Make",
"search_filter_camera_model": "Model",
"search_filter_camera_title": "Select camera type",
"search_filter_date": "Date",
"search_filter_date_interval": "{start} to {end}",
"search_filter_date_title": "Select a date range",
"search_filter_display_option_archive": "Archive",
"search_filter_display_option_favorite": "Favorite",
"search_filter_display_option_not_in_album": "Not in album",
"search_filter_display_options": "Display Options",
"search_filter_display_options_title": "Display options",
"search_filter_location": "Location",
"search_filter_location_city": "City",
"search_filter_location_country": "Country",
"search_filter_location_state": "State",
"search_filter_location_title": "Select location",
"search_filter_media_type": "Media Type",
"search_filter_media_type_all": "All",
"search_filter_media_type_image": "Image",
"search_filter_media_type_title": "Select media type",
"search_filter_media_type_video": "Video",
"search_filter_people": "People",
"search_filter_people_title": "Select people",
"search_page_categories": "Categories",
"search_page_favorites": "Favorites",
"search_page_motion_photos": "Motion Photos",
"search_page_no_objects": "No Objects Info Available",
"search_page_no_places": "No Places Info Available",
"search_page_people": "People",
"search_page_person_add_name_dialog_cancel": "Cancel",
"search_page_person_add_name_dialog_hint": "Name",
"search_page_person_add_name_dialog_save": "Save",
"search_page_person_add_name_dialog_title": "Add a name",
"search_page_person_add_name_subtitle": "Find them fast by name with search",
"search_page_person_add_name_title": "Add a name",
"search_page_person_edit_name": "Edit name",
"search_page_places": "Places",
"search_page_recently_added": "Recently added",
"search_page_screenshots": "Screenshots",
"search_page_search_photos_videos": "Search for your photos and videos",
"search_page_selfies": "Selfies",
"search_page_things": "Things",
"search_page_videos": "Videos",
"search_page_view_all_button": "View all",
"search_page_your_activity": "Your activity",
"search_page_your_map": "Your Map",
"search_result_page_new_search_hint": "New Search",
"search_suggestion_list_smart_search_hint_1": "Smart search is enabled by default, to search for metadata use the syntax ",
"search_suggestion_list_smart_search_hint_2": "m:your-search-term",
"select_additional_user_for_sharing_page_suggestions": "Suggestions",
"select_user_for_sharing_page_err_album": "Failed to create album",
"select_user_for_sharing_page_share_suggestions": "Suggestions",
"server_endpoint": "Server Endpoint",
"server_info_box_app_version": "App Version",
"server_info_box_latest_release": "Latest Version",
"server_info_box_server_url": "Server URL",
"server_info_box_server_version": "Server Version",
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
"setting_image_viewer_original_title": "Load original image",
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
"setting_image_viewer_preview_title": "Load preview image",
"setting_image_viewer_title": "Images",
"setting_languages_apply": "Apply",
"setting_languages_subtitle": "Change the app's language",
"setting_languages_title": "Languages",
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
"setting_notifications_notify_hours": "{} hours",
"setting_notifications_notify_immediately": "immediately",
"setting_notifications_notify_minutes": "{} minutes",
"setting_notifications_notify_never": "never",
"setting_notifications_notify_seconds": "{} seconds",
"setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset",
"setting_notifications_single_progress_title": "Show background backup detail progress",
"setting_notifications_subtitle": "Adjust your notification preferences",
"setting_notifications_title": "Notifications",
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
"setting_notifications_total_progress_title": "Show background backup total progress",
"setting_pages_app_bar_settings": "Settings",
"settings_require_restart": "Please restart Immich to apply this setting",
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
"setting_video_viewer_looping_title": "Looping",
"setting_video_viewer_original_video_subtitle": "When streaming a video from the server, play the original even when a transcode is available. May lead to buffering. Videos available locally are played in original quality regardless of this setting.",
"setting_video_viewer_original_video_title": "Force original video",
"setting_video_viewer_title": "Videos",
"share_add": "Add",
"share_add_photos": "Add photos",
"share_add_title": "Add a title",
"share_assets_selected": "{} selected",
"share_create_album": "Create album",
"shared_album_activities_input_disable": "Comment is disabled",
"shared_album_activities_input_hint": "Say something",
"shared_album_activity_remove_content": "Do you want to delete this activity?",
"shared_album_activity_remove_title": "Delete Activity",
"shared_album_activity_setting_subtitle": "Let others respond",
"shared_album_activity_setting_title": "Comments & likes",
"shared_album_section_people_action_error": "Error leaving/removing from album",
"shared_album_section_people_action_leave": "Remove user from album",
"shared_album_section_people_action_remove_user": "Remove user from album",
"shared_album_section_people_owner_label": "Owner",
"shared_album_section_people_title": "PEOPLE",
"share_dialog_preparing": "Preparing...",
"shared_intent_upload_button_progress_text": "{} / {} Uploaded",
"shared_link_app_bar_title": "Shared Links",
"shared_link_clipboard_copied_massage": "Copied to clipboard",
"shared_link_clipboard_text": "Link: {}\nPassword: {}",
"shared_link_create_app_bar_title": "Create link to share",
"shared_link_create_error": "Error while creating shared link",
"shared_link_create_info": "Let anyone with the link see the selected photo(s)",
"shared_link_create_submit_button": "Create link",
"shared_link_edit_allow_download": "Allow public user to download",
"shared_link_edit_allow_upload": "Allow public user to upload",
"shared_link_edit_app_bar_title": "Edit link",
"shared_link_edit_change_expiry": "Change expiration time",
"shared_link_edit_description": "Description",
"shared_link_edit_description_hint": "Enter the share description",
"shared_link_edit_expire_after": "Expire after",
"shared_link_edit_expire_after_option_day": "1 day",
"shared_link_edit_expire_after_option_days": "{} days",
"shared_link_edit_expire_after_option_hour": "1 hour",
"shared_link_edit_expire_after_option_hours": "{} hours",
"shared_link_edit_expire_after_option_minute": "1 minute",
"shared_link_edit_expire_after_option_minutes": "{} minutes",
"shared_link_edit_expire_after_option_months": "{} months",
"shared_link_edit_expire_after_option_never": "Never",
"shared_link_edit_expire_after_option_year": "{} year",
"shared_link_edit_password": "Password",
"shared_link_edit_password_hint": "Enter the share password",
"shared_link_edit_show_meta": "Show metadata",
"shared_link_edit_submit_button": "Update link",
"shared_link_empty": "You don't have any shared links",
"shared_link_error_server_url_fetch": "Cannot fetch the server url",
"shared_link_expired": "Expired",
"shared_link_expires_day": "Expires in {} day",
"shared_link_expires_days": "Expires in {} days",
"shared_link_expires_hour": "Expires in {} hour",
"shared_link_expires_hours": "Expires in {} hours",
"shared_link_expires_minute": "Expires in {} minute",
"shared_link_expires_minutes": "Expires in {} minutes",
"shared_link_expires_never": "Expires ∞",
"shared_link_expires_second": "Expires in {} second",
"shared_link_expires_seconds": "Expires in {} seconds",
"shared_link_individual_shared": "Individual shared",
"shared_link_info_chip_download": "Download",
"shared_link_info_chip_metadata": "EXIF",
"shared_link_info_chip_upload": "Upload",
"shared_link_manage_links": "Manage Shared links",
"shared_link_public_album": "Public album",
"shared_links": "Shared links",
"share_done": "Done",
"shared_with_me": "Shared with me",
"share_invite": "Invite to album",
"sharing_page_album": "Shared albums",
"sharing_page_description": "Create shared albums to share photos and videos with people in your network.",
"sharing_page_empty_list": "EMPTY LIST",
"sharing_silver_appbar_create_shared_album": "New shared album",
"sharing_silver_appbar_shared_links": "Shared links",
"sharing_silver_appbar_share_partner": "Share with partner",
"start_date": "Start date",
"sync": "Sync",
"sync_albums": "Sync albums",
"sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums",
"sync_upload_album_setting_subtitle": "Create and upload your photos and videos to the selected albums on Immich",
"tab_controller_nav_library": "Library",
"tab_controller_nav_photos": "Photos",
"tab_controller_nav_search": "Search",
"tab_controller_nav_sharing": "Sharing",
"theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles",
"theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})",
"theme_setting_colorful_interface_subtitle": "Apply primary color to background surfaces.",
"theme_setting_colorful_interface_title": "Colorful interface",
"theme_setting_dark_mode_switch": "Dark mode",
"theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer",
"theme_setting_image_viewer_quality_title": "Image viewer quality",
"theme_setting_primary_color_subtitle": "Pick a color for primary actions and accents.",
"theme_setting_primary_color_title": "Primary color",
"theme_setting_system_primary_color_title": "Use system color",
"theme_setting_system_theme_switch": "Automatic (Follow system setting)",
"theme_setting_theme_subtitle": "Choose the app's theme setting",
"theme_setting_theme_title": "Theme",
"theme_setting_three_stage_loading_subtitle": "Three-stage loading might increase the loading performance but causes significantly higher network load",
"theme_setting_three_stage_loading_title": "Enable three-stage loading",
"translated_text_options": "Options",
"trash": "Trash",
"trash_emptied": "Emptied trash",
"trash_page_delete": "Delete",
"trash_page_delete_all": "Delete All",
"trash_page_empty_trash_btn": "Empty trash",
"trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich",
"trash_page_empty_trash_dialog_ok": "Ok",
"trash_page_info": "Trashed items will be permanently deleted after {} days",
"trash_page_no_assets": "No trashed assets",
"trash_page_restore": "Restore",
"trash_page_restore_all": "Restore All",
"trash_page_select_assets_btn": "Select assets",
"trash_page_select_btn": "Select",
"trash_page_title": "Trash ({})",
"upload": "Upload",
"upload_dialog_cancel": "Cancel",
"upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
"upload_dialog_ok": "Upload",
"upload_dialog_title": "Upload Asset",
"uploading": "Uploading",
"upload_to_immich": "Upload to Immich ({})",
"use_current_connection": "use current connection",
"validate_endpoint_error": "Please enter a valid URL",
"version_announcement_overlay_ack": "Acknowledge",
"version_announcement_overlay_release_notes": "release notes",
"version_announcement_overlay_text_1": "Hi friend, there is a new release of",
"version_announcement_overlay_text_2": "please take your time to visit the ",
"version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.",
"version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89",
"videos": "Videos",
"viewer_remove_from_stack": "Remove from Stack",
"viewer_stack_use_as_main_asset": "Use as Main Asset",
"viewer_unstack": "Un-Stack",
"wifi_name": "WiFi Name",
"your_wifi_name": "Your WiFi name"
}

View File

@@ -1,673 +0,0 @@
{
"action_common_back": "Back",
"action_common_cancel": "Cancel",
"action_common_clear": "Clear",
"action_common_confirm": "Confirm",
"action_common_save": "Save",
"action_common_select": "Select",
"action_common_update": "Update",
"add_a_name": "Add a name",
"add_endpoint": "Add endpoint",
"add_to_album_bottom_sheet_added": "Added to {album}",
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
"advanced_settings_log_level_title": "Log level: {}",
"advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from assets on the device. Activate this setting to load remote images instead.",
"advanced_settings_prefer_remote_title": "Prefer remote images",
"advanced_settings_proxy_headers_subtitle": "Define proxy headers Immich should send with each network request",
"advanced_settings_proxy_headers_title": "Proxy Headers",
"advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.",
"advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates (EXPERIMENTAL)",
"advanced_settings_tile_subtitle": "Advanced user's settings",
"advanced_settings_tile_title": "Advanced",
"advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
"advanced_settings_troubleshooting_title": "Troubleshooting",
"album_info_card_backup_album_excluded": "EXCLUDED",
"album_info_card_backup_album_included": "INCLUDED",
"albums": "Albums",
"album_thumbnail_card_item": "1 item",
"album_thumbnail_card_items": "{} items",
"album_thumbnail_card_shared": " · Shared",
"album_thumbnail_owned": "Owned",
"album_thumbnail_shared_by": "Shared by {}",
"album_viewer_appbar_delete_confirm": "Are you sure you want to delete this album from your account?",
"album_viewer_appbar_share_delete": "Delete album",
"album_viewer_appbar_share_err_delete": "Failed to delete album",
"album_viewer_appbar_share_err_leave": "Failed to leave album",
"album_viewer_appbar_share_err_remove": "There are problems in removing assets from album",
"album_viewer_appbar_share_err_title": "Failed to change album title",
"album_viewer_appbar_share_leave": "Leave album",
"album_viewer_appbar_share_remove": "Remove from album",
"album_viewer_appbar_share_to": "Share To",
"album_viewer_page_share_add_users": "Add users",
"all": "All",
"all_people_page_title": "People",
"all_videos_page_title": "Videos",
"app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out",
"archived": "Archived",
"archive_page_no_archived_assets": "No archived assets found",
"archive_page_title": "Archive ({})",
"asset_action_delete_err_read_only": "Cannot delete read only asset(s), skipping",
"asset_action_share_err_offline": "Cannot fetch offline asset(s), skipping",
"asset_list_group_by_sub_title": "Group by",
"asset_list_layout_settings_dynamic_layout_title": "Dynamic layout",
"asset_list_layout_settings_group_automatically": "Automatic",
"asset_list_layout_settings_group_by": "Group assets by",
"asset_list_layout_settings_group_by_month": "Month",
"asset_list_layout_settings_group_by_month_day": "Month + day",
"asset_list_layout_sub_title": "Layout",
"asset_list_settings_subtitle": "Photo grid layout settings",
"asset_list_settings_title": "Photo Grid",
"asset_restored_successfully": "Asset restored successfully",
"assets_deleted_permanently": "{} asset(s) deleted permanently",
"assets_deleted_permanently_from_server": "{} asset(s) deleted permanently from the Immich server",
"assets_removed_permanently_from_device": "{} asset(s) removed permanently from your device",
"assets_restored_successfully": "{} asset(s) restored successfully",
"assets_trashed": "{} asset(s) trashed",
"assets_trashed_from_server": "{} asset(s) trashed from the Immich server",
"asset_viewer_settings_subtitle": "Manage your gallery viewer settings",
"asset_viewer_settings_title": "Asset Viewer",
"automatic_endpoint_switching_subtitle": "Connect locally over designated Wi-Fi when available and use alternative connections elsewhere",
"automatic_endpoint_switching_title": "Automatic URL switching",
"background_location_permission": "Background location permission",
"background_location_permission_content": "In order to switch networks when running in the background, Immich must *always* have precise location access so the app can read the Wi-Fi network's name",
"backup_album_selection_page_albums_device": "Albums on device ({})",
"backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude",
"backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.",
"backup_album_selection_page_select_albums": "Select albums",
"backup_album_selection_page_selection_info": "Selection Info",
"backup_album_selection_page_total_assets": "Total unique assets",
"backup_all": "All",
"backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…",
"backup_background_service_connection_failed_message": "Failed to connect to the server. Retrying…",
"backup_background_service_current_upload_notification": "Uploading {}",
"backup_background_service_default_notification": "Checking for new assets…",
"backup_background_service_error_title": "Backup error",
"backup_background_service_in_progress_notification": "Backing up your assets…",
"backup_background_service_upload_failure_notification": "Failed to upload {}",
"backup_controller_page_albums": "Backup Albums",
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings",
"backup_controller_page_background_battery_info_link": "Show me how",
"backup_controller_page_background_battery_info_message": "For the best background backup experience, please disable any battery optimizations restricting background activity for Immich.\n\nSince this is device-specific, please lookup the required information for your device manufacturer.",
"backup_controller_page_background_battery_info_ok": "OK",
"backup_controller_page_background_battery_info_title": "Battery optimizations",
"backup_controller_page_background_charging": "Only while charging",
"backup_controller_page_background_configure_error": "Failed to configure the background service",
"backup_controller_page_background_delay": "Delay new assets backup: {}",
"backup_controller_page_background_description": "Turn on the background service to automatically backup any new assets without needing to open the app",
"backup_controller_page_background_is_off": "Automatic background backup is off",
"backup_controller_page_background_is_on": "Automatic background backup is on",
"backup_controller_page_background_turn_off": "Turn off background service",
"backup_controller_page_background_turn_on": "Turn on background service",
"backup_controller_page_background_wifi": "Only on WiFi",
"backup_controller_page_backup": "Backup",
"backup_controller_page_backup_selected": "Selected: ",
"backup_controller_page_backup_sub": "Backed up photos and videos",
"backup_controller_page_cancel": "Cancel",
"backup_controller_page_created": "Created on: {}",
"backup_controller_page_desc_backup": "Turn on foreground backup to automatically upload new assets to the server when opening the app.",
"backup_controller_page_excluded": "Excluded: ",
"backup_controller_page_failed": "Failed ({})",
"backup_controller_page_filename": "File name: {} [{}]",
"backup_controller_page_id": "ID: {}",
"backup_controller_page_info": "Backup Information",
"backup_controller_page_none_selected": "None selected",
"backup_controller_page_remainder": "Remainder",
"backup_controller_page_remainder_sub": "Remaining photos and videos to back up from selection",
"backup_controller_page_select": "Select",
"backup_controller_page_server_storage": "Server Storage",
"backup_controller_page_start_backup": "Start Backup",
"backup_controller_page_status_off": "Automatic foreground backup is off",
"backup_controller_page_status_on": "Automatic foreground backup is on",
"backup_controller_page_storage_format": "{} of {} used",
"backup_controller_page_to_backup": "Albums to be backed up",
"backup_controller_page_total": "Total",
"backup_controller_page_total_sub": "All unique photos and videos from selected albums",
"backup_controller_page_turn_off": "Turn off foreground backup",
"backup_controller_page_turn_on": "Turn on foreground backup",
"backup_controller_page_uploading_file_info": "Uploading file info",
"backup_err_only_album": "Cannot remove the only album",
"backup_info_card_assets": "assets",
"backup_manual_cancelled": "Cancelled",
"backup_manual_failed": "Failed",
"backup_manual_in_progress": "Upload already in progress. Try after sometime",
"backup_manual_success": "Success",
"backup_manual_title": "Upload status",
"backup_options_page_title": "Backup options",
"backup_setting_subtitle": "Manage background and foreground upload settings",
"cache_settings_album_thumbnails": "Library page thumbnails ({} assets)",
"cache_settings_clear_cache_button": "Clear cache",
"cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.",
"cache_settings_duplicated_assets_clear_button": "CLEAR",
"cache_settings_duplicated_assets_subtitle": "Photos and videos that are black listed by the app",
"cache_settings_duplicated_assets_title": "Duplicated Assets ({})",
"cache_settings_image_cache_size": "Image cache size ({} assets)",
"cache_settings_statistics_album": "Library thumbnails",
"cache_settings_statistics_assets": "{} assets ({})",
"cache_settings_statistics_full": "Full images",
"cache_settings_statistics_shared": "Shared album thumbnails",
"cache_settings_statistics_thumbnail": "Thumbnails",
"cache_settings_statistics_title": "Cache usage",
"cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application",
"cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)",
"cache_settings_tile_subtitle": "Control the local storage behaviour",
"cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Caching Settings",
"cancel": "Cancel",
"canceled": "Canceled",
"change_display_order": "Change display order",
"change_password_form_confirm_password": "Confirm Password",
"change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
"change_password_form_new_password": "New Password",
"change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Re-enter New Password",
"check_corrupt_asset_backup": "Check for corrupt asset backups",
"check_corrupt_asset_backup_button": "Perform check",
"check_corrupt_asset_backup_description": "Run this check only over Wi-Fi and once all assets have been backed-up. The procedure might take a few minutes.",
"client_cert_dialog_msg_confirm": "OK",
"client_cert_enter_password": "Enter Password",
"client_cert_import": "Import",
"client_cert_import_success_msg": "Client certificate is imported",
"client_cert_invalid_msg": "Invalid certificate file or wrong password",
"client_cert_remove": "Remove",
"client_cert_remove_msg": "Client certificate is removed",
"client_cert_subtitle": "Supports PKCS12 (.p12, .pfx) format only. Certificate Import/Remove is available only before login",
"client_cert_title": "SSL Client Certificate (EXPERIMENTAL)",
"common_add_to_album": "Add to album",
"common_change_password": "Change Password",
"common_create_new_album": "Create new album",
"common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.",
"common_shared": "Shared",
"completed": "Completed",
"contextual_search": "Sunrise on the beach",
"control_bottom_app_bar_add_to_album": "Add to album",
"control_bottom_app_bar_album_info": "{} items",
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
"control_bottom_app_bar_archive": "Archive",
"control_bottom_app_bar_create_new_album": "Create new album",
"control_bottom_app_bar_delete": "Delete",
"control_bottom_app_bar_delete_from_immich": "Delete from Immich",
"control_bottom_app_bar_delete_from_local": "Delete from device",
"control_bottom_app_bar_download": "Download",
"control_bottom_app_bar_edit": "Edit",
"control_bottom_app_bar_edit_location": "Edit Location",
"control_bottom_app_bar_edit_time": "Edit Date & Time",
"control_bottom_app_bar_favorite": "Favorite",
"control_bottom_app_bar_share": "Share",
"control_bottom_app_bar_share_to": "Share To",
"control_bottom_app_bar_stack": "Stack",
"control_bottom_app_bar_trash_from_immich": "Move to Trash",
"control_bottom_app_bar_unarchive": "Unarchive",
"control_bottom_app_bar_unfavorite": "Unfavorite",
"control_bottom_app_bar_upload": "Upload",
"create_album": "Create album",
"create_album_page_untitled": "Untitled",
"create_new": "CREATE NEW",
"create_shared_album_page_create": "Create",
"create_shared_album_page_share": "Share",
"create_shared_album_page_share_add_assets": "ADD ASSETS",
"create_shared_album_page_share_select_photos": "Select Photos",
"crop": "Crop",
"curated_location_page_title": "Places",
"curated_object_page_title": "Things",
"current_server_address": "Current server address",
"daily_title_text_date": "E, MMM dd",
"daily_title_text_date_year": "E, MMM dd, yyyy",
"date_format": "E, LLL d, y • h:mm a",
"delete_dialog_alert": "These items will be permanently deleted from Immich and from your device",
"delete_dialog_alert_local": "These items will be permanently removed from your device but still be available on the Immich server",
"delete_dialog_alert_local_non_backed_up": "Some of the items aren't backed up to Immich and will be permanently removed from your device",
"delete_dialog_alert_remote": "These items will be permanently deleted from the Immich server",
"delete_dialog_cancel": "Cancel",
"delete_dialog_ok": "Delete",
"delete_dialog_ok_force": "Delete Anyway",
"delete_dialog_title": "Delete Permanently",
"delete_local_dialog_ok_backed_up_only": "Delete Backed Up Only",
"delete_local_dialog_ok_force": "Delete Anyway",
"delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?",
"delete_shared_link_dialog_title": "Delete Shared Link",
"description_input_hint_text": "Add description...",
"description_input_submit_error": "Error updating description, check the log for more details",
"download_canceled": "Download canceled",
"download_complete": "Download complete",
"download_enqueue": "Download enqueued",
"download_error": "Download Error",
"download_failed": "Download failed",
"download_filename": "file: {}",
"download_finished": "Download finished",
"downloading": "Downloading...",
"downloading_media": "Downloading media",
"download_notfound": "Download not found",
"download_paused": "Download paused",
"download_started": "Download started",
"download_sucess": "Download success",
"download_sucess_android": "The media has been downloaded to DCIM/Immich",
"download_waiting_to_retry": "Waiting to retry",
"edit_date_time_dialog_date_time": "Date and Time",
"edit_date_time_dialog_timezone": "Timezone",
"edit_image_title": "Edit",
"edit_location_dialog_title": "Location",
"end_date": "End date",
"enqueued": "Enqueued",
"enter_wifi_name": "Enter WiFi name",
"error_change_sort_album": "Failed to change album sort order",
"error_saving_image": "Error: {}",
"exif_bottom_sheet_description": "Add Description...",
"exif_bottom_sheet_details": "DETAILS",
"exif_bottom_sheet_location": "LOCATION",
"exif_bottom_sheet_location_add": "Add a location",
"exif_bottom_sheet_people": "PEOPLE",
"exif_bottom_sheet_person_add_person": "Add name",
"experimental_settings_new_asset_list_subtitle": "Work in progress",
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
"experimental_settings_subtitle": "Use at your own risk!",
"experimental_settings_title": "Experimental",
"external_network": "External network",
"external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom",
"failed": "Failed",
"favorites": "Favorites",
"favorites_page_no_favorites": "No favorite assets found",
"favorites_page_title": "Favorites",
"filename_search": "File name or extension",
"filter": "Filter",
"get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network",
"grant_permission": "Grant permission",
"haptic_feedback_switch": "Enable haptic feedback",
"haptic_feedback_title": "Haptic Feedback",
"header_settings_add_header_tip": "Add Header",
"header_settings_field_validator_msg": "Value cannot be empty",
"header_settings_header_name_input": "Header name",
"header_settings_header_value_input": "Header value",
"header_settings_page_title": "Proxy Headers (EXPERIMENTAL)",
"headers_settings_tile_subtitle": "Define proxy headers the app should send with each network request",
"headers_settings_tile_title": "Custom proxy headers",
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
"home_page_building_timeline": "Building the timeline",
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
"home_page_delete_remote_err_local": "Local assets in delete remote selection, skipping",
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
"home_page_share_err_local": "Can not share local assets via link, skipping",
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
"ignore_icloud_photos": "Ignore iCloud photos",
"ignore_icloud_photos_description": "Photos that are stored on iCloud will not be uploaded to the Immich server",
"image_saved_successfully": "Image saved",
"image_viewer_page_state_provider_download_error": "Download Error",
"image_viewer_page_state_provider_download_started": "Download Started",
"image_viewer_page_state_provider_download_success": "Download Success",
"image_viewer_page_state_provider_share_error": "Share Error",
"invalid_date": "Invalid date",
"invalid_date_format": "Invalid date format",
"library": "Library",
"library_page_albums": "Albums",
"library_page_archive": "Archive",
"library_page_device_albums": "Albums on Device",
"library_page_favorites": "Favorites",
"library_page_new_album": "New album",
"library_page_sharing": "Sharing",
"library_page_sort_asset_count": "Number of assets",
"library_page_sort_created": "Created date",
"library_page_sort_last_modified": "Last modified",
"library_page_sort_most_oldest_photo": "Oldest photo",
"library_page_sort_most_recent_photo": "Most recent photo",
"library_page_sort_title": "Album title",
"local_network": "Local network",
"local_network_sheet_info": "The app will connect to the server through this URL when using the specified Wi-Fi network",
"location_permission": "Location permission",
"location_permission_content": "In order to use the auto-switching feature, Immich needs precise location permission so it can read the current WiFi network's name",
"location_picker_choose_on_map": "Choose on map",
"location_picker_latitude": "Latitude",
"location_picker_latitude_error": "Enter a valid latitude",
"location_picker_latitude_hint": "Enter your latitude here",
"location_picker_longitude": "Longitude",
"location_picker_longitude_error": "Enter a valid longitude",
"location_picker_longitude_hint": "Enter your longitude here",
"login_disabled": "Login has been disabled",
"login_form_api_exception": "API exception. Please check the server URL and try again.",
"login_form_back_button_text": "Back",
"login_form_button_text": "Login",
"login_form_email_hint": "youremail@email.com",
"login_form_endpoint_hint": "http://your-server-ip:port",
"login_form_endpoint_url": "Server Endpoint URL",
"login_form_err_http": "Please specify http:// or https://",
"login_form_err_invalid_email": "Invalid Email",
"login_form_err_invalid_url": "Invalid URL",
"login_form_err_leading_whitespace": "Leading whitespace",
"login_form_err_trailing_whitespace": "Trailing whitespace",
"login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
"login_form_failed_login": "Error logging you in, check server URL, email and password",
"login_form_handshake_exception": "There was an Handshake Exception with the server. Enable self-signed certificate support in the settings if you are using a self-signed certificate.",
"login_form_label_email": "Email",
"login_form_label_password": "Password",
"login_form_next_button": "Next",
"login_form_password_hint": "password",
"login_form_save_login": "Stay logged in",
"login_form_server_empty": "Enter a server URL.",
"login_form_server_error": "Could not connect to server.",
"login_password_changed_error": "There was an error updating your password",
"login_password_changed_success": "Password updated successfully",
"map_assets_in_bound": "{} photo",
"map_assets_in_bounds": "{} photos",
"map_cannot_get_user_location": "Cannot get user's location",
"map_location_dialog_cancel": "Cancel",
"map_location_dialog_yes": "Yes",
"map_location_picker_page_use_location": "Use this location",
"map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?",
"map_location_service_disabled_title": "Location Service disabled",
"map_no_assets_in_bounds": "No photos in this area",
"map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?",
"map_no_location_permission_title": "Location Permission denied",
"map_settings_dark_mode": "Dark mode",
"map_settings_date_range_option_all": "All",
"map_settings_date_range_option_day": "Past 24 hours",
"map_settings_date_range_option_days": "Past {} days",
"map_settings_date_range_option_year": "Past year",
"map_settings_date_range_option_years": "Past {} years",
"map_settings_dialog_cancel": "Cancel",
"map_settings_dialog_save": "Save",
"map_settings_dialog_title": "Map Settings",
"map_settings_include_show_archived": "Include Archived",
"map_settings_include_show_partners": "Include Partners",
"map_settings_only_relative_range": "Date range",
"map_settings_only_show_favorites": "Show Favorite Only",
"map_settings_theme_settings": "Map Theme",
"map_zoom_to_see_photos": "Zoom out to see photos",
"memories_all_caught_up": "All caught up",
"memories_check_back_tomorrow": "Check back tomorrow for more memories",
"memories_start_over": "Start Over",
"memories_swipe_to_close": "Swipe up to close",
"memories_year_ago": "A year ago",
"memories_years_ago": "{} years ago",
"monthly_title_text_date_format": "MMMM y",
"motion_photos_page_title": "Motion Photos",
"multiselect_grid_edit_date_time_err_read_only": "Cannot edit date of read only asset(s), skipping",
"multiselect_grid_edit_gps_err_read_only": "Cannot edit location of read only asset(s), skipping",
"my_albums": "My albums",
"networking_settings": "Networking",
"networking_subtitle": "Manage the server endpoint settings",
"no_assets_to_show": "No assets to show",
"no_name": "No name",
"notification_permission_dialog_cancel": "Cancel",
"notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.",
"notification_permission_dialog_settings": "Settings",
"notification_permission_list_tile_content": "Grant permission to enable notifications.",
"notification_permission_list_tile_enable_button": "Enable Notifications",
"notification_permission_list_tile_title": "Notification Permission",
"not_selected": "Not selected",
"on_this_device": "On this device",
"partner_list_user_photos": "{user}'s photos",
"partner_list_view_all": "View all",
"partner_page_add_partner": "Add partner",
"partner_page_empty_message": "Your photos are not yet shared with any partner.",
"partner_page_no_more_users": "No more users to add",
"partner_page_partner_add_failed": "Failed to add partner",
"partner_page_select_partner": "Select partner",
"partner_page_shared_to_title": "Shared to",
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
"partner_page_stop_sharing_title": "Stop sharing your photos?",
"partner_page_title": "Partner",
"partners": "Partners",
"paused": "Paused",
"people": "People",
"permission_onboarding_back": "Back",
"permission_onboarding_continue_anyway": "Continue anyway",
"permission_onboarding_get_started": "Get started",
"permission_onboarding_go_to_settings": "Go to settings",
"permission_onboarding_grant_permission": "Grant permission",
"permission_onboarding_log_out": "Log out",
"permission_onboarding_permission_denied": "Permission denied. To use Immich, grant photo and video permissions in Settings.",
"permission_onboarding_permission_granted": "Permission granted! You are all set.",
"permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.",
"permission_onboarding_request": "Immich requires permission to view your photos and videos.",
"places": "Places",
"preferences_settings_subtitle": "Manage the app's preferences",
"preferences_settings_title": "Preferences",
"profile_drawer_app_logs": "Logs",
"profile_drawer_client_out_of_date_major": "Mobile App is out of date. Please update to the latest major version.",
"profile_drawer_client_out_of_date_minor": "Mobile App is out of date. Please update to the latest minor version.",
"profile_drawer_client_server_up_to_date": "Client and Server are up-to-date",
"profile_drawer_documentation": "Documentation",
"profile_drawer_github": "GitHub",
"profile_drawer_server_out_of_date_major": "Server is out of date. Please update to the latest major version.",
"profile_drawer_server_out_of_date_minor": "Server is out of date. Please update to the latest minor version.",
"profile_drawer_settings": "Settings",
"profile_drawer_sign_out": "Sign Out",
"profile_drawer_trash": "Trash",
"recently_added": "Recently added",
"recently_added_page_title": "Recently Added",
"save": "Save",
"save_to_gallery": "Save to gallery",
"scaffold_body_error_occurred": "Error occurred",
"search_albums": "Search albums",
"search_bar_hint": "Search your photos",
"search_filter_apply": "Apply filter",
"search_filter_camera": "Camera",
"search_filter_camera_make": "Make",
"search_filter_camera_model": "Model",
"search_filter_camera_title": "Select camera type",
"search_filter_date": "Date",
"search_filter_date_interval": "{start} to {end}",
"search_filter_date_title": "Select a date range",
"search_filter_display_option_archive": "Archive",
"search_filter_display_option_favorite": "Favorite",
"search_filter_display_option_not_in_album": "Not in album",
"search_filter_display_options": "Display Options",
"search_filter_display_options_title": "Display options",
"search_filter_location": "Location",
"search_filter_location_city": "City",
"search_filter_location_country": "Country",
"search_filter_location_state": "State",
"search_filter_location_title": "Select location",
"search_filter_media_type": "Media Type",
"search_filter_media_type_all": "All",
"search_filter_media_type_image": "Image",
"search_filter_media_type_title": "Select media type",
"search_filter_media_type_video": "Video",
"search_filter_people": "People",
"search_filter_people_title": "Select people",
"search_page_categories": "Categories",
"search_page_favorites": "Favorites",
"search_page_motion_photos": "Motion Photos",
"search_page_no_objects": "No Objects Info Available",
"search_page_no_places": "No Places Info Available",
"search_page_people": "People",
"search_page_person_add_name_dialog_cancel": "Cancel",
"search_page_person_add_name_dialog_hint": "Name",
"search_page_person_add_name_dialog_save": "Save",
"search_page_person_add_name_dialog_title": "Add a name",
"search_page_person_add_name_subtitle": "Find them fast by name with search",
"search_page_person_add_name_title": "Add a name",
"search_page_person_edit_name": "Edit name",
"search_page_places": "Places",
"search_page_recently_added": "Recently added",
"search_page_screenshots": "Screenshots",
"search_page_search_photos_videos": "Search for your photos and videos",
"search_page_selfies": "Selfies",
"search_page_things": "Things",
"search_page_videos": "Videos",
"search_page_view_all_button": "View all",
"search_page_your_activity": "Your activity",
"search_page_your_map": "Your Map",
"search_result_page_new_search_hint": "New Search",
"search_suggestion_list_smart_search_hint_1": "Smart search is enabled by default, to search for metadata use the syntax ",
"search_suggestion_list_smart_search_hint_2": "m:your-search-term",
"select_additional_user_for_sharing_page_suggestions": "Suggestions",
"select_user_for_sharing_page_err_album": "Failed to create album",
"select_user_for_sharing_page_share_suggestions": "Suggestions",
"server_endpoint": "Server Endpoint",
"server_info_box_app_version": "App Version",
"server_info_box_latest_release": "Latest Version",
"server_info_box_server_url": "Server URL",
"server_info_box_server_version": "Server Version",
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
"setting_image_viewer_original_title": "Load original image",
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
"setting_image_viewer_preview_title": "Load preview image",
"setting_image_viewer_title": "Images",
"setting_languages_apply": "Apply",
"setting_languages_subtitle": "Change the app's language",
"setting_languages_title": "Languages",
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
"setting_notifications_notify_hours": "{} hours",
"setting_notifications_notify_immediately": "immediately",
"setting_notifications_notify_minutes": "{} minutes",
"setting_notifications_notify_never": "never",
"setting_notifications_notify_seconds": "{} seconds",
"setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset",
"setting_notifications_single_progress_title": "Show background backup detail progress",
"setting_notifications_subtitle": "Adjust your notification preferences",
"setting_notifications_title": "Notifications",
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
"setting_notifications_total_progress_title": "Show background backup total progress",
"setting_pages_app_bar_settings": "Settings",
"settings_require_restart": "Please restart Immich to apply this setting",
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
"setting_video_viewer_looping_title": "Looping",
"setting_video_viewer_original_video_subtitle": "When streaming a video from the server, play the original even when a transcode is available. May lead to buffering. Videos available locally are played in original quality regardless of this setting.",
"setting_video_viewer_original_video_title": "Force original video",
"setting_video_viewer_title": "Videos",
"share_add": "Add",
"share_add_photos": "Add photos",
"share_add_title": "Add a title",
"share_assets_selected": "{} selected",
"share_create_album": "Create album",
"shared_album_activities_input_disable": "Comment is disabled",
"shared_album_activities_input_hint": "Say something",
"shared_album_activity_remove_content": "Do you want to delete this activity?",
"shared_album_activity_remove_title": "Delete Activity",
"shared_album_activity_setting_subtitle": "Let others respond",
"shared_album_activity_setting_title": "Comments & likes",
"shared_album_section_people_action_error": "Error leaving/removing from album",
"shared_album_section_people_action_leave": "Remove user from album",
"shared_album_section_people_action_remove_user": "Remove user from album",
"shared_album_section_people_owner_label": "Owner",
"shared_album_section_people_title": "PEOPLE",
"share_dialog_preparing": "Preparing...",
"shared_intent_upload_button_progress_text": "{} / {} Uploaded",
"shared_link_app_bar_title": "Shared Links",
"shared_link_clipboard_copied_massage": "Copied to clipboard",
"shared_link_clipboard_text": "Link: {}\nPassword: {}",
"shared_link_create_app_bar_title": "Create link to share",
"shared_link_create_error": "Error while creating shared link",
"shared_link_create_info": "Let anyone with the link see the selected photo(s)",
"shared_link_create_submit_button": "Create link",
"shared_link_edit_allow_download": "Allow public user to download",
"shared_link_edit_allow_upload": "Allow public user to upload",
"shared_link_edit_app_bar_title": "Edit link",
"shared_link_edit_change_expiry": "Change expiration time",
"shared_link_edit_description": "Description",
"shared_link_edit_description_hint": "Enter the share description",
"shared_link_edit_expire_after": "Expire after",
"shared_link_edit_expire_after_option_day": "1 day",
"shared_link_edit_expire_after_option_days": "{} days",
"shared_link_edit_expire_after_option_hour": "1 hour",
"shared_link_edit_expire_after_option_hours": "{} hours",
"shared_link_edit_expire_after_option_minute": "1 minute",
"shared_link_edit_expire_after_option_minutes": "{} minutes",
"shared_link_edit_expire_after_option_months": "{} months",
"shared_link_edit_expire_after_option_never": "Never",
"shared_link_edit_expire_after_option_year": "{} year",
"shared_link_edit_password": "Password",
"shared_link_edit_password_hint": "Enter the share password",
"shared_link_edit_show_meta": "Show metadata",
"shared_link_edit_submit_button": "Update link",
"shared_link_empty": "You don't have any shared links",
"shared_link_error_server_url_fetch": "Cannot fetch the server url",
"shared_link_expired": "Expired",
"shared_link_expires_day": "Expires in {} day",
"shared_link_expires_days": "Expires in {} days",
"shared_link_expires_hour": "Expires in {} hour",
"shared_link_expires_hours": "Expires in {} hours",
"shared_link_expires_minute": "Expires in {} minute",
"shared_link_expires_minutes": "Expires in {} minutes",
"shared_link_expires_never": "Expires ∞",
"shared_link_expires_second": "Expires in {} second",
"shared_link_expires_seconds": "Expires in {} seconds",
"shared_link_individual_shared": "Individual shared",
"shared_link_info_chip_download": "Download",
"shared_link_info_chip_metadata": "EXIF",
"shared_link_info_chip_upload": "Upload",
"shared_link_manage_links": "Manage Shared links",
"shared_link_public_album": "Public album",
"shared_links": "Shared links",
"share_done": "Done",
"shared_with_me": "Shared with me",
"share_invite": "Invite to album",
"sharing_page_album": "Shared albums",
"sharing_page_description": "Create shared albums to share photos and videos with people in your network.",
"sharing_page_empty_list": "EMPTY LIST",
"sharing_silver_appbar_create_shared_album": "New shared album",
"sharing_silver_appbar_shared_links": "Shared links",
"sharing_silver_appbar_share_partner": "Share with partner",
"start_date": "Start date",
"sync": "Sync",
"sync_albums": "Sync albums",
"sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums",
"sync_upload_album_setting_subtitle": "Create and upload your photos and videos to the selected albums on Immich",
"tab_controller_nav_library": "Library",
"tab_controller_nav_photos": "Photos",
"tab_controller_nav_search": "Search",
"tab_controller_nav_sharing": "Sharing",
"theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles",
"theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})",
"theme_setting_colorful_interface_subtitle": "Apply primary color to background surfaces.",
"theme_setting_colorful_interface_title": "Colorful interface",
"theme_setting_dark_mode_switch": "Dark mode",
"theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer",
"theme_setting_image_viewer_quality_title": "Image viewer quality",
"theme_setting_primary_color_subtitle": "Pick a color for primary actions and accents.",
"theme_setting_primary_color_title": "Primary color",
"theme_setting_system_primary_color_title": "Use system color",
"theme_setting_system_theme_switch": "Automatic (Follow system setting)",
"theme_setting_theme_subtitle": "Choose the app's theme setting",
"theme_setting_theme_title": "Theme",
"theme_setting_three_stage_loading_subtitle": "Three-stage loading might increase the loading performance but causes significantly higher network load",
"theme_setting_three_stage_loading_title": "Enable three-stage loading",
"translated_text_options": "Options",
"trash": "Trash",
"trash_emptied": "Emptied trash",
"trash_page_delete": "Delete",
"trash_page_delete_all": "Delete All",
"trash_page_empty_trash_btn": "Empty trash",
"trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich",
"trash_page_empty_trash_dialog_ok": "Ok",
"trash_page_info": "Trashed items will be permanently deleted after {} days",
"trash_page_no_assets": "No trashed assets",
"trash_page_restore": "Restore",
"trash_page_restore_all": "Restore All",
"trash_page_select_assets_btn": "Select assets",
"trash_page_select_btn": "Select",
"trash_page_title": "Trash ({})",
"upload": "Upload",
"upload_dialog_cancel": "Cancel",
"upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
"upload_dialog_ok": "Upload",
"upload_dialog_title": "Upload Asset",
"uploading": "Uploading",
"upload_to_immich": "Upload to Immich ({})",
"use_current_connection": "use current connection",
"validate_endpoint_error": "Please enter a valid URL",
"version_announcement_overlay_ack": "Acknowledge",
"version_announcement_overlay_release_notes": "release notes",
"version_announcement_overlay_text_1": "Hi friend, there is a new release of",
"version_announcement_overlay_text_2": "please take your time to visit the ",
"version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.",
"version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89",
"videos": "Videos",
"viewer_remove_from_stack": "Remove from Stack",
"viewer_stack_use_as_main_asset": "Use as Main Asset",
"viewer_unstack": "Un-Stack",
"wifi_name": "WiFi Name",
"your_wifi_name": "Your WiFi name"
}

View File

@@ -19,7 +19,7 @@ platform :ios do
desc "iOS Release"
lane :release do
increment_version_number(
version_number: "1.125.3"
version_number: "1.125.1"
)
increment_build_number(
build_number: latest_testflight_build_number + 1,

View File

@@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/duration_extensions.dart';
import 'package:immich_mobile/widgets/common/dropdown_search_menu.dart';
import 'package:timezone/timezone.dart' as tz;
import 'package:timezone/timezone.dart';
@@ -25,7 +24,7 @@ Future<String?> showDateTimePicker({
}
String _getFormattedOffset(int offsetInMilli, tz.Location location) {
return "${location.name} (${Duration(milliseconds: offsetInMilli).formatAsOffset()})";
return "${location.name} (UTC${Duration(milliseconds: offsetInMilli).formatAsOffset()})";
}
class _DateTimePicker extends HookWidget {
@@ -74,6 +73,7 @@ class _DateTimePicker extends HookWidget {
// returns a list of location<name> along with it's offset in duration
List<_TimeZoneOffset> getAllTimeZones() {
return tz.timeZoneDatabase.locations.values
.where((l) => !l.currentTimeZone.abbreviation.contains("0"))
.map(_TimeZoneOffset.fromLocation)
.sorted()
.toList();
@@ -133,78 +133,83 @@ class _DateTimePicker extends HookWidget {
context.pop(dtWithOffset);
}
return AlertDialog(
contentPadding: const EdgeInsets.symmetric(vertical: 32, horizontal: 18),
actions: [
TextButton(
onPressed: () => context.pop(),
child: Text(
"action_common_cancel",
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
color: context.colorScheme.error,
),
).tr(),
),
TextButton(
onPressed: popWithDateTime,
child: Text(
"action_common_update",
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
color: context.primaryColor,
),
).tr(),
),
],
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"edit_date_time_dialog_date_time",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
).tr(),
const SizedBox(height: 32),
ListTile(
tileColor: context.colorScheme.surfaceContainerHighest,
shape: ShapeBorder.lerp(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
return LayoutBuilder(
builder: (context, constraint) => AlertDialog(
contentPadding:
const EdgeInsets.symmetric(vertical: 32, horizontal: 18),
actions: [
TextButton(
onPressed: () => context.pop(),
child: Text(
"action_common_cancel",
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
color: context.colorScheme.error,
),
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
1,
),
trailing: Icon(
Icons.edit_outlined,
size: 18,
color: context.primaryColor,
),
title: Text(
DateFormat("dd-MM-yyyy hh:mm a").format(date.value),
style: context.textTheme.bodyMedium,
).tr(),
onTap: pickDate,
),
const SizedBox(height: 24),
DropdownSearchMenu(
trailingIcon: Icon(
Icons.arrow_drop_down,
color: context.primaryColor,
),
hintText: "edit_date_time_dialog_timezone".tr(),
label: const Text('edit_date_time_dialog_timezone').tr(),
textStyle: context.textTheme.bodyMedium,
onSelected: (value) => tzOffset.value = value,
initialSelection: tzOffset.value,
dropdownMenuEntries: menuEntries,
TextButton(
onPressed: popWithDateTime,
child: Text(
"action_common_update",
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
color: context.primaryColor,
),
).tr(),
),
],
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"edit_date_time_dialog_date_time",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
).tr(),
const SizedBox(height: 32),
ListTile(
tileColor: context.colorScheme.surfaceContainerHighest,
shape: ShapeBorder.lerp(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
1,
),
trailing: Icon(
Icons.edit_outlined,
size: 18,
color: context.primaryColor,
),
title: Text(
DateFormat("dd-MM-yyyy hh:mm a").format(date.value),
style: context.textTheme.bodyMedium,
).tr(),
onTap: pickDate,
),
const SizedBox(height: 24),
DropdownMenu(
width: 275,
menuHeight: 300,
trailingIcon: Icon(
Icons.arrow_drop_down,
color: context.primaryColor,
),
hintText: "edit_date_time_dialog_timezone".tr(),
label: const Text('edit_date_time_dialog_timezone').tr(),
textStyle: context.textTheme.bodyMedium,
onSelected: (value) => tzOffset.value = value!,
initialSelection: tzOffset.value,
dropdownMenuEntries: menuEntries,
),
],
),
),
);
}

View File

@@ -1,169 +0,0 @@
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
class DropdownSearchMenu<T> extends HookWidget {
const DropdownSearchMenu({
super.key,
required this.dropdownMenuEntries,
this.initialSelection,
this.onSelected,
this.trailingIcon,
this.hintText,
this.label,
this.textStyle,
this.menuConstraints,
});
final List<DropdownMenuEntry<T>> dropdownMenuEntries;
final T? initialSelection;
final ValueChanged<T>? onSelected;
final Widget? trailingIcon;
final String? hintText;
final Widget? label;
final TextStyle? textStyle;
final BoxConstraints? menuConstraints;
@override
Widget build(BuildContext context) {
final selectedItem = useState<DropdownMenuEntry<T>?>(
dropdownMenuEntries
.firstWhereOrNull((item) => item.value == initialSelection),
);
final showTimeZoneDropdown = useState<bool>(false);
final effectiveConstraints = menuConstraints ??
const BoxConstraints(
minWidth: 280,
maxWidth: 280,
minHeight: 0,
maxHeight: 280,
);
final inputDecoration = InputDecoration(
contentPadding: const EdgeInsets.fromLTRB(12, 4, 12, 4),
border: const OutlineInputBorder(),
suffixIcon: trailingIcon,
label: label,
hintText: hintText,
).applyDefaults(context.themeData.inputDecorationTheme);
if (!showTimeZoneDropdown.value) {
return ConstrainedBox(
constraints: effectiveConstraints,
child: GestureDetector(
onTap: () => showTimeZoneDropdown.value = true,
child: InputDecorator(
decoration: inputDecoration,
child: selectedItem.value != null
? Text(
selectedItem.value!.label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: textStyle,
)
: null,
),
),
);
}
return ConstrainedBox(
constraints: effectiveConstraints,
child: Autocomplete<DropdownMenuEntry<T>>(
displayStringForOption: (option) => option.label,
optionsBuilder: (textEditingValue) {
return dropdownMenuEntries.where(
(item) => item.label
.toLowerCase()
.trim()
.contains(textEditingValue.text.toLowerCase().trim()),
);
},
onSelected: (option) {
selectedItem.value = option;
showTimeZoneDropdown.value = false;
onSelected?.call(option.value);
},
fieldViewBuilder: (context, textEditingController, focusNode, _) {
return TextField(
autofocus: true,
focusNode: focusNode,
controller: textEditingController,
decoration: inputDecoration.copyWith(
hintText: "edit_date_time_dialog_search_timezone".tr(),
),
maxLines: 1,
style: context.textTheme.bodyMedium,
expands: false,
onTapOutside: (event) {
showTimeZoneDropdown.value = false;
focusNode.unfocus();
},
onSubmitted: (_) {
showTimeZoneDropdown.value = false;
},
);
},
optionsViewBuilder: (context, onSelected, options) {
// This widget is a copy of the default implementation.
// We have only changed the `constraints` parameter.
return Align(
alignment: Alignment.topLeft,
child: ConstrainedBox(
constraints: effectiveConstraints,
child: Material(
elevation: 4.0,
child: ListView.builder(
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: options.length,
itemBuilder: (BuildContext context, int index) {
final option = options.elementAt(index);
return InkWell(
onTap: () => onSelected(option),
child: Builder(
builder: (BuildContext context) {
final bool highlight =
AutocompleteHighlightedOption.of(context) ==
index;
if (highlight) {
SchedulerBinding.instance.addPostFrameCallback(
(Duration timeStamp) {
Scrollable.ensureVisible(
context,
alignment: 0.5,
);
},
debugLabel: 'AutocompleteOptions.ensureVisible',
);
}
return Container(
color: highlight
? Theme.of(context)
.colorScheme
.onSurface
.withOpacity(0.12)
: null,
padding: const EdgeInsets.all(16.0),
child: Text(
option.label,
style: textStyle,
),
);
},
),
);
},
),
),
),
);
},
),
);
}
}

View File

@@ -28,7 +28,8 @@ class UserCircleAvatar extends ConsumerWidget {
final profileImageUrl =
'${Store.get(StoreKey.serverEndpoint)}/users/${user.id}/profile-image?d=${Random().nextInt(1024)}';
final textIcon = DefaultTextStyle(
final textIcon = Text(
user.name[0].toUpperCase(),
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
@@ -36,7 +37,6 @@ class UserCircleAvatar extends ConsumerWidget {
? Colors.black
: Colors.white,
),
child: Text(user.name[0].toUpperCase()),
);
return CircleAvatar(
backgroundColor: user.avatarColor.toColor(),

View File

@@ -3,7 +3,7 @@ Immich API
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 1.125.3
- API version: 1.125.1
- Generator version: 7.8.0
- Build package: org.openapitools.codegen.languages.DartClientCodegen

View File

@@ -2,7 +2,7 @@ name: immich_mobile
description: Immich - selfhosted backup media file on mobile phone
publish_to: 'none'
version: 1.125.3+179
version: 1.125.1+177
environment:
sdk: '>=3.3.0 <4.0.0'

View File

@@ -7454,7 +7454,7 @@
"info": {
"title": "Immich",
"description": "Immich API",
"version": "1.125.3",
"version": "1.125.1",
"contact": {}
},
"tags": [],

View File

@@ -1,12 +1,12 @@
{
"name": "@immich/sdk",
"version": "1.125.3",
"version": "1.125.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@immich/sdk",
"version": "1.125.3",
"version": "1.125.1",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@oazapfts/runtime": "^1.0.2"

View File

@@ -1,6 +1,6 @@
{
"name": "@immich/sdk",
"version": "1.125.3",
"version": "1.125.1",
"description": "Auto-generated TypeScript SDK for the Immich API",
"type": "module",
"main": "./build/index.js",

View File

@@ -1,6 +1,6 @@
/**
* Immich
* 1.125.3
* 1.125.1
* DO NOT MODIFY - This file has been generated using oazapfts.
* See https://www.npmjs.com/package/oazapfts
*/

View File

@@ -40,12 +40,12 @@
<a href="README_vi_VN.md">Tiếng Việt</a>
</p>
## ข้อควรระวัง
## ข้อจำกัดความรับผิดชอบ
- ⚠️ โพรเจกต์นี้กำลังอยู่ระหว่างการพัฒนา**มีการเปลี่ยนแปลงบ่อยมาก**
- ⚠️ อาจจะเกิดข้อผิดพลาดและการเปลี่ยนแปลงที่ส่งผลเสีย
- ⚠️ **ห้ามใช้ระบบนี้เป็นวิธีการเดียวในการจัดเก็บภาพถ่ายและวิดีโอของคุณ**
- ⚠️ ปฏิบัติตามแผนการสำรองข้อมูลแบบ [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) สำหรับภาพถ่ายและวิดีโอที่สำคัญของคุณอยู่เสมอ
- ⚠️ โพรเจกต์นี้กำลังอยู่ระหว่างการพัฒนา**ที่มีการเปลี่ยนแปลงบ่อยมาก**
- ⚠️ คาดว่าจะมีข้อผิดพลาดและการเปลี่ยนแปลงที่ส่งผลเสีย
- ⚠️ **ห้ามใช้แอปนี้เป็นวิธีการเดียวในการจัดเก็บภาพถ่ายและวิดีโอของคุณ**
- ⚠️ ปฏิบัติตามแผนการสำรองข้อมูลแบบ [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) สำหรับภาพถ่ายและวิดีโอที่สำคัญของคุณอยู่เสมอ!
> [!NOTE]
> คุณสามารถหาคู่มือหลัก รวมถึงคู่มือการติดตั้ง ได้ที่ https://immich.app/
@@ -79,15 +79,15 @@
| :----------------------------------------- | ------ | ------ |
| อัปโหลดและดูวิดีโอและภาพถ่าย | ใช่ | ใช่ |
| การสำรองข้อมูลอัตโนมัติเมื่อเปิดแอป | ใช่ | N/A |
| ป้องกันการซ้ำของไฟล์ | ใช่ | ใช่ |
| ป้องกันการซ้ำซ้อนของไฟล์ | ใช่ | ใช่ |
| เลือกอัลบั้มสำหรับสำรองข้อมูล | ใช่ | N/A |
| ดาวน์โหลดภาพถ่ายและวิดีโอไปยังอุปกรณ์ | ใช่ | ใช่ |
| รองรับผู้ใช้หลายคน | ใช่ | ใช่ |
| อัลบั้มและอัลบั้มแชร์ | ใช่ | ใช่ |
| แถบเลื่อนแบบลากได้ | ใช่ | ใช่ |
| รองรับรูปแบบไฟล์ RAW | ใช่ | ใช่ |
| ดูข้อมูลเมตาดาต้า (EXIF, แผนที่) | ใช่ | ใช่ |
| ค้นหาจากข้อมูลเมตาดาต้า วัตถุ ใบหน้า และ CLIP | ใช่ | ใช่ |
| ดูข้อมูลเมตา (EXIF, แผนที่) | ใช่ | ใช่ |
| ค้นหาจากข้อมูลเมตา วัตถุ ใบหน้า และ CLIP | ใช่ | ใช่ |
| ฟังก์ชันการจัดการผู้ดูแลระบบ | ไม่ใช่ | ใช่ |
| การสำรองข้อมูลพื้นหลัง | ใช่ | N/A |
| การเลื่อนแบบเสมือน | ใช่ | ใช่ |
@@ -100,7 +100,7 @@
| การจัดเก็บและรายการโปรด | ใช่ | ใช่ |
| แผนที่ทั่วโลก | ใช่ | ใช่ |
| การแชร์กับคู่หู | ใช่ | ใช่ |
| ระบบจดจำใบหน้าและการจัดกลุ่ม | ใช่ | ใช่ |
| การจดจำใบหน้าและการจัดกลุ่ม | ใช่ | ใช่ |
| ความทรงจำ (x ปีที่แล้ว) | ใช่ | ใช่ |
| รองรับแบบออฟไลน์ | ใช่ | ไม่ใช่ |
| แกลเลอรีแบบอ่านอย่างเดียว | ใช่ | ใช่ |
@@ -108,13 +108,13 @@
## การแปลภาษา
อ่านเพิ่มเติมเกี่ยวกับการแปล [ที่นี่](https://immich.app/docs/developer/translations)
อ่านเพิ่มเติมเกี่ยวกับการแปลภาษา [ที่นี่](https://immich.app/docs/developer/translations)
<a href="https://hosted.weblate.org/engage/immich/">
<img src="https://hosted.weblate.org/widget/immich/immich/multi-auto.svg" alt="สถานะการแปล" />
</a>
## กิจกรรมของ Repository
## กิจกรรมของคลังเก็บข้อมูล
![กิจกรรม](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "ภาพการวิเคราะห์ของ Repobeats")

View File

@@ -1,12 +1,12 @@
{
"name": "immich",
"version": "1.125.3",
"version": "1.125.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "immich",
"version": "1.125.3",
"version": "1.125.1",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@nestjs/bullmq": "^11.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "immich",
"version": "1.125.3",
"version": "1.125.1",
"description": "",
"author": "",
"private": true,

View File

@@ -3,11 +3,9 @@ import { Inject, Module, OnModuleDestroy, OnModuleInit, ValidationPipe } from '@
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE, ModuleRef } from '@nestjs/core';
import { ScheduleModule, SchedulerRegistry } from '@nestjs/schedule';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PostgresJSDialect } from 'kysely-postgres-js';
import { ClsModule } from 'nestjs-cls';
import { KyselyModule } from 'nestjs-kysely';
import { OpenTelemetryModule } from 'nestjs-otel';
import postgres from 'postgres';
import { commands } from 'src/commands';
import { IWorker } from 'src/constants';
import { controllers } from 'src/controllers';
@@ -59,19 +57,7 @@ const imports = [
},
}),
TypeOrmModule.forFeature(entities),
KyselyModule.forRoot({
dialect: new PostgresJSDialect({ postgres: postgres(database.config.kysely) }),
log(event) {
if (event.level === 'error') {
console.error('Query failed :', {
durationMs: event.queryDurationMillis,
error: event.error,
sql: event.query.sql,
params: event.query.parameters,
});
}
},
}),
KyselyModule.forRoot(database.config.kysely),
];
class BaseModule implements OnModuleInit, OnModuleDestroy {

View File

@@ -4,12 +4,10 @@ import { Reflector } from '@nestjs/core';
import { SchedulerRegistry } from '@nestjs/schedule';
import { Test } from '@nestjs/testing';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PostgresJSDialect } from 'kysely-postgres-js';
import { KyselyModule } from 'nestjs-kysely';
import { OpenTelemetryModule } from 'nestjs-otel';
import { mkdir, rm, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import postgres from 'postgres';
import { format } from 'sql-formatter';
import { GENERATE_SQL_KEY, GenerateSqlQueries } from 'src/decorators';
import { entities } from 'src/entities';
@@ -86,7 +84,7 @@ class SqlGenerator {
const moduleFixture = await Test.createTestingModule({
imports: [
KyselyModule.forRoot({
dialect: new PostgresJSDialect({ postgres: postgres(database.config.kysely) }),
...database.config.kysely,
log: (event) => {
if (event.level === 'query') {
this.sqlLogger.logQuery(event.query.sql);

View File

@@ -9,8 +9,8 @@ export const IAlbumRepository = 'IAlbumRepository';
export interface AlbumAssetCount {
albumId: string;
assetCount: number;
startDate: Date | null;
endDate: Date | null;
startDate: Date | undefined;
endDate: Date | undefined;
}
export interface AlbumInfoOptions {

View File

@@ -13,7 +13,7 @@ if (immichApp) {
let apiProcess: ChildProcess | undefined;
const onError = (name: string, error: Error) => {
console.error(`${name} worker error: ${error}, stack: ${error.stack}`);
console.error(`${name} worker error: ${error}`);
};
const onExit = (name: string, exitCode: number | null) => {

View File

@@ -3,10 +3,10 @@ import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddTimeBucketIndices1734574016301 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE INDEX idx_local_date_time_month ON assets ((date_trunc('MONTH', "localDateTime" at time zone 'UTC') at time zone 'UTC'))`,
`CREATE INDEX idx_local_date_time_month ON public.assets ((date_trunc('MONTH', "localDateTime" at time zone 'UTC') at time zone 'UTC'))`,
);
await queryRunner.query(
`CREATE INDEX idx_local_date_time ON assets ((("localDateTime" at time zone 'UTC')::date))`,
`CREATE INDEX idx_local_date_time ON public.assets ((("localDateTime" at time zone 'UTC')::date))`,
);
await queryRunner.query(`DROP INDEX "IDX_day_of_month"`);
await queryRunner.query(`DROP INDEX "IDX_month"`);

View File

@@ -90,7 +90,7 @@ select
(
select
"assets".*,
"exif" as "exifInfo"
to_json("exif") as "exifInfo"
from
"assets"
inner join "exif" on "assets"."id" = "exif"."assetId"
@@ -98,6 +98,7 @@ select
where
"albums_assets_assets"."albumsId" = "albums"."id"
and "assets"."deletedAt" is null
and "assets"."isArchived" = $1
order by
"assets"."fileCreatedAt" desc
) as "asset"
@@ -105,7 +106,7 @@ select
from
"albums"
where
"albums"."id" = $1
"albums"."id" = $2
and "albums"."deletedAt" is null
-- AlbumRepository.getByAssetId
@@ -180,20 +181,19 @@ select
) as "albumUsers"
from
"albums"
inner join "albums_assets_assets" as "album_assets" on "album_assets"."albumsId" = "albums"."id"
left join "albums_assets_assets" as "album_assets" on "album_assets"."albumsId" = "albums"."id"
left join "albums_shared_users_users" as "album_users" on "album_users"."albumsId" = "albums"."id"
where
(
"albums"."ownerId" = $1
or exists (
select
from
"albums_shared_users_users" as "album_users"
where
"album_users"."albumsId" = "albums"."id"
and "album_users"."usersId" = $2
(
"albums"."ownerId" = $1
and "album_assets"."assetsId" = $2
)
or (
"album_users"."usersId" = $3
and "album_assets"."assetsId" = $4
)
)
and "album_assets"."assetsId" = $3
and "albums"."deletedAt" is null
order by
"albums"."createdAt" desc,
@@ -201,17 +201,16 @@ order by
-- AlbumRepository.getMetadataForIds
select
"albums"."id" as "albumId",
"albums"."id",
min("assets"."fileCreatedAt") as "startDate",
max("assets"."fileCreatedAt") as "endDate",
count("assets"."id")::int as "assetCount"
count("assets"."id") as "assetCount"
from
"albums"
left join "albums_assets_assets" as "album_assets" on "album_assets"."albumsId" = "albums"."id"
left join "assets" on "assets"."id" = "album_assets"."assetsId"
where
"albums"."id" in ($1)
and "assets"."deletedAt" is null
group by
"albums"."id"
@@ -307,8 +306,8 @@ order by
"albums"."createdAt" desc
-- AlbumRepository.getShared
select
"albums".*,
select distinct
on ("albums"."createdAt") "albums".*,
(
select
coalesce(json_agg(agg), '[]')
@@ -391,26 +390,15 @@ select
) as "sharedLinks"
from
"albums"
left join "albums_shared_users_users" as "shared_albums" on "shared_albums"."albumsId" = "albums"."id"
left join "shared_links" on "shared_links"."albumId" = "albums"."id"
where
(
exists (
select
from
"albums_shared_users_users" as "album_users"
where
"album_users"."albumsId" = "albums"."id"
and (
"albums"."ownerId" = $1
or "album_users"."usersId" = $2
)
)
or exists (
select
from
"shared_links"
where
"shared_links"."albumId" = "albums"."id"
and "shared_links"."userId" = $3
"shared_albums"."usersId" = $1
or "shared_links"."userId" = $2
or (
"albums"."ownerId" = $3
and "shared_albums"."usersId" is not null
)
)
and "albums"."deletedAt" is null
@@ -418,8 +406,48 @@ order by
"albums"."createdAt" desc
-- AlbumRepository.getNotShared
select
"albums".*,
select distinct
on ("albums"."createdAt") "albums".*,
(
select
coalesce(json_agg(agg), '[]')
from
(
select
"album_users".*,
(
select
to_json(obj)
from
(
select
"id",
"email",
"createdAt",
"profileImagePath",
"isAdmin",
"shouldChangePassword",
"deletedAt",
"oauthId",
"updatedAt",
"storageLabel",
"name",
"quotaSizeInBytes",
"quotaUsageInBytes",
"status",
"profileChangedAt"
from
"users"
where
"users"."id" = "album_users"."usersId"
) as obj
) as "user"
from
"albums_shared_users_users" as "album_users"
where
"album_users"."albumsId" = "albums"."id"
) as agg
) as "albumUsers",
(
select
to_json(obj)
@@ -446,26 +474,29 @@ select
where
"users"."id" = "albums"."ownerId"
) as obj
) as "owner"
) as "owner",
(
select
coalesce(json_agg(agg), '[]')
from
(
select
*
from
"shared_links"
where
"shared_links"."albumId" = "albums"."id"
) as agg
) as "sharedLinks"
from
"albums"
left join "albums_shared_users_users" as "shared_albums" on "shared_albums"."albumsId" = "albums"."id"
left join "shared_links" on "shared_links"."albumId" = "albums"."id"
where
"albums"."ownerId" = $1
and "shared_albums"."usersId" is null
and "shared_links"."userId" is null
and "albums"."deletedAt" is null
and not exists (
select
from
"albums_shared_users_users" as "album_users"
where
"album_users"."albumsId" = "albums"."id"
)
and not exists (
select
from
"shared_links"
where
"shared_links"."albumId" = "albums"."id"
)
order by
"albums"."createdAt" desc

View File

@@ -36,7 +36,7 @@ offset
and "assets"."deletedAt" is null
and "assets"."id" < $6
order by
random()
"assets"."id"
limit
$7
)
@@ -56,7 +56,7 @@ union all
and "assets"."deletedAt" is null
and "assets"."id" > $13
order by
random()
"assets"."id"
limit
$14
)

View File

@@ -59,10 +59,11 @@ const withAssets = (eb: ExpressionBuilder<DB, 'albums'>) => {
.selectFrom('assets')
.selectAll('assets')
.innerJoin('exif', 'assets.id', 'exif.assetId')
.select((eb) => eb.table('exif').as('exifInfo'))
.select((eb) => eb.fn.toJson('exif').as('exifInfo'))
.innerJoin('albums_assets_assets', 'albums_assets_assets.assetsId', 'assets.id')
.whereRef('albums_assets_assets.albumsId', '=', 'albums.id')
.where('assets.deletedAt', 'is', null)
.where('assets.isArchived', '=', false)
.orderBy('assets.fileCreatedAt', 'desc')
.as('asset'),
)
@@ -93,19 +94,14 @@ export class AlbumRepository implements IAlbumRepository {
return this.db
.selectFrom('albums')
.selectAll('albums')
.innerJoin('albums_assets_assets as album_assets', 'album_assets.albumsId', 'albums.id')
.leftJoin('albums_assets_assets as album_assets', 'album_assets.albumsId', 'albums.id')
.leftJoin('albums_shared_users_users as album_users', 'album_users.albumsId', 'albums.id')
.where((eb) =>
eb.or([
eb('albums.ownerId', '=', ownerId),
eb.exists(
eb
.selectFrom('albums_shared_users_users as album_users')
.whereRef('album_users.albumsId', '=', 'albums.id')
.where('album_users.usersId', '=', ownerId),
),
eb.and([eb('albums.ownerId', '=', ownerId), eb('album_assets.assetsId', '=', assetId)]),
eb.and([eb('album_users.usersId', '=', ownerId), eb('album_assets.assetsId', '=', assetId)]),
]),
)
.where('album_assets.assetsId', '=', assetId)
.where('albums.deletedAt', 'is', null)
.orderBy('albums.createdAt', 'desc')
.select(withOwner)
@@ -122,18 +118,24 @@ export class AlbumRepository implements IAlbumRepository {
return [];
}
return this.db
const metadatas = await this.db
.selectFrom('albums')
.leftJoin('albums_assets_assets as album_assets', 'album_assets.albumsId', 'albums.id')
.leftJoin('assets', 'assets.id', 'album_assets.assetsId')
.select('albums.id as albumId')
.select('albums.id')
.select((eb) => eb.fn.min('assets.fileCreatedAt').as('startDate'))
.select((eb) => eb.fn.max('assets.fileCreatedAt').as('endDate'))
.select((eb) => sql<number>`${eb.fn.count('assets.id')}::int`.as('assetCount'))
.select((eb) => eb.fn.count('assets.id').as('assetCount'))
.where('albums.id', 'in', ids)
.where('assets.deletedAt', 'is', null)
.groupBy('albums.id')
.execute();
return metadatas.map((metadatas) => ({
albumId: metadatas.id,
assetCount: Number(metadatas.assetCount),
startDate: metadatas.startDate ? new Date(metadatas.startDate) : undefined,
endDate: metadatas.endDate ? new Date(metadatas.endDate) : undefined,
}));
}
@GenerateSql({ params: [DummyValue.UUID] })
@@ -158,20 +160,14 @@ export class AlbumRepository implements IAlbumRepository {
return this.db
.selectFrom('albums')
.selectAll('albums')
.distinctOn('albums.createdAt')
.leftJoin('albums_shared_users_users as shared_albums', 'shared_albums.albumsId', 'albums.id')
.leftJoin('shared_links', 'shared_links.albumId', 'albums.id')
.where((eb) =>
eb.or([
eb.exists(
eb
.selectFrom('albums_shared_users_users as album_users')
.whereRef('album_users.albumsId', '=', 'albums.id')
.where((eb) => eb.or([eb('albums.ownerId', '=', ownerId), eb('album_users.usersId', '=', ownerId)])),
),
eb.exists(
eb
.selectFrom('shared_links')
.whereRef('shared_links.albumId', '=', 'albums.id')
.where('shared_links.userId', '=', ownerId),
),
eb('shared_albums.usersId', '=', ownerId),
eb('shared_links.userId', '=', ownerId),
eb.and([eb('albums.ownerId', '=', ownerId), eb('shared_albums.usersId', 'is not', null)]),
]),
)
.where('albums.deletedAt', 'is', null)
@@ -190,21 +186,16 @@ export class AlbumRepository implements IAlbumRepository {
return this.db
.selectFrom('albums')
.selectAll('albums')
.distinctOn('albums.createdAt')
.leftJoin('albums_shared_users_users as shared_albums', 'shared_albums.albumsId', 'albums.id')
.leftJoin('shared_links', 'shared_links.albumId', 'albums.id')
.where('albums.ownerId', '=', ownerId)
.where('shared_albums.usersId', 'is', null)
.where('shared_links.userId', 'is', null)
.where('albums.deletedAt', 'is', null)
.where((eb) =>
eb.not(
eb.exists(
eb
.selectFrom('albums_shared_users_users as album_users')
.whereRef('album_users.albumsId', '=', 'albums.id'),
),
),
)
.where((eb) =>
eb.not(eb.exists(eb.selectFrom('shared_links').whereRef('shared_links.albumId', '=', 'albums.id'))),
)
.select(withAlbumUsers)
.select(withOwner)
.select(withSharedLink)
.orderBy('albums.createdAt', 'desc')
.execute() as unknown as Promise<AlbumEntity[]>;
}
@@ -291,6 +282,7 @@ export class AlbumRepository implements IAlbumRepository {
.selectAll()
.where('id', '=', newAlbum.id)
.select(withOwner)
.select(withSharedLink)
.select(withAssets)
.select(withAlbumUsers)
.executeTakeFirst() as unknown as Promise<AlbumEntity>;
@@ -300,7 +292,7 @@ export class AlbumRepository implements IAlbumRepository {
update(id: string, album: Updateable<Albums>): Promise<AlbumEntity> {
return this.db
.updateTable('albums')
.set(album)
.set({ ...album, updatedAt: new Date() })
.where('id', '=', id)
.returningAll('albums')
.returning(withOwner)
@@ -343,6 +335,7 @@ export class AlbumRepository implements IAlbumRepository {
.select('album_assets.assetsId')
.orderBy('assets.fileCreatedAt', 'desc')
.limit(1),
updatedAt: new Date(),
}))
.where((eb) =>
eb.or([

View File

@@ -1,3 +1,4 @@
import { PostgresJSDialect } from 'kysely-postgres-js';
import { ImmichTelemetry } from 'src/enum';
import { clearEnvCache, ConfigRepository } from 'src/repositories/config.repository';
@@ -80,13 +81,10 @@ describe('getEnv', () => {
const { database } = getEnv();
expect(database).toEqual({
config: {
kysely: expect.objectContaining({
host: 'database',
port: 5432,
database: 'immich',
username: 'postgres',
password: 'postgres',
}),
kysely: {
dialect: expect.any(PostgresJSDialect),
log: expect.any(Function),
},
typeorm: expect.objectContaining({
type: 'postgres',
host: 'database',
@@ -106,94 +104,6 @@ describe('getEnv', () => {
const { database } = getEnv();
expect(database).toMatchObject({ skipMigrations: true });
});
it('should use DB_URL', () => {
process.env.DB_URL = 'postgres://postgres1:postgres2@database1:54320/immich';
const { database } = getEnv();
expect(database.config.kysely).toMatchObject({
host: 'database1',
password: 'postgres2',
user: 'postgres1',
port: 54_320,
database: 'immich',
});
});
it('should handle sslmode=require', () => {
process.env.DB_URL = 'postgres://postgres1:postgres2@database1:54320/immich?sslmode=require';
const { database } = getEnv();
expect(database.config.kysely).toMatchObject({ ssl: {} });
});
it('should handle sslmode=prefer', () => {
process.env.DB_URL = 'postgres://postgres1:postgres2@database1:54320/immich?sslmode=prefer';
const { database } = getEnv();
expect(database.config.kysely).toMatchObject({ ssl: {} });
});
it('should handle sslmode=verify-ca', () => {
process.env.DB_URL = 'postgres://postgres1:postgres2@database1:54320/immich?sslmode=verify-ca';
const { database } = getEnv();
expect(database.config.kysely).toMatchObject({ ssl: {} });
});
it('should handle sslmode=verify-full', () => {
process.env.DB_URL = 'postgres://postgres1:postgres2@database1:54320/immich?sslmode=verify-full';
const { database } = getEnv();
expect(database.config.kysely).toMatchObject({ ssl: {} });
});
it('should handle sslmode=no-verify', () => {
process.env.DB_URL = 'postgres://postgres1:postgres2@database1:54320/immich?sslmode=no-verify';
const { database } = getEnv();
expect(database.config.kysely).toMatchObject({ ssl: { rejectUnauthorized: false } });
});
it('should handle ssl=true', () => {
process.env.DB_URL = 'postgres://postgres1:postgres2@database1:54320/immich?ssl=true';
const { database } = getEnv();
expect(database.config.kysely).toMatchObject({ ssl: true });
});
it('should reject invalid ssl', () => {
process.env.DB_URL = 'postgres://postgres1:postgres2@database1:54320/immich?ssl=invalid';
expect(() => getEnv()).toThrowError('Invalid ssl option: invalid');
});
it('should handle socket: URLs', () => {
process.env.DB_URL = 'socket:/run/postgresql?db=database1';
const { database } = getEnv();
expect(database.config.kysely).toMatchObject({
host: '/run/postgresql',
database: 'database1',
});
});
it('should handle sockets in postgres: URLs', () => {
process.env.DB_URL = 'postgres:///database2?host=/path/to/socket';
const { database } = getEnv();
expect(database.config.kysely).toMatchObject({
host: '/path/to/socket',
database: 'database2',
});
});
});
describe('redis', () => {

View File

@@ -5,11 +5,12 @@ import { plainToInstance } from 'class-transformer';
import { validateSync } from 'class-validator';
import { Request, Response } from 'express';
import { RedisOptions } from 'ioredis';
import { KyselyConfig } from 'kysely';
import { PostgresJSDialect } from 'kysely-postgres-js';
import { CLS_ID, ClsModuleOptions } from 'nestjs-cls';
import { OpenTelemetryModuleOptions } from 'nestjs-otel/lib/interfaces';
import { join, resolve } from 'node:path';
import { parse } from 'pg-connection-string';
import { Notice } from 'postgres';
import postgres, { Notice } from 'postgres';
import { citiesFile, excludePaths, IWorker } from 'src/constants';
import { Telemetry } from 'src/decorators';
import { EnvDto } from 'src/dtos/env.dto';
@@ -19,20 +20,6 @@ import { QueueName } from 'src/interfaces/job.interface';
import { setDifference } from 'src/utils/set';
import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions.js';
type Ssl = 'require' | 'allow' | 'prefer' | 'verify-full' | boolean | object;
type PostgresConnectionConfig = {
host?: string;
password?: string;
user?: string;
port?: number;
database?: string;
client_encoding?: string;
ssl?: Ssl;
application_name?: string;
fallback_application_name?: string;
options?: string;
};
export interface EnvData {
host?: string;
port: number;
@@ -66,7 +53,7 @@ export interface EnvData {
};
database: {
config: { typeorm: PostgresConnectionOptions & DatabaseConnectionParams; kysely: PostgresConnectionConfig };
config: { typeorm: PostgresConnectionOptions & DatabaseConnectionParams; kysely: KyselyConfig };
skipMigrations: boolean;
vectorExtension: VectorExtension;
};
@@ -137,9 +124,6 @@ const asSet = <T>(value: string | undefined, defaults: T[]) => {
return new Set(values.length === 0 ? defaults : (values as T[]));
};
const isValidSsl = (ssl?: string | boolean | object): ssl is Ssl =>
typeof ssl !== 'string' || ssl === 'require' || ssl === 'allow' || ssl === 'prefer' || ssl === 'verify-full';
const getEnv = (): EnvData => {
const dto = plainToInstance(EnvDto, process.env);
const errors = validateSync(dto);
@@ -201,31 +185,6 @@ const getEnv = (): EnvData => {
}
}
const parts = {
connectionType: 'parts',
host: dto.DB_HOSTNAME || 'database',
port: dto.DB_PORT || 5432,
username: dto.DB_USERNAME || 'postgres',
password: dto.DB_PASSWORD || 'postgres',
database: dto.DB_DATABASE_NAME || 'immich',
} as const;
let parsedOptions: PostgresConnectionConfig = parts;
if (dto.DB_URL) {
const parsed = parse(dto.DB_URL);
if (!isValidSsl(parsed.ssl)) {
throw new Error(`Invalid ssl option: ${parsed.ssl}`);
}
parsedOptions = {
...parsed,
ssl: parsed.ssl,
host: parsed.host ?? undefined,
port: parsed.port ? Number(parsed.port) : undefined,
database: parsed.database ?? undefined,
};
}
const driverOptions = {
onnotice: (notice: Notice) => {
if (notice['severity'] !== 'NOTICE') {
@@ -247,9 +206,17 @@ const getEnv = (): EnvData => {
serialize: (value: number) => value.toString(),
},
},
...parsedOptions,
};
const parts = {
connectionType: 'parts',
host: dto.DB_HOSTNAME || 'database',
port: dto.DB_PORT || 5432,
username: dto.DB_USERNAME || 'postgres',
password: dto.DB_PASSWORD || 'postgres',
database: dto.DB_DATABASE_NAME || 'immich',
} as const;
return {
host: dto.IMMICH_HOST,
port: dto.IMMICH_PORT || 2283,
@@ -315,7 +282,21 @@ const getEnv = (): EnvData => {
parseInt8: true,
...(databaseUrl ? { connectionType: 'url', url: databaseUrl } : parts),
},
kysely: driverOptions,
kysely: {
dialect: new PostgresJSDialect({
postgres: databaseUrl ? postgres(databaseUrl, driverOptions) : postgres({ ...parts, ...driverOptions }),
}),
log(event) {
if (event.level === 'error') {
console.error('Query failed :', {
durationMs: event.queryDurationMillis,
error: event.error,
sql: event.query.sql,
params: event.query.parameters,
});
}
},
},
},
skipMigrations: dto.DB_SKIP_MIGRATIONS ?? false,

View File

@@ -72,14 +72,8 @@ export class SearchRepository implements ISearchRepository {
async searchRandom(size: number, options: AssetSearchOptions): Promise<AssetEntity[]> {
const uuid = randomUUID();
const builder = searchAssetBuilder(this.db, options);
const lessThan = builder
.where('assets.id', '<', uuid)
.orderBy(sql`random()`)
.limit(size);
const greaterThan = builder
.where('assets.id', '>', uuid)
.orderBy(sql`random()`)
.limit(size);
const lessThan = builder.where('assets.id', '<', uuid).orderBy('assets.id').limit(size);
const greaterThan = builder.where('assets.id', '>', uuid).orderBy('assets.id').limit(size);
const { rows } = await sql`${lessThan} union all ${greaterThan} limit ${size}`.execute(this.db);
return rows as any as AssetEntity[];
}
@@ -298,7 +292,7 @@ export class SearchRepository implements ISearchRepository {
await sql`truncate ${sql.table('smart_search')}`.execute(trx);
await trx.schema
.alterTable('smart_search')
.alterColumn('embedding', (col) => col.setDataType(sql.raw(`vector(${dimSize})`)))
.alterColumn('embedding', (col) => col.setDataType(sql.lit(`vector(${dimSize})`)))
.execute();
await sql`reindex index clip_index`.execute(trx);
});

View File

@@ -52,8 +52,8 @@ describe(AlbumService.name, () => {
it('gets list of albums for auth user', async () => {
albumMock.getOwned.mockResolvedValue([albumStub.empty, albumStub.sharedWithUser]);
albumMock.getMetadataForIds.mockResolvedValue([
{ albumId: albumStub.empty.id, assetCount: 0, startDate: null, endDate: null },
{ albumId: albumStub.sharedWithUser.id, assetCount: 0, startDate: null, endDate: null },
{ albumId: albumStub.empty.id, assetCount: 0, startDate: undefined, endDate: undefined },
{ albumId: albumStub.sharedWithUser.id, assetCount: 0, startDate: undefined, endDate: undefined },
]);
const result = await sut.getAll(authStub.admin, {});
@@ -82,7 +82,7 @@ describe(AlbumService.name, () => {
it('gets list of albums that are shared', async () => {
albumMock.getShared.mockResolvedValue([albumStub.sharedWithUser]);
albumMock.getMetadataForIds.mockResolvedValue([
{ albumId: albumStub.sharedWithUser.id, assetCount: 0, startDate: null, endDate: null },
{ albumId: albumStub.sharedWithUser.id, assetCount: 0, startDate: undefined, endDate: undefined },
]);
const result = await sut.getAll(authStub.admin, { shared: true });
@@ -94,7 +94,7 @@ describe(AlbumService.name, () => {
it('gets list of albums that are NOT shared', async () => {
albumMock.getNotShared.mockResolvedValue([albumStub.empty]);
albumMock.getMetadataForIds.mockResolvedValue([
{ albumId: albumStub.empty.id, assetCount: 0, startDate: null, endDate: null },
{ albumId: albumStub.empty.id, assetCount: 0, startDate: undefined, endDate: undefined },
]);
const result = await sut.getAll(authStub.admin, { shared: false });

View File

@@ -55,7 +55,13 @@ export class AlbumService extends BaseService {
const results = await this.albumRepository.getMetadataForIds(albums.map((album) => album.id));
const albumMetadata: Record<string, AlbumAssetCount> = {};
for (const metadata of results) {
albumMetadata[metadata.albumId] = metadata;
const { albumId, assetCount, startDate, endDate } = metadata;
albumMetadata[albumId] = {
albumId,
assetCount,
startDate,
endDate,
};
}
return Promise.all(
@@ -64,8 +70,8 @@ export class AlbumService extends BaseService {
return {
...mapAlbumWithoutAssets(album),
sharedLinks: undefined,
startDate: albumMetadata[album.id].startDate ?? undefined,
endDate: albumMetadata[album.id].endDate ?? undefined,
startDate: albumMetadata[album.id].startDate,
endDate: albumMetadata[album.id].endDate,
assetCount: albumMetadata[album.id].assetCount,
lastModifiedAssetTimestamp: lastModifiedAsset?.updatedAt,
};
@@ -83,8 +89,8 @@ export class AlbumService extends BaseService {
return {
...mapAlbum(album, withAssets, auth),
startDate: albumMetadataForIds.startDate ?? undefined,
endDate: albumMetadataForIds.endDate ?? undefined,
startDate: albumMetadataForIds.startDate,
endDate: albumMetadataForIds.endDate,
assetCount: albumMetadataForIds.assetCount,
lastModifiedAssetTimestamp: lastModifiedAsset?.updatedAt,
};

View File

@@ -416,34 +416,6 @@ describe(AssetService.name, () => {
await sut.updateAll(authStub.admin, { ids: ['asset-1', 'asset-2'], isArchived: true });
expect(assetMock.updateAll).toHaveBeenCalledWith(['asset-1', 'asset-2'], { isArchived: true });
});
it('should not update Assets table if no relevant fields are provided', async () => {
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
await sut.updateAll(authStub.admin, {
ids: ['asset-1'],
latitude: 0,
longitude: 0,
isArchived: undefined,
isFavorite: undefined,
duplicateId: undefined,
rating: undefined,
});
expect(assetMock.updateAll).not.toHaveBeenCalled();
});
it('should update Assets table if isArchived field is provided', async () => {
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
await sut.updateAll(authStub.admin, {
ids: ['asset-1'],
latitude: 0,
longitude: 0,
isArchived: undefined,
isFavorite: false,
duplicateId: undefined,
rating: undefined,
});
expect(assetMock.updateAll).toHaveBeenCalled();
});
});
describe('deleteAll', () => {

View File

@@ -142,14 +142,7 @@ export class AssetService extends BaseService {
await this.updateMetadata({ id, dateTimeOriginal, latitude, longitude });
}
if (
options.isArchived != undefined ||
options.isFavorite != undefined ||
options.duplicateId != undefined ||
options.rating != undefined
) {
await this.assetRepository.updateAll(ids, options);
}
await this.assetRepository.updateAll(ids, options);
}
@OnJob({ name: JobName.ASSET_DELETION_CHECK, queue: QueueName.BACKGROUND_TASK })

View File

@@ -1,3 +1,4 @@
import { PostgresJSDialect } from 'kysely-postgres-js';
import {
DatabaseExtension,
EXTENSION_NAMES,
@@ -61,11 +62,8 @@ describe(DatabaseService.name, () => {
database: {
config: {
kysely: {
host: 'database',
port: 5432,
user: 'postgres',
password: 'postgres',
database: 'immich',
dialect: expect.any(PostgresJSDialect),
log: ['error'],
},
typeorm: {
connectionType: 'parts',
@@ -300,11 +298,8 @@ describe(DatabaseService.name, () => {
database: {
config: {
kysely: {
host: 'database',
port: 5432,
user: 'postgres',
password: 'postgres',
database: 'immich',
dialect: expect.any(PostgresJSDialect),
log: ['error'],
},
typeorm: {
connectionType: 'parts',
@@ -333,11 +328,8 @@ describe(DatabaseService.name, () => {
database: {
config: {
kysely: {
host: 'database',
port: 5432,
user: 'postgres',
password: 'postgres',
database: 'immich',
dialect: expect.any(PostgresJSDialect),
log: ['error'],
},
typeorm: {
connectionType: 'parts',

View File

@@ -337,31 +337,12 @@ describe(LibraryService.name, () => {
expect(assetMock.updateAll).toHaveBeenCalledWith([assetStub.trashedOffline.id], {
deletedAt: null,
fileCreatedAt: assetStub.trashedOffline.fileModifiedAt,
fileModifiedAt: assetStub.trashedOffline.fileModifiedAt,
isOffline: false,
originalFileName: 'path.jpg',
});
});
it('should not touch fileCreatedAt when un-trashing an asset previously marked as offline', async () => {
const mockAssetJob: ILibraryAssetJob = {
id: assetStub.external.id,
importPaths: ['/'],
exclusionPatterns: [],
};
assetMock.getById.mockResolvedValue(assetStub.trashedOffline);
storageMock.stat.mockResolvedValue({ mtime: assetStub.trashedOffline.fileModifiedAt } as Stats);
await expect(sut.handleSyncAsset(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS);
expect(assetMock.updateAll).toHaveBeenCalledWith(
[assetStub.trashedOffline.id],
expect.not.objectContaining({
fileCreatedAt: expect.anything(),
}),
);
});
});
it('should update file when mtime has changed', async () => {
@@ -379,6 +360,7 @@ describe(LibraryService.name, () => {
expect(assetMock.updateAll).toHaveBeenCalledWith([assetStub.external.id], {
fileModifiedAt: newMTime,
fileCreatedAt: newMTime,
isOffline: false,
originalFileName: 'photo.jpg',
deletedAt: null,

View File

@@ -511,6 +511,7 @@ export class LibraryService extends BaseService {
await this.assetRepository.updateAll([asset.id], {
isOffline: false,
deletedAt: null,
fileCreatedAt: mtime,
fileModifiedAt: mtime,
originalFileName: parse(asset.originalPath).base,
});

View File

@@ -62,7 +62,7 @@ async function bootstrap() {
app.use(app.get(ApiService).ssr(excludePaths));
const server = await (host ? app.listen(port, host) : app.listen(port));
server.requestTimeout = 24 * 60 * 60 * 1000;
server.requestTimeout = 30 * 60 * 1000;
logger.log(`Immich Server is listening on ${await app.getUrl()} [v${serverVersion}] [${environment}] `);
}

View File

@@ -1,3 +1,5 @@
import { PostgresJSDialect } from 'kysely-postgres-js';
import postgres from 'postgres';
import { ImmichEnvironment, ImmichWorker } from 'src/enum';
import { DatabaseExtension } from 'src/interfaces/database.interface';
import { EnvData } from 'src/repositories/config.repository';
@@ -22,7 +24,12 @@ const envData: EnvData = {
database: {
config: {
kysely: { database: 'immich', host: 'database', port: 5432 },
kysely: {
dialect: new PostgresJSDialect({
postgres: postgres({ database: 'immich', host: 'database', port: 5432 }),
}),
log: ['error'],
},
typeorm: {
connectionType: 'parts',
database: 'immich',

6
web/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "immich-web",
"version": "1.125.3",
"version": "1.125.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "immich-web",
"version": "1.125.3",
"version": "1.125.1",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@formatjs/icu-messageformat-parser": "^2.9.8",
@@ -75,7 +75,7 @@
},
"../open-api/typescript-sdk": {
"name": "@immich/sdk",
"version": "1.125.3",
"version": "1.125.1",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@oazapfts/runtime": "^1.0.2"

View File

@@ -1,6 +1,6 @@
{
"name": "immich-web",
"version": "1.125.3",
"version": "1.125.1",
"license": "GNU Affero General Public License version 3",
"scripts": {
"dev": "vite dev --host 0.0.0.0 --port 3000",

View File

@@ -11,11 +11,7 @@
<section class="min-w-screen flex min-h-dvh items-center justify-center relative">
<div class="absolute -z-10 w-full h-full flex place-items-center place-content-center">
<img
src={immichLogo}
class="max-w-screen-md mx-auto h-full mb-2 antialiased -z-10 overflow-hidden"
alt="Immich logo"
/>
<img src={immichLogo} class="max-w-screen-md mx-auto h-full mb-2 antialiased -z-10" alt="Immich logo" />
<div
class="w-full h-[99%] absolute left-0 top-0 backdrop-blur-[200px] bg-transparent dark:bg-immich-dark-bg/20"
></div>