Files
Cibello-app/apps/mobile/src/app/_layout.tsx
T
Claude 2d59a3e636 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>
2026-08-21 01:50:08 +00:00

132 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useState } from "react";
import { Stack } from "expo-router";
import { StatusBar } from "expo-status-bar";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { QueryClient } from "@tanstack/react-query";
import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client";
import { createAsyncStoragePersister } from "@tanstack/query-async-storage-persister";
import { useAuth } from "@/lib/auth";
import { AnalyticsProvider } from "@/lib/analytics";
import { BRAND } from "@/lib/brand";
import { t, useI18nVersion } from "@/lib/i18n";
import { colors } from "@/lib/theme";
import { LoadingView } from "@/components/ui";
/**
* Rotlayout: React Query med AsyncStorage-persistens för Smart Cache/offline
* (spec §44: cachelagrade recept, lager och inköpslista fungerar offline).
*/
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 30_000,
gcTime: 7 * 24 * 3600_000, // behåll cache en vecka för offline-läge
retry: 1,
},
},
});
const persister = createAsyncStoragePersister({
storage: AsyncStorage,
key: `${BRAND.slug}-query-cache-v2`,
});
export default function RootLayout() {
const hydrate = useAuth((s) => s.hydrate);
const hydrated = useAuth((s) => s.hydrated);
const [ready, setReady] = useState(false);
// Språkbyte utan omstart (i18n M4): versionen bumpar vid setLocale och
// key={} re-monterar hela trädet så att alla t()-strängar byts direkt.
const i18nVersion = useI18nVersion();
useEffect(() => {
void hydrate().then(() => setReady(true));
}, [hydrate]);
if (!hydrated || !ready) return <LoadingView />;
return (
<PersistQueryClientProvider
key={`i18n-${i18nVersion}`}
client={queryClient}
persistOptions={{ persister }}
>
<AnalyticsProvider>
<StatusBar style="dark" />
<Stack
screenOptions={{
headerStyle: { backgroundColor: colors.background },
headerTintColor: colors.text,
headerShadowVisible: false,
// Chevron utan textetikett tar bort "tillbaka"-labeln som annars
// blinkar/hoppar när den mäts om under push-övergången.
headerBackButtonDisplayMode: "minimal",
contentStyle: { backgroundColor: colors.background },
fullScreenGestureEnabled: true,
}}
>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="(auth)/login" options={{ headerShown: false }} />
<Stack.Screen name="(auth)/register" options={{ headerShown: false }} />
<Stack.Screen name="(auth)/forgot-password" options={{ headerShown: false }} />
<Stack.Screen name="(auth)/reset-password" options={{ headerShown: false }} />
<Stack.Screen name="(auth)/verify-email" options={{ headerShown: false }} />
<Stack.Screen name="onboarding" options={{ headerShown: false, gestureEnabled: false }} />
<Stack.Screen name="recipe/[id]" options={{ title: "" }} />
<Stack.Screen
name="cooking/[id]"
options={{ title: "", presentation: "fullScreenModal" }}
/>
<Stack.Screen
name="scan-review/[jobId]"
options={{ title: t("scan.review.title"), fullScreenGestureEnabled: false }}
/>
<Stack.Screen
name="swap-meal/[entryId]"
options={{ presentation: "modal", title: t("nav.swap") }}
/>
<Stack.Screen
name="meal-review/[jobId]"
options={{ title: t("scan.meal.title"), fullScreenGestureEnabled: false }}
/>
<Stack.Screen name="reconciliation" options={{ title: t("reconciliation.title") }} />
<Stack.Screen
name="scan-diff-review/[jobId]"
options={{ title: t("scan.review.title"), fullScreenGestureEnabled: false }}
/>
<Stack.Screen
name="shopping"
options={{ title: t("shopping.title"), presentation: "modal" }}
/>
<Stack.Screen
name="meal-boxes"
options={{ title: t("mealbox.title"), presentation: "modal" }}
/>
<Stack.Screen
name="household"
options={{ title: t("home.household"), presentation: "modal" }}
/>
<Stack.Screen name="memory" options={{ title: t("memory.title") }} />
<Stack.Screen name="saved-recipes" options={{ title: t("nav.savedRecipes") }} />
<Stack.Screen name="recipes" options={{ title: t("nav.browseRecipes") }} />
<Stack.Screen name="kitchen" options={{ title: t("nav.kitchen") }} />
<Stack.Screen
name="profile"
options={{ title: t("profile.title"), presentation: "modal" }}
/>
<Stack.Screen
name="paywall"
options={{ title: t("paywall.title"), presentation: "modal" }}
/>
<Stack.Screen
name="log-meal"
options={{ title: t("myday.logMeal"), presentation: "modal" }}
/>
<Stack.Screen name="barcode" options={{ title: t("nav.barcode") }} />
</Stack>
</AnalyticsProvider>
</PersistQueryClientProvider>
);
}