mirror of
https://github.com/immich-app/immich
synced 2025-11-07 17:27:20 +00:00
feat(web): translations (#9854)
* First test * Added translation using Weblate (French) * Translated using Weblate (German) Currently translated at 100.0% (4 of 4 strings) Translation: immich/web Translate-URL: http://familie-mach.net/projects/immich/web/de/ * Translated using Weblate (French) Currently translated at 100.0% (4 of 4 strings) Translation: immich/web Translate-URL: http://familie-mach.net/projects/immich/web/fr/ * Further testing * Further testing * Translated using Weblate (German) Currently translated at 100.0% (18 of 18 strings) Translation: immich/web Translate-URL: http://familie-mach.net/projects/immich/web/de/ * Further work * Update string file. * More strings * Automatically changed strings * Add automatically translated german file for testing purposes * Fix merge-face-selector component * Make server stats strings uppercase * Fix uppercase string * Fix some strings in jobs-panel * Fix lower and uppercase strings. Add a few additional string. Fix a few unnecessary replacements * Update german test translations * Fix typo in locales file * Change string keys * Extract more strings * Extract and replace some more strings * Update testtranslationfile * Change translation keys * Fix rebase errors * Fix one more rebase error * Remove german translation file * Co-authored-by: Daniel Dietzler <danieldietzler@users.noreply.github.com> * chore: clean up translations * chore: add new line * fix formatting * chore: fixes * fix: loading and tests --------- Co-authored-by: root <root@Blacki> Co-authored-by: admin <admin@example.com> Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
This commit is contained in:
parent
a2bccf23c9
commit
f446bc8caa
177 changed files with 2779 additions and 1017 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue