/** * Zentrale Übersetzungen (i18n-ähnlich). * Quelle: CMS translation_bundle (API, hooks.server.ts setzt event.locals.translations). * Wird in +layout.server.ts geladen und über TranslationProvider/Svelte-Context bereitgestellt. */ import { getContext } from "svelte"; export type Translations = Record; /** Context-Key für die t-Funktion (von TranslationProvider gesetzt). */ export const T_CONTEXT_KEY = Symbol("translations.t"); /** Platzhalter für Ersetzung: z. B. { name: "Max" } ersetzt {{name}} im Übersetzungstext. */ export type TranslationReplacements = Record; /** t(key) oder t(key, { placeholder: value }) – mit optionalen Ersetzungen für {{placeholder}}. */ export type TFunction = ( key: string, replacements?: TranslationReplacements, ) => string; /** * Ersetzt {{name}} im Text durch replacements[name]; fehlende Keys bleiben als {{name}}. */ export function replacePlaceholders( text: string, replacements?: TranslationReplacements | null, ): string { if (!replacements || typeof text !== "string") return text; return text.replace(/\{\{(\w+)\}\}/g, (_, name: string) => { const val = replacements[name]; return val !== undefined && val !== null ? String(val) : `{{${name}}}`; }); } /** * t-Funktion aus Svelte-Context holen. * Zuverlässig innerhalb des TranslationProvider-Baums. */ export function useTranslate(): TFunction { const fromContext = getContext(T_CONTEXT_KEY); if (fromContext) return fromContext; return (key: string, replacements?: TranslationReplacements): string => { return replacePlaceholders(key, replacements); }; } /** Bekannte Übersetzungs-Keys – nur hier eintragen, Nutzung über T.xy (Autocomplete, keine Tippfehler). */ const TRANSLATION_KEYS = [ "searchable_text_help", "searchable_text_help_aria", "searchable_text_placeholder", "searchable_text_search_aria", "searchable_text_clear_search", "searchable_text_tag_all", "searchable_text_no_results", "searchable_text_count_all", "searchable_text_count_filtered", "searchable_text_copy_aria", "searchable_text_copy_button", "searchable_text_copied", "searchable_text_untitled", "footer_copyright", "nav_start", "nav_posts", "nav_home", "header_search_open", "header_search_close", "header_menu_open", "header_menu_close", "header_nav_aria", "header_overlay_close", "header_social_aria", "site_name_fallback", "pagination_prev", "pagination_next", "blog_tag_all", "blog_filter_aria", "blog_count", "page_404_title", "page_404_text", "page_404_back", "posts_title", "posts_subtitle", "posts_tag_title", "posts_tag_subtitle", "posts_cms_error", "iframe_activate_aria", "iframe_missing", "image_gallery_swipe_aria", "image_gallery_prev_aria", "image_gallery_next_aria", "image_gallery_position_aria", "image_gallery_slide_aria", "image_gallery_empty", "image_gallery_download", "image_gallery_close", "image_gallery_open_aria", "image_gallery_modal_aria", "youtube_missing", "youtube_title_fallback", "calendar_prev_month", "calendar_next_month", "calendar_weekday_mo", "calendar_weekday_di", "calendar_weekday_mi", "calendar_weekday_do", "calendar_weekday_fr", "calendar_weekday_sa", "calendar_weekday_so", "calendar_more_info", "calendar_no_events", "calendar_time_suffix", "calendar_select_day", "calendar_next_events", "calendar_show_more", "calendar_show_less", "post_overview_all", "blog_search_label", "blog_search_placeholder", "blog_search_clear", "blog_year_label", "blog_year_all", "blog_upcoming_events", "blog_results_count", "blog_results_for", "blog_results_in_year", "blog_filter_reset", "blog_no_results", "blog_rss_subscribe", "post_map", "post_map_activate", "post_map_open_osm", "post_comments", ] as const; export type TranslationKey = (typeof TRANSLATION_KEYS)[number]; /** Keys als Objekt: T.searchable_text_help → "searchable_text_help" (für t(T.xy)). */ export const T: { [K in TranslationKey]: K } = Object.fromEntries( TRANSLATION_KEYS.map((k) => [k, k]), ) as { [K in TranslationKey]: K }; /** * Gibt den übersetzten Text für key zurück, optional mit Ersetzung von {{placeholder}}. * Fallback: key selbst (damit fehlende Einträge im CMS erkennbar sind). */ export function t( translations: Translations | null | undefined, key: string, replacements?: TranslationReplacements, ): string { if (!key) return key; const raw = translations?.[key] ?? key; return replacePlaceholders(raw, replacements); }