feat: add product selection step before meal planning

Inserts a new PlanProductsSheet as step 1 of the planning flow.
Users see their current products as a multi-select checklist (all
selected by default) before choosing the planning mode and dates.

- Empty state explains the benefit and offers "Add products" CTA
  while always allowing "Plan without products" to skip
- Selected product IDs flow through PlanMenuSheet →
  PlanDatePickerSheet → MenuService.generateForDates → backend
- Backend: added ProductIDs field to generate-menu request body;
  uses ListForPromptByIDs when set, ListForPrompt otherwise
- Backend: added Repository.ListForPromptByIDs (filtered SQL query)
- All 12 ARB locale files updated with planProducts* keys

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
dbastrikin
2026-03-23 16:07:28 +02:00
parent b6c75a3488
commit b38190ff5b
33 changed files with 1007 additions and 77 deletions

View File

@@ -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.
@@ -124,6 +126,7 @@ func (h *Handler) GenerateMenu(w http.ResponseWriter, r *http.Request) {
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,9 +153,15 @@ func (h *Handler) GenerateMenu(w http.ResponseWriter, r *http.Request) {
menuReq := buildMenuRequest(u, locale.FromContext(r.Context()))
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)
if generateError != nil {
@@ -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,9 +221,15 @@ func (h *Handler) generateForDates(w http.ResponseWriter, r *http.Request, userI
menuReq := buildMenuRequest(u, locale.FromContext(r.Context()))
menuReq.MealTypes = mealTypes
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)
var plans []*MenuPlan

View File

@@ -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) {

View File

@@ -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,12 +1598,22 @@ class _FutureDayPlanButton extends ConsumerWidget {
void _openPlanSheet(BuildContext context, WidgetRef ref) {
final defaultStart = DateTime.parse(dateString);
// Step 1: product selection
showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
useSafeArea: true,
builder: (_) => PlanProductsSheet(
onContinue: (selectedProductIds) {
// Step 2: planning mode selection
showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
useSafeArea: true,
builder: (_) => PlanMenuSheet(
onModeSelected: (mode) {
selectedProductIds: selectedProductIds,
onModeSelected: (mode, productIds) {
// Step 3: date / meal type selection
showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
@@ -1610,6 +1621,10 @@ class _FutureDayPlanButton extends ConsumerWidget {
builder: (_) => PlanDatePickerSheet(
mode: mode,
defaultStart: defaultStart,
selectedProductIds: productIds,
),
);
},
),
);
},

View File

@@ -192,11 +192,13 @@ class PlanMenuService {
Future<void> generateForDates({
required List<String> dates,
required List<String> mealTypes,
List<String> 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))));

View File

@@ -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<List<MenuPlan>> generateForDates({
required List<String> dates,
required List<String> mealTypes,
List<String> productIds = const [],
}) async {
final data = await _client.post('/ai/generate-menu', data: {
final body = <String, dynamic>{
'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<dynamic>;
return plans
.map((planJson) => MenuPlan.fromJson(planJson as Map<String, dynamic>))

View File

@@ -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<String> selectedProductIds;
@override
ConsumerState<PlanDatePickerSheet> createState() =>
_PlanDatePickerSheetState();
@@ -107,6 +112,7 @@ class _PlanDatePickerSheetState extends ConsumerState<PlanDatePickerSheet> {
await ref.read(planMenuServiceProvider).generateForDates(
dates: dates,
mealTypes: mealTypes,
productIds: widget.selectedProductIds,
);
if (mounted) {
Navigator.pop(context);

View File

@@ -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<String> productIds) onModeSelected;
final List<String> 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);
}
}

View File

@@ -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<String> selectedProductIds) onContinue;
@override
ConsumerState<PlanProductsSheet> createState() => _PlanProductsSheetState();
}
class _PlanProductsSheetState extends ConsumerState<PlanProductsSheet> {
// 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<String>? _selected;
void _initSelected(List<UserProduct> products) {
if (_selected == null) {
_selected = {for (final product in products) product.id};
}
}
void _toggleAll(List<UserProduct> 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),
),
],
);
}
}

View File

@@ -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": "إلغاء تحديد الكل"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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": "सभी हटाएं"
}

View File

@@ -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"
}

View File

@@ -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": "すべて解除"
}

View File

@@ -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": "모두 해제"
}

View File

@@ -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

View File

@@ -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 => 'إلغاء تحديد الكل';
}

View File

@@ -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';
}

View File

@@ -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';
}

View File

@@ -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';
}

View File

@@ -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';
}

View File

@@ -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 => 'सभी हटाएं';
}

View File

@@ -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';
}

View File

@@ -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 => 'すべて解除';
}

View File

@@ -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 => '모두 해제';
}

View File

@@ -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';
}

View File

@@ -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 => 'Снять всё';
}

View File

@@ -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 => '取消全选';
}

View File

@@ -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"
}

View File

@@ -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": "Снять всё"
}

View File

@@ -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": "取消全选"
}