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:
@@ -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>
|
||||
)}
|
||||
|
||||
|
||||
@@ -92,10 +92,7 @@ export default function PlanScreen() {
|
||||
}, []);
|
||||
// Veckan börjar ALLTID på måndag (mån–sö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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user