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:
@@ -8,6 +8,7 @@
|
||||
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
||||
import GroupTab from '$lib/components/elements/group-tab.svelte';
|
||||
import SearchBar from '$lib/components/elements/search-bar.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -29,7 +30,7 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="w-60">
|
||||
<SearchBar placeholder="Search albums" bind:name={searchQuery} showLoadingSpinner={false} />
|
||||
<SearchBar placeholder={$t('search_albums')} bind:name={searchQuery} showLoadingSpinner={false} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -41,10 +42,6 @@
|
||||
{searchQuery}
|
||||
bind:albumGroupIds={albumGroups}
|
||||
>
|
||||
<EmptyPlaceholder
|
||||
slot="empty"
|
||||
text="Create an album to organize your photos and videos"
|
||||
onClick={() => createAlbumAndRedirect()}
|
||||
/>
|
||||
<EmptyPlaceholder slot="empty" text={$t('no_albums_message')} onClick={() => createAlbumAndRedirect()} />
|
||||
</Albums>
|
||||
</UserPageLayout>
|
||||
|
||||
+34
-21
@@ -81,6 +81,7 @@
|
||||
import { fly } from 'svelte/transition';
|
||||
import type { PageData } from './$types';
|
||||
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -210,7 +211,7 @@
|
||||
isLiked = data[0];
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, "Can't get Favorite");
|
||||
handleError(error, $t('errors.unable_to_load_liked_status'));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -340,7 +341,7 @@
|
||||
await refreshAlbum();
|
||||
viewMode = album.albumUsers.length > 0 ? ViewMode.VIEW_USERS : ViewMode.VIEW;
|
||||
} catch (error) {
|
||||
handleError(error, 'Error deleting shared user');
|
||||
handleError(error, $t('errors.unable_to_load_album'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -363,7 +364,7 @@
|
||||
await deleteAlbum({ id: album.id });
|
||||
await goto(backUrl);
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to delete album');
|
||||
handleError(error, $t('unable_to_delete_album'));
|
||||
} finally {
|
||||
viewMode = ViewMode.VIEW;
|
||||
}
|
||||
@@ -419,21 +420,21 @@
|
||||
<AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}>
|
||||
<CreateSharedLink />
|
||||
<SelectAllAssets {assetStore} {assetInteractionStore} />
|
||||
<AssetSelectContextMenu icon={mdiPlus} title="Add to...">
|
||||
<AssetSelectContextMenu icon={mdiPlus} title={$t('add_to')}>
|
||||
<AddToAlbum />
|
||||
<AddToAlbum shared />
|
||||
</AssetSelectContextMenu>
|
||||
{#if isAllUserOwned}
|
||||
<FavoriteAction removeFavorite={isAllFavorite} onFavorite={() => assetStore.triggerUpdate()} />
|
||||
{/if}
|
||||
<AssetSelectContextMenu icon={mdiDotsVertical} title="Menu">
|
||||
<AssetSelectContextMenu icon={mdiDotsVertical} title={$t('menu')}>
|
||||
<DownloadAction menuItem filename="{album.albumName}.zip" />
|
||||
{#if isAllUserOwned}
|
||||
<ChangeDate menuItem />
|
||||
<ChangeLocation menuItem />
|
||||
{#if $selectedAssets.size === 1}
|
||||
<MenuOption
|
||||
text="Set as album cover"
|
||||
text={$t('set_as_album_cover')}
|
||||
icon={mdiImageOutline}
|
||||
on:click={() => updateThumbnailUsingCurrentSelection()}
|
||||
/>
|
||||
@@ -454,7 +455,7 @@
|
||||
<svelte:fragment slot="trailing">
|
||||
{#if isEditor}
|
||||
<CircleIconButton
|
||||
title="Add photos"
|
||||
title={$t('add_photos')}
|
||||
on:click={() => (viewMode = ViewMode.SELECT_ASSETS)}
|
||||
icon={mdiImagePlusOutline}
|
||||
/>
|
||||
@@ -462,28 +463,40 @@
|
||||
|
||||
{#if isOwned}
|
||||
<CircleIconButton
|
||||
title="Share"
|
||||
title={$t('share')}
|
||||
on:click={() => (viewMode = ViewMode.SELECT_USERS)}
|
||||
icon={mdiShareVariantOutline}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if album.assetCount > 0}
|
||||
<CircleIconButton title="Slideshow" on:click={handleStartSlideshow} icon={mdiPresentationPlay} />
|
||||
<CircleIconButton title="Download" on:click={handleDownloadAlbum} icon={mdiFolderDownloadOutline} />
|
||||
<CircleIconButton title={$t('slideshow')} on:click={handleStartSlideshow} icon={mdiPresentationPlay} />
|
||||
<CircleIconButton title={$t('download')} on:click={handleDownloadAlbum} icon={mdiFolderDownloadOutline} />
|
||||
|
||||
{#if isOwned}
|
||||
<div use:clickOutside={{ onOutclick: () => (viewMode = ViewMode.VIEW) }}>
|
||||
<CircleIconButton title="Album options" on:click={handleOpenAlbumOptions} icon={mdiDotsVertical} />
|
||||
<CircleIconButton
|
||||
title={$t('album_options')}
|
||||
on:click={handleOpenAlbumOptions}
|
||||
icon={mdiDotsVertical}
|
||||
/>
|
||||
{#if viewMode === ViewMode.ALBUM_OPTIONS}
|
||||
<ContextMenu {...contextMenuPosition}>
|
||||
<MenuOption
|
||||
icon={mdiImageOutline}
|
||||
text="Select album cover"
|
||||
text={$t('select_album_cover')}
|
||||
on:click={() => (viewMode = ViewMode.SELECT_THUMBNAIL)}
|
||||
/>
|
||||
<MenuOption icon={mdiCogOutline} text="Options" on:click={() => (viewMode = ViewMode.OPTIONS)} />
|
||||
<MenuOption icon={mdiDeleteOutline} text="Delete album" on:click={() => handleRemoveAlbum()} />
|
||||
<MenuOption
|
||||
icon={mdiCogOutline}
|
||||
text={$t('options')}
|
||||
on:click={() => (viewMode = ViewMode.OPTIONS)}
|
||||
/>
|
||||
<MenuOption
|
||||
icon={mdiDeleteOutline}
|
||||
text={$t('delete_album')}
|
||||
on:click={() => handleRemoveAlbum()}
|
||||
/>
|
||||
</ContextMenu>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -525,7 +538,7 @@
|
||||
Select from computer
|
||||
</button>
|
||||
<Button size="sm" rounded="lg" disabled={$timelineSelected.size === 0} on:click={handleAddAssets}
|
||||
>Done</Button
|
||||
>{$t('done')}</Button
|
||||
>
|
||||
</svelte:fragment>
|
||||
</ControlAppBar>
|
||||
@@ -533,7 +546,7 @@
|
||||
|
||||
{#if viewMode === ViewMode.SELECT_THUMBNAIL}
|
||||
<ControlAppBar on:close={() => (viewMode = ViewMode.VIEW)}>
|
||||
<svelte:fragment slot="leading">Select Album Cover</svelte:fragment>
|
||||
<svelte:fragment slot="leading">{$t('select_album_cover')}</svelte:fragment>
|
||||
</ControlAppBar>
|
||||
{/if}
|
||||
{/if}
|
||||
@@ -577,7 +590,7 @@
|
||||
<!-- link -->
|
||||
{#if album.hasSharedLink && isOwned}
|
||||
<CircleIconButton
|
||||
title="Create link to share"
|
||||
title={$t('create_link_to_share')}
|
||||
color="gray"
|
||||
size="20"
|
||||
icon={mdiLink}
|
||||
@@ -600,7 +613,7 @@
|
||||
<!-- display ellipsis if there are readonly users too -->
|
||||
{#if albumHasViewers}
|
||||
<CircleIconButton
|
||||
title="View all users"
|
||||
title={$t('view_all_users')}
|
||||
color="gray"
|
||||
size="20"
|
||||
icon={mdiDotsVertical}
|
||||
@@ -614,7 +627,7 @@
|
||||
size="20"
|
||||
icon={mdiPlus}
|
||||
on:click={() => (viewMode = ViewMode.SELECT_USERS)}
|
||||
title="Add more users"
|
||||
title={$t('add_more_users')}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -627,7 +640,7 @@
|
||||
{#if album.assetCount === 0}
|
||||
<section id="empty-album" class=" mt-[200px] flex place-content-center place-items-center">
|
||||
<div class="w-[300px]">
|
||||
<p class="text-xs dark:text-immich-dark-fg">ADD PHOTOS</p>
|
||||
<p class="text-xs dark:text-immich-dark-fg">{$t('add_photos').toUpperCase()}</p>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => (viewMode = ViewMode.SELECT_ASSETS)}
|
||||
@@ -636,7 +649,7 @@
|
||||
<span class="text-text-immich-primary dark:text-immich-dark-primary"
|
||||
><Icon path={mdiPlus} size="24" />
|
||||
</span>
|
||||
<span class="text-lg">Select photos</span>
|
||||
<span class="text-lg">{$t('select_photos')}</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import { AssetStore } from '$lib/stores/assets.store';
|
||||
import type { PageData } from './$types';
|
||||
import { mdiPlus, mdiDotsVertical } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -31,12 +32,12 @@
|
||||
<ArchiveAction unarchive onArchive={(assetIds) => assetStore.removeAssets(assetIds)} />
|
||||
<CreateSharedLink />
|
||||
<SelectAllAssets {assetStore} {assetInteractionStore} />
|
||||
<AssetSelectContextMenu icon={mdiPlus} title="Add to...">
|
||||
<AssetSelectContextMenu icon={mdiPlus} title={$t('add_to')}>
|
||||
<AddToAlbum />
|
||||
<AddToAlbum shared />
|
||||
</AssetSelectContextMenu>
|
||||
<FavoriteAction removeFavorite={isAllFavorite} onFavorite={() => assetStore.triggerUpdate()} />
|
||||
<AssetSelectContextMenu icon={mdiDotsVertical} title="Add">
|
||||
<AssetSelectContextMenu icon={mdiDotsVertical} title={$t('add')}>
|
||||
<DownloadAction menuItem />
|
||||
<DeleteAssets menuItem onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)} />
|
||||
</AssetSelectContextMenu>
|
||||
@@ -45,6 +46,6 @@
|
||||
|
||||
<UserPageLayout hideNavbar={$isMultiSelectState} title={data.meta.title} scrollbar={false}>
|
||||
<AssetGrid {assetStore} {assetInteractionStore} removeAction={AssetAction.UNARCHIVE}>
|
||||
<EmptyPlaceholder text="Archive photos and videos to hide them from your Photos view" slot="empty" />
|
||||
<EmptyPlaceholder text={$t('no_archived_assets_message')} slot="empty" />
|
||||
</AssetGrid>
|
||||
</UserPageLayout>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import type { SearchExploreResponseDto } from '@immich/sdk';
|
||||
import type { PageData } from './$types';
|
||||
import { getMetadataSearchQuery } from '$lib/utils/metadata-search';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -43,11 +44,11 @@
|
||||
{#if hasPeople}
|
||||
<div class="mb-6 mt-2">
|
||||
<div class="flex justify-between">
|
||||
<p class="mb-4 font-medium dark:text-immich-dark-fg">People</p>
|
||||
<p class="mb-4 font-medium dark:text-immich-dark-fg">{$t('people')}</p>
|
||||
<a
|
||||
href={AppRoute.PEOPLE}
|
||||
class="pr-4 text-sm font-medium hover:text-immich-primary dark:text-immich-dark-fg dark:hover:text-immich-dark-primary"
|
||||
draggable="false">View All</a
|
||||
draggable="false">{$t('view_all')}</a
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
@@ -75,11 +76,11 @@
|
||||
{#if places.length > 0}
|
||||
<div class="mb-6 mt-2">
|
||||
<div class="flex justify-between">
|
||||
<p class="mb-4 font-medium dark:text-immich-dark-fg">Places</p>
|
||||
<p class="mb-4 font-medium dark:text-immich-dark-fg">{$t('places')}</p>
|
||||
<a
|
||||
href={AppRoute.PLACES}
|
||||
class="pr-4 text-sm font-medium hover:text-immich-primary dark:text-immich-dark-fg dark:hover:text-immich-dark-primary"
|
||||
draggable="false">View All</a
|
||||
draggable="false">{$t('view_all')}</a
|
||||
>
|
||||
</div>
|
||||
<div class="flex flex-row flex-wrap gap-4">
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
import { AssetStore } from '$lib/stores/assets.store';
|
||||
import type { PageData } from './$types';
|
||||
import { mdiDotsVertical, mdiPlus } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -34,11 +35,11 @@
|
||||
<FavoriteAction removeFavorite onFavorite={(assetIds) => assetStore.removeAssets(assetIds)} />
|
||||
<CreateSharedLink />
|
||||
<SelectAllAssets {assetStore} {assetInteractionStore} />
|
||||
<AssetSelectContextMenu icon={mdiPlus} title="Add to...">
|
||||
<AssetSelectContextMenu icon={mdiPlus} title={$t('add_to')}>
|
||||
<AddToAlbum />
|
||||
<AddToAlbum shared />
|
||||
</AssetSelectContextMenu>
|
||||
<AssetSelectContextMenu icon={mdiDotsVertical} title="Menu">
|
||||
<AssetSelectContextMenu icon={mdiDotsVertical} title={$t('menu')}>
|
||||
<DownloadAction menuItem />
|
||||
<ChangeDate menuItem />
|
||||
<ChangeLocation menuItem />
|
||||
@@ -50,6 +51,6 @@
|
||||
|
||||
<UserPageLayout hideNavbar={$isMultiSelectState} title={data.meta.title} scrollbar={false}>
|
||||
<AssetGrid {assetStore} {assetInteractionStore} removeAction={AssetAction.UNFAVORITE}>
|
||||
<EmptyPlaceholder text="Add favorites to quickly find your best pictures and videos" slot="empty" />
|
||||
<EmptyPlaceholder text={$t('no_favorites_message')} slot="empty" />
|
||||
</AssetGrid>
|
||||
</UserPageLayout>
|
||||
|
||||
+2
-1
@@ -13,6 +13,7 @@
|
||||
import { onDestroy } from 'svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { mdiPlus, mdiArrowLeft } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -29,7 +30,7 @@
|
||||
{#if $isMultiSelectState}
|
||||
<AssetSelectControlBar assets={$selectedAssets} clearSelect={clearMultiselect}>
|
||||
<CreateSharedLink />
|
||||
<AssetSelectContextMenu icon={mdiPlus} title="Add">
|
||||
<AssetSelectContextMenu icon={mdiPlus} title={$t('add')}>
|
||||
<AddToAlbum />
|
||||
<AddToAlbum shared />
|
||||
</AssetSelectContextMenu>
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
import { clearQueryParam } from '$lib/utils/navigation';
|
||||
import SearchPeople from '$lib/components/faces-page/people-search.svelte';
|
||||
import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -204,11 +205,11 @@
|
||||
}
|
||||
countTotalPeople--;
|
||||
notificationController.show({
|
||||
message: 'Merge people successfully',
|
||||
message: $t('merge_people_successfully'),
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to save name');
|
||||
handleError(error, $t('unable_to_save_name'));
|
||||
}
|
||||
if (personToBeMergedIn.name !== personName && edittingPerson.id === personToBeMergedIn.id) {
|
||||
/*
|
||||
@@ -227,14 +228,14 @@
|
||||
}
|
||||
}
|
||||
notificationController.show({
|
||||
message: 'Change name successfully',
|
||||
message: $t('change_name_successfully'),
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
|
||||
// trigger reactivity
|
||||
people = people;
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to save name');
|
||||
handleError(error, $t('unable_to_save_name'));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -274,11 +275,11 @@
|
||||
showChangeNameModal = false;
|
||||
countHiddenPeople++;
|
||||
notificationController.show({
|
||||
message: 'Changed visibility successfully',
|
||||
message: $t('changed_visibility_successfully'),
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to hide person');
|
||||
handleError(error, $t('unable_to_hide_person'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -349,7 +350,7 @@
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to save name');
|
||||
handleError(error, $t('unable_to_save_name'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -372,11 +373,11 @@
|
||||
return person;
|
||||
});
|
||||
notificationController.show({
|
||||
message: 'Change name successfully',
|
||||
message: $t('change_name_successfully'),
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to save name');
|
||||
handleError(error, $t('unable_to_save_name'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -399,7 +400,7 @@
|
||||
{/if}
|
||||
|
||||
<UserPageLayout
|
||||
title="People"
|
||||
title={$t('people')}
|
||||
description={countVisiblePeople === 0 && !searchName ? undefined : `(${countVisiblePeople.toLocaleString($locale)})`}
|
||||
>
|
||||
<svelte:fragment slot="buttons">
|
||||
@@ -409,7 +410,7 @@
|
||||
<div class="w-40 lg:w-80 h-10">
|
||||
<SearchPeople
|
||||
type="searchBar"
|
||||
placeholder="Search people"
|
||||
placeholder={$t('search_people')}
|
||||
onReset={onResetSearchBar}
|
||||
onSearch={handleSearch}
|
||||
bind:searchName
|
||||
@@ -453,10 +454,10 @@
|
||||
{/if}
|
||||
|
||||
{#if showChangeNameModal}
|
||||
<FullScreenModal title="Change name" onClose={() => (showChangeNameModal = false)}>
|
||||
<FullScreenModal title={$t('change_name')} onClose={() => (showChangeNameModal = false)}>
|
||||
<form on:submit|preventDefault={submitNameChange} autocomplete="off" id="change-name-form">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="immich-form-label" for="name">Name</label>
|
||||
<label class="immich-form-label" for="name">{$t('name')}</label>
|
||||
<input
|
||||
class="immich-form-input"
|
||||
id="name"
|
||||
@@ -473,9 +474,9 @@
|
||||
fullwidth
|
||||
on:click={() => {
|
||||
showChangeNameModal = false;
|
||||
}}>Cancel</Button
|
||||
}}>{$t('cancel')}</Button
|
||||
>
|
||||
<Button type="submit" fullwidth form="change-name-form">Ok</Button>
|
||||
<Button type="submit" fullwidth form="change-name-form">{$t('ok')}</Button>
|
||||
</svelte:fragment>
|
||||
</FullScreenModal>
|
||||
{/if}
|
||||
|
||||
+23
-18
@@ -56,6 +56,7 @@
|
||||
import { onMount } from 'svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { listNavigation } from '$lib/actions/list-navigation';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -182,13 +183,13 @@
|
||||
});
|
||||
|
||||
notificationController.show({
|
||||
message: 'Changed visibility successfully',
|
||||
message: $t('changed_visibility_successfully'),
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
|
||||
await goto(previousRoute, { replaceState: true });
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to hide person');
|
||||
handleError(error, $t('unable_to_hide_person'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -208,7 +209,7 @@
|
||||
|
||||
await updatePerson({ id: data.person.id, personUpdateDto: { featureFaceAssetId: asset.id } });
|
||||
|
||||
notificationController.show({ message: 'Feature photo updated', type: NotificationType.Info });
|
||||
notificationController.show({ message: $t('feature_photo_updated'), type: NotificationType.Info });
|
||||
assetInteractionStore.clearMultiselect();
|
||||
|
||||
viewMode = ViewMode.VIEW_ASSETS;
|
||||
@@ -224,7 +225,7 @@
|
||||
mergePersonDto: { ids: [personToMerge.id] },
|
||||
});
|
||||
notificationController.show({
|
||||
message: 'Merge people successfully',
|
||||
message: $t('merge_people_successfully'),
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
people = people.filter((person: PersonResponseDto) => person.id !== personToMerge.id);
|
||||
@@ -235,7 +236,7 @@
|
||||
}
|
||||
await goto(`${AppRoute.PEOPLE}/${personToBeMergedIn.id}`, { replaceState: true });
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to save name');
|
||||
handleError(error, $t('unable_to_save_name'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -257,11 +258,11 @@
|
||||
await updatePerson({ id: data.person.id, personUpdateDto: { name: personName } });
|
||||
|
||||
notificationController.show({
|
||||
message: 'Change name successfully',
|
||||
message: $t('change_name_successfully'),
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to save name');
|
||||
handleError(error, $t('unable_to_save_name'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -379,14 +380,18 @@
|
||||
<AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}>
|
||||
<CreateSharedLink />
|
||||
<SelectAllAssets {assetStore} {assetInteractionStore} />
|
||||
<AssetSelectContextMenu icon={mdiPlus} title="Add to...">
|
||||
<AssetSelectContextMenu icon={mdiPlus} title={$t('add_to')}>
|
||||
<AddToAlbum />
|
||||
<AddToAlbum shared />
|
||||
</AssetSelectContextMenu>
|
||||
<FavoriteAction removeFavorite={isAllFavorite} onFavorite={() => assetStore.triggerUpdate()} />
|
||||
<AssetSelectContextMenu icon={mdiDotsVertical} title="Add">
|
||||
<AssetSelectContextMenu icon={mdiDotsVertical} title={$t('add')}>
|
||||
<DownloadAction menuItem filename="{data.person.name || 'immich'}.zip" />
|
||||
<MenuOption icon={mdiAccountMultipleCheckOutline} text="Fix incorrect match" on:click={handleReassignAssets} />
|
||||
<MenuOption
|
||||
icon={mdiAccountMultipleCheckOutline}
|
||||
text={$t('fix_incorrect_match')}
|
||||
on:click={handleReassignAssets}
|
||||
/>
|
||||
<ChangeDate menuItem />
|
||||
<ChangeLocation menuItem />
|
||||
<ArchiveAction menuItem unarchive={isAllArchive} onArchive={(assetIds) => $assetStore.removeAssets(assetIds)} />
|
||||
@@ -397,24 +402,24 @@
|
||||
{#if viewMode === ViewMode.VIEW_ASSETS || viewMode === ViewMode.SUGGEST_MERGE || viewMode === ViewMode.BIRTH_DATE}
|
||||
<ControlAppBar showBackButton backIcon={mdiArrowLeft} on:close={() => goto(previousRoute)}>
|
||||
<svelte:fragment slot="trailing">
|
||||
<AssetSelectContextMenu icon={mdiDotsVertical} title="Menu">
|
||||
<AssetSelectContextMenu icon={mdiDotsVertical} title={$t('menu')}>
|
||||
<MenuOption
|
||||
text="Select featured photo"
|
||||
text={$t('select_featured_photo')}
|
||||
icon={mdiAccountBoxOutline}
|
||||
on:click={() => (viewMode = ViewMode.SELECT_PERSON)}
|
||||
/>
|
||||
<MenuOption
|
||||
text={data.person.isHidden ? 'Unhide person' : 'Hide person'}
|
||||
text={data.person.isHidden ? $t('unhide_person') : $t('hide_person')}
|
||||
icon={data.person.isHidden ? mdiEyeOutline : mdiEyeOffOutline}
|
||||
on:click={() => toggleHidePerson()}
|
||||
/>
|
||||
<MenuOption
|
||||
text="Set date of birth"
|
||||
text={$t('set_date_of_birth')}
|
||||
icon={mdiCalendarEditOutline}
|
||||
on:click={() => (viewMode = ViewMode.BIRTH_DATE)}
|
||||
/>
|
||||
<MenuOption
|
||||
text="Merge people"
|
||||
text={$t('merge_people')}
|
||||
icon={mdiAccountMultipleCheckOutline}
|
||||
on:click={() => (viewMode = ViewMode.MERGE_PEOPLE)}
|
||||
/>
|
||||
@@ -425,7 +430,7 @@
|
||||
|
||||
{#if viewMode === ViewMode.SELECT_PERSON}
|
||||
<ControlAppBar on:close={() => (viewMode = ViewMode.VIEW_ASSETS)}>
|
||||
<svelte:fragment slot="leading">Select featured photo</svelte:fragment>
|
||||
<svelte:fragment slot="leading">{$t('select_featured_photo')}</svelte:fragment>
|
||||
</ControlAppBar>
|
||||
{/if}
|
||||
{/if}
|
||||
@@ -466,7 +471,7 @@
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center justify-center"
|
||||
title="Edit name"
|
||||
title={$t('edit_name')}
|
||||
on:click={() => (isEditingName = true)}
|
||||
>
|
||||
<ImageThumbnail
|
||||
@@ -486,7 +491,7 @@
|
||||
{`${numberOfAssets} asset${s(numberOfAssets)}`}
|
||||
</p>
|
||||
{:else}
|
||||
<p class="font-medium">Add a name</p>
|
||||
<p class="font-medium">{$t('add_a_name')}</p>
|
||||
<p class="text-sm text-gray-500 dark:text-immich-gray">Find them fast by name with search</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||
import { mdiDotsVertical, mdiPlus } from '@mdi/js';
|
||||
import { preferences, user } from '$lib/stores/user.store';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
let { isViewing: showAssetViewer } = assetViewingStore;
|
||||
const assetStore = new AssetStore({ isArchived: false, withStacked: true, withPartners: true });
|
||||
@@ -57,12 +58,12 @@
|
||||
>
|
||||
<CreateSharedLink />
|
||||
<SelectAllAssets {assetStore} {assetInteractionStore} />
|
||||
<AssetSelectContextMenu icon={mdiPlus} title="Add to...">
|
||||
<AssetSelectContextMenu icon={mdiPlus} title={$t('add_to')}>
|
||||
<AddToAlbum />
|
||||
<AddToAlbum shared />
|
||||
</AssetSelectContextMenu>
|
||||
<FavoriteAction removeFavorite={isAllFavorite} onFavorite={() => assetStore.triggerUpdate()} />
|
||||
<AssetSelectContextMenu icon={mdiDotsVertical} title="Menu">
|
||||
<AssetSelectContextMenu icon={mdiDotsVertical} title={$t('menu')}>
|
||||
<DownloadAction menuItem />
|
||||
{#if $selectedAssets.size > 1 || isAssetStackSelected}
|
||||
<StackAction
|
||||
@@ -92,6 +93,6 @@
|
||||
{#if $preferences.memories.enabled}
|
||||
<MemoryLane />
|
||||
{/if}
|
||||
<EmptyPlaceholder text="CLICK TO UPLOAD YOUR FIRST PHOTO" onClick={() => openFileUploadDialog()} slot="empty" />
|
||||
<EmptyPlaceholder text={$t('no_assets_message')} onClick={() => openFileUploadDialog()} slot="empty" />
|
||||
</AssetGrid>
|
||||
</UserPageLayout>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import type { PageData } from './$types';
|
||||
import { getMetadataSearchQuery } from '$lib/utils/metadata-search';
|
||||
import type { AssetResponseDto } from '@immich/sdk';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -24,7 +25,7 @@
|
||||
|
||||
<svelte:window bind:innerHeight />
|
||||
|
||||
<UserPageLayout title="Places">
|
||||
<UserPageLayout title={$t('places')}>
|
||||
{#if hasPlaces}
|
||||
<div class="flex flex-row flex-wrap gap-4">
|
||||
{#each places as item (item.id)}
|
||||
@@ -47,7 +48,7 @@
|
||||
<div class="flex min-h-[calc(66vh_-_11rem)] w-full place-content-center items-center dark:text-white">
|
||||
<div class="flex flex-col content-center items-center text-center">
|
||||
<Icon path={mdiMapMarkerOff} size="3.5em" />
|
||||
<p class="mt-5 text-3xl font-medium">No places</p>
|
||||
<p class="mt-5 text-3xl font-medium">{$t('no_places')}</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import AlbumCardGroup from '$lib/components/album-page/album-card-group.svelte';
|
||||
import { isAlbumsRoute, isPeopleRoute } from '$lib/utils/navigation';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
const MAX_ASSET_COUNT = 5000;
|
||||
let { isViewing: showAssetViewer } = assetViewingStore;
|
||||
@@ -141,7 +142,7 @@
|
||||
|
||||
nextPage = assets.nextPage ? Number(assets.nextPage) : null;
|
||||
} catch (error) {
|
||||
handleError(error, 'Loading search results failed');
|
||||
handleError(error, $t('loading_search_results_failed'));
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
@@ -161,20 +162,20 @@
|
||||
|
||||
function getHumanReadableSearchKey(key: keyof SearchTerms): string {
|
||||
const keyMap: Partial<Record<keyof SearchTerms, string>> = {
|
||||
takenAfter: 'Start date',
|
||||
takenBefore: 'End date',
|
||||
isArchived: 'In archive',
|
||||
isFavorite: 'Favorite',
|
||||
isNotInAlbum: 'Not in any album',
|
||||
type: 'Media type',
|
||||
query: 'Context',
|
||||
city: 'City',
|
||||
country: 'Country',
|
||||
state: 'State',
|
||||
make: 'Camera make',
|
||||
model: 'Camera model',
|
||||
personIds: 'People',
|
||||
originalFileName: 'File name',
|
||||
takenAfter: $t('start_date'),
|
||||
takenBefore: $t('end_date'),
|
||||
isArchived: $t('in_archive'),
|
||||
isFavorite: $t('favorite'),
|
||||
isNotInAlbum: $t('not_in_any_album'),
|
||||
type: $t('media_type'),
|
||||
query: $t('context'),
|
||||
city: $t('city'),
|
||||
country: $t('country'),
|
||||
state: $t('state'),
|
||||
make: $t('camera_brand'),
|
||||
model: $t('camera_model'),
|
||||
personIds: $t('people'),
|
||||
originalFileName: $t('file_name'),
|
||||
};
|
||||
return keyMap[key] || key;
|
||||
}
|
||||
@@ -185,7 +186,7 @@
|
||||
const person = await getPerson({ id: personId });
|
||||
|
||||
if (person.name == '') {
|
||||
return 'No Name';
|
||||
return $t('no_name');
|
||||
}
|
||||
|
||||
return person.name;
|
||||
@@ -209,14 +210,14 @@
|
||||
<div class="fixed z-[100] top-0 left-0 w-full">
|
||||
<AssetSelectControlBar assets={selectedAssets} clearSelect={() => (selectedAssets = new Set())}>
|
||||
<CreateSharedLink />
|
||||
<CircleIconButton title="Select all" icon={mdiSelectAll} on:click={handleSelectAll} />
|
||||
<AssetSelectContextMenu icon={mdiPlus} title="Add to...">
|
||||
<CircleIconButton title={$t('select_all')} icon={mdiSelectAll} on:click={handleSelectAll} />
|
||||
<AssetSelectContextMenu icon={mdiPlus} title={$t('add_to')}>
|
||||
<AddToAlbum />
|
||||
<AddToAlbum shared />
|
||||
</AssetSelectContextMenu>
|
||||
<FavoriteAction removeFavorite={isAllFavorite} onFavorite={triggerAssetUpdate} />
|
||||
|
||||
<AssetSelectContextMenu icon={mdiDotsVertical} title="Add">
|
||||
<AssetSelectContextMenu icon={mdiDotsVertical} title={$t('add')}>
|
||||
<DownloadAction menuItem />
|
||||
<ChangeDate menuItem />
|
||||
<ChangeLocation menuItem />
|
||||
@@ -275,7 +276,7 @@
|
||||
<section class="immich-scrollbar relative overflow-y-auto">
|
||||
{#if searchResultAlbums.length > 0}
|
||||
<section>
|
||||
<div class="ml-6 text-4xl font-medium text-black/70 dark:text-white/80">ALBUMS</div>
|
||||
<div class="ml-6 text-4xl font-medium text-black/70 dark:text-white/80">{$t('albums').toUpperCase()}</div>
|
||||
<AlbumCardGroup albums={searchResultAlbums} showDateRange showItemCount />
|
||||
|
||||
<div class="m-6 text-4xl font-medium text-black/70 dark:text-white/80">PHOTOS & VIDEOS</div>
|
||||
@@ -294,7 +295,7 @@
|
||||
<div class="flex min-h-[calc(66vh_-_11rem)] w-full place-content-center items-center dark:text-white">
|
||||
<div class="flex flex-col content-center items-center text-center">
|
||||
<Icon path={mdiImageOffOutline} size="3.5em" />
|
||||
<p class="mt-5 text-3xl font-medium">No results</p>
|
||||
<p class="mt-5 text-3xl font-medium">{$t('no_results')}</p>
|
||||
<p class="text-base font-normal">Try a synonym or more general keyword</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import { getMySharedLink, SharedLinkType } from '@immich/sdk';
|
||||
import type { PageData } from './$types';
|
||||
import { setSharedLink } from '$lib/utils';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let data: PageData;
|
||||
let { sharedLink, passwordRequired, sharedLinkKey: key, meta } = data;
|
||||
@@ -24,7 +25,7 @@
|
||||
setSharedLink(sharedLink);
|
||||
passwordRequired = false;
|
||||
isOwned = $user ? $user.id === sharedLink.userId : false;
|
||||
title = (sharedLink.album ? sharedLink.album.albumName : 'Public Share') + ' - Immich';
|
||||
title = (sharedLink.album ? sharedLink.album.albumName : $t('public_share')) + ' - Immich';
|
||||
description = sharedLink.description || `${sharedLink.assets.length} shared photos & videos.`;
|
||||
} catch (error) {
|
||||
handleError(error, 'Failed to get shared link');
|
||||
@@ -54,14 +55,14 @@
|
||||
class="relative h-screen overflow-hidden bg-immich-bg px-6 pt-[var(--navbar-height)] dark:bg-immich-dark-bg sm:px-12 md:px-24 lg:px-40"
|
||||
>
|
||||
<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">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">
|
||||
Please enter the password to view this page.
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<form novalidate autocomplete="off" on:submit|preventDefault={handlePasswordSubmit}>
|
||||
<input type="password" class="immich-form-input mr-2" placeholder="Password" bind:value={password} />
|
||||
<Button type="submit">Submit</Button>
|
||||
<input type="password" class="immich-form-input mr-2" placeholder={$t('password')} bind:value={password} />
|
||||
<Button type="submit">{$t('submit')}</Button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
type AlbumViewSettings,
|
||||
} from '$lib/stores/preferences.store';
|
||||
import Albums from '$lib/components/album-page/albums-list.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -38,14 +39,14 @@
|
||||
<LinkButton on:click={() => createAlbumAndRedirect()}>
|
||||
<div class="flex flex-wrap place-items-center justify-center gap-x-1 text-sm">
|
||||
<Icon path={mdiPlusBoxOutline} size="18" class="shrink-0" />
|
||||
<span class="leading-none max-sm:text-xs">Create album</span>
|
||||
<span class="leading-none max-sm:text-xs">{$t('create_album')}</span>
|
||||
</div>
|
||||
</LinkButton>
|
||||
|
||||
<LinkButton on:click={() => goto(AppRoute.SHARED_LINKS)}>
|
||||
<div class="flex flex-wrap place-items-center justify-center gap-x-1 text-sm">
|
||||
<Icon path={mdiLink} size="18" class="shrink-0" />
|
||||
<span class="leading-none max-sm:text-xs">Shared links</span>
|
||||
<span class="leading-none max-sm:text-xs">{$t('shared_links')}</span>
|
||||
</div>
|
||||
</LinkButton>
|
||||
</div>
|
||||
@@ -54,7 +55,7 @@
|
||||
{#if data.partners.length > 0}
|
||||
<div class="mb-6 mt-2">
|
||||
<div>
|
||||
<p class="mb-4 font-medium dark:text-immich-dark-fg">Partners</p>
|
||||
<p class="mb-4 font-medium dark:text-immich-dark-fg">{$t('partners')}</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row flex-wrap gap-4">
|
||||
@@ -82,18 +83,14 @@
|
||||
|
||||
<div class="mb-6 mt-2">
|
||||
<div>
|
||||
<p class="mb-4 font-medium dark:text-immich-dark-fg">Albums</p>
|
||||
<p class="mb-4 font-medium dark:text-immich-dark-fg">{$t('albums')}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<!-- Shared Album List -->
|
||||
<Albums sharedAlbums={data.sharedAlbums} userSettings={settings} showOwner>
|
||||
<!-- Empty List -->
|
||||
<EmptyPlaceholder
|
||||
slot="empty"
|
||||
text="Create an album to share photos and videos with people in your network"
|
||||
src={empty2Url}
|
||||
/>
|
||||
<EmptyPlaceholder slot="empty" text={$t('no_shared_albums_message')} src={empty2Url} />
|
||||
</Albums>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import { mdiArrowLeft } from '@mdi/js';
|
||||
import { onMount } from 'svelte';
|
||||
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
let sharedLinks: SharedLinkResponseDto[] = [];
|
||||
let editSharedLink: SharedLinkResponseDto | null = null;
|
||||
@@ -42,7 +43,7 @@
|
||||
|
||||
try {
|
||||
await removeSharedLink({ id });
|
||||
notificationController.show({ message: 'Deleted shared link', type: NotificationType.Info });
|
||||
notificationController.show({ message: $t('deleted_shared_link'), type: NotificationType.Info });
|
||||
await refresh();
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to delete shared link');
|
||||
@@ -60,12 +61,12 @@
|
||||
</script>
|
||||
|
||||
<ControlAppBar backIcon={mdiArrowLeft} on:close={() => goto(AppRoute.SHARING)}>
|
||||
<svelte:fragment slot="leading">Shared links</svelte:fragment>
|
||||
<svelte:fragment slot="leading">{$t('shared_links')}</svelte:fragment>
|
||||
</ControlAppBar>
|
||||
|
||||
<section class="mt-[120px] flex flex-col pb-[120px]">
|
||||
<div class="m-auto mb-4 w-[50%] dark:text-immich-gray">
|
||||
<p>Manage shared links</p>
|
||||
<p>{$t('manage_shared_links')}</p>
|
||||
</div>
|
||||
{#if sharedLinks.length === 0}
|
||||
<div
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
import type { PageData } from './$types';
|
||||
import { handlePromiseError } from '$lib/utils';
|
||||
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -56,7 +57,7 @@
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, 'Error emptying trash');
|
||||
handleError(error, $t('errors.unable_to_empty_trash'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -81,7 +82,7 @@
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, 'Error restoring trash');
|
||||
handleError(error, $t('errors.unable_to_restore_trash'));
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -115,7 +116,7 @@
|
||||
<p class="font-medium text-gray-500/60 dark:text-gray-300/60 p-4">
|
||||
Trashed items will be permanently deleted after {$serverConfig.trashDays} days.
|
||||
</p>
|
||||
<EmptyPlaceholder text="Trashed photos and videos will show up here." src={empty3Url} slot="empty" />
|
||||
<EmptyPlaceholder text={$t('trash_no_results_message')} src={empty3Url} slot="empty" />
|
||||
</AssetGrid>
|
||||
</UserPageLayout>
|
||||
{/if}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import type { PageData } from './$types';
|
||||
import ShowShortcuts from '$lib/components/shared-components/show-shortcuts.svelte';
|
||||
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let data: PageData;
|
||||
export let isShowKeyboardShortcut = false;
|
||||
@@ -14,7 +15,7 @@
|
||||
<svelte:fragment slot="buttons">
|
||||
<CircleIconButton
|
||||
icon={mdiKeyboard}
|
||||
title="Show keyboard shortcuts"
|
||||
title={$t('show_keyboard_shortcuts')}
|
||||
on:click={() => (isShowKeyboardShortcut = !isShowKeyboardShortcut)}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
|
||||
+2
-1
@@ -10,6 +10,7 @@
|
||||
} from '$lib/components/shared-components/notification/notification';
|
||||
import { s } from '$lib/utils';
|
||||
import { deleteAssets, getConfig, updateAssets } from '@immich/sdk';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -39,7 +40,7 @@
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to resolve duplicate');
|
||||
handleError(error, $t('unable_to_resolve_duplicate'));
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||
import { copyToClipboard } from '$lib/utils';
|
||||
import { mdiCodeTags, mdiContentCopy, mdiMessage, mdiPartyPopper } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
const handleCopy = async () => {
|
||||
//
|
||||
@@ -40,7 +41,7 @@
|
||||
<CircleIconButton
|
||||
color="primary"
|
||||
icon={mdiContentCopy}
|
||||
title="Copy error"
|
||||
title={$t('copy_error')}
|
||||
on:click={() => handleCopy()}
|
||||
/>
|
||||
</div>
|
||||
@@ -52,7 +53,7 @@
|
||||
<div class="flex w-full flex-col gap-2">
|
||||
<p class="text-red-500">{$page.error?.message} ({$page.error?.code})</p>
|
||||
{#if $page.error?.stack}
|
||||
<label for="stacktrace">Stacktrace</label>
|
||||
<label for="stacktrace">{$t('stacktrace')}</label>
|
||||
<pre id="stacktrace" class="text-xs">{$page.error?.stack || 'No stack'}</pre>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -70,7 +71,7 @@
|
||||
>
|
||||
<div class="flex flex-col place-content-center place-items-center gap-2">
|
||||
<Icon path={mdiMessage} size={24} />
|
||||
<p class="text-sm">Get Help</p>
|
||||
<p class="text-sm">{$t('get_help')}</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
@@ -82,7 +83,7 @@
|
||||
>
|
||||
<div class="flex flex-col place-content-center place-items-center gap-2">
|
||||
<Icon path={mdiPartyPopper} size={24} />
|
||||
<p class="text-sm">Read Changelog</p>
|
||||
<p class="text-sm">{$t('read_changelog')}</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
@@ -94,7 +95,7 @@
|
||||
>
|
||||
<div class="flex flex-col place-content-center place-items-center gap-2">
|
||||
<Icon path={mdiCodeTags} size={24} />
|
||||
<p class="text-sm">Check Logs</p>
|
||||
<p class="text-sm">{$t('check_logs')}</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import '../app.css';
|
||||
import { isAssetViewerRoute, isSharedLinkRoute } from '$lib/utils/navigation';
|
||||
import DialogWrapper from '$lib/components/shared-components/dialog/dialog-wrapper.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
let showNavigationLoadingBar = false;
|
||||
|
||||
@@ -108,7 +109,7 @@
|
||||
<noscript
|
||||
class="absolute z-[1000] flex h-screen w-screen place-content-center place-items-center bg-immich-bg dark:bg-immich-dark-bg dark:text-immich-dark-fg"
|
||||
>
|
||||
<FullscreenContainer title="Welcome to Immich">
|
||||
<FullscreenContainer title={$t('welcome_to_immich')}>
|
||||
To use Immich, you must enable JavaScript or use a JavaScript compatible browser.
|
||||
</FullscreenContainer>
|
||||
</noscript>
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
import { fallbackLang, langs } from '$lib/constants';
|
||||
import { lang } from '$lib/stores/preferences.store';
|
||||
import { defaults } from '@immich/sdk';
|
||||
import { init, register } from 'svelte-i18n';
|
||||
import { get } from 'svelte/store';
|
||||
import type { LayoutLoad } from './$types';
|
||||
|
||||
export const ssr = false;
|
||||
export const csr = true;
|
||||
|
||||
export const load = (({ fetch }) => {
|
||||
export const load = (async ({ fetch }) => {
|
||||
// set event.fetch on the fetch-client used by @immich/sdk
|
||||
// https://kit.svelte.dev/docs/load#making-fetch-requests
|
||||
// https://github.com/oazapfts/oazapfts/blob/main/README.md#fetch-options
|
||||
defaults.fetch = fetch;
|
||||
|
||||
for (const { code, loader } of langs) {
|
||||
register(code, loader);
|
||||
}
|
||||
|
||||
const preferenceLang = get(lang);
|
||||
|
||||
await init({ fallbackLocale: fallbackLang, initialLocale: preferenceLang || fallbackLang });
|
||||
|
||||
return {
|
||||
meta: {
|
||||
title: 'Immich',
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import Button from '$lib/components/elements/buttons/button.svelte';
|
||||
import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte';
|
||||
import { AppRoute } from '$lib/constants';
|
||||
import { t } from 'svelte-i18n';
|
||||
</script>
|
||||
|
||||
<section class="flex h-screen w-screen place-content-center place-items-center">
|
||||
@@ -9,10 +10,10 @@
|
||||
<div class="flex place-content-center place-items-center">
|
||||
<ImmichLogo noText class="text-center" height="200" width="200" />
|
||||
</div>
|
||||
<h1 class="text-4xl font-bold text-immich-primary dark:text-immich-dark-primary">Welcome to immich</h1>
|
||||
<h1 class="text-4xl font-bold text-immich-primary dark:text-immich-dark-primary">{$t('welcome_to_immich')}</h1>
|
||||
<a href={AppRoute.AUTH_REGISTER}>
|
||||
<Button size="lg" rounded="lg">
|
||||
<span class="px-2 font-bold">Getting Started</span>
|
||||
<span class="px-2 font-bold">{$t('getting_started')}</span>
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
import type { PageData } from './$types';
|
||||
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 data: PageData;
|
||||
|
||||
@@ -124,7 +125,7 @@
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to create library');
|
||||
handleError(error, $t('unable_to_create_library'));
|
||||
} finally {
|
||||
toCreateLibrary = false;
|
||||
await readLibraryList();
|
||||
@@ -142,7 +143,7 @@
|
||||
closeAll();
|
||||
await readLibraryList();
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to update library');
|
||||
handleError(error, $t('unable_to_update_library'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -162,7 +163,7 @@
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to remove library');
|
||||
handleError(error, $t('unable_to_remove_library'));
|
||||
} finally {
|
||||
confirmDeleteLibrary = null;
|
||||
deletedLibrary = null;
|
||||
@@ -180,7 +181,7 @@
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to scan libraries');
|
||||
handleError(error, $t('unable_to_scan_libraries'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -192,7 +193,7 @@
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to scan library');
|
||||
handleError(error, $t('unable_to_scan_library'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -204,7 +205,7 @@
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to scan library');
|
||||
handleError(error, $t('unable_to_scan_library'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -216,7 +217,7 @@
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to scan library');
|
||||
handleError(error, $t('unable_to_scan_library'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -328,14 +329,14 @@
|
||||
<LinkButton on:click={() => handleScanAll()}>
|
||||
<div class="flex gap-1 text-sm">
|
||||
<Icon path={mdiSync} size="18" />
|
||||
<span>Scan All Libraries</span>
|
||||
<span>{$t('scan_all_libraries')}</span>
|
||||
</div>
|
||||
</LinkButton>
|
||||
{/if}
|
||||
<LinkButton on:click={() => (toCreateLibrary = true)}>
|
||||
<div class="flex gap-1 text-sm">
|
||||
<Icon path={mdiPlusBoxOutline} size="18" />
|
||||
<span>Create Library</span>
|
||||
<span>{$t('create_library')}</span>
|
||||
</div>
|
||||
</LinkButton>
|
||||
</div>
|
||||
@@ -347,11 +348,11 @@
|
||||
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="grid grid-cols-6 w-full place-items-center">
|
||||
<th class="text-center text-sm font-medium">Type</th>
|
||||
<th class="text-center text-sm font-medium">Name</th>
|
||||
<th class="text-center text-sm font-medium">Owner</th>
|
||||
<th class="text-center text-sm font-medium">Assets</th>
|
||||
<th class="text-center text-sm font-medium">Size</th>
|
||||
<th class="text-center text-sm font-medium">{$t('type')}</th>
|
||||
<th class="text-center text-sm font-medium">{$t('name')}</th>
|
||||
<th class="text-center text-sm font-medium">{$t('owner')}</th>
|
||||
<th class="text-center text-sm font-medium">{$t('assets')}</th>
|
||||
<th class="text-center text-sm font-medium">{$t('size')}</th>
|
||||
<th class="text-center text-sm font-medium" />
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -390,7 +391,7 @@
|
||||
<CircleIconButton
|
||||
color="primary"
|
||||
icon={mdiDotsVertical}
|
||||
title="Library options"
|
||||
title={$t('library_options')}
|
||||
size="16"
|
||||
on:click={(e) => showMenu(e, library, index)}
|
||||
/>
|
||||
@@ -401,24 +402,27 @@
|
||||
<MenuOption on:click={() => onRenameClicked()} text={`Rename`} />
|
||||
|
||||
{#if selectedLibrary}
|
||||
<MenuOption on:click={() => onEditImportPathClicked()} text="Edit Import Paths" />
|
||||
<MenuOption on:click={() => onScanSettingClicked()} text="Scan Settings" />
|
||||
<MenuOption on:click={() => onEditImportPathClicked()} text={$t('edit_import_paths')} />
|
||||
<MenuOption on:click={() => onScanSettingClicked()} text={$t('scan_settings')} />
|
||||
<hr />
|
||||
<MenuOption on:click={() => onScanNewLibraryClicked()} text="Scan New Library Files" />
|
||||
<MenuOption on:click={() => onScanNewLibraryClicked()} text={$t('scan_new_library_files')} />
|
||||
<MenuOption
|
||||
on:click={() => onScanAllLibraryFilesClicked()}
|
||||
text="Re-scan All Library Files"
|
||||
subtitle={'Only refreshes modified files'}
|
||||
text={$t('scan_all_library_files')}
|
||||
subtitle={$t('only_refreshes_modified_files')}
|
||||
/>
|
||||
<MenuOption
|
||||
on:click={() => onForceScanAllLibraryFilesClicked()}
|
||||
text="Force Re-scan All Library Files"
|
||||
subtitle={'Refreshes every file'}
|
||||
text={$t('force_re-scan_library_files')}
|
||||
subtitle={$t('refreshes_every_file')}
|
||||
/>
|
||||
<hr />
|
||||
<MenuOption on:click={() => onRemoveOfflineFilesClicked()} text="Remove Offline Files" />
|
||||
<MenuOption
|
||||
on:click={() => onRemoveOfflineFilesClicked()}
|
||||
text={$t('remove_offline_files')}
|
||||
/>
|
||||
<MenuOption on:click={() => onDeleteLibraryClicked()}>
|
||||
<p class="text-red-600">Delete library</p>
|
||||
<p class="text-red-600">{$t('delete_library')}</p>
|
||||
</MenuOption>
|
||||
{/if}
|
||||
</ContextMenu>
|
||||
@@ -459,10 +463,7 @@
|
||||
|
||||
<!-- Empty message -->
|
||||
{:else}
|
||||
<EmptyPlaceholder
|
||||
text="Create an external library to view your photos and videos"
|
||||
onClick={() => (toCreateLibrary = true)}
|
||||
/>
|
||||
<EmptyPlaceholder text={$t('no_libraries_message')} onClick={() => (toCreateLibrary = true)} />
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import { fixAuditFiles, getAuditFiles, getFileChecksums, type FileReportItemDto } from '@immich/sdk';
|
||||
import { mdiCheckAll, mdiContentCopy, mdiDownload, mdiRefresh, mdiWrench } from '@mdi/js';
|
||||
import type { PageData } from './$types';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -84,7 +85,7 @@
|
||||
|
||||
matches = [];
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to repair items');
|
||||
handleError(error, $t('unable_to_repair_items'));
|
||||
} finally {
|
||||
repairing = false;
|
||||
}
|
||||
@@ -107,9 +108,9 @@
|
||||
orphans = report.orphans;
|
||||
extras = normalize(report.extras);
|
||||
|
||||
notificationController.show({ message: 'Refreshed', type: NotificationType.Info });
|
||||
notificationController.show({ message: $t('refreshed'), type: NotificationType.Info });
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to load items');
|
||||
handleError(error, $t('unable_to_load_items'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -120,7 +121,7 @@
|
||||
notificationController.show({ message: `Matched 1 item`, type: NotificationType.Info });
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to check item');
|
||||
handleError(error, $t('unable_to_check_item'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -136,7 +137,7 @@
|
||||
count += await loadAndMatch(filenames.slice(index, index + chunkSize));
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to check items');
|
||||
handleError(error, $t('unable_to_check_items'));
|
||||
} finally {
|
||||
checking = false;
|
||||
}
|
||||
@@ -203,7 +204,7 @@
|
||||
<section class="w-full pb-28 sm:w-5/6 md:w-[850px]">
|
||||
{#if matches.length + extras.length + orphans.length === 0}
|
||||
<div class="w-full">
|
||||
<EmptyPlaceholder fullWidth text="Untracked and missing files will show up here" src={empty4Url} />
|
||||
<EmptyPlaceholder fullWidth text={$t('repair_no_results_message')} src={empty4Url} />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="gap-2">
|
||||
@@ -266,7 +267,7 @@
|
||||
title={orphan.pathValue}
|
||||
>
|
||||
<td on:click={() => copyToClipboard(orphan.pathValue)}>
|
||||
<CircleIconButton title="Copy file path" icon={mdiContentCopy} size="18" />
|
||||
<CircleIconButton title={$t('copy_file_path')} icon={mdiContentCopy} size="18" />
|
||||
</td>
|
||||
<td class="truncate text-sm font-mono text-left" title={orphan.pathValue}>
|
||||
{orphan.pathValue}
|
||||
@@ -306,7 +307,7 @@
|
||||
title={extra.filename}
|
||||
>
|
||||
<td on:click={() => copyToClipboard(extra.filename)}>
|
||||
<CircleIconButton title="Copy file path" icon={mdiContentCopy} size="18" />
|
||||
<CircleIconButton title={$t('copy_file_path')} icon={mdiContentCopy} size="18" />
|
||||
</td>
|
||||
<td class="w-full text-md text-ellipsis flex justify-between pr-5">
|
||||
<span class="text-ellipsis grow truncate font-mono text-sm pr-5" title={extra.filename}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
import type { SystemConfigDto } from '@immich/sdk';
|
||||
import { mdiAlert, mdiContentCopy, mdiDownload, mdiUpload } from '@mdi/js';
|
||||
import type { PageData } from './$types';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -82,92 +83,92 @@
|
||||
}> = [
|
||||
{
|
||||
item: AuthSettings,
|
||||
title: 'Authentication Settings',
|
||||
subtitle: 'Manage password, OAuth, and other authentication settings',
|
||||
title: $t('admin.authentication_settings'),
|
||||
subtitle: $t('admin.authentication_settings_description'),
|
||||
key: 'image',
|
||||
},
|
||||
{
|
||||
item: ImageSettings,
|
||||
title: 'Image Settings',
|
||||
subtitle: 'Manage the quality and resolution of generated images',
|
||||
title: $t('admin.image_settings'),
|
||||
subtitle: $t('admin.image_settings_description'),
|
||||
key: 'image',
|
||||
},
|
||||
{
|
||||
item: JobSettings,
|
||||
title: 'Job Settings',
|
||||
subtitle: 'Manage job concurrency',
|
||||
title: $t('admin.job_settings'),
|
||||
subtitle: $t('admin.job_settings_description'),
|
||||
key: 'job',
|
||||
},
|
||||
{
|
||||
item: LibrarySettings,
|
||||
title: 'External Library',
|
||||
subtitle: 'Manage external library settings',
|
||||
title: $t('admin.library_settings'),
|
||||
subtitle: $t('admin.library_settings_description'),
|
||||
key: 'external-library',
|
||||
},
|
||||
{
|
||||
item: LoggingSettings,
|
||||
title: 'Logging',
|
||||
subtitle: 'Manage log settings',
|
||||
title: $t('admin.logging_settings'),
|
||||
subtitle: $t('admin.manage_log_settings'),
|
||||
key: 'logging',
|
||||
},
|
||||
{
|
||||
item: MachineLearningSettings,
|
||||
title: 'Machine Learning Settings',
|
||||
subtitle: 'Manage machine learning features and settings',
|
||||
title: $t('admin.machine_learning_settings'),
|
||||
subtitle: $t('admin.machine_learning_settings_description'),
|
||||
key: 'machine-learning',
|
||||
},
|
||||
{
|
||||
item: MapSettings,
|
||||
title: 'Map & GPS Settings',
|
||||
subtitle: 'Manage map related features and setting',
|
||||
title: $t('admin.map_settings'),
|
||||
subtitle: $t('admin.map_settings_description'),
|
||||
key: 'location',
|
||||
},
|
||||
{
|
||||
item: NotificationSettings,
|
||||
title: 'Notification Settings',
|
||||
subtitle: 'Manage notification settings, including email',
|
||||
title: $t('admin.notification_settings'),
|
||||
subtitle: $t('admin.notification_settings_description'),
|
||||
key: 'notifications',
|
||||
},
|
||||
{
|
||||
item: ServerSettings,
|
||||
title: 'Server Settings',
|
||||
subtitle: 'Manage server settings',
|
||||
title: $t('admin.server_settings'),
|
||||
subtitle: $t('admin.server_settings_description'),
|
||||
key: 'server',
|
||||
},
|
||||
{
|
||||
item: StorageTemplateSettings,
|
||||
title: 'Storage Template',
|
||||
subtitle: 'Manage the folder structure and file name of the upload asset',
|
||||
title: $t('admin.storage_template_settings'),
|
||||
subtitle: $t('admin.storage_template_settings_description'),
|
||||
key: 'storage-template',
|
||||
},
|
||||
{
|
||||
item: ThemeSettings,
|
||||
title: 'Theme Settings',
|
||||
subtitle: 'Manage customization of the Immich web interface',
|
||||
title: $t('admin.theme_settings'),
|
||||
subtitle: $t('admin.theme_settings_description'),
|
||||
key: 'theme',
|
||||
},
|
||||
{
|
||||
item: TrashSettings,
|
||||
title: 'Trash Settings',
|
||||
subtitle: 'Manage trash settings',
|
||||
title: $t('admin.trash_settings'),
|
||||
subtitle: $t('admin.trash_settings_description'),
|
||||
key: 'trash',
|
||||
},
|
||||
{
|
||||
item: UserSettings,
|
||||
title: 'User Settings',
|
||||
subtitle: 'Manage user settings',
|
||||
title: $t('admin.user_settings'),
|
||||
subtitle: $t('admin.user_settings_description'),
|
||||
key: 'user-settings',
|
||||
},
|
||||
{
|
||||
item: NewVersionCheckSettings,
|
||||
title: 'Version Check',
|
||||
subtitle: 'Enable/disable the new version notification',
|
||||
title: $t('admin.version_check_settings'),
|
||||
subtitle: $t('admin.version_check_settings_description'),
|
||||
key: 'version-check',
|
||||
},
|
||||
{
|
||||
item: FFmpegSettings,
|
||||
title: 'Video Transcoding Settings',
|
||||
subtitle: 'Manage the resolution and encoding information of the video files',
|
||||
title: $t('admin.transcoding_settings'),
|
||||
subtitle: $t('admin.transcoding_settings_description'),
|
||||
key: 'video-transcoding',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import { onMount } from 'svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -154,8 +155,8 @@
|
||||
|
||||
{#if shouldShowPasswordResetSuccess}
|
||||
<ConfirmDialog
|
||||
title="Password reset success"
|
||||
confirmText="Done"
|
||||
title={$t('password_reset_success')}
|
||||
confirmText={$t('done')}
|
||||
onConfirm={() => (shouldShowPasswordResetSuccess = false)}
|
||||
onCancel={() => (shouldShowPasswordResetSuccess = false)}
|
||||
hideCancelButton={true}
|
||||
@@ -171,7 +172,7 @@
|
||||
>
|
||||
{newPassword}
|
||||
</code>
|
||||
<LinkButton on:click={() => copyToClipboard(newPassword)} title="Copy password">
|
||||
<LinkButton on:click={() => copyToClipboard(newPassword)} title={$t('copy_password')}>
|
||||
<div class="flex place-items-center gap-2 text-sm">
|
||||
<Icon path={mdiContentCopy} size="18" />
|
||||
</div>
|
||||
@@ -192,10 +193,12 @@
|
||||
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-8/12 sm:w-5/12 lg:w-6/12 xl:w-4/12 2xl:w-5/12 text-center text-sm font-medium">Email</th>
|
||||
<th class="hidden sm:block w-3/12 text-center text-sm font-medium">Name</th>
|
||||
<th class="hidden xl:block w-3/12 2xl:w-2/12 text-center text-sm font-medium">Has quota</th>
|
||||
<th class="w-4/12 lg:w-3/12 xl:w-2/12 text-center text-sm font-medium">Action</th>
|
||||
<th class="w-8/12 sm:w-5/12 lg:w-6/12 xl:w-4/12 2xl:w-5/12 text-center text-sm font-medium"
|
||||
>{$t('email')}</th
|
||||
>
|
||||
<th class="hidden sm:block w-3/12 text-center text-sm font-medium">{$t('name')}</th>
|
||||
<th class="hidden xl:block w-3/12 2xl:w-2/12 text-center text-sm font-medium">{$t('has_quota')}</th>
|
||||
<th class="w-4/12 lg:w-3/12 xl:w-2/12 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">
|
||||
@@ -227,7 +230,7 @@
|
||||
{#if !immichUser.deletedAt}
|
||||
<CircleIconButton
|
||||
icon={mdiPencilOutline}
|
||||
title="Edit user"
|
||||
title={$t('edit_user')}
|
||||
color="primary"
|
||||
size="16"
|
||||
on:click={() => editUserHandler(immichUser)}
|
||||
@@ -235,7 +238,7 @@
|
||||
{#if immichUser.id !== $user.id}
|
||||
<CircleIconButton
|
||||
icon={mdiTrashCanOutline}
|
||||
title="Delete user"
|
||||
title={$t('delete_user')}
|
||||
color="primary"
|
||||
size="16"
|
||||
on:click={() => deleteUserHandler(immichUser)}
|
||||
@@ -258,7 +261,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Button size="sm" on:click={() => (shouldShowCreateUserForm = true)}>Create user</Button>
|
||||
<Button size="sm" on:click={() => (shouldShowCreateUserForm = true)}>{$t('create_user')}</Button>
|
||||
</section>
|
||||
</section>
|
||||
</UserPageLayout>
|
||||
|
||||
Reference in New Issue
Block a user