Flutter client: - Progress dialog: redesigned with pulsing animated icon, info hint about background mode, full-width Minimize button; dismiss signal via ValueNotifier so the dialog always closes regardless of widget lifecycle - Background recognition: when user taps Minimize, wasMinimizedByUser flag is set; on completion a snackbar is shown instead of opening DishResultSheet directly; snackbar action opens the sheet on demand - Fix dialog spinning forever: finally block guarantees dismissSignal=true on all exit paths including early returns from context.mounted checks - Fix DishResultSheet not appearing: add ValueKey to _DailyMealsSection and meal card Padding so Flutter reuses elements when _TodayJobsWidget is inserted/removed from the SliverChildListDelegate list - todayJobsProvider refresh: added refresh() method; called after job submit and on DishJobDone; all ref.read() calls guarded with context.mounted checks - food_search_sheet: scan buttons replaced with full-width stacked OutlinedButtons - app.dart: WidgetsBindingObserver refreshes scan providers on app resume - L10n: added dishRecognitionHint and minimize keys to all 12 locales Backend: - migrations/003: ALTER TYPE recipe_source ADD VALUE 'recommendation' to fix 22P02 error in GET /home/summary -> getRecommendations() - item_enricher: normalizeProductCategory() validates AI-returned category against known slugs, falls back to "other" — fixes products_category_fkey FK violation during receipt recognition - recognition prompt: enumerate valid categories so AI returns correct values Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
589 lines
14 KiB
Dart
589 lines
14 KiB
Dart
// ignore: unused_import
|
||
import 'package:intl/intl.dart' as intl;
|
||
import 'app_localizations.dart';
|
||
|
||
// ignore_for_file: type=lint
|
||
|
||
/// The translations for Russian (`ru`).
|
||
class AppLocalizationsRu extends AppLocalizations {
|
||
AppLocalizationsRu([String locale = 'ru']) : super(locale);
|
||
|
||
@override
|
||
String get appTitle => 'FoodAI';
|
||
|
||
@override
|
||
String get greetingMorning => 'Доброе утро';
|
||
|
||
@override
|
||
String get greetingAfternoon => 'Добрый день';
|
||
|
||
@override
|
||
String get greetingEvening => 'Добрый вечер';
|
||
|
||
@override
|
||
String get caloriesUnit => 'ккал';
|
||
|
||
@override
|
||
String get gramsUnit => 'г';
|
||
|
||
@override
|
||
String get goalLabel => 'цель:';
|
||
|
||
@override
|
||
String get consumed => 'Потреблено';
|
||
|
||
@override
|
||
String get remaining => 'Осталось';
|
||
|
||
@override
|
||
String get exceeded => 'Превышение';
|
||
|
||
@override
|
||
String get proteinLabel => 'Белки';
|
||
|
||
@override
|
||
String get fatLabel => 'Жиры';
|
||
|
||
@override
|
||
String get carbsLabel => 'Углеводы';
|
||
|
||
@override
|
||
String get today => 'Сегодня';
|
||
|
||
@override
|
||
String get yesterday => 'Вчера';
|
||
|
||
@override
|
||
String get mealsSection => 'Приёмы пищи';
|
||
|
||
@override
|
||
String get addDish => 'Добавить блюдо';
|
||
|
||
@override
|
||
String get scanDish => 'Сканировать';
|
||
|
||
@override
|
||
String get menu => 'Меню';
|
||
|
||
@override
|
||
String get dishHistory => 'История блюд';
|
||
|
||
@override
|
||
String get recommendCook => 'Рекомендуем приготовить';
|
||
|
||
@override
|
||
String get camera => 'Камера';
|
||
|
||
@override
|
||
String get gallery => 'Галерея';
|
||
|
||
@override
|
||
String get analyzingPhoto => 'Анализируем фото...';
|
||
|
||
@override
|
||
String get inQueue => 'Вы в очереди';
|
||
|
||
@override
|
||
String queuePosition(int position) {
|
||
return 'Позиция $position';
|
||
}
|
||
|
||
@override
|
||
String get processing => 'Обрабатываем...';
|
||
|
||
@override
|
||
String get upgradePrompt => 'Хотите без очереди? Upgrade →';
|
||
|
||
@override
|
||
String get recognitionFailed => 'Не удалось распознать. Попробуйте ещё раз.';
|
||
|
||
@override
|
||
String get dishRecognition => 'Распознавание блюд';
|
||
|
||
@override
|
||
String get all => 'Все';
|
||
|
||
@override
|
||
String get dishRecognized => 'Блюдо распознано';
|
||
|
||
@override
|
||
String get recognizing => 'Распознаётся…';
|
||
|
||
@override
|
||
String get recognitionError => 'Ошибка распознавания';
|
||
|
||
@override
|
||
String get dishResultTitle => 'Распознано блюдо';
|
||
|
||
@override
|
||
String get selectDish => 'Выберите блюдо';
|
||
|
||
@override
|
||
String get dishNotRecognized => 'Блюдо не распознано';
|
||
|
||
@override
|
||
String get tryAgain => 'Попробовать снова';
|
||
|
||
@override
|
||
String get nutritionApproximate =>
|
||
'КБЖУ приблизительные — определены по фото.';
|
||
|
||
@override
|
||
String get portion => 'Порция';
|
||
|
||
@override
|
||
String get mealType => 'Приём пищи';
|
||
|
||
@override
|
||
String get dateLabel => 'Дата';
|
||
|
||
@override
|
||
String get addToJournal => 'Добавить в журнал';
|
||
|
||
@override
|
||
String get addFailed => 'Не удалось добавить. Попробуйте ещё раз.';
|
||
|
||
@override
|
||
String get historyTitle => 'История распознавания';
|
||
|
||
@override
|
||
String get historyLoadError => 'Не удалось загрузить историю';
|
||
|
||
@override
|
||
String get retry => 'Повторить';
|
||
|
||
@override
|
||
String get noHistory => 'Нет распознаваний';
|
||
|
||
@override
|
||
String get profileTitle => 'Профиль';
|
||
|
||
@override
|
||
String get edit => 'Изменить';
|
||
|
||
@override
|
||
String get bodyParams => 'ПАРАМЕТРЫ ТЕЛА';
|
||
|
||
@override
|
||
String get goalActivity => 'ЦЕЛЬ И АКТИВНОСТЬ';
|
||
|
||
@override
|
||
String get nutrition => 'ПИТАНИЕ';
|
||
|
||
@override
|
||
String get settings => 'НАСТРОЙКИ';
|
||
|
||
@override
|
||
String get height => 'Рост';
|
||
|
||
@override
|
||
String get weight => 'Вес';
|
||
|
||
@override
|
||
String get age => 'Возраст';
|
||
|
||
@override
|
||
String get gender => 'Пол';
|
||
|
||
@override
|
||
String get genderMale => 'Мужской';
|
||
|
||
@override
|
||
String get genderFemale => 'Женский';
|
||
|
||
@override
|
||
String get goalLoss => 'Похудение';
|
||
|
||
@override
|
||
String get goalMaintain => 'Поддержание';
|
||
|
||
@override
|
||
String get goalGain => 'Набор массы';
|
||
|
||
@override
|
||
String get activityLow => 'Низкая';
|
||
|
||
@override
|
||
String get activityMedium => 'Средняя';
|
||
|
||
@override
|
||
String get activityHigh => 'Высокая';
|
||
|
||
@override
|
||
String get calorieGoal => 'Норма калорий';
|
||
|
||
@override
|
||
String get mealTypes => 'Приёмы пищи';
|
||
|
||
@override
|
||
String get formulaNote => 'Рассчитано по формуле Миффлина-Сан Жеора';
|
||
|
||
@override
|
||
String get language => 'Язык';
|
||
|
||
@override
|
||
String get notSet => 'Не задано';
|
||
|
||
@override
|
||
String get calorieHint => 'Укажите параметры тела для расчёта нормы калорий';
|
||
|
||
@override
|
||
String get logout => 'Выйти из аккаунта';
|
||
|
||
@override
|
||
String get editProfile => 'Редактировать профиль';
|
||
|
||
@override
|
||
String get cancel => 'Отмена';
|
||
|
||
@override
|
||
String get save => 'Сохранить';
|
||
|
||
@override
|
||
String get nameLabel => 'Имя';
|
||
|
||
@override
|
||
String get heightCm => 'Рост (см)';
|
||
|
||
@override
|
||
String get weightKg => 'Вес (кг)';
|
||
|
||
@override
|
||
String get birthDate => 'Дата рождения';
|
||
|
||
@override
|
||
String get nameRequired => 'Введите имя';
|
||
|
||
@override
|
||
String get profileUpdated => 'Профиль обновлён';
|
||
|
||
@override
|
||
String get profileSaveFailed => 'Не удалось сохранить';
|
||
|
||
@override
|
||
String get mealTypeBreakfast => 'Завтрак';
|
||
|
||
@override
|
||
String get mealTypeSecondBreakfast => 'Второй завтрак';
|
||
|
||
@override
|
||
String get mealTypeLunch => 'Обед';
|
||
|
||
@override
|
||
String get mealTypeAfternoonSnack => 'Полдник';
|
||
|
||
@override
|
||
String get mealTypeDinner => 'Ужин';
|
||
|
||
@override
|
||
String get mealTypeSnack => 'Перекус';
|
||
|
||
@override
|
||
String get navHome => 'Главная';
|
||
|
||
@override
|
||
String get navProducts => 'Продукты';
|
||
|
||
@override
|
||
String get navRecipes => 'Рецепты';
|
||
|
||
@override
|
||
String get addFromReceiptOrPhoto => 'Добавить из чека или фото';
|
||
|
||
@override
|
||
String get scanScreenTitle => 'Сканировать';
|
||
|
||
@override
|
||
String get barcodeScanSubtitle => 'Найти продукт по штрихкоду';
|
||
|
||
@override
|
||
String get chooseMethod => 'Выберите способ';
|
||
|
||
@override
|
||
String get photoReceipt => 'Сфотографировать чек';
|
||
|
||
@override
|
||
String get photoReceiptSubtitle => 'Распознаем все продукты из чека';
|
||
|
||
@override
|
||
String get photoProducts => 'Сфотографировать продукты';
|
||
|
||
@override
|
||
String get photoProductsSubtitle => 'Холодильник, стол, полка — до 3 фото';
|
||
|
||
@override
|
||
String get addPackagedFood => 'Добавить готовый продукт';
|
||
|
||
@override
|
||
String get scanBarcode => 'Сканировать штрихкод';
|
||
|
||
@override
|
||
String get portionWeightG => 'Вес порции (г)';
|
||
|
||
@override
|
||
String get productNotFound => 'Продукт не найден';
|
||
|
||
@override
|
||
String get enterManually => 'Ввести вручную';
|
||
|
||
@override
|
||
String get perHundredG => 'на 100 г';
|
||
|
||
@override
|
||
String get searchFoodHint => 'Поиск продуктов и блюд...';
|
||
|
||
@override
|
||
String get recentlyUsedLabel => 'Недавно использованные';
|
||
|
||
@override
|
||
String get productsSection => 'Продукты';
|
||
|
||
@override
|
||
String get dishesSection => 'Блюда';
|
||
|
||
@override
|
||
String noResultsForQuery(String query) {
|
||
return 'По запросу \"$query\" ничего не найдено';
|
||
}
|
||
|
||
@override
|
||
String get servingsLabel => 'Порций';
|
||
|
||
@override
|
||
String get addToDiary => 'Добавить в дневник';
|
||
|
||
@override
|
||
String get scanDishPhoto => 'Сканировать фото';
|
||
|
||
@override
|
||
String planningForDate(String date) {
|
||
return 'Планирование на $date';
|
||
}
|
||
|
||
@override
|
||
String get markAsEaten => 'Отметить как съеденное';
|
||
|
||
@override
|
||
String get plannedMealLabel => 'Запланировано';
|
||
|
||
@override
|
||
String get generateWeekLabel => 'Запланировать неделю';
|
||
|
||
@override
|
||
String get generateWeekSubtitle =>
|
||
'AI составит меню с завтраком, обедом и ужином на всю неделю';
|
||
|
||
@override
|
||
String get generatingMenu => 'Генерируем меню...';
|
||
|
||
@override
|
||
String get dayPlannedLabel => 'День запланирован';
|
||
|
||
@override
|
||
String get planMenuButton => 'Спланировать меню';
|
||
|
||
@override
|
||
String get planMenuTitle => 'Что запланировать?';
|
||
|
||
@override
|
||
String get planOptionSingleMeal => '1 приём пищи';
|
||
|
||
@override
|
||
String get planOptionSingleMealDesc => 'Выберите день и приём пищи';
|
||
|
||
@override
|
||
String get planOptionDay => '1 день';
|
||
|
||
@override
|
||
String get planOptionDayDesc => 'Все приёмы пищи за день';
|
||
|
||
@override
|
||
String get planOptionDays => 'Несколько дней';
|
||
|
||
@override
|
||
String get planOptionDaysDesc => 'Настроить период';
|
||
|
||
@override
|
||
String get planOptionWeek => 'Неделя';
|
||
|
||
@override
|
||
String get planOptionWeekDesc => '7 дней сразу';
|
||
|
||
@override
|
||
String get planSelectDate => 'Выберите дату';
|
||
|
||
@override
|
||
String get planSelectMealType => 'Приём пищи';
|
||
|
||
@override
|
||
String get planSelectRange => 'Выберите период';
|
||
|
||
@override
|
||
String get planGenerateButton => 'Запланировать';
|
||
|
||
@override
|
||
String get planGenerating => 'Генерирую план…';
|
||
|
||
@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 => 'Снять всё';
|
||
|
||
@override
|
||
String get recentScans => 'Последние сканирования';
|
||
|
||
@override
|
||
String get seeAllScans => 'Все';
|
||
|
||
@override
|
||
String get productJobHistoryTitle => 'История сканирования';
|
||
|
||
@override
|
||
String get jobTypeReceipt => 'Чек';
|
||
|
||
@override
|
||
String get jobTypeProducts => 'Продукты';
|
||
|
||
@override
|
||
String get scanSubmitting => 'Отправка...';
|
||
|
||
@override
|
||
String get processingProducts => 'Обработка...';
|
||
|
||
@override
|
||
String get clearAllProducts => 'Очистить список';
|
||
|
||
@override
|
||
String get clearAllConfirmTitle => 'Очистить список продуктов?';
|
||
|
||
@override
|
||
String get clearAllConfirmMessage =>
|
||
'Все продукты будут удалены без возможности восстановления.';
|
||
|
||
@override
|
||
String get addManually => 'Вручную';
|
||
|
||
@override
|
||
String get scan => 'Сканировать';
|
||
|
||
@override
|
||
String get addProduct => 'Добавить';
|
||
|
||
@override
|
||
String get searchProducts => 'Поиск продуктов';
|
||
|
||
@override
|
||
String get searchProductsHint =>
|
||
'Введите название продукта или добавьте вручную';
|
||
|
||
@override
|
||
String noSearchResults(String query) {
|
||
return 'Ничего не найдено по запросу \"$query\"';
|
||
}
|
||
|
||
@override
|
||
String get quantity => 'Количество';
|
||
|
||
@override
|
||
String get storageDays => 'Дней хранения';
|
||
|
||
@override
|
||
String get addToShelf => 'В холодильник';
|
||
|
||
@override
|
||
String get errorGeneric => 'Что-то пошло не так';
|
||
|
||
@override
|
||
String get nutritionOptional =>
|
||
'Питательная ценность на 100г (необязательно)';
|
||
|
||
@override
|
||
String get calories => 'Калории';
|
||
|
||
@override
|
||
String get protein => 'Белки';
|
||
|
||
@override
|
||
String get fat => 'Жиры';
|
||
|
||
@override
|
||
String get carbs => 'Углеводы';
|
||
|
||
@override
|
||
String get fiber => 'Клетчатка';
|
||
|
||
@override
|
||
String get productAddedToShelf => 'Добавлено в холодильник';
|
||
|
||
@override
|
||
String recognitionFoundProducts(int count) {
|
||
return 'Найдено $count продуктов';
|
||
}
|
||
|
||
@override
|
||
String get recognitionAddAll => 'Добавить всё';
|
||
|
||
@override
|
||
String get recognitionAddToStock => 'В запасы';
|
||
|
||
@override
|
||
String recognitionAdded(int count) {
|
||
return 'Добавлено $count продуктов';
|
||
}
|
||
|
||
@override
|
||
String get recognitionProductsFailed => 'Не удалось добавить продукты';
|
||
|
||
@override
|
||
String get recognitionEmpty => 'Продукты не найдены';
|
||
|
||
@override
|
||
String recognitionConfidence(int percent) {
|
||
return '$percent% уверенность';
|
||
}
|
||
|
||
@override
|
||
String get recognitionReplaceProduct => 'Заменить продукт';
|
||
|
||
@override
|
||
String get scanJobCloseHint =>
|
||
'Можно закрыть приложение — скан появится в последних сканированиях на экране продуктов';
|
||
|
||
@override
|
||
String get minimize => 'Свернуть';
|
||
|
||
@override
|
||
String get dishRecognitionHint =>
|
||
'Можно свернуть — результат появится на главном экране по завершении';
|
||
}
|