mirror of
https://github.com/immich-app/immich.git
synced 2025-12-19 09:13:14 +03:00
chore(web): more translations for user settings and admin pages (#10161)
* chore(web): more translations for user settings and admin pages * JobSettings translations * feedback * missed one * feedback
This commit is contained in:
@@ -76,13 +76,10 @@
|
||||
</div>
|
||||
|
||||
{#if forceDelete}
|
||||
<p class="text-immich-error">
|
||||
WARNING: This will immediately remove the user and all assets. This cannot be undone and the files cannot be
|
||||
recovered.
|
||||
</p>
|
||||
<p class="text-immich-error">{$t('admin.force_delete_user_warning')}</p>
|
||||
|
||||
<p class="immich-form-label text-sm" id="confirm-user-desc">
|
||||
To confirm, type "{user.email}" below
|
||||
{$t('admin.confirm_email_below', { values: { email: user.email } })}
|
||||
</p>
|
||||
|
||||
<input
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
<Badge color="primary">
|
||||
<div class="flex flex-row gap-1">
|
||||
<span class="text-sm">
|
||||
{jobCounts.failed.toLocaleString($locale)} failed
|
||||
{$t('admin.jobs_failed', { values: { jobCount: jobCounts.failed.toLocaleString($locale) } })}
|
||||
</span>
|
||||
<CircleIconButton
|
||||
color="primary"
|
||||
@@ -74,7 +74,7 @@
|
||||
{#if jobCounts.delayed > 0}
|
||||
<Badge color="secondary">
|
||||
<span class="text-sm">
|
||||
{jobCounts.delayed.toLocaleString($locale)} delayed
|
||||
{$t('admin.jobs_delayed', { values: { jobCount: jobCounts.delayed.toLocaleString($locale) } })}
|
||||
</span>
|
||||
</Badge>
|
||||
{/if}
|
||||
@@ -119,12 +119,14 @@
|
||||
color="light-gray"
|
||||
on:click={() => dispatch('command', { command: JobCommand.Start, force: false })}
|
||||
>
|
||||
<Icon path={mdiAlertCircle} size="36" /> DISABLED
|
||||
<Icon path={mdiAlertCircle} size="36" />
|
||||
{$t('disabled').toUpperCase()}
|
||||
</JobTileButton>
|
||||
{:else if !isIdle}
|
||||
{#if waitingCount > 0}
|
||||
<JobTileButton color="gray" on:click={() => dispatch('command', { command: JobCommand.Empty, force: false })}>
|
||||
<Icon path={mdiClose} size="24" /> CLEAR
|
||||
<Icon path={mdiClose} size="24" />
|
||||
{$t('clear').toUpperCase()}
|
||||
</JobTileButton>
|
||||
{/if}
|
||||
{#if queueStatus.isPaused}
|
||||
@@ -134,14 +136,16 @@
|
||||
on:click={() => dispatch('command', { command: JobCommand.Resume, force: false })}
|
||||
>
|
||||
<!-- size property is not reactive, so have to use width and height -->
|
||||
<Icon path={mdiFastForward} {size} /> RESUME
|
||||
<Icon path={mdiFastForward} {size} />
|
||||
{$t('resume').toUpperCase()}
|
||||
</JobTileButton>
|
||||
{:else}
|
||||
<JobTileButton
|
||||
color="light-gray"
|
||||
on:click={() => dispatch('command', { command: JobCommand.Pause, force: false })}
|
||||
>
|
||||
<Icon path={mdiPause} size="24" /> PAUSE
|
||||
<Icon path={mdiPause} size="24" />
|
||||
{$t('pause').toUpperCase()}
|
||||
</JobTileButton>
|
||||
{/if}
|
||||
{:else if allowForceCommand}
|
||||
@@ -161,7 +165,8 @@
|
||||
color="light-gray"
|
||||
on:click={() => dispatch('command', { command: JobCommand.Start, force: false })}
|
||||
>
|
||||
<Icon path={mdiPlay} size="48" /> START
|
||||
<Icon path={mdiPlay} size="48" />
|
||||
{$t('start').toUpperCase()}
|
||||
</JobTileButton>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
if (dto.force) {
|
||||
const isConfirmed = await dialogController.show({
|
||||
id: 'confirm-reprocess-all-faces',
|
||||
prompt: 'Are you sure you want to reprocess all faces? This will also clear named people.',
|
||||
prompt: $t('admin.confirm_reprocess_all_faces'),
|
||||
});
|
||||
|
||||
if (isConfirmed) {
|
||||
@@ -60,23 +60,23 @@
|
||||
$: jobDetails = <Partial<Record<JobName, JobDetails>>>{
|
||||
[JobName.ThumbnailGeneration]: {
|
||||
icon: mdiFileJpgBox,
|
||||
title: getJobName(JobName.ThumbnailGeneration),
|
||||
title: $getJobName(JobName.ThumbnailGeneration),
|
||||
subtitle: $t('admin.thumbnail_generation_job_description'),
|
||||
},
|
||||
[JobName.MetadataExtraction]: {
|
||||
icon: mdiTable,
|
||||
title: getJobName(JobName.MetadataExtraction),
|
||||
title: $getJobName(JobName.MetadataExtraction),
|
||||
subtitle: $t('admin.metadata_extraction_job_description'),
|
||||
},
|
||||
[JobName.Library]: {
|
||||
icon: mdiLibraryShelves,
|
||||
title: getJobName(JobName.Library),
|
||||
title: $getJobName(JobName.Library),
|
||||
subtitle: $t('admin.library_tasks_description'),
|
||||
allText: $t('all').toUpperCase(),
|
||||
missingText: $t('refresh').toUpperCase(),
|
||||
},
|
||||
[JobName.Sidecar]: {
|
||||
title: getJobName(JobName.Sidecar),
|
||||
title: $getJobName(JobName.Sidecar),
|
||||
icon: mdiFileXmlBox,
|
||||
subtitle: $t('admin.sidecar_job_description'),
|
||||
allText: $t('sync').toUpperCase(),
|
||||
@@ -85,46 +85,44 @@
|
||||
},
|
||||
[JobName.SmartSearch]: {
|
||||
icon: mdiImageSearch,
|
||||
title: getJobName(JobName.SmartSearch),
|
||||
title: $getJobName(JobName.SmartSearch),
|
||||
subtitle: $t('admin.smart_search_job_description'),
|
||||
disabled: !$featureFlags.smartSearch,
|
||||
},
|
||||
[JobName.DuplicateDetection]: {
|
||||
icon: mdiContentDuplicate,
|
||||
title: getJobName(JobName.DuplicateDetection),
|
||||
title: $getJobName(JobName.DuplicateDetection),
|
||||
subtitle: $t('admin.duplicate_detection_job_description'),
|
||||
disabled: !$featureFlags.duplicateDetection,
|
||||
},
|
||||
[JobName.FaceDetection]: {
|
||||
icon: mdiFaceRecognition,
|
||||
title: getJobName(JobName.FaceDetection),
|
||||
subtitle:
|
||||
'Detect the faces in assets using machine learning. For videos, only the thumbnail is considered. "All" (re-)processes all assets. "Missing" queues assets that haven\'t been processed yet. Detected faces will be queued for Facial Recognition after Face Detection is complete, grouping them into existing or new people.',
|
||||
title: $getJobName(JobName.FaceDetection),
|
||||
subtitle: $t('admin.face_detection_description'),
|
||||
handleCommand: handleConfirmCommand,
|
||||
disabled: !$featureFlags.facialRecognition,
|
||||
},
|
||||
[JobName.FacialRecognition]: {
|
||||
icon: mdiTagFaces,
|
||||
title: getJobName(JobName.FacialRecognition),
|
||||
subtitle:
|
||||
'Group detected faces into people. This step runs after Face Detection is complete. "All" (re-)clusters all faces. "Missing" queues faces that don\'t have a person assigned.',
|
||||
title: $getJobName(JobName.FacialRecognition),
|
||||
subtitle: $t('admin.facial_recognition_job_description'),
|
||||
handleCommand: handleConfirmCommand,
|
||||
disabled: !$featureFlags.facialRecognition,
|
||||
},
|
||||
[JobName.VideoConversion]: {
|
||||
icon: mdiVideo,
|
||||
title: getJobName(JobName.VideoConversion),
|
||||
title: $getJobName(JobName.VideoConversion),
|
||||
subtitle: $t('admin.video_conversion_job_description'),
|
||||
},
|
||||
[JobName.StorageTemplateMigration]: {
|
||||
icon: mdiFolderMove,
|
||||
title: getJobName(JobName.StorageTemplateMigration),
|
||||
title: $getJobName(JobName.StorageTemplateMigration),
|
||||
allowForceCommand: false,
|
||||
description: StorageMigrationDescription,
|
||||
},
|
||||
[JobName.Migration]: {
|
||||
icon: mdiFolderMove,
|
||||
title: getJobName(JobName.Migration),
|
||||
title: $getJobName(JobName.Migration),
|
||||
subtitle: $t('admin.migration_job_description'),
|
||||
allowForceCommand: false,
|
||||
},
|
||||
@@ -140,14 +138,14 @@
|
||||
switch (jobCommand.command) {
|
||||
case JobCommand.Empty: {
|
||||
notificationController.show({
|
||||
message: `Cleared jobs for: ${title}`,
|
||||
message: $t('admin.cleared_jobs', { values: { job: title } }),
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, `Command '${jobCommand.command}' failed for job: ${title}`);
|
||||
handleError(error, $t('admin.failed_job_command', { values: { command: jobCommand.command, job: title } }));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
{#if user.quotaSizeInBytes}
|
||||
({((user.usage / user.quotaSizeInBytes) * 100).toFixed(0)}%)
|
||||
{:else}
|
||||
(Unlimited)
|
||||
({$t('unlimited')})
|
||||
{/if}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
}
|
||||
|
||||
notificationController.show({
|
||||
message: 'Reset settings to the recent saved settings',
|
||||
message: $t('admin.reset_settings_to_recent_saved'),
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
};
|
||||
@@ -64,7 +64,7 @@
|
||||
}
|
||||
|
||||
notificationController.show({
|
||||
message: $t('reset_settings_to_default'),
|
||||
message: $t('admin.reset_settings_to_default'),
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -270,7 +270,7 @@
|
||||
},
|
||||
{
|
||||
value: TranscodeHWAccel.Disabled,
|
||||
text: $t('admin.disabled'),
|
||||
text: $t('disabled'),
|
||||
},
|
||||
]}
|
||||
isEdited={config.ffmpeg.accel !== savedConfig.ffmpeg.accel}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import SettingInputField, {
|
||||
SettingInputFieldType,
|
||||
} from '$lib/components/shared-components/settings/setting-input-field.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let savedConfig: SystemConfigDto;
|
||||
export let defaultConfig: SystemConfigDto;
|
||||
@@ -45,7 +46,7 @@
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.NUMBER}
|
||||
{disabled}
|
||||
label="{getJobName(jobName)} Concurrency"
|
||||
label={$t('admin.job_concurrency', { values: { job: $getJobName(jobName) } })}
|
||||
desc=""
|
||||
bind:value={config.job[jobName].concurrency}
|
||||
required={true}
|
||||
@@ -54,11 +55,11 @@
|
||||
{:else}
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.NUMBER}
|
||||
label="{getJobName(jobName)} Concurrency"
|
||||
label={$t('admin.job_concurrency', { values: { job: $getJobName(jobName) } })}
|
||||
desc=""
|
||||
value="1"
|
||||
disabled={true}
|
||||
title="This job is not concurrency-safe."
|
||||
title={$t('admin.job_not_concurrency_safe')}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -84,7 +84,9 @@
|
||||
|
||||
{#if $featureFlags.email}
|
||||
<div class="my-4 flex place-items-center justify-between gap-2">
|
||||
<label class="text-sm dark:text-immich-dark-fg" for="send-welcome-email"> Send welcome email </label>
|
||||
<label class="text-sm dark:text-immich-dark-fg" for="send-welcome-email">
|
||||
{$t('admin.send_welcome_email')}
|
||||
</label>
|
||||
<Slider id="send-welcome-email" bind:checked={notify} />
|
||||
</div>
|
||||
{/if}
|
||||
@@ -101,7 +103,7 @@
|
||||
|
||||
<div class="my-4 flex place-items-center justify-between gap-2">
|
||||
<label class="text-sm dark:text-immich-dark-fg" for="require-password-change">
|
||||
Require user to change password on first login
|
||||
{$t('admin.require_password_change_on_login')}
|
||||
</label>
|
||||
<Slider id="require-password-change" bind:checked={shouldChangePassword} />
|
||||
</div>
|
||||
@@ -113,9 +115,9 @@
|
||||
|
||||
<div class="my-4 flex flex-col gap-2">
|
||||
<label class="flex items-center gap-2 immich-form-label" for="quotaSize">
|
||||
Quota Size (GiB)
|
||||
{$t('admin.quota_size_gib')}
|
||||
{#if quotaSizeWarning}
|
||||
<p class="text-red-400 text-sm">You set a quota higher than the disk size</p>
|
||||
<p class="text-red-400 text-sm">{$t('admin.quota_higher_than_disk_size')}</p>
|
||||
{/if}
|
||||
</label>
|
||||
<input class="immich-form-input" id="quotaSize" type="number" min="0" bind:value={quotaSize} />
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
const resetPassword = async () => {
|
||||
const isConfirmed = await dialogController.show({
|
||||
id: 'confirm-reset-password',
|
||||
prompt: `Are you sure you want to reset ${user.name}'s password?`,
|
||||
prompt: $t('admin.confirm_user_password_reset', { values: { user: user.name } }),
|
||||
});
|
||||
|
||||
if (!isConfirmed) {
|
||||
@@ -110,13 +110,14 @@
|
||||
</div>
|
||||
|
||||
<div class="my-4 flex flex-col gap-2">
|
||||
<label class="flex items-center gap-2 immich-form-label" for="quotaSize"
|
||||
>Quota Size (GiB) {#if quotaSizeWarning}
|
||||
<p class="text-red-400 text-sm">You set a quota higher than the disk size</p>
|
||||
<label class="flex items-center gap-2 immich-form-label" for="quotaSize">
|
||||
{$t('admin.quota_size_gib')}
|
||||
{#if quotaSizeWarning}
|
||||
<p class="text-red-400 text-sm">{$t('errors.quota_higher_than_disk_size')}</p>
|
||||
{/if}</label
|
||||
>
|
||||
<input class="immich-form-input" id="quotaSize" name="quotaSize" type="number" min="0" bind:value={quotaSize} />
|
||||
<p>Note: Enter 0 for unlimited quota</p>
|
||||
<p>{$t('admin.note_unlimited_quota')}</p>
|
||||
</div>
|
||||
|
||||
<div class="my-4 flex flex-col gap-2">
|
||||
@@ -130,10 +131,10 @@
|
||||
/>
|
||||
|
||||
<p>
|
||||
Note: To apply the Storage Label to previously uploaded assets, run the
|
||||
{$t('admin.note_apply_storage_label_previous_assets')}
|
||||
<a href={AppRoute.ADMIN_JOBS} class="text-immich-primary dark:text-immich-dark-primary">
|
||||
Storage Migration Job</a
|
||||
>
|
||||
{$t('admin.storage_template_migration_job')}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -32,11 +32,9 @@
|
||||
<FullScreenModal title={$t('add_exclusion_pattern')} icon={mdiFolderRemove} onClose={handleCancel}>
|
||||
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off" id="add-exclusion-pattern-form">
|
||||
<p class="py-5 text-sm">
|
||||
Exclusion patterns lets you ignore files and folders when scanning your library. This is useful if you have
|
||||
folders that contain files you don't want to import, such as RAW files.
|
||||
{$t('admin.exclusion_pattern_description')}
|
||||
<br /><br />
|
||||
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/**".
|
||||
{$t('admin.add_exclusion_pattern_description')}
|
||||
</p>
|
||||
<div class="my-4 flex flex-col gap-2">
|
||||
<label class="immich-form-label" for="exclusionPattern">{$t('pattern')}</label>
|
||||
@@ -50,7 +48,7 @@
|
||||
</div>
|
||||
<div class="mt-8 flex w-full gap-4">
|
||||
{#if isDuplicate}
|
||||
<p class="text-red-500 text-sm">This exclusion pattern already exists.</p>
|
||||
<p class="text-red-500 text-sm">{$t('errors.exclusion_pattern_already_exists')}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -33,9 +33,7 @@
|
||||
|
||||
<FullScreenModal {title} icon={mdiFolderSync} onClose={handleCancel}>
|
||||
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off" id="library-import-path-form">
|
||||
<p class="py-5 text-sm">
|
||||
Specify a folder to import. This folder, including subfolders, will be scanned for images and videos.
|
||||
</p>
|
||||
<p class="py-5 text-sm">{$t('admin.library_import_path_description')}</p>
|
||||
|
||||
<div class="my-4 flex flex-col gap-2">
|
||||
<label class="immich-form-label" for="path">{$t('path')}</label>
|
||||
@@ -44,7 +42,7 @@
|
||||
|
||||
<div class="mt-8 flex w-full gap-4">
|
||||
{#if isDuplicate}
|
||||
<p class="text-red-500 text-sm">This import path already exists.</p>
|
||||
<p class="text-red-500 text-sm">{$t('admin.import_path_already_exists')}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
import type { ValidateLibraryImportPathResponseDto } from '@immich/sdk';
|
||||
import { NotificationType, notificationController } from '../shared-components/notification/notification';
|
||||
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||
import { s } from '$lib/utils';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let library: LibraryResponseDto;
|
||||
@@ -54,13 +53,13 @@
|
||||
if (failedPaths === 0) {
|
||||
if (notifyIfSuccessful) {
|
||||
notificationController.show({
|
||||
message: `All paths validated successfully`,
|
||||
message: $t('admin.paths_validated_successfully'),
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
notificationController.show({
|
||||
message: `${failedPaths} path${s(failedPaths)} failed validation`,
|
||||
message: $t('errors.paths_validation_failed', { values: { paths: failedPaths } }),
|
||||
type: NotificationType.Warning,
|
||||
});
|
||||
}
|
||||
@@ -95,7 +94,7 @@
|
||||
await revalidate(false);
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to add import path');
|
||||
handleError(error, $t('errors.unable_to_add_import_path'));
|
||||
} finally {
|
||||
addImportPath = false;
|
||||
importPathToAdd = null;
|
||||
@@ -121,7 +120,7 @@
|
||||
}
|
||||
} catch (error) {
|
||||
editImportPath = null;
|
||||
handleError(error, 'Unable to edit import path');
|
||||
handleError(error, $t('errors.unable_to_edit_import_path'));
|
||||
} finally {
|
||||
editImportPath = null;
|
||||
}
|
||||
@@ -141,7 +140,7 @@
|
||||
library.importPaths = library.importPaths.filter((path) => path != pathToDelete);
|
||||
await handleValidation();
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to delete import path');
|
||||
handleError(error, $t('errors.unable_to_delete_import_path'));
|
||||
} finally {
|
||||
editImportPath = null;
|
||||
}
|
||||
@@ -230,7 +229,7 @@
|
||||
>
|
||||
<td class="w-4/5 text-ellipsis px-4 text-sm">
|
||||
{#if importPaths.length === 0}
|
||||
No paths added
|
||||
{$t('admin.no_paths_added')}
|
||||
{/if}</td
|
||||
>
|
||||
<td class="w-1/5 text-ellipsis px-4 text-sm"
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
exclusionPatterns = library.exclusionPatterns;
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to add exclusion pattern');
|
||||
handleError(error, $t('errors.unable_to_add_exclusion_pattern'));
|
||||
} finally {
|
||||
exclusionPatternToAdd = '';
|
||||
addExclusionPattern = false;
|
||||
@@ -74,7 +74,7 @@
|
||||
library.exclusionPatterns[editExclusionPattern] = editedExclusionPattern;
|
||||
exclusionPatterns = library.exclusionPatterns;
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to edit exclude pattern');
|
||||
handleError(error, $t('errors.unable_to_edit_exclusion_pattern'));
|
||||
} finally {
|
||||
editExclusionPattern = null;
|
||||
}
|
||||
@@ -94,7 +94,7 @@
|
||||
library.exclusionPatterns = library.exclusionPatterns.filter((path) => path != pathToDelete);
|
||||
exclusionPatterns = library.exclusionPatterns;
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to delete exclude pattern');
|
||||
handleError(error, $t('errors.unable_to_delete_exclusion_pattern'));
|
||||
} finally {
|
||||
editExclusionPattern = null;
|
||||
}
|
||||
@@ -162,7 +162,7 @@
|
||||
>
|
||||
<td class="w-3/4 text-ellipsis px-4 text-sm">
|
||||
{#if exclusionPatterns.length === 0}
|
||||
No pattern added
|
||||
{$t('admin.no_pattern_added')}
|
||||
{/if}
|
||||
</td>
|
||||
<td class="w-1/4 text-ellipsis px-4 text-sm"
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
<FullScreenModal title={$t('select_library_owner')} icon={mdiFolderSync} onClose={handleCancel}>
|
||||
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off" id="select-library-owner-form">
|
||||
<p class="p-5 text-sm">NOTE: This cannot be changed later!</p>
|
||||
<p class="p-5 text-sm">{$t('admin.note_cannot_be_changed_later')}</p>
|
||||
|
||||
<SettingSelect bind:value={ownerId} options={userOptions} name="user" />
|
||||
</form>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { t, init } from 'svelte-i18n';
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
|
||||
let time = new Date();
|
||||
|
||||
@@ -77,6 +78,7 @@
|
||||
}
|
||||
|
||||
await init({ fallbackLocale: defaultLang.code, initialLocale: newLang });
|
||||
await invalidateAll();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -143,7 +145,7 @@
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<SettingSwitch
|
||||
title="Play video thumbnail on hover"
|
||||
title={$t('video_hover_setting')}
|
||||
subtitle={$t('video_hover_setting_description')}
|
||||
bind:checked={$playVideoThumbnailOnHover}
|
||||
on:toggle={() => ($playVideoThumbnailOnHover = !$playVideoThumbnailOnHover)}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
} catch (error) {
|
||||
console.error('Error [user-profile] [changePassword]', error);
|
||||
notificationController.show({
|
||||
message: (error as HttpError)?.body?.message || 'Unable to change password',
|
||||
message: (error as HttpError)?.body?.message || $t('errors.unable_to_change_password'),
|
||||
type: NotificationType.Error,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<Button type="submit" size="sm" on:click={() => handleSave()}>Save</Button>
|
||||
<Button type="submit" size="sm" on:click={() => handleSave()}>{$t('save')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to link OAuth account');
|
||||
handleError(error, $t('errors.unable_to_link_oauth_account'));
|
||||
} finally {
|
||||
await goto('?open=oauth');
|
||||
}
|
||||
|
||||
@@ -79,8 +79,8 @@
|
||||
const handleRemovePartner = async (partner: PartnerResponseDto) => {
|
||||
const isConfirmed = await dialogController.show({
|
||||
id: 'remove-partner',
|
||||
title: 'Stop sharing your photos?',
|
||||
prompt: `${partner.name} will no longer be able to access your photos.`,
|
||||
title: $t('stop_photo_sharing'),
|
||||
prompt: $t('stop_photo_sharing_description', { values: { partner: partner.name } }),
|
||||
});
|
||||
|
||||
if (!isConfirmed) {
|
||||
@@ -115,7 +115,7 @@
|
||||
partner.inTimeline = inTimeline;
|
||||
partners = partners;
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to update timeline display status');
|
||||
handleError(error, $t('errors.unable_to_update_timeline_display_status'));
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -142,7 +142,7 @@
|
||||
on:click={() => handleRemovePartner(partner.user)}
|
||||
icon={mdiClose}
|
||||
size={'16'}
|
||||
title="Stop sharing your photos with this user"
|
||||
title={$t('stop_sharing_photos_with_user')}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -151,14 +151,18 @@
|
||||
<!-- I am sharing my assets with this user -->
|
||||
{#if partner.sharedByMe}
|
||||
<hr class="my-4 border border-gray-200 dark:border-gray-700" />
|
||||
<p class="text-xs font-medium my-4">SHARED WITH {partner.user.name.toUpperCase()}</p>
|
||||
<p class="text-md">{partner.user.name} can access</p>
|
||||
<p class="text-xs font-medium my-4">
|
||||
{$t('shared_with_partner', { values: { partner: partner.user.name } }).toUpperCase()}
|
||||
</p>
|
||||
<p class="text-md">{$t('partner_can_access', { values: { partner: partner.user.name } })}</p>
|
||||
<ul class="text-sm">
|
||||
<li class="flex gap-2 place-items-center py-1 mt-2">
|
||||
<Icon path={mdiCheck} /> All your photos and videos except those in Archived and Deleted
|
||||
<Icon path={mdiCheck} />
|
||||
{$t('partner_can_access_assets')}
|
||||
</li>
|
||||
<li class="flex gap-2 place-items-center py-1">
|
||||
<Icon path={mdiCheck} /> The location where your photos were taken
|
||||
<Icon path={mdiCheck} />
|
||||
{$t('partner_can_access_location')}
|
||||
</li>
|
||||
</ul>
|
||||
{/if}
|
||||
@@ -166,7 +170,9 @@
|
||||
<!-- this user is sharing assets with me -->
|
||||
{#if partner.sharedWithMe}
|
||||
<hr class="my-4 border border-gray-200 dark:border-gray-700" />
|
||||
<p class="text-xs font-medium my-4">PHOTOS FROM {partner.user.name.toUpperCase()}</p>
|
||||
<p class="text-xs font-medium my-4">
|
||||
{$t('shared_from_partner', { values: { partner: partner.user.name } }).toUpperCase()}
|
||||
</p>
|
||||
<SettingSwitch
|
||||
title={$t('show_in_timeline')}
|
||||
subtitle={$t('show_in_timeline_setting_description')}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
const data = await createApiKey({ apiKeyCreateDto: detail });
|
||||
secret = data.secret;
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to create a new API Key');
|
||||
handleError(error, $t('errors.unable_to_create_api_key'));
|
||||
} finally {
|
||||
await refreshKeys();
|
||||
newKey = null;
|
||||
@@ -48,11 +48,11 @@
|
||||
try {
|
||||
await updateApiKey({ id: editKey.id, apiKeyUpdateDto: { name: detail.name } });
|
||||
notificationController.show({
|
||||
message: `Saved API Key`,
|
||||
message: $t('saved_api_key'),
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to save API Key');
|
||||
handleError(error, $t('errors.unable_to_save_api_key'));
|
||||
} finally {
|
||||
await refreshKeys();
|
||||
editKey = null;
|
||||
@@ -62,7 +62,7 @@
|
||||
const handleDelete = async (key: ApiKeyResponseDto) => {
|
||||
const isConfirmed = await dialogController.show({
|
||||
id: 'delete-api-key',
|
||||
prompt: 'Are you sure you want to delete this API key?',
|
||||
prompt: $t('delete_api_key_prompt'),
|
||||
});
|
||||
|
||||
if (!isConfirmed) {
|
||||
@@ -72,11 +72,11 @@
|
||||
try {
|
||||
await deleteApiKey({ id: key.id });
|
||||
notificationController.show({
|
||||
message: `Removed API Key: ${key.name}`,
|
||||
message: $t('removed_api_key', { values: { name: key.name } }),
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to remove API Key');
|
||||
handleError(error, $t('errors.unable_to_remove_api_key'));
|
||||
} finally {
|
||||
await refreshKeys();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user