diff --git a/i18n/en.json b/i18n/en.json index a25d62e94c8..a43d288954b 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -694,6 +694,7 @@ "daily_title_text_date": "E, MMM dd", "daily_title_text_date_year": "E, MMM dd, yyyy", "dark": "Dark", + "darkTheme": "Toggle dark theme", "date_after": "Date after", "date_and_time": "Date and Time", "date_before": "Date before", @@ -1817,7 +1818,6 @@ "to_parent": "Go to parent", "to_trash": "Trash", "toggle_settings": "Toggle settings", - "toggle_theme": "Toggle dark theme", "total": "Total", "total_usage": "Total usage", "trash": "Trash", diff --git a/web/src/lib/components/shared-components/theme-button.svelte b/web/src/lib/components/shared-components/theme-button.svelte index 74937703402..17b0b539ee0 100644 --- a/web/src/lib/components/shared-components/theme-button.svelte +++ b/web/src/lib/components/shared-components/theme-button.svelte @@ -1,13 +1,20 @@ {#if !themeManager.theme.system} - themeManager.setTheme(theme == 'dark' ? Theme.DARK : Theme.LIGHT)} - /> + {#await langs + .find((item) => item.code === get(lang)) + ?.loader() ?? defaultLang.loader() then { default: translations }} + themeManager.setTheme(theme == 'dark' ? Theme.DARK : Theme.LIGHT)} + /> + {/await} {/if} diff --git a/web/src/lib/components/user-settings-page/app-settings.svelte b/web/src/lib/components/user-settings-page/app-settings.svelte index adb37d5d939..d248038a240 100644 --- a/web/src/lib/components/user-settings-page/app-settings.svelte +++ b/web/src/lib/components/user-settings-page/app-settings.svelte @@ -39,7 +39,7 @@ }; const handleToggleLocaleBrowser = () => { - $locale = $locale ? undefined : fallbackLocale.code; + $locale = $locale === 'default' ? fallbackLocale.code : 'default'; }; const handleLocaleChange = (newLocale: string | undefined) => { @@ -89,13 +89,13 @@

{selectedDate}

- {#if $locale !== undefined} + {#if $locale !== 'default'}
($alwaysLoadOriginalFile = !$alwaysLoadOriginalFile)} />
@@ -121,16 +120,10 @@ title={$t('video_hover_setting')} subtitle={$t('video_hover_setting_description')} bind:checked={$playVideoThumbnailOnHover} - onToggle={() => ($playVideoThumbnailOnHover = !$playVideoThumbnailOnHover)} />
- ($loopVideo = !$loopVideo)} - /> +
diff --git a/web/src/lib/constants.ts b/web/src/lib/constants.ts index fdb18b39781..ae53b4e7f3f 100644 --- a/web/src/lib/constants.ts +++ b/web/src/lib/constants.ts @@ -273,9 +273,17 @@ export const locales = [ { 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: 'Arabic', code: 'ar', loader: () => import('$i18n/ar.json'), rtl: true }, { name: 'Azerbaijani', code: 'az', loader: () => import('$i18n/az.json'), rtl: true }, @@ -359,7 +367,7 @@ export const langs = [ weblateCode: 'zh_SIMPLIFIED', 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 { diff --git a/web/src/lib/stores/preferences.store.ts b/web/src/lib/stores/preferences.store.ts index e7f38eb6d08..f0689e5d6e6 100644 --- a/web/src/lib/stores/preferences.store.ts +++ b/web/src/lib/stores/preferences.store.ts @@ -9,9 +9,9 @@ export interface ThemeSetting { } // Locale to use for formatting dates, numbers, etc. -export const locale = persisted('locale', undefined, { +export const locale = persisted('locale', 'default', { serializer: { - parse: (text) => (text == '' ? 'en-US' : text), + parse: (text) => text || 'default', stringify: (object) => object ?? '', }, }); diff --git a/web/src/lib/utils/timeline-util.spec.ts b/web/src/lib/utils/timeline-util.spec.ts index f842b948e06..c77aefc0b49 100644 --- a/web/src/lib/utils/timeline-util.spec.ts +++ b/web/src/lib/utils/timeline-util.spec.ts @@ -1,3 +1,4 @@ +import { locale } from '$lib/stores/preferences.store'; import { parseUtcDate } from '$lib/utils/date-time'; import { formatGroupTitle } from '$lib/utils/timeline-util'; import { DateTime } from 'luxon'; @@ -16,48 +17,63 @@ describe('formatGroupTitle', () => { it('formats today', () => { const date = parseUtcDate('2024-07-27T01:00:00Z'); - expect(formatGroupTitle(date.setLocale('en'))).toBe('today'); - expect(formatGroupTitle(date.setLocale('es'))).toBe('hoy'); + locale.set('en'); + expect(formatGroupTitle(date)).toBe('today'); + locale.set('es'); + expect(formatGroupTitle(date)).toBe('hoy'); }); it('formats yesterday', () => { const date = parseUtcDate('2024-07-26T23:59:59Z'); - expect(formatGroupTitle(date.setLocale('en'))).toBe('yesterday'); - expect(formatGroupTitle(date.setLocale('fr'))).toBe('hier'); + locale.set('en'); + expect(formatGroupTitle(date)).toBe('yesterday'); + locale.set('fr'); + expect(formatGroupTitle(date)).toBe('hier'); }); it('formats last week', () => { const date = parseUtcDate('2024-07-21T00:00:00Z'); - expect(formatGroupTitle(date.setLocale('en'))).toBe('Sunday'); - expect(formatGroupTitle(date.setLocale('ar-SA'))).toBe('الأحد'); + locale.set('en'); + expect(formatGroupTitle(date)).toBe('Sunday'); + locale.set('ar-SA'); + expect(formatGroupTitle(date)).toBe('الأحد'); }); it('formats date 7 days ago', () => { const date = parseUtcDate('2024-07-20T00:00:00Z'); - expect(formatGroupTitle(date.setLocale('en'))).toBe('Sat, Jul 20'); - expect(formatGroupTitle(date.setLocale('de'))).toBe('Sa., 20. Juli'); + locale.set('en'); + expect(formatGroupTitle(date)).toBe('Sat, Jul 20'); + locale.set('de'); + expect(formatGroupTitle(date)).toBe('Sa., 20. Juli'); }); it('formats date this year', () => { const date = parseUtcDate('2020-01-01T00:00:00Z'); - expect(formatGroupTitle(date.setLocale('en'))).toBe('Wed, Jan 1, 2020'); - expect(formatGroupTitle(date.setLocale('ja'))).toBe('2020年1月1日(水)'); + locale.set('en'); + expect(formatGroupTitle(date)).toBe('Wed, Jan 1, 2020'); + locale.set('ja'); + expect(formatGroupTitle(date)).toBe('2020年1月1日(水)'); }); it('formats future date', () => { 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'); - 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'); - 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', () => { const date = DateTime.invalid('test'); - expect(formatGroupTitle(date.setLocale('en'))).toBe('Invalid DateTime'); - expect(formatGroupTitle(date.setLocale('es'))).toBe('Invalid DateTime'); + locale.set('en'); + expect(formatGroupTitle(date)).toBe('Invalid DateTime'); + locale.set('es'); + expect(formatGroupTitle(date)).toBe('Invalid DateTime'); }); }); diff --git a/web/src/lib/utils/timeline-util.ts b/web/src/lib/utils/timeline-util.ts index d8252271a22..7538e0e02c1 100644 --- a/web/src/lib/utils/timeline-util.ts +++ b/web/src/lib/utils/timeline-util.ts @@ -62,12 +62,12 @@ export function formatGroupTitle(_date: DateTime): string { // Today if (today.hasSame(date, 'day')) { - return date.toRelativeCalendar(); + return date.toRelativeCalendar({ locale: get(locale) }); } // Yesterday if (today.minus({ days: 1 }).hasSame(date, 'day')) { - return date.toRelativeCalendar(); + return date.toRelativeCalendar({ locale: get(locale) }); } // Last week