From 04b72a326ad133d5fc22e150523f50ad7779bd6f Mon Sep 17 00:00:00 2001 From: mARTin Date: Sun, 5 Apr 2026 18:08:05 +0200 Subject: [PATCH] feat: integrate zitat-service.de API for DE/EN/ES quotes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add zitat-service.de as built-in multilingual fallback in goal API: tries the user's preferred language (de/en/es) automatically before falling back to ZenQuotes (EN) and then local curated quotes - Parser now also handles {quote, authorName} response format from zitat-service.de alongside all previously supported formats - Preset source pills in settings: add Zitat-Service (DE), (EN), (ES) (supports de, en, es — no fr/it available from that API) - Remove Forismatic from presets (unreliable, often returns empty) - Update hint text to reflect DE/EN/ES API availability vs FR/IT local v1.84.1 --- package.json | 2 +- src/app/api/goal/route.ts | 30 ++++++++++++++++++++++++------ src/components/SettingsSidebar.tsx | 8 +++++--- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index ae82050..fc28767 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.84.0", + "version": "1.84.1", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view", "main": "index.js", "scripts": { diff --git a/src/app/api/goal/route.ts b/src/app/api/goal/route.ts index 62211c0..ac3ab95 100644 --- a/src/app/api/goal/route.ts +++ b/src/app/api/goal/route.ts @@ -111,14 +111,16 @@ export async function GET(req: Request) { // Quotable: {content, author} if (data.content && data.author) return `${data.content} — ${data.author}`; // Forismatic: {quoteText, quoteAuthor} - if (data.quoteText) return data.quoteAuthor ? `${data.quoteText.trim()} — ${data.quoteAuthor.trim()}` : data.quoteText.trim(); - // Common: {text, author} or {quote, author} + if (data.quoteText) return data.quoteAuthor?.trim() ? `${data.quoteText.trim()} — ${data.quoteAuthor.trim()}` : data.quoteText.trim(); + // zitat-service.de: {quote, authorName} + if (data.quote && data.authorName) return `${data.quote} — ${data.authorName}`; + // Common: {text, author} or {quote, author} or {body, ...} const text = data.text || data.quote || data.body || data.sentence; - const author = data.author || data.by || data.from || data.speaker; + const author = data.author || data.authorName || data.by || data.from || data.speaker; if (text) return author ? `${text} — ${author}` : text; - // Nested: {data: {quote, author}} + // Nested: {data: {...}} if (data.data) return parseQuoteResponse(data.data); - // Nested: {slip: {advice}} + // Advice Slip: {slip: {advice}} if (data.slip?.advice) return data.slip.advice; } catch { /* ignore */ } return null; @@ -139,7 +141,23 @@ export async function GET(req: Request) { } } - // ZenQuotes as built-in English fallback + // Built-in multilingual fallbacks via zitat-service.de (de, en, es) + ZenQuotes (en) + const zitatLangMap: Record = { de: 'de', en: 'en', es: 'es' }; + const zitatLang = quoteLangs.find(l => zitatLangMap[l]); + if (zitatLang) { + try { + const res = await fetch(`https://api.zitat-service.de/v1/quote?language=${zitatLangMap[zitatLang]}`, { signal: AbortSignal.timeout(4000) }); + if (res.ok) { + const data = await res.json(); + const parsed = parseQuoteResponse(data); + if (parsed) return NextResponse.json({ goal: parsed, isQuote: true }); + } + } catch (e) { + console.error('Failed to fetch from zitat-service.de:', e); + } + } + + // ZenQuotes as English fallback if (quoteLangs.includes('en')) { try { const res = await fetch('https://zenquotes.io/api/random', { signal: AbortSignal.timeout(3000) }); diff --git a/src/components/SettingsSidebar.tsx b/src/components/SettingsSidebar.tsx index d1f6174..d170376 100644 --- a/src/components/SettingsSidebar.tsx +++ b/src/components/SettingsSidebar.tsx @@ -3782,8 +3782,10 @@ function SettingsSidebar({ { label: "ZenQuotes (EN)", url: "https://zenquotes.io/api/random" }, { label: "Stoic Quotes (EN)", url: "https://stoic.tekloon.net/stoic-quote" }, { label: "Quotable (EN)", url: "https://api.quotable.io/quotes/random" }, - { label: "Forismatic (EN)", url: "https://api.forismatic.com/api/1.0/?method=getQuote&format=json&lang=en" }, { label: "Advice Slip (EN)", url: "https://api.adviceslip.com/advice" }, + { label: "Zitat-Service (DE)", url: "https://api.zitat-service.de/v1/quote?language=de" }, + { label: "Zitat-Service (EN)", url: "https://api.zitat-service.de/v1/quote?language=en" }, + { label: "Zitat-Service (ES)", url: "https://api.zitat-service.de/v1/quote?language=es" }, ].map(({ label, url }) => { const current: string[] = profile.quoteSourceUrls || []; const already = current.includes(url); @@ -3815,8 +3817,8 @@ function SettingsSidebar({

{profile.language === "de" - ? "Für DE, FR, IT, ES werden kuratierte lokale Zitate verwendet." - : "For DE, FR, IT, ES, curated local quotes are used automatically."} + ? "DE/EN/ES: auch über Zitat-Service API verfügbar. FR/IT: kuratierte lokale Sammlung." + : "DE/EN/ES: also available via Zitat-Service API. FR/IT: curated local collection."}