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

@ -12,6 +12,7 @@
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
import SearchPeople from '$lib/components/faces-page/people-search.svelte';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { t } from 'svelte-i18n';
export let peopleWithFaces: AssetFaceResponseDto[];
export let allPeople: PersonResponseDto[];
@ -119,19 +120,19 @@
<div class="flex place-items-center justify-between gap-2">
{#if !searchFaces}
<div class="flex items-center gap-2">
<CircleIconButton icon={mdiArrowLeftThin} title="Back" on:click={handleBackButton} />
<p class="flex text-lg text-immich-fg dark:text-immich-dark-fg">Select face</p>
<CircleIconButton icon={mdiArrowLeftThin} title={$t('back')} on:click={handleBackButton} />
<p class="flex text-lg text-immich-fg dark:text-immich-dark-fg">{$t('select_face')}</p>
</div>
<div class="flex justify-end gap-2">
<CircleIconButton
icon={mdiMagnify}
title="Search for existing person"
title={$t('search_for_existing_person')}
on:click={() => {
searchFaces = true;
}}
/>
{#if !isShowLoadingNewPerson}
<CircleIconButton icon={mdiPlus} title="Create new person" on:click={handleCreatePerson} />
<CircleIconButton icon={mdiPlus} title={$t('create_new_person')} on:click={handleCreatePerson} />
{:else}
<div class="flex place-content-center place-items-center">
<LoadingSpinner />
@ -139,7 +140,7 @@
{/if}
</div>
{:else}
<CircleIconButton icon={mdiArrowLeftThin} title="Back" on:click={handleBackButton} />
<CircleIconButton icon={mdiArrowLeftThin} title={$t('back')} on:click={handleBackButton} />
<div class="w-full flex">
<SearchPeople
type="input"
@ -153,11 +154,11 @@
</div>
{/if}
</div>
<CircleIconButton icon={mdiClose} title="Cancel search" on:click={() => (searchFaces = false)} />
<CircleIconButton icon={mdiClose} title={$t('cancel_search')} on:click={() => (searchFaces = false)} />
{/if}
</div>
<div class="px-4 py-4 text-sm">
<h2 class="mb-8 mt-4 uppercase">All people</h2>
<h2 class="mb-8 mt-4 uppercase">{$t('all_people')}</h2>
<div class="immich-scrollbar mt-4 flex flex-wrap gap-2 overflow-y-auto">
{#each showPeople as person (person.id)}
{#if person.id !== editedPerson.id}

View file

@ -4,6 +4,7 @@
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
import Button from '../elements/buttons/button.svelte';
import SearchPeople from '$lib/components/faces-page/people-search.svelte';
import { t } from 'svelte-i18n';
export let person: PersonResponseDto;
export let name: string;
@ -35,6 +36,6 @@
inputClass="w-full gap-2 bg-gray-100 dark:bg-gray-700 dark:text-white"
bind:showLoadingSpinner={isSearchingPeople}
/>
<Button size="sm" type="submit">Done</Button>
<Button size="sm" type="submit">{$t('done')}</Button>
</form>
</div>

View file

@ -17,6 +17,7 @@
import FaceThumbnail from './face-thumbnail.svelte';
import PeopleList from './people-list.svelte';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
import { t } from 'svelte-i18n';
export let person: PersonResponseDto;
let people: PersonResponseDto[] = [];
@ -86,7 +87,7 @@
});
dispatch('merge', mergedPerson);
} catch (error) {
handleError(error, 'Cannot merge people');
handleError(error, $t('cannot_merge_people'));
}
};
</script>
@ -134,7 +135,7 @@
{#if selectedPeople.length === 1}
<div class="absolute bottom-2">
<CircleIconButton
title="Swap merge direction"
title={$t('swap_merge_direction')}
icon={mdiSwapHorizontal}
size="24"
on:click={handleSwapPeople}

View file

@ -8,6 +8,7 @@
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
import Button from '../elements/buttons/button.svelte';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
import { t } from 'svelte-i18n';
export let personMerge1: PersonResponseDto;
export let personMerge2: PersonResponseDto;
@ -44,7 +45,7 @@
</div>
<div class="mx-0.5 flex md:mx-2">
<CircleIconButton
title="Swap merge direction"
title={$t('swap_merge_direction')}
icon={mdiMerge}
on:click={() => ([personMerge1, personMerge2] = [personMerge2, personMerge1])}
/>
@ -104,7 +105,7 @@
<p class="text-sm text-gray-500 dark:text-gray-300">They will be merged together</p>
</div>
<svelte:fragment slot="sticky-bottom">
<Button fullwidth color="gray" on:click={() => dispatch('reject')}>No</Button>
<Button fullwidth on:click={() => dispatch('confirm', [personMerge1, personMerge2])}>Yes</Button>
<Button fullwidth color="gray" on:click={() => dispatch('reject')}>{$t('no')}</Button>
<Button fullwidth on:click={() => dispatch('confirm', [personMerge1, personMerge2])}>{$t('yes')}</Button>
</svelte:fragment>
</FullScreenModal>

View file

@ -16,6 +16,7 @@
import MenuOption from '../shared-components/context-menu/menu-option.svelte';
import Portal from '../shared-components/portal/portal.svelte';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { t } from 'svelte-i18n';
export let person: PersonResponseDto;
export let preload = false;
@ -76,7 +77,7 @@
<CircleIconButton
color="opaque"
icon={mdiDotsVertical}
title="Show person options"
title={$t('show_person_options')}
size="20"
padding="2"
class="icon-white-drop-shadow"
@ -88,17 +89,17 @@
{#if showContextMenu}
<Portal target="body">
<ContextMenu {...contextMenuPosition} onClose={() => onMenuExit()}>
<MenuOption on:click={() => onMenuClick('hide-person')} icon={mdiEyeOffOutline} text="Hide person" />
<MenuOption on:click={() => onMenuClick('change-name')} icon={mdiAccountEditOutline} text="Change name" />
<MenuOption on:click={() => onMenuClick('hide-person')} icon={mdiEyeOffOutline} text={$t('hide_person')} />
<MenuOption on:click={() => onMenuClick('change-name')} icon={mdiAccountEditOutline} text={$t('change_name')} />
<MenuOption
on:click={() => onMenuClick('set-birth-date')}
icon={mdiCalendarEditOutline}
text="Set date of birth"
text={$t('set_date_of_birth')}
/>
<MenuOption
on:click={() => onMenuClick('merge-people')}
icon={mdiAccountMultipleCheckOutline}
text="Merge people"
text={$t('merge_people')}
/>
</ContextMenu>
</Portal>

View file

@ -3,6 +3,7 @@
import { createEventDispatcher } from 'svelte';
import FaceThumbnail from './face-thumbnail.svelte';
import SearchPeople from '$lib/components/faces-page/people-search.svelte';
import { t } from 'svelte-i18n';
export let screenHeight: number;
export let people: PersonResponseDto[];
@ -25,7 +26,7 @@
</script>
<div class=" w-40 sm:w-48 md:w-96 h-14 mb-8">
<SearchPeople type="searchBar" placeholder="Search people" bind:searchName={name} bind:searchedPeopleLocal />
<SearchPeople type="searchBar" placeholder={$t('search_people')} bind:searchName={name} bind:searchedPeopleLocal />
</div>
<div

View file

@ -5,6 +5,7 @@
import { handleError } from '$lib/utils/handle-error';
import { searchNameLocal } from '$lib/utils/person';
import { searchPerson, type PersonResponseDto } from '@immich/sdk';
import { t } from 'svelte-i18n';
export let searchName: string;
export let searchedPeopleLocal: PersonResponseDto[];
@ -12,7 +13,7 @@
export let numberPeopleToSearch: number = maximumLengthSearchPeople;
export let inputClass: string = 'w-full gap-2 bg-immich-bg dark:bg-immich-dark-bg';
export let showLoadingSpinner: boolean = false;
export let placeholder: string = 'Name or nickname';
export let placeholder: string = $t('name_or_nickname');
export let onReset = () => {};
export let onSearch = () => {};
@ -61,7 +62,7 @@
searchedPeople = data;
searchWord = searchName;
} catch (error) {
handleError(error, "Can't search people");
handleError(error, $t('cant_search_people'));
} finally {
clearTimeout(timeout);
timeout = null;

View file

@ -23,6 +23,7 @@
import { NotificationType, notificationController } from '../shared-components/notification/notification';
import AssignFaceSidePanel from './assign-face-side-panel.svelte';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { t } from 'svelte-i18n';
export let assetId: string;
export let assetType: AssetTypeEnum;
@ -64,7 +65,7 @@
allPeople = people;
peopleWithFaces = await getFaces({ id: assetId });
} catch (error) {
handleError(error, "Can't get faces");
handleError(error, $t('cant_get_faces'));
} finally {
clearTimeout(timeout);
}
@ -142,7 +143,7 @@
type: NotificationType.Info,
});
} catch (error) {
handleError(error, "Can't apply changes");
handleError(error, $t('cant_apply_changes'));
}
}
@ -184,8 +185,8 @@
>
<div class="flex place-items-center justify-between gap-2">
<div class="flex items-center gap-2">
<CircleIconButton icon={mdiArrowLeftThin} title="Back" on:click={handleBackButton} />
<p class="flex text-lg text-immich-fg dark:text-immich-dark-fg">Edit faces</p>
<CircleIconButton icon={mdiArrowLeftThin} title={$t('back')} on:click={handleBackButton} />
<p class="flex text-lg text-immich-fg dark:text-immich-dark-fg">{$t('edit_faces')}</p>
</div>
{#if !isShowLoadingDone}
<button
@ -225,7 +226,7 @@
shadow
url={selectedPersonToCreate[face.id]}
altText={selectedPersonToCreate[face.id]}
title={'New person'}
title={$t('new_person')}
widthStyle={thumbnailWidth}
heightStyle={thumbnailWidth}
/>
@ -272,7 +273,7 @@
<CircleIconButton
color="primary"
icon={mdiRestart}
title="Reset"
title={$t('reset')}
size="18"
padding="1"
class="absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform"
@ -282,7 +283,7 @@
<CircleIconButton
color="primary"
icon={mdiMinus}
title="Select new face"
title={$t('select_new_face')}
size="18"
padding="1"
class="absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform"

View file

@ -4,6 +4,7 @@
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
import { mdiCake } from '@mdi/js';
import DateInput from '../elements/date-input.svelte';
import { t } from 'svelte-i18n';
export let birthDate: string;
@ -20,7 +21,7 @@
};
</script>
<FullScreenModal title="Set date of birth" icon={mdiCake} onClose={handleCancel}>
<FullScreenModal title={$t('set_date_of_birth')} icon={mdiCake} onClose={handleCancel}>
<div class="text-immich-primary dark:text-immich-dark-primary">
<p class="text-sm dark:text-immich-dark-fg">
Date of birth is used to calculate the age of this person at the time of a photo.
@ -40,7 +41,7 @@
</div>
</form>
<svelte:fragment slot="sticky-bottom">
<Button color="gray" fullwidth on:click={() => handleCancel()}>Cancel</Button>
<Button type="submit" fullwidth form="set-birth-date-form">Set</Button>
<Button color="gray" fullwidth on:click={() => handleCancel()}>{$t('cancel')}</Button>
<Button type="submit" fullwidth form="set-birth-date-form">{$t('set')}</Button>
</svelte:fragment>
</FullScreenModal>

View file

@ -14,6 +14,7 @@
import { mdiClose, mdiEye, mdiEyeOff, mdiEyeSettings, mdiRestart } from '@mdi/js';
import { locale } from '$lib/stores/preferences.store';
import Button from '$lib/components/elements/buttons/button.svelte';
import { t } from 'svelte-i18n';
export let showLoadingSpinner: boolean;
export let toggleVisibility: ToggleVisibilty = ToggleVisibilty.VIEW_ALL;
@ -51,7 +52,7 @@
class="fixed top-0 z-10 flex h-16 w-full items-center justify-between border-b bg-white p-1 dark:border-immich-dark-gray dark:bg-black dark:text-immich-dark-fg md:p-8"
>
<div class="flex items-center">
<CircleIconButton title="Close" icon={mdiClose} on:click={onClose} />
<CircleIconButton title={$t('close')} icon={mdiClose} on:click={onClose} />
<div class="flex gap-2 items-center">
<p class="ml-2">Show & hide people</p>
<p class="text-sm text-gray-400 dark:text-gray-600">({countTotalPeople.toLocaleString($locale)})</p>
@ -59,15 +60,15 @@
</div>
<div class="flex items-center justify-end">
<div class="flex items-center md:mr-8">
<CircleIconButton title="Reset people visibility" icon={mdiRestart} on:click={onReset} />
<CircleIconButton title={$t('reset_people_visibility')} icon={mdiRestart} on:click={onReset} />
<CircleIconButton
title="Toggle visibility"
title={$t('toggle_visibility')}
icon={toggleIcon}
on:click={() => onChange(getNextVisibility(toggleVisibility))}
/>
</div>
{#if !showLoadingSpinner}
<Button on:click={onDone} size="sm" rounded="lg">Done</Button>
<Button on:click={onDone} size="sm" rounded="lg">{$t('done')}</Button>
{:else}
<LoadingSpinner />
{/if}