feat: dölj SEK-receptkostnad för icke-SEK-användare + i18n:remove-language

- Recept-kostnaden ligger i SEK (basvaluta, D-028). För en spansk användare
  (currency != SEK) nollas estimatedCostMinorPerPortion i API:t (list + detalj)
  -> kostnads-taggen försvinner helt i stället för att visa en svensk SEK-siffra.
  (Beslut: hellre ingen uppskattning än fel valuta, tills riktpriser via kvitton/M8.)
- i18n:remove-language – motsvarighet till add-language, tar bort ett språk rent.
  Verifierat: add el -> remove el -> byte-rent, vakt + typecheck gröna.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude
2026-08-21 12:59:56 +00:00
parent 2076bbc15b
commit e7657d0153
3 changed files with 72 additions and 4 deletions
+15 -3
View File
@@ -125,8 +125,12 @@ export async function recipeRoutes(app: FastifyInstance) {
.offset(q.offset);
// Titlar på användarens språk där publicerad översättning finns (i18n M3).
const languageTag = await userLanguageTag(app.db, req.userId);
const listPrefs = await loadLocalePreferences(app.db, req.userId);
const languageTag = listPrefs.languageTag;
const candidates = languageCandidates(languageTag);
// Recept-kostnaden ligger i SEK (basvaluta, D-028). Visa den bara för SEK-
// användare annars ingen siffra alls (hellre inget än fel valuta).
const listShowCost = listPrefs.currencyCode === "SEK";
let titleMap = new Map<string, string>();
if (!candidates.includes("sv") && rows.length > 0) {
const translations = await app.db
@@ -153,7 +157,11 @@ export async function recipeRoutes(app: FastifyInstance) {
}
return {
recipes: rows.map((r) => ({ ...r, title: titleMap.get(r.id) ?? r.titleSv })),
recipes: rows.map((r) => ({
...r,
title: titleMap.get(r.id) ?? r.titleSv,
estimatedCostMinorPerPortion: listShowCost ? r.estimatedCostMinorPerPortion : null,
})),
language: candidates.includes("sv") ? "sv" : languageTag,
};
});
@@ -237,7 +245,10 @@ export async function recipeRoutes(app: FastifyInstance) {
// Täckning mot hushållets lager: vad har du hemma, vad saknas (→ inköpslista).
const coverage = await recipeCoverage(app, householdId, recipe.ingredients);
const languageTag = await userLanguageTag(app.db, req.userId);
const detailPrefs = await loadLocalePreferences(app.db, req.userId);
const languageTag = detailPrefs.languageTag;
// Recept-kostnaden är i SEK (D-028) dölj den för icke-SEK-användare.
const detailShowCost = detailPrefs.currencyCode === "SEK";
const translation = await resolveRecipeTranslation(app.db, id, languageTag);
const ingredientNames = await resolveIngredientNames(
app.db,
@@ -247,6 +258,7 @@ export async function recipeRoutes(app: FastifyInstance) {
return {
...recipe,
estimatedCostMinorPerPortion: detailShowCost ? recipe.estimatedCostMinorPerPortion : null,
language: translation?.language ?? "sv",
title: translation?.title ?? recipe.titleSv,
description: translation ? translation.description : recipe.descriptionSv,
+2 -1
View File
@@ -10,7 +10,8 @@
"typecheck": "tsc --noEmit",
"test": "vitest run --passWithNoTests",
"i18n:check": "node scripts/i18n-check.mjs",
"i18n:add-language": "node scripts/i18n-add-language.mjs"
"i18n:add-language": "node scripts/i18n-add-language.mjs",
"i18n:remove-language": "node scripts/i18n-remove-language.mjs"
},
"dependencies": {
"@app/analytics": "workspace:*",
@@ -0,0 +1,55 @@
#!/usr/bin/env node
/**
* i18n:remove-language ta bort ett språk rent (motsatsen till add-language).
*
* node scripts/i18n-remove-language.mjs <kod>
* ex: node scripts/i18n-remove-language.mjs el
*
* Tar bort: locale-mappen, profile.language.<kod>-raden i alla filer, och de tre
* raderna i i18n.ts (import + SUPPORTED_LANGUAGES + resources). Endast radborttag
* → minimala diffar. Kör i18n:check + typecheck efteråt för att bekräfta.
*/
import { rmSync, readFileSync, writeFileSync, existsSync, readdirSync, statSync } from "node:fs";
import { join } from "node:path";
const ROOT = process.cwd(); // apps/mobile
const LOC = join(ROOT, "src/locales");
const I18N = join(ROOT, "src/lib/i18n.ts");
const code = process.argv[2];
if (!code || !/^[a-z]{2,3}$/.test(code)) {
console.error("Användning: node scripts/i18n-remove-language.mjs <kod> (t.ex. el)");
process.exit(1);
}
if (code === "sv") {
console.error("Kan inte ta bort sv svenska är källspråket.");
process.exit(1);
}
if (!existsSync(join(LOC, code))) {
console.error(`src/locales/${code} finns inte inget att ta bort.`);
process.exit(1);
}
// 1. locale-mappen
rmSync(join(LOC, code), { recursive: true, force: true });
// 2. profile.language.<kod>-raden ur alla kvarvarande locale-filer
const labelNeedle = `"profile.language.${code}":`;
for (const lang of readdirSync(LOC).filter((d) => statSync(join(LOC, d)).isDirectory())) {
const p = join(LOC, lang, "common.json");
const lines = readFileSync(p, "utf8").split("\n");
const kept = lines.filter((l) => !l.includes(labelNeedle));
if (kept.length !== lines.length) writeFileSync(p, kept.join("\n"));
}
// 3. de tre raderna i i18n.ts
const importNeedle = `import ${code} from "../locales/${code}/common.json";`;
const supportedNeedle = `code: "${code}",`;
const resourceRe = new RegExp(`^\\s+${code}: \\{ common: ${code} \\},$`);
const kept = readFileSync(I18N, "utf8")
.split("\n")
.filter((l) => l.trim() !== importNeedle && !l.includes(supportedNeedle) && !resourceRe.test(l));
writeFileSync(I18N, kept.join("\n"));
console.log(`✓ Tog bort språket "${code}" (locale-mapp, etikett i alla filer, i18n.ts-rader).`);
console.log(`Kör: pnpm --filter @app/mobile i18n:check && pnpm --filter @app/mobile typecheck`);