From 9be9b2987bd91bac9c6451b27e6b76258eafc02a Mon Sep 17 00:00:00 2001 From: knuthtimo-lab Date: Wed, 8 Jul 2026 22:07:59 +0200 Subject: [PATCH] TTikotok --- app.json | 2 +- app/(tabs)/profile.tsx | 62 ++++++- app/_layout.tsx | 5 +- app/profile/billing.tsx | 23 +-- app/scanner.tsx | 61 +++---- context/AppContext.tsx | 14 ++ package-lock.json | 4 +- package.json | 2 +- server/index.js | 72 ++++---- utils/translations.ts | 372 ++++++++++++++++++++-------------------- 10 files changed, 349 insertions(+), 268 deletions(-) diff --git a/app.json b/app.json index 15f76c5..72f2484 100644 --- a/app.json +++ b/app.json @@ -2,7 +2,7 @@ "expo": { "name": "GreenLens", "slug": "greenlens", - "version": "2.3.0", + "version": "2.4.0", "orientation": "portrait", "icon": "./assets/icon.png", "userInterfaceStyle": "automatic", diff --git a/app/(tabs)/profile.tsx b/app/(tabs)/profile.tsx index 1b61271..d5eadf5 100644 --- a/app/(tabs)/profile.tsx +++ b/app/(tabs)/profile.tsx @@ -54,6 +54,10 @@ const getProfileCopy = (language: Language) => { logoutConfirmTitle: 'Abmelden?', logoutConfirmMessage: 'Möchtest du dich wirklich abmelden?', logoutConfirmBtn: 'Abmelden', + guestTitle: 'Du bist noch nicht angemeldet', + guestBody: 'Melde dich an, um Pflanzen zu scannen und deine Sammlung zu sichern.', + guestSignIn: 'Anmelden', + guestSignUp: 'Kostenlos registrieren', }; } if (language === 'es') { @@ -79,6 +83,10 @@ const getProfileCopy = (language: Language) => { logoutConfirmTitle: '¿Cerrar sesión?', logoutConfirmMessage: '¿Realmente quieres cerrar sesión?', logoutConfirmBtn: 'Cerrar sesión', + guestTitle: 'Aún no has iniciado sesión', + guestBody: 'Inicia sesión para escanear plantas y guardar tu colección.', + guestSignIn: 'Iniciar sesión', + guestSignUp: 'Registrarse gratis', }; } return { @@ -103,6 +111,10 @@ const getProfileCopy = (language: Language) => { logoutConfirmTitle: 'Sign out?', logoutConfirmMessage: 'Do you really want to sign out?', logoutConfirmBtn: 'Sign Out', + guestTitle: "You're not signed in yet", + guestBody: 'Sign in to scan plants and keep your collection safe.', + guestSignIn: 'Log in', + guestSignUp: 'Sign up for free', }; }; @@ -118,6 +130,7 @@ export default function ProfileScreen() { profileName, setProfileName, signOut, + session, } = useApp(); const router = useRouter(); @@ -199,7 +212,7 @@ export default function ProfileScreen() { const menuItems = [ { label: copy.menuSettings, icon: 'settings-outline', route: '/profile/preferences' as any }, { label: copy.menuBilling, icon: 'card-outline', route: '/profile/billing' as any }, - { label: copy.menuData, icon: 'shield-checkmark-outline', route: '/profile/data' as any }, + ...(session ? [{ label: copy.menuData, icon: 'shield-checkmark-outline', route: '/profile/data' as any }] : []), ]; return ( @@ -228,6 +241,26 @@ export default function ProfileScreen() { + {!session ? ( + + {copy.guestTitle} + {copy.guestBody} + router.push('/auth/signup')} + activeOpacity={0.85} + > + {copy.guestSignUp} + + router.push('/auth/login')} + activeOpacity={0.85} + > + {copy.guestSignIn} + + + ) : ( {copy.account} @@ -315,6 +348,7 @@ export default function ProfileScreen() { + )} {menuItems.map((item, idx) => ( @@ -336,6 +370,7 @@ export default function ProfileScreen() { ))} {/* Logout */} + {session && ( {copy.logout} + )} @@ -387,6 +423,30 @@ const styles = StyleSheet.create({ accountCard: { marginBottom: 14, }, + guestBody: { + fontSize: 14, + lineHeight: 20, + marginBottom: 16, + }, + guestPrimaryBtn: { + height: 50, + borderRadius: 14, + alignItems: 'center', + justifyContent: 'center', + marginBottom: 8, + }, + guestPrimaryText: { + fontSize: 15, + fontWeight: '800', + }, + guestSecondaryBtn: { + alignItems: 'center', + paddingVertical: 10, + }, + guestSecondaryText: { + fontSize: 14, + fontWeight: '700', + }, logoutBtn: { flexDirection: 'row', alignItems: 'center', diff --git a/app/_layout.tsx b/app/_layout.tsx index d11595c..f975e1f 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -96,6 +96,7 @@ function RootLayoutInner() { colorPalette, signOut, session, + hasCompletedOnboarding, isInitializing, isLoadingPlants, } = useApp(); @@ -124,7 +125,9 @@ function RootLayoutInner() { let content = null; if (isAppReady) { - if (!session) { + // Guests who finished onboarding (e.g. dismissed the soft paywall) browse the + // full app; paid/credit actions are gated at the point of use instead. + if (!session && !hasCompletedOnboarding) { // Only redirect if we are not already on an auth-related page or the scanner if (!isAllowedWithoutSession) { content = ; diff --git a/app/profile/billing.tsx b/app/profile/billing.tsx index d98beef..11b2ce8 100644 --- a/app/profile/billing.tsx +++ b/app/profile/billing.tsx @@ -24,7 +24,7 @@ type SubscriptionProductId = 'monthly_pro' | 'yearly_pro'; type TopupProductId = Extract; type SubscriptionPackages = Partial>; type TopupProducts = Partial>; -type PaywallPlanId = 'weekly' | 'yearly'; +type PaywallPlanId = 'monthly' | 'yearly'; const PAYWALL_BACKGROUND = require('../../assets/paywall_scan_background.png'); @@ -159,7 +159,7 @@ const getBillingCopy = (language: Language) => { planCardBody: 'Unbegrenzte KI-Scans, Gesundheitsdiagnose, 7-Tage-Rettungspläne, 100 Credits/Monat', planCardPriceTrial: (price: string) => `7 Tage gratis, dann ${price}/Jahr`, planCardPriceMonthly: (price: string) => `${price}/Monat`, - trialToggleLabel: 'Gratis-Test aktiviert', + trialToggleLabel: '7 Tage gratis testen', dueTodayTrial: 'Fällig heute — 7 Tage gratis', dueTodayAmount: '0,00 €', dueLater: (date: string) => `Fällig am ${date}`, @@ -224,7 +224,7 @@ const getBillingCopy = (language: Language) => { planCardBody: 'Escaneos IA ilimitados, diagnóstico de salud, planes de rescate de 7 días, 100 créditos/mes', planCardPriceTrial: (price: string) => `7 días gratis, luego ${price}/año`, planCardPriceMonthly: (price: string) => `${price}/mes`, - trialToggleLabel: 'Prueba gratis activada', + trialToggleLabel: 'Probar 7 días gratis', dueTodayTrial: 'Hoy — 7 días gratis', dueTodayAmount: '0,00 €', dueLater: (date: string) => `El ${date}`, @@ -289,7 +289,7 @@ const getBillingCopy = (language: Language) => { planCardBody: 'Unlimited AI scans, health diagnosis, 7-day rescue plans, 100 credits/month', planCardPriceTrial: (price: string) => `Free for 7 days, then ${price}/year`, planCardPriceMonthly: (price: string) => `${price}/month`, - trialToggleLabel: 'Free Trial Enabled', + trialToggleLabel: 'Try 7 days free', dueTodayTrial: 'Due today — 7 days free', dueTodayAmount: '€0.00', dueLater: (date: string) => `Due ${date}`, @@ -308,7 +308,7 @@ export default function BillingScreen() { const params = useLocalSearchParams<{ view?: string; context?: string }>(); const paywallRequested = params.view === 'paywall'; const onboardingContext = params.context === 'onboarding'; - const { isDarkMode, language, billingSummary, isLoadingBilling, simulatePurchase, simulateWebhookEvent, syncRevenueCatState, colorPalette, session } = useApp(); + const { isDarkMode, language, billingSummary, isLoadingBilling, simulatePurchase, simulateWebhookEvent, syncRevenueCatState, colorPalette, session, hasCompletedOnboarding, markOnboardingCompleted } = useApp(); const colors = useColors(isDarkMode, colorPalette); const posthog = useSafeAnalytics(); const copy = getBillingCopy(language); @@ -320,7 +320,8 @@ export default function BillingScreen() { const [storeError, setStoreError] = useState(null); const [subscriptionPackages, setSubscriptionPackages] = useState({}); const [topupProducts, setTopupProducts] = useState({}); - const [selectedPaywallPlan, setSelectedPaywallPlan] = useState('yearly'); + // Monthly is the default; the 7-day trial (yearly plan) must be opted into via the toggle. + const [selectedPaywallPlan, setSelectedPaywallPlan] = useState('monthly'); // Cancel Flow State const [cancelStep, setCancelStep] = useState<'none' | 'survey' | 'offer'>('none'); @@ -426,7 +427,9 @@ export default function BillingScreen() { if (showPaywallPlans) { posthog.capture('paywall_dismissed', { context: onboardingContext ? 'onboarding' : 'in_app' }); if (onboardingContext) { - router.replace('/auth/signup'); + // Guests continue into the app; paid actions are gated at the point of use. + markOnboardingCompleted(); + router.replace('/(tabs)'); return; } if (session) { @@ -434,7 +437,7 @@ export default function BillingScreen() { else router.replace('/(tabs)'); return; } - router.replace('/onboarding'); + router.replace(hasCompletedOnboarding ? '/(tabs)' : '/onboarding'); return; } if (router.canGoBack()) { @@ -442,7 +445,7 @@ export default function BillingScreen() { return; } router.replace('/(tabs)'); - }, [router, showPaywallPlans, onboardingContext, session, posthog]); + }, [router, showPaywallPlans, onboardingContext, session, posthog, hasCompletedOnboarding, markOnboardingCompleted]); const postPurchaseRoute = onboardingContext ? '/auth/signup' : '/(tabs)'; @@ -653,7 +656,7 @@ export default function BillingScreen() { {copy.trialToggleLabel} setSelectedPaywallPlan(next ? 'yearly' : 'weekly')} + onValueChange={(next) => setSelectedPaywallPlan(next ? 'yearly' : 'monthly')} trackColor={{ true: colors.primary, false: colors.border }} thumbColor="#FFFFFF" /> diff --git a/app/scanner.tsx b/app/scanner.tsx index 5a07e7e..7aea806 100644 --- a/app/scanner.tsx +++ b/app/scanner.tsx @@ -19,13 +19,13 @@ import { PlantRecognitionService } from '../services/plantRecognitionService'; import { IdentificationResult } from '../types'; import { ResultCard } from '../components/ResultCard'; import { backendApiClient, isInsufficientCreditsError, isNetworkError, isTimeoutError } from '../services/backend/backendApiClient'; -import { isBackendApiError } from '../services/backend/contracts'; -import { createIdempotencyKey } from '../utils/idempotency'; -import { AuthService } from '../services/authService'; -import { consumeSharedImageUri, SHARE_INTENT_KEY } from '../utils/shareHandoff'; -import { OutOfCreditsSheet } from '../components/OutOfCreditsSheet'; +import { isBackendApiError } from '../services/backend/contracts'; +import { createIdempotencyKey } from '../utils/idempotency'; +import { AuthService } from '../services/authService'; +import { consumeSharedImageUri, SHARE_INTENT_KEY } from '../utils/shareHandoff'; +import { OutOfCreditsSheet } from '../components/OutOfCreditsSheet'; -const DEMO_SCAN_LIMIT = 3; +const DEMO_SCAN_LIMIT = 1; const getBillingCopy = (language: 'de' | 'en' | 'es') => { if (language === 'de') { @@ -51,8 +51,8 @@ const getBillingCopy = (language: 'de' | 'en' | 'es') => { demoTitle: 'Rettungsplan bereit', demoMessage: 'Wir haben mögliche Ursachen erkannt. Schalte die vollständige KI-Diagnose und deinen 7-Tage-Rettungsplan frei.', demoNoCreditsTitle: 'Demo-Scans aufgebraucht', - demoNoCreditsMessage: 'Du hast deine 3 kostenlosen Demo-Scans auf diesem Gerät genutzt. Starte Pro, um weiter Pflanzen zu scannen.', - demoCreditsRemaining: (count: number) => `${count} Demo-Scans übrig`, + demoNoCreditsMessage: 'Du hast deinen kostenlosen Demo-Scan auf diesem Gerät genutzt. Starte Pro, um weiter Pflanzen zu scannen.', + demoCreditsRemaining: (count: number) => (count === 1 ? '1 Demo-Scan übrig' : `${count} Demo-Scans übrig`), creditsRemaining: (count: number) => `${count} Scans übrig`, appleCta: 'Mit Apple fortfahren', emailCta: 'Mit E-Mail fortfahren', @@ -83,8 +83,8 @@ const getBillingCopy = (language: 'de' | 'en' | 'es') => { demoTitle: 'Plan de rescate listo', demoMessage: 'Detectamos posibles causas. Desbloquea el diagnóstico completo con IA y tu plan de rescate de 7 días.', demoNoCreditsTitle: 'Escaneos demo agotados', - demoNoCreditsMessage: 'Ya usaste tus 3 escaneos demo gratuitos en este dispositivo. Inicia Pro para seguir escaneando plantas.', - demoCreditsRemaining: (count: number) => `${count} escaneos demo restantes`, + demoNoCreditsMessage: 'Ya usaste tu escaneo demo gratuito en este dispositivo. Inicia Pro para seguir escaneando plantas.', + demoCreditsRemaining: (count: number) => (count === 1 ? '1 escaneo demo restante' : `${count} escaneos demo restantes`), creditsRemaining: (count: number) => `${count} escaneos restantes`, appleCta: 'Continuar con Apple', emailCta: 'Continuar con email', @@ -114,8 +114,8 @@ const getBillingCopy = (language: 'de' | 'en' | 'es') => { demoTitle: 'Rescue plan ready', demoMessage: 'We found possible causes. Unlock the full AI diagnosis and your 7-day rescue plan.', demoNoCreditsTitle: 'Demo scans used', - demoNoCreditsMessage: 'You used your 3 free demo scans on this device. Start Pro to keep scanning plants.', - demoCreditsRemaining: (count: number) => `${count} demo scans left`, + demoNoCreditsMessage: 'You used your free demo scan on this device. Start Pro to keep scanning plants.', + demoCreditsRemaining: (count: number) => (count === 1 ? '1 demo scan left' : `${count} demo scans left`), creditsRemaining: (count: number) => `${count} scans left`, appleCta: 'Continue with Apple', emailCta: 'Continue with email', @@ -142,6 +142,7 @@ export default function ScannerScreen() { setPendingPlant, guestScanCount, incrementGuestScanCount, + hasCompletedOnboarding, } = useApp(); const colors = useColors(isDarkMode, colorPalette); const router = useRouter(); @@ -160,7 +161,7 @@ export default function ScannerScreen() { : params.sharedImageKey; const hasActiveEntitlement = billingSummary?.entitlement?.plan === 'pro' && billingSummary?.entitlement?.status === 'active'; - const isDemoMode = !session; // guests get limited AI demo scans; signed-in users burn real credits + const isDemoMode = !session; // guests get limited AI demo scans; signed-in users burn real credits const availableCredits = billingSummary?.credits.available ?? 0; const demoScansRemaining = Math.max(0, DEMO_SCAN_LIMIT - guestScanCount); @@ -293,22 +294,22 @@ export default function ScannerScreen() { }, 150); try { - if (isDemoMode) { - posthog.capture('demo_scan_started', { - authenticated: Boolean(session), - scan_type: isHealthMode ? 'health_check' : 'identification', - demo_scans_used: guestScanCount, - demo_scans_remaining: demoScansRemaining, - }); - const demoResult = await PlantRecognitionService.identify(imageUri, language, { - idempotencyKey: createIdempotencyKey('demo-scan-plant'), - }); - setAnalysisProgress(100); - await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); - await new Promise(resolve => setTimeout(resolve, 350)); - incrementGuestScanCount(); - setAnalysisResult(demoResult); - posthog.capture('demo_scan_completed', { + if (isDemoMode) { + posthog.capture('demo_scan_started', { + authenticated: Boolean(session), + scan_type: isHealthMode ? 'health_check' : 'identification', + demo_scans_used: guestScanCount, + demo_scans_remaining: demoScansRemaining, + }); + const demoResult = await PlantRecognitionService.identify(imageUri, language, { + idempotencyKey: createIdempotencyKey('demo-scan-plant'), + }); + setAnalysisProgress(100); + await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + await new Promise(resolve => setTimeout(resolve, 350)); + incrementGuestScanCount(); + setAnalysisResult(demoResult); + posthog.capture('demo_scan_completed', { authenticated: Boolean(session), latency_ms: Date.now() - startTime, demo_scans_used_after: guestScanCount + 1, @@ -600,7 +601,7 @@ export default function ScannerScreen() { router.back(); return; } - router.replace('/onboarding'); + router.replace(hasCompletedOnboarding ? '/(tabs)' : '/onboarding'); }; const controlsPaddingBottom = Math.max(20, insets.bottom + 10); diff --git a/context/AppContext.tsx b/context/AppContext.tsx index 2d302e1..ffad074 100644 --- a/context/AppContext.tsx +++ b/context/AppContext.tsx @@ -63,6 +63,8 @@ interface AppState { getPendingPlant: () => { result: IdentificationResult; imageUri: string } | null; guestScanCount: number; incrementGuestScanCount: () => void; + hasCompletedOnboarding: boolean; + markOnboardingCompleted: () => void; } const AppContext = createContext(null); @@ -153,6 +155,7 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children const [profileImageUri, setProfileImageUri] = useState(null); const [pendingPlant, setPendingPlantState] = useState<{ result: IdentificationResult; imageUri: string } | null>(null); const [guestScanCount, setGuestScanCount] = useState(0); + const [hasCompletedOnboarding, setHasCompletedOnboarding] = useState(false); const [isInitializing, setIsInitializing] = useState(true); const [isLoadingPlants, setIsLoadingPlants] = useState(true); const [billingSummary, setBillingSummary] = useState(null); @@ -372,6 +375,10 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children setGuestScanCount(parseInt(savedCount, 10) || 0); } + if (AppMetaDb.get('onboarding_completed') === '1') { + setHasCompletedOnboarding(true); + } + const s = await AuthService.getSession(); if (!s) { resetStateForSignedOutUser(); @@ -574,6 +581,11 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children }); }, []); + const markOnboardingCompleted = useCallback(() => { + AppMetaDb.set('onboarding_completed', '1'); + setHasCompletedOnboarding(true); + }, []); + return ( = ({ children getPendingPlant, guestScanCount, incrementGuestScanCount, + hasCompletedOnboarding, + markOnboardingCompleted, }}> {children} diff --git a/package-lock.json b/package-lock.json index def1fc8..66acacf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "greenlens", - "version": "2.3.0", + "version": "2.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "greenlens", - "version": "2.3.0", + "version": "2.4.0", "hasInstallScript": true, "dependencies": { "@expo/vector-icons": "^15.0.3", diff --git a/package.json b/package.json index e2a694b..cb8ae41 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "greenlens", - "version": "2.3.0", + "version": "2.4.0", "main": "expo-router/entry", "private": true, "scripts": { diff --git a/server/index.js b/server/index.js index 1d188a5..e963681 100644 --- a/server/index.js +++ b/server/index.js @@ -204,11 +204,11 @@ const resolveIdempotencyKey = (request) => { return ''; }; -const ensureNotGuest = (userId, requiredCredits) => { - // Guests may use the limited pre-auth demo identification scan. Other - // endpoints must not consume credits from the shared 'guest' account. - if (isGuest(userId)) { - const error = new Error('Sign in to use scan credits.'); +const ensureNotGuest = (userId, requiredCredits) => { + // Guests may use the limited pre-auth demo identification scan. Other + // endpoints must not consume credits from the shared 'guest' account. + if (isGuest(userId)) { + const error = new Error('Sign in to use scan credits.'); error.code = 'INSUFFICIENT_CREDITS'; error.status = 402; error.metadata = { required: requiredCredits, available: 0 }; @@ -726,20 +726,20 @@ app.post('/v1/scan', async (request, response) => { let modelUsed = null; let modelFallbackCount = 0; - const [accountSnapshot, catalogEntries] = await Promise.all([ - getAccountSnapshot(db, userId), - getCachedCatalogEntries(db), - ]); - if (isGuest(userId)) { - modelPath.push('guest-demo-no-credit'); - } else { - creditsCharged += await consumeCreditsWithIdempotency( - db, - userId, - chargeKey('scan-primary', userId, idempotencyKey), - SCAN_PRIMARY_COST, - ); - } + const [accountSnapshot, catalogEntries] = await Promise.all([ + getAccountSnapshot(db, userId), + getCachedCatalogEntries(db), + ]); + if (isGuest(userId)) { + modelPath.push('guest-demo-no-credit'); + } else { + creditsCharged += await consumeCreditsWithIdempotency( + db, + userId, + chargeKey('scan-primary', userId, idempotencyKey), + SCAN_PRIMARY_COST, + ); + } // Free tier gets the same model quality; quantity (3 credits/month) is the differentiator. const scanPlan = 'pro'; @@ -767,14 +767,14 @@ app.post('/v1/scan', async (request, response) => { usedOpenAi = true; modelUsed = openAiPrimary.modelUsed || modelUsed; modelPath.push('openai-primary'); - if (grounded.grounded) modelPath.push('catalog-grounded-primary'); - } else { - if (isGuest(userId)) { - const error = new Error('AI demo scan failed. Please try again with a clearer plant photo.'); - error.code = 'PROVIDER_ERROR'; - error.status = 502; - throw error; - } + if (grounded.grounded) modelPath.push('catalog-grounded-primary'); + } else { + if (isGuest(userId)) { + const error = new Error('AI demo scan failed. Please try again with a clearer plant photo.'); + error.code = 'PROVIDER_ERROR'; + error.status = 502; + throw error; + } console.warn(`OpenAI primary identification returned null for user ${userId} — using catalog fallback.`, { attemptedModels: openAiPrimary?.attemptedModels, plant: result?.name, @@ -782,14 +782,14 @@ app.post('/v1/scan', async (request, response) => { modelPath.push('openai-primary-failed'); modelPath.push('catalog-primary-fallback'); } - } else { - if (isGuest(userId)) { - const error = new Error('AI demo scan is unavailable. Please configure OpenAI before enabling guest demo scans.'); - error.code = 'PROVIDER_ERROR'; - error.status = 502; - throw error; - } - console.log(`OpenAI not configured, using catalog fallback for user ${userId}`); + } else { + if (isGuest(userId)) { + const error = new Error('AI demo scan is unavailable. Please configure OpenAI before enabling guest demo scans.'); + error.code = 'PROVIDER_ERROR'; + error.status = 502; + throw error; + } + console.log(`OpenAI not configured, using catalog fallback for user ${userId}`); modelPath.push('openai-not-configured'); modelPath.push('catalog-primary-fallback'); } @@ -1219,7 +1219,7 @@ app.get('/api/tiktok/connect', (request, response) => { const authUrl = new URL('https://www.tiktok.com/v2/auth/authorize/'); authUrl.searchParams.set('client_key', clientKey); - authUrl.searchParams.set('scope', 'user.info.basic,video.publish'); + authUrl.searchParams.set('scope', 'user.info.basic,video.upload,video.publish'); authUrl.searchParams.set('response_type', 'code'); authUrl.searchParams.set('redirect_uri', redirectUri); authUrl.searchParams.set('state', oauthState); diff --git a/utils/translations.ts b/utils/translations.ts index 32f0a9e..f536a06 100644 --- a/utils/translations.ts +++ b/utils/translations.ts @@ -29,47 +29,47 @@ export const translations = { // Info noPlants: 'Noch keine Pflanzen.', - nextStepsTitle: 'Deine nächsten Schritte', - stepScan: 'Erste Pflanze scannen', - stepReminder: 'Smart Reminders aktivieren', - stepLexicon: 'Pflanzenlexikon erkunden', - stepTheme: 'App anpassen', - stepCollection: '3 Pflanzen speichern', - onboardingChecklistTitle: 'Dein Start in GreenLens', - onboardingChecklistIntro: 'Onboarding', - onboardingChecklistProgress: '{0} von {1} erledigt', - onboardingChecklistNextLabel: 'Als nächstes: {0}', - onboardingChecklistDone: 'Du hast die wichtigsten Startschritte abgeschlossen.', - customizeOnboardingTitle: 'Mach GreenLens zu deiner App', - customizeOnboardingSubtitle: 'Wähle Look, Farben und Sprache jetzt kurz aus. Das kostet kaum Zeit, erhöht aber die Bindung.', - customizeOnboardingPreview: 'Live-Vorschau', - customizeOnboardingContinue: 'Fertig und weiter', - customizeOnboardingSkip: 'Später', - sourceOnboardingTitle: 'Wie hast du GreenLens gefunden?', - sourceOnboardingSubtitle: 'Eine kurze Antwort hilft uns zu verstehen, welche Kanäle wirklich funktionieren.', - sourceOnboardingContinue: 'Weiter', - sourceOnboardingSkip: 'Überspringen', - sourceOptionAppStore: 'App Store / Play Store', - sourceOptionInstagram: 'Instagram', - sourceOptionTikTok: 'TikTok', - sourceOptionFriend: 'Freunde oder Familie', - sourceOptionSearch: 'Google oder Suche', - sourceOptionOther: 'Etwas anderes', - goalOnboardingTitle: 'Was willst du zuerst erreichen?', - goalOnboardingSubtitle: 'So können wir deinen Einstieg besser ausrichten und die richtigen nächsten Schritte zeigen.', - goalOnboardingContinue: 'Weiter', - goalOnboardingSkip: 'Überspringen', - goalOptionIdentify: 'Pflanzen schnell erkennen', - goalOptionCare: 'Pflanzen besser pflegen', - goalOptionCollection: 'Meine Sammlung aufbauen', - goalOptionLearn: 'Mehr über Pflanzen lernen', - experienceOnboardingTitle: 'Wie fit bist du bei Pflanzen?', - experienceOnboardingSubtitle: 'Damit Tipps und Sprache besser zu deinem Level passen.', - experienceOnboardingContinue: 'Fertig', - experienceOnboardingSkip: 'Überspringen', - experienceOptionBeginner: 'Anfänger:in', - experienceOptionIntermediate: 'Schon etwas Erfahrung', - experienceOptionAdvanced: 'Sehr erfahren', + nextStepsTitle: 'Deine nächsten Schritte', + stepScan: 'Erste Pflanze scannen', + stepReminder: 'Smart Reminders aktivieren', + stepLexicon: 'Pflanzenlexikon erkunden', + stepTheme: 'App anpassen', + stepCollection: '3 Pflanzen speichern', + onboardingChecklistTitle: 'Dein Start in GreenLens', + onboardingChecklistIntro: 'Onboarding', + onboardingChecklistProgress: '{0} von {1} erledigt', + onboardingChecklistNextLabel: 'Als nächstes: {0}', + onboardingChecklistDone: 'Du hast die wichtigsten Startschritte abgeschlossen.', + customizeOnboardingTitle: 'Mach GreenLens zu deiner App', + customizeOnboardingSubtitle: 'Wähle Look, Farben und Sprache jetzt kurz aus. Das kostet kaum Zeit, erhöht aber die Bindung.', + customizeOnboardingPreview: 'Live-Vorschau', + customizeOnboardingContinue: 'Fertig und weiter', + customizeOnboardingSkip: 'Später', + sourceOnboardingTitle: 'Wie hast du GreenLens gefunden?', + sourceOnboardingSubtitle: 'Eine kurze Antwort hilft uns zu verstehen, welche Kanäle wirklich funktionieren.', + sourceOnboardingContinue: 'Weiter', + sourceOnboardingSkip: 'Überspringen', + sourceOptionAppStore: 'App Store / Play Store', + sourceOptionInstagram: 'Instagram', + sourceOptionTikTok: 'TikTok', + sourceOptionFriend: 'Freunde oder Familie', + sourceOptionSearch: 'Google oder Suche', + sourceOptionOther: 'Etwas anderes', + goalOnboardingTitle: 'Was willst du zuerst erreichen?', + goalOnboardingSubtitle: 'So können wir deinen Einstieg besser ausrichten und die richtigen nächsten Schritte zeigen.', + goalOnboardingContinue: 'Weiter', + goalOnboardingSkip: 'Überspringen', + goalOptionIdentify: 'Pflanzen schnell erkennen', + goalOptionCare: 'Pflanzen besser pflegen', + goalOptionCollection: 'Meine Sammlung aufbauen', + goalOptionLearn: 'Mehr über Pflanzen lernen', + experienceOnboardingTitle: 'Wie fit bist du bei Pflanzen?', + experienceOnboardingSubtitle: 'Damit Tipps und Sprache besser zu deinem Level passen.', + experienceOnboardingContinue: 'Fertig', + experienceOnboardingSkip: 'Überspringen', + experienceOptionBeginner: 'Anfänger:in', + experienceOptionIntermediate: 'Schon etwas Erfahrung', + experienceOptionAdvanced: 'Sehr erfahren', // Filters allGood: 'Alles gut', @@ -226,10 +226,10 @@ export const translations = { tourFabDesc: "Tippe hier um eine Pflanze zu fotografieren — die KI erkennt sie sofort.", tourSearchTitle: "🔍 Pflanzenlexikon", tourSearchDesc: "Durchsuche tausende Pflanzen oder lass die KI nach der perfekten suchen.", - tourProfileTitle: "👤 Dein Profil", - tourProfileDesc: "Passe Design, Sprache und Benachrichtigungen ganz nach deinem Geschmack an.", - tourChecklistTitle: "✅ Dein Onboarding", - tourChecklistDesc: "Hier siehst du deine nächsten Schritte und kommst schneller zu echtem Nutzen in der App.", + tourProfileTitle: "👤 Dein Profil", + tourProfileDesc: "Passe Design, Sprache und Benachrichtigungen ganz nach deinem Geschmack an.", + tourChecklistTitle: "✅ Dein Onboarding", + tourChecklistDesc: "Hier siehst du deine nächsten Schritte und kommst schneller zu echtem Nutzen in der App.", coachSkip: "Ueberspringen", coachNext: "Weiter", coachDone: "Fertig", @@ -247,23 +247,23 @@ export const translations = { onboardingFeatureScan: "Pflanzen scannen & erkennen", onboardingFeatureReminder: "Gießerinnerungen & Pflege", onboardingFeatureLexicon: "Digitales Pflanzen-Lexikon", - onboardingScanBtn: "Pflanze scannen", - onboardingRegister: "Registrieren", - onboardingLogin: "Anmelden", - onboardingDisclaimer: "Deine Daten bleiben privat und lokal auf deinem Gerät.", - welcomeHeadline: "Pflanzenpflege\nbeginnt hier", - welcomeSubheadline: "Scanne ein Blatt, erkenne die Pflanze und halte sie gesund.", - welcomeFeatureIdentifyTitle: "KI-Pflanzenerkennung", - welcomeFeatureIdentifyDesc: "Teste bis zu 5 Demo-Scans direkt auf diesem Gerät.", - welcomeFeatureReminderTitle: "Pflegeplan & Erinnerungen", - welcomeFeatureReminderDesc: "Erhalte klare Tipps für Gießen, Licht und Standort.", - welcomeFeatureLibraryTitle: "Pflanzen speichern", - welcomeFeatureLibraryDesc: "Registriere dich, um deine gescannten Pflanzen zu sichern.", - welcomeDemoScan: "Demo-Scan testen", - welcomeSubscriptionPlans: "Abo-Pläne & Preise ansehen", - welcomeLegal: "Abo-Details, Wiederherstellen, Nutzungsbedingungen und Datenschutzrichtlinie werden vor dem Kauf angezeigt.", - - // Auth + onboardingScanBtn: "Pflanze scannen", + onboardingRegister: "Registrieren", + onboardingLogin: "Anmelden", + onboardingDisclaimer: "Deine Daten bleiben privat und lokal auf deinem Gerät.", + welcomeHeadline: "Pflanzenpflege\nbeginnt hier", + welcomeSubheadline: "Scanne ein Blatt, erkenne die Pflanze und halte sie gesund.", + welcomeFeatureIdentifyTitle: "KI-Pflanzenerkennung", + welcomeFeatureIdentifyDesc: "Teste einen kostenlosen Demo-Scan direkt auf diesem Gerät.", + welcomeFeatureReminderTitle: "Pflegeplan & Erinnerungen", + welcomeFeatureReminderDesc: "Erhalte klare Tipps für Gießen, Licht und Standort.", + welcomeFeatureLibraryTitle: "Pflanzen speichern", + welcomeFeatureLibraryDesc: "Registriere dich, um deine gescannten Pflanzen zu sichern.", + welcomeDemoScan: "Demo-Scan testen", + welcomeSubscriptionPlans: "Abo-Pläne & Preise ansehen", + welcomeLegal: "Abo-Details, Wiederherstellen, Nutzungsbedingungen und Datenschutzrichtlinie werden vor dem Kauf angezeigt.", + + // Auth createAccount: "Konto erstellen", welcomeBack: "Willkommen zurück", namePlaceholder: "Dein Name", @@ -313,47 +313,47 @@ export const translations = { paletteSunset: 'Sunset', paletteMono: 'Mono', noPlants: 'No plants yet.', - nextStepsTitle: 'Your next steps', - stepScan: 'Scan first plant', - stepReminder: 'Enable smart reminders', - stepLexicon: 'Explore plant lexicon', - stepTheme: 'Customize app', - stepCollection: 'Save 3 plants', - onboardingChecklistTitle: 'Your GreenLens kickoff', - onboardingChecklistIntro: 'Onboarding', - onboardingChecklistProgress: '{0} of {1} done', - onboardingChecklistNextLabel: 'Next up: {0}', - onboardingChecklistDone: 'You completed the core getting-started steps.', - customizeOnboardingTitle: 'Make GreenLens feel like yours', - customizeOnboardingSubtitle: 'Set your look, colors, and language now. It is quick and makes the app feel more personal.', - customizeOnboardingPreview: 'Live preview', - customizeOnboardingContinue: 'Continue', - customizeOnboardingSkip: 'Maybe later', - sourceOnboardingTitle: 'How did you find GreenLens?', - sourceOnboardingSubtitle: 'One quick answer helps us understand which channels are actually working.', - sourceOnboardingContinue: 'Continue', - sourceOnboardingSkip: 'Skip', - sourceOptionAppStore: 'App Store / Play Store', - sourceOptionInstagram: 'Instagram', - sourceOptionTikTok: 'TikTok', - sourceOptionFriend: 'Friends or family', - sourceOptionSearch: 'Google or search', - sourceOptionOther: 'Something else', - goalOnboardingTitle: 'What do you want to achieve first?', - goalOnboardingSubtitle: 'This helps us tailor your first steps and show the most relevant actions.', - goalOnboardingContinue: 'Continue', - goalOnboardingSkip: 'Skip', - goalOptionIdentify: 'Identify plants quickly', - goalOptionCare: 'Take better care of plants', - goalOptionCollection: 'Build my collection', - goalOptionLearn: 'Learn more about plants', - experienceOnboardingTitle: 'How experienced are you with plants?', - experienceOnboardingSubtitle: 'So tips and wording can better match your level.', - experienceOnboardingContinue: 'Finish', - experienceOnboardingSkip: 'Skip', - experienceOptionBeginner: 'Beginner', - experienceOptionIntermediate: 'Some experience', - experienceOptionAdvanced: 'Very experienced', + nextStepsTitle: 'Your next steps', + stepScan: 'Scan first plant', + stepReminder: 'Enable smart reminders', + stepLexicon: 'Explore plant lexicon', + stepTheme: 'Customize app', + stepCollection: 'Save 3 plants', + onboardingChecklistTitle: 'Your GreenLens kickoff', + onboardingChecklistIntro: 'Onboarding', + onboardingChecklistProgress: '{0} of {1} done', + onboardingChecklistNextLabel: 'Next up: {0}', + onboardingChecklistDone: 'You completed the core getting-started steps.', + customizeOnboardingTitle: 'Make GreenLens feel like yours', + customizeOnboardingSubtitle: 'Set your look, colors, and language now. It is quick and makes the app feel more personal.', + customizeOnboardingPreview: 'Live preview', + customizeOnboardingContinue: 'Continue', + customizeOnboardingSkip: 'Maybe later', + sourceOnboardingTitle: 'How did you find GreenLens?', + sourceOnboardingSubtitle: 'One quick answer helps us understand which channels are actually working.', + sourceOnboardingContinue: 'Continue', + sourceOnboardingSkip: 'Skip', + sourceOptionAppStore: 'App Store / Play Store', + sourceOptionInstagram: 'Instagram', + sourceOptionTikTok: 'TikTok', + sourceOptionFriend: 'Friends or family', + sourceOptionSearch: 'Google or search', + sourceOptionOther: 'Something else', + goalOnboardingTitle: 'What do you want to achieve first?', + goalOnboardingSubtitle: 'This helps us tailor your first steps and show the most relevant actions.', + goalOnboardingContinue: 'Continue', + goalOnboardingSkip: 'Skip', + goalOptionIdentify: 'Identify plants quickly', + goalOptionCare: 'Take better care of plants', + goalOptionCollection: 'Build my collection', + goalOptionLearn: 'Learn more about plants', + experienceOnboardingTitle: 'How experienced are you with plants?', + experienceOnboardingSubtitle: 'So tips and wording can better match your level.', + experienceOnboardingContinue: 'Finish', + experienceOnboardingSkip: 'Skip', + experienceOptionBeginner: 'Beginner', + experienceOptionIntermediate: 'Some experience', + experienceOptionAdvanced: 'Very experienced', allGood: 'All good', toWater: 'To water', searchPlaceholder: 'Search plants...', @@ -498,10 +498,10 @@ registerToSave: "Sign up to save", tourFabDesc: "Tap here to photograph a plant — the AI recognizes it instantly.", tourSearchTitle: "🔍 Plant Encyclopedia", tourSearchDesc: "Search thousands of plants or let the AI find the perfect one.", - tourProfileTitle: "👤 Your Profile", - tourProfileDesc: "Customize design, language, and notifications to your liking.", - tourChecklistTitle: "✅ Your onboarding", - tourChecklistDesc: "This keeps your next actions visible so you reach real value faster.", + tourProfileTitle: "👤 Your Profile", + tourProfileDesc: "Customize design, language, and notifications to your liking.", + tourChecklistTitle: "✅ Your onboarding", + tourChecklistDesc: "This keeps your next actions visible so you reach real value faster.", coachSkip: "Skip", coachNext: "Next", coachDone: "Done", @@ -519,23 +519,23 @@ registerToSave: "Sign up to save", onboardingFeatureScan: "Scan & identify plants", onboardingFeatureReminder: "Watering reminders & care", onboardingFeatureLexicon: "Digital plant encyclopedia", - onboardingScanBtn: "Scan Plant", - onboardingRegister: "Sign Up", - onboardingLogin: "Log In", - onboardingDisclaimer: "Your data stays private and local on your device.", - welcomeHeadline: "Plant care\nstarts here", - welcomeSubheadline: "Scan a leaf, learn the plant, keep it healthy.", - welcomeFeatureIdentifyTitle: "AI plant identification", - welcomeFeatureIdentifyDesc: "Try up to 3 demo scans on this device.", - welcomeFeatureReminderTitle: "Care plan & reminders", - welcomeFeatureReminderDesc: "Get clear guidance for water, light, and placement.", - welcomeFeatureLibraryTitle: "Save your plants", - welcomeFeatureLibraryDesc: "Sign up to keep scanned plants in your collection.", - welcomeDemoScan: "Try Demo Scan", - welcomeSubscriptionPlans: "View Subscription Plans & Pricing", - welcomeLegal: "Subscription details, Restore, Terms of Use, and Privacy Policy are shown before purchase.", - - // Auth + onboardingScanBtn: "Scan Plant", + onboardingRegister: "Sign Up", + onboardingLogin: "Log In", + onboardingDisclaimer: "Your data stays private and local on your device.", + welcomeHeadline: "Plant care\nstarts here", + welcomeSubheadline: "Scan a leaf, learn the plant, keep it healthy.", + welcomeFeatureIdentifyTitle: "AI plant identification", + welcomeFeatureIdentifyDesc: "Try one free demo scan on this device.", + welcomeFeatureReminderTitle: "Care plan & reminders", + welcomeFeatureReminderDesc: "Get clear guidance for water, light, and placement.", + welcomeFeatureLibraryTitle: "Save your plants", + welcomeFeatureLibraryDesc: "Sign up to keep scanned plants in your collection.", + welcomeDemoScan: "Try Demo Scan", + welcomeSubscriptionPlans: "View Subscription Plans & Pricing", + welcomeLegal: "Subscription details, Restore, Terms of Use, and Privacy Policy are shown before purchase.", + + // Auth createAccount: "Create Account", welcomeBack: "Welcome back", namePlaceholder: "Your name", @@ -585,47 +585,47 @@ registerToSave: "Sign up to save", paletteSunset: 'Sunset', paletteMono: 'Mono', noPlants: 'Aún no hay plantas.', - nextStepsTitle: 'Tus próximos pasos', - stepScan: 'Escanear primera planta', - stepReminder: 'Activar recordatorios inteligentes', - stepLexicon: 'Explorar enciclopedia', - stepTheme: 'Personalizar app', - stepCollection: 'Guardar 3 plantas', - onboardingChecklistTitle: 'Tu inicio en GreenLens', - onboardingChecklistIntro: 'Onboarding', - onboardingChecklistProgress: '{0} de {1} completados', - onboardingChecklistNextLabel: 'Siguiente paso: {0}', - onboardingChecklistDone: 'Ya completaste los pasos iniciales clave.', - customizeOnboardingTitle: 'Haz que GreenLens se sienta tuya', - customizeOnboardingSubtitle: 'Elige apariencia, colores e idioma ahora. Es rápido y hace la app más personal.', - customizeOnboardingPreview: 'Vista previa', - customizeOnboardingContinue: 'Continuar', - customizeOnboardingSkip: 'Más tarde', - sourceOnboardingTitle: '¿Cómo encontraste GreenLens?', - sourceOnboardingSubtitle: 'Una respuesta rápida nos ayuda a entender qué canales realmente funcionan.', - sourceOnboardingContinue: 'Continuar', - sourceOnboardingSkip: 'Omitir', - sourceOptionAppStore: 'App Store / Play Store', - sourceOptionInstagram: 'Instagram', - sourceOptionTikTok: 'TikTok', - sourceOptionFriend: 'Amigos o familia', - sourceOptionSearch: 'Google o búsqueda', - sourceOptionOther: 'Otra cosa', - goalOnboardingTitle: '¿Qué quieres lograr primero?', - goalOnboardingSubtitle: 'Así podemos ajustar mejor tus primeros pasos y mostrarte las acciones correctas.', - goalOnboardingContinue: 'Continuar', - goalOnboardingSkip: 'Omitir', - goalOptionIdentify: 'Identificar plantas rápido', - goalOptionCare: 'Cuidar mejor mis plantas', - goalOptionCollection: 'Construir mi colección', - goalOptionLearn: 'Aprender más sobre plantas', - experienceOnboardingTitle: '¿Qué nivel tienes con plantas?', - experienceOnboardingSubtitle: 'Para que los consejos y el lenguaje encajen mejor con tu nivel.', - experienceOnboardingContinue: 'Finalizar', - experienceOnboardingSkip: 'Omitir', - experienceOptionBeginner: 'Principiante', - experienceOptionIntermediate: 'Algo de experiencia', - experienceOptionAdvanced: 'Muy avanzado', + nextStepsTitle: 'Tus próximos pasos', + stepScan: 'Escanear primera planta', + stepReminder: 'Activar recordatorios inteligentes', + stepLexicon: 'Explorar enciclopedia', + stepTheme: 'Personalizar app', + stepCollection: 'Guardar 3 plantas', + onboardingChecklistTitle: 'Tu inicio en GreenLens', + onboardingChecklistIntro: 'Onboarding', + onboardingChecklistProgress: '{0} de {1} completados', + onboardingChecklistNextLabel: 'Siguiente paso: {0}', + onboardingChecklistDone: 'Ya completaste los pasos iniciales clave.', + customizeOnboardingTitle: 'Haz que GreenLens se sienta tuya', + customizeOnboardingSubtitle: 'Elige apariencia, colores e idioma ahora. Es rápido y hace la app más personal.', + customizeOnboardingPreview: 'Vista previa', + customizeOnboardingContinue: 'Continuar', + customizeOnboardingSkip: 'Más tarde', + sourceOnboardingTitle: '¿Cómo encontraste GreenLens?', + sourceOnboardingSubtitle: 'Una respuesta rápida nos ayuda a entender qué canales realmente funcionan.', + sourceOnboardingContinue: 'Continuar', + sourceOnboardingSkip: 'Omitir', + sourceOptionAppStore: 'App Store / Play Store', + sourceOptionInstagram: 'Instagram', + sourceOptionTikTok: 'TikTok', + sourceOptionFriend: 'Amigos o familia', + sourceOptionSearch: 'Google o búsqueda', + sourceOptionOther: 'Otra cosa', + goalOnboardingTitle: '¿Qué quieres lograr primero?', + goalOnboardingSubtitle: 'Así podemos ajustar mejor tus primeros pasos y mostrarte las acciones correctas.', + goalOnboardingContinue: 'Continuar', + goalOnboardingSkip: 'Omitir', + goalOptionIdentify: 'Identificar plantas rápido', + goalOptionCare: 'Cuidar mejor mis plantas', + goalOptionCollection: 'Construir mi colección', + goalOptionLearn: 'Aprender más sobre plantas', + experienceOnboardingTitle: '¿Qué nivel tienes con plantas?', + experienceOnboardingSubtitle: 'Para que los consejos y el lenguaje encajen mejor con tu nivel.', + experienceOnboardingContinue: 'Finalizar', + experienceOnboardingSkip: 'Omitir', + experienceOptionBeginner: 'Principiante', + experienceOptionIntermediate: 'Algo de experiencia', + experienceOptionAdvanced: 'Muy avanzado', allGood: 'Todo bien', toWater: 'Regar', searchPlaceholder: 'Buscar plantas...', @@ -770,10 +770,10 @@ registerToSave: "Regístrate para guardar", tourFabDesc: "Toca aquí para fotografiar una planta — la IA la reconoce al instante.", tourSearchTitle: "🔍 Enciclopedia", tourSearchDesc: "Busca en miles de plantas o deja que la IA encuentre la perfecta.", - tourProfileTitle: "👤 Tu Perfil", - tourProfileDesc: "Personaliza diseño, idioma y notificaciones a tu gusto.", - tourChecklistTitle: "✅ Tu onboarding", - tourChecklistDesc: "Aquí ves tus siguientes pasos para llegar antes al valor real de la app.", + tourProfileTitle: "👤 Tu Perfil", + tourProfileDesc: "Personaliza diseño, idioma y notificaciones a tu gusto.", + tourChecklistTitle: "✅ Tu onboarding", + tourChecklistDesc: "Aquí ves tus siguientes pasos para llegar antes al valor real de la app.", coachSkip: "Saltar", coachNext: "Siguiente", coachDone: "Listo", @@ -791,23 +791,23 @@ registerToSave: "Regístrate para guardar", onboardingFeatureScan: "Escanea e identifica plantas", onboardingFeatureReminder: "Recordatorios de riego y cuidado", onboardingFeatureLexicon: "Enciclopedia digital de plantas", - onboardingScanBtn: "Escanear Planta", - onboardingRegister: "Registrarse", - onboardingLogin: "Iniciar sesión", - onboardingDisclaimer: "Tus datos permanecen privados y locales en tu dispositivo.", - welcomeHeadline: "El cuidado\nempieza aquí", - welcomeSubheadline: "Escanea una hoja, conoce la planta y mantenla sana.", - welcomeFeatureIdentifyTitle: "Identificación con IA", - welcomeFeatureIdentifyDesc: "Prueba hasta 3 escaneos demo en este dispositivo.", - welcomeFeatureReminderTitle: "Plan de cuidado y recordatorios", - welcomeFeatureReminderDesc: "Recibe consejos claros sobre riego, luz y ubicación.", - welcomeFeatureLibraryTitle: "Guardar tus plantas", - welcomeFeatureLibraryDesc: "Regístrate para conservar las plantas escaneadas.", - welcomeDemoScan: "Probar escaneo demo", - welcomeSubscriptionPlans: "Ver planes y precios", - welcomeLegal: "Los detalles de suscripción, Restaurar, Términos de uso y Política de privacidad se muestran antes de comprar.", - - // Auth + onboardingScanBtn: "Escanear Planta", + onboardingRegister: "Registrarse", + onboardingLogin: "Iniciar sesión", + onboardingDisclaimer: "Tus datos permanecen privados y locales en tu dispositivo.", + welcomeHeadline: "El cuidado\nempieza aquí", + welcomeSubheadline: "Escanea una hoja, conoce la planta y mantenla sana.", + welcomeFeatureIdentifyTitle: "Identificación con IA", + welcomeFeatureIdentifyDesc: "Prueba un escaneo demo gratuito en este dispositivo.", + welcomeFeatureReminderTitle: "Plan de cuidado y recordatorios", + welcomeFeatureReminderDesc: "Recibe consejos claros sobre riego, luz y ubicación.", + welcomeFeatureLibraryTitle: "Guardar tus plantas", + welcomeFeatureLibraryDesc: "Regístrate para conservar las plantas escaneadas.", + welcomeDemoScan: "Probar escaneo demo", + welcomeSubscriptionPlans: "Ver planes y precios", + welcomeLegal: "Los detalles de suscripción, Restaurar, Términos de uso y Política de privacidad se muestran antes de comprar.", + + // Auth createAccount: "Crear cuenta", welcomeBack: "Bienvenido de vuelta", namePlaceholder: "Tu nombre",