mirror of
https://github.com/immich-app/immich
synced 2025-11-14 17:36:12 +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 { shortcuts } from '$lib/actions/shortcut';
|
||||
import { focusOutside } from '$lib/actions/focus-outside';
|
||||
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let value = '';
|
||||
export let grayTheme: boolean;
|
||||
|
|
@ -103,9 +104,9 @@
|
|||
on:submit|preventDefault={onSubmit}
|
||||
>
|
||||
<div class="absolute inset-y-0 left-0 flex items-center pl-2">
|
||||
<CircleIconButton type="submit" title="Search" icon={mdiMagnify} size="20" />
|
||||
<CircleIconButton type="submit" title={$t('search')} icon={mdiMagnify} size="20" />
|
||||
</div>
|
||||
<label for="main-search-bar" class="sr-only">Search your photos</label>
|
||||
<label for="main-search-bar" class="sr-only">{$t('search_your_photos')}</label>
|
||||
<input
|
||||
type="text"
|
||||
name="q"
|
||||
|
|
@ -117,7 +118,7 @@
|
|||
showFilter
|
||||
? 'rounded-t-3xl border border-gray-200 bg-white dark:border-gray-800'
|
||||
: 'rounded-3xl border border-transparent bg-gray-200'}"
|
||||
placeholder="Search your photos"
|
||||
placeholder={$t('search_your_photos')}
|
||||
required
|
||||
pattern="^(?!m:$).*$"
|
||||
bind:value
|
||||
|
|
@ -132,11 +133,11 @@
|
|||
/>
|
||||
|
||||
<div class="absolute inset-y-0 {showClearIcon ? 'right-14' : 'right-2'} flex items-center pl-6 transition-all">
|
||||
<CircleIconButton title="Show search options" icon={mdiTune} on:click={onFilterClick} size="20" />
|
||||
<CircleIconButton title={$t('show_search_options')} icon={mdiTune} on:click={onFilterClick} size="20" />
|
||||
</div>
|
||||
{#if showClearIcon}
|
||||
<div class="absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<CircleIconButton type="reset" icon={mdiClose} title="Clear" size="20" />
|
||||
<CircleIconButton type="reset" icon={mdiClose} title={$t('clear')} size="20" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
import { SearchSuggestionType, getSearchSuggestions } from '@immich/sdk';
|
||||
import Combobox, { toComboBoxOptions } from '../combobox.svelte';
|
||||
import { handlePromiseError } from '$lib/utils';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let filters: SearchCameraFilter;
|
||||
|
||||
|
|
@ -36,25 +37,25 @@
|
|||
</script>
|
||||
|
||||
<div id="camera-selection">
|
||||
<p class="immich-form-label">CAMERA</p>
|
||||
<p class="immich-form-label">{$t('camera').toUpperCase()}</p>
|
||||
|
||||
<div class="grid grid-cols-[repeat(auto-fit,minmax(10rem,1fr))] gap-5 mt-1">
|
||||
<div class="w-full">
|
||||
<Combobox
|
||||
label="Make"
|
||||
label={$t('make')}
|
||||
on:select={({ detail }) => (filters.make = detail?.value)}
|
||||
options={toComboBoxOptions(makes)}
|
||||
placeholder="Search camera make..."
|
||||
placeholder={$t('search_camera_make')}
|
||||
selectedOption={makeFilter ? { label: makeFilter, value: makeFilter } : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="w-full">
|
||||
<Combobox
|
||||
label="Model"
|
||||
label={$t('model')}
|
||||
on:select={({ detail }) => (filters.model = detail?.value)}
|
||||
options={toComboBoxOptions(models)}
|
||||
placeholder="Search camera model..."
|
||||
placeholder={$t('search_camera_model')}
|
||||
selectedOption={modelFilter ? { label: modelFilter, value: modelFilter } : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,13 +7,14 @@
|
|||
|
||||
<script lang="ts">
|
||||
import DateInput from '$lib/components/elements/date-input.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let filters: SearchDateFilter;
|
||||
</script>
|
||||
|
||||
<div id="date-range-selection" class="grid grid-cols-[repeat(auto-fit,minmax(10rem,1fr))] gap-5">
|
||||
<label class="immich-form-label" for="start-date">
|
||||
<span>START DATE</span>
|
||||
<span>{$t('start_date').toUpperCase()}</span>
|
||||
<DateInput
|
||||
class="immich-form-input w-full mt-1 hover:cursor-pointer"
|
||||
type="date"
|
||||
|
|
@ -25,7 +26,7 @@
|
|||
</label>
|
||||
|
||||
<label class="immich-form-label" for="end-date">
|
||||
<span>END DATE</span>
|
||||
<span>{$t('end_date').toUpperCase()}</span>
|
||||
<DateInput
|
||||
class="immich-form-input w-full mt-1 hover:cursor-pointer"
|
||||
type="date"
|
||||
|
|
|
|||
|
|
@ -8,17 +8,18 @@
|
|||
|
||||
<script lang="ts">
|
||||
import Checkbox from '$lib/components/elements/checkbox.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let filters: SearchDisplayFilters;
|
||||
</script>
|
||||
|
||||
<div id="display-options-selection">
|
||||
<fieldset>
|
||||
<legend class="immich-form-label">DISPLAY OPTIONS</legend>
|
||||
<legend class="immich-form-label">{$t('display_options').toUpperCase()}</legend>
|
||||
<div class="flex flex-wrap gap-x-5 gap-y-2 mt-1">
|
||||
<Checkbox id="not-in-album-checkbox" label="Not in any album" bind:checked={filters.isNotInAlbum} />
|
||||
<Checkbox id="archive-checkbox" label="Archive" bind:checked={filters.isArchive} />
|
||||
<Checkbox id="favorite-checkbox" label="Favorite" bind:checked={filters.isFavorite} />
|
||||
<Checkbox id="not-in-album-checkbox" label={$t('not_in_any_album')} bind:checked={filters.isNotInAlbum} />
|
||||
<Checkbox id="archive-checkbox" label={$t('archive')} bind:checked={filters.isArchive} />
|
||||
<Checkbox id="favorite-checkbox" label={$t('favorite')} bind:checked={filters.isFavorite} />
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
import { parseUtcDate } from '$lib/utils/date-time';
|
||||
import SearchDisplaySection from './search-display-section.svelte';
|
||||
import SearchTextSection from './search-text-section.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let searchQuery: MetadataSearchDto | SmartSearchDto;
|
||||
|
||||
|
|
@ -153,8 +154,8 @@
|
|||
id="button-row"
|
||||
class="flex justify-end gap-4 border-t dark:border-gray-800 dark:bg-immich-dark-gray px-4 sm:py-6 py-4 mt-2 rounded-b-3xl"
|
||||
>
|
||||
<Button type="reset" color="gray">Clear all</Button>
|
||||
<Button type="submit">Search</Button>
|
||||
<Button type="reset" color="gray">{$t('clear_all')}</Button>
|
||||
<Button type="submit">{$t('search')}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import { mdiMagnify, mdiClose } from '@mdi/js';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { fly } from 'svelte/transition';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
selectSearchTerm: string;
|
||||
|
|
@ -18,12 +19,12 @@
|
|||
>
|
||||
{#if $savedSearchTerms.length > 0}
|
||||
<div class="flex items-center justify-between px-5 pt-5 text-xs">
|
||||
<p>RECENT SEARCHES</p>
|
||||
<p>{$t('recent_searches').toUpperCase()}</p>
|
||||
<div class="flex w-18 items-center justify-center">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg p-2 font-semibold text-immich-primary hover:bg-immich-primary/25 dark:text-immich-dark-primary"
|
||||
on:click={() => dispatch('clearAllSearchTerms')}>Clear all</button
|
||||
on:click={() => dispatch('clearAllSearchTerms')}>{$t('clear_all')}</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
import { getSearchSuggestions, SearchSuggestionType } from '@immich/sdk';
|
||||
import Combobox, { toComboBoxOptions } from '../combobox.svelte';
|
||||
import { handlePromiseError } from '$lib/utils';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let filters: SearchLocationFilter;
|
||||
|
||||
|
|
@ -58,35 +59,35 @@
|
|||
</script>
|
||||
|
||||
<div id="location-selection">
|
||||
<p class="immich-form-label">PLACE</p>
|
||||
<p class="immich-form-label">{$t('place').toUpperCase()}</p>
|
||||
|
||||
<div class="grid grid-cols-[repeat(auto-fit,minmax(10rem,1fr))] gap-5 mt-1">
|
||||
<div class="w-full">
|
||||
<Combobox
|
||||
label="Country"
|
||||
label={$t('country')}
|
||||
on:select={({ detail }) => (filters.country = detail?.value)}
|
||||
options={toComboBoxOptions(countries)}
|
||||
placeholder="Search country..."
|
||||
placeholder={$t('search_country')}
|
||||
selectedOption={filters.country ? { label: filters.country, value: filters.country } : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="w-full">
|
||||
<Combobox
|
||||
label="State"
|
||||
label={$t('state')}
|
||||
on:select={({ detail }) => (filters.state = detail?.value)}
|
||||
options={toComboBoxOptions(states)}
|
||||
placeholder="Search state..."
|
||||
placeholder={$t('search_state')}
|
||||
selectedOption={filters.state ? { label: filters.state, value: filters.state } : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="w-full">
|
||||
<Combobox
|
||||
label="City"
|
||||
label={$t('city')}
|
||||
on:select={({ detail }) => (filters.city = detail?.value)}
|
||||
options={toComboBoxOptions(cities)}
|
||||
placeholder="Search city..."
|
||||
placeholder={$t('search_city')}
|
||||
selectedOption={filters.city ? { label: filters.city, value: filters.city } : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,30 @@
|
|||
<script lang="ts">
|
||||
import RadioButton from '$lib/components/elements/radio-button.svelte';
|
||||
import { MediaType } from './search-filter-box.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let filteredMedia: MediaType;
|
||||
</script>
|
||||
|
||||
<div id="media-type-selection">
|
||||
<fieldset>
|
||||
<legend class="immich-form-label">MEDIA TYPE</legend>
|
||||
<legend class="immich-form-label">{$t('media_type').toUpperCase()}</legend>
|
||||
<div class="flex flex-wrap gap-x-5 gap-y-2 mt-1">
|
||||
<RadioButton name="media-type" id="type-all" bind:group={filteredMedia} label="All" value={MediaType.All} />
|
||||
<RadioButton name="media-type" id="type-image" bind:group={filteredMedia} label="Image" value={MediaType.Image} />
|
||||
<RadioButton name="media-type" id="type-video" bind:group={filteredMedia} label="Video" value={MediaType.Video} />
|
||||
<RadioButton name="media-type" id="type-all" bind:group={filteredMedia} label={$t('all')} value={MediaType.All} />
|
||||
<RadioButton
|
||||
name="media-type"
|
||||
id="type-image"
|
||||
bind:group={filteredMedia}
|
||||
label={$t('image')}
|
||||
value={MediaType.Image}
|
||||
/>
|
||||
<RadioButton
|
||||
name="media-type"
|
||||
id="type-video"
|
||||
bind:group={filteredMedia}
|
||||
label={$t('video')}
|
||||
value={MediaType.Video}
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
import { getAllPeople, type PersonResponseDto } from '@immich/sdk';
|
||||
import { mdiClose, mdiArrowRight } from '@mdi/js';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let width: number;
|
||||
export let selectedPeople: Set<string>;
|
||||
|
|
@ -28,7 +29,7 @@
|
|||
const res = await getAllPeople({ withHidden: false });
|
||||
return orderBySelectedPeopleFirst(res.people);
|
||||
} catch (error) {
|
||||
handleError(error, 'Failed to get people');
|
||||
handleError(error, $t('failed_to_get_people'));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -55,8 +56,8 @@
|
|||
|
||||
<div id="people-selection" class="-mb-4">
|
||||
<div class="flex items-center w-full justify-between gap-6">
|
||||
<p class="immich-form-label py-3">PEOPLE</p>
|
||||
<SearchBar bind:name placeholder="Filter people" showLoadingSpinner={false} />
|
||||
<p class="immich-form-label py-3">{$t('people').toUpperCase()}</p>
|
||||
<SearchBar bind:name placeholder={$t('filter_people')} showLoadingSpinner={false} />
|
||||
</div>
|
||||
|
||||
<div class="flex -mx-1 max-h-64 gap-1 mt-2 flex-wrap overflow-y-auto immich-scrollbar">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import RadioButton from '$lib/components/elements/radio-button.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let filename: string | undefined;
|
||||
export let context: string | undefined;
|
||||
|
|
@ -21,33 +22,33 @@
|
|||
</script>
|
||||
|
||||
<fieldset>
|
||||
<legend class="immich-form-label">Search type</legend>
|
||||
<legend class="immich-form-label">{$t('search_type')}</legend>
|
||||
<div class="flex flex-wrap gap-x-5 gap-y-2 mt-1 mb-2">
|
||||
<RadioButton
|
||||
name="query-type"
|
||||
id="context-radio"
|
||||
bind:group={selectedOption}
|
||||
label="Context"
|
||||
label={$t('context')}
|
||||
value={TextSearchOptions.Context}
|
||||
/>
|
||||
<RadioButton
|
||||
name="query-type"
|
||||
id="file-name-radio"
|
||||
bind:group={selectedOption}
|
||||
label="File name or extension"
|
||||
label={$t('file_name_or_extension')}
|
||||
value={TextSearchOptions.Filename}
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
{#if selectedOption === TextSearchOptions.Context}
|
||||
<label for="context-input" class="immich-form-label">Search by context</label>
|
||||
<label for="context-input" class="immich-form-label">{$t('search_by_context')}</label>
|
||||
<input
|
||||
class="immich-form-input hover:cursor-text w-full !mt-1"
|
||||
type="text"
|
||||
id="context-input"
|
||||
name="context"
|
||||
placeholder="Sunrise on the beach"
|
||||
placeholder={$t('sunrise_on_the_beach')}
|
||||
bind:value={context}
|
||||
/>
|
||||
{:else}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue