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
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue