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:
Manic-87
2024-06-04 21:53:00 +02:00
committed by GitHub
parent a2bccf23c9
commit f446bc8caa
177 changed files with 2779 additions and 1017 deletions
+3 -6
View File
@@ -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>
@@ -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>
+5 -4
View File
@@ -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>
@@ -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>
+16 -15
View File
@@ -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}
@@ -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>
+3 -2
View File
@@ -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>
+6 -9
View File
@@ -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>
@@ -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>
+6 -5
View File
@@ -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>
+2 -1
View File
@@ -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>
+13 -1
View File
@@ -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',
+3 -2
View File
@@ -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>
+9 -8
View File
@@ -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>