feat: integrate zitat-service.de API for DE/EN/ES quotes

- 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
This commit is contained in:
mARTin 2026-04-05 18:08:05 +02:00
parent 2ec1278b19
commit 04b72a326a
3 changed files with 30 additions and 10 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "my-weekly-todo-list", "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", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@ -111,14 +111,16 @@ export async function GET(req: Request) {
// Quotable: {content, author} // Quotable: {content, author}
if (data.content && data.author) return `${data.content}${data.author}`; if (data.content && data.author) return `${data.content}${data.author}`;
// Forismatic: {quoteText, quoteAuthor} // Forismatic: {quoteText, quoteAuthor}
if (data.quoteText) return data.quoteAuthor ? `${data.quoteText.trim()}${data.quoteAuthor.trim()}` : data.quoteText.trim(); if (data.quoteText) return data.quoteAuthor?.trim() ? `${data.quoteText.trim()}${data.quoteAuthor.trim()}` : data.quoteText.trim();
// Common: {text, author} or {quote, author} // 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 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; if (text) return author ? `${text}${author}` : text;
// Nested: {data: {quote, author}} // Nested: {data: {...}}
if (data.data) return parseQuoteResponse(data.data); if (data.data) return parseQuoteResponse(data.data);
// Nested: {slip: {advice}} // Advice Slip: {slip: {advice}}
if (data.slip?.advice) return data.slip.advice; if (data.slip?.advice) return data.slip.advice;
} catch { /* ignore */ } } catch { /* ignore */ }
return null; 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<string, string> = { 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')) { if (quoteLangs.includes('en')) {
try { try {
const res = await fetch('https://zenquotes.io/api/random', { signal: AbortSignal.timeout(3000) }); const res = await fetch('https://zenquotes.io/api/random', { signal: AbortSignal.timeout(3000) });

View File

@ -3782,8 +3782,10 @@ function SettingsSidebar({
{ label: "ZenQuotes (EN)", url: "https://zenquotes.io/api/random" }, { label: "ZenQuotes (EN)", url: "https://zenquotes.io/api/random" },
{ label: "Stoic Quotes (EN)", url: "https://stoic.tekloon.net/stoic-quote" }, { label: "Stoic Quotes (EN)", url: "https://stoic.tekloon.net/stoic-quote" },
{ label: "Quotable (EN)", url: "https://api.quotable.io/quotes/random" }, { 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: "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 }) => { ].map(({ label, url }) => {
const current: string[] = profile.quoteSourceUrls || []; const current: string[] = profile.quoteSourceUrls || [];
const already = current.includes(url); const already = current.includes(url);
@ -3815,8 +3817,8 @@ function SettingsSidebar({
</div> </div>
<p style={{ fontSize: "0.72rem", color: "var(--weekly-text-light)", marginTop: "6px", fontStyle: "italic" }}> <p style={{ fontSize: "0.72rem", color: "var(--weekly-text-light)", marginTop: "6px", fontStyle: "italic" }}>
{profile.language === "de" {profile.language === "de"
? "Für DE, FR, IT, ES werden kuratierte lokale Zitate verwendet." ? "DE/EN/ES: auch über Zitat-Service API verfügbar. FR/IT: kuratierte lokale Sammlung."
: "For DE, FR, IT, ES, curated local quotes are used automatically."} : "DE/EN/ES: also available via Zitat-Service API. FR/IT: curated local collection."}
</p> </p>
</div> </div>
</div> </div>