174 lines
8.2 KiB
TypeScript
174 lines
8.2 KiB
TypeScript
import type { ProvenanceEntry } from "./types.js";
|
|
|
|
/**
|
|
* Mall-baserade provenienssträngar för personalisering (S1).
|
|
* Ingen fri AI-text till användaren — alla strängar byggs från grundade fakta.
|
|
* R7/stödjande ton: positiv framing, aldrig restriktiv eller skam.
|
|
*/
|
|
|
|
export type LanguageTag =
|
|
"sv" | "en" | "es" | "it" | "de" | "fr" | "da" | "nb" | "fi" | "nl" | "pl" | "pt";
|
|
|
|
const TEMPLATES: Record<string, Record<LanguageTag, string>> = {
|
|
favoriteCuisine: {
|
|
sv: "För att du har berättat att du gillar {{cuisine}} mat.",
|
|
en: "Because you said you like {{cuisine}} food.",
|
|
es: "Porque dijiste que te gusta la comida {{cuisine}}.",
|
|
it: "Perché hai detto che ti piace la cucina {{cuisine}}.",
|
|
de: "Weil du gesagt hast, dass du {{cuisine}} Essen magst.",
|
|
fr: "Parce que vous avez dit que vous aimiez la cuisine {{cuisine}}.",
|
|
da: "Fordi du har sagt, at du kan lide {{cuisine}} mad.",
|
|
nb: "Fordi du har sagt at du liker {{cuisine}} mat.",
|
|
fi: "Koska kerroit pitäväsi {{cuisine}} ruoasta.",
|
|
nl: "Omdat je hebt gezegd dat je {{cuisine}} eten lekker vindt.",
|
|
pl: "Ponieważ powiedziałeś, że lubisz kuchnię {{cuisine}}.",
|
|
pt: "Porque disseste que gostas de comida {{cuisine}}.",
|
|
},
|
|
cookedOften: {
|
|
sv: "För att du lagat {{recipe}} {{count}} gånger den senaste månaden.",
|
|
en: "Because you've cooked {{recipe}} {{count}} times in the past month.",
|
|
es: "Porque has cocinado {{recipe}} {{count}} veces el último mes.",
|
|
it: "Perché hai cucinato {{recipe}} {{count}} volte nell'ultimo mese.",
|
|
de: "Weil du {{recipe}} {{count}} Mal im letzten Monat gekocht hast.",
|
|
fr: "Parce que vous avez cuisiné {{recipe}} {{count}} fois le mois dernier.",
|
|
da: "Fordi du har lavet {{recipe}} {{count}} gange den seneste måned.",
|
|
nb: "Fordi du har laget {{recipe}} {{count}} ganger den siste måneden.",
|
|
fi: "Koska olet kokannut {{recipe}} {{count}} kertaa viimeisen kuukauden aikana.",
|
|
nl: "Omdat je {{recipe}} {{count}} keer hebt gekookt afgelopen maand.",
|
|
pl: "Ponieważ gotowałeś {{recipe}} {{count}} razy w ostatnim miesiącu.",
|
|
pt: "Porque cozinhaste {{recipe}} {{count}} vezes no último mês.",
|
|
},
|
|
expiringIngredient: {
|
|
sv: "För att ni har {{count}} {{ingredient}} som bör användas inom {{days}} dagar.",
|
|
en: "Because you have {{count}} {{ingredient}} to use within {{days}} days.",
|
|
es: "Porque tenéis {{count}} {{ingredient}} para usar en {{days}} días.",
|
|
it: "Perché avete {{count}} {{ingredient}} da usare entro {{days}} giorni.",
|
|
de: "Weil ihr {{count}} {{ingredient}} habt, die binnen {{days}} Tagen verwendet werden sollten.",
|
|
fr: "Parce que vous avez {{count}} {{ingredient}} à utiliser dans {{days}} jours.",
|
|
da: "Fordi I har {{count}} {{ingredient}}, der bør bruges inden for {{days}} dage.",
|
|
nb: "Fordi dere har {{count}} {{ingredient}} som bør brukes innen {{days}} dager.",
|
|
fi: "Koska sinulla on {{count}} {{ingredient}}, jotka tulisi käyttää {{days}} päivän kuluessa.",
|
|
nl: "Omdat jullie {{count}} {{ingredient}} hebben die binnen {{days}} dagen gebruikt moeten worden.",
|
|
pl: "Ponieważ macie {{count}} {{ingredient}}, które należy zużyć w ciągu {{days}} dni.",
|
|
pt: "Porque tens {{count}} {{ingredient}} para usar em {{days}} dias.",
|
|
},
|
|
fitsProteinGoal: {
|
|
sv: "För att det passar ditt proteinmål i dag.",
|
|
en: "Because it fits your protein goal for today.",
|
|
es: "Porque encaja con tu objetivo de proteína de hoy.",
|
|
it: "Perché si adatta al tuo obiettivo proteico di oggi.",
|
|
de: "Weil es zu deinem Protein-Ziel für heute passt.",
|
|
fr: "Parce que cela correspond à votre objectif de protéines pour aujourd'hui.",
|
|
da: "Fordi det passer til dit proteinmål i dag.",
|
|
nb: "Fordi det passer proteinmålet ditt i dag.",
|
|
fi: "Koska se sopii päivän proteiinitavoitteesi.",
|
|
nl: "Omdat het past bij je eiwitdoel voor vandaag.",
|
|
pl: "Ponieważ pasuje do dzisiejszego celu białkowego.",
|
|
pt: "Porque se adequa à tua meta de proteína para hoje.",
|
|
},
|
|
fitsVegetableGoal: {
|
|
sv: "För att det bidrar till dina grönsaker i dag.",
|
|
en: "Because it contributes to your vegetables for today.",
|
|
es: "Porque contribuye a tus verduras de hoy.",
|
|
it: "Perché contribuisce alle tue verdure di oggi.",
|
|
de: "Weil es zu deinem Gemüse für heute beiträgt.",
|
|
fr: "Parce que cela contribue à vos légumes d'aujourd'hui.",
|
|
da: "Fordi det bidrager til dine grøntsager i dag.",
|
|
nb: "Fordi det bidrar til grønnsakene dine i dag.",
|
|
fi: "Koska se lisää päivän kasvissi saantia.",
|
|
nl: "Omdat het bijdraagt aan je groenten voor vandaag.",
|
|
pl: "Ponieważ przyczynia się do dzisiejszego spożycia warzyw.",
|
|
pt: "Porque contribui para os teus vegetais de hoje.",
|
|
},
|
|
tastePreference: {
|
|
sv: "För att det matchar din smakprofil ({{axis}}).",
|
|
en: "Because it matches your taste profile ({{axis}}).",
|
|
es: "Porque coincide con tu perfil de sabor ({{axis}}).",
|
|
it: "Perché corrisponde al tuo profilo di gusto ({{axis}}).",
|
|
de: "Weil es zu deinem Geschmacksprofil passt ({{axis}}).",
|
|
fr: "Parce que cela correspond à votre profil de goût ({{axis}}).",
|
|
da: "Fordi det matcher din smagsprofil ({{axis}}).",
|
|
nb: "Fordi det matcher smaksprofilen din ({{axis}}).",
|
|
fi: "Koska se sopii makuprofiilisi ({{axis}}).",
|
|
nl: "Omdat het bij je smaakprofiel past ({{axis}}).",
|
|
pl: "Ponieważ pasuje do Twojego profilu smakowego ({{axis}}).",
|
|
pt: "Porque corresponde ao teu perfil de sabor ({{axis}}).",
|
|
},
|
|
usesStapleYouFinish: {
|
|
sv: "För att ni brukar äta upp {{ingredient}} när ni lagar det.",
|
|
en: "Because you usually finish {{ingredient}} when you cook it.",
|
|
es: "Porque soléis acabar {{ingredient}} cuando lo cocináis.",
|
|
it: "Perché di solito finite {{ingredient}} quando lo cucinate.",
|
|
de: "Weil ihr {{ingredient}} normalerweise aufesset, wenn ihr es kocht.",
|
|
fr: "Parce que vous finissez habituellement {{ingredient}} quand vous le cuisinez.",
|
|
da: "Fordi I plejer at spise {{ingredient}} op, når I laver det.",
|
|
nb: "Fordi dere pleier å spise opp {{ingredient}} når dere lager det.",
|
|
fi: "Koska yleensä syöt {{ingredient}} loppuun, kun kokkaat sitä.",
|
|
nl: "Omdat jullie {{ingredient}} meestal opeten als jullie het koken.",
|
|
pl: "Ponieważ zwykle zjadacie {{ingredient}}, gdy to gotujecie.",
|
|
pt: "Porque normalmente acabas {{ingredient}} quando cozinhas.",
|
|
},
|
|
};
|
|
|
|
export function renderProvenance(entries: ProvenanceEntry[], languageTag: string): string {
|
|
const lang = (languageTag.split("-")[0] ?? "sv") as LanguageTag;
|
|
const parts: string[] = [];
|
|
for (const entry of entries) {
|
|
const tmpl = TEMPLATES[entry.key];
|
|
if (!tmpl) continue;
|
|
let s = tmpl[lang] ?? tmpl.sv;
|
|
for (const [k, v] of Object.entries(entry.args)) {
|
|
s = s.replaceAll(`{{${k}}}`, String(v));
|
|
}
|
|
parts.push(s);
|
|
}
|
|
return parts.join(" ");
|
|
}
|
|
|
|
export function renderProvenanceList(entries: ProvenanceEntry[], languageTag: string): string[] {
|
|
const lang = (languageTag.split("-")[0] ?? "sv") as LanguageTag;
|
|
return entries
|
|
.map((entry) => {
|
|
const tmpl = TEMPLATES[entry.key];
|
|
if (!tmpl) return null;
|
|
let s = tmpl[lang] ?? tmpl.sv;
|
|
for (const [k, v] of Object.entries(entry.args)) {
|
|
s = s.replaceAll(`{{${k}}}`, String(v));
|
|
}
|
|
return s;
|
|
})
|
|
.filter((s): s is string => s != null);
|
|
}
|
|
|
|
/** Språk som varje proveniensmall är översatt till. */
|
|
export function provenanceTemplateLanguages(): Record<string, LanguageTag[]> {
|
|
const result: Record<string, LanguageTag[]> = {};
|
|
for (const [key, t] of Object.entries(TEMPLATES)) {
|
|
result[key] = Object.keys(t) as LanguageTag[];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/** Förbjudna ord/fraser i proveniens/copy — R3 (ingen skam) + R7 (välmående). */
|
|
export const FORBIDDEN_COPY_PATTERNS = [
|
|
/överskrid/i,
|
|
/bara .* kvar/i,
|
|
/begränsa/i,
|
|
/undvik/i,
|
|
/skärp/i,
|
|
/skäms/i,
|
|
/dålig/i,
|
|
/för mycket/i,
|
|
/för lite/i,
|
|
/borde inte/i,
|
|
/får inte/i,
|
|
/måste sluta/i,
|
|
/överdriv/i,
|
|
/kalori/i, // näringsmål ska ramas positivt, aldrig kcal-centrerat
|
|
];
|
|
|
|
/** Kontrollera att en sträng inte innehåller förbjuden copy. */
|
|
export function containsForbiddenCopy(text: string): boolean {
|
|
return FORBIDDEN_COPY_PATTERNS.some((re) => re.test(text));
|
|
}
|