mirror of
https://github.com/immich-app/immich
synced 2025-11-07 17:27:20 +00:00
feat(web): translations (#9854)
* First test * Added translation using Weblate (French) * Translated using Weblate (German) Currently translated at 100.0% (4 of 4 strings) Translation: immich/web Translate-URL: http://familie-mach.net/projects/immich/web/de/ * Translated using Weblate (French) Currently translated at 100.0% (4 of 4 strings) Translation: immich/web Translate-URL: http://familie-mach.net/projects/immich/web/fr/ * Further testing * Further testing * Translated using Weblate (German) Currently translated at 100.0% (18 of 18 strings) Translation: immich/web Translate-URL: http://familie-mach.net/projects/immich/web/de/ * Further work * Update string file. * More strings * Automatically changed strings * Add automatically translated german file for testing purposes * Fix merge-face-selector component * Make server stats strings uppercase * Fix uppercase string * Fix some strings in jobs-panel * Fix lower and uppercase strings. Add a few additional string. Fix a few unnecessary replacements * Update german test translations * Fix typo in locales file * Change string keys * Extract more strings * Extract and replace some more strings * Update testtranslationfile * Change translation keys * Fix rebase errors * Fix one more rebase error * Remove german translation file * Co-authored-by: Daniel Dietzler <danieldietzler@users.noreply.github.com> * chore: clean up translations * chore: add new line * fix formatting * chore: fixes * fix: loading and tests --------- Co-authored-by: root <root@Blacki> Co-authored-by: admin <admin@example.com> Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
This commit is contained in:
parent
a2bccf23c9
commit
f446bc8caa
177 changed files with 2779 additions and 1017 deletions
|
|
@ -2,10 +2,11 @@
|
|||
import type { ComboBoxOption } from '$lib/components/shared-components/combobox.svelte';
|
||||
import SettingCombobox from '$lib/components/shared-components/settings/setting-combobox.svelte';
|
||||
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
|
||||
import { fallbackLocale, locales } from '$lib/constants';
|
||||
import { fallbackLang, fallbackLocale, langs, locales } from '$lib/constants';
|
||||
import {
|
||||
alwaysLoadOriginalFile,
|
||||
colorTheme,
|
||||
lang,
|
||||
locale,
|
||||
loopVideo,
|
||||
playVideoThumbnailOnHover,
|
||||
|
|
@ -15,6 +16,8 @@
|
|||
import { findLocale } from '$lib/utils';
|
||||
import { onMount } from 'svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { t, locale as i18nLocale, init } from 'svelte-i18n';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
let time = new Date();
|
||||
|
||||
|
|
@ -62,6 +65,20 @@
|
|||
$locale = $locale ? undefined : fallbackLocale.code;
|
||||
};
|
||||
|
||||
const handleLanguageChange = async (newLang: string | undefined) => {
|
||||
newLang = newLang || fallbackLang;
|
||||
$lang = newLang;
|
||||
|
||||
const previousLang = get(i18nLocale);
|
||||
|
||||
if (newLang === 'dev') {
|
||||
await init({ fallbackLocale: 'dev', initialLocale: 'dev' });
|
||||
} else if (previousLang == 'dev' && newLang !== 'dev') {
|
||||
await init({ fallbackLocale: 'en-US', initialLocale: newLang });
|
||||
}
|
||||
$i18nLocale = newLang;
|
||||
};
|
||||
|
||||
const handleLocaleChange = (newLocale: string | undefined) => {
|
||||
$locale = newLocale;
|
||||
};
|
||||
|
|
@ -72,17 +89,28 @@
|
|||
<div class="ml-4 mt-4 flex flex-col gap-4">
|
||||
<div class="ml-4">
|
||||
<SettingSwitch
|
||||
title="Theme selection"
|
||||
subtitle="Automatically set the theme to light or dark based on your browser's system preference"
|
||||
title={$t('theme_selection')}
|
||||
subtitle={$t('theme_selection_description')}
|
||||
bind:checked={$colorTheme.system}
|
||||
on:toggle={handleToggleColorTheme}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="ml-4">
|
||||
<SettingCombobox
|
||||
comboboxPlaceholder={$t('language')}
|
||||
{selectedOption}
|
||||
options={langs.map((lang) => ({ label: lang.name, value: lang.code }))}
|
||||
title={$t('language')}
|
||||
subtitle={$t('language_setting_description')}
|
||||
onSelect={(combobox) => handleLanguageChange(combobox?.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="ml-4">
|
||||
<SettingSwitch
|
||||
title="Default Locale"
|
||||
subtitle="Format dates and numbers based on your browser locale"
|
||||
title={$t('default_locale')}
|
||||
subtitle={$t('default_locale_description')}
|
||||
checked={$locale == undefined}
|
||||
on:toggle={handleToggleLocaleBrowser}
|
||||
>
|
||||
|
|
@ -92,11 +120,11 @@
|
|||
{#if $locale !== undefined}
|
||||
<div class="ml-4">
|
||||
<SettingCombobox
|
||||
comboboxPlaceholder="Searching locales..."
|
||||
comboboxPlaceholder={$t('searching_locales')}
|
||||
{selectedOption}
|
||||
options={getAllLanguages()}
|
||||
title="Custom Locale"
|
||||
subtitle="Format dates and numbers based on the language and the region"
|
||||
title={$t('custom_locale')}
|
||||
subtitle={$t('custom_locale_description')}
|
||||
onSelect={(combobox) => handleLocaleChange(combobox?.value)}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -104,8 +132,8 @@
|
|||
|
||||
<div class="ml-4">
|
||||
<SettingSwitch
|
||||
title="Display original photos"
|
||||
subtitle="Prefer to display the original photo when viewing an asset rather than thumbnails when the original asset is web-compatible. This may result in slower photo display speeds."
|
||||
title={$t('display_original_photos')}
|
||||
subtitle={$t('display_original_photos_setting_description')}
|
||||
bind:checked={$alwaysLoadOriginalFile}
|
||||
on:toggle={() => ($alwaysLoadOriginalFile = !$alwaysLoadOriginalFile)}
|
||||
/>
|
||||
|
|
@ -113,15 +141,15 @@
|
|||
<div class="ml-4">
|
||||
<SettingSwitch
|
||||
title="Play video thumbnail on hover"
|
||||
subtitle="Play video thumbnail when mouse is hovering over item. Even when disabled, playback can be started by hovering over the play icon."
|
||||
subtitle={$t('video_hover_setting_description')}
|
||||
bind:checked={$playVideoThumbnailOnHover}
|
||||
on:toggle={() => ($playVideoThumbnailOnHover = !$playVideoThumbnailOnHover)}
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<SettingSwitch
|
||||
title="Loop videos"
|
||||
subtitle="Enable to automatically loop a video in the detail viewer."
|
||||
title={$t('loop_videos')}
|
||||
subtitle={$t('loop_videos_description')}
|
||||
bind:checked={$loopVideo}
|
||||
on:toggle={() => ($loopVideo = !$loopVideo)}
|
||||
/>
|
||||
|
|
@ -129,23 +157,23 @@
|
|||
|
||||
<div class="ml-4">
|
||||
<SettingSwitch
|
||||
title="Permanent deletion warning"
|
||||
subtitle="Show a warning when permanently deleting assets"
|
||||
title={$t('permanent_deletion_warning')}
|
||||
subtitle={$t('permanent_deletion_warning_setting_description')}
|
||||
bind:checked={$showDeleteModal}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="ml-4">
|
||||
<SettingSwitch
|
||||
title="People"
|
||||
subtitle="Display a link to People in the sidebar"
|
||||
title={$t('people')}
|
||||
subtitle={$t('people_sidebar_description')}
|
||||
bind:checked={$sidebarSettings.people}
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<SettingSwitch
|
||||
title="Sharing"
|
||||
subtitle="Display a link to Sharing in the sidebar"
|
||||
title={$t('sharing')}
|
||||
subtitle={$t('sharing_sidebar_description')}
|
||||
bind:checked={$sidebarSettings.sharing}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
import SettingInputField, {
|
||||
SettingInputFieldType,
|
||||
} from '$lib/components/shared-components/settings/setting-input-field.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
let password = '';
|
||||
let newPassword = '';
|
||||
|
|
@ -21,7 +22,7 @@
|
|||
await changePassword({ changePasswordDto: { password, newPassword } });
|
||||
|
||||
notificationController.show({
|
||||
message: 'Updated password',
|
||||
message: $t('updated_password'),
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
|
||||
|
|
@ -44,7 +45,7 @@
|
|||
<div class="ml-4 mt-4 flex flex-col gap-4">
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.PASSWORD}
|
||||
label="PASSWORD"
|
||||
label={$t('password').toUpperCase()}
|
||||
bind:value={password}
|
||||
required={true}
|
||||
passwordAutocomplete="current-password"
|
||||
|
|
@ -52,7 +53,7 @@
|
|||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.PASSWORD}
|
||||
label="NEW PASSWORD"
|
||||
label={$t('new_password').toUpperCase()}
|
||||
bind:value={newPassword}
|
||||
required={true}
|
||||
passwordAutocomplete="new-password"
|
||||
|
|
@ -60,7 +61,7 @@
|
|||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.PASSWORD}
|
||||
label="CONFIRM PASSWORD"
|
||||
label={$t('confirm_password').toUpperCase()}
|
||||
bind:value={confirmPassword}
|
||||
required={true}
|
||||
passwordAutocomplete="new-password"
|
||||
|
|
@ -71,7 +72,7 @@
|
|||
type="submit"
|
||||
size="sm"
|
||||
disabled={!(password && newPassword && newPassword === confirmPassword)}
|
||||
on:click={() => handleChangePassword()}>Save</Button
|
||||
on:click={() => handleChangePassword()}>{$t('save')}</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
} from '@mdi/js';
|
||||
import { DateTime, type ToRelativeCalendarOptions } from 'luxon';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let device: SessionResponseDto;
|
||||
|
||||
|
|
@ -52,11 +53,11 @@
|
|||
{#if device.deviceType || device.deviceOS}
|
||||
<span>{device.deviceOS || 'Unknown'} • {device.deviceType || 'Unknown'}</span>
|
||||
{:else}
|
||||
<span>Unknown</span>
|
||||
<span>{$t('unknown')}</span>
|
||||
{/if}
|
||||
</span>
|
||||
<div class="text-sm">
|
||||
<span class="">Last seen</span>
|
||||
<span class="">{$t('last_seen')}</span>
|
||||
<span>{DateTime.fromISO(device.updatedAt, { locale: $locale }).toRelativeCalendar(options)}</span>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400"> - </span>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
||||
|
|
@ -69,7 +70,7 @@
|
|||
<CircleIconButton
|
||||
color="primary"
|
||||
icon={mdiTrashCanOutline}
|
||||
title="Log out"
|
||||
title={$t('log_out')}
|
||||
size="16"
|
||||
on:click={() => dispatcher('delete')}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
import { notificationController, NotificationType } from '../shared-components/notification/notification';
|
||||
import DeviceCard from './device-card.svelte';
|
||||
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let devices: SessionResponseDto[];
|
||||
|
||||
|
|
@ -60,13 +61,17 @@
|
|||
<section class="my-4">
|
||||
{#if currentDevice}
|
||||
<div class="mb-6">
|
||||
<h3 class="mb-2 text-xs font-medium text-immich-primary dark:text-immich-dark-primary">CURRENT DEVICE</h3>
|
||||
<h3 class="mb-2 text-xs font-medium text-immich-primary dark:text-immich-dark-primary">
|
||||
{$t('current_device').toUpperCase()}
|
||||
</h3>
|
||||
<DeviceCard device={currentDevice} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if otherDevices.length > 0}
|
||||
<div class="mb-6">
|
||||
<h3 class="mb-2 text-xs font-medium text-immich-primary dark:text-immich-dark-primary">OTHER DEVICES</h3>
|
||||
<h3 class="mb-2 text-xs font-medium text-immich-primary dark:text-immich-dark-primary">
|
||||
{$t('other_devices').toUpperCase()}
|
||||
</h3>
|
||||
{#each otherDevices as device, index}
|
||||
<DeviceCard {device} on:delete={() => handleDelete(device)} />
|
||||
{#if index !== otherDevices.length - 1}
|
||||
|
|
@ -74,9 +79,11 @@
|
|||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<h3 class="mb-2 text-xs font-medium text-immich-primary dark:text-immich-dark-primary">LOG OUT ALL DEVICES</h3>
|
||||
<h3 class="mb-2 text-xs font-medium text-immich-primary dark:text-immich-dark-primary">
|
||||
{$t('log_out_all_devices').toUpperCase()}
|
||||
</h3>
|
||||
<div class="flex justify-end">
|
||||
<Button color="red" size="sm" on:click={handleDeleteAll}>Log Out All Devices</Button>
|
||||
<Button color="red" size="sm" on:click={handleDeleteAll}>{$t('log_out_all_devices')}</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
|
||||
import { preferences } from '$lib/stores/user.store';
|
||||
import Button from '../elements/buttons/button.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
let memoriesEnabled = $preferences?.memories?.enabled ?? false;
|
||||
|
||||
|
|
@ -18,9 +19,9 @@
|
|||
const data = await updateMyPreferences({ userPreferencesUpdateDto: { memories: { enabled: memoriesEnabled } } });
|
||||
$preferences.memories.enabled = data.memories.enabled;
|
||||
|
||||
notificationController.show({ message: 'Saved settings', type: NotificationType.Info });
|
||||
notificationController.show({ message: $t('saved_settings'), type: NotificationType.Info });
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to update settings');
|
||||
handleError(error, $t('errors.unable_to_update_settings'));
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
@ -31,13 +32,13 @@
|
|||
<div class="ml-4 mt-4 flex flex-col gap-4">
|
||||
<div class="ml-4">
|
||||
<SettingSwitch
|
||||
title="Time-based memories"
|
||||
subtitle="Photos from previous years"
|
||||
title={$t('time_based_memories')}
|
||||
subtitle={$t('photos_from_previous_years')}
|
||||
bind:checked={memoriesEnabled}
|
||||
/>
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
import Button from '../elements/buttons/button.svelte';
|
||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||
import { notificationController, NotificationType } from '../shared-components/notification/notification';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let user: UserAdminResponseDto;
|
||||
|
||||
|
|
@ -22,7 +23,7 @@
|
|||
user = await oauth.link(window.location);
|
||||
|
||||
notificationController.show({
|
||||
message: 'Linked OAuth account',
|
||||
message: $t('linked_oauth_account'),
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
} catch (error) {
|
||||
|
|
@ -39,11 +40,11 @@
|
|||
try {
|
||||
user = await oauth.unlink();
|
||||
notificationController.show({
|
||||
message: 'Unlinked OAuth account',
|
||||
message: $t('unlinked_oauth_account'),
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to unlink account');
|
||||
handleError(error, $t('errors.unable_to_unlink_account'));
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
@ -57,9 +58,9 @@
|
|||
</div>
|
||||
{:else if $featureFlags.oauth}
|
||||
{#if user.oauthId}
|
||||
<Button size="sm" on:click={() => handleUnlink()}>Unlink Oauth</Button>
|
||||
<Button size="sm" on:click={() => handleUnlink()}>{$t('unlink_oauth')}</Button>
|
||||
{:else}
|
||||
<Button size="sm" on:click={() => oauth.authorize(window.location)}>Link to OAuth</Button>
|
||||
<Button size="sm" on:click={() => oauth.authorize(window.location)}>{$t('link_to_oauth')}</Button>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import Button from '../elements/buttons/button.svelte';
|
||||
import UserAvatar from '../shared-components/user-avatar.svelte';
|
||||
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let user: UserResponseDto;
|
||||
export let onClose: () => void;
|
||||
|
|
@ -32,7 +33,7 @@
|
|||
};
|
||||
</script>
|
||||
|
||||
<FullScreenModal title="Add partner" showLogo {onClose}>
|
||||
<FullScreenModal title={$t('add_partner')} showLogo {onClose}>
|
||||
<div class="immich-scrollbar max-h-[300px] overflow-y-auto">
|
||||
{#if availableUsers.length > 0}
|
||||
{#each availableUsers as user}
|
||||
|
|
@ -68,7 +69,7 @@
|
|||
|
||||
{#if selectedUsers.length > 0}
|
||||
<div class="pt-5">
|
||||
<Button size="sm" fullwidth on:click={() => dispatch('add-users', selectedUsers)}>Add</Button>
|
||||
<Button size="sm" fullwidth on:click={() => dispatch('add-users', selectedUsers)}>{$t('add')}</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
import PartnerSelectionModal from './partner-selection-modal.svelte';
|
||||
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
|
||||
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
interface PartnerSharing {
|
||||
user: UserResponseDto;
|
||||
|
|
@ -90,7 +91,7 @@
|
|||
await removePartner({ id: partner.id });
|
||||
await refreshPartners();
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to remove partner');
|
||||
handleError(error, $t('errors.unable_to_remove_partner'));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -103,7 +104,7 @@
|
|||
await refreshPartners();
|
||||
createPartnerFlag = false;
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to add partners');
|
||||
handleError(error, $t('errors.unable_to_add_partners'));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -167,8 +168,8 @@
|
|||
<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>
|
||||
<SettingSwitch
|
||||
title="Show in timeline"
|
||||
subtitle="Show photos and videos from this user in your timeline"
|
||||
title={$t('show_in_timeline')}
|
||||
subtitle={$t('show_in_timeline_setting_description')}
|
||||
bind:checked={partner.inTimeline}
|
||||
on:toggle={({ detail }) => handleShowOnTimelineChanged(partner, detail)}
|
||||
/>
|
||||
|
|
@ -179,7 +180,7 @@
|
|||
{/if}
|
||||
|
||||
<div class="flex justify-end mt-5">
|
||||
<Button size="sm" on:click={() => (createPartnerFlag = true)}>Add partner</Button>
|
||||
<Button size="sm" on:click={() => (createPartnerFlag = true)}>{$t('add_partner')}</Button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
import { NotificationType, notificationController } from '../shared-components/notification/notification';
|
||||
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let keys: ApiKeyResponseDto[];
|
||||
|
||||
|
|
@ -84,8 +85,8 @@
|
|||
|
||||
{#if newKey}
|
||||
<APIKeyForm
|
||||
title="New API key"
|
||||
submitText="Create"
|
||||
title={$t('new_api_key')}
|
||||
submitText={$t('create')}
|
||||
apiKey={newKey}
|
||||
on:submit={({ detail }) => handleCreate(detail)}
|
||||
on:cancel={() => (newKey = null)}
|
||||
|
|
@ -98,8 +99,8 @@
|
|||
|
||||
{#if editKey}
|
||||
<APIKeyForm
|
||||
title="API key"
|
||||
submitText="Save"
|
||||
title={$t('api_key')}
|
||||
submitText={$t('save')}
|
||||
apiKey={editKey}
|
||||
on:submit={({ detail }) => handleUpdate(detail)}
|
||||
on:cancel={() => (editKey = null)}
|
||||
|
|
@ -109,7 +110,7 @@
|
|||
<section class="my-4">
|
||||
<div class="flex flex-col gap-2" in:fade={{ duration: 500 }}>
|
||||
<div class="mb-2 flex justify-end">
|
||||
<Button size="sm" on:click={() => (newKey = { name: 'API Key' })}>New API Key</Button>
|
||||
<Button size="sm" on:click={() => (newKey = { name: $t('api_key') })}>{$t('new_api_key')}</Button>
|
||||
</div>
|
||||
|
||||
{#if keys.length > 0}
|
||||
|
|
@ -118,9 +119,9 @@
|
|||
class="mb-4 flex h-12 w-full rounded-md border bg-gray-50 text-immich-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-primary"
|
||||
>
|
||||
<tr class="flex w-full place-items-center">
|
||||
<th class="w-1/3 text-center text-sm font-medium">Name</th>
|
||||
<th class="w-1/3 text-center text-sm font-medium">Created</th>
|
||||
<th class="w-1/3 text-center text-sm font-medium">Action</th>
|
||||
<th class="w-1/3 text-center text-sm font-medium">{$t('name')}</th>
|
||||
<th class="w-1/3 text-center text-sm font-medium">{$t('created')}</th>
|
||||
<th class="w-1/3 text-center text-sm font-medium">{$t('action')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="block w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray">
|
||||
|
|
@ -141,14 +142,14 @@
|
|||
<CircleIconButton
|
||||
color="primary"
|
||||
icon={mdiPencilOutline}
|
||||
title="Edit key"
|
||||
title={$t('edit_key')}
|
||||
size="16"
|
||||
on:click={() => (editKey = key)}
|
||||
/>
|
||||
<CircleIconButton
|
||||
color="primary"
|
||||
icon={mdiTrashCanOutline}
|
||||
title="Delete key"
|
||||
title={$t('delete_key')}
|
||||
size="16"
|
||||
on:click={() => handleDelete(key)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
import { fade } from 'svelte/transition';
|
||||
import { handleError } from '../../utils/handle-error';
|
||||
import Button from '../elements/buttons/button.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
let editedUser = cloneDeep($user);
|
||||
|
||||
|
|
@ -28,11 +29,11 @@
|
|||
$user = data;
|
||||
|
||||
notificationController.show({
|
||||
message: 'Saved profile',
|
||||
message: $t('saved_profile'),
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to save profile');
|
||||
handleError(error, $t('errors.unable_to_save_profile'));
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
@ -43,30 +44,34 @@
|
|||
<div class="ml-4 mt-4 flex flex-col gap-4">
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="USER ID"
|
||||
label={$t('user_id').toUpperCase()}
|
||||
bind:value={editedUser.id}
|
||||
disabled={true}
|
||||
/>
|
||||
|
||||
<SettingInputField inputType={SettingInputFieldType.EMAIL} label="EMAIL" bind:value={editedUser.email} />
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.EMAIL}
|
||||
label={$t('email').toUpperCase()}
|
||||
bind:value={editedUser.email}
|
||||
/>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="NAME"
|
||||
label={$t('name').toUpperCase()}
|
||||
bind:value={editedUser.name}
|
||||
required={true}
|
||||
/>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="STORAGE LABEL"
|
||||
label={$t('storage_label').toUpperCase()}
|
||||
disabled={true}
|
||||
value={editedUser.storageLabel || ''}
|
||||
required={false}
|
||||
/>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<Button type="submit" size="sm" on:click={() => handleSaveProfile()}>Save</Button>
|
||||
<Button type="submit" size="sm" on:click={() => handleSaveProfile()}>{$t('save')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
import UserAPIKeyList from './user-api-key-list.svelte';
|
||||
import UserProfileSettings from './user-profile-settings.svelte';
|
||||
import NotificationsSettings from '$lib/components/user-settings-page/notifications-settings.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let keys: ApiKeyResponseDto[] = [];
|
||||
export let sessions: SessionResponseDto[] = [];
|
||||
|
|
@ -26,23 +27,23 @@
|
|||
</script>
|
||||
|
||||
<SettingAccordionState queryParam={QueryParameter.IS_OPEN}>
|
||||
<SettingAccordion key="app-settings" title="App Settings" subtitle="Manage the app settings">
|
||||
<SettingAccordion key="app-settings" title={$t('app_settings')} subtitle={$t('manage_the_app_settings')}>
|
||||
<AppSettings />
|
||||
</SettingAccordion>
|
||||
|
||||
<SettingAccordion key="account" title="Account" subtitle="Manage your account">
|
||||
<SettingAccordion key="account" title={$t('account')} subtitle={$t('manage_your_account')}>
|
||||
<UserProfileSettings />
|
||||
</SettingAccordion>
|
||||
|
||||
<SettingAccordion key="api-keys" title="API Keys" subtitle="Manage your API keys">
|
||||
<SettingAccordion key="api-keys" title={$t('api_keys')} subtitle={$t('manage_your_api_keys')}>
|
||||
<UserAPIKeyList bind:keys />
|
||||
</SettingAccordion>
|
||||
|
||||
<SettingAccordion key="authorized-devices" title="Authorized Devices" subtitle="Manage your logged-in devices">
|
||||
<SettingAccordion key="authorized-devices" title={$t('authorized_devices')} subtitle={$t('manage_your_devices')}>
|
||||
<DeviceList bind:devices={sessions} />
|
||||
</SettingAccordion>
|
||||
|
||||
<SettingAccordion key="memories" title="Memories" subtitle="Manage what you see in your memories">
|
||||
<SettingAccordion key="memories" title={$t('memories')} subtitle={$t('memories_setting_description')}>
|
||||
<MemoriesSettings />
|
||||
</SettingAccordion>
|
||||
|
||||
|
|
@ -51,16 +52,21 @@
|
|||
</SettingAccordion>
|
||||
|
||||
{#if $featureFlags.loaded && $featureFlags.oauth}
|
||||
<SettingAccordion key="oauth" title="OAuth" subtitle="Manage your OAuth connection" isOpen={oauthOpen || undefined}>
|
||||
<SettingAccordion
|
||||
key="oauth"
|
||||
title={$t('oauth')}
|
||||
subtitle={$t('manage_your_oauth_connection')}
|
||||
isOpen={oauthOpen || undefined}
|
||||
>
|
||||
<OAuthSettings user={$user} />
|
||||
</SettingAccordion>
|
||||
{/if}
|
||||
|
||||
<SettingAccordion key="password" title="Password" subtitle="Change your password">
|
||||
<SettingAccordion key="password" title={$t('password')} subtitle={$t('change_your_password')}>
|
||||
<ChangePasswordSettings />
|
||||
</SettingAccordion>
|
||||
|
||||
<SettingAccordion key="partner-sharing" title="Partner Sharing" subtitle="Manage sharing with partners">
|
||||
<SettingAccordion key="partner-sharing" title={$t('partner_sharing')} subtitle={$t('manage_sharing_with_partners')}>
|
||||
<PartnerSettings user={$user} />
|
||||
</SettingAccordion>
|
||||
</SettingAccordionState>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue