i18n(common): bootstrap shared namespace + migrate top-level UI
Add a per-locale common dictionary at src/lib/i18n/common/{de,en}.ts and
the shim src/lib/js/commonI18n.ts. Migrate inline lang ternaries on the
homepage (welcome/sections/links), OfflineSyncButton (all label
ternaries), DatePicker (today/select date), ErrorView (Error/Fehler
eyebrow), and UserHeader (login aria/title) to use the shared dict.
The long marketing intro paragraphs on the homepage stay inline since
they're one-shot content with no drift risk and don't benefit from
per-key extraction.
Bump site version to 1.57.0 (new namespace).
This commit is contained in:
@@ -2,7 +2,10 @@
|
||||
import ChevronLeft from '@lucide/svelte/icons/chevron-left';
|
||||
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
||||
import Calendar from '@lucide/svelte/icons/calendar';
|
||||
import { m } from '$lib/js/commonI18n';
|
||||
/** @typedef {import('$lib/js/commonI18n').CommonLang} CommonLang */
|
||||
let { value = $bindable(''), lang = 'en', min = '', max = '' } = $props();
|
||||
const t = $derived(m[/** @type {CommonLang} */ (lang)]);
|
||||
|
||||
let open = $state(false);
|
||||
/** @type {HTMLDivElement | null} */
|
||||
@@ -35,8 +38,8 @@
|
||||
const todayStr = new Date().toISOString().slice(0, 10);
|
||||
|
||||
const displayDate = $derived.by(() => {
|
||||
if (!value) return lang === 'en' ? 'Select date' : 'Datum wählen';
|
||||
if (value === todayStr) return lang === 'en' ? 'Today' : 'Heute';
|
||||
if (!value) return t.select_date;
|
||||
if (value === todayStr) return t.today;
|
||||
const d = new Date(value + 'T12:00:00');
|
||||
return d.toLocaleDateString(lang === 'de' ? 'de-DE' : 'en-US', { weekday: 'short', month: 'short', day: 'numeric' });
|
||||
});
|
||||
@@ -182,7 +185,7 @@
|
||||
|
||||
{#if value !== todayStr}
|
||||
<button type="button" class="dp-today-btn" onclick={goToday}>
|
||||
{lang === 'en' ? 'Today' : 'Heute'}
|
||||
{t.today}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import SearchX from '@lucide/svelte/icons/search-x';
|
||||
import TriangleAlert from '@lucide/svelte/icons/triangle-alert';
|
||||
import CircleAlert from '@lucide/svelte/icons/circle-alert';
|
||||
import { m } from '$lib/js/commonI18n';
|
||||
interface BibleQuote {
|
||||
text: string;
|
||||
reference: string;
|
||||
@@ -43,6 +44,7 @@
|
||||
}
|
||||
|
||||
let Icon = $derived(icon ?? defaultIcon(status));
|
||||
const t = $derived(m[isEnglish ? 'en' : 'de']);
|
||||
let openQuote = $derived(isEnglish ? '\u201C' : '\u201E');
|
||||
let closeQuote = $derived(isEnglish ? '\u201D' : '\u201C');
|
||||
</script>
|
||||
@@ -52,7 +54,7 @@
|
||||
<header class="eyebrow">
|
||||
<Icon size={14} strokeWidth={1.5} aria-hidden="true" />
|
||||
<span class="eyebrow-label">
|
||||
{isEnglish ? 'Error' : 'Fehler'}
|
||||
{t.error_label}
|
||||
</span>
|
||||
</header>
|
||||
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { pwaStore } from '$lib/stores/pwa.svelte';
|
||||
import { m, type CommonLang } from '$lib/js/commonI18n';
|
||||
|
||||
let { lang = 'de' }: { lang?: string } = $props();
|
||||
|
||||
let showTooltip = $state(false);
|
||||
let mounted = $state(false);
|
||||
|
||||
const t = $derived(m[lang as CommonLang]);
|
||||
const labels = $derived({
|
||||
syncForOffline: lang === 'en' ? 'Save for offline' : 'Offline speichern',
|
||||
syncing: lang === 'en' ? 'Syncing...' : 'Synchronisiere...',
|
||||
offlineReady: lang === 'en' ? 'Offline ready' : 'Offline bereit',
|
||||
lastSync: lang === 'en' ? 'Last sync' : 'Letzte Sync',
|
||||
recipes: lang === 'en' ? 'recipes' : 'Rezepte',
|
||||
syncNow: lang === 'en' ? 'Sync now' : 'Jetzt synchronisieren',
|
||||
clearData: lang === 'en' ? 'Clear offline data' : 'Offline-Daten löschen'
|
||||
syncForOffline: t.sync_for_offline,
|
||||
syncing: t.syncing,
|
||||
offlineReady: t.offline_ready,
|
||||
lastSync: t.last_sync,
|
||||
recipes: t.recipes_word,
|
||||
syncNow: t.sync_now,
|
||||
clearData: t.clear_offline_data
|
||||
});
|
||||
|
||||
onMount(async () => {
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
import { page } from '$app/state';
|
||||
import { browser } from '$app/environment';
|
||||
import LogIn from '@lucide/svelte/icons/log-in';
|
||||
import { m, type CommonLang } from '$lib/js/commonI18n';
|
||||
|
||||
let { user, recipeLang = 'rezepte', lang = 'de' } = $props();
|
||||
const t = $derived(m[lang as CommonLang]);
|
||||
|
||||
function toggle_options(){
|
||||
const el = document.querySelector("#options-wrap") as HTMLElement | null;
|
||||
@@ -167,8 +169,8 @@
|
||||
<a
|
||||
class="entry login-link"
|
||||
href={`${resolve('/login')}?callbackUrl=${encodeURIComponent(page.url.pathname + (browser ? page.url.search : ''))}`}
|
||||
aria-label={lang === 'de' ? 'Anmelden' : 'Login'}
|
||||
title={lang === 'de' ? 'Anmelden' : 'Login'}
|
||||
aria-label={t.login}
|
||||
title={t.login}
|
||||
>
|
||||
<LogIn size={18} />
|
||||
</a>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/** German common UI strings — source of truth for the key set. */
|
||||
|
||||
export const de = {
|
||||
// Auth / user header
|
||||
login: 'Anmelden',
|
||||
|
||||
// (main) homepage
|
||||
welcome: 'Willkommen auf bocken.org',
|
||||
pages: 'Seiten',
|
||||
recipes: 'Rezepte',
|
||||
family_photos: 'Familienbilder',
|
||||
video_conferences: 'Videokonferenzen',
|
||||
search_engine: 'Suchmaschine',
|
||||
shopping: 'Einkauf',
|
||||
family_tree: 'Stammbaum',
|
||||
faith: 'Glaube',
|
||||
documents: 'Dokumente',
|
||||
audiobooks_podcasts: 'Hörbücher & Podcasts',
|
||||
nutrition: 'Ernährung',
|
||||
tasks: 'Aufgaben',
|
||||
|
||||
// Offline sync button
|
||||
sync_for_offline: 'Offline speichern',
|
||||
syncing: 'Synchronisiere…',
|
||||
offline_ready: 'Offline bereit',
|
||||
last_sync: 'Letzte Sync',
|
||||
recipes_word: 'Rezepte',
|
||||
sync_now: 'Jetzt synchronisieren',
|
||||
clear_offline_data: 'Offline-Daten löschen',
|
||||
|
||||
// Date picker
|
||||
select_date: 'Datum wählen',
|
||||
today: 'Heute',
|
||||
|
||||
// Error view
|
||||
error_label: 'Fehler'
|
||||
} as const;
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { de } from './de';
|
||||
|
||||
export const en = {
|
||||
// Auth / user header
|
||||
login: 'Login',
|
||||
|
||||
// (main) homepage
|
||||
welcome: 'Welcome to bocken.org',
|
||||
pages: 'Pages',
|
||||
recipes: 'Recipes',
|
||||
family_photos: 'Family Photos',
|
||||
video_conferences: 'Video Conferences',
|
||||
search_engine: 'Search Engine',
|
||||
shopping: 'Shopping',
|
||||
family_tree: 'Family Tree',
|
||||
faith: 'Faith',
|
||||
documents: 'Documents',
|
||||
audiobooks_podcasts: 'Audiobooks & Podcasts',
|
||||
nutrition: 'Nutrition',
|
||||
tasks: 'Tasks',
|
||||
|
||||
// Offline sync button
|
||||
sync_for_offline: 'Save for offline',
|
||||
syncing: 'Syncing…',
|
||||
offline_ready: 'Offline ready',
|
||||
last_sync: 'Last sync',
|
||||
recipes_word: 'recipes',
|
||||
sync_now: 'Sync now',
|
||||
clear_offline_data: 'Clear offline data',
|
||||
|
||||
// Date picker
|
||||
select_date: 'Select date',
|
||||
today: 'Today',
|
||||
|
||||
// Error view
|
||||
error_label: 'Error'
|
||||
} as const satisfies Record<keyof typeof de, string>;
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Shared/top-level UI strings used across the site (homepage, auth header,
|
||||
* offline sync button, date picker, error view).
|
||||
*
|
||||
* Per-locale tables live in `$lib/i18n/common/{de,en}.ts`. Use
|
||||
* `m[lang].key` (or `m[lang][expr]` for dynamic keys) directly:
|
||||
*
|
||||
* import { m } from '$lib/js/commonI18n';
|
||||
* const t = $derived(m[lang]);
|
||||
* ... t.login ...
|
||||
*/
|
||||
|
||||
import { de } from '$lib/i18n/common/de';
|
||||
import { en } from '$lib/i18n/common/en';
|
||||
|
||||
export const m = { de, en } as const;
|
||||
|
||||
export type CommonLang = keyof typeof m;
|
||||
export type CommonKey = keyof typeof de;
|
||||
Reference in New Issue
Block a user