diff --git a/backend/internal/domain/menu/handler.go b/backend/internal/domain/menu/handler.go index eb43df6..8e13fa0 100644 --- a/backend/internal/domain/menu/handler.go +++ b/backend/internal/domain/menu/handler.go @@ -31,6 +31,8 @@ type UserLoader interface { // ProductLister returns human-readable product lines for the AI prompt. type ProductLister interface { ListForPrompt(ctx context.Context, userID string) ([]string, error) + // ListForPromptByIDs returns only the products with the given IDs. + ListForPromptByIDs(ctx context.Context, userID string, ids []string) ([]string, error) } // RecipeSaver creates a dish+recipe and returns the new recipe ID. @@ -121,9 +123,10 @@ func (h *Handler) GenerateMenu(w http.ResponseWriter, r *http.Request) { } var body struct { - Week string `json:"week"` // optional, defaults to current week - Dates []string `json:"dates"` // YYYY-MM-DD; triggers partial generation - MealTypes []string `json:"meal_types"` // overrides user preference when set + Week string `json:"week"` // optional, defaults to current week + Dates []string `json:"dates"` // YYYY-MM-DD; triggers partial generation + MealTypes []string `json:"meal_types"` // overrides user preference when set + ProductIDs []string `json:"product_ids"` // when set, only these products are passed to AI } _ = json.NewDecoder(r.Body).Decode(&body) @@ -136,7 +139,7 @@ func (h *Handler) GenerateMenu(w http.ResponseWriter, r *http.Request) { } if len(body.Dates) > 0 { - h.generateForDates(w, r, userID, u, body.Dates, body.MealTypes) + h.generateForDates(w, r, userID, u, body.Dates, body.MealTypes, body.ProductIDs) return } @@ -150,8 +153,14 @@ func (h *Handler) GenerateMenu(w http.ResponseWriter, r *http.Request) { menuReq := buildMenuRequest(u, locale.FromContext(r.Context())) - if products, productError := h.productLister.ListForPrompt(r.Context(), userID); productError == nil { - menuReq.AvailableProducts = products + if len(body.ProductIDs) > 0 { + if products, productError := h.productLister.ListForPromptByIDs(r.Context(), userID, body.ProductIDs); productError == nil { + menuReq.AvailableProducts = products + } + } else { + if products, productError := h.productLister.ListForPrompt(r.Context(), userID); productError == nil { + menuReq.AvailableProducts = products + } } days, generateError := h.menuGenerator.GenerateMenu(r.Context(), menuReq) @@ -194,7 +203,7 @@ func (h *Handler) GenerateMenu(w http.ResponseWriter, r *http.Request) { // generateForDates handles partial menu generation for specific dates and meal types. // It groups dates by ISO week, generates only the requested slots, upserts them // without touching other existing slots, and returns {"plans":[...]}. -func (h *Handler) generateForDates(w http.ResponseWriter, r *http.Request, userID string, u *user.User, dates, requestedMealTypes []string) { +func (h *Handler) generateForDates(w http.ResponseWriter, r *http.Request, userID string, u *user.User, dates, requestedMealTypes, productIDs []string) { mealTypes := requestedMealTypes if len(mealTypes) == 0 { // Fall back to user's preferred meal types. @@ -212,8 +221,14 @@ func (h *Handler) generateForDates(w http.ResponseWriter, r *http.Request, userI menuReq := buildMenuRequest(u, locale.FromContext(r.Context())) menuReq.MealTypes = mealTypes - if products, productError := h.productLister.ListForPrompt(r.Context(), userID); productError == nil { - menuReq.AvailableProducts = products + if len(productIDs) > 0 { + if products, productError := h.productLister.ListForPromptByIDs(r.Context(), userID, productIDs); productError == nil { + menuReq.AvailableProducts = products + } + } else { + if products, productError := h.productLister.ListForPrompt(r.Context(), userID); productError == nil { + menuReq.AvailableProducts = products + } } weekGroups := groupDatesByWeek(dates) diff --git a/backend/internal/domain/userproduct/repository.go b/backend/internal/domain/userproduct/repository.go index 062b307..4680ccb 100644 --- a/backend/internal/domain/userproduct/repository.go +++ b/backend/internal/domain/userproduct/repository.go @@ -161,6 +161,46 @@ func (r *Repository) ListForPrompt(requestContext context.Context, userID string return lines, rows.Err() } +func (r *Repository) ListForPromptByIDs(requestContext context.Context, userID string, ids []string) ([]string, error) { + rows, queryError := r.pool.Query(requestContext, ` + WITH up AS ( + SELECT name, quantity, unit, + (added_at + storage_days * INTERVAL '1 day') AS expires_at + FROM user_products + WHERE user_id = $1 AND id = ANY($2) + ) + SELECT name, quantity, unit, expires_at + FROM up + ORDER BY expires_at ASC`, userID, ids) + if queryError != nil { + return nil, fmt.Errorf("list user products by ids for prompt: %w", queryError) + } + defer rows.Close() + + var lines []string + now := time.Now() + for rows.Next() { + var name, unit string + var qty float64 + var expiresAt time.Time + if scanError := rows.Scan(&name, &qty, &unit, &expiresAt); scanError != nil { + return nil, fmt.Errorf("scan user product for prompt: %w", scanError) + } + daysLeft := int(expiresAt.Sub(now).Hours() / 24) + line := fmt.Sprintf("- %s %.0f %s", name, qty, unit) + switch { + case daysLeft <= 0: + line += " (expires today ⚠)" + case daysLeft == 1: + line += " (expires tomorrow ⚠)" + case daysLeft <= 3: + line += fmt.Sprintf(" (expires in %d days ⚠)", daysLeft) + } + lines = append(lines, line) + } + return lines, rows.Err() +} + // --- helpers --- func scanUserProduct(row pgx.Row) (*UserProduct, error) { diff --git a/client/lib/features/home/home_screen.dart b/client/lib/features/home/home_screen.dart index 074dc81..4800b44 100644 --- a/client/lib/features/home/home_screen.dart +++ b/client/lib/features/home/home_screen.dart @@ -19,6 +19,7 @@ import '../diary/food_search_sheet.dart'; import '../menu/menu_provider.dart'; import '../menu/plan_date_picker_sheet.dart'; import '../menu/plan_menu_sheet.dart'; +import '../menu/plan_products_sheet.dart'; import '../profile/profile_provider.dart'; import '../scan/dish_result_screen.dart'; import '../scan/recognition_service.dart'; @@ -1597,19 +1598,33 @@ class _FutureDayPlanButton extends ConsumerWidget { void _openPlanSheet(BuildContext context, WidgetRef ref) { final defaultStart = DateTime.parse(dateString); + // Step 1: product selection showModalBottomSheet( context: context, isScrollControlled: true, useSafeArea: true, - builder: (_) => PlanMenuSheet( - onModeSelected: (mode) { + builder: (_) => PlanProductsSheet( + onContinue: (selectedProductIds) { + // Step 2: planning mode selection showModalBottomSheet( context: context, isScrollControlled: true, useSafeArea: true, - builder: (_) => PlanDatePickerSheet( - mode: mode, - defaultStart: defaultStart, + builder: (_) => PlanMenuSheet( + selectedProductIds: selectedProductIds, + onModeSelected: (mode, productIds) { + // Step 3: date / meal type selection + showModalBottomSheet( + context: context, + isScrollControlled: true, + useSafeArea: true, + builder: (_) => PlanDatePickerSheet( + mode: mode, + defaultStart: defaultStart, + selectedProductIds: productIds, + ), + ); + }, ), ); }, diff --git a/client/lib/features/menu/menu_provider.dart b/client/lib/features/menu/menu_provider.dart index 73b600b..2468b7e 100644 --- a/client/lib/features/menu/menu_provider.dart +++ b/client/lib/features/menu/menu_provider.dart @@ -192,11 +192,13 @@ class PlanMenuService { Future generateForDates({ required List dates, required List mealTypes, + List productIds = const [], }) async { final menuService = _ref.read(menuServiceProvider); final plans = await menuService.generateForDates( dates: dates, mealTypes: mealTypes, + productIds: productIds, ); for (final plan in plans) { _ref.invalidate(menuProvider(isoWeekString(DateTime.parse(plan.weekStart)))); diff --git a/client/lib/features/menu/menu_service.dart b/client/lib/features/menu/menu_service.dart index 5ab9e4d..23ec5f8 100644 --- a/client/lib/features/menu/menu_service.dart +++ b/client/lib/features/menu/menu_service.dart @@ -27,15 +27,19 @@ class MenuService { } /// Generates meals for specific [dates] (YYYY-MM-DD) and [mealTypes]. + /// When [productIds] is non-empty, only those products are passed to AI. /// Returns the updated MenuPlan for each affected week. Future> generateForDates({ required List dates, required List mealTypes, + List productIds = const [], }) async { - final data = await _client.post('/ai/generate-menu', data: { + final body = { 'dates': dates, 'meal_types': mealTypes, - }); + }; + if (productIds.isNotEmpty) body['product_ids'] = productIds; + final data = await _client.post('/ai/generate-menu', data: body); final plans = data['plans'] as List; return plans .map((planJson) => MenuPlan.fromJson(planJson as Map)) diff --git a/client/lib/features/menu/plan_date_picker_sheet.dart b/client/lib/features/menu/plan_date_picker_sheet.dart index a5f73f6..b67d42e 100644 --- a/client/lib/features/menu/plan_date_picker_sheet.dart +++ b/client/lib/features/menu/plan_date_picker_sheet.dart @@ -16,6 +16,7 @@ class PlanDatePickerSheet extends ConsumerStatefulWidget { super.key, required this.mode, required this.defaultStart, + required this.selectedProductIds, }); final PlanMode mode; @@ -24,6 +25,10 @@ class PlanDatePickerSheet extends ConsumerStatefulWidget { /// planned date. final DateTime defaultStart; + /// Product IDs selected in the previous step. Empty list means the AI + /// will use all of the user's products (default behaviour). + final List selectedProductIds; + @override ConsumerState createState() => _PlanDatePickerSheetState(); @@ -107,6 +112,7 @@ class _PlanDatePickerSheetState extends ConsumerState { await ref.read(planMenuServiceProvider).generateForDates( dates: dates, mealTypes: mealTypes, + productIds: widget.selectedProductIds, ); if (mounted) { Navigator.pop(context); diff --git a/client/lib/features/menu/plan_menu_sheet.dart b/client/lib/features/menu/plan_menu_sheet.dart index daf2db9..f9f4d57 100644 --- a/client/lib/features/menu/plan_menu_sheet.dart +++ b/client/lib/features/menu/plan_menu_sheet.dart @@ -5,11 +5,17 @@ import 'package:food_ai/l10n/app_localizations.dart'; enum PlanMode { singleMeal, singleDay, days, week } /// Bottom sheet that lets the user choose a planning horizon. -/// Closes itself and calls [onModeSelected] with the chosen mode. +/// Closes itself and calls [onModeSelected] with the chosen mode and the +/// product IDs selected in the previous step (may be empty). class PlanMenuSheet extends StatelessWidget { - const PlanMenuSheet({super.key, required this.onModeSelected}); + const PlanMenuSheet({ + super.key, + required this.onModeSelected, + required this.selectedProductIds, + }); - final void Function(PlanMode mode) onModeSelected; + final void Function(PlanMode mode, List productIds) onModeSelected; + final List selectedProductIds; @override Widget build(BuildContext context) { @@ -61,7 +67,7 @@ class PlanMenuSheet extends StatelessWidget { void _select(BuildContext context, PlanMode mode) { Navigator.pop(context); - onModeSelected(mode); + onModeSelected(mode, selectedProductIds); } } diff --git a/client/lib/features/menu/plan_products_sheet.dart b/client/lib/features/menu/plan_products_sheet.dart new file mode 100644 index 0000000..5c392ca --- /dev/null +++ b/client/lib/features/menu/plan_products_sheet.dart @@ -0,0 +1,247 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:food_ai/l10n/app_localizations.dart'; + +import '../../shared/models/user_product.dart'; +import '../products/user_product_provider.dart'; + +/// Step-1 bottom sheet for the meal planning flow. +/// +/// Shows the user's current products as a multi-select checklist so they +/// can choose which products the AI should consider when generating the menu. +/// All products are selected by default. The user may deselect individual +/// items or skip the step entirely. +/// +/// Calls [onContinue] with the list of selected product IDs (empty list +/// means "plan without products"). +class PlanProductsSheet extends ConsumerStatefulWidget { + const PlanProductsSheet({super.key, required this.onContinue}); + + final void Function(List selectedProductIds) onContinue; + + @override + ConsumerState createState() => _PlanProductsSheetState(); +} + +class _PlanProductsSheetState extends ConsumerState { + // IDs of products the user has checked. Null means "not yet initialised" + // (we wait for the first data frame to select all by default). + Set? _selected; + + void _initSelected(List products) { + if (_selected == null) { + _selected = {for (final product in products) product.id}; + } + } + + void _toggleAll(List products) { + setState(() { + if (_selected!.length == products.length) { + _selected = {}; + } else { + _selected = {for (final product in products) product.id}; + } + }); + } + + void _toggleProduct(String productId) { + setState(() { + if (_selected!.contains(productId)) { + _selected = Set.of(_selected!)..remove(productId); + } else { + _selected = Set.of(_selected!)..add(productId); + } + }); + } + + void _continue() { + Navigator.pop(context); + widget.onContinue(_selected?.toList() ?? []); + } + + void _skip() { + Navigator.pop(context); + widget.onContinue([]); + } + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + final theme = Theme.of(context); + final productsAsync = ref.watch(userProductsProvider); + + return SafeArea( + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 20, 16, 16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + l10n.planProductsTitle, + style: theme.textTheme.titleLarge, + textAlign: TextAlign.center, + ), + const SizedBox(height: 4), + Text( + l10n.planProductsSubtitle, + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + productsAsync.when( + loading: () => const _LoadingSkeleton(), + error: (_, __) => _EmptyState(onSkip: _skip, onAddProducts: () { + Navigator.pop(context); + context.push('/products'); + }), + data: (products) { + if (products.isEmpty) { + return _EmptyState( + onSkip: _skip, + onAddProducts: () { + Navigator.pop(context); + context.push('/products'); + }, + ); + } + _initSelected(products); + final allSelected = _selected!.length == products.length; + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Select all / deselect all chip + Align( + alignment: Alignment.centerLeft, + child: ActionChip( + label: Text( + allSelected + ? l10n.planProductsDeselectAll + : l10n.planProductsSelectAll, + ), + onPressed: () => _toggleAll(products), + ), + ), + const SizedBox(height: 8), + // Product list (scrollable, capped at ~4 items before scrolling) + ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 280), + child: ListView.builder( + shrinkWrap: true, + itemCount: products.length, + itemBuilder: (context, index) { + final product = products[index]; + final isChecked = + _selected!.contains(product.id); + return CheckboxListTile( + value: isChecked, + onChanged: (_) => _toggleProduct(product.id), + title: Text(product.name), + subtitle: Text( + '${product.quantity.toStringAsFixed(product.quantity.truncateToDouble() == product.quantity ? 0 : 1)} ${product.unit}', + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), + ), + controlAffinity: ListTileControlAffinity.leading, + dense: true, + ); + }, + ), + ), + const SizedBox(height: 16), + FilledButton( + onPressed: _continue, + child: Text(l10n.planProductsContinue), + ), + TextButton( + onPressed: _skip, + child: Text(l10n.planProductsSkip), + ), + ], + ); + }, + ), + ], + ), + ), + ); + } +} + +class _LoadingSkeleton extends StatelessWidget { + const _LoadingSkeleton(); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final skeletonColor = theme.colorScheme.surfaceContainerHighest; + return Column( + mainAxisSize: MainAxisSize.min, + children: List.generate( + 3, + (index) => Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: Container( + height: 48, + decoration: BoxDecoration( + color: skeletonColor, + borderRadius: BorderRadius.circular(8), + ), + ), + ), + ), + ); + } +} + +class _EmptyState extends StatelessWidget { + const _EmptyState({required this.onSkip, required this.onAddProducts}); + + final VoidCallback onSkip; + final VoidCallback onAddProducts; + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + final theme = Theme.of(context); + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Icon( + Icons.kitchen_outlined, + size: 56, + color: theme.colorScheme.onSurfaceVariant, + ), + const SizedBox(height: 12), + Text( + l10n.planProductsEmpty, + style: theme.textTheme.titleMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + l10n.planProductsEmptyMessage, + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 20), + FilledButton( + onPressed: onAddProducts, + child: Text(l10n.planProductsAddProducts), + ), + TextButton( + onPressed: onSkip, + child: Text(l10n.planProductsSkipNoProducts), + ), + ], + ); + } +} diff --git a/client/lib/l10n/app_ar.arb b/client/lib/l10n/app_ar.arb index 6a0b838..142c97a 100644 --- a/client/lib/l10n/app_ar.arb +++ b/client/lib/l10n/app_ar.arb @@ -118,7 +118,9 @@ "noResultsForQuery": "لم يتم العثور على نتائج لـ \"{query}\"", "@noResultsForQuery": { "placeholders": { - "query": { "type": "String" } + "query": { + "type": "String" + } } }, "servingsLabel": "حصص", @@ -127,7 +129,9 @@ "planningForDate": "", "@planningForDate": { "placeholders": { - "date": { "type": "String" } + "date": { + "type": "String" + } } }, "markAsEaten": "وضع علامة كمأكول", @@ -136,7 +140,6 @@ "generateWeekSubtitle": "سيقوم الذكاء الاصطناعي بإنشاء قائمة طعام تشمل الإفطار والغداء والعشاء لكامل الأسبوع", "generatingMenu": "جارٍ إنشاء القائمة...", "dayPlannedLabel": "تم تخطيط اليوم", - "planMenuButton": "تخطيط الوجبات", "planMenuTitle": "ماذا تريد تخطيطه؟", "planOptionSingleMeal": "وجبة واحدة", @@ -151,6 +154,16 @@ "planSelectMealType": "نوع الوجبة", "planSelectRange": "اختر الفترة", "planGenerateButton": "تخطيط", - "planGenerating": "جارٍ إنشاء الخطة\u2026", - "planSuccess": "تم تخطيط القائمة!" + "planGenerating": "جارٍ إنشاء الخطة…", + "planSuccess": "تم تخطيط القائمة!", + "planProductsTitle": "مكونات القائمة", + "planProductsSubtitle": "سيأخذ الذكاء الاصطناعي المكونات المختارة بعين الاعتبار عند إنشاء الوصفات", + "planProductsEmpty": "لم يتم إضافة منتجات", + "planProductsEmptyMessage": "أضف المنتجات الموجودة لديك في المنزل — سيقترح الذكاء الاصطناعي وصفات مما لديك بالفعل", + "planProductsAddProducts": "إضافة منتجات", + "planProductsContinue": "متابعة", + "planProductsSkip": "تخطي اختيار المنتجات", + "planProductsSkipNoProducts": "التخطيط بدون منتجات", + "planProductsSelectAll": "تحديد الكل", + "planProductsDeselectAll": "إلغاء تحديد الكل" } diff --git a/client/lib/l10n/app_de.arb b/client/lib/l10n/app_de.arb index 5b382a2..9b258f1 100644 --- a/client/lib/l10n/app_de.arb +++ b/client/lib/l10n/app_de.arb @@ -118,7 +118,9 @@ "noResultsForQuery": "Keine Ergebnisse für \"{query}\"", "@noResultsForQuery": { "placeholders": { - "query": { "type": "String" } + "query": { + "type": "String" + } } }, "servingsLabel": "Portionen", @@ -127,7 +129,9 @@ "planningForDate": "", "@planningForDate": { "placeholders": { - "date": { "type": "String" } + "date": { + "type": "String" + } } }, "markAsEaten": "Als gegessen markieren", @@ -136,7 +140,6 @@ "generateWeekSubtitle": "KI erstellt einen Menüplan mit Frühstück, Mittagessen und Abendessen für die ganze Woche", "generatingMenu": "Menü wird erstellt...", "dayPlannedLabel": "Tag geplant", - "planMenuButton": "Mahlzeiten planen", "planMenuTitle": "Was planen?", "planOptionSingleMeal": "Einzelne Mahlzeit", @@ -151,6 +154,16 @@ "planSelectMealType": "Mahlzeittyp", "planSelectRange": "Zeitraum wählen", "planGenerateButton": "Planen", - "planGenerating": "Plan wird erstellt\u2026", - "planSuccess": "Menü geplant!" + "planGenerating": "Plan wird erstellt…", + "planSuccess": "Menü geplant!", + "planProductsTitle": "Zutaten für den Speiseplan", + "planProductsSubtitle": "Die KI berücksichtigt die ausgewählten Produkte bei der Rezeptgenerierung", + "planProductsEmpty": "Keine Produkte hinzugefügt", + "planProductsEmptyMessage": "Füge Produkte hinzu, die du zu Hause hast — die KI schlägt Rezepte aus deinen Vorräten vor", + "planProductsAddProducts": "Produkte hinzufügen", + "planProductsContinue": "Weiter", + "planProductsSkip": "Produktauswahl überspringen", + "planProductsSkipNoProducts": "Ohne Produkte planen", + "planProductsSelectAll": "Alle auswählen", + "planProductsDeselectAll": "Alle abwählen" } diff --git a/client/lib/l10n/app_en.arb b/client/lib/l10n/app_en.arb index fead0a7..63e7490 100644 --- a/client/lib/l10n/app_en.arb +++ b/client/lib/l10n/app_en.arb @@ -150,5 +150,15 @@ "planSelectRange": "Select period", "planGenerateButton": "Plan", "planGenerating": "Generating plan\u2026", - "planSuccess": "Menu planned!" + "planSuccess": "Menu planned!", + "planProductsTitle": "Products for the menu", + "planProductsSubtitle": "AI will take the selected products into account when generating recipes", + "planProductsEmpty": "No products added", + "planProductsEmptyMessage": "Add products you have at home \u2014 AI will suggest recipes from what you already have", + "planProductsAddProducts": "Add products", + "planProductsContinue": "Continue", + "planProductsSkip": "Skip product selection", + "planProductsSkipNoProducts": "Plan without products", + "planProductsSelectAll": "Select all", + "planProductsDeselectAll": "Deselect all" } diff --git a/client/lib/l10n/app_es.arb b/client/lib/l10n/app_es.arb index b4968d7..c6aada7 100644 --- a/client/lib/l10n/app_es.arb +++ b/client/lib/l10n/app_es.arb @@ -118,7 +118,9 @@ "noResultsForQuery": "Nada encontrado para \"{query}\"", "@noResultsForQuery": { "placeholders": { - "query": { "type": "String" } + "query": { + "type": "String" + } } }, "servingsLabel": "Porciones", @@ -127,7 +129,9 @@ "planningForDate": "", "@planningForDate": { "placeholders": { - "date": { "type": "String" } + "date": { + "type": "String" + } } }, "markAsEaten": "Marcar como comido", @@ -136,7 +140,6 @@ "generateWeekSubtitle": "La IA creará un menú con desayuno, comida y cena para toda la semana", "generatingMenu": "Generando menú...", "dayPlannedLabel": "Día planificado", - "planMenuButton": "Planificar comidas", "planMenuTitle": "¿Qué planificar?", "planOptionSingleMeal": "Una comida", @@ -151,6 +154,16 @@ "planSelectMealType": "Tipo de comida", "planSelectRange": "Seleccionar período", "planGenerateButton": "Planificar", - "planGenerating": "Generando plan\u2026", - "planSuccess": "¡Menú planificado!" + "planGenerating": "Generando plan…", + "planSuccess": "¡Menú planificado!", + "planProductsTitle": "Productos para el menú", + "planProductsSubtitle": "La IA tendrá en cuenta los productos seleccionados al generar recetas", + "planProductsEmpty": "No hay productos añadidos", + "planProductsEmptyMessage": "Añade productos que tengas en casa — la IA sugerirá recetas con lo que ya tienes", + "planProductsAddProducts": "Añadir productos", + "planProductsContinue": "Continuar", + "planProductsSkip": "Omitir selección de productos", + "planProductsSkipNoProducts": "Planificar sin productos", + "planProductsSelectAll": "Seleccionar todo", + "planProductsDeselectAll": "Deseleccionar todo" } diff --git a/client/lib/l10n/app_fr.arb b/client/lib/l10n/app_fr.arb index 3d83613..bf731fd 100644 --- a/client/lib/l10n/app_fr.arb +++ b/client/lib/l10n/app_fr.arb @@ -118,7 +118,9 @@ "noResultsForQuery": "Rien trouvé pour \"{query}\"", "@noResultsForQuery": { "placeholders": { - "query": { "type": "String" } + "query": { + "type": "String" + } } }, "servingsLabel": "Portions", @@ -127,7 +129,9 @@ "planningForDate": "", "@planningForDate": { "placeholders": { - "date": { "type": "String" } + "date": { + "type": "String" + } } }, "markAsEaten": "Marquer comme mangé", @@ -136,7 +140,6 @@ "generateWeekSubtitle": "L'IA créera un menu avec petit-déjeuner, déjeuner et dîner pour toute la semaine", "generatingMenu": "Génération du menu...", "dayPlannedLabel": "Jour planifié", - "planMenuButton": "Planifier les repas", "planMenuTitle": "Que planifier ?", "planOptionSingleMeal": "Un repas", @@ -151,6 +154,16 @@ "planSelectMealType": "Type de repas", "planSelectRange": "Choisir la période", "planGenerateButton": "Planifier", - "planGenerating": "Génération du plan\u2026", - "planSuccess": "Menu planifié !" + "planGenerating": "Génération du plan…", + "planSuccess": "Menu planifié !", + "planProductsTitle": "Produits pour le menu", + "planProductsSubtitle": "L'IA tiendra compte des produits sélectionnés lors de la génération des recettes", + "planProductsEmpty": "Aucun produit ajouté", + "planProductsEmptyMessage": "Ajoutez des produits que vous avez à la maison — l'IA suggérera des recettes à partir de ce que vous avez déjà", + "planProductsAddProducts": "Ajouter des produits", + "planProductsContinue": "Continuer", + "planProductsSkip": "Ignorer la sélection des produits", + "planProductsSkipNoProducts": "Planifier sans produits", + "planProductsSelectAll": "Tout sélectionner", + "planProductsDeselectAll": "Tout désélectionner" } diff --git a/client/lib/l10n/app_hi.arb b/client/lib/l10n/app_hi.arb index 7c7601b..c327d4e 100644 --- a/client/lib/l10n/app_hi.arb +++ b/client/lib/l10n/app_hi.arb @@ -118,7 +118,9 @@ "noResultsForQuery": "\"{query}\" के लिए कुछ नहीं मिला", "@noResultsForQuery": { "placeholders": { - "query": { "type": "String" } + "query": { + "type": "String" + } } }, "servingsLabel": "सर्विंग", @@ -127,7 +129,9 @@ "planningForDate": "", "@planningForDate": { "placeholders": { - "date": { "type": "String" } + "date": { + "type": "String" + } } }, "markAsEaten": "खाया हुआ चिह्नित करें", @@ -136,7 +140,6 @@ "generateWeekSubtitle": "AI पूरे सप्ताह के लिए नाश्ता, दोपहर का खाना और रात के खाने के साथ मेनू बनाएगा", "generatingMenu": "मेनू बना रहे हैं...", "dayPlannedLabel": "दिन की योजना बनाई गई", - "planMenuButton": "भोजन की योजना बनाएं", "planMenuTitle": "क्या योजना बनानी है?", "planOptionSingleMeal": "एक भोजन", @@ -151,6 +154,16 @@ "planSelectMealType": "भोजन का प्रकार", "planSelectRange": "अवधि चुनें", "planGenerateButton": "योजना बनाएं", - "planGenerating": "योजना बना रहे हैं\u2026", - "planSuccess": "मेनू की योजना बनाई गई!" + "planGenerating": "योजना बना रहे हैं…", + "planSuccess": "मेनू की योजना बनाई गई!", + "planProductsTitle": "मेनू के लिए उत्पाद", + "planProductsSubtitle": "AI रेसिपी बनाते समय चुने हुए उत्पादों को ध्यान में रखेगा", + "planProductsEmpty": "कोई उत्पाद नहीं जोड़ा गया", + "planProductsEmptyMessage": "घर पर उपलब्ध उत्पाद जोड़ें — AI आपके पास पहले से मौजूद चीज़ों से रेसिपी सुझाएगा", + "planProductsAddProducts": "उत्पाद जोड़ें", + "planProductsContinue": "जारी रखें", + "planProductsSkip": "उत्पाद चयन छोड़ें", + "planProductsSkipNoProducts": "उत्पादों के बिना योजना बनाएं", + "planProductsSelectAll": "सभी चुनें", + "planProductsDeselectAll": "सभी हटाएं" } diff --git a/client/lib/l10n/app_it.arb b/client/lib/l10n/app_it.arb index f4d6bcd..3cfe716 100644 --- a/client/lib/l10n/app_it.arb +++ b/client/lib/l10n/app_it.arb @@ -118,7 +118,9 @@ "noResultsForQuery": "Nessun risultato per \"{query}\"", "@noResultsForQuery": { "placeholders": { - "query": { "type": "String" } + "query": { + "type": "String" + } } }, "servingsLabel": "Porzioni", @@ -127,7 +129,9 @@ "planningForDate": "", "@planningForDate": { "placeholders": { - "date": { "type": "String" } + "date": { + "type": "String" + } } }, "markAsEaten": "Segna come mangiato", @@ -136,7 +140,6 @@ "generateWeekSubtitle": "L'AI creerà un menu con colazione, pranzo e cena per tutta la settimana", "generatingMenu": "Generazione menu...", "dayPlannedLabel": "Giorno pianificato", - "planMenuButton": "Pianifica i pasti", "planMenuTitle": "Cosa pianificare?", "planOptionSingleMeal": "Un pasto", @@ -151,6 +154,16 @@ "planSelectMealType": "Tipo di pasto", "planSelectRange": "Seleziona periodo", "planGenerateButton": "Pianifica", - "planGenerating": "Generazione piano\u2026", - "planSuccess": "Menu pianificato!" + "planGenerating": "Generazione piano…", + "planSuccess": "Menu pianificato!", + "planProductsTitle": "Prodotti per il menu", + "planProductsSubtitle": "L'AI terrà conto dei prodotti selezionati nella generazione delle ricette", + "planProductsEmpty": "Nessun prodotto aggiunto", + "planProductsEmptyMessage": "Aggiungi prodotti che hai in casa — l'AI suggerirà ricette con quello che hai già", + "planProductsAddProducts": "Aggiungi prodotti", + "planProductsContinue": "Continua", + "planProductsSkip": "Salta la selezione dei prodotti", + "planProductsSkipNoProducts": "Pianifica senza prodotti", + "planProductsSelectAll": "Seleziona tutto", + "planProductsDeselectAll": "Deseleziona tutto" } diff --git a/client/lib/l10n/app_ja.arb b/client/lib/l10n/app_ja.arb index 884fdc2..bb0d0e5 100644 --- a/client/lib/l10n/app_ja.arb +++ b/client/lib/l10n/app_ja.arb @@ -118,7 +118,9 @@ "noResultsForQuery": "「{query}」の検索結果はありません", "@noResultsForQuery": { "placeholders": { - "query": { "type": "String" } + "query": { + "type": "String" + } } }, "servingsLabel": "人前", @@ -127,7 +129,9 @@ "planningForDate": "", "@planningForDate": { "placeholders": { - "date": { "type": "String" } + "date": { + "type": "String" + } } }, "markAsEaten": "食べた印をつける", @@ -136,7 +140,6 @@ "generateWeekSubtitle": "AIが一週間の朝食・昼食・夕食のメニューを作成します", "generatingMenu": "メニューを生成中...", "dayPlannedLabel": "日の計画済み", - "planMenuButton": "食事を計画する", "planMenuTitle": "何を計画する?", "planOptionSingleMeal": "1食", @@ -151,6 +154,16 @@ "planSelectMealType": "食事タイプ", "planSelectRange": "期間を選択", "planGenerateButton": "計画する", - "planGenerating": "プランを生成中\u2026", - "planSuccess": "メニューが計画されました!" + "planGenerating": "プランを生成中…", + "planSuccess": "メニューが計画されました!", + "planProductsTitle": "メニューの食材", + "planProductsSubtitle": "AIはレシピ生成時に選択した食材を考慮します", + "planProductsEmpty": "食材が追加されていません", + "planProductsEmptyMessage": "家にある食材を追加してください — AIが手持ちの食材でレシピを提案します", + "planProductsAddProducts": "食材を追加", + "planProductsContinue": "続ける", + "planProductsSkip": "食材選択をスキップ", + "planProductsSkipNoProducts": "食材なしでプランニング", + "planProductsSelectAll": "すべて選択", + "planProductsDeselectAll": "すべて解除" } diff --git a/client/lib/l10n/app_ko.arb b/client/lib/l10n/app_ko.arb index fde3c49..e2ee16e 100644 --- a/client/lib/l10n/app_ko.arb +++ b/client/lib/l10n/app_ko.arb @@ -118,7 +118,9 @@ "noResultsForQuery": "\"{query}\"에 대한 결과 없음", "@noResultsForQuery": { "placeholders": { - "query": { "type": "String" } + "query": { + "type": "String" + } } }, "servingsLabel": "인분", @@ -127,7 +129,9 @@ "planningForDate": "", "@planningForDate": { "placeholders": { - "date": { "type": "String" } + "date": { + "type": "String" + } } }, "markAsEaten": "먹은 것으로 표시", @@ -136,7 +140,6 @@ "generateWeekSubtitle": "AI가 한 주 동안 아침, 점심, 저녁 식사 메뉴를 만들어 드립니다", "generatingMenu": "메뉴 생성 중...", "dayPlannedLabel": "일일 계획 완료", - "planMenuButton": "식사 계획하기", "planMenuTitle": "무엇을 계획하시겠어요?", "planOptionSingleMeal": "식사 1회", @@ -151,6 +154,16 @@ "planSelectMealType": "식사 유형", "planSelectRange": "기간 선택", "planGenerateButton": "계획하기", - "planGenerating": "플랜 생성 중\u2026", - "planSuccess": "메뉴가 계획되었습니다!" + "planGenerating": "플랜 생성 중…", + "planSuccess": "메뉴가 계획되었습니다!", + "planProductsTitle": "메뉴 재료", + "planProductsSubtitle": "AI가 레시피 생성 시 선택한 재료를 고려합니다", + "planProductsEmpty": "추가된 재료가 없습니다", + "planProductsEmptyMessage": "집에 있는 재료를 추가하세요 — AI가 이미 있는 재료로 레시피를 제안합니다", + "planProductsAddProducts": "재료 추가", + "planProductsContinue": "계속", + "planProductsSkip": "재료 선택 건너뛰기", + "planProductsSkipNoProducts": "재료 없이 계획하기", + "planProductsSelectAll": "모두 선택", + "planProductsDeselectAll": "모두 해제" } diff --git a/client/lib/l10n/app_localizations.dart b/client/lib/l10n/app_localizations.dart index 4e17d4a..45fe496 100644 --- a/client/lib/l10n/app_localizations.dart +++ b/client/lib/l10n/app_localizations.dart @@ -927,6 +927,66 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Menu planned!'** String get planSuccess; + + /// No description provided for @planProductsTitle. + /// + /// In en, this message translates to: + /// **'Products for the menu'** + String get planProductsTitle; + + /// No description provided for @planProductsSubtitle. + /// + /// In en, this message translates to: + /// **'AI will take the selected products into account when generating recipes'** + String get planProductsSubtitle; + + /// No description provided for @planProductsEmpty. + /// + /// In en, this message translates to: + /// **'No products added'** + String get planProductsEmpty; + + /// No description provided for @planProductsEmptyMessage. + /// + /// In en, this message translates to: + /// **'Add products you have at home — AI will suggest recipes from what you already have'** + String get planProductsEmptyMessage; + + /// No description provided for @planProductsAddProducts. + /// + /// In en, this message translates to: + /// **'Add products'** + String get planProductsAddProducts; + + /// No description provided for @planProductsContinue. + /// + /// In en, this message translates to: + /// **'Continue'** + String get planProductsContinue; + + /// No description provided for @planProductsSkip. + /// + /// In en, this message translates to: + /// **'Skip product selection'** + String get planProductsSkip; + + /// No description provided for @planProductsSkipNoProducts. + /// + /// In en, this message translates to: + /// **'Plan without products'** + String get planProductsSkipNoProducts; + + /// No description provided for @planProductsSelectAll. + /// + /// In en, this message translates to: + /// **'Select all'** + String get planProductsSelectAll; + + /// No description provided for @planProductsDeselectAll. + /// + /// In en, this message translates to: + /// **'Deselect all'** + String get planProductsDeselectAll; } class _AppLocalizationsDelegate diff --git a/client/lib/l10n/app_localizations_ar.dart b/client/lib/l10n/app_localizations_ar.dart index 13fd59a..943d35c 100644 --- a/client/lib/l10n/app_localizations_ar.dart +++ b/client/lib/l10n/app_localizations_ar.dart @@ -420,4 +420,36 @@ class AppLocalizationsAr extends AppLocalizations { @override String get planSuccess => 'تم تخطيط القائمة!'; + + @override + String get planProductsTitle => 'مكونات القائمة'; + + @override + String get planProductsSubtitle => + 'سيأخذ الذكاء الاصطناعي المكونات المختارة بعين الاعتبار عند إنشاء الوصفات'; + + @override + String get planProductsEmpty => 'لم يتم إضافة منتجات'; + + @override + String get planProductsEmptyMessage => + 'أضف المنتجات الموجودة لديك في المنزل — سيقترح الذكاء الاصطناعي وصفات مما لديك بالفعل'; + + @override + String get planProductsAddProducts => 'إضافة منتجات'; + + @override + String get planProductsContinue => 'متابعة'; + + @override + String get planProductsSkip => 'تخطي اختيار المنتجات'; + + @override + String get planProductsSkipNoProducts => 'التخطيط بدون منتجات'; + + @override + String get planProductsSelectAll => 'تحديد الكل'; + + @override + String get planProductsDeselectAll => 'إلغاء تحديد الكل'; } diff --git a/client/lib/l10n/app_localizations_de.dart b/client/lib/l10n/app_localizations_de.dart index 1f20ec8..ac31373 100644 --- a/client/lib/l10n/app_localizations_de.dart +++ b/client/lib/l10n/app_localizations_de.dart @@ -422,4 +422,36 @@ class AppLocalizationsDe extends AppLocalizations { @override String get planSuccess => 'Menü geplant!'; + + @override + String get planProductsTitle => 'Zutaten für den Speiseplan'; + + @override + String get planProductsSubtitle => + 'Die KI berücksichtigt die ausgewählten Produkte bei der Rezeptgenerierung'; + + @override + String get planProductsEmpty => 'Keine Produkte hinzugefügt'; + + @override + String get planProductsEmptyMessage => + 'Füge Produkte hinzu, die du zu Hause hast — die KI schlägt Rezepte aus deinen Vorräten vor'; + + @override + String get planProductsAddProducts => 'Produkte hinzufügen'; + + @override + String get planProductsContinue => 'Weiter'; + + @override + String get planProductsSkip => 'Produktauswahl überspringen'; + + @override + String get planProductsSkipNoProducts => 'Ohne Produkte planen'; + + @override + String get planProductsSelectAll => 'Alle auswählen'; + + @override + String get planProductsDeselectAll => 'Alle abwählen'; } diff --git a/client/lib/l10n/app_localizations_en.dart b/client/lib/l10n/app_localizations_en.dart index dbc151d..8b1a777 100644 --- a/client/lib/l10n/app_localizations_en.dart +++ b/client/lib/l10n/app_localizations_en.dart @@ -420,4 +420,36 @@ class AppLocalizationsEn extends AppLocalizations { @override String get planSuccess => 'Menu planned!'; + + @override + String get planProductsTitle => 'Products for the menu'; + + @override + String get planProductsSubtitle => + 'AI will take the selected products into account when generating recipes'; + + @override + String get planProductsEmpty => 'No products added'; + + @override + String get planProductsEmptyMessage => + 'Add products you have at home — AI will suggest recipes from what you already have'; + + @override + String get planProductsAddProducts => 'Add products'; + + @override + String get planProductsContinue => 'Continue'; + + @override + String get planProductsSkip => 'Skip product selection'; + + @override + String get planProductsSkipNoProducts => 'Plan without products'; + + @override + String get planProductsSelectAll => 'Select all'; + + @override + String get planProductsDeselectAll => 'Deselect all'; } diff --git a/client/lib/l10n/app_localizations_es.dart b/client/lib/l10n/app_localizations_es.dart index d6ea969..33963f9 100644 --- a/client/lib/l10n/app_localizations_es.dart +++ b/client/lib/l10n/app_localizations_es.dart @@ -422,4 +422,36 @@ class AppLocalizationsEs extends AppLocalizations { @override String get planSuccess => '¡Menú planificado!'; + + @override + String get planProductsTitle => 'Productos para el menú'; + + @override + String get planProductsSubtitle => + 'La IA tendrá en cuenta los productos seleccionados al generar recetas'; + + @override + String get planProductsEmpty => 'No hay productos añadidos'; + + @override + String get planProductsEmptyMessage => + 'Añade productos que tengas en casa — la IA sugerirá recetas con lo que ya tienes'; + + @override + String get planProductsAddProducts => 'Añadir productos'; + + @override + String get planProductsContinue => 'Continuar'; + + @override + String get planProductsSkip => 'Omitir selección de productos'; + + @override + String get planProductsSkipNoProducts => 'Planificar sin productos'; + + @override + String get planProductsSelectAll => 'Seleccionar todo'; + + @override + String get planProductsDeselectAll => 'Deseleccionar todo'; } diff --git a/client/lib/l10n/app_localizations_fr.dart b/client/lib/l10n/app_localizations_fr.dart index 0b1f233..d8515c5 100644 --- a/client/lib/l10n/app_localizations_fr.dart +++ b/client/lib/l10n/app_localizations_fr.dart @@ -423,4 +423,36 @@ class AppLocalizationsFr extends AppLocalizations { @override String get planSuccess => 'Menu planifié !'; + + @override + String get planProductsTitle => 'Produits pour le menu'; + + @override + String get planProductsSubtitle => + 'L\'IA tiendra compte des produits sélectionnés lors de la génération des recettes'; + + @override + String get planProductsEmpty => 'Aucun produit ajouté'; + + @override + String get planProductsEmptyMessage => + 'Ajoutez des produits que vous avez à la maison — l\'IA suggérera des recettes à partir de ce que vous avez déjà'; + + @override + String get planProductsAddProducts => 'Ajouter des produits'; + + @override + String get planProductsContinue => 'Continuer'; + + @override + String get planProductsSkip => 'Ignorer la sélection des produits'; + + @override + String get planProductsSkipNoProducts => 'Planifier sans produits'; + + @override + String get planProductsSelectAll => 'Tout sélectionner'; + + @override + String get planProductsDeselectAll => 'Tout désélectionner'; } diff --git a/client/lib/l10n/app_localizations_hi.dart b/client/lib/l10n/app_localizations_hi.dart index f899d6f..b8f7370 100644 --- a/client/lib/l10n/app_localizations_hi.dart +++ b/client/lib/l10n/app_localizations_hi.dart @@ -421,4 +421,36 @@ class AppLocalizationsHi extends AppLocalizations { @override String get planSuccess => 'मेनू की योजना बनाई गई!'; + + @override + String get planProductsTitle => 'मेनू के लिए उत्पाद'; + + @override + String get planProductsSubtitle => + 'AI रेसिपी बनाते समय चुने हुए उत्पादों को ध्यान में रखेगा'; + + @override + String get planProductsEmpty => 'कोई उत्पाद नहीं जोड़ा गया'; + + @override + String get planProductsEmptyMessage => + 'घर पर उपलब्ध उत्पाद जोड़ें — AI आपके पास पहले से मौजूद चीज़ों से रेसिपी सुझाएगा'; + + @override + String get planProductsAddProducts => 'उत्पाद जोड़ें'; + + @override + String get planProductsContinue => 'जारी रखें'; + + @override + String get planProductsSkip => 'उत्पाद चयन छोड़ें'; + + @override + String get planProductsSkipNoProducts => 'उत्पादों के बिना योजना बनाएं'; + + @override + String get planProductsSelectAll => 'सभी चुनें'; + + @override + String get planProductsDeselectAll => 'सभी हटाएं'; } diff --git a/client/lib/l10n/app_localizations_it.dart b/client/lib/l10n/app_localizations_it.dart index ca56c9f..b2506a6 100644 --- a/client/lib/l10n/app_localizations_it.dart +++ b/client/lib/l10n/app_localizations_it.dart @@ -422,4 +422,36 @@ class AppLocalizationsIt extends AppLocalizations { @override String get planSuccess => 'Menu pianificato!'; + + @override + String get planProductsTitle => 'Prodotti per il menu'; + + @override + String get planProductsSubtitle => + 'L\'AI terrà conto dei prodotti selezionati nella generazione delle ricette'; + + @override + String get planProductsEmpty => 'Nessun prodotto aggiunto'; + + @override + String get planProductsEmptyMessage => + 'Aggiungi prodotti che hai in casa — l\'AI suggerirà ricette con quello che hai già'; + + @override + String get planProductsAddProducts => 'Aggiungi prodotti'; + + @override + String get planProductsContinue => 'Continua'; + + @override + String get planProductsSkip => 'Salta la selezione dei prodotti'; + + @override + String get planProductsSkipNoProducts => 'Pianifica senza prodotti'; + + @override + String get planProductsSelectAll => 'Seleziona tutto'; + + @override + String get planProductsDeselectAll => 'Deseleziona tutto'; } diff --git a/client/lib/l10n/app_localizations_ja.dart b/client/lib/l10n/app_localizations_ja.dart index 653c314..90a6f9a 100644 --- a/client/lib/l10n/app_localizations_ja.dart +++ b/client/lib/l10n/app_localizations_ja.dart @@ -418,4 +418,35 @@ class AppLocalizationsJa extends AppLocalizations { @override String get planSuccess => 'メニューが計画されました!'; + + @override + String get planProductsTitle => 'メニューの食材'; + + @override + String get planProductsSubtitle => 'AIはレシピ生成時に選択した食材を考慮します'; + + @override + String get planProductsEmpty => '食材が追加されていません'; + + @override + String get planProductsEmptyMessage => + '家にある食材を追加してください — AIが手持ちの食材でレシピを提案します'; + + @override + String get planProductsAddProducts => '食材を追加'; + + @override + String get planProductsContinue => '続ける'; + + @override + String get planProductsSkip => '食材選択をスキップ'; + + @override + String get planProductsSkipNoProducts => '食材なしでプランニング'; + + @override + String get planProductsSelectAll => 'すべて選択'; + + @override + String get planProductsDeselectAll => 'すべて解除'; } diff --git a/client/lib/l10n/app_localizations_ko.dart b/client/lib/l10n/app_localizations_ko.dart index 7a1b39a..b420421 100644 --- a/client/lib/l10n/app_localizations_ko.dart +++ b/client/lib/l10n/app_localizations_ko.dart @@ -418,4 +418,35 @@ class AppLocalizationsKo extends AppLocalizations { @override String get planSuccess => '메뉴가 계획되었습니다!'; + + @override + String get planProductsTitle => '메뉴 재료'; + + @override + String get planProductsSubtitle => 'AI가 레시피 생성 시 선택한 재료를 고려합니다'; + + @override + String get planProductsEmpty => '추가된 재료가 없습니다'; + + @override + String get planProductsEmptyMessage => + '집에 있는 재료를 추가하세요 — AI가 이미 있는 재료로 레시피를 제안합니다'; + + @override + String get planProductsAddProducts => '재료 추가'; + + @override + String get planProductsContinue => '계속'; + + @override + String get planProductsSkip => '재료 선택 건너뛰기'; + + @override + String get planProductsSkipNoProducts => '재료 없이 계획하기'; + + @override + String get planProductsSelectAll => '모두 선택'; + + @override + String get planProductsDeselectAll => '모두 해제'; } diff --git a/client/lib/l10n/app_localizations_pt.dart b/client/lib/l10n/app_localizations_pt.dart index 02e07e2..e44fae5 100644 --- a/client/lib/l10n/app_localizations_pt.dart +++ b/client/lib/l10n/app_localizations_pt.dart @@ -422,4 +422,36 @@ class AppLocalizationsPt extends AppLocalizations { @override String get planSuccess => 'Menu planejado!'; + + @override + String get planProductsTitle => 'Produtos para o menu'; + + @override + String get planProductsSubtitle => + 'A IA levará em conta os produtos selecionados ao gerar receitas'; + + @override + String get planProductsEmpty => 'Nenhum produto adicionado'; + + @override + String get planProductsEmptyMessage => + 'Adicione produtos que você tem em casa — a IA sugerirá receitas com o que você já tem'; + + @override + String get planProductsAddProducts => 'Adicionar produtos'; + + @override + String get planProductsContinue => 'Continuar'; + + @override + String get planProductsSkip => 'Pular seleção de produtos'; + + @override + String get planProductsSkipNoProducts => 'Planejar sem produtos'; + + @override + String get planProductsSelectAll => 'Selecionar tudo'; + + @override + String get planProductsDeselectAll => 'Desmarcar tudo'; } diff --git a/client/lib/l10n/app_localizations_ru.dart b/client/lib/l10n/app_localizations_ru.dart index da19b4e..00d74f4 100644 --- a/client/lib/l10n/app_localizations_ru.dart +++ b/client/lib/l10n/app_localizations_ru.dart @@ -420,4 +420,36 @@ class AppLocalizationsRu extends AppLocalizations { @override String get planSuccess => 'Меню запланировано!'; + + @override + String get planProductsTitle => 'Продукты для меню'; + + @override + String get planProductsSubtitle => + 'AI учтёт выбранные продукты при составлении рецептов'; + + @override + String get planProductsEmpty => 'Продукты не добавлены'; + + @override + String get planProductsEmptyMessage => + 'Добавьте продукты, которые есть у вас дома — AI подберёт рецепты из того, что уже есть'; + + @override + String get planProductsAddProducts => 'Добавить продукты'; + + @override + String get planProductsContinue => 'Продолжить'; + + @override + String get planProductsSkip => 'Пропустить выбор продуктов'; + + @override + String get planProductsSkipNoProducts => 'Планировать без продуктов'; + + @override + String get planProductsSelectAll => 'Выбрать все'; + + @override + String get planProductsDeselectAll => 'Снять всё'; } diff --git a/client/lib/l10n/app_localizations_zh.dart b/client/lib/l10n/app_localizations_zh.dart index b33e61d..9f36aa9 100644 --- a/client/lib/l10n/app_localizations_zh.dart +++ b/client/lib/l10n/app_localizations_zh.dart @@ -418,4 +418,34 @@ class AppLocalizationsZh extends AppLocalizations { @override String get planSuccess => '菜单已规划!'; + + @override + String get planProductsTitle => '菜单食材'; + + @override + String get planProductsSubtitle => 'AI在生成食谱时会考虑所选食材'; + + @override + String get planProductsEmpty => '尚未添加食材'; + + @override + String get planProductsEmptyMessage => '添加您家中的食材 — AI将根据您已有的食材推荐食谱'; + + @override + String get planProductsAddProducts => '添加食材'; + + @override + String get planProductsContinue => '继续'; + + @override + String get planProductsSkip => '跳过食材选择'; + + @override + String get planProductsSkipNoProducts => '不选食材直接规划'; + + @override + String get planProductsSelectAll => '全选'; + + @override + String get planProductsDeselectAll => '取消全选'; } diff --git a/client/lib/l10n/app_pt.arb b/client/lib/l10n/app_pt.arb index cb3f9f3..3d2c53f 100644 --- a/client/lib/l10n/app_pt.arb +++ b/client/lib/l10n/app_pt.arb @@ -118,7 +118,9 @@ "noResultsForQuery": "Nada encontrado para \"{query}\"", "@noResultsForQuery": { "placeholders": { - "query": { "type": "String" } + "query": { + "type": "String" + } } }, "servingsLabel": "Porções", @@ -127,7 +129,9 @@ "planningForDate": "", "@planningForDate": { "placeholders": { - "date": { "type": "String" } + "date": { + "type": "String" + } } }, "markAsEaten": "Marcar como comido", @@ -136,7 +140,6 @@ "generateWeekSubtitle": "A IA criará um menu com café da manhã, almoço e jantar para a semana inteira", "generatingMenu": "Gerando menu...", "dayPlannedLabel": "Dia planejado", - "planMenuButton": "Planejar refeições", "planMenuTitle": "O que planejar?", "planOptionSingleMeal": "Uma refeição", @@ -151,6 +154,16 @@ "planSelectMealType": "Tipo de refeição", "planSelectRange": "Selecionar período", "planGenerateButton": "Planejar", - "planGenerating": "Gerando plano\u2026", - "planSuccess": "Menu planejado!" + "planGenerating": "Gerando plano…", + "planSuccess": "Menu planejado!", + "planProductsTitle": "Produtos para o menu", + "planProductsSubtitle": "A IA levará em conta os produtos selecionados ao gerar receitas", + "planProductsEmpty": "Nenhum produto adicionado", + "planProductsEmptyMessage": "Adicione produtos que você tem em casa — a IA sugerirá receitas com o que você já tem", + "planProductsAddProducts": "Adicionar produtos", + "planProductsContinue": "Continuar", + "planProductsSkip": "Pular seleção de produtos", + "planProductsSkipNoProducts": "Planejar sem produtos", + "planProductsSelectAll": "Selecionar tudo", + "planProductsDeselectAll": "Desmarcar tudo" } diff --git a/client/lib/l10n/app_ru.arb b/client/lib/l10n/app_ru.arb index d4cf741..6d5ba0a 100644 --- a/client/lib/l10n/app_ru.arb +++ b/client/lib/l10n/app_ru.arb @@ -28,7 +28,9 @@ "queuePosition": "Позиция {position}", "@queuePosition": { "placeholders": { - "position": { "type": "int" } + "position": { + "type": "int" + } } }, "processing": "Обрабатываем...", @@ -116,7 +118,9 @@ "noResultsForQuery": "По запросу \"{query}\" ничего не найдено", "@noResultsForQuery": { "placeholders": { - "query": { "type": "String" } + "query": { + "type": "String" + } } }, "servingsLabel": "Порций", @@ -125,7 +129,9 @@ "planningForDate": "Планирование на {date}", "@planningForDate": { "placeholders": { - "date": { "type": "String" } + "date": { + "type": "String" + } } }, "markAsEaten": "Отметить как съеденное", @@ -134,7 +140,6 @@ "generateWeekSubtitle": "AI составит меню с завтраком, обедом и ужином на всю неделю", "generatingMenu": "Генерируем меню...", "dayPlannedLabel": "День запланирован", - "planMenuButton": "Спланировать меню", "planMenuTitle": "Что запланировать?", "planOptionSingleMeal": "1 приём пищи", @@ -149,6 +154,16 @@ "planSelectMealType": "Приём пищи", "planSelectRange": "Выберите период", "planGenerateButton": "Запланировать", - "planGenerating": "Генерирую план\u2026", - "planSuccess": "Меню запланировано!" + "planGenerating": "Генерирую план…", + "planSuccess": "Меню запланировано!", + "planProductsTitle": "Продукты для меню", + "planProductsSubtitle": "AI учтёт выбранные продукты при составлении рецептов", + "planProductsEmpty": "Продукты не добавлены", + "planProductsEmptyMessage": "Добавьте продукты, которые есть у вас дома — AI подберёт рецепты из того, что уже есть", + "planProductsAddProducts": "Добавить продукты", + "planProductsContinue": "Продолжить", + "planProductsSkip": "Пропустить выбор продуктов", + "planProductsSkipNoProducts": "Планировать без продуктов", + "planProductsSelectAll": "Выбрать все", + "planProductsDeselectAll": "Снять всё" } diff --git a/client/lib/l10n/app_zh.arb b/client/lib/l10n/app_zh.arb index 0131a66..8fb6c7f 100644 --- a/client/lib/l10n/app_zh.arb +++ b/client/lib/l10n/app_zh.arb @@ -118,7 +118,9 @@ "noResultsForQuery": "未找到 \"{query}\" 的结果", "@noResultsForQuery": { "placeholders": { - "query": { "type": "String" } + "query": { + "type": "String" + } } }, "servingsLabel": "份数", @@ -127,7 +129,9 @@ "planningForDate": "", "@planningForDate": { "placeholders": { - "date": { "type": "String" } + "date": { + "type": "String" + } } }, "markAsEaten": "标记为已吃", @@ -136,7 +140,6 @@ "generateWeekSubtitle": "AI将为整周创建含早餐、午餐和晚餐的菜单", "generatingMenu": "正在生成菜单...", "dayPlannedLabel": "今日已规划", - "planMenuButton": "规划餐食", "planMenuTitle": "规划什么?", "planOptionSingleMeal": "单次餐食", @@ -151,6 +154,16 @@ "planSelectMealType": "餐食类型", "planSelectRange": "选择时间段", "planGenerateButton": "规划", - "planGenerating": "正在生成计划\u2026", - "planSuccess": "菜单已规划!" + "planGenerating": "正在生成计划…", + "planSuccess": "菜单已规划!", + "planProductsTitle": "菜单食材", + "planProductsSubtitle": "AI在生成食谱时会考虑所选食材", + "planProductsEmpty": "尚未添加食材", + "planProductsEmptyMessage": "添加您家中的食材 — AI将根据您已有的食材推荐食谱", + "planProductsAddProducts": "添加食材", + "planProductsContinue": "继续", + "planProductsSkip": "跳过食材选择", + "planProductsSkipNoProducts": "不选食材直接规划", + "planProductsSelectAll": "全选", + "planProductsDeselectAll": "取消全选" }