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
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
177 changed files with 2779 additions and 1017 deletions

View file

@ -3,6 +3,7 @@
import { mdiCommentOutline, mdiHeart, mdiHeartOutline } from '@mdi/js';
import { createEventDispatcher } from 'svelte';
import Icon from '../elements/icon.svelte';
import { t } from 'svelte-i18n';
export let isLiked: ActivityResponseDto | null;
export let numberOfComments: number | undefined;
@ -29,7 +30,7 @@
{#if numberOfComments}
<div class="text-xl">{numberOfComments}</div>
{:else if !isShowActivity}
<div class="text-lg">Say something</div>
<div class="text-lg">{$t('say_something')}</div>
{/if}
</div>
</button>

View file

@ -25,6 +25,7 @@
import UserAvatar from '../shared-components/user-avatar.svelte';
import { locale } from '$lib/stores/preferences.store';
import { shortcut } from '$lib/actions/shortcut';
import { t } from 'svelte-i18n';
const units: Intl.RelativeTimeFormatUnit[] = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'];
@ -91,7 +92,7 @@
try {
reactions = await getActivities({ assetId, albumId });
} catch (error) {
handleError(error, 'Error when fetching reactions');
handleError(error, $t('errors.unable_to_load_asset_activity'));
}
};
@ -120,7 +121,7 @@
type: NotificationType.Info,
});
} catch (error) {
handleError(error, `Can't remove ${reaction.type}`);
handleError(error, $t('errors.unable_to_remove_reaction'));
}
};
@ -140,7 +141,7 @@
// Re-render the activity feed
reactions = reactions;
} catch (error) {
handleError(error, "Can't add your comment");
handleError(error, $t('errors.unable_to_add_comment'));
} finally {
clearTimeout(timeout);
}
@ -159,9 +160,9 @@
bind:clientHeight={activityHeight}
>
<div class="flex place-items-center gap-2">
<CircleIconButton on:click={() => dispatch('close')} icon={mdiClose} title="Close" />
<CircleIconButton on:click={() => dispatch('close')} icon={mdiClose} title={$t('close')} />
<p class="text-lg text-immich-fg dark:text-immich-dark-fg">Activity</p>
<p class="text-lg text-immich-fg dark:text-immich-dark-fg">{$t('activity')}</p>
</div>
</div>
{#if innerHeight}
@ -190,7 +191,7 @@
<div class="flex items-start w-fit pt-[5px]">
<CircleIconButton
icon={mdiDotsVertical}
title="Comment options"
title={$t('comment_options')}
size="16"
on:click={() => (showDeleteReaction[index] ? '' : showOptionsMenu(index))}
/>
@ -242,7 +243,7 @@
<div class="flex items-start w-fit">
<CircleIconButton
icon={mdiDotsVertical}
title="Reaction options"
title={$t('reaction_options')}
size="16"
on:click={() => (showDeleteReaction[index] ? '' : showOptionsMenu(index))}
/>
@ -289,7 +290,7 @@
bind:this={textArea}
bind:value={message}
use:autoGrowHeight={'5px'}
placeholder={disabled ? 'Comments are disabled' : 'Say something'}
placeholder={disabled ? $t('comments_are_disabled') : $t('say_something')}
on:input={() => autoGrowHeight(textArea, '5px')}
use:shortcut={{
shortcut: { key: 'Enter' },
@ -308,7 +309,12 @@
</div>
{:else if message}
<div class="flex items-end w-fit ml-0">
<CircleIconButton title="Send message" size="15" icon={mdiSend} class="dark:text-immich-dark-gray" />
<CircleIconButton
title={$t('send_message')}
size="15"
icon={mdiSend}
class="dark:text-immich-dark-gray"
/>
</div>
{/if}
</form>

View file

@ -38,6 +38,7 @@
import { createEventDispatcher } from 'svelte';
import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
import MenuOption from '../shared-components/context-menu/menu-option.svelte';
import { t } from 'svelte-i18n';
export let asset: AssetResponseDto;
export let album: AlbumResponseDto | null = null;
@ -107,7 +108,7 @@
class="z-[1001] flex h-16 place-items-center justify-between bg-gradient-to-b from-black/40 px-3 transition-transform duration-200"
>
<div class="text-white">
<CircleIconButton color="opaque" icon={mdiArrowLeft} title="Go back" on:click={() => dispatch('back')} />
<CircleIconButton color="opaque" icon={mdiArrowLeft} title={$t('go_back')} on:click={() => dispatch('back')} />
</div>
<div
class="flex w-[calc(100%-3rem)] justify-end gap-2 overflow-hidden text-white"
@ -118,7 +119,7 @@
color="opaque"
icon={mdiShareVariantOutline}
on:click={() => dispatch('showShareModal')}
title="Share"
title={$t('share')}
/>
{/if}
{#if asset.isOffline}
@ -126,7 +127,7 @@
color="opaque"
icon={mdiAlertOutline}
on:click={() => dispatch('showDetail')}
title="Asset Offline"
title={$t('asset_offline')}
/>
{/if}
{#if showMotionPlayButton}
@ -134,14 +135,14 @@
<CircleIconButton
color="opaque"
icon={mdiMotionPauseOutline}
title="Stop Motion Photo"
title={$t('stop_motion_photo')}
on:click={() => dispatch('stopMotionPhoto')}
/>
{:else}
<CircleIconButton
color="opaque"
icon={mdiPlaySpeed}
title="Play Motion Photo"
title={$t('play_motion_photo')}
on:click={() => dispatch('playMotionPhoto')}
/>
{/if}
@ -151,7 +152,7 @@
color="opaque"
hideMobile={true}
icon={$photoZoomState && $photoZoomState.currentZoom > 1 ? mdiMagnifyMinusOutline : mdiMagnifyPlusOutline}
title="Zoom Image"
title={$t('zoom_image')}
on:click={() => {
const zoomImage = new CustomEvent('zoomImage');
window.dispatchEvent(zoomImage);
@ -162,7 +163,7 @@
<CircleIconButton
color="opaque"
icon={mdiContentCopy}
title="Copy Image"
title={$t('copy_image')}
on:click={() => {
const copyEvent = new CustomEvent('copyImage');
window.dispatchEvent(copyEvent);
@ -175,7 +176,7 @@
color="opaque"
icon={mdiFolderDownloadOutline}
on:click={() => dispatch('download')}
title="Download"
title={$t('download')}
/>
{/if}
@ -184,7 +185,7 @@
color="opaque"
icon={mdiInformationOutline}
on:click={() => dispatch('showDetail')}
title="Info"
title={$t('info')}
/>
{/if}
@ -193,45 +194,58 @@
color="opaque"
icon={asset.isFavorite ? mdiHeart : mdiHeartOutline}
on:click={() => dispatch('favorite')}
title={asset.isFavorite ? 'Unfavorite' : 'Favorite'}
title={asset.isFavorite ? $t('unfavorite') : $t('favorite')}
/>
{/if}
{#if isOwner}
<CircleIconButton color="opaque" icon={mdiDeleteOutline} on:click={() => dispatch('delete')} title="Delete" />
<CircleIconButton
color="opaque"
icon={mdiDeleteOutline}
on:click={() => dispatch('delete')}
title={$t('delete')}
/>
<div
use:clickOutside={{
onOutclick: () => (isShowAssetOptions = false),
onEscape: () => (isShowAssetOptions = false),
}}
>
<CircleIconButton color="opaque" icon={mdiDotsVertical} on:click={showOptionsMenu} title="More" />
<CircleIconButton color="opaque" icon={mdiDotsVertical} on:click={showOptionsMenu} title={$t('more')} />
{#if isShowAssetOptions}
<ContextMenu {...contextMenuPosition} direction="left">
{#if showSlideshow}
<MenuOption icon={mdiPresentationPlay} on:click={() => onMenuClick('playSlideShow')} text="Slideshow" />
<MenuOption
icon={mdiPresentationPlay}
on:click={() => onMenuClick('playSlideShow')}
text={$t('slideshow')}
/>
{/if}
{#if showDownloadButton}
<MenuOption icon={mdiFolderDownloadOutline} on:click={() => onMenuClick('download')} text="Download" />
<MenuOption
icon={mdiFolderDownloadOutline}
on:click={() => onMenuClick('download')}
text={$t('download')}
/>
{/if}
{#if asset.isTrashed}
<MenuOption icon={mdiHistory} on:click={() => onMenuClick('restoreAsset')} text="Restore" />
<MenuOption icon={mdiHistory} on:click={() => onMenuClick('restoreAsset')} text={$t('restore')} />
{:else}
<MenuOption icon={mdiImageAlbum} on:click={() => onMenuClick('addToAlbum')} text="Add to album" />
<MenuOption icon={mdiImageAlbum} on:click={() => onMenuClick('addToAlbum')} text={$t('add_to_album')} />
<MenuOption
icon={mdiShareVariantOutline}
on:click={() => onMenuClick('addToSharedAlbum')}
text="Add to shared album"
text={$t('add_to_shared_album')}
/>
{/if}
{#if isOwner}
{#if hasStackChildren}
<MenuOption icon={mdiImageMinusOutline} on:click={() => onMenuClick('unstack')} text="Un-stack" />
<MenuOption icon={mdiImageMinusOutline} on:click={() => onMenuClick('unstack')} text={$t('unstack')} />
{/if}
{#if album}
<MenuOption
text="Set as album cover"
text={$t('set_as_album_cover')}
icon={mdiImageOutline}
on:click={() => onMenuClick('setAsAlbumCover')}
/>
@ -240,18 +254,18 @@
<MenuOption
icon={mdiAccountCircleOutline}
on:click={() => onMenuClick('asProfileImage')}
text="Set as profile picture"
text={$t('set_as_profile_picture')}
/>
{/if}
<MenuOption
on:click={() => dispatch('toggleArchive')}
icon={asset.isArchived ? mdiArchiveArrowUpOutline : mdiArchiveArrowDownOutline}
text={asset.isArchived ? 'Unarchive' : 'Archive'}
text={asset.isArchived ? $t('unarchive') : $t('archive')}
/>
<MenuOption
icon={mdiUpload}
on:click={() => openFileUploadDialog({ multiple: false, assetId: asset.id })}
text="Replace with upload"
text={$t('replace_with_upload')}
/>
<hr />
<MenuOption

View file

@ -53,6 +53,7 @@
import VideoViewer from './video-wrapper-viewer.svelte';
import { navigate } from '$lib/utils/navigation';
import { websocketEvents } from '$lib/stores/websocket';
import { t } from 'svelte-i18n';
export let assetStore: AssetStore | null = null;
export let asset: AssetResponseDto;
@ -169,7 +170,7 @@
});
isLiked = data.length > 0 ? data[0] : null;
} catch (error) {
handleError(error, "Can't get Favorite");
handleError(error, $t('errors.unable_to_load_liked_status'));
}
}
};
@ -352,11 +353,11 @@
dispatch('action', { type: AssetAction.TRASH, asset });
notificationController.show({
message: 'Moved to trash',
message: $t('moved_to_trash'),
type: NotificationType.Info,
});
} catch (error) {
handleError(error, 'Unable to trash asset');
handleError(error, $t('errors.unable_to_trash_asset'));
}
};
@ -367,11 +368,11 @@
dispatch('action', { type: AssetAction.DELETE, asset });
notificationController.show({
message: 'Permanently deleted asset',
message: $t('permanently_deleted_asset'),
type: NotificationType.Info,
});
} catch (error) {
handleError(error, 'Unable to delete asset');
handleError(error, $t('errors.unable_to_delete_asset'));
} finally {
isShowDeleteConfirmation = false;
}
@ -428,7 +429,7 @@
message: `Restored asset`,
});
} catch (error) {
handleError(error, 'Error restoring asset');
handleError(error, $t('errors.unable_to_restore_assets'));
}
};
@ -483,7 +484,7 @@
try {
await assetViewerHtmlElement.requestFullscreen();
} catch (error) {
console.error('Error entering fullscreen', error);
handleError(error, $t('errors.unable_to_enter_fullscreen'));
$slideshowState = SlideshowState.StopSlideshow;
}
};
@ -495,7 +496,7 @@
await document.exitFullscreen();
}
} catch (error) {
console.error('Error exiting fullscreen', error);
handleError(error, $t('errors.unable_to_exit_fullscreen'));
} finally {
$stopSlideshowProgress = true;
$slideshowState = SlideshowState.None;
@ -534,7 +535,7 @@
});
notificationController.show({
type: NotificationType.Info,
message: 'Album cover updated',
message: $t('album_cover_updated'),
timeout: 1500,
});
} catch (error) {
@ -606,7 +607,7 @@
{#if $slideshowState === SlideshowState.None && showNavigation}
<div class="z-[1001] my-auto column-span-1 col-start-1 row-span-full row-start-1 justify-self-start">
<NavigationArea onClick={(e) => navigateAsset('previous', e)} label="View previous asset">
<NavigationArea onClick={(e) => navigateAsset('previous', e)} label={$t('view_previous_asset')}>
<Icon path={mdiChevronLeft} size="36" ariaHidden />
</NavigationArea>
</div>
@ -703,7 +704,7 @@
{#if $slideshowState === SlideshowState.None && showNavigation}
<div class="z-[1001] my-auto col-span-1 col-start-4 row-span-full row-start-1 justify-self-end">
<NavigationArea onClick={(e) => navigateAsset('next', e)} label="View next asset">
<NavigationArea onClick={(e) => navigateAsset('next', e)} label={$t('view_next_asset')}>
<Icon path={mdiChevronRight} size="36" ariaHidden />
</NavigationArea>
</div>

View file

@ -6,6 +6,7 @@
import { handleError } from '$lib/utils/handle-error';
import { updateAsset, type AssetResponseDto } from '@immich/sdk';
import AutogrowTextarea from '$lib/components/shared-components/autogrow-textarea.svelte';
import { t } from 'svelte-i18n';
export let asset: AssetResponseDto;
export let isOwner: boolean;
@ -20,7 +21,7 @@
message: 'Asset description has been updated',
});
} catch (error) {
handleError(error, 'Cannot update the description');
handleError(error, $t('cannot_update_the_description'));
}
description = newDescription;
};
@ -32,7 +33,7 @@
content={description}
class="max-h-[500px] w-full border-b border-gray-500 bg-transparent text-base text-black outline-none transition-all focus:border-b-2 focus:border-immich-primary disabled:border-none dark:text-white dark:focus:border-immich-dark-primary immich-scrollbar"
onContentUpdate={handleFocusOut}
placeholder="Add a description"
placeholder={$t('add_a_description')}
/>
</section>
{:else if description}

View file

@ -5,6 +5,7 @@
import { handleError } from '$lib/utils/handle-error';
import { updateAsset, type AssetResponseDto } from '@immich/sdk';
import { mdiMapMarkerOutline, mdiPencil } from '@mdi/js';
import { t } from 'svelte-i18n';
export let isOwner: boolean;
export let asset: AssetResponseDto;
@ -20,7 +21,7 @@
updateAssetDto: { latitude: gps.lat, longitude: gps.lng },
});
} catch (error) {
handleError(error, 'Unable to change location');
handleError(error, $t('errors.unable_to_change_location'));
}
}
</script>
@ -30,7 +31,7 @@
type="button"
class="flex w-full text-left justify-between place-items-start gap-4 py-4"
on:click={() => (isOwner ? (isShowChangeLocation = true) : null)}
title={isOwner ? 'Edit location' : ''}
title={isOwner ? $t('edit_location') : ''}
class:hover:dark:text-immich-dark-primary={isOwner}
class:hover:text-immich-primary={isOwner}
>
@ -63,12 +64,12 @@
type="button"
class="flex w-full text-left justify-between place-items-start gap-4 py-4 rounded-lg hover:dark:text-immich-dark-primary hover:text-immich-primary"
on:click={() => (isShowChangeLocation = true)}
title="Add location"
title={$t('add_location')}
>
<div class="flex gap-4">
<div><Icon path={mdiMapMarkerOutline} size="24" /></div>
<p>Add a location</p>
<p>{$t('add_a_location')}</p>
</div>
<div class="focus:outline-none p-1">
<Icon path={mdiPencil} size="20" />

View file

@ -40,6 +40,7 @@
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
import AlbumListItemDetails from './album-list-item-details.svelte';
import DetailPanelDescription from '$lib/components/asset-viewer/detail-panel-description.svelte';
import { t } from 'svelte-i18n';
export let asset: AssetResponseDto;
export let albums: AlbumResponseDto[] = [];
@ -130,21 +131,21 @@
try {
await updateAsset({ id: asset.id, updateAssetDto: { dateTimeOriginal } });
} catch (error) {
handleError(error, 'Unable to change date');
handleError(error, $t('errors.unable_to_change_date'));
}
}
</script>
<section class="relative p-2 dark:bg-immich-dark-bg dark:text-immich-dark-fg">
<div class="flex place-items-center gap-2">
<CircleIconButton icon={mdiClose} title="Close" on:click={() => dispatch('close')} />
<p class="text-lg text-immich-fg dark:text-immich-dark-fg">Info</p>
<CircleIconButton icon={mdiClose} title={$t('close')} on:click={() => dispatch('close')} />
<p class="text-lg text-immich-fg dark:text-immich-dark-fg">{$t('info')}</p>
</div>
{#if asset.isOffline}
<section class="px-4 py-4">
<div role="alert">
<div class="rounded-t bg-red-500 px-4 py-2 font-bold text-white">Asset offline</div>
<div class="rounded-t bg-red-500 px-4 py-2 font-bold text-white">{$t('asset_offline')}</div>
<div class="rounded-b border border-t-0 border-red-400 bg-red-100 px-4 py-3 text-red-700">
<p>
This asset is offline. Immich can not access its file location. Please ensure the asset is available and
@ -160,11 +161,11 @@
{#if !isSharedLink() && people.length > 0}
<section class="px-4 py-4 text-sm">
<div class="flex h-10 w-full items-center justify-between">
<h2>PEOPLE</h2>
<h2>{$t('people').toUpperCase()}</h2>
<div class="flex gap-2 items-center">
{#if people.some((person) => person.isHidden)}
<CircleIconButton
title="Show hidden people"
title={$t('show_hidden_people')}
icon={showingHiddenPeople ? mdiEyeOff : mdiEye}
padding="1"
buttonSize="32"
@ -172,7 +173,7 @@
/>
{/if}
<CircleIconButton
title="Edit people"
title={$t('edit_people')}
icon={mdiPencil}
padding="1"
size="20"
@ -247,10 +248,10 @@
<div class="px-4 py-4">
{#if asset.exifInfo}
<div class="flex h-10 w-full items-center justify-between text-sm">
<h2>DETAILS</h2>
<h2>{$t('details').toUpperCase()}</h2>
</div>
{:else}
<p class="text-sm">NO EXIF INFO AVAILABLE</p>
<p class="text-sm">{$t('no_exif_info_available').toUpperCase()}</p>
{/if}
{#if asset.exifInfo?.dateTimeOriginal}
@ -261,7 +262,7 @@
type="button"
class="flex w-full text-left justify-between place-items-start gap-4 py-4"
on:click={() => (isOwner ? (isShowChangeDate = true) : null)}
title={isOwner ? 'Edit date' : ''}
title={isOwner ? $t('edit_date') : ''}
class:hover:dark:text-immich-dark-primary={isOwner}
class:hover:text-immich-primary={isOwner}
>
@ -340,7 +341,7 @@
{#if isOwner}
<CircleIconButton
icon={mdiInformationOutline}
title="Show file location"
title={$t('show_file_location')}
size="16"
padding="2"
on:click={toggleAssetPath}
@ -448,7 +449,7 @@
{#if currentAlbum && currentAlbum.albumUsers.length > 0 && asset.owner}
<section class="px-6 dark:text-immich-dark-fg mt-4">
<p class="text-sm">SHARED BY</p>
<p class="text-sm">{$t('shared_by').toUpperCase()}</p>
<div class="flex gap-4 pt-4">
<div>
<UserAvatar user={asset.owner} size="md" />
@ -465,7 +466,7 @@
{#if albums.length > 0}
<section class="p-6 dark:text-immich-dark-fg">
<p class="pb-4 text-sm">APPEARS IN</p>
<p class="pb-4 text-sm">{$t('appears_in').toUpperCase()}</p>
{#each albums as album}
<a data-sveltekit-preload-data="hover" href={`/albums/${album.id}`}>
<div class="flex gap-4 py-2 hover:cursor-pointer items-center">

View file

@ -5,6 +5,7 @@
import { asByteUnitString } from '../../utils/byte-units';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
import { mdiClose } from '@mdi/js';
import { t } from 'svelte-i18n';
const abort = (downloadKey: string, download: DownloadProgress) => {
download.abort?.abort();
@ -17,7 +18,7 @@
transition:fly={{ x: -100, duration: 350 }}
class="absolute bottom-10 left-2 z-[10000] max-h-[270px] w-[315px] rounded-2xl border bg-immich-bg p-4 text-sm shadow-sm"
>
<p class="mb-2 text-xs text-gray-500">DOWNLOADING</p>
<p class="mb-2 text-xs text-gray-500">{$t('downloading').toUpperCase()}</p>
<div class="my-2 mb-2 flex max-h-[200px] flex-col overflow-y-auto text-sm">
{#each Object.keys($downloadAssets) as downloadKey (downloadKey)}
{@const download = $downloadAssets[downloadKey]}
@ -40,7 +41,7 @@
</div>
<div class="absolute right-2">
<CircleIconButton
title="Close"
title={$t('close')}
on:click={() => abort(downloadKey, download)}
size="20"
icon={mdiClose}

View file

@ -15,6 +15,7 @@
import { NotificationType, notificationController } from '../shared-components/notification/notification';
import { getAltText } from '$lib/utils/thumbnail-util';
import { SlideshowLook, slideshowLookCssMapping, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
import { t } from 'svelte-i18n';
const { slideshowState, slideshowLook } = slideshowStore;
@ -99,7 +100,7 @@
await copyImageToClipboard(assetData);
notificationController.show({
type: NotificationType.Info,
message: 'Copied image to clipboard.',
message: $t('copied_image_to_clipboard'),
timeout: 3000,
});
} catch (error) {
@ -134,7 +135,7 @@
});
const onCopyShortcut = (event: KeyboardEvent) => {
if (window.getSelection()?.type === 'Range') {
if (window.getSelection()?.type === $t('range')) {
return;
}
event.preventDefault();

View file

@ -6,6 +6,7 @@
import { mdiChevronLeft, mdiChevronRight, mdiClose, mdiCog, mdiFullscreen, mdiPause, mdiPlay } from '@mdi/js';
import { onDestroy, onMount } from 'svelte';
import { fly } from 'svelte/transition';
import { t } from 'svelte-i18n';
export let isFullScreen: boolean;
export let onNext = () => {};
@ -94,23 +95,28 @@
transition:fly={{ duration: 150 }}
role="navigation"
>
<CircleIconButton buttonSize="50" icon={mdiClose} on:click={onClose} title="Exit Slideshow" />
<CircleIconButton buttonSize="50" icon={mdiClose} on:click={onClose} title={$t('exit_slideshow')} />
<CircleIconButton
buttonSize="50"
icon={progressBarStatus === ProgressBarStatus.Paused ? mdiPlay : mdiPause}
on:click={() => (progressBarStatus === ProgressBarStatus.Paused ? progressBar.play() : progressBar.pause())}
title={progressBarStatus === ProgressBarStatus.Paused ? 'Play' : 'Pause'}
title={progressBarStatus === ProgressBarStatus.Paused ? $t('play') : $t('pause')}
/>
<CircleIconButton buttonSize="50" icon={mdiChevronLeft} on:click={onPrevious} title={$t('previous')} />
<CircleIconButton buttonSize="50" icon={mdiChevronRight} on:click={onNext} title={$t('next')} />
<CircleIconButton
buttonSize="50"
icon={mdiCog}
on:click={() => (showSettings = !showSettings)}
title={$t('next')}
/>
<CircleIconButton buttonSize="50" icon={mdiChevronLeft} on:click={onPrevious} title="Previous" />
<CircleIconButton buttonSize="50" icon={mdiChevronRight} on:click={onNext} title="Next" />
<CircleIconButton buttonSize="50" icon={mdiCog} on:click={() => (showSettings = !showSettings)} title="Next" />
{#if !isFullScreen}
<CircleIconButton
buttonSize="50"
icon={mdiFullscreen}
on:click={onSetToFullScreen}
title="Set Slideshow to fullscreen"
title={$t('set_slideshow_to_fullscreen')}
/>
{/if}
</div>

View file

@ -6,6 +6,7 @@
import { AssetMediaSize } from '@immich/sdk';
import { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';
import { t } from 'svelte-i18n';
export let assetId: string;
export let loopVideo: boolean;
@ -31,7 +32,7 @@
await video.play();
dispatch('onVideoStarted');
} catch (error) {
handleError(error, 'Unable to play video');
handleError(error, $t('errors.unable_to_play_video'));
} finally {
isVideoLoading = false;
}