i18n(rekommendationer): localisera 💡-raden + meal-box-text (12 språk)

- 💡 why-texten: what-to-eat skickar nu användarens languageTag till motorn;
  scoring använder buildWhy(..., ctx.languageTag) i stället för hårdkodat buildWhySv.
  WHY_TEMPLATES har redan alla 12 språk – felet var bara att sv-SE var fast.
  Default sv-SE = oförändrat beteende (säkert). (Rest: utgår-ingrediensens NAMN
  i meningen är fortf. svenskt – motorn har bara svenska namn; noterat.)
- meal-box-förslagens text: whySv-sträng -> useByUrgent-flagga + mealbox.eatBy/ready
  i alla 12 språk (klienten renderar via t()).

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude
2026-08-21 02:32:31 +00:00
parent 6d36ec7459
commit 2076bbc15b
16 changed files with 40 additions and 16 deletions
+4 -11
View File
@@ -433,6 +433,7 @@ export async function recommendationRoutes(app: FastifyInstance) {
cravingCuisine: craving?.cuisine,
cravingMaxKcal: craving?.maxKcal,
personalizationEnabled,
languageTag: localePrefs.languageTag,
memoryItems: personalizationEnabled ? memoryItems : undefined,
tasteSignals: personalizationEnabled ? tasteSignals : undefined,
cookingAssumptions: personalizationEnabled ? cookingAssumptions : undefined,
@@ -583,20 +584,14 @@ export async function recommendationRoutes(app: FastifyInstance) {
titleSv: box.titleSv,
portionsRemaining: box.portionsRemaining,
recommendedUseBy: box.recommendedUseBy,
whySv:
Date.parse(box.recommendedUseBy) <= today.getTime() + 2 * 86_400_000
? `Matlådan bör ätas senast ${box.recommendedUseBy}. Noll matlagning, noll svinn.`
: "Färdig mat som väntar snabbaste middagen i huset.",
useByUrgent: Date.parse(box.recommendedUseBy) <= today.getTime() + 2 * 86_400_000,
}));
// Betyg (stjärnor) finns på kandidaterna men följer INTE med i
// ScoredRecommendation som rankAll returnerar. Återkoppla dem per recept så
// mobilen kan visa "★ 4,5 · 12" i listan (annars syns aldrig satta betyg).
const ratingByRecipe = new Map(
scoredCandidates.map((c) => [
c.recipeId,
{ average: c.ratingAverage, count: c.ratingCount },
]),
scoredCandidates.map((c) => [c.recipeId, { average: c.ratingAverage, count: c.ratingCount }]),
);
// "Menade du …?" bara när en sökning gav noll träffar (grov felstavning).
@@ -614,9 +609,7 @@ export async function recommendationRoutes(app: FastifyInstance) {
);
const localizedById = await resolveIngredientNames(
app.db,
allIngredients
.map((i) => i.canonicalIngredientId)
.filter((id): id is string => id != null),
allIngredients.map((i) => i.canonicalIngredientId).filter((id): id is string => id != null),
languageTag,
);
const svToLocalized = new Map<string, string>();