chore(web): another missing translations (#10274)

* chore(web): another missing translations

* unused removed

* more keys

* lint fix

* test fixed

* dynamic translation fix

* fixes

* people search translation

* params fixed

* keep filter setting fix

* lint fix

* $t fixes

* Update web/src/lib/i18n/en.json

Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>

* another missing

* activity translation

* link sharing translations

* expiration dropdown fix - didn't work localized

* notification title

* device logout

* search results

* reset to default

* unsaved change

* select from computer

* selected

* select-2

* select-3

* unmerge

* pluralize, force icu message

* Update web/src/lib/components/asset-viewer/asset-viewer.svelte

Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>

* review fixes

* remove user

* plural fixes

* ffmpeg settings

* fixes

* error title

* plural fixes

* onboarding

* change password

* more more

* console log fix

* another

* api key desc

* map marker

* format fix

* key fix

* asset-utils

* utils

* misc

---------

Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
This commit is contained in:
waclaw66 2024-06-24 15:50:01 +02:00 committed by GitHub
parent df9e074304
commit dd2c7400a6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
90 changed files with 635 additions and 322 deletions

View file

@ -2,6 +2,7 @@ import { sdkMock } from '$lib/__mocks__/sdk.mock';
import { albumFactory } from '@test-data';
import '@testing-library/jest-dom';
import { fireEvent, render, waitFor, type RenderResult } from '@testing-library/svelte';
import { init, register, waitLocale } from 'svelte-i18n';
import AlbumCard from '../album-card.svelte';
const onShowContextMenu = vi.fn();
@ -9,6 +10,12 @@ const onShowContextMenu = vi.fn();
describe('AlbumCard component', () => {
let sut: RenderResult<AlbumCard>;
beforeAll(async () => {
await init({ fallbackLocale: 'en-US' });
register('en-US', () => import('$lib/i18n/en.json'));
await waitLocale('en-US');
});
it.each([
{
album: albumFactory.build({ albumThumbnailAssetId: null, shared: false, assetCount: 0 }),
@ -36,7 +43,7 @@ describe('AlbumCard component', () => {
const albumImgElement = sut.getByTestId('album-image');
const albumNameElement = sut.getByTestId('album-name');
const albumDetailsElement = sut.getByTestId('album-details');
const detailsText = `${count} items` + (shared ? ' . shared' : '');
const detailsText = `${count} items` + (shared ? ' . Shared' : '');
expect(albumImgElement).toHaveAttribute('src');
expect(albumImgElement).toHaveAttribute('alt', album.albumName);

View file

@ -9,6 +9,7 @@
import { mdiChevronRight } from '@mdi/js';
import AlbumCard from '$lib/components/album-page/album-card.svelte';
import Icon from '$lib/components/elements/icon.svelte';
import { t } from 'svelte-i18n';
export let albums: AlbumResponseDto[];
export let group: AlbumGroup | undefined = undefined;
@ -41,7 +42,7 @@
class="inline-block -mt-2.5 transition-all duration-[250ms] {iconRotation}"
/>
<span class="font-bold text-3xl text-black dark:text-white">{group.name}</span>
<span class="ml-1.5">({albums.length} {albums.length > 1 ? 'albums' : 'album'})</span>
<span class="ml-1.5">({$t('albums_count', { values: { count: albums.length } })})</span>
</button>
<hr class="dark:border-immich-dark-gray" />
</div>

View file

@ -1,5 +1,4 @@
<script lang="ts">
import { locale } from '$lib/stores/preferences.store';
import { user } from '$lib/stores/user.store';
import type { AlbumResponseDto } from '@immich/sdk';
import { mdiDotsVertical } from '@mdi/js';
@ -7,7 +6,6 @@
import { getShortDateRange } from '$lib/utils/date-time';
import AlbumCover from '$lib/components/album-page/album-cover.svelte';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { s } from '$lib/utils';
import { t } from 'svelte-i18n';
export let album: AlbumResponseDto;
@ -66,8 +64,7 @@
<span class="flex gap-2 text-sm dark:text-immich-dark-fg" data-testid="album-details">
{#if showItemCount}
<p>
{album.assetCount.toLocaleString($locale)}
item{s(album.assetCount)}
{$t('items_count', { values: { count: album.assetCount } })}
</p>
{/if}
@ -79,7 +76,7 @@
{#if $user.id === album.ownerId}
<p>{$t('owned')}</p>
{:else if album.owner}
<p>Shared by {album.owner.name}</p>
<p>{$t('shared_by_user', { values: { user: album.owner.name } })}</p>
{:else}
<p>{$t('shared')}</p>
{/if}

View file

@ -65,7 +65,7 @@
/>
{/if}
<SettingSwitch
title="Comments & likes"
title={$t('comments_and_likes')}
subtitle={$t('let_others_respond')}
checked={album.isActivityEnabled}
on:toggle={() => dispatch('toggleEnableActivity')}

View file

@ -2,6 +2,7 @@
import { dateFormats } from '$lib/constants';
import { locale } from '$lib/stores/preferences.store';
import type { AlbumResponseDto } from '@immich/sdk';
import { t } from 'svelte-i18n';
export let album: AlbumResponseDto;
@ -28,5 +29,5 @@
<span class="my-2 flex gap-2 text-sm font-medium text-gray-500" data-testid="album-details">
<span>{getDateRange(startDate, endDate)}</span>
<span></span>
<span>{album.assetCount} items</span>
<span>{$t('items_count', { values: { count: album.assetCount } })}</span>
</span>

View file

@ -4,6 +4,7 @@
import Icon from '$lib/components/elements/icon.svelte';
import {
AlbumFilter,
AlbumSortBy,
AlbumGroupBy,
AlbumViewMode,
albumViewSettings,
@ -25,6 +26,7 @@
type AlbumGroupOptionMetadata,
type AlbumSortOptionMetadata,
findGroupOptionMetadata,
findFilterOption,
findSortOptionMetadata,
getSelectedAlbumGroupOption,
groupOptionsMetadata,
@ -43,6 +45,11 @@
return ordering === SortOrder.Asc ? SortOrder.Desc : SortOrder.Asc;
};
const handleChangeAlbumFilter = (filter: string, defaultFilter: AlbumFilter) => {
$albumViewSettings.filter =
Object.keys(albumFilterNames).find((key) => albumFilterNames[key as AlbumFilter] === filter) ?? defaultFilter;
};
const handleChangeGroupBy = ({ id, defaultOrder }: AlbumGroupOptionMetadata) => {
if ($albumViewSettings.groupBy === id) {
$albumViewSettings.groupOrder = flipOrdering($albumViewSettings.groupOrder);
@ -69,6 +76,10 @@
let selectedGroupOption: AlbumGroupOptionMetadata;
let groupIcon: string;
$: selectedFilterOption = albumFilterNames[findFilterOption($albumViewSettings.filter)];
$: selectedSortOption = findSortOptionMetadata($albumViewSettings.sortBy);
$: {
selectedGroupOption = findGroupOptionMetadata($albumViewSettings.groupBy);
if (selectedGroupOption.isDisabled()) {
@ -76,8 +87,6 @@
}
}
$: selectedSortOption = findSortOptionMetadata($albumViewSettings.sortBy);
$: {
if (selectedGroupOption.id === AlbumGroupBy.None) {
groupIcon = mdiFolderRemoveOutline;
@ -88,14 +97,41 @@
}
$: sortIcon = $albumViewSettings.sortOrder === SortOrder.Desc ? mdiArrowDownThin : mdiArrowUpThin;
$: albumFilterNames = ((): Record<AlbumFilter, string> => {
return {
[AlbumFilter.All]: $t('all'),
[AlbumFilter.Owned]: $t('owned'),
[AlbumFilter.Shared]: $t('shared'),
};
})();
$: albumSortByNames = ((): Record<AlbumSortBy, string> => {
return {
[AlbumSortBy.Title]: $t('sort_title'),
[AlbumSortBy.ItemCount]: $t('sort_items'),
[AlbumSortBy.DateModified]: $t('sort_modified'),
[AlbumSortBy.DateCreated]: $t('sort_created'),
[AlbumSortBy.MostRecentPhoto]: $t('sort_recent'),
[AlbumSortBy.OldestPhoto]: $t('sort_oldest'),
};
})();
$: albumGroupByNames = ((): Record<AlbumGroupBy, string> => {
return {
[AlbumGroupBy.None]: $t('group_no'),
[AlbumGroupBy.Owner]: $t('group_owner'),
[AlbumGroupBy.Year]: $t('group_year'),
};
})();
</script>
<!-- Filter Albums by Sharing Status (All, Owned, Shared) -->
<div class="hidden xl:block h-10">
<GroupTab
filters={Object.keys(AlbumFilter)}
selected={$albumViewSettings.filter}
onSelect={(selected) => ($albumViewSettings.filter = selected)}
filters={Object.values(albumFilterNames)}
selected={selectedFilterOption}
onSelect={(selected) => handleChangeAlbumFilter(selected, AlbumFilter.All)}
/>
</div>
@ -118,8 +154,8 @@
options={Object.values(sortOptionsMetadata)}
selectedOption={selectedSortOption}
on:select={({ detail }) => handleChangeSortBy(detail)}
render={({ text }) => ({
title: text,
render={({ id }) => ({
title: albumSortByNames[id],
icon: sortIcon,
})}
/>
@ -130,8 +166,8 @@
options={Object.values(groupOptionsMetadata)}
selectedOption={selectedGroupOption}
on:select={({ detail }) => handleChangeGroupBy(detail)}
render={({ text, isDisabled }) => ({
title: text,
render={({ id, isDisabled }) => ({
title: albumGroupByNames[id],
icon: groupIcon,
disabled: isDisabled(),
})}

View file

@ -304,7 +304,7 @@
const isConfirmed = await dialogController.show({
id: 'delete-album',
prompt: `Are you sure you want to delete the album ${albumToDelete.albumName}?\nIf this album is shared, other users will not be able to access it anymore.`,
prompt: $t('album_delete_confirmation', { values: { album: albumToDelete.albumName } }),
});
if (!isConfirmed) {
@ -340,7 +340,7 @@
message: $t('album_info_updated'),
type: NotificationType.Info,
button: {
text: 'View Album',
text: $t('view_album'),
onClick() {
return goto(`${AppRoute.ALBUMS}/${album.id}`);
},

View file

@ -1,6 +1,7 @@
<script lang="ts">
import { albumViewSettings, SortOrder } from '$lib/stores/preferences.store';
import { albumViewSettings, SortOrder, AlbumSortBy } from '$lib/stores/preferences.store';
import type { AlbumSortOptionMetadata } from '$lib/utils/album-utils';
import { t } from 'svelte-i18n';
export let option: AlbumSortOptionMetadata;
@ -12,6 +13,17 @@
$albumViewSettings.sortOrder = option.defaultOrder;
}
};
$: albumSortByNames = ((): Record<AlbumSortBy, string> => {
return {
[AlbumSortBy.Title]: $t('sort_title'),
[AlbumSortBy.ItemCount]: $t('sort_items'),
[AlbumSortBy.DateModified]: $t('sort_modified'),
[AlbumSortBy.DateCreated]: $t('sort_created'),
[AlbumSortBy.MostRecentPhoto]: $t('sort_recent'),
[AlbumSortBy.OldestPhoto]: $t('sort_oldest'),
};
})();
</script>
<th class="text-sm font-medium {option.columnStyle}">
@ -27,6 +39,6 @@
&#8593;
{/if}
{/if}
{option.text}
{albumSortByNames[option.id]}
</button>
</th>

View file

@ -34,7 +34,9 @@
path={mdiShareVariantOutline}
size="16"
class="inline ml-1 opacity-70"
title={album.ownerId === $user.id ? $t('shared_by_you') : `Shared by ${album.owner.name}`}
title={album.ownerId === $user.id
? $t('shared_by_you')
: $t('shared_by_user', { values: { user: album.owner.name } })}
/>
{/if}
</td>

View file

@ -13,6 +13,7 @@
sortOptionsMetadata,
type AlbumGroup,
} from '$lib/utils/album-utils';
import { t } from 'svelte-i18n';
export let groupedAlbums: AlbumGroup[];
export let albumGroupOption: string = AlbumGroupBy.None;
@ -58,8 +59,7 @@
/>
<span class="font-bold text-2xl">{albumGroup.name}</span>
<span class="ml-1.5">
({albumGroup.albums.length}
{albumGroup.albums.length > 1 ? 'albums' : 'album'})
({$t('albums_count', { values: { count: albumGroup.albums.length } })})
</span>
</td>
</tr>

View file

@ -53,7 +53,10 @@
try {
await removeUserFromAlbum({ id: album.id, userId });
dispatch('remove', userId);
const message = userId === 'me' ? `Left ${album.albumName}` : `Removed ${selectedRemoveUser.name}`;
const message =
userId === 'me'
? $t('album_user_left', { values: { album: album.albumName } })
: $t('album_user_removed', { values: { user: selectedRemoveUser.name } });
notificationController.show({ type: NotificationType.Info, message });
} catch (error) {
handleError(error, $t('errors.unable_to_remove_album_users'));
@ -65,7 +68,9 @@
const handleSetReadonly = async (user: UserResponseDto, role: AlbumUserRole) => {
try {
await updateAlbumUser({ id: album.id, userId: user.id, updateAlbumUserDto: { role } });
const message = `Set ${user.name} as ${role}`;
const message = $t('user_role_set', {
values: { user: user.name, role: role == AlbumUserRole.Viewer ? $t('role_viewer') : $t('role_editor') },
});
dispatch('refreshAlbum');
notificationController.show({ type: NotificationType.Info, message });
} catch (error) {
@ -101,9 +106,9 @@
<div id="icon-{user.id}" class="flex place-items-center gap-2 text-sm">
<div>
{#if role === AlbumUserRole.Viewer}
Viewer
{$t('role_viewer')}
{:else}
Editor
{$t('role_editor')}
{/if}
</div>
{#if isOwned}
@ -135,8 +140,8 @@
{#if selectedRemoveUser && selectedRemoveUser?.id === currentUser?.id}
<ConfirmDialog
title="Leave album?"
prompt="Are you sure you want to leave {album.albumName}?"
title={$t('album_leave')}
prompt={$t('album_leave_confirmation', { values: { album: album.albumName } })}
confirmText={$t('leave')}
onConfirm={handleRemoveUser}
onCancel={() => (selectedRemoveUser = null)}
@ -145,9 +150,9 @@
{#if selectedRemoveUser && selectedRemoveUser?.id !== currentUser?.id}
<ConfirmDialog
title="Remove user?"
prompt="Are you sure you want to remove {selectedRemoveUser.name}?"
confirmText={$t('remove')}
title={$t('album_remove_user')}
prompt={$t('album_remove_user_confirmation', { values: { user: selectedRemoveUser.name } })}
confirmText={$t('remove_user')}
onConfirm={handleRemoveUser}
onCancel={() => (selectedRemoveUser = null)}
/>

View file

@ -37,7 +37,7 @@
disabled={selectedThumbnail == undefined}
on:click={() => dispatch('thumbnail', selectedThumbnail)}
>
Done
{$t('done')}
</Button>
</svelte:fragment>
</ControlAppBar>

View file

@ -24,9 +24,9 @@
let selectedUsers: Record<string, { user: UserResponseDto; role: AlbumUserRole }> = {};
const roleOptions: Array<{ title: string; value: AlbumUserRole | 'none'; icon?: string }> = [
{ title: $t('editor'), value: AlbumUserRole.Editor, icon: mdiPencil },
{ title: $t('viewer'), value: AlbumUserRole.Viewer, icon: mdiEye },
{ title: $t('remove'), value: 'none' },
{ title: $t('role_editor'), value: AlbumUserRole.Editor, icon: mdiPencil },
{ title: $t('role_viewer'), value: AlbumUserRole.Viewer, icon: mdiEye },
{ title: $t('remove_user'), value: 'none' },
];
const dispatch = createEventDispatcher<{
@ -110,7 +110,7 @@
{#if users.length + Object.keys(selectedUsers).length === 0}
<p class="p-5 text-sm">
Looks like you have shared this album with all users or you don't have any user to share with.
{$t('album_share_no_users')}
</p>
{/if}