i18n(app-svep) + i18n-vakt: hela mobil-UI:t översatt + CI-vakt mot regress

- Svepte 51 hårdkodade strängar -> t() över konto, sök/bläddra, sparade recept,
  kök, planera, skanna, byt måltid, inköp, minne, cooking, api-fel (+ locLabel/
  kategorier via nycklar). 47 nya nycklar, översatta till alla 12 språk.
- NY: apps/mobile/scripts/i18n-check.mjs (i18n:check) – fäller bygget om ett språk
  saknar en nyckel, en använd nyckel saknas, eller det finns hårdkodad UI-text.
  Wire:ad i CI (.github/workflows/ci.yml) efter typecheck. => 'lägg till språk'
  blir: kör vakten, den listar exakt vad som fattas. Aldrig mer leta för hand.
- Prettier-fixade 5 filer från tidigare leveranser så format:check blir grön.
- submit-recipe fri-text-payload + firebase dev-stubs markerade // i18n-ignore.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude
2026-08-21 01:50:08 +00:00
parent cadaa1a41f
commit 2d59a3e636
36 changed files with 1123 additions and 288 deletions
+5 -2
View File
@@ -74,7 +74,7 @@ const CATS: ReadonlyArray<{
{ key: "dinner", labelKey: "myday.mealType.dinner", mealType: "dinner", glyph: "🍽️" },
{ key: "snack", labelKey: "myday.mealType.snack", mealType: "snack", glyph: "🍎" },
{ key: "dessert", labelKey: "myday.mealType.dessert", mealType: "dessert", glyph: "🍰" },
{ key: "baking", labelKey: "cat.baking", label: "Baka", tag: "baking", ctxMeal: "dessert", glyph: "🥐" },
{ key: "baking", labelKey: "cat.baking", tag: "baking", ctxMeal: "dessert", glyph: "🥐" },
];
const errText = (e: unknown): string | undefined =>
@@ -249,7 +249,10 @@ export default function WhatToEatScreen() {
{/* "Menade du …?" stavningsförslag när sök gav noll träffar. */}
{allRecs.length === 0 && submittedSearch && query.data.context.searchSuggestion && (
<Pressable onPress={() => submitSearch(query.data!.context.searchSuggestion!)}>
<Tag label={t("wte.didYouMean", { suggestion: query.data.context.searchSuggestion })} tone="accent" />
<Tag
label={t("wte.didYouMean", { suggestion: query.data.context.searchSuggestion })}
tone="accent"
/>
</Pressable>
)}
+17 -8
View File
@@ -92,10 +92,7 @@ export default function PlanScreen() {
}, []);
// Veckan börjar ALLTID på måndag (månsön), oavsett vilken dag det är idag.
const thisMonday = useMemo(() => mondayOf(todayMidnight), [todayMidnight]);
const weekStart = useMemo(
() => addDays(thisMonday, weekOffset * 7),
[thisMonday, weekOffset],
);
const weekStart = useMemo(() => addDays(thisMonday, weekOffset * 7), [thisMonday, weekOffset]);
const weekStartKey = toKey(weekStart);
const weekDays = useMemo(
() => Array.from({ length: 7 }, (_, i) => addDays(weekStart, i)),
@@ -116,7 +113,11 @@ export default function PlanScreen() {
const toggleSlot = (dayIdx: number, meal: string) =>
setMealSlots((prev) =>
prev.map((meals, i) =>
i !== dayIdx ? meals : meals.includes(meal) ? meals.filter((m) => m !== meal) : [...meals, meal],
i !== dayIdx
? meals
: meals.includes(meal)
? meals.filter((m) => m !== meal)
: [...meals, meal],
),
);
@@ -362,10 +363,18 @@ export default function PlanScreen() {
{weekDays.map((d, i) => (
<Row
key={toKey(d)}
style={{ justifyContent: "space-between", alignItems: "center", marginVertical: 2 }}
style={{
justifyContent: "space-between",
alignItems: "center",
marginVertical: 2,
}}
>
<Small>
{cap(new Intl.DateTimeFormat(locale, { weekday: "short", day: "numeric" }).format(d))}
{cap(
new Intl.DateTimeFormat(locale, { weekday: "short", day: "numeric" }).format(
d,
),
)}
</Small>
<Row>
{(["breakfast", "lunch", "dinner"] as const).map((meal) => (
@@ -509,7 +518,7 @@ export default function PlanScreen() {
</>
) : null}
<Button
label="Byt rätt"
label={t("plan.swapDish")}
variant="secondary"
onPress={() => {
const e = actionEntry;
+6 -10
View File
@@ -48,19 +48,15 @@ const MAX_PHOTOS = 6; // matchar createScanInputSchema.imageCount.max
function askAddMore(count: number, maxCount: number): Promise<boolean> {
return new Promise((resolve) => {
if (count >= maxCount) {
Alert.alert("Klart", `Max ${maxCount} bilder den här skanningen analyserar.`, [
Alert.alert(t("scan.maxTitle"), t("scan.maxBody", { max: maxCount }), [
{ text: "OK", onPress: () => resolve(false) },
]);
return;
}
Alert.alert(
`Bild ${count} tagen`,
"Ta gärna fler vinklar av samma plats appen listar varje vara en gång. Varje bild räknas som en skanning.",
[
{ text: "Ta en till", onPress: () => resolve(true) },
{ text: `Analysera (${count})`, style: "default", onPress: () => resolve(false) },
],
);
Alert.alert(t("scan.photoTaken", { count }), t("scan.multiAngleHint"), [
{ text: t("scan.takeAnother"), onPress: () => resolve(true) },
{ text: t("scan.analyzeCount", { count }), style: "default", onPress: () => resolve(false) },
]);
});
}
@@ -220,7 +216,7 @@ export default function ScanScreen() {
<Small> {t("scan.tips.shelf")}</Small>
<Small> {t("scan.tips.light")}</Small>
<Small> {t("scan.tips.move")}</Small>
<Small> Kyl/frys/skafferi: ta flera bilder i samma skanning appen slår ihop dubbletter.</Small>
<Small> {t("scan.tips.merge")}</Small>
</Card>
</Screen>
);