feat: multi-language quote selection with per-user language picker
Users can now choose which languages their motivational quotes appear in via toggle buttons in Settings. Added 45 curated quotes in French, Spanish, and Italian. Quote language preference is stored per-user in the database. v1.22.0
This commit is contained in:
parent
9ac7198b24
commit
0b68d275d0
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "my-weekly-todo-list",
|
||||
"version": "1.21.0",
|
||||
"version": "1.22.0",
|
||||
"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": {
|
||||
|
||||
@ -98,6 +98,7 @@ model User {
|
||||
customWeekdayNames String? @default("")
|
||||
startDayOffset Int @default(-1)
|
||||
quoteSourceUrls String[] @default([])
|
||||
quoteLanguages String[] @default(["en", "de"])
|
||||
accounts Account[]
|
||||
cachedCalendarEvents CachedCalendarEvent[]
|
||||
calendarConnections CalendarConnection[]
|
||||
|
||||
@ -36,6 +36,7 @@ export async function GET(req: Request) {
|
||||
goalFallbackType: true,
|
||||
goalDefaultSentence: true,
|
||||
language: true,
|
||||
quoteLanguages: true,
|
||||
}
|
||||
});
|
||||
|
||||
@ -92,9 +93,11 @@ export async function GET(req: Request) {
|
||||
return NextResponse.json({ goal: holidayHint, isHoliday: true });
|
||||
}
|
||||
|
||||
// 4. Fallback to ZenQuotes motivational quote (English only)
|
||||
const userLang = user?.language || 'en';
|
||||
if (userLang === 'en') {
|
||||
// 4. Fallback to ZenQuotes motivational quote (if English is in quote languages)
|
||||
const quoteLangs: string[] = (user as any)?.quoteLanguages?.length > 0
|
||||
? (user as any).quoteLanguages
|
||||
: [user?.language || 'en'];
|
||||
if (quoteLangs.includes('en')) {
|
||||
try {
|
||||
const res = await fetch('https://zenquotes.io/api/random', { signal: AbortSignal.timeout(3000) });
|
||||
if (res.ok) {
|
||||
@ -108,8 +111,9 @@ export async function GET(req: Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Fallback to local curated quotes (supports DE and EN)
|
||||
const localQuote = getRandomLocalQuote(userLang);
|
||||
// 5. Fallback to local curated quotes (pick random from selected languages)
|
||||
const randomLang = quoteLangs[Math.floor(Math.random() * quoteLangs.length)];
|
||||
const localQuote = getRandomLocalQuote(randomLang);
|
||||
if (localQuote) {
|
||||
return NextResponse.json({ goal: `${localQuote.text} — ${localQuote.author}`, isQuote: true });
|
||||
}
|
||||
|
||||
@ -88,6 +88,7 @@ export async function GET(request: NextRequest) {
|
||||
weekdayCase: true,
|
||||
customWeekdayNames: true,
|
||||
quoteSourceUrls: true,
|
||||
quoteLanguages: true,
|
||||
accountNumber: true,
|
||||
createdAt: true
|
||||
}
|
||||
@ -130,7 +131,7 @@ export async function PATCH(request: NextRequest) {
|
||||
cwFontFamily, cwFontSize, cwFontWeight, cwColor,
|
||||
yearFontFamily, yearFontSize, yearFontWeight, yearColor,
|
||||
showTaskCheckboxes, dayHeaderGap,
|
||||
showCompletedTasks, showLines, startDayOffset, weekdayFormat, weekdayCase, customWeekdayNames, quoteSourceUrls
|
||||
showCompletedTasks, showLines, startDayOffset, weekdayFormat, weekdayCase, customWeekdayNames, quoteSourceUrls, quoteLanguages
|
||||
} = body;
|
||||
|
||||
const updateData: any = {
|
||||
@ -208,6 +209,7 @@ export async function PATCH(request: NextRequest) {
|
||||
...(weekdayCase !== undefined && { weekdayCase }),
|
||||
...(customWeekdayNames !== undefined && { customWeekdayNames }),
|
||||
...(quoteSourceUrls !== undefined && { quoteSourceUrls }),
|
||||
...(quoteLanguages !== undefined && { quoteLanguages }),
|
||||
};
|
||||
if (password && password.trim() !== "") {
|
||||
updateData.passwordHash = await bcrypt.hash(password, 10);
|
||||
@ -293,6 +295,7 @@ export async function PATCH(request: NextRequest) {
|
||||
weekdayCase: true,
|
||||
customWeekdayNames: true,
|
||||
quoteSourceUrls: true,
|
||||
quoteLanguages: true,
|
||||
accountNumber: true,
|
||||
}
|
||||
});
|
||||
|
||||
@ -1126,6 +1126,7 @@ export default function WeeklyView() {
|
||||
dayHeaderGap?: string;
|
||||
showTaskCheckboxes?: boolean;
|
||||
quoteSourceUrls?: string[];
|
||||
quoteLanguages?: string[];
|
||||
startDayOffset?: number;
|
||||
id?: string;
|
||||
accountNumber?: number;
|
||||
@ -1195,6 +1196,7 @@ export default function WeeklyView() {
|
||||
yearFontWeight: "700",
|
||||
quoteSourceUrl: "",
|
||||
quoteSourceUrls: [],
|
||||
quoteLanguages: ["en", "de"],
|
||||
});
|
||||
const [motivationalQuote, setMotivationalQuote] = useState("");
|
||||
const [showSummary, setShowSummary] = useState(false);
|
||||
@ -1645,15 +1647,18 @@ export default function WeeklyView() {
|
||||
}
|
||||
}
|
||||
|
||||
// Final fallback: use local curated quotes
|
||||
const lang = profile.language === "de" ? "de" : "en";
|
||||
const localQuote = getRandomLocalQuote(lang);
|
||||
// Final fallback: use local curated quotes in user-selected languages
|
||||
const quoteLangs = profile.quoteLanguages && profile.quoteLanguages.length > 0
|
||||
? profile.quoteLanguages
|
||||
: [profile.language || "en"];
|
||||
const randomLang = quoteLangs[Math.floor(Math.random() * quoteLangs.length)];
|
||||
const localQuote = getRandomLocalQuote(randomLang);
|
||||
if (localQuote) {
|
||||
setMotivationalQuote(`${localQuote.text} — ${localQuote.author}`);
|
||||
} else {
|
||||
setMotivationalQuote(lang === "de" ? "Bleib fokussiert und produktiv." : "Stay focused and productive.");
|
||||
setMotivationalQuote(randomLang === "de" ? "Bleib fokussiert und produktiv." : "Stay focused and productive.");
|
||||
}
|
||||
}, [profile.goalFallbackType, profile.quoteSourceUrl, profile.quoteSourceUrls, profile.language]);
|
||||
}, [profile.goalFallbackType, profile.quoteSourceUrl, profile.quoteSourceUrls, profile.language, profile.quoteLanguages]);
|
||||
|
||||
// Fetch tasks on mount
|
||||
useEffect(() => {
|
||||
@ -8003,9 +8008,11 @@ interface SettingsSidebarProps {
|
||||
showTaskCheckboxes?: boolean;
|
||||
startDayOffset?: number;
|
||||
quoteSourceUrls: string[];
|
||||
quoteLanguages: string[];
|
||||
}) => void;
|
||||
setCurrentWeekStart: (d: Date) => void;
|
||||
quoteSourceUrls?: string[];
|
||||
quoteLanguages?: string[];
|
||||
goal: string;
|
||||
setGoal: (goal: string) => void;
|
||||
saveGoal: (goal: string) => void;
|
||||
@ -8434,6 +8441,7 @@ function SettingsSidebar({
|
||||
dayHeaderGap?: string;
|
||||
showTaskCheckboxes?: boolean;
|
||||
quoteSourceUrls?: string[];
|
||||
quoteLanguages?: string[];
|
||||
startDayOffset?: number;
|
||||
id?: string;
|
||||
accountNumber?: number;
|
||||
@ -11775,8 +11783,82 @@ function SettingsSidebar({
|
||||
<p style={{ fontSize: "0.75rem", color: "var(--weekly-settings-label)", marginTop: "8px", fontStyle: "italic" }}>
|
||||
{profile.language === "de"
|
||||
? "Wenn keine externe Quelle antwortet, werden lokale kuratierte Zitate in Ihrer Sprache verwendet."
|
||||
: profile.language === "fr"
|
||||
? "Si aucune source externe ne répond, des citations locales dans votre langue sont utilisées."
|
||||
: profile.language === "es"
|
||||
? "Si ninguna fuente externa responde, se usan citas locales en tu idioma."
|
||||
: profile.language === "it"
|
||||
? "Se nessuna fonte esterna risponde, vengono usate citazioni locali nella tua lingua."
|
||||
: "If no external source responds, curated local quotes in your language are used as fallback."}
|
||||
</p>
|
||||
<div style={{ marginTop: "12px" }}>
|
||||
<label
|
||||
style={{
|
||||
display: "block",
|
||||
fontSize: "0.9rem",
|
||||
marginBottom: "6px",
|
||||
color: "var(--weekly-settings-label)",
|
||||
}}
|
||||
>
|
||||
{profile.language === "de" ? "Zitatsprachen" : profile.language === "fr" ? "Langues des citations" : profile.language === "es" ? "Idiomas de citas" : profile.language === "it" ? "Lingue delle citazioni" : "Quote Languages"}
|
||||
</label>
|
||||
<div style={{ display: "flex", flexWrap: "wrap", gap: "8px" }}>
|
||||
{[
|
||||
{ code: "en", label: "English" },
|
||||
{ code: "de", label: "Deutsch" },
|
||||
{ code: "fr", label: "Français" },
|
||||
{ code: "es", label: "Español" },
|
||||
{ code: "it", label: "Italiano" },
|
||||
].map((lang) => {
|
||||
const selected = (profile.quoteLanguages || ["en", "de"]).includes(lang.code);
|
||||
return (
|
||||
<label
|
||||
key={lang.code}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "4px",
|
||||
fontSize: "0.85rem",
|
||||
cursor: "pointer",
|
||||
padding: "4px 10px",
|
||||
borderRadius: "6px",
|
||||
border: selected ? "1px solid var(--weekly-teal)" : "1px solid var(--weekly-border)",
|
||||
background: selected ? "var(--weekly-teal)" : "transparent",
|
||||
color: selected ? "white" : "var(--weekly-text)",
|
||||
transition: "all 0.15s ease",
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selected}
|
||||
onChange={() => {
|
||||
const current = profile.quoteLanguages || ["en", "de"];
|
||||
const updated = selected
|
||||
? current.filter((c: string) => c !== lang.code)
|
||||
: [...current, lang.code];
|
||||
if (updated.length > 0) {
|
||||
setProfile((p) => ({ ...p, quoteLanguages: updated }));
|
||||
}
|
||||
}}
|
||||
style={{ display: "none" }}
|
||||
/>
|
||||
{lang.label}
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<p style={{ fontSize: "0.75rem", color: "var(--weekly-text-light)", marginTop: "4px" }}>
|
||||
{profile.language === "de"
|
||||
? "Wählen Sie die Sprachen für Ihre Zitate. Mindestens eine muss ausgewählt sein."
|
||||
: profile.language === "fr"
|
||||
? "Choisissez les langues de vos citations. Au moins une doit être sélectionnée."
|
||||
: profile.language === "es"
|
||||
? "Elige los idiomas de tus citas. Al menos uno debe estar seleccionado."
|
||||
: profile.language === "it"
|
||||
? "Scegli le lingue delle citazioni. Almeno una deve essere selezionata."
|
||||
: "Choose which languages your quotes appear in. At least one must be selected."}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{profile.goalFallbackType === "default" && (
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
export interface LocalQuote {
|
||||
text: string;
|
||||
author: string;
|
||||
language: "de" | "en";
|
||||
language: "de" | "en" | "fr" | "es" | "it";
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
@ -525,6 +525,279 @@ export const LOCAL_QUOTES: LocalQuote[] = [
|
||||
language: "en",
|
||||
tags: ["work", "perseverance", "success"],
|
||||
},
|
||||
// --- French Quotes ---
|
||||
{
|
||||
text: "Il n'y a qu'une façon d'échouer, c'est d'abandonner avant d'avoir réussi.",
|
||||
author: "Olivier Lockert",
|
||||
language: "fr",
|
||||
tags: ["perseverance", "motivation"],
|
||||
},
|
||||
{
|
||||
text: "Le succès n'est pas final, l'échec n'est pas fatal : c'est le courage de continuer qui compte.",
|
||||
author: "Winston Churchill",
|
||||
language: "fr",
|
||||
tags: ["perseverance", "success", "motivation"],
|
||||
},
|
||||
{
|
||||
text: "La vie, ce n'est pas d'attendre que les orages passent, c'est d'apprendre à danser sous la pluie.",
|
||||
author: "Sénèque",
|
||||
language: "fr",
|
||||
tags: ["life", "wisdom", "perseverance"],
|
||||
},
|
||||
{
|
||||
text: "Ce n'est pas parce que les choses sont difficiles que nous n'osons pas, c'est parce que nous n'osons pas qu'elles sont difficiles.",
|
||||
author: "Sénèque",
|
||||
language: "fr",
|
||||
tags: ["motivation", "wisdom"],
|
||||
},
|
||||
{
|
||||
text: "Le meilleur moment pour planter un arbre était il y a vingt ans. Le deuxième meilleur moment est maintenant.",
|
||||
author: "Proverbe chinois",
|
||||
language: "fr",
|
||||
tags: ["motivation", "wisdom", "work"],
|
||||
},
|
||||
{
|
||||
text: "Celui qui déplace une montagne commence par déplacer de petites pierres.",
|
||||
author: "Confucius",
|
||||
language: "fr",
|
||||
tags: ["perseverance", "motivation", "work"],
|
||||
},
|
||||
{
|
||||
text: "La simplicité est la sophistication suprême.",
|
||||
author: "Léonard de Vinci",
|
||||
language: "fr",
|
||||
tags: ["wisdom", "creativity"],
|
||||
},
|
||||
{
|
||||
text: "Rien n'est permanent, sauf le changement.",
|
||||
author: "Héraclite",
|
||||
language: "fr",
|
||||
tags: ["wisdom", "life"],
|
||||
},
|
||||
{
|
||||
text: "Fais de ta vie un rêve, et d'un rêve, une réalité.",
|
||||
author: "Antoine de Saint-Exupéry",
|
||||
language: "fr",
|
||||
tags: ["motivation", "life", "creativity"],
|
||||
},
|
||||
{
|
||||
text: "L'imagination est plus importante que le savoir.",
|
||||
author: "Albert Einstein",
|
||||
language: "fr",
|
||||
tags: ["creativity", "wisdom"],
|
||||
},
|
||||
{
|
||||
text: "On ne voit bien qu'avec le cœur. L'essentiel est invisible pour les yeux.",
|
||||
author: "Antoine de Saint-Exupéry",
|
||||
language: "fr",
|
||||
tags: ["wisdom", "life"],
|
||||
},
|
||||
{
|
||||
text: "Le bonheur n'est pas quelque chose de prêt à l'emploi. Il vient de vos propres actions.",
|
||||
author: "Dalaï Lama",
|
||||
language: "fr",
|
||||
tags: ["wisdom", "life", "motivation"],
|
||||
},
|
||||
{
|
||||
text: "Chaque jour est une nouvelle chance de changer ta vie.",
|
||||
author: "Inconnu",
|
||||
language: "fr",
|
||||
tags: ["motivation", "life"],
|
||||
},
|
||||
{
|
||||
text: "Il faut toujours viser la lune, car même en cas d'échec, on atterrit dans les étoiles.",
|
||||
author: "Oscar Wilde",
|
||||
language: "fr",
|
||||
tags: ["motivation", "success"],
|
||||
},
|
||||
{
|
||||
text: "La créativité, c'est l'intelligence qui s'amuse.",
|
||||
author: "Albert Einstein",
|
||||
language: "fr",
|
||||
tags: ["creativity", "humor"],
|
||||
},
|
||||
// --- Spanish Quotes ---
|
||||
{
|
||||
text: "No es la especie más fuerte la que sobrevive, sino la que mejor se adapta al cambio.",
|
||||
author: "Charles Darwin",
|
||||
language: "es",
|
||||
tags: ["wisdom", "perseverance", "life"],
|
||||
},
|
||||
{
|
||||
text: "El único modo de hacer un gran trabajo es amar lo que haces.",
|
||||
author: "Steve Jobs",
|
||||
language: "es",
|
||||
tags: ["work", "motivation", "success"],
|
||||
},
|
||||
{
|
||||
text: "La vida es lo que pasa mientras estás ocupado haciendo otros planes.",
|
||||
author: "John Lennon",
|
||||
language: "es",
|
||||
tags: ["life", "wisdom"],
|
||||
},
|
||||
{
|
||||
text: "Sé tú mismo; todos los demás ya están ocupados.",
|
||||
author: "Oscar Wilde",
|
||||
language: "es",
|
||||
tags: ["life", "wisdom", "humor"],
|
||||
},
|
||||
{
|
||||
text: "El mejor momento para plantar un árbol fue hace veinte años. El segundo mejor momento es ahora.",
|
||||
author: "Proverbio chino",
|
||||
language: "es",
|
||||
tags: ["motivation", "wisdom", "work"],
|
||||
},
|
||||
{
|
||||
text: "La imaginación es más importante que el conocimiento.",
|
||||
author: "Albert Einstein",
|
||||
language: "es",
|
||||
tags: ["creativity", "wisdom"],
|
||||
},
|
||||
{
|
||||
text: "No importa lo lento que vayas, siempre y cuando no te detengas.",
|
||||
author: "Confucio",
|
||||
language: "es",
|
||||
tags: ["perseverance", "motivation"],
|
||||
},
|
||||
{
|
||||
text: "El secreto de ir adelante es empezar.",
|
||||
author: "Mark Twain",
|
||||
language: "es",
|
||||
tags: ["motivation", "work", "success"],
|
||||
},
|
||||
{
|
||||
text: "La felicidad de tu vida depende de la calidad de tus pensamientos.",
|
||||
author: "Marco Aurelio",
|
||||
language: "es",
|
||||
tags: ["wisdom", "life"],
|
||||
},
|
||||
{
|
||||
text: "Sé el cambio que deseas ver en el mundo.",
|
||||
author: "Mahatma Gandhi",
|
||||
language: "es",
|
||||
tags: ["wisdom", "life", "motivation"],
|
||||
},
|
||||
{
|
||||
text: "La creatividad es la inteligencia divirtiéndose.",
|
||||
author: "Albert Einstein",
|
||||
language: "es",
|
||||
tags: ["creativity", "humor"],
|
||||
},
|
||||
{
|
||||
text: "Cada día es una nueva oportunidad para cambiar tu vida.",
|
||||
author: "Desconocido",
|
||||
language: "es",
|
||||
tags: ["motivation", "life"],
|
||||
},
|
||||
{
|
||||
text: "Lo que no te mata, te hace más fuerte.",
|
||||
author: "Friedrich Nietzsche",
|
||||
language: "es",
|
||||
tags: ["perseverance", "motivation"],
|
||||
},
|
||||
{
|
||||
text: "Haz lo que puedas, con lo que tengas, donde estés.",
|
||||
author: "Theodore Roosevelt",
|
||||
language: "es",
|
||||
tags: ["motivation", "work", "perseverance"],
|
||||
},
|
||||
{
|
||||
text: "Grandes cosas nunca vienen de la zona de confort.",
|
||||
author: "Desconocido",
|
||||
language: "es",
|
||||
tags: ["motivation", "perseverance", "success"],
|
||||
},
|
||||
// --- Italian Quotes ---
|
||||
{
|
||||
text: "La semplicità è la raffinatezza suprema.",
|
||||
author: "Leonardo da Vinci",
|
||||
language: "it",
|
||||
tags: ["wisdom", "creativity"],
|
||||
},
|
||||
{
|
||||
text: "Nel mezzo delle difficoltà nascono le opportunità.",
|
||||
author: "Albert Einstein",
|
||||
language: "it",
|
||||
tags: ["perseverance", "motivation", "success"],
|
||||
},
|
||||
{
|
||||
text: "L'unico modo di fare un ottimo lavoro è amare quello che fai.",
|
||||
author: "Steve Jobs",
|
||||
language: "it",
|
||||
tags: ["work", "motivation", "success"],
|
||||
},
|
||||
{
|
||||
text: "Chi non osa nulla, non speri nulla.",
|
||||
author: "Friedrich Schiller",
|
||||
language: "it",
|
||||
tags: ["motivation", "perseverance"],
|
||||
},
|
||||
{
|
||||
text: "La vita è quello che ti accade mentre sei impegnato a fare altri progetti.",
|
||||
author: "John Lennon",
|
||||
language: "it",
|
||||
tags: ["life", "wisdom"],
|
||||
},
|
||||
{
|
||||
text: "Sii il cambiamento che vuoi vedere nel mondo.",
|
||||
author: "Mahatma Gandhi",
|
||||
language: "it",
|
||||
tags: ["wisdom", "life", "motivation"],
|
||||
},
|
||||
{
|
||||
text: "Il miglior momento per piantare un albero era vent'anni fa. Il secondo miglior momento è adesso.",
|
||||
author: "Proverbio cinese",
|
||||
language: "it",
|
||||
tags: ["motivation", "wisdom", "work"],
|
||||
},
|
||||
{
|
||||
text: "Non importa quanto vai piano, l'importante è non fermarsi.",
|
||||
author: "Confucio",
|
||||
language: "it",
|
||||
tags: ["perseverance", "motivation"],
|
||||
},
|
||||
{
|
||||
text: "Il segreto per andare avanti è iniziare.",
|
||||
author: "Mark Twain",
|
||||
language: "it",
|
||||
tags: ["motivation", "work", "success"],
|
||||
},
|
||||
{
|
||||
text: "La fantasia è più importante del sapere.",
|
||||
author: "Albert Einstein",
|
||||
language: "it",
|
||||
tags: ["creativity", "wisdom"],
|
||||
},
|
||||
{
|
||||
text: "Quello che non ti uccide ti rende più forte.",
|
||||
author: "Friedrich Nietzsche",
|
||||
language: "it",
|
||||
tags: ["perseverance", "motivation"],
|
||||
},
|
||||
{
|
||||
text: "La creatività è l'intelligenza che si diverte.",
|
||||
author: "Albert Einstein",
|
||||
language: "it",
|
||||
tags: ["creativity", "humor"],
|
||||
},
|
||||
{
|
||||
text: "Ogni giorno è un nuovo inizio.",
|
||||
author: "T.S. Eliot",
|
||||
language: "it",
|
||||
tags: ["motivation", "life"],
|
||||
},
|
||||
{
|
||||
text: "Fai quel che puoi, con quel che hai, dove sei.",
|
||||
author: "Theodore Roosevelt",
|
||||
language: "it",
|
||||
tags: ["motivation", "work", "perseverance"],
|
||||
},
|
||||
{
|
||||
text: "Le grandi cose non vengono mai dalla zona di comfort.",
|
||||
author: "Sconosciuto",
|
||||
language: "it",
|
||||
tags: ["motivation", "perseverance", "success"],
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user