mirror of
https://github.com/immich-app/immich.git
synced 2024-12-29 15:11:58 +00:00
feat(web): add more translations (#10700)
* feat(web): add more translations * formatting
This commit is contained in:
parent
e54c18367b
commit
c58148af35
20 changed files with 90 additions and 53 deletions
|
@ -74,7 +74,7 @@
|
||||||
<div class="flex justify-center m-4 gap-2">
|
<div class="flex justify-center m-4 gap-2">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="queue-user-deletion-checkbox"
|
id="queue-user-deletion-checkbox"
|
||||||
label="Queue user and assets for immediate deletion"
|
label={$t('admin.user_delete_immediately_checkbox')}
|
||||||
labelClass="text-sm dark:text-immich-dark-fg"
|
labelClass="text-sm dark:text-immich-dark-fg"
|
||||||
bind:checked={forceDelete}
|
bind:checked={forceDelete}
|
||||||
on:change={() => {
|
on:change={() => {
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
>
|
>
|
||||||
<svelte:fragment slot="prompt">
|
<svelte:fragment slot="prompt">
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<p>Are you sure you want to disable all login methods? Login will be completely disabled.</p>
|
<p>{$t('admin.authentication_settings_disable_all')}</p>
|
||||||
<p>
|
<p>
|
||||||
<FormatMessage key="admin.authentication_settings_reenable" let:message>
|
<FormatMessage key="admin.authentication_settings_reenable" let:message>
|
||||||
<a
|
<a
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
export let config: SystemConfigDto; // this is the config that is being edited
|
export let config: SystemConfigDto; // this is the config that is being edited
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
|
|
||||||
const cronExpressionOptions = [
|
$: cronExpressionOptions = [
|
||||||
{ title: $t('interval.night_at_midnight'), expression: '0 0 * * *' },
|
{ title: $t('interval.night_at_midnight'), expression: '0 0 * * *' },
|
||||||
{ title: $t('interval.night_at_twoam'), expression: '0 2 * * *' },
|
{ title: $t('interval.night_at_twoam'), expression: '0 2 * * *' },
|
||||||
{ title: $t('interval.day_at_onepm'), expression: '0 13 * * *' },
|
{ title: $t('interval.day_at_onepm'), expression: '0 13 * * *' },
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
SettingInputFieldType,
|
SettingInputFieldType,
|
||||||
} from '$lib/components/shared-components/settings/setting-input-field.svelte';
|
} from '$lib/components/shared-components/settings/setting-input-field.svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
import FormatMessage from '$lib/components/i18n/format-message.svelte';
|
||||||
|
|
||||||
export let savedConfig: SystemConfigDto;
|
export let savedConfig: SystemConfigDto;
|
||||||
export let defaultConfig: SystemConfigDto;
|
export let defaultConfig: SystemConfigDto;
|
||||||
|
@ -52,12 +53,16 @@
|
||||||
<SettingAccordion key="reverse-geocoding" title={$t('admin.map_reverse_geocoding_settings')}>
|
<SettingAccordion key="reverse-geocoding" title={$t('admin.map_reverse_geocoding_settings')}>
|
||||||
<svelte:fragment slot="subtitle">
|
<svelte:fragment slot="subtitle">
|
||||||
<p class="text-sm dark:text-immich-dark-fg">
|
<p class="text-sm dark:text-immich-dark-fg">
|
||||||
Manage <a
|
<FormatMessage key="admin.map_manage_reverse_geocoding_settings" let:message>
|
||||||
href="https://immich.app/docs/features/reverse-geocoding"
|
<a
|
||||||
class="underline"
|
href="https://immich.app/docs/features/reverse-geocoding"
|
||||||
target="_blank"
|
class="underline"
|
||||||
rel="noreferrer">{$t('admin.map_reverse_geocoding')}</a
|
target="_blank"
|
||||||
> settings
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
{message}
|
||||||
|
</a>
|
||||||
|
</FormatMessage>
|
||||||
</p>
|
</p>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<div class="ml-4 mt-4 flex flex-col gap-4">
|
<div class="ml-4 mt-4 flex flex-col gap-4">
|
||||||
|
|
|
@ -12,13 +12,13 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mt-2 text-sm">
|
<div class="mt-2 text-sm">
|
||||||
<h4>DATE & TIME</h4>
|
<h4>{$t('date_and_time').toUpperCase()}</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-2 rounded-lg bg-gray-200 p-4 text-xs dark:bg-gray-700 dark:text-immich-dark-fg">
|
<div class="mt-2 rounded-lg bg-gray-200 p-4 text-xs dark:bg-gray-700 dark:text-immich-dark-fg">
|
||||||
<div class="mb-2 text-gray-600 dark:text-immich-dark-fg">
|
<div class="mb-2 text-gray-600 dark:text-immich-dark-fg">
|
||||||
<p>Asset's creation timestamp is used for the datetime information</p>
|
<p>{$t('admin.storage_template_date_time_description')}</p>
|
||||||
<p>Sample time 2022-02-03T04:56:05.250</p>
|
<p>{$t('admin.storage_template_date_time_sample', { values: { date: '2022-02-03T04:56:05.250' } })}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-[40px]">
|
<div class="flex gap-[40px]">
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
await updateAsset({ id: asset.id, updateAssetDto: { description: newDescription } });
|
await updateAsset({ id: asset.id, updateAssetDto: { description: newDescription } });
|
||||||
notificationController.show({
|
notificationController.show({
|
||||||
type: NotificationType.Info,
|
type: NotificationType.Info,
|
||||||
message: 'Asset description has been updated',
|
message: $t('asset_description_updated'),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, $t('cannot_update_the_description'));
|
handleError(error, $t('cannot_update_the_description'));
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import type { AdapterConstructor, PluginConstructor } from '@photo-sphere-viewer/core';
|
import type { AdapterConstructor, PluginConstructor } from '@photo-sphere-viewer/core';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||||
|
import { t } from 'svelte-i18n';
|
||||||
export let asset: Pick<AssetResponseDto, 'id' | 'type'>;
|
export let asset: Pick<AssetResponseDto, 'id' | 'type'>;
|
||||||
|
|
||||||
const photoSphereConfigs =
|
const photoSphereConfigs =
|
||||||
|
@ -35,6 +36,6 @@
|
||||||
{:then [data, module, adapter, plugins, navbar]}
|
{:then [data, module, adapter, plugins, navbar]}
|
||||||
<svelte:component this={module.default} panorama={data} plugins={plugins ?? undefined} {navbar} {adapter} />
|
<svelte:component this={module.default} panorama={data} plugins={plugins ?? undefined} {navbar} {adapter} />
|
||||||
{:catch}
|
{:catch}
|
||||||
Failed to load asset
|
{$t('errors.failed_to_load_asset')}
|
||||||
{/await}
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -209,7 +209,7 @@
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
{#each peopleWithFaces as face, index}
|
{#each peopleWithFaces as face, index}
|
||||||
{@const personName = face.person ? face.person?.name : 'Unassigned'}
|
{@const personName = face.person ? face.person?.name : $t('face_unassigned')}
|
||||||
<div class="relative z-[20001] h-[115px] w-[95px]">
|
<div class="relative z-[20001] h-[115px] w-[95px]">
|
||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
|
@ -261,8 +261,8 @@
|
||||||
curve
|
curve
|
||||||
shadow
|
shadow
|
||||||
url="/src/lib/assets/no-thumbnail.png"
|
url="/src/lib/assets/no-thumbnail.png"
|
||||||
altText="Unassigned"
|
altText={$t('face_unassigned')}
|
||||||
title="Unassigned"
|
title={$t('face_unassigned')}
|
||||||
widthStyle="90px"
|
widthStyle="90px"
|
||||||
heightStyle="90px"
|
heightStyle="90px"
|
||||||
thumbhash={null}
|
thumbhash={null}
|
||||||
|
@ -273,8 +273,8 @@
|
||||||
curve
|
curve
|
||||||
shadow
|
shadow
|
||||||
url={data === null ? '/src/lib/assets/no-thumbnail.png' : data}
|
url={data === null ? '/src/lib/assets/no-thumbnail.png' : data}
|
||||||
altText="Unassigned"
|
altText={$t('face_unassigned')}
|
||||||
title="Unassigned"
|
title={$t('face_unassigned')}
|
||||||
widthStyle="90px"
|
widthStyle="90px"
|
||||||
heightStyle="90px"
|
heightStyle="90px"
|
||||||
thumbhash={null}
|
thumbhash={null}
|
||||||
|
@ -289,7 +289,7 @@
|
||||||
{#if selectedPersonToReassign[face.id]?.id}
|
{#if selectedPersonToReassign[face.id]?.id}
|
||||||
{selectedPersonToReassign[face.id]?.name}
|
{selectedPersonToReassign[face.id]?.name}
|
||||||
{:else}
|
{:else}
|
||||||
<span class={personName == 'Unassigned' ? 'dark:text-gray-500' : ''}>{personName}</span>
|
<span class={personName === $t('face_unassigned') ? 'dark:text-gray-500' : ''}>{personName}</span>
|
||||||
{/if}
|
{/if}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -322,7 +322,7 @@
|
||||||
<div
|
<div
|
||||||
class="flex place-content-center place-items-center rounded-full bg-[#d3d3d3] p-1 transition-all absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform"
|
class="flex place-content-center place-items-center rounded-full bg-[#d3d3d3] p-1 transition-all absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform"
|
||||||
>
|
>
|
||||||
<Icon color="primary" path={mdiAccountOff} ariaLabel="Just a face" size="18" />
|
<Icon color="primary" path={mdiAccountOff} ariaHidden size="18" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -57,7 +57,12 @@
|
||||||
<div class="dark:text-immich-dark-fg">
|
<div class="dark:text-immich-dark-fg">
|
||||||
<div
|
<div
|
||||||
class="storage-status grid grid-cols-[64px_auto]"
|
class="storage-status grid grid-cols-[64px_auto]"
|
||||||
title="Used {getByteUnitString(usedBytes, $locale, 3)} of {getByteUnitString(availableBytes, $locale, 3)}"
|
title={$t('storage_usage', {
|
||||||
|
values: {
|
||||||
|
used: getByteUnitString(usedBytes, $locale, 3),
|
||||||
|
available: getByteUnitString(availableBytes, $locale, 3),
|
||||||
|
},
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
<div class="pb-[2.15rem] pl-5 pr-6 text-immich-primary dark:text-immich-dark-primary group-hover:sm:pb-0 md:pb-0">
|
<div class="pb-[2.15rem] pl-5 pr-6 text-immich-primary dark:text-immich-dark-primary group-hover:sm:pb-0 md:pb-0">
|
||||||
<Icon path={mdiChartPie} size="24" />
|
<Icon path={mdiChartPie} size="24" />
|
||||||
|
|
|
@ -67,7 +67,7 @@
|
||||||
{:else if uploadAsset.state === UploadState.DUPLICATED}
|
{:else if uploadAsset.state === UploadState.DUPLICATED}
|
||||||
<div class="h-[15px] rounded-md bg-immich-warning transition-all" style="width: 100%" />
|
<div class="h-[15px] rounded-md bg-immich-warning transition-all" style="width: 100%" />
|
||||||
<p class="absolute top-0 h-full w-full text-center text-[10px]">
|
<p class="absolute top-0 h-full w-full text-center text-[10px]">
|
||||||
Skipped
|
{$t('asset_skipped')}
|
||||||
{#if uploadAsset.message}
|
{#if uploadAsset.message}
|
||||||
({uploadAsset.message})
|
({uploadAsset.message})
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -75,7 +75,7 @@
|
||||||
{:else if uploadAsset.state === UploadState.DONE}
|
{:else if uploadAsset.state === UploadState.DONE}
|
||||||
<div class="h-[15px] rounded-md bg-immich-success transition-all" style="width: 100%" />
|
<div class="h-[15px] rounded-md bg-immich-success transition-all" style="width: 100%" />
|
||||||
<p class="absolute top-0 h-full w-full text-center text-[10px]">
|
<p class="absolute top-0 h-full w-full text-center text-[10px]">
|
||||||
Uploaded
|
{$t('asset_uploaded')}
|
||||||
{#if uploadAsset.message}
|
{#if uploadAsset.message}
|
||||||
({uploadAsset.message})
|
({uploadAsset.message})
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getProfileImageUrl } from '$lib/utils';
|
import { getProfileImageUrl } from '$lib/utils';
|
||||||
import { type UserAvatarColor } from '@immich/sdk';
|
import { type UserAvatarColor } from '@immich/sdk';
|
||||||
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -77,7 +78,7 @@
|
||||||
<img
|
<img
|
||||||
bind:this={img}
|
bind:this={img}
|
||||||
src={getProfileImageUrl(user.id)}
|
src={getProfileImageUrl(user.id)}
|
||||||
alt="Profile image of {title}"
|
alt={$t('profile_image_of_user', { values: { user: title } })}
|
||||||
class="h-full w-full object-cover"
|
class="h-full w-full object-cover"
|
||||||
class:hidden={showFallback}
|
class:hidden={showFallback}
|
||||||
draggable="false"
|
draggable="false"
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
</FormatMessage>
|
</FormatMessage>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 font-medium">Your friend, Alex</div>
|
<div class="mt-4 font-medium">{$t('version_announcement_closing')}</div>
|
||||||
|
|
||||||
<div class="font-sm mt-8">
|
<div class="font-sm mt-8">
|
||||||
<code>{$t('server_version')}: {serverVersion}</code>
|
<code>{$t('server_version')}: {serverVersion}</code>
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
<div class="flex flex-col justify-center gap-1 dark:text-white">
|
<div class="flex flex-col justify-center gap-1 dark:text-white">
|
||||||
<span class="text-sm">
|
<span class="text-sm">
|
||||||
{#if device.deviceType || device.deviceOS}
|
{#if device.deviceType || device.deviceOS}
|
||||||
<span>{device.deviceOS || 'Unknown'} • {device.deviceType || 'Unknown'}</span>
|
<span>{device.deviceOS || $t('unknown')} • {device.deviceType || $t('unknown')}</span>
|
||||||
{:else}
|
{:else}
|
||||||
<span>{$t('unknown')}</span>
|
<span>{$t('unknown')}</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
"add_exclusion_pattern_description": "Add exclusion patterns. Globbing using *, **, and ? is supported. To ignore all files in any directory named \"Raw\", use \"**/Raw/**\". To ignore all files ending in \".tif\", use \"**/*.tif\". To ignore an absolute path, use \"/path/to/ignore/**\".",
|
"add_exclusion_pattern_description": "Add exclusion patterns. Globbing using *, **, and ? is supported. To ignore all files in any directory named \"Raw\", use \"**/Raw/**\". To ignore all files ending in \".tif\", use \"**/*.tif\". To ignore an absolute path, use \"/path/to/ignore/**\".",
|
||||||
"authentication_settings": "Authentication Settings",
|
"authentication_settings": "Authentication Settings",
|
||||||
"authentication_settings_description": "Manage password, OAuth, and other authentication settings",
|
"authentication_settings_description": "Manage password, OAuth, and other authentication settings",
|
||||||
|
"authentication_settings_disable_all": "Are you sure you want to disable all login methods? Login will be completely disabled.",
|
||||||
"authentication_settings_reenable": "To re-enable, use a <link>Server Command</link>.",
|
"authentication_settings_reenable": "To re-enable, use a <link>Server Command</link>.",
|
||||||
"background_task_job": "Background Tasks",
|
"background_task_job": "Background Tasks",
|
||||||
"check_all": "Check All",
|
"check_all": "Check All",
|
||||||
|
@ -125,6 +126,7 @@
|
||||||
"map_dark_style": "Dark style",
|
"map_dark_style": "Dark style",
|
||||||
"map_enable_description": "Enable map features",
|
"map_enable_description": "Enable map features",
|
||||||
"map_light_style": "Light style",
|
"map_light_style": "Light style",
|
||||||
|
"map_manage_reverse_geocoding_settings": "Manage <link>Reverse Geocoding</link> settings",
|
||||||
"map_reverse_geocoding": "Reverse Geocoding",
|
"map_reverse_geocoding": "Reverse Geocoding",
|
||||||
"map_reverse_geocoding_enable_description": "Enable reverse geocoding",
|
"map_reverse_geocoding_enable_description": "Enable reverse geocoding",
|
||||||
"map_reverse_geocoding_settings": "Reverse Geocoding Settings",
|
"map_reverse_geocoding_settings": "Reverse Geocoding Settings",
|
||||||
|
@ -209,6 +211,8 @@
|
||||||
"sidecar_job_description": "Discover or synchronize sidecar metadata from the filesystem",
|
"sidecar_job_description": "Discover or synchronize sidecar metadata from the filesystem",
|
||||||
"slideshow_duration_description": "Number of seconds to display each image",
|
"slideshow_duration_description": "Number of seconds to display each image",
|
||||||
"smart_search_job_description": "Run machine learning on assets to support smart search",
|
"smart_search_job_description": "Run machine learning on assets to support smart search",
|
||||||
|
"storage_template_date_time_description": "Asset's creation timestamp is used for the datetime information",
|
||||||
|
"storage_template_date_time_sample": "Sample time {date}",
|
||||||
"storage_template_enable_description": "Enable storage template engine",
|
"storage_template_enable_description": "Enable storage template engine",
|
||||||
"storage_template_hash_verification_enabled": "Hash verification enabled",
|
"storage_template_hash_verification_enabled": "Hash verification enabled",
|
||||||
"storage_template_hash_verification_enabled_description": "Enables hash verification, don't disable this unless you're certain of the implications",
|
"storage_template_hash_verification_enabled_description": "Enables hash verification, don't disable this unless you're certain of the implications",
|
||||||
|
@ -298,10 +302,12 @@
|
||||||
"user_delete_delay_settings": "Delete delay",
|
"user_delete_delay_settings": "Delete delay",
|
||||||
"user_delete_delay_settings_description": "Number of days after removal to permanently delete a user's account and assets. The user deletion job runs at midnight to check for users that are ready for deletion. Changes to this setting will be evaluated at the next execution.",
|
"user_delete_delay_settings_description": "Number of days after removal to permanently delete a user's account and assets. The user deletion job runs at midnight to check for users that are ready for deletion. Changes to this setting will be evaluated at the next execution.",
|
||||||
"user_delete_immediately": "<b>{user}</b>'s account and assets will be queued for permanent deletion <b>immediately</b>.",
|
"user_delete_immediately": "<b>{user}</b>'s account and assets will be queued for permanent deletion <b>immediately</b>.",
|
||||||
|
"user_delete_immediately_checkbox": "Queue user and assets for immediate deletion",
|
||||||
"user_management": "User Management",
|
"user_management": "User Management",
|
||||||
"user_password_has_been_reset": "The user's password has been reset:",
|
"user_password_has_been_reset": "The user's password has been reset:",
|
||||||
"user_password_reset_description": "Please provide the temporary password to the user and inform them they will need to change the password at their next login.",
|
"user_password_reset_description": "Please provide the temporary password to the user and inform them they will need to change the password at their next login.",
|
||||||
"user_restore_description": "<b>{user}</b>'s account will be restored.",
|
"user_restore_description": "<b>{user}</b>'s account will be restored.",
|
||||||
|
"user_restore_scheduled_removal": "Restore user - scheduled removal on {date, date, long}",
|
||||||
"user_settings": "User Settings",
|
"user_settings": "User Settings",
|
||||||
"user_settings_description": "Manage user settings",
|
"user_settings_description": "Manage user settings",
|
||||||
"user_successfully_removed": "User {email} has been successfully removed.",
|
"user_successfully_removed": "User {email} has been successfully removed.",
|
||||||
|
@ -358,10 +364,17 @@
|
||||||
"archived_count": "{count, plural, other {Archived #}}",
|
"archived_count": "{count, plural, other {Archived #}}",
|
||||||
"are_these_the_same_person": "Are these the same person?",
|
"are_these_the_same_person": "Are these the same person?",
|
||||||
"are_you_sure_to_do_this": "Are you sure you want to do this?",
|
"are_you_sure_to_do_this": "Are you sure you want to do this?",
|
||||||
|
"asset_added_to_album": "Added to album",
|
||||||
|
"asset_adding_to_album": "Adding to album...",
|
||||||
|
"asset_description_updated": "Asset description has been updated",
|
||||||
"asset_filename_is_offline": "Asset {filename} is offline",
|
"asset_filename_is_offline": "Asset {filename} is offline",
|
||||||
"asset_has_unassigned_faces": "Asset has unassigned faces",
|
"asset_has_unassigned_faces": "Asset has unassigned faces",
|
||||||
|
"asset_hashing": "Hashing...",
|
||||||
"asset_offline": "Asset offline",
|
"asset_offline": "Asset offline",
|
||||||
"asset_offline_description": "This asset is offline. Immich can not access its file location. Please ensure the asset is available and then rescan the library.",
|
"asset_offline_description": "This asset is offline. Immich can not access its file location. Please ensure the asset is available and then rescan the library.",
|
||||||
|
"asset_skipped": "Skipped",
|
||||||
|
"asset_uploaded": "Uploaded",
|
||||||
|
"asset_uploading": "Uploading...",
|
||||||
"assets": "Assets",
|
"assets": "Assets",
|
||||||
"assets_added_count": "Added {count, plural, one {# asset} other {# assets}}",
|
"assets_added_count": "Added {count, plural, one {# asset} other {# assets}}",
|
||||||
"assets_added_to_album_count": "Added {count, plural, one {# asset} other {# assets}} to the album",
|
"assets_added_to_album_count": "Added {count, plural, one {# asset} other {# assets}} to the album",
|
||||||
|
@ -456,6 +469,7 @@
|
||||||
"date_after": "Date after",
|
"date_after": "Date after",
|
||||||
"date_and_time": "Date and Time",
|
"date_and_time": "Date and Time",
|
||||||
"date_before": "Date before",
|
"date_before": "Date before",
|
||||||
|
"date_of_birth_saved": "Date of birth saved successfully",
|
||||||
"date_range": "Date range",
|
"date_range": "Date range",
|
||||||
"day": "Day",
|
"day": "Day",
|
||||||
"deduplicate_all": "Deduplicate All",
|
"deduplicate_all": "Deduplicate All",
|
||||||
|
@ -544,6 +558,8 @@
|
||||||
"failed_to_create_shared_link": "Failed to create shared link",
|
"failed_to_create_shared_link": "Failed to create shared link",
|
||||||
"failed_to_edit_shared_link": "Failed to edit shared link",
|
"failed_to_edit_shared_link": "Failed to edit shared link",
|
||||||
"failed_to_get_people": "Failed to get people",
|
"failed_to_get_people": "Failed to get people",
|
||||||
|
"failed_to_load_asset": "Failed to load asset",
|
||||||
|
"failed_to_load_assets": "Failed to load assets",
|
||||||
"failed_to_stack_assets": "Failed to stack assets",
|
"failed_to_stack_assets": "Failed to stack assets",
|
||||||
"failed_to_unstack_assets": "Failed to un-stack assets",
|
"failed_to_unstack_assets": "Failed to un-stack assets",
|
||||||
"import_path_already_exists": "This import path already exists.",
|
"import_path_already_exists": "This import path already exists.",
|
||||||
|
@ -569,6 +585,7 @@
|
||||||
"unable_to_change_visibility": "Unable to change the visibility for {count, plural, one {# person} other {# people}}",
|
"unable_to_change_visibility": "Unable to change the visibility for {count, plural, one {# person} other {# people}}",
|
||||||
"unable_to_complete_oauth_login": "Unable to complete OAuth login",
|
"unable_to_complete_oauth_login": "Unable to complete OAuth login",
|
||||||
"unable_to_connect": "Unable to connect",
|
"unable_to_connect": "Unable to connect",
|
||||||
|
"unable_to_connect_to_server": "Unable to connect to server",
|
||||||
"unable_to_copy_to_clipboard": "Cannot copy to clipboard, make sure you are accessing the page through https",
|
"unable_to_copy_to_clipboard": "Cannot copy to clipboard, make sure you are accessing the page through https",
|
||||||
"unable_to_create_admin_account": "Unable to create admin account",
|
"unable_to_create_admin_account": "Unable to create admin account",
|
||||||
"unable_to_create_api_key": "Unable to create a new API Key",
|
"unable_to_create_api_key": "Unable to create a new API Key",
|
||||||
|
@ -588,6 +605,7 @@
|
||||||
"unable_to_enter_fullscreen": "Unable to enter fullscreen",
|
"unable_to_enter_fullscreen": "Unable to enter fullscreen",
|
||||||
"unable_to_exit_fullscreen": "Unable to exit fullscreen",
|
"unable_to_exit_fullscreen": "Unable to exit fullscreen",
|
||||||
"unable_to_get_comments_number": "Unable to get number of comments",
|
"unable_to_get_comments_number": "Unable to get number of comments",
|
||||||
|
"unable_to_get_shared_link": "Failed to get shared link",
|
||||||
"unable_to_hide_person": "Unable to hide person",
|
"unable_to_hide_person": "Unable to hide person",
|
||||||
"unable_to_link_oauth_account": "Unable to link OAuth account",
|
"unable_to_link_oauth_account": "Unable to link OAuth account",
|
||||||
"unable_to_load_album": "Unable to load album",
|
"unable_to_load_album": "Unable to load album",
|
||||||
|
@ -616,6 +634,7 @@
|
||||||
"unable_to_restore_user": "Unable to restore user",
|
"unable_to_restore_user": "Unable to restore user",
|
||||||
"unable_to_save_album": "Unable to save album",
|
"unable_to_save_album": "Unable to save album",
|
||||||
"unable_to_save_api_key": "Unable to save API Key",
|
"unable_to_save_api_key": "Unable to save API Key",
|
||||||
|
"unable_to_save_date_of_birth": "Unable to save date of birth",
|
||||||
"unable_to_save_name": "Unable to save name",
|
"unable_to_save_name": "Unable to save name",
|
||||||
"unable_to_save_profile": "Unable to save profile",
|
"unable_to_save_profile": "Unable to save profile",
|
||||||
"unable_to_save_settings": "Unable to save settings",
|
"unable_to_save_settings": "Unable to save settings",
|
||||||
|
@ -632,7 +651,8 @@
|
||||||
"unable_to_update_location": "Unable to update location",
|
"unable_to_update_location": "Unable to update location",
|
||||||
"unable_to_update_settings": "Unable to update settings",
|
"unable_to_update_settings": "Unable to update settings",
|
||||||
"unable_to_update_timeline_display_status": "Unable to update timeline display status",
|
"unable_to_update_timeline_display_status": "Unable to update timeline display status",
|
||||||
"unable_to_update_user": "Unable to update user"
|
"unable_to_update_user": "Unable to update user",
|
||||||
|
"unable_to_upload_file": "Unable to upload file"
|
||||||
},
|
},
|
||||||
"exif": "Exif",
|
"exif": "Exif",
|
||||||
"exit_slideshow": "Exit Slideshow",
|
"exit_slideshow": "Exit Slideshow",
|
||||||
|
@ -646,6 +666,7 @@
|
||||||
"extension": "Extension",
|
"extension": "Extension",
|
||||||
"external": "External",
|
"external": "External",
|
||||||
"external_libraries": "External Libraries",
|
"external_libraries": "External Libraries",
|
||||||
|
"face_unassigned": "Unassigned",
|
||||||
"favorite": "Favorite",
|
"favorite": "Favorite",
|
||||||
"favorite_or_unfavorite_photo": "Favorite or unfavorite photo",
|
"favorite_or_unfavorite_photo": "Favorite or unfavorite photo",
|
||||||
"favorites": "Favorites",
|
"favorites": "Favorites",
|
||||||
|
@ -870,6 +891,7 @@
|
||||||
"previous_memory": "Previous memory",
|
"previous_memory": "Previous memory",
|
||||||
"previous_or_next_photo": "Previous or next photo",
|
"previous_or_next_photo": "Previous or next photo",
|
||||||
"primary": "Primary",
|
"primary": "Primary",
|
||||||
|
"profile_image_of_user": "Profile image of {title}",
|
||||||
"profile_picture_set": "Profile picture set.",
|
"profile_picture_set": "Profile picture set.",
|
||||||
"public_album": "Public album",
|
"public_album": "Public album",
|
||||||
"public_share": "Public Share",
|
"public_share": "Public Share",
|
||||||
|
@ -991,6 +1013,7 @@
|
||||||
"shared_photos_and_videos_count": "{assetCount, plural, other {# shared photos & videos.}}",
|
"shared_photos_and_videos_count": "{assetCount, plural, other {# shared photos & videos.}}",
|
||||||
"shared_with_partner": "Shared with {partner}",
|
"shared_with_partner": "Shared with {partner}",
|
||||||
"sharing": "Sharing",
|
"sharing": "Sharing",
|
||||||
|
"sharing_enter_password": "Please enter the password to view this page.",
|
||||||
"sharing_sidebar_description": "Display a link to Sharing in the sidebar",
|
"sharing_sidebar_description": "Display a link to Sharing in the sidebar",
|
||||||
"shift_to_permanent_delete": "press ⇧ to permanently delete asset",
|
"shift_to_permanent_delete": "press ⇧ to permanently delete asset",
|
||||||
"show_album_options": "Show album options",
|
"show_album_options": "Show album options",
|
||||||
|
@ -1107,6 +1130,7 @@
|
||||||
"validate": "Validate",
|
"validate": "Validate",
|
||||||
"variables": "Variables",
|
"variables": "Variables",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
|
"version_announcement_closing": "Your friend, Alex",
|
||||||
"version_announcement_message": "Hi friend, there is a new version of the application please take your time to visit the <link>release notes</link> and ensure your <code>docker-compose.yml</code>, and <code>.env</code> setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your application automatically.",
|
"version_announcement_message": "Hi friend, there is a new version of the application please take your time to visit the <link>release notes</link> and ensure your <code>docker-compose.yml</code>, and <code>.env</code> setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your application automatically.",
|
||||||
"video": "Video",
|
"video": "Video",
|
||||||
"video_hover_setting": "Play video thumbnail on hover",
|
"video_hover_setting": "Play video thumbnail on hover",
|
||||||
|
|
|
@ -3,7 +3,8 @@ import { fromLocalDateTime } from '$lib/utils/timeline-util';
|
||||||
import { TimeBucketSize, getTimeBucket, getTimeBuckets, type AssetResponseDto } from '@immich/sdk';
|
import { TimeBucketSize, getTimeBucket, getTimeBuckets, type AssetResponseDto } from '@immich/sdk';
|
||||||
import { throttle } from 'lodash-es';
|
import { throttle } from 'lodash-es';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { writable, type Unsubscriber } from 'svelte/store';
|
import { t } from 'svelte-i18n';
|
||||||
|
import { get, writable, type Unsubscriber } from 'svelte/store';
|
||||||
import { handleError } from '../utils/handle-error';
|
import { handleError } from '../utils/handle-error';
|
||||||
import { websocketEvents } from './websocket';
|
import { websocketEvents } from './websocket';
|
||||||
|
|
||||||
|
@ -286,7 +287,8 @@ export class AssetStore {
|
||||||
|
|
||||||
this.emit(true);
|
this.emit(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, 'Failed to load assets');
|
const $t = get(t);
|
||||||
|
handleError(error, $t('errors.failed_to_load_assets'));
|
||||||
} finally {
|
} finally {
|
||||||
bucket.cancelToken = null;
|
bucket.cancelToken = null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@ import {
|
||||||
type AssetMediaResponseDto,
|
type AssetMediaResponseDto,
|
||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
import { tick } from 'svelte';
|
import { tick } from 'svelte';
|
||||||
|
import { t } from 'svelte-i18n';
|
||||||
|
import { get } from 'svelte/store';
|
||||||
import { getServerErrorMessage, handleError } from './handle-error';
|
import { getServerErrorMessage, handleError } from './handle-error';
|
||||||
|
|
||||||
let _extensions: string[];
|
let _extensions: string[];
|
||||||
|
@ -83,6 +85,7 @@ function getDeviceAssetId(asset: File) {
|
||||||
async function fileUploader(assetFile: File, albumId?: string, replaceAssetId?: string): Promise<string | undefined> {
|
async function fileUploader(assetFile: File, albumId?: string, replaceAssetId?: string): Promise<string | undefined> {
|
||||||
const fileCreatedAt = new Date(assetFile.lastModified).toISOString();
|
const fileCreatedAt = new Date(assetFile.lastModified).toISOString();
|
||||||
const deviceAssetId = getDeviceAssetId(assetFile);
|
const deviceAssetId = getDeviceAssetId(assetFile);
|
||||||
|
const $t = get(t);
|
||||||
|
|
||||||
uploadAssetsStore.markStarted(deviceAssetId);
|
uploadAssetsStore.markStarted(deviceAssetId);
|
||||||
|
|
||||||
|
@ -103,7 +106,7 @@ async function fileUploader(assetFile: File, albumId?: string, replaceAssetId?:
|
||||||
let responseData: AssetMediaResponseDto | undefined;
|
let responseData: AssetMediaResponseDto | undefined;
|
||||||
const key = getKey();
|
const key = getKey();
|
||||||
if (crypto?.subtle?.digest && !key) {
|
if (crypto?.subtle?.digest && !key) {
|
||||||
uploadAssetsStore.updateAsset(deviceAssetId, { message: 'Hashing...' });
|
uploadAssetsStore.updateAsset(deviceAssetId, { message: $t('asset_hashing') });
|
||||||
await tick();
|
await tick();
|
||||||
try {
|
try {
|
||||||
const bytes = await assetFile.arrayBuffer();
|
const bytes = await assetFile.arrayBuffer();
|
||||||
|
@ -124,7 +127,7 @@ async function fileUploader(assetFile: File, albumId?: string, replaceAssetId?:
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!responseData) {
|
if (!responseData) {
|
||||||
uploadAssetsStore.updateAsset(deviceAssetId, { message: 'Uploading...' });
|
uploadAssetsStore.updateAsset(deviceAssetId, { message: $t('asset_uploading') });
|
||||||
if (replaceAssetId) {
|
if (replaceAssetId) {
|
||||||
const response = await uploadRequest<AssetMediaResponseDto>({
|
const response = await uploadRequest<AssetMediaResponseDto>({
|
||||||
url: getBaseUrl() + getAssetOriginalPath(replaceAssetId) + (key ? `?key=${key}` : ''),
|
url: getBaseUrl() + getAssetOriginalPath(replaceAssetId) + (key ? `?key=${key}` : ''),
|
||||||
|
@ -141,7 +144,7 @@ async function fileUploader(assetFile: File, albumId?: string, replaceAssetId?:
|
||||||
});
|
});
|
||||||
|
|
||||||
if (![200, 201].includes(response.status)) {
|
if (![200, 201].includes(response.status)) {
|
||||||
throw new Error('Failed to upload file');
|
throw new Error($t('errors.unable_to_upload_file'));
|
||||||
}
|
}
|
||||||
|
|
||||||
responseData = response.data;
|
responseData = response.data;
|
||||||
|
@ -155,9 +158,9 @@ async function fileUploader(assetFile: File, albumId?: string, replaceAssetId?:
|
||||||
}
|
}
|
||||||
|
|
||||||
if (albumId) {
|
if (albumId) {
|
||||||
uploadAssetsStore.updateAsset(deviceAssetId, { message: 'Adding to album...' });
|
uploadAssetsStore.updateAsset(deviceAssetId, { message: $t('asset_adding_to_album') });
|
||||||
await addAssetsToAlbum(albumId, [responseData.id]);
|
await addAssetsToAlbum(albumId, [responseData.id]);
|
||||||
uploadAssetsStore.updateAsset(deviceAssetId, { message: 'Added to album' });
|
uploadAssetsStore.updateAsset(deviceAssetId, { message: $t('asset_added_to_album') });
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadAssetsStore.updateAsset(deviceAssetId, {
|
uploadAssetsStore.updateAsset(deviceAssetId, {
|
||||||
|
@ -170,7 +173,7 @@ async function fileUploader(assetFile: File, albumId?: string, replaceAssetId?:
|
||||||
|
|
||||||
return responseData.id;
|
return responseData.id;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, 'Unable to upload file');
|
handleError(error, $t('errors.unable_to_upload_file'));
|
||||||
const reason = getServerErrorMessage(error) || error;
|
const reason = getServerErrorMessage(error) || error;
|
||||||
uploadAssetsStore.updateAsset(deviceAssetId, { state: UploadState.ERROR, error: reason });
|
uploadAssetsStore.updateAsset(deviceAssetId, { state: UploadState.ERROR, error: reason });
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -331,9 +331,9 @@
|
||||||
return person;
|
return person;
|
||||||
});
|
});
|
||||||
|
|
||||||
notificationController.show({ message: 'Date of birth saved successfully', type: NotificationType.Info });
|
notificationController.show({ message: $t('date_of_birth_saved'), type: NotificationType.Info });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, 'Unable to save date of birth');
|
handleError(error, $t('errors.unable_to_save_date_of_birth'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -26,9 +26,11 @@
|
||||||
passwordRequired = false;
|
passwordRequired = false;
|
||||||
isOwned = $user ? $user.id === sharedLink.userId : false;
|
isOwned = $user ? $user.id === sharedLink.userId : false;
|
||||||
title = (sharedLink.album ? sharedLink.album.albumName : $t('public_share')) + ' - Immich';
|
title = (sharedLink.album ? sharedLink.album.albumName : $t('public_share')) + ' - Immich';
|
||||||
description = sharedLink.description || `${sharedLink.assets.length} shared photos & videos.`;
|
description =
|
||||||
|
sharedLink.description ||
|
||||||
|
$t('shared_photos_and_videos_count', { values: { assetCount: sharedLink.assets.length } });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, 'Failed to get shared link');
|
handleError(error, $t('errors.unable_to_get_shared_link'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -57,7 +59,7 @@
|
||||||
<div class="flex flex-col items-center justify-center mt-20">
|
<div class="flex flex-col items-center justify-center mt-20">
|
||||||
<div class="text-2xl font-bold text-immich-primary dark:text-immich-dark-primary">{$t('password_required')}</div>
|
<div class="text-2xl font-bold text-immich-primary dark:text-immich-dark-primary">{$t('password_required')}</div>
|
||||||
<div class="mt-4 text-lg text-immich-primary dark:text-immich-dark-primary">
|
<div class="mt-4 text-lg text-immich-primary dark:text-immich-dark-primary">
|
||||||
Please enter the password to view this page.
|
{$t('sharing_enter_password')}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<form novalidate autocomplete="off" on:submit|preventDefault={handlePasswordSubmit}>
|
<form novalidate autocomplete="off" on:submit|preventDefault={handlePasswordSubmit}>
|
||||||
|
|
|
@ -78,7 +78,7 @@
|
||||||
try {
|
try {
|
||||||
await loadConfig();
|
await loadConfig();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, 'Unable to connect to server');
|
handleError(error, $t('errors.unable_to_connect_to_server'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -59,16 +59,8 @@
|
||||||
return websocketEvents.on('on_user_delete', onDeleteSuccess);
|
return websocketEvents.on('on_user_delete', onDeleteSuccess);
|
||||||
});
|
});
|
||||||
|
|
||||||
const deleteDateFormat: Intl.DateTimeFormatOptions = {
|
const getDeleteDate = (deletedAt: string): Date => {
|
||||||
month: 'long',
|
return DateTime.fromISO(deletedAt).plus({ days: $serverConfig.userDeleteDelay }).toJSDate();
|
||||||
day: 'numeric',
|
|
||||||
year: 'numeric',
|
|
||||||
};
|
|
||||||
|
|
||||||
const getDeleteDate = (deletedAt: string): string => {
|
|
||||||
return DateTime.fromISO(deletedAt)
|
|
||||||
.plus({ days: $serverConfig.userDeleteDelay })
|
|
||||||
.toLocaleString(deleteDateFormat, { locale: $locale });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onUserCreated = async () => {
|
const onUserCreated = async () => {
|
||||||
|
@ -245,7 +237,9 @@
|
||||||
{#if immichUser.deletedAt && immichUser.status === UserStatus.Deleted}
|
{#if immichUser.deletedAt && immichUser.status === UserStatus.Deleted}
|
||||||
<CircleIconButton
|
<CircleIconButton
|
||||||
icon={mdiDeleteRestore}
|
icon={mdiDeleteRestore}
|
||||||
title="Restore user - scheduled removal on {getDeleteDate(immichUser.deletedAt)}"
|
title={$t('admin.user_restore_scheduled_removal', {
|
||||||
|
values: { date: getDeleteDate(immichUser.deletedAt) },
|
||||||
|
})}
|
||||||
color="primary"
|
color="primary"
|
||||||
size="16"
|
size="16"
|
||||||
on:click={() => restoreUserHandler(immichUser)}
|
on:click={() => restoreUserHandler(immichUser)}
|
||||||
|
|
Loading…
Reference in a new issue