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:
@@ -31,6 +31,8 @@ type UserLoader interface {
|
|||||||
// ProductLister returns human-readable product lines for the AI prompt.
|
// ProductLister returns human-readable product lines for the AI prompt.
|
||||||
type ProductLister interface {
|
type ProductLister interface {
|
||||||
ListForPrompt(ctx context.Context, userID string) ([]string, error)
|
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.
|
// 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 {
|
var body struct {
|
||||||
Week string `json:"week"` // optional, defaults to current week
|
Week string `json:"week"` // optional, defaults to current week
|
||||||
Dates []string `json:"dates"` // YYYY-MM-DD; triggers partial generation
|
Dates []string `json:"dates"` // YYYY-MM-DD; triggers partial generation
|
||||||
MealTypes []string `json:"meal_types"` // overrides user preference when set
|
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)
|
_ = 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 {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,8 +153,14 @@ func (h *Handler) GenerateMenu(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
menuReq := buildMenuRequest(u, locale.FromContext(r.Context()))
|
menuReq := buildMenuRequest(u, locale.FromContext(r.Context()))
|
||||||
|
|
||||||
if products, productError := h.productLister.ListForPrompt(r.Context(), userID); productError == nil {
|
if len(body.ProductIDs) > 0 {
|
||||||
menuReq.AvailableProducts = products
|
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)
|
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.
|
// generateForDates handles partial menu generation for specific dates and meal types.
|
||||||
// It groups dates by ISO week, generates only the requested slots, upserts them
|
// It groups dates by ISO week, generates only the requested slots, upserts them
|
||||||
// without touching other existing slots, and returns {"plans":[...]}.
|
// 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
|
mealTypes := requestedMealTypes
|
||||||
if len(mealTypes) == 0 {
|
if len(mealTypes) == 0 {
|
||||||
// Fall back to user's preferred meal types.
|
// 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 := buildMenuRequest(u, locale.FromContext(r.Context()))
|
||||||
menuReq.MealTypes = mealTypes
|
menuReq.MealTypes = mealTypes
|
||||||
if products, productError := h.productLister.ListForPrompt(r.Context(), userID); productError == nil {
|
if len(productIDs) > 0 {
|
||||||
menuReq.AvailableProducts = products
|
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)
|
weekGroups := groupDatesByWeek(dates)
|
||||||
|
|||||||
@@ -161,6 +161,46 @@ func (r *Repository) ListForPrompt(requestContext context.Context, userID string
|
|||||||
return lines, rows.Err()
|
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 ---
|
// --- helpers ---
|
||||||
|
|
||||||
func scanUserProduct(row pgx.Row) (*UserProduct, error) {
|
func scanUserProduct(row pgx.Row) (*UserProduct, error) {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import '../diary/food_search_sheet.dart';
|
|||||||
import '../menu/menu_provider.dart';
|
import '../menu/menu_provider.dart';
|
||||||
import '../menu/plan_date_picker_sheet.dart';
|
import '../menu/plan_date_picker_sheet.dart';
|
||||||
import '../menu/plan_menu_sheet.dart';
|
import '../menu/plan_menu_sheet.dart';
|
||||||
|
import '../menu/plan_products_sheet.dart';
|
||||||
import '../profile/profile_provider.dart';
|
import '../profile/profile_provider.dart';
|
||||||
import '../scan/dish_result_screen.dart';
|
import '../scan/dish_result_screen.dart';
|
||||||
import '../scan/recognition_service.dart';
|
import '../scan/recognition_service.dart';
|
||||||
@@ -1597,19 +1598,33 @@ class _FutureDayPlanButton extends ConsumerWidget {
|
|||||||
|
|
||||||
void _openPlanSheet(BuildContext context, WidgetRef ref) {
|
void _openPlanSheet(BuildContext context, WidgetRef ref) {
|
||||||
final defaultStart = DateTime.parse(dateString);
|
final defaultStart = DateTime.parse(dateString);
|
||||||
|
// Step 1: product selection
|
||||||
showModalBottomSheet<void>(
|
showModalBottomSheet<void>(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
useSafeArea: true,
|
useSafeArea: true,
|
||||||
builder: (_) => PlanMenuSheet(
|
builder: (_) => PlanProductsSheet(
|
||||||
onModeSelected: (mode) {
|
onContinue: (selectedProductIds) {
|
||||||
|
// Step 2: planning mode selection
|
||||||
showModalBottomSheet<void>(
|
showModalBottomSheet<void>(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
useSafeArea: true,
|
useSafeArea: true,
|
||||||
builder: (_) => PlanDatePickerSheet(
|
builder: (_) => PlanMenuSheet(
|
||||||
mode: mode,
|
selectedProductIds: selectedProductIds,
|
||||||
defaultStart: defaultStart,
|
onModeSelected: (mode, productIds) {
|
||||||
|
// Step 3: date / meal type selection
|
||||||
|
showModalBottomSheet<void>(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
useSafeArea: true,
|
||||||
|
builder: (_) => PlanDatePickerSheet(
|
||||||
|
mode: mode,
|
||||||
|
defaultStart: defaultStart,
|
||||||
|
selectedProductIds: productIds,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -192,11 +192,13 @@ class PlanMenuService {
|
|||||||
Future<void> generateForDates({
|
Future<void> generateForDates({
|
||||||
required List<String> dates,
|
required List<String> dates,
|
||||||
required List<String> mealTypes,
|
required List<String> mealTypes,
|
||||||
|
List<String> productIds = const [],
|
||||||
}) async {
|
}) async {
|
||||||
final menuService = _ref.read(menuServiceProvider);
|
final menuService = _ref.read(menuServiceProvider);
|
||||||
final plans = await menuService.generateForDates(
|
final plans = await menuService.generateForDates(
|
||||||
dates: dates,
|
dates: dates,
|
||||||
mealTypes: mealTypes,
|
mealTypes: mealTypes,
|
||||||
|
productIds: productIds,
|
||||||
);
|
);
|
||||||
for (final plan in plans) {
|
for (final plan in plans) {
|
||||||
_ref.invalidate(menuProvider(isoWeekString(DateTime.parse(plan.weekStart))));
|
_ref.invalidate(menuProvider(isoWeekString(DateTime.parse(plan.weekStart))));
|
||||||
|
|||||||
@@ -27,15 +27,19 @@ class MenuService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Generates meals for specific [dates] (YYYY-MM-DD) and [mealTypes].
|
/// 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.
|
/// Returns the updated MenuPlan for each affected week.
|
||||||
Future<List<MenuPlan>> generateForDates({
|
Future<List<MenuPlan>> generateForDates({
|
||||||
required List<String> dates,
|
required List<String> dates,
|
||||||
required List<String> mealTypes,
|
required List<String> mealTypes,
|
||||||
|
List<String> productIds = const [],
|
||||||
}) async {
|
}) async {
|
||||||
final data = await _client.post('/ai/generate-menu', data: {
|
final body = <String, dynamic>{
|
||||||
'dates': dates,
|
'dates': dates,
|
||||||
'meal_types': mealTypes,
|
'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>;
|
final plans = data['plans'] as List<dynamic>;
|
||||||
return plans
|
return plans
|
||||||
.map((planJson) => MenuPlan.fromJson(planJson as Map<String, dynamic>))
|
.map((planJson) => MenuPlan.fromJson(planJson as Map<String, dynamic>))
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class PlanDatePickerSheet extends ConsumerStatefulWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
required this.mode,
|
required this.mode,
|
||||||
required this.defaultStart,
|
required this.defaultStart,
|
||||||
|
required this.selectedProductIds,
|
||||||
});
|
});
|
||||||
|
|
||||||
final PlanMode mode;
|
final PlanMode mode;
|
||||||
@@ -24,6 +25,10 @@ class PlanDatePickerSheet extends ConsumerStatefulWidget {
|
|||||||
/// planned date.
|
/// planned date.
|
||||||
final DateTime defaultStart;
|
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
|
@override
|
||||||
ConsumerState<PlanDatePickerSheet> createState() =>
|
ConsumerState<PlanDatePickerSheet> createState() =>
|
||||||
_PlanDatePickerSheetState();
|
_PlanDatePickerSheetState();
|
||||||
@@ -107,6 +112,7 @@ class _PlanDatePickerSheetState extends ConsumerState<PlanDatePickerSheet> {
|
|||||||
await ref.read(planMenuServiceProvider).generateForDates(
|
await ref.read(planMenuServiceProvider).generateForDates(
|
||||||
dates: dates,
|
dates: dates,
|
||||||
mealTypes: mealTypes,
|
mealTypes: mealTypes,
|
||||||
|
productIds: widget.selectedProductIds,
|
||||||
);
|
);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
|||||||
@@ -5,11 +5,17 @@ import 'package:food_ai/l10n/app_localizations.dart';
|
|||||||
enum PlanMode { singleMeal, singleDay, days, week }
|
enum PlanMode { singleMeal, singleDay, days, week }
|
||||||
|
|
||||||
/// Bottom sheet that lets the user choose a planning horizon.
|
/// 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 {
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -61,7 +67,7 @@ class PlanMenuSheet extends StatelessWidget {
|
|||||||
|
|
||||||
void _select(BuildContext context, PlanMode mode) {
|
void _select(BuildContext context, PlanMode mode) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
onModeSelected(mode);
|
onModeSelected(mode, selectedProductIds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
247
client/lib/features/menu/plan_products_sheet.dart
Normal file
247
client/lib/features/menu/plan_products_sheet.dart
Normal 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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -118,7 +118,9 @@
|
|||||||
"noResultsForQuery": "لم يتم العثور على نتائج لـ \"{query}\"",
|
"noResultsForQuery": "لم يتم العثور على نتائج لـ \"{query}\"",
|
||||||
"@noResultsForQuery": {
|
"@noResultsForQuery": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"query": { "type": "String" }
|
"query": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"servingsLabel": "حصص",
|
"servingsLabel": "حصص",
|
||||||
@@ -127,7 +129,9 @@
|
|||||||
"planningForDate": "",
|
"planningForDate": "",
|
||||||
"@planningForDate": {
|
"@planningForDate": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"date": { "type": "String" }
|
"date": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"markAsEaten": "وضع علامة كمأكول",
|
"markAsEaten": "وضع علامة كمأكول",
|
||||||
@@ -136,7 +140,6 @@
|
|||||||
"generateWeekSubtitle": "سيقوم الذكاء الاصطناعي بإنشاء قائمة طعام تشمل الإفطار والغداء والعشاء لكامل الأسبوع",
|
"generateWeekSubtitle": "سيقوم الذكاء الاصطناعي بإنشاء قائمة طعام تشمل الإفطار والغداء والعشاء لكامل الأسبوع",
|
||||||
"generatingMenu": "جارٍ إنشاء القائمة...",
|
"generatingMenu": "جارٍ إنشاء القائمة...",
|
||||||
"dayPlannedLabel": "تم تخطيط اليوم",
|
"dayPlannedLabel": "تم تخطيط اليوم",
|
||||||
|
|
||||||
"planMenuButton": "تخطيط الوجبات",
|
"planMenuButton": "تخطيط الوجبات",
|
||||||
"planMenuTitle": "ماذا تريد تخطيطه؟",
|
"planMenuTitle": "ماذا تريد تخطيطه؟",
|
||||||
"planOptionSingleMeal": "وجبة واحدة",
|
"planOptionSingleMeal": "وجبة واحدة",
|
||||||
@@ -151,6 +154,16 @@
|
|||||||
"planSelectMealType": "نوع الوجبة",
|
"planSelectMealType": "نوع الوجبة",
|
||||||
"planSelectRange": "اختر الفترة",
|
"planSelectRange": "اختر الفترة",
|
||||||
"planGenerateButton": "تخطيط",
|
"planGenerateButton": "تخطيط",
|
||||||
"planGenerating": "جارٍ إنشاء الخطة\u2026",
|
"planGenerating": "جارٍ إنشاء الخطة…",
|
||||||
"planSuccess": "تم تخطيط القائمة!"
|
"planSuccess": "تم تخطيط القائمة!",
|
||||||
|
"planProductsTitle": "مكونات القائمة",
|
||||||
|
"planProductsSubtitle": "سيأخذ الذكاء الاصطناعي المكونات المختارة بعين الاعتبار عند إنشاء الوصفات",
|
||||||
|
"planProductsEmpty": "لم يتم إضافة منتجات",
|
||||||
|
"planProductsEmptyMessage": "أضف المنتجات الموجودة لديك في المنزل — سيقترح الذكاء الاصطناعي وصفات مما لديك بالفعل",
|
||||||
|
"planProductsAddProducts": "إضافة منتجات",
|
||||||
|
"planProductsContinue": "متابعة",
|
||||||
|
"planProductsSkip": "تخطي اختيار المنتجات",
|
||||||
|
"planProductsSkipNoProducts": "التخطيط بدون منتجات",
|
||||||
|
"planProductsSelectAll": "تحديد الكل",
|
||||||
|
"planProductsDeselectAll": "إلغاء تحديد الكل"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,9 @@
|
|||||||
"noResultsForQuery": "Keine Ergebnisse für \"{query}\"",
|
"noResultsForQuery": "Keine Ergebnisse für \"{query}\"",
|
||||||
"@noResultsForQuery": {
|
"@noResultsForQuery": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"query": { "type": "String" }
|
"query": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"servingsLabel": "Portionen",
|
"servingsLabel": "Portionen",
|
||||||
@@ -127,7 +129,9 @@
|
|||||||
"planningForDate": "",
|
"planningForDate": "",
|
||||||
"@planningForDate": {
|
"@planningForDate": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"date": { "type": "String" }
|
"date": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"markAsEaten": "Als gegessen markieren",
|
"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",
|
"generateWeekSubtitle": "KI erstellt einen Menüplan mit Frühstück, Mittagessen und Abendessen für die ganze Woche",
|
||||||
"generatingMenu": "Menü wird erstellt...",
|
"generatingMenu": "Menü wird erstellt...",
|
||||||
"dayPlannedLabel": "Tag geplant",
|
"dayPlannedLabel": "Tag geplant",
|
||||||
|
|
||||||
"planMenuButton": "Mahlzeiten planen",
|
"planMenuButton": "Mahlzeiten planen",
|
||||||
"planMenuTitle": "Was planen?",
|
"planMenuTitle": "Was planen?",
|
||||||
"planOptionSingleMeal": "Einzelne Mahlzeit",
|
"planOptionSingleMeal": "Einzelne Mahlzeit",
|
||||||
@@ -151,6 +154,16 @@
|
|||||||
"planSelectMealType": "Mahlzeittyp",
|
"planSelectMealType": "Mahlzeittyp",
|
||||||
"planSelectRange": "Zeitraum wählen",
|
"planSelectRange": "Zeitraum wählen",
|
||||||
"planGenerateButton": "Planen",
|
"planGenerateButton": "Planen",
|
||||||
"planGenerating": "Plan wird erstellt\u2026",
|
"planGenerating": "Plan wird erstellt…",
|
||||||
"planSuccess": "Menü geplant!"
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,5 +150,15 @@
|
|||||||
"planSelectRange": "Select period",
|
"planSelectRange": "Select period",
|
||||||
"planGenerateButton": "Plan",
|
"planGenerateButton": "Plan",
|
||||||
"planGenerating": "Generating plan\u2026",
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,9 @@
|
|||||||
"noResultsForQuery": "Nada encontrado para \"{query}\"",
|
"noResultsForQuery": "Nada encontrado para \"{query}\"",
|
||||||
"@noResultsForQuery": {
|
"@noResultsForQuery": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"query": { "type": "String" }
|
"query": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"servingsLabel": "Porciones",
|
"servingsLabel": "Porciones",
|
||||||
@@ -127,7 +129,9 @@
|
|||||||
"planningForDate": "",
|
"planningForDate": "",
|
||||||
"@planningForDate": {
|
"@planningForDate": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"date": { "type": "String" }
|
"date": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"markAsEaten": "Marcar como comido",
|
"markAsEaten": "Marcar como comido",
|
||||||
@@ -136,7 +140,6 @@
|
|||||||
"generateWeekSubtitle": "La IA creará un menú con desayuno, comida y cena para toda la semana",
|
"generateWeekSubtitle": "La IA creará un menú con desayuno, comida y cena para toda la semana",
|
||||||
"generatingMenu": "Generando menú...",
|
"generatingMenu": "Generando menú...",
|
||||||
"dayPlannedLabel": "Día planificado",
|
"dayPlannedLabel": "Día planificado",
|
||||||
|
|
||||||
"planMenuButton": "Planificar comidas",
|
"planMenuButton": "Planificar comidas",
|
||||||
"planMenuTitle": "¿Qué planificar?",
|
"planMenuTitle": "¿Qué planificar?",
|
||||||
"planOptionSingleMeal": "Una comida",
|
"planOptionSingleMeal": "Una comida",
|
||||||
@@ -151,6 +154,16 @@
|
|||||||
"planSelectMealType": "Tipo de comida",
|
"planSelectMealType": "Tipo de comida",
|
||||||
"planSelectRange": "Seleccionar período",
|
"planSelectRange": "Seleccionar período",
|
||||||
"planGenerateButton": "Planificar",
|
"planGenerateButton": "Planificar",
|
||||||
"planGenerating": "Generando plan\u2026",
|
"planGenerating": "Generando plan…",
|
||||||
"planSuccess": "¡Menú planificado!"
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,9 @@
|
|||||||
"noResultsForQuery": "Rien trouvé pour \"{query}\"",
|
"noResultsForQuery": "Rien trouvé pour \"{query}\"",
|
||||||
"@noResultsForQuery": {
|
"@noResultsForQuery": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"query": { "type": "String" }
|
"query": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"servingsLabel": "Portions",
|
"servingsLabel": "Portions",
|
||||||
@@ -127,7 +129,9 @@
|
|||||||
"planningForDate": "",
|
"planningForDate": "",
|
||||||
"@planningForDate": {
|
"@planningForDate": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"date": { "type": "String" }
|
"date": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"markAsEaten": "Marquer comme mangé",
|
"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",
|
"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...",
|
"generatingMenu": "Génération du menu...",
|
||||||
"dayPlannedLabel": "Jour planifié",
|
"dayPlannedLabel": "Jour planifié",
|
||||||
|
|
||||||
"planMenuButton": "Planifier les repas",
|
"planMenuButton": "Planifier les repas",
|
||||||
"planMenuTitle": "Que planifier ?",
|
"planMenuTitle": "Que planifier ?",
|
||||||
"planOptionSingleMeal": "Un repas",
|
"planOptionSingleMeal": "Un repas",
|
||||||
@@ -151,6 +154,16 @@
|
|||||||
"planSelectMealType": "Type de repas",
|
"planSelectMealType": "Type de repas",
|
||||||
"planSelectRange": "Choisir la période",
|
"planSelectRange": "Choisir la période",
|
||||||
"planGenerateButton": "Planifier",
|
"planGenerateButton": "Planifier",
|
||||||
"planGenerating": "Génération du plan\u2026",
|
"planGenerating": "Génération du plan…",
|
||||||
"planSuccess": "Menu planifié !"
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,9 @@
|
|||||||
"noResultsForQuery": "\"{query}\" के लिए कुछ नहीं मिला",
|
"noResultsForQuery": "\"{query}\" के लिए कुछ नहीं मिला",
|
||||||
"@noResultsForQuery": {
|
"@noResultsForQuery": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"query": { "type": "String" }
|
"query": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"servingsLabel": "सर्विंग",
|
"servingsLabel": "सर्विंग",
|
||||||
@@ -127,7 +129,9 @@
|
|||||||
"planningForDate": "",
|
"planningForDate": "",
|
||||||
"@planningForDate": {
|
"@planningForDate": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"date": { "type": "String" }
|
"date": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"markAsEaten": "खाया हुआ चिह्नित करें",
|
"markAsEaten": "खाया हुआ चिह्नित करें",
|
||||||
@@ -136,7 +140,6 @@
|
|||||||
"generateWeekSubtitle": "AI पूरे सप्ताह के लिए नाश्ता, दोपहर का खाना और रात के खाने के साथ मेनू बनाएगा",
|
"generateWeekSubtitle": "AI पूरे सप्ताह के लिए नाश्ता, दोपहर का खाना और रात के खाने के साथ मेनू बनाएगा",
|
||||||
"generatingMenu": "मेनू बना रहे हैं...",
|
"generatingMenu": "मेनू बना रहे हैं...",
|
||||||
"dayPlannedLabel": "दिन की योजना बनाई गई",
|
"dayPlannedLabel": "दिन की योजना बनाई गई",
|
||||||
|
|
||||||
"planMenuButton": "भोजन की योजना बनाएं",
|
"planMenuButton": "भोजन की योजना बनाएं",
|
||||||
"planMenuTitle": "क्या योजना बनानी है?",
|
"planMenuTitle": "क्या योजना बनानी है?",
|
||||||
"planOptionSingleMeal": "एक भोजन",
|
"planOptionSingleMeal": "एक भोजन",
|
||||||
@@ -151,6 +154,16 @@
|
|||||||
"planSelectMealType": "भोजन का प्रकार",
|
"planSelectMealType": "भोजन का प्रकार",
|
||||||
"planSelectRange": "अवधि चुनें",
|
"planSelectRange": "अवधि चुनें",
|
||||||
"planGenerateButton": "योजना बनाएं",
|
"planGenerateButton": "योजना बनाएं",
|
||||||
"planGenerating": "योजना बना रहे हैं\u2026",
|
"planGenerating": "योजना बना रहे हैं…",
|
||||||
"planSuccess": "मेनू की योजना बनाई गई!"
|
"planSuccess": "मेनू की योजना बनाई गई!",
|
||||||
|
"planProductsTitle": "मेनू के लिए उत्पाद",
|
||||||
|
"planProductsSubtitle": "AI रेसिपी बनाते समय चुने हुए उत्पादों को ध्यान में रखेगा",
|
||||||
|
"planProductsEmpty": "कोई उत्पाद नहीं जोड़ा गया",
|
||||||
|
"planProductsEmptyMessage": "घर पर उपलब्ध उत्पाद जोड़ें — AI आपके पास पहले से मौजूद चीज़ों से रेसिपी सुझाएगा",
|
||||||
|
"planProductsAddProducts": "उत्पाद जोड़ें",
|
||||||
|
"planProductsContinue": "जारी रखें",
|
||||||
|
"planProductsSkip": "उत्पाद चयन छोड़ें",
|
||||||
|
"planProductsSkipNoProducts": "उत्पादों के बिना योजना बनाएं",
|
||||||
|
"planProductsSelectAll": "सभी चुनें",
|
||||||
|
"planProductsDeselectAll": "सभी हटाएं"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,9 @@
|
|||||||
"noResultsForQuery": "Nessun risultato per \"{query}\"",
|
"noResultsForQuery": "Nessun risultato per \"{query}\"",
|
||||||
"@noResultsForQuery": {
|
"@noResultsForQuery": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"query": { "type": "String" }
|
"query": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"servingsLabel": "Porzioni",
|
"servingsLabel": "Porzioni",
|
||||||
@@ -127,7 +129,9 @@
|
|||||||
"planningForDate": "",
|
"planningForDate": "",
|
||||||
"@planningForDate": {
|
"@planningForDate": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"date": { "type": "String" }
|
"date": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"markAsEaten": "Segna come mangiato",
|
"markAsEaten": "Segna come mangiato",
|
||||||
@@ -136,7 +140,6 @@
|
|||||||
"generateWeekSubtitle": "L'AI creerà un menu con colazione, pranzo e cena per tutta la settimana",
|
"generateWeekSubtitle": "L'AI creerà un menu con colazione, pranzo e cena per tutta la settimana",
|
||||||
"generatingMenu": "Generazione menu...",
|
"generatingMenu": "Generazione menu...",
|
||||||
"dayPlannedLabel": "Giorno pianificato",
|
"dayPlannedLabel": "Giorno pianificato",
|
||||||
|
|
||||||
"planMenuButton": "Pianifica i pasti",
|
"planMenuButton": "Pianifica i pasti",
|
||||||
"planMenuTitle": "Cosa pianificare?",
|
"planMenuTitle": "Cosa pianificare?",
|
||||||
"planOptionSingleMeal": "Un pasto",
|
"planOptionSingleMeal": "Un pasto",
|
||||||
@@ -151,6 +154,16 @@
|
|||||||
"planSelectMealType": "Tipo di pasto",
|
"planSelectMealType": "Tipo di pasto",
|
||||||
"planSelectRange": "Seleziona periodo",
|
"planSelectRange": "Seleziona periodo",
|
||||||
"planGenerateButton": "Pianifica",
|
"planGenerateButton": "Pianifica",
|
||||||
"planGenerating": "Generazione piano\u2026",
|
"planGenerating": "Generazione piano…",
|
||||||
"planSuccess": "Menu pianificato!"
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,9 @@
|
|||||||
"noResultsForQuery": "「{query}」の検索結果はありません",
|
"noResultsForQuery": "「{query}」の検索結果はありません",
|
||||||
"@noResultsForQuery": {
|
"@noResultsForQuery": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"query": { "type": "String" }
|
"query": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"servingsLabel": "人前",
|
"servingsLabel": "人前",
|
||||||
@@ -127,7 +129,9 @@
|
|||||||
"planningForDate": "",
|
"planningForDate": "",
|
||||||
"@planningForDate": {
|
"@planningForDate": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"date": { "type": "String" }
|
"date": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"markAsEaten": "食べた印をつける",
|
"markAsEaten": "食べた印をつける",
|
||||||
@@ -136,7 +140,6 @@
|
|||||||
"generateWeekSubtitle": "AIが一週間の朝食・昼食・夕食のメニューを作成します",
|
"generateWeekSubtitle": "AIが一週間の朝食・昼食・夕食のメニューを作成します",
|
||||||
"generatingMenu": "メニューを生成中...",
|
"generatingMenu": "メニューを生成中...",
|
||||||
"dayPlannedLabel": "日の計画済み",
|
"dayPlannedLabel": "日の計画済み",
|
||||||
|
|
||||||
"planMenuButton": "食事を計画する",
|
"planMenuButton": "食事を計画する",
|
||||||
"planMenuTitle": "何を計画する?",
|
"planMenuTitle": "何を計画する?",
|
||||||
"planOptionSingleMeal": "1食",
|
"planOptionSingleMeal": "1食",
|
||||||
@@ -151,6 +154,16 @@
|
|||||||
"planSelectMealType": "食事タイプ",
|
"planSelectMealType": "食事タイプ",
|
||||||
"planSelectRange": "期間を選択",
|
"planSelectRange": "期間を選択",
|
||||||
"planGenerateButton": "計画する",
|
"planGenerateButton": "計画する",
|
||||||
"planGenerating": "プランを生成中\u2026",
|
"planGenerating": "プランを生成中…",
|
||||||
"planSuccess": "メニューが計画されました!"
|
"planSuccess": "メニューが計画されました!",
|
||||||
|
"planProductsTitle": "メニューの食材",
|
||||||
|
"planProductsSubtitle": "AIはレシピ生成時に選択した食材を考慮します",
|
||||||
|
"planProductsEmpty": "食材が追加されていません",
|
||||||
|
"planProductsEmptyMessage": "家にある食材を追加してください — AIが手持ちの食材でレシピを提案します",
|
||||||
|
"planProductsAddProducts": "食材を追加",
|
||||||
|
"planProductsContinue": "続ける",
|
||||||
|
"planProductsSkip": "食材選択をスキップ",
|
||||||
|
"planProductsSkipNoProducts": "食材なしでプランニング",
|
||||||
|
"planProductsSelectAll": "すべて選択",
|
||||||
|
"planProductsDeselectAll": "すべて解除"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,9 @@
|
|||||||
"noResultsForQuery": "\"{query}\"에 대한 결과 없음",
|
"noResultsForQuery": "\"{query}\"에 대한 결과 없음",
|
||||||
"@noResultsForQuery": {
|
"@noResultsForQuery": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"query": { "type": "String" }
|
"query": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"servingsLabel": "인분",
|
"servingsLabel": "인분",
|
||||||
@@ -127,7 +129,9 @@
|
|||||||
"planningForDate": "",
|
"planningForDate": "",
|
||||||
"@planningForDate": {
|
"@planningForDate": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"date": { "type": "String" }
|
"date": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"markAsEaten": "먹은 것으로 표시",
|
"markAsEaten": "먹은 것으로 표시",
|
||||||
@@ -136,7 +140,6 @@
|
|||||||
"generateWeekSubtitle": "AI가 한 주 동안 아침, 점심, 저녁 식사 메뉴를 만들어 드립니다",
|
"generateWeekSubtitle": "AI가 한 주 동안 아침, 점심, 저녁 식사 메뉴를 만들어 드립니다",
|
||||||
"generatingMenu": "메뉴 생성 중...",
|
"generatingMenu": "메뉴 생성 중...",
|
||||||
"dayPlannedLabel": "일일 계획 완료",
|
"dayPlannedLabel": "일일 계획 완료",
|
||||||
|
|
||||||
"planMenuButton": "식사 계획하기",
|
"planMenuButton": "식사 계획하기",
|
||||||
"planMenuTitle": "무엇을 계획하시겠어요?",
|
"planMenuTitle": "무엇을 계획하시겠어요?",
|
||||||
"planOptionSingleMeal": "식사 1회",
|
"planOptionSingleMeal": "식사 1회",
|
||||||
@@ -151,6 +154,16 @@
|
|||||||
"planSelectMealType": "식사 유형",
|
"planSelectMealType": "식사 유형",
|
||||||
"planSelectRange": "기간 선택",
|
"planSelectRange": "기간 선택",
|
||||||
"planGenerateButton": "계획하기",
|
"planGenerateButton": "계획하기",
|
||||||
"planGenerating": "플랜 생성 중\u2026",
|
"planGenerating": "플랜 생성 중…",
|
||||||
"planSuccess": "메뉴가 계획되었습니다!"
|
"planSuccess": "메뉴가 계획되었습니다!",
|
||||||
|
"planProductsTitle": "메뉴 재료",
|
||||||
|
"planProductsSubtitle": "AI가 레시피 생성 시 선택한 재료를 고려합니다",
|
||||||
|
"planProductsEmpty": "추가된 재료가 없습니다",
|
||||||
|
"planProductsEmptyMessage": "집에 있는 재료를 추가하세요 — AI가 이미 있는 재료로 레시피를 제안합니다",
|
||||||
|
"planProductsAddProducts": "재료 추가",
|
||||||
|
"planProductsContinue": "계속",
|
||||||
|
"planProductsSkip": "재료 선택 건너뛰기",
|
||||||
|
"planProductsSkipNoProducts": "재료 없이 계획하기",
|
||||||
|
"planProductsSelectAll": "모두 선택",
|
||||||
|
"planProductsDeselectAll": "모두 해제"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -927,6 +927,66 @@ abstract class AppLocalizations {
|
|||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Menu planned!'**
|
/// **'Menu planned!'**
|
||||||
String get planSuccess;
|
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
|
class _AppLocalizationsDelegate
|
||||||
|
|||||||
@@ -420,4 +420,36 @@ class AppLocalizationsAr extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get planSuccess => 'تم تخطيط القائمة!';
|
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 => 'إلغاء تحديد الكل';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -422,4 +422,36 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get planSuccess => 'Menü geplant!';
|
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';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -420,4 +420,36 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get planSuccess => 'Menu planned!';
|
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';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -422,4 +422,36 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get planSuccess => '¡Menú planificado!';
|
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';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -423,4 +423,36 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get planSuccess => 'Menu planifié !';
|
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';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -421,4 +421,36 @@ class AppLocalizationsHi extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get planSuccess => 'मेनू की योजना बनाई गई!';
|
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 => 'सभी हटाएं';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -422,4 +422,36 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get planSuccess => 'Menu pianificato!';
|
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';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -418,4 +418,35 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get planSuccess => 'メニューが計画されました!';
|
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 => 'すべて解除';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -418,4 +418,35 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get planSuccess => '메뉴가 계획되었습니다!';
|
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 => '모두 해제';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -422,4 +422,36 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get planSuccess => 'Menu planejado!';
|
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';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -420,4 +420,36 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get planSuccess => 'Меню запланировано!';
|
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 => 'Снять всё';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -418,4 +418,34 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get planSuccess => '菜单已规划!';
|
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 => '取消全选';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,9 @@
|
|||||||
"noResultsForQuery": "Nada encontrado para \"{query}\"",
|
"noResultsForQuery": "Nada encontrado para \"{query}\"",
|
||||||
"@noResultsForQuery": {
|
"@noResultsForQuery": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"query": { "type": "String" }
|
"query": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"servingsLabel": "Porções",
|
"servingsLabel": "Porções",
|
||||||
@@ -127,7 +129,9 @@
|
|||||||
"planningForDate": "",
|
"planningForDate": "",
|
||||||
"@planningForDate": {
|
"@planningForDate": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"date": { "type": "String" }
|
"date": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"markAsEaten": "Marcar como comido",
|
"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",
|
"generateWeekSubtitle": "A IA criará um menu com café da manhã, almoço e jantar para a semana inteira",
|
||||||
"generatingMenu": "Gerando menu...",
|
"generatingMenu": "Gerando menu...",
|
||||||
"dayPlannedLabel": "Dia planejado",
|
"dayPlannedLabel": "Dia planejado",
|
||||||
|
|
||||||
"planMenuButton": "Planejar refeições",
|
"planMenuButton": "Planejar refeições",
|
||||||
"planMenuTitle": "O que planejar?",
|
"planMenuTitle": "O que planejar?",
|
||||||
"planOptionSingleMeal": "Uma refeição",
|
"planOptionSingleMeal": "Uma refeição",
|
||||||
@@ -151,6 +154,16 @@
|
|||||||
"planSelectMealType": "Tipo de refeição",
|
"planSelectMealType": "Tipo de refeição",
|
||||||
"planSelectRange": "Selecionar período",
|
"planSelectRange": "Selecionar período",
|
||||||
"planGenerateButton": "Planejar",
|
"planGenerateButton": "Planejar",
|
||||||
"planGenerating": "Gerando plano\u2026",
|
"planGenerating": "Gerando plano…",
|
||||||
"planSuccess": "Menu planejado!"
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,9 @@
|
|||||||
"queuePosition": "Позиция {position}",
|
"queuePosition": "Позиция {position}",
|
||||||
"@queuePosition": {
|
"@queuePosition": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"position": { "type": "int" }
|
"position": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"processing": "Обрабатываем...",
|
"processing": "Обрабатываем...",
|
||||||
@@ -116,7 +118,9 @@
|
|||||||
"noResultsForQuery": "По запросу \"{query}\" ничего не найдено",
|
"noResultsForQuery": "По запросу \"{query}\" ничего не найдено",
|
||||||
"@noResultsForQuery": {
|
"@noResultsForQuery": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"query": { "type": "String" }
|
"query": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"servingsLabel": "Порций",
|
"servingsLabel": "Порций",
|
||||||
@@ -125,7 +129,9 @@
|
|||||||
"planningForDate": "Планирование на {date}",
|
"planningForDate": "Планирование на {date}",
|
||||||
"@planningForDate": {
|
"@planningForDate": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"date": { "type": "String" }
|
"date": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"markAsEaten": "Отметить как съеденное",
|
"markAsEaten": "Отметить как съеденное",
|
||||||
@@ -134,7 +140,6 @@
|
|||||||
"generateWeekSubtitle": "AI составит меню с завтраком, обедом и ужином на всю неделю",
|
"generateWeekSubtitle": "AI составит меню с завтраком, обедом и ужином на всю неделю",
|
||||||
"generatingMenu": "Генерируем меню...",
|
"generatingMenu": "Генерируем меню...",
|
||||||
"dayPlannedLabel": "День запланирован",
|
"dayPlannedLabel": "День запланирован",
|
||||||
|
|
||||||
"planMenuButton": "Спланировать меню",
|
"planMenuButton": "Спланировать меню",
|
||||||
"planMenuTitle": "Что запланировать?",
|
"planMenuTitle": "Что запланировать?",
|
||||||
"planOptionSingleMeal": "1 приём пищи",
|
"planOptionSingleMeal": "1 приём пищи",
|
||||||
@@ -149,6 +154,16 @@
|
|||||||
"planSelectMealType": "Приём пищи",
|
"planSelectMealType": "Приём пищи",
|
||||||
"planSelectRange": "Выберите период",
|
"planSelectRange": "Выберите период",
|
||||||
"planGenerateButton": "Запланировать",
|
"planGenerateButton": "Запланировать",
|
||||||
"planGenerating": "Генерирую план\u2026",
|
"planGenerating": "Генерирую план…",
|
||||||
"planSuccess": "Меню запланировано!"
|
"planSuccess": "Меню запланировано!",
|
||||||
|
"planProductsTitle": "Продукты для меню",
|
||||||
|
"planProductsSubtitle": "AI учтёт выбранные продукты при составлении рецептов",
|
||||||
|
"planProductsEmpty": "Продукты не добавлены",
|
||||||
|
"planProductsEmptyMessage": "Добавьте продукты, которые есть у вас дома — AI подберёт рецепты из того, что уже есть",
|
||||||
|
"planProductsAddProducts": "Добавить продукты",
|
||||||
|
"planProductsContinue": "Продолжить",
|
||||||
|
"planProductsSkip": "Пропустить выбор продуктов",
|
||||||
|
"planProductsSkipNoProducts": "Планировать без продуктов",
|
||||||
|
"planProductsSelectAll": "Выбрать все",
|
||||||
|
"planProductsDeselectAll": "Снять всё"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,9 @@
|
|||||||
"noResultsForQuery": "未找到 \"{query}\" 的结果",
|
"noResultsForQuery": "未找到 \"{query}\" 的结果",
|
||||||
"@noResultsForQuery": {
|
"@noResultsForQuery": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"query": { "type": "String" }
|
"query": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"servingsLabel": "份数",
|
"servingsLabel": "份数",
|
||||||
@@ -127,7 +129,9 @@
|
|||||||
"planningForDate": "",
|
"planningForDate": "",
|
||||||
"@planningForDate": {
|
"@planningForDate": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"date": { "type": "String" }
|
"date": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"markAsEaten": "标记为已吃",
|
"markAsEaten": "标记为已吃",
|
||||||
@@ -136,7 +140,6 @@
|
|||||||
"generateWeekSubtitle": "AI将为整周创建含早餐、午餐和晚餐的菜单",
|
"generateWeekSubtitle": "AI将为整周创建含早餐、午餐和晚餐的菜单",
|
||||||
"generatingMenu": "正在生成菜单...",
|
"generatingMenu": "正在生成菜单...",
|
||||||
"dayPlannedLabel": "今日已规划",
|
"dayPlannedLabel": "今日已规划",
|
||||||
|
|
||||||
"planMenuButton": "规划餐食",
|
"planMenuButton": "规划餐食",
|
||||||
"planMenuTitle": "规划什么?",
|
"planMenuTitle": "规划什么?",
|
||||||
"planOptionSingleMeal": "单次餐食",
|
"planOptionSingleMeal": "单次餐食",
|
||||||
@@ -151,6 +154,16 @@
|
|||||||
"planSelectMealType": "餐食类型",
|
"planSelectMealType": "餐食类型",
|
||||||
"planSelectRange": "选择时间段",
|
"planSelectRange": "选择时间段",
|
||||||
"planGenerateButton": "规划",
|
"planGenerateButton": "规划",
|
||||||
"planGenerating": "正在生成计划\u2026",
|
"planGenerating": "正在生成计划…",
|
||||||
"planSuccess": "菜单已规划!"
|
"planSuccess": "菜单已规划!",
|
||||||
|
"planProductsTitle": "菜单食材",
|
||||||
|
"planProductsSubtitle": "AI在生成食谱时会考虑所选食材",
|
||||||
|
"planProductsEmpty": "尚未添加食材",
|
||||||
|
"planProductsEmptyMessage": "添加您家中的食材 — AI将根据您已有的食材推荐食谱",
|
||||||
|
"planProductsAddProducts": "添加食材",
|
||||||
|
"planProductsContinue": "继续",
|
||||||
|
"planProductsSkip": "跳过食材选择",
|
||||||
|
"planProductsSkipNoProducts": "不选食材直接规划",
|
||||||
|
"planProductsSelectAll": "全选",
|
||||||
|
"planProductsDeselectAll": "取消全选"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user