feat(i18n): översätt kvarvarande vyer + härda vakten (audit-backlog)
- API: översatt title/displayName/toName läggs nu bredvid Sv-fälten i favoriter,
receptvarianter, /scaled, skapar-profiler, topplistor, memory-impact,
substitutions — samt de denormaliserade titlarna (min dag, historik, matlådor,
matlåde-förslag, veckoplan) via nullable recipeId med svensk fallback.
10 endpoints, batchade resolvers (ett anrop per endpoint).
- Mobil: konsumerar de nya fälten med ?? Sv-fallback (swap-meal, sparade recept,
varianter, min dag, logg, matlådor, veckoplan, inköpslista). Hårdkodade
strängar -> t() (kitchen, scan-review, register, recept protein/Skapat av);
decimalkomma -> Intl.NumberFormat. Allergen-etiketter -> t() (+5 nya nycklar).
11 nya nycklar i alla 12 språk.
- i18n-vakt härdad: fångar nu hårdkodad svenska i prop={`...`}-mallliteraler
(blind fläck förr). Verifierat att den fäller men inte ger falska positiv.
- whySv var redan lokaliserad (buildWhy med språktagg) - orörd.
- typecheck grönt (alla paket), vakt grön (603 nycklar).
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,8 @@ import { and, desc, eq, sql } from "drizzle-orm";
|
||||
import { schema } from "@app/database";
|
||||
import { idParamSchema } from "@app/validation";
|
||||
import { errors, parse } from "../lib/errors.js";
|
||||
import { resolveRecipeTitles } from "../lib/contentLanguage.js";
|
||||
import { loadLocalePreferences } from "../lib/localeContext.js";
|
||||
|
||||
/**
|
||||
* Community & creators (spec §35–38).
|
||||
@@ -42,6 +44,13 @@ export async function communityRoutes(app: FastifyInstance) {
|
||||
.orderBy(desc(schema.recipes.cookCount))
|
||||
.limit(30);
|
||||
|
||||
const languageTag = (await loadLocalePreferences(app.db, req.userId)).languageTag;
|
||||
const titleMap = await resolveRecipeTitles(
|
||||
app.db,
|
||||
recipes.map((r) => r.id),
|
||||
languageTag,
|
||||
);
|
||||
|
||||
return {
|
||||
userId: id,
|
||||
displayName: user.displayName,
|
||||
@@ -51,7 +60,7 @@ export async function communityRoutes(app: FastifyInstance) {
|
||||
totalCooks: stats?.totalCooks ?? 0,
|
||||
averageRating: stats?.averageRating ?? null,
|
||||
badges: stats?.badges ?? [],
|
||||
recipes,
|
||||
recipes: recipes.map((r) => ({ ...r, title: titleMap.get(r.id) ?? r.titleSv })),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -97,8 +106,9 @@ export async function communityRoutes(app: FastifyInstance) {
|
||||
const kind = (req.params as { kind: string }).kind;
|
||||
const MIN_RATINGS = 5;
|
||||
|
||||
let rows: Array<{ id: string; titleSv: string; value: number | null }>;
|
||||
if (kind === "most-cooked") {
|
||||
const rows = await app.db
|
||||
rows = await app.db
|
||||
.select({
|
||||
id: schema.recipes.id,
|
||||
titleSv: schema.recipes.titleSv,
|
||||
@@ -108,10 +118,8 @@ export async function communityRoutes(app: FastifyInstance) {
|
||||
.where(eq(schema.recipes.status, "published"))
|
||||
.orderBy(desc(schema.recipes.cookCount))
|
||||
.limit(20);
|
||||
return { enabled: true, kind, entries: rows };
|
||||
}
|
||||
if (kind === "top-rated") {
|
||||
const rows = await app.db
|
||||
} else if (kind === "top-rated") {
|
||||
rows = await app.db
|
||||
.select({
|
||||
id: schema.recipes.id,
|
||||
titleSv: schema.recipes.titleSv,
|
||||
@@ -126,10 +134,8 @@ export async function communityRoutes(app: FastifyInstance) {
|
||||
)
|
||||
.orderBy(desc(schema.recipes.ratingAverage))
|
||||
.limit(20);
|
||||
return { enabled: true, kind, entries: rows };
|
||||
}
|
||||
if (kind === "budget") {
|
||||
const rows = await app.db
|
||||
} else if (kind === "budget") {
|
||||
rows = await app.db
|
||||
.select({
|
||||
id: schema.recipes.id,
|
||||
titleSv: schema.recipes.titleSv,
|
||||
@@ -144,10 +150,8 @@ export async function communityRoutes(app: FastifyInstance) {
|
||||
)
|
||||
.orderBy(schema.recipes.estimatedCostMinorPerPortion)
|
||||
.limit(20);
|
||||
return { enabled: true, kind, entries: rows };
|
||||
}
|
||||
if (kind === "protein") {
|
||||
const rows = await app.db
|
||||
} else if (kind === "protein") {
|
||||
rows = await app.db
|
||||
.select({
|
||||
id: schema.recipes.id,
|
||||
titleSv: schema.recipes.titleSv,
|
||||
@@ -157,9 +161,21 @@ export async function communityRoutes(app: FastifyInstance) {
|
||||
.where(eq(schema.recipes.status, "published"))
|
||||
.orderBy(desc(sql`(${schema.recipes.nutritionPerPortion}->>'proteinG')::float`))
|
||||
.limit(20);
|
||||
return { enabled: true, kind, entries: rows };
|
||||
} else {
|
||||
// Spec §38: ingen ranking på vikt, viktnedgång, kalorier eller BMI.
|
||||
throw errors.badRequest("Okänd rankingtyp.");
|
||||
}
|
||||
// Spec §38: ingen ranking på vikt, viktnedgång, kalorier eller BMI.
|
||||
throw errors.badRequest("Okänd rankingtyp.");
|
||||
|
||||
const languageTag = (await loadLocalePreferences(app.db, req.userId)).languageTag;
|
||||
const titleMap = await resolveRecipeTitles(
|
||||
app.db,
|
||||
rows.map((r) => r.id),
|
||||
languageTag,
|
||||
);
|
||||
return {
|
||||
enabled: true,
|
||||
kind,
|
||||
entries: rows.map((r) => ({ ...r, title: titleMap.get(r.id) ?? r.titleSv })),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user