mirror of
https://github.com/immich-app/immich
synced 2025-06-09 05:48:12 +00:00
fix(web): persisted store (#18385)
* fix(web): persisted store * fix: translation * fix: test * fix: test * revert i18n changes * fix blank locale
This commit is contained in:
parent
6b4d5e3beb
commit
daf1bee7ac
@ -694,6 +694,7 @@
|
|||||||
"daily_title_text_date": "E, MMM dd",
|
"daily_title_text_date": "E, MMM dd",
|
||||||
"daily_title_text_date_year": "E, MMM dd, yyyy",
|
"daily_title_text_date_year": "E, MMM dd, yyyy",
|
||||||
"dark": "Dark",
|
"dark": "Dark",
|
||||||
|
"darkTheme": "Toggle dark theme",
|
||||||
"date_after": "Date after",
|
"date_after": "Date after",
|
||||||
"date_and_time": "Date and Time",
|
"date_and_time": "Date and Time",
|
||||||
"date_before": "Date before",
|
"date_before": "Date before",
|
||||||
@ -1817,7 +1818,6 @@
|
|||||||
"to_parent": "Go to parent",
|
"to_parent": "Go to parent",
|
||||||
"to_trash": "Trash",
|
"to_trash": "Trash",
|
||||||
"toggle_settings": "Toggle settings",
|
"toggle_settings": "Toggle settings",
|
||||||
"toggle_theme": "Toggle dark theme",
|
|
||||||
"total": "Total",
|
"total": "Total",
|
||||||
"total_usage": "Total usage",
|
"total_usage": "Total usage",
|
||||||
"trash": "Trash",
|
"trash": "Trash",
|
||||||
|
@ -1,13 +1,20 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Theme } from '$lib/constants';
|
import { defaultLang, langs, Theme } from '$lib/constants';
|
||||||
import { themeManager } from '$lib/managers/theme-manager.svelte';
|
import { themeManager } from '$lib/managers/theme-manager.svelte';
|
||||||
|
import { lang } from '$lib/stores/preferences.store';
|
||||||
import { ThemeSwitcher } from '@immich/ui';
|
import { ThemeSwitcher } from '@immich/ui';
|
||||||
|
import { get } from 'svelte/store';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !themeManager.theme.system}
|
{#if !themeManager.theme.system}
|
||||||
<ThemeSwitcher
|
{#await langs
|
||||||
size="medium"
|
.find((item) => item.code === get(lang))
|
||||||
color="secondary"
|
?.loader() ?? defaultLang.loader() then { default: translations }}
|
||||||
onChange={(theme) => themeManager.setTheme(theme == 'dark' ? Theme.DARK : Theme.LIGHT)}
|
<ThemeSwitcher
|
||||||
/>
|
size="medium"
|
||||||
|
color="secondary"
|
||||||
|
{translations}
|
||||||
|
onChange={(theme) => themeManager.setTheme(theme == 'dark' ? Theme.DARK : Theme.LIGHT)}
|
||||||
|
/>
|
||||||
|
{/await}
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleToggleLocaleBrowser = () => {
|
const handleToggleLocaleBrowser = () => {
|
||||||
$locale = $locale ? undefined : fallbackLocale.code;
|
$locale = $locale === 'default' ? fallbackLocale.code : 'default';
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLocaleChange = (newLocale: string | undefined) => {
|
const handleLocaleChange = (newLocale: string | undefined) => {
|
||||||
@ -89,13 +89,13 @@
|
|||||||
<SettingSwitch
|
<SettingSwitch
|
||||||
title={$t('default_locale')}
|
title={$t('default_locale')}
|
||||||
subtitle={$t('default_locale_description')}
|
subtitle={$t('default_locale_description')}
|
||||||
checked={$locale == undefined}
|
checked={$locale == 'default'}
|
||||||
onToggle={handleToggleLocaleBrowser}
|
onToggle={handleToggleLocaleBrowser}
|
||||||
>
|
>
|
||||||
<p class="mt-2 dark:text-gray-400">{selectedDate}</p>
|
<p class="mt-2 dark:text-gray-400">{selectedDate}</p>
|
||||||
</SettingSwitch>
|
</SettingSwitch>
|
||||||
</div>
|
</div>
|
||||||
{#if $locale !== undefined}
|
{#if $locale !== 'default'}
|
||||||
<div class="ms-4">
|
<div class="ms-4">
|
||||||
<SettingCombobox
|
<SettingCombobox
|
||||||
comboboxPlaceholder={$t('searching_locales')}
|
comboboxPlaceholder={$t('searching_locales')}
|
||||||
@ -113,7 +113,6 @@
|
|||||||
title={$t('display_original_photos')}
|
title={$t('display_original_photos')}
|
||||||
subtitle={$t('display_original_photos_setting_description')}
|
subtitle={$t('display_original_photos_setting_description')}
|
||||||
bind:checked={$alwaysLoadOriginalFile}
|
bind:checked={$alwaysLoadOriginalFile}
|
||||||
onToggle={() => ($alwaysLoadOriginalFile = !$alwaysLoadOriginalFile)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="ms-4">
|
<div class="ms-4">
|
||||||
@ -121,16 +120,10 @@
|
|||||||
title={$t('video_hover_setting')}
|
title={$t('video_hover_setting')}
|
||||||
subtitle={$t('video_hover_setting_description')}
|
subtitle={$t('video_hover_setting_description')}
|
||||||
bind:checked={$playVideoThumbnailOnHover}
|
bind:checked={$playVideoThumbnailOnHover}
|
||||||
onToggle={() => ($playVideoThumbnailOnHover = !$playVideoThumbnailOnHover)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="ms-4">
|
<div class="ms-4">
|
||||||
<SettingSwitch
|
<SettingSwitch title={$t('loop_videos')} subtitle={$t('loop_videos_description')} bind:checked={$loopVideo} />
|
||||||
title={$t('loop_videos')}
|
|
||||||
subtitle={$t('loop_videos_description')}
|
|
||||||
bind:checked={$loopVideo}
|
|
||||||
onToggle={() => ($loopVideo = !$loopVideo)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ms-4">
|
<div class="ms-4">
|
||||||
|
@ -273,9 +273,17 @@ export const locales = [
|
|||||||
{ code: 'zu-ZA', name: 'Zulu (South Africa)' },
|
{ code: 'zu-ZA', name: 'Zulu (South Africa)' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const defaultLang = { name: 'English', code: 'en', loader: () => import('$i18n/en.json') };
|
interface Lang {
|
||||||
|
name: string;
|
||||||
|
code: string;
|
||||||
|
loader: () => Promise<{ default: object }>;
|
||||||
|
rtl?: boolean;
|
||||||
|
weblateCode?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const langs = [
|
export const defaultLang: Lang = { name: 'English', code: 'en', loader: () => import('$i18n/en.json') };
|
||||||
|
|
||||||
|
export const langs: Lang[] = [
|
||||||
{ name: 'Afrikaans', code: 'af', loader: () => import('$i18n/af.json') },
|
{ name: 'Afrikaans', code: 'af', loader: () => import('$i18n/af.json') },
|
||||||
{ name: 'Arabic', code: 'ar', loader: () => import('$i18n/ar.json'), rtl: true },
|
{ name: 'Arabic', code: 'ar', loader: () => import('$i18n/ar.json'), rtl: true },
|
||||||
{ name: 'Azerbaijani', code: 'az', loader: () => import('$i18n/az.json'), rtl: true },
|
{ name: 'Azerbaijani', code: 'az', loader: () => import('$i18n/az.json'), rtl: true },
|
||||||
@ -359,7 +367,7 @@ export const langs = [
|
|||||||
weblateCode: 'zh_SIMPLIFIED',
|
weblateCode: 'zh_SIMPLIFIED',
|
||||||
loader: () => import('$i18n/zh_SIMPLIFIED.json'),
|
loader: () => import('$i18n/zh_SIMPLIFIED.json'),
|
||||||
},
|
},
|
||||||
{ name: 'Development (keys only)', code: 'dev', loader: () => Promise.resolve({}) },
|
{ name: 'Development (keys only)', code: 'dev', loader: () => Promise.resolve({ default: {} }) },
|
||||||
];
|
];
|
||||||
|
|
||||||
export enum ImmichProduct {
|
export enum ImmichProduct {
|
||||||
|
@ -9,9 +9,9 @@ export interface ThemeSetting {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Locale to use for formatting dates, numbers, etc.
|
// Locale to use for formatting dates, numbers, etc.
|
||||||
export const locale = persisted<string | undefined>('locale', undefined, {
|
export const locale = persisted<string | undefined>('locale', 'default', {
|
||||||
serializer: {
|
serializer: {
|
||||||
parse: (text) => (text == '' ? 'en-US' : text),
|
parse: (text) => text || 'default',
|
||||||
stringify: (object) => object ?? '',
|
stringify: (object) => object ?? '',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import { parseUtcDate } from '$lib/utils/date-time';
|
import { parseUtcDate } from '$lib/utils/date-time';
|
||||||
import { formatGroupTitle } from '$lib/utils/timeline-util';
|
import { formatGroupTitle } from '$lib/utils/timeline-util';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
@ -16,48 +17,63 @@ describe('formatGroupTitle', () => {
|
|||||||
|
|
||||||
it('formats today', () => {
|
it('formats today', () => {
|
||||||
const date = parseUtcDate('2024-07-27T01:00:00Z');
|
const date = parseUtcDate('2024-07-27T01:00:00Z');
|
||||||
expect(formatGroupTitle(date.setLocale('en'))).toBe('today');
|
locale.set('en');
|
||||||
expect(formatGroupTitle(date.setLocale('es'))).toBe('hoy');
|
expect(formatGroupTitle(date)).toBe('today');
|
||||||
|
locale.set('es');
|
||||||
|
expect(formatGroupTitle(date)).toBe('hoy');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('formats yesterday', () => {
|
it('formats yesterday', () => {
|
||||||
const date = parseUtcDate('2024-07-26T23:59:59Z');
|
const date = parseUtcDate('2024-07-26T23:59:59Z');
|
||||||
expect(formatGroupTitle(date.setLocale('en'))).toBe('yesterday');
|
locale.set('en');
|
||||||
expect(formatGroupTitle(date.setLocale('fr'))).toBe('hier');
|
expect(formatGroupTitle(date)).toBe('yesterday');
|
||||||
|
locale.set('fr');
|
||||||
|
expect(formatGroupTitle(date)).toBe('hier');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('formats last week', () => {
|
it('formats last week', () => {
|
||||||
const date = parseUtcDate('2024-07-21T00:00:00Z');
|
const date = parseUtcDate('2024-07-21T00:00:00Z');
|
||||||
expect(formatGroupTitle(date.setLocale('en'))).toBe('Sunday');
|
locale.set('en');
|
||||||
expect(formatGroupTitle(date.setLocale('ar-SA'))).toBe('الأحد');
|
expect(formatGroupTitle(date)).toBe('Sunday');
|
||||||
|
locale.set('ar-SA');
|
||||||
|
expect(formatGroupTitle(date)).toBe('الأحد');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('formats date 7 days ago', () => {
|
it('formats date 7 days ago', () => {
|
||||||
const date = parseUtcDate('2024-07-20T00:00:00Z');
|
const date = parseUtcDate('2024-07-20T00:00:00Z');
|
||||||
expect(formatGroupTitle(date.setLocale('en'))).toBe('Sat, Jul 20');
|
locale.set('en');
|
||||||
expect(formatGroupTitle(date.setLocale('de'))).toBe('Sa., 20. Juli');
|
expect(formatGroupTitle(date)).toBe('Sat, Jul 20');
|
||||||
|
locale.set('de');
|
||||||
|
expect(formatGroupTitle(date)).toBe('Sa., 20. Juli');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('formats date this year', () => {
|
it('formats date this year', () => {
|
||||||
const date = parseUtcDate('2020-01-01T00:00:00Z');
|
const date = parseUtcDate('2020-01-01T00:00:00Z');
|
||||||
expect(formatGroupTitle(date.setLocale('en'))).toBe('Wed, Jan 1, 2020');
|
locale.set('en');
|
||||||
expect(formatGroupTitle(date.setLocale('ja'))).toBe('2020年1月1日(水)');
|
expect(formatGroupTitle(date)).toBe('Wed, Jan 1, 2020');
|
||||||
|
locale.set('ja');
|
||||||
|
expect(formatGroupTitle(date)).toBe('2020年1月1日(水)');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('formats future date', () => {
|
it('formats future date', () => {
|
||||||
const tomorrow = parseUtcDate('2024-07-28T00:00:00Z');
|
const tomorrow = parseUtcDate('2024-07-28T00:00:00Z');
|
||||||
expect(formatGroupTitle(tomorrow.setLocale('en'))).toBe('Sun, Jul 28');
|
locale.set('en');
|
||||||
|
expect(formatGroupTitle(tomorrow)).toBe('Sun, Jul 28');
|
||||||
|
|
||||||
const nextMonth = parseUtcDate('2024-08-28T00:00:00Z');
|
const nextMonth = parseUtcDate('2024-08-28T00:00:00Z');
|
||||||
expect(formatGroupTitle(nextMonth.setLocale('en'))).toBe('Wed, Aug 28');
|
locale.set('en');
|
||||||
|
expect(formatGroupTitle(nextMonth)).toBe('Wed, Aug 28');
|
||||||
|
|
||||||
const nextYear = parseUtcDate('2025-01-10T12:00:00Z');
|
const nextYear = parseUtcDate('2025-01-10T12:00:00Z');
|
||||||
expect(formatGroupTitle(nextYear.setLocale('en'))).toBe('Fri, Jan 10, 2025');
|
locale.set('en');
|
||||||
|
expect(formatGroupTitle(nextYear)).toBe('Fri, Jan 10, 2025');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns "Invalid DateTime" when date is invalid', () => {
|
it('returns "Invalid DateTime" when date is invalid', () => {
|
||||||
const date = DateTime.invalid('test');
|
const date = DateTime.invalid('test');
|
||||||
expect(formatGroupTitle(date.setLocale('en'))).toBe('Invalid DateTime');
|
locale.set('en');
|
||||||
expect(formatGroupTitle(date.setLocale('es'))).toBe('Invalid DateTime');
|
expect(formatGroupTitle(date)).toBe('Invalid DateTime');
|
||||||
|
locale.set('es');
|
||||||
|
expect(formatGroupTitle(date)).toBe('Invalid DateTime');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -62,12 +62,12 @@ export function formatGroupTitle(_date: DateTime): string {
|
|||||||
|
|
||||||
// Today
|
// Today
|
||||||
if (today.hasSame(date, 'day')) {
|
if (today.hasSame(date, 'day')) {
|
||||||
return date.toRelativeCalendar();
|
return date.toRelativeCalendar({ locale: get(locale) });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Yesterday
|
// Yesterday
|
||||||
if (today.minus({ days: 1 }).hasSame(date, 'day')) {
|
if (today.minus({ days: 1 }).hasSame(date, 'day')) {
|
||||||
return date.toRelativeCalendar();
|
return date.toRelativeCalendar({ locale: get(locale) });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Last week
|
// Last week
|
||||||
|
Loading…
x
Reference in New Issue
Block a user