feat: Flutter client localisation (12 languages)
Add flutter_localizations + intl, 12 ARB files (en/ru/es/de/fr/it/pt/zh/ja/ko/ar/hi), replace all hardcoded Russian UI strings with AppLocalizations, detect system locale on first launch, localise bottom nav bar labels, document rule in CLAUDE.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
50
CLAUDE.md
50
CLAUDE.md
@@ -91,3 +91,53 @@ Single- or two-letter names are only allowed when the abbreviation *is* the full
|
||||
This rule applies to all languages in this repo (Go and Dart).
|
||||
In Go, name variables to avoid shadowing the `error` built-in and the `context` package —
|
||||
use descriptive prefixes: `parseError`, `requestContext`, etc.
|
||||
|
||||
## Flutter Client Localisation
|
||||
|
||||
**Rule:** Every UI string in `client/` must go through `AppLocalizations`.
|
||||
Never hardcode user-visible text in Dart source files.
|
||||
|
||||
### Current setup
|
||||
|
||||
- `flutter_localizations` (sdk: flutter) + `intl: ^0.20.2` in `client/pubspec.yaml`
|
||||
- `generate: true` under `flutter:` in `client/pubspec.yaml`
|
||||
- `client/l10n.yaml` — generator config (arb-dir, template-arb-file, output-class)
|
||||
- Generated class: `package:food_ai/l10n/app_localizations.dart`
|
||||
|
||||
### Supported languages
|
||||
|
||||
`en`, `ru`, `es`, `de`, `fr`, `it`, `pt`, `zh`, `ja`, `ko`, `ar`, `hi`
|
||||
|
||||
### ARB files
|
||||
|
||||
All translation files live in `client/lib/l10n/`:
|
||||
|
||||
- `app_en.arb` — English (template / canonical)
|
||||
- `app_ru.arb`, `app_es.arb`, `app_de.arb`, `app_fr.arb`, `app_it.arb`
|
||||
- `app_pt.arb`, `app_zh.arb`, `app_ja.arb`, `app_ko.arb`, `app_ar.arb`, `app_hi.arb`
|
||||
|
||||
### Usage pattern
|
||||
|
||||
```dart
|
||||
import 'package:food_ai/l10n/app_localizations.dart';
|
||||
|
||||
// Inside build():
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
Text(l10n.someKey)
|
||||
```
|
||||
|
||||
### Adding new strings
|
||||
|
||||
1. Add the key + English value to `client/lib/l10n/app_en.arb` (template file).
|
||||
2. Add the same key with the correct translation to **all other 11 ARB files**.
|
||||
3. Run `flutter gen-l10n` inside `client/` to regenerate `AppLocalizations`.
|
||||
4. Use `l10n.<newKey>` in the widget.
|
||||
|
||||
For parameterised strings use the ICU placeholder syntax in ARB files:
|
||||
|
||||
```json
|
||||
"queuePosition": "Position {position}",
|
||||
"@queuePosition": { "placeholders": { "position": { "type": "int" } } }
|
||||
```
|
||||
|
||||
Then call `l10n.queuePosition(n)` in Dart.
|
||||
|
||||
4
client/l10n.yaml
Normal file
4
client/l10n.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
arb-dir: lib/l10n
|
||||
template-arb-file: app_en.arb
|
||||
output-localization-file: app_localizations.dart
|
||||
output-class: AppLocalizations
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:food_ai/l10n/app_localizations.dart';
|
||||
|
||||
import 'core/locale/language_provider.dart';
|
||||
import 'core/router/app_router.dart';
|
||||
import 'core/theme/app_theme.dart';
|
||||
|
||||
@@ -10,12 +12,16 @@ class App extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final router = ref.watch(routerProvider);
|
||||
final languageCode = ref.watch(languageProvider);
|
||||
|
||||
return MaterialApp.router(
|
||||
title: 'FoodAI',
|
||||
theme: appTheme(),
|
||||
routerConfig: router,
|
||||
debugShowCheckedModeBanner: false,
|
||||
locale: Locale(languageCode),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../l10n/app_localizations.dart';
|
||||
|
||||
import '../auth/auth_provider.dart';
|
||||
import '../../features/auth/login_screen.dart';
|
||||
import '../../features/auth/register_screen.dart';
|
||||
@@ -208,15 +210,17 @@ class MainShell extends ConsumerWidget {
|
||||
orElse: () => 0,
|
||||
);
|
||||
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return Scaffold(
|
||||
body: child,
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
currentIndex: currentIndex,
|
||||
onTap: (index) => context.go(_tabs[index]),
|
||||
items: [
|
||||
const BottomNavigationBarItem(
|
||||
icon: Icon(Icons.home),
|
||||
label: 'Главная',
|
||||
BottomNavigationBarItem(
|
||||
icon: const Icon(Icons.home),
|
||||
label: l10n.navHome,
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Badge(
|
||||
@@ -224,19 +228,19 @@ class MainShell extends ConsumerWidget {
|
||||
label: Text('$expiringCount'),
|
||||
child: const Icon(Icons.kitchen),
|
||||
),
|
||||
label: 'Продукты',
|
||||
label: l10n.navProducts,
|
||||
),
|
||||
const BottomNavigationBarItem(
|
||||
icon: Icon(Icons.calendar_month),
|
||||
label: 'Меню',
|
||||
BottomNavigationBarItem(
|
||||
icon: const Icon(Icons.calendar_month),
|
||||
label: l10n.menu,
|
||||
),
|
||||
const BottomNavigationBarItem(
|
||||
icon: Icon(Icons.menu_book),
|
||||
label: 'Рецепты',
|
||||
BottomNavigationBarItem(
|
||||
icon: const Icon(Icons.menu_book),
|
||||
label: l10n.navRecipes,
|
||||
),
|
||||
const BottomNavigationBarItem(
|
||||
icon: Icon(Icons.person),
|
||||
label: 'Профиль',
|
||||
BottomNavigationBarItem(
|
||||
icon: const Icon(Icons.person),
|
||||
label: l10n.profileTitle,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -56,3 +56,23 @@ final todayJobsProvider =
|
||||
StateNotifierProvider<TodayJobsNotifier, AsyncValue<List<DishJobSummary>>>(
|
||||
(ref) => TodayJobsNotifier(ref.read(recognitionServiceProvider)),
|
||||
);
|
||||
|
||||
// ── All recognition jobs (history screen) ─────────────────────
|
||||
|
||||
class AllJobsNotifier extends StateNotifier<AsyncValue<List<DishJobSummary>>> {
|
||||
final RecognitionService _service;
|
||||
|
||||
AllJobsNotifier(this._service) : super(const AsyncValue.loading()) {
|
||||
load();
|
||||
}
|
||||
|
||||
Future<void> load() async {
|
||||
state = const AsyncValue.loading();
|
||||
state = await AsyncValue.guard(() => _service.listAllJobs());
|
||||
}
|
||||
}
|
||||
|
||||
final allJobsProvider =
|
||||
StateNotifierProvider<AllJobsNotifier, AsyncValue<List<DishJobSummary>>>(
|
||||
(ref) => AllJobsNotifier(ref.read(recognitionServiceProvider)),
|
||||
);
|
||||
|
||||
@@ -2,9 +2,11 @@ import 'dart:math' as math;
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:food_ai/l10n/app_localizations.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../../core/storage/local_preferences_provider.dart';
|
||||
import '../../core/theme/app_colors.dart';
|
||||
@@ -24,6 +26,7 @@ class HomeScreen extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final homeSummaryState = ref.watch(homeProvider);
|
||||
final profile = ref.watch(profileProvider).valueOrNull;
|
||||
final userName = profile?.name;
|
||||
@@ -100,7 +103,7 @@ class HomeScreen extends ConsumerWidget {
|
||||
_QuickActionsRow(),
|
||||
if (recommendations.isNotEmpty) ...[
|
||||
const SizedBox(height: 20),
|
||||
_SectionTitle('Рекомендуем приготовить'),
|
||||
_SectionTitle(l10n.recommendCook),
|
||||
const SizedBox(height: 12),
|
||||
_RecommendationsRow(recipes: recommendations),
|
||||
],
|
||||
@@ -120,26 +123,25 @@ class _AppBar extends StatelessWidget {
|
||||
final String? userName;
|
||||
const _AppBar({this.userName});
|
||||
|
||||
String get _greetingBase {
|
||||
String _greetingBase(AppLocalizations l10n) {
|
||||
final hour = DateTime.now().hour;
|
||||
if (hour < 12) return 'Доброе утро';
|
||||
if (hour < 18) return 'Добрый день';
|
||||
return 'Добрый вечер';
|
||||
}
|
||||
|
||||
String get _greeting {
|
||||
final name = userName;
|
||||
if (name != null && name.isNotEmpty) return '$_greetingBase, $name!';
|
||||
return _greetingBase;
|
||||
if (hour < 12) return l10n.greetingMorning;
|
||||
if (hour < 18) return l10n.greetingAfternoon;
|
||||
return l10n.greetingEvening;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
final base = _greetingBase(l10n);
|
||||
final greeting = (userName != null && userName!.isNotEmpty)
|
||||
? '$base, $userName!'
|
||||
: base;
|
||||
return SliverAppBar(
|
||||
pinned: false,
|
||||
floating: true,
|
||||
title: Text(_greeting, style: theme.textTheme.titleMedium),
|
||||
title: Text(greeting, style: theme.textTheme.titleMedium),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -160,11 +162,6 @@ class _DateSelector extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DateSelectorState extends State<_DateSelector> {
|
||||
static const _weekDayShort = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
|
||||
static const _monthNames = [
|
||||
'января', 'февраля', 'марта', 'апреля', 'мая', 'июня',
|
||||
'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря',
|
||||
];
|
||||
// Total days available in the past (index 0 = today, index N-1 = oldest)
|
||||
static const _totalDays = 365;
|
||||
static const _pillWidth = 48.0;
|
||||
@@ -172,12 +169,10 @@ class _DateSelectorState extends State<_DateSelector> {
|
||||
|
||||
late final ScrollController _scrollController;
|
||||
|
||||
String _formatSelectedDate(DateTime date) {
|
||||
String _formatSelectedDate(DateTime date, String localeCode) {
|
||||
final now = DateTime.now();
|
||||
final dayName = _weekDayShort[date.weekday - 1];
|
||||
final month = _monthNames[date.month - 1];
|
||||
final yearSuffix = date.year != now.year ? ' ${date.year}' : '';
|
||||
return '$dayName, ${date.day} $month$yearSuffix';
|
||||
return DateFormat('EEE, d MMMM', localeCode).format(date) + yearSuffix;
|
||||
}
|
||||
|
||||
// Index in the reversed list: 0 = today, 1 = yesterday, …
|
||||
@@ -253,6 +248,8 @@ class _DateSelectorState extends State<_DateSelector> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final localeCode = Localizations.localeOf(context).toString();
|
||||
final theme = Theme.of(context);
|
||||
final today = DateTime.now();
|
||||
final todayNormalized = DateTime(today.year, today.month, today.day);
|
||||
@@ -276,7 +273,7 @@ class _DateSelectorState extends State<_DateSelector> {
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_formatSelectedDate(widget.selectedDate),
|
||||
_formatSelectedDate(widget.selectedDate, localeCode),
|
||||
style: theme.textTheme.labelMedium
|
||||
?.copyWith(fontWeight: FontWeight.w600),
|
||||
textAlign: TextAlign.center,
|
||||
@@ -290,7 +287,7 @@ class _DateSelectorState extends State<_DateSelector> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
),
|
||||
child: Text(
|
||||
'Сегодня',
|
||||
l10n.today,
|
||||
style: theme.textTheme.labelMedium
|
||||
?.copyWith(color: theme.colorScheme.primary),
|
||||
),
|
||||
@@ -336,7 +333,7 @@ class _DateSelectorState extends State<_DateSelector> {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
_weekDayShort[date.weekday - 1],
|
||||
DateFormat('EEE', localeCode).format(date),
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: isSelected
|
||||
? theme.colorScheme.onPrimary
|
||||
@@ -386,21 +383,22 @@ class _CaloriesCard extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
if (dailyGoal == 0) return const SizedBox.shrink();
|
||||
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
final logged = loggedCalories.toInt();
|
||||
final rawProgress = dailyGoal > 0 ? loggedCalories / dailyGoal : 0.0;
|
||||
final isOverGoal = rawProgress > 1.0;
|
||||
final ringColor = _ringColorFor(rawProgress, goalType);
|
||||
|
||||
final String secondaryLabel;
|
||||
final String secondaryValue;
|
||||
final Color secondaryColor;
|
||||
if (isOverGoal) {
|
||||
final overBy = (loggedCalories - dailyGoal).toInt();
|
||||
secondaryLabel = '+$overBy перебор';
|
||||
secondaryValue = '+$overBy ${l10n.caloriesUnit}';
|
||||
secondaryColor = AppColors.error;
|
||||
} else {
|
||||
final remaining = (dailyGoal - loggedCalories).toInt();
|
||||
secondaryLabel = 'осталось $remaining';
|
||||
secondaryValue = '$remaining ${l10n.caloriesUnit}';
|
||||
secondaryColor = AppColors.textSecondary;
|
||||
}
|
||||
|
||||
@@ -433,14 +431,14 @@ class _CaloriesCard extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'ккал',
|
||||
l10n.caloriesUnit,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'цель: $dailyGoal',
|
||||
'${l10n.goalLabel} $dailyGoal',
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
@@ -456,20 +454,20 @@ class _CaloriesCard extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_CalorieStat(
|
||||
label: 'Потреблено',
|
||||
value: '$logged ккал',
|
||||
label: l10n.consumed,
|
||||
value: '$logged ${l10n.caloriesUnit}',
|
||||
valueColor: ringColor,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_CalorieStat(
|
||||
label: isOverGoal ? 'Превышение' : 'Осталось',
|
||||
value: secondaryLabel,
|
||||
label: isOverGoal ? l10n.exceeded : l10n.remaining,
|
||||
value: secondaryValue,
|
||||
valueColor: secondaryColor,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_CalorieStat(
|
||||
label: 'Цель',
|
||||
value: '$dailyGoal ккал',
|
||||
label: l10n.goalLabel.replaceAll(':', '').trim(),
|
||||
value: '$dailyGoal ${l10n.caloriesUnit}',
|
||||
valueColor: AppColors.textPrimary,
|
||||
),
|
||||
],
|
||||
@@ -532,28 +530,29 @@ class _MacrosRow extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _MacroChip(
|
||||
label: 'Белки',
|
||||
value: '${proteinG.toStringAsFixed(1)} г',
|
||||
label: l10n.proteinLabel,
|
||||
value: '${proteinG.toStringAsFixed(1)} ${l10n.gramsUnit}',
|
||||
color: Colors.blue,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: _MacroChip(
|
||||
label: 'Жиры',
|
||||
value: '${fatG.toStringAsFixed(1)} г',
|
||||
label: l10n.fatLabel,
|
||||
value: '${fatG.toStringAsFixed(1)} ${l10n.gramsUnit}',
|
||||
color: Colors.orange,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: _MacroChip(
|
||||
label: 'Углеводы',
|
||||
value: '${carbsG.toStringAsFixed(1)} г',
|
||||
label: l10n.carbsLabel,
|
||||
value: '${carbsG.toStringAsFixed(1)} ${l10n.gramsUnit}',
|
||||
color: Colors.green,
|
||||
),
|
||||
),
|
||||
@@ -712,11 +711,12 @@ class _DailyMealsSection extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Приёмы пищи', style: theme.textTheme.titleSmall),
|
||||
Text(l10n.mealsSection, style: theme.textTheme.titleSmall),
|
||||
const SizedBox(height: 8),
|
||||
...mealTypeIds.map((mealTypeId) {
|
||||
final mealTypeOption = mealTypeById(mealTypeId);
|
||||
@@ -743,6 +743,8 @@ Future<void> _pickAndShowDishResult(
|
||||
WidgetRef ref,
|
||||
String mealTypeId,
|
||||
) async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
// 1. Choose image source.
|
||||
final source = await showModalBottomSheet<ImageSource>(
|
||||
context: context,
|
||||
@@ -752,12 +754,12 @@ Future<void> _pickAndShowDishResult(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.camera_alt),
|
||||
title: const Text('Камера'),
|
||||
title: Text(l10n.camera),
|
||||
onTap: () => Navigator.pop(context, ImageSource.camera),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.photo_library),
|
||||
title: const Text('Галерея'),
|
||||
title: Text(l10n.gallery),
|
||||
onTap: () => Navigator.pop(context, ImageSource.gallery),
|
||||
),
|
||||
],
|
||||
@@ -778,7 +780,7 @@ Future<void> _pickAndShowDishResult(
|
||||
// 3. Show progress dialog.
|
||||
// Capture root navigator before await to avoid GoRouter inner-navigator issues.
|
||||
final rootNavigator = Navigator.of(context, rootNavigator: true);
|
||||
final progressNotifier = _DishProgressNotifier();
|
||||
final progressNotifier = _DishProgressNotifier(initialMessage: l10n.analyzingPhoto);
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
@@ -809,11 +811,11 @@ Future<void> _pickAndShowDishResult(
|
||||
switch (event) {
|
||||
case DishJobQueued():
|
||||
progressNotifier.update(
|
||||
message: 'Вы в очереди #${event.position + 1} · ~${event.estimatedSeconds} сек',
|
||||
message: '${l10n.inQueue} · ${l10n.queuePosition(event.position + 1)}',
|
||||
showUpgrade: event.position > 0,
|
||||
);
|
||||
case DishJobProcessing():
|
||||
progressNotifier.update(message: 'Обрабатываем...');
|
||||
progressNotifier.update(message: l10n.processing);
|
||||
case DishJobDone():
|
||||
rootNavigator.pop(); // close dialog
|
||||
if (!context.mounted) return;
|
||||
@@ -825,6 +827,7 @@ Future<void> _pickAndShowDishResult(
|
||||
dish: event.result,
|
||||
preselectedMealType: mealTypeId.isNotEmpty ? mealTypeId : null,
|
||||
jobId: jobCreated.jobId,
|
||||
targetDate: targetDate,
|
||||
onAdded: () => Navigator.pop(sheetContext),
|
||||
),
|
||||
);
|
||||
@@ -836,7 +839,7 @@ Future<void> _pickAndShowDishResult(
|
||||
SnackBar(
|
||||
content: Text(event.error),
|
||||
action: SnackBarAction(
|
||||
label: 'Повторить',
|
||||
label: l10n.retry,
|
||||
onPressed: () => _pickAndShowDishResult(context, ref, mealTypeId),
|
||||
),
|
||||
),
|
||||
@@ -849,9 +852,7 @@ Future<void> _pickAndShowDishResult(
|
||||
if (context.mounted) {
|
||||
rootNavigator.pop(); // close dialog
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Не удалось распознать. Попробуйте ещё раз.'),
|
||||
),
|
||||
SnackBar(content: Text(l10n.recognitionFailed)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -872,7 +873,10 @@ class _DishProgressState {
|
||||
}
|
||||
|
||||
class _DishProgressNotifier extends ChangeNotifier {
|
||||
_DishProgressState _state = const _DishProgressState(message: 'Анализируем фото...');
|
||||
late _DishProgressState _state;
|
||||
|
||||
_DishProgressNotifier({required String initialMessage})
|
||||
: _state = _DishProgressState(message: initialMessage);
|
||||
|
||||
_DishProgressState get state => _state;
|
||||
|
||||
@@ -889,6 +893,7 @@ class _DishProgressDialog extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return ListenableBuilder(
|
||||
listenable: notifier,
|
||||
builder: (context, _) {
|
||||
@@ -903,7 +908,7 @@ class _DishProgressDialog extends StatelessWidget {
|
||||
if (state.showUpgrade) ...[
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'Хотите без очереди? Upgrade →',
|
||||
l10n.upgradePrompt,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
@@ -931,6 +936,7 @@ class _MealCard extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
final totalCalories = entries.fold<double>(
|
||||
0.0, (sum, entry) => sum + (entry.calories ?? 0));
|
||||
@@ -946,12 +952,12 @@ class _MealCard extends ConsumerWidget {
|
||||
Text(mealTypeOption.emoji,
|
||||
style: const TextStyle(fontSize: 20)),
|
||||
const SizedBox(width: 8),
|
||||
Text(mealTypeOption.label,
|
||||
Text(mealTypeLabel(mealTypeOption.id, l10n),
|
||||
style: theme.textTheme.titleSmall),
|
||||
const Spacer(),
|
||||
if (totalCalories > 0)
|
||||
Text(
|
||||
'${totalCalories.toInt()} ккал',
|
||||
'${totalCalories.toInt()} ${l10n.caloriesUnit}',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
@@ -959,7 +965,7 @@ class _MealCard extends ConsumerWidget {
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add, size: 20),
|
||||
visualDensity: VisualDensity.compact,
|
||||
tooltip: 'Добавить блюдо',
|
||||
tooltip: l10n.addDish,
|
||||
onPressed: () => _pickAndShowDishResult(
|
||||
context, ref, mealTypeOption.id),
|
||||
),
|
||||
@@ -990,6 +996,7 @@ class _DiaryEntryTile extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
final calories = entry.calories?.toInt();
|
||||
final hasProtein = (entry.proteinG ?? 0) > 0;
|
||||
@@ -1016,7 +1023,7 @@ class _DiaryEntryTile extends StatelessWidget {
|
||||
children: [
|
||||
if (calories != null)
|
||||
Text(
|
||||
'$calories ккал',
|
||||
'$calories ${l10n.caloriesUnit}',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
@@ -1095,6 +1102,7 @@ class _TodayJobsWidget extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
final visibleJobs = jobs.take(3).toList();
|
||||
final hasMore = jobs.length > 3;
|
||||
@@ -1104,7 +1112,7 @@ class _TodayJobsWidget extends ConsumerWidget {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text('Распознавания', style: theme.textTheme.titleSmall),
|
||||
Text(l10n.dishRecognition, style: theme.textTheme.titleSmall),
|
||||
const Spacer(),
|
||||
if (hasMore)
|
||||
TextButton(
|
||||
@@ -1114,7 +1122,7 @@ class _TodayJobsWidget extends ConsumerWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
),
|
||||
child: Text(
|
||||
'Все',
|
||||
l10n.all,
|
||||
style: theme.textTheme.labelMedium?.copyWith(
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
@@ -1139,6 +1147,7 @@ class _JobTile extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
|
||||
final isDone = job.status == 'done';
|
||||
@@ -1162,13 +1171,13 @@ class _JobTile extends ConsumerWidget {
|
||||
final dishName = job.result?.candidates.isNotEmpty == true
|
||||
? job.result!.best.dishName
|
||||
: null;
|
||||
final subtitle = dishName ?? (isFailed ? (job.error ?? 'Ошибка') : 'Обрабатывается…');
|
||||
final subtitle = dishName ?? (isFailed ? (job.error ?? l10n.recognitionError) : l10n.recognizing);
|
||||
|
||||
return Card(
|
||||
child: ListTile(
|
||||
leading: Icon(statusIcon, color: statusColor),
|
||||
title: Text(
|
||||
dishName ?? (isProcessing ? 'Распознаётся…' : 'Ошибка'),
|
||||
dishName ?? (isProcessing ? l10n.recognizing : l10n.recognitionError),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
subtitle: Text(
|
||||
@@ -1193,6 +1202,8 @@ class _JobTile extends ConsumerWidget {
|
||||
dish: job.result!,
|
||||
preselectedMealType: job.targetMealType,
|
||||
jobId: job.id,
|
||||
targetDate: job.targetDate,
|
||||
createdAt: job.createdAt,
|
||||
onAdded: () => Navigator.pop(sheetContext),
|
||||
),
|
||||
);
|
||||
@@ -1210,12 +1221,13 @@ class _QuickActionsRow extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _ActionButton(
|
||||
icon: Icons.document_scanner_outlined,
|
||||
label: 'Сканировать',
|
||||
label: l10n.scanDish,
|
||||
onTap: () => context.push('/scan'),
|
||||
),
|
||||
),
|
||||
@@ -1223,7 +1235,7 @@ class _QuickActionsRow extends StatelessWidget {
|
||||
Expanded(
|
||||
child: _ActionButton(
|
||||
icon: Icons.calendar_month_outlined,
|
||||
label: 'Меню',
|
||||
label: l10n.menu,
|
||||
onTap: () => context.push('/menu'),
|
||||
),
|
||||
),
|
||||
@@ -1231,7 +1243,7 @@ class _QuickActionsRow extends StatelessWidget {
|
||||
Expanded(
|
||||
child: _ActionButton(
|
||||
icon: Icons.history,
|
||||
label: 'История',
|
||||
label: l10n.dishHistory,
|
||||
onTap: () => context.push('/scan/history'),
|
||||
),
|
||||
),
|
||||
@@ -1309,6 +1321,7 @@ class _RecipeCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return SizedBox(
|
||||
@@ -1343,7 +1356,7 @@ class _RecipeCard extends StatelessWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(8, 0, 8, 6),
|
||||
child: Text(
|
||||
'≈${recipe.calories!.toInt()} ккал',
|
||||
'≈${recipe.calories!.toInt()} ${l10n.caloriesUnit}',
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:food_ai/l10n/app_localizations.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../core/theme/app_colors.dart';
|
||||
@@ -1151,6 +1152,7 @@ class _MealTypesStepContent extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -1178,7 +1180,7 @@ class _MealTypesStepContent extends StatelessWidget {
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
mealTypeOption.label,
|
||||
mealTypeLabel(mealTypeOption.id, l10n),
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: isSelected
|
||||
? accentColor
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:food_ai/l10n/app_localizations.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../core/auth/auth_provider.dart';
|
||||
@@ -15,16 +16,17 @@ class ProfileScreen extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final state = ref.watch(profileProvider);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Профиль'),
|
||||
title: Text(l10n.profileTitle),
|
||||
actions: [
|
||||
if (state.hasValue)
|
||||
TextButton(
|
||||
onPressed: () => _openEdit(context, state.value!),
|
||||
child: const Text('Изменить'),
|
||||
child: Text(l10n.edit),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -33,7 +35,7 @@ class ProfileScreen extends ConsumerWidget {
|
||||
error: (_, __) => Center(
|
||||
child: FilledButton(
|
||||
onPressed: () => ref.read(profileProvider.notifier).load(),
|
||||
child: const Text('Повторить'),
|
||||
child: Text(l10n.retry),
|
||||
),
|
||||
),
|
||||
data: (user) => _ProfileBody(user: user),
|
||||
@@ -58,6 +60,28 @@ class _ProfileBody extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
String? genderLabel(String? gender) => switch (gender) {
|
||||
'male' => l10n.genderMale,
|
||||
'female' => l10n.genderFemale,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
String? goalLabel(String? goal) => switch (goal) {
|
||||
'lose' => l10n.goalLoss,
|
||||
'maintain' => l10n.goalMaintain,
|
||||
'gain' => l10n.goalGain,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
String? activityLabel(String? activity) => switch (activity) {
|
||||
'low' => l10n.activityLow,
|
||||
'moderate' => l10n.activityMedium,
|
||||
'high' => l10n.activityHigh,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
return ListView(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 32),
|
||||
children: [
|
||||
@@ -65,50 +89,49 @@ class _ProfileBody extends ConsumerWidget {
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Body params
|
||||
_SectionLabel('ПАРАМЕТРЫ ТЕЛА'),
|
||||
_SectionLabel(l10n.bodyParams),
|
||||
const SizedBox(height: 6),
|
||||
_InfoCard(children: [
|
||||
_InfoRow('Рост', user.heightCm != null ? '${user.heightCm} см' : null),
|
||||
_InfoRow(l10n.height, user.heightCm != null ? '${user.heightCm} см' : null),
|
||||
const Divider(height: 1, indent: 16),
|
||||
_InfoRow('Вес',
|
||||
_InfoRow(l10n.weight,
|
||||
user.weightKg != null ? '${_fmt(user.weightKg!)} кг' : null),
|
||||
const Divider(height: 1, indent: 16),
|
||||
_InfoRow('Возраст', user.age != null ? '${user.age} лет' : null),
|
||||
_InfoRow(l10n.age, user.age != null ? '${user.age}' : null),
|
||||
const Divider(height: 1, indent: 16),
|
||||
_InfoRow('Пол', _genderLabel(user.gender)),
|
||||
_InfoRow(l10n.gender, genderLabel(user.gender)),
|
||||
]),
|
||||
if (user.heightCm == null && user.weightKg == null) ...[
|
||||
const SizedBox(height: 8),
|
||||
_HintBanner('Укажите параметры тела для расчёта нормы калорий'),
|
||||
_HintBanner(l10n.calorieHint),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Goal & activity
|
||||
_SectionLabel('ЦЕЛЬ И АКТИВНОСТЬ'),
|
||||
_SectionLabel(l10n.goalActivity),
|
||||
const SizedBox(height: 6),
|
||||
_InfoCard(children: [
|
||||
_InfoRow('Цель', _goalLabel(user.goal)),
|
||||
_InfoRow(l10n.goalLabel.replaceAll(':', '').trim(), goalLabel(user.goal)),
|
||||
const Divider(height: 1, indent: 16),
|
||||
_InfoRow('Активность', _activityLabel(user.activity)),
|
||||
_InfoRow('Активность', activityLabel(user.activity)),
|
||||
]),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Calories + meal types
|
||||
_SectionLabel('ПИТАНИЕ'),
|
||||
_SectionLabel(l10n.nutrition),
|
||||
const SizedBox(height: 6),
|
||||
_InfoCard(children: [
|
||||
_InfoRow(
|
||||
'Норма калорий',
|
||||
l10n.calorieGoal,
|
||||
user.dailyCalories != null
|
||||
? '${user.dailyCalories} ккал/день'
|
||||
? '${user.dailyCalories} ${l10n.caloriesUnit}/день'
|
||||
: null,
|
||||
),
|
||||
const Divider(height: 1, indent: 16),
|
||||
_InfoRow(
|
||||
'Приёмы пищи',
|
||||
l10n.mealTypes,
|
||||
user.mealTypes
|
||||
.map((mealTypeId) =>
|
||||
mealTypeById(mealTypeId)?.label ?? mealTypeId)
|
||||
.map((mealTypeId) => mealTypeLabel(mealTypeId, l10n))
|
||||
.join(', '),
|
||||
),
|
||||
]),
|
||||
@@ -117,7 +140,7 @@ class _ProfileBody extends ConsumerWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Text(
|
||||
'Рассчитано по формуле Миффлина-Сан Жеора',
|
||||
l10n.formulaNote,
|
||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
@@ -127,11 +150,11 @@ class _ProfileBody extends ConsumerWidget {
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Settings
|
||||
_SectionLabel('НАСТРОЙКИ'),
|
||||
_SectionLabel(l10n.settings),
|
||||
const SizedBox(height: 6),
|
||||
_InfoCard(children: [
|
||||
_InfoRow(
|
||||
'Язык',
|
||||
l10n.language,
|
||||
ref.watch(supportedLanguagesProvider).valueOrNull?[
|
||||
user.preferences['language'] as String? ?? 'ru'] ??
|
||||
'Русский',
|
||||
@@ -144,28 +167,10 @@ class _ProfileBody extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
static String _fmt(double w) =>
|
||||
w == w.truncateToDouble() ? w.toInt().toString() : w.toStringAsFixed(1);
|
||||
|
||||
static String? _genderLabel(String? g) => switch (g) {
|
||||
'male' => 'Мужской',
|
||||
'female' => 'Женский',
|
||||
_ => null,
|
||||
};
|
||||
|
||||
static String? _goalLabel(String? g) => switch (g) {
|
||||
'lose' => 'Похудение',
|
||||
'maintain' => 'Поддержание',
|
||||
'gain' => 'Набор массы',
|
||||
_ => null,
|
||||
};
|
||||
|
||||
static String? _activityLabel(String? a) => switch (a) {
|
||||
'low' => 'Низкая',
|
||||
'moderate' => 'Средняя',
|
||||
'high' => 'Высокая',
|
||||
_ => null,
|
||||
};
|
||||
static String _fmt(double weight) =>
|
||||
weight == weight.truncateToDouble()
|
||||
? weight.toInt().toString()
|
||||
: weight.toStringAsFixed(1);
|
||||
}
|
||||
|
||||
// ── Header ────────────────────────────────────────────────────
|
||||
@@ -241,6 +246,7 @@ class _InfoRow extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13),
|
||||
@@ -249,7 +255,7 @@ class _InfoRow extends StatelessWidget {
|
||||
Text(label, style: theme.textTheme.bodyMedium),
|
||||
const Spacer(),
|
||||
Text(
|
||||
value ?? 'Не задано',
|
||||
value ?? l10n.notSet,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: value != null
|
||||
? AppColors.textPrimary
|
||||
@@ -296,31 +302,33 @@ class _HintBanner extends StatelessWidget {
|
||||
class _LogoutButton extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: AppColors.error,
|
||||
side: const BorderSide(color: AppColors.error),
|
||||
),
|
||||
onPressed: () => _confirmLogout(context, ref),
|
||||
child: const Text('Выйти из аккаунта'),
|
||||
child: Text(l10n.logout),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _confirmLogout(BuildContext context, WidgetRef ref) async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text('Выйти из аккаунта?'),
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
title: Text('${l10n.logout}?'),
|
||||
content: const Text('Вы будете перенаправлены на экран входа.'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(false),
|
||||
child: const Text('Отмена'),
|
||||
onPressed: () => Navigator.of(dialogContext).pop(false),
|
||||
child: Text(l10n.cancel),
|
||||
),
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(foregroundColor: AppColors.error),
|
||||
onPressed: () => Navigator.of(ctx).pop(true),
|
||||
child: const Text('Выйти'),
|
||||
onPressed: () => Navigator.of(dialogContext).pop(true),
|
||||
child: Text(l10n.logout),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -343,9 +351,9 @@ class EditProfileSheet extends ConsumerStatefulWidget {
|
||||
|
||||
class _EditProfileSheetState extends ConsumerState<EditProfileSheet> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
late final TextEditingController _nameCtrl;
|
||||
late final TextEditingController _heightCtrl;
|
||||
late final TextEditingController _weightCtrl;
|
||||
late final TextEditingController _nameController;
|
||||
late final TextEditingController _heightController;
|
||||
late final TextEditingController _weightController;
|
||||
DateTime? _selectedDob;
|
||||
String? _gender;
|
||||
String? _goal;
|
||||
@@ -357,30 +365,32 @@ class _EditProfileSheetState extends ConsumerState<EditProfileSheet> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final u = widget.user;
|
||||
_nameCtrl = TextEditingController(text: u.name);
|
||||
_heightCtrl = TextEditingController(text: u.heightCm?.toString() ?? '');
|
||||
_weightCtrl = TextEditingController(
|
||||
text: u.weightKg != null ? _fmt(u.weightKg!) : '');
|
||||
final user = widget.user;
|
||||
_nameController = TextEditingController(text: user.name);
|
||||
_heightController = TextEditingController(text: user.heightCm?.toString() ?? '');
|
||||
_weightController = TextEditingController(
|
||||
text: user.weightKg != null ? _fmt(user.weightKg!) : '');
|
||||
_selectedDob =
|
||||
u.dateOfBirth != null ? DateTime.tryParse(u.dateOfBirth!) : null;
|
||||
_gender = u.gender;
|
||||
_goal = u.goal;
|
||||
_activity = u.activity;
|
||||
_language = u.preferences['language'] as String? ?? 'ru';
|
||||
_mealTypes = List<String>.from(u.mealTypes);
|
||||
user.dateOfBirth != null ? DateTime.tryParse(user.dateOfBirth!) : null;
|
||||
_gender = user.gender;
|
||||
_goal = user.goal;
|
||||
_activity = user.activity;
|
||||
_language = user.preferences['language'] as String? ?? 'ru';
|
||||
_mealTypes = List<String>.from(user.mealTypes);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameCtrl.dispose();
|
||||
_heightCtrl.dispose();
|
||||
_weightCtrl.dispose();
|
||||
_nameController.dispose();
|
||||
_heightController.dispose();
|
||||
_weightController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
String _fmt(double w) =>
|
||||
w == w.truncateToDouble() ? w.toInt().toString() : w.toStringAsFixed(1);
|
||||
String _fmt(double weight) =>
|
||||
weight == weight.truncateToDouble()
|
||||
? weight.toInt().toString()
|
||||
: weight.toStringAsFixed(1);
|
||||
|
||||
Future<void> _pickDob() async {
|
||||
final now = DateTime.now();
|
||||
@@ -395,13 +405,14 @@ class _EditProfileSheetState extends ConsumerState<EditProfileSheet> {
|
||||
}
|
||||
|
||||
Future<void> _save() async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
setState(() => _saving = true);
|
||||
|
||||
final req = UpdateProfileRequest(
|
||||
name: _nameCtrl.text.trim(),
|
||||
heightCm: int.tryParse(_heightCtrl.text),
|
||||
weightKg: double.tryParse(_weightCtrl.text),
|
||||
final request = UpdateProfileRequest(
|
||||
name: _nameController.text.trim(),
|
||||
heightCm: int.tryParse(_heightController.text),
|
||||
weightKg: double.tryParse(_weightController.text),
|
||||
dateOfBirth: _selectedDob != null
|
||||
? '${_selectedDob!.year.toString().padLeft(4, '0')}-'
|
||||
'${_selectedDob!.month.toString().padLeft(2, '0')}-'
|
||||
@@ -414,7 +425,7 @@ class _EditProfileSheetState extends ConsumerState<EditProfileSheet> {
|
||||
mealTypes: _mealTypes,
|
||||
);
|
||||
|
||||
final ok = await ref.read(profileProvider.notifier).update(req);
|
||||
final ok = await ref.read(profileProvider.notifier).update(request);
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() => _saving = false);
|
||||
@@ -422,17 +433,18 @@ class _EditProfileSheetState extends ConsumerState<EditProfileSheet> {
|
||||
if (ok) {
|
||||
Navigator.of(context).pop();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Профиль обновлён')),
|
||||
SnackBar(content: Text(l10n.profileUpdated)),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Не удалось сохранить')),
|
||||
SnackBar(content: Text(l10n.profileSaveFailed)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
final bottomInset = MediaQuery.viewInsetsOf(context).bottom;
|
||||
|
||||
@@ -459,12 +471,11 @@ class _EditProfileSheetState extends ConsumerState<EditProfileSheet> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Text('Редактировать профиль',
|
||||
style: theme.textTheme.titleMedium),
|
||||
Text(l10n.editProfile, style: theme.textTheme.titleMedium),
|
||||
const Spacer(),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Отмена'),
|
||||
child: Text(l10n.cancel),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -479,11 +490,13 @@ class _EditProfileSheetState extends ConsumerState<EditProfileSheet> {
|
||||
children: [
|
||||
// Name
|
||||
TextFormField(
|
||||
controller: _nameCtrl,
|
||||
decoration: const InputDecoration(labelText: 'Имя'),
|
||||
controller: _nameController,
|
||||
decoration: InputDecoration(labelText: l10n.nameLabel),
|
||||
textCapitalization: TextCapitalization.words,
|
||||
validator: (v) =>
|
||||
(v == null || v.trim().isEmpty) ? 'Введите имя' : null,
|
||||
validator: (value) =>
|
||||
(value == null || value.trim().isEmpty)
|
||||
? l10n.nameRequired
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
@@ -492,17 +505,16 @@ class _EditProfileSheetState extends ConsumerState<EditProfileSheet> {
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _heightCtrl,
|
||||
decoration:
|
||||
const InputDecoration(labelText: 'Рост (см)'),
|
||||
controller: _heightController,
|
||||
decoration: InputDecoration(labelText: l10n.heightCm),
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly
|
||||
],
|
||||
validator: (v) {
|
||||
if (v == null || v.isEmpty) return null;
|
||||
final n = int.tryParse(v);
|
||||
if (n == null || n < 100 || n > 250) {
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) return null;
|
||||
final number = int.tryParse(value);
|
||||
if (number == null || number < 100 || number > 250) {
|
||||
return '100–250';
|
||||
}
|
||||
return null;
|
||||
@@ -512,19 +524,18 @@ class _EditProfileSheetState extends ConsumerState<EditProfileSheet> {
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _weightCtrl,
|
||||
decoration:
|
||||
const InputDecoration(labelText: 'Вес (кг)'),
|
||||
controller: _weightController,
|
||||
decoration: InputDecoration(labelText: l10n.weightKg),
|
||||
keyboardType: const TextInputType.numberWithOptions(
|
||||
decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(
|
||||
RegExp(r'[0-9.]'))
|
||||
],
|
||||
validator: (v) {
|
||||
if (v == null || v.isEmpty) return null;
|
||||
final n = double.tryParse(v);
|
||||
if (n == null || n < 30 || n > 300) {
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) return null;
|
||||
final number = double.tryParse(value);
|
||||
if (number == null || number < 30 || number > 300) {
|
||||
return '30–300';
|
||||
}
|
||||
return null;
|
||||
@@ -539,14 +550,13 @@ class _EditProfileSheetState extends ConsumerState<EditProfileSheet> {
|
||||
InkWell(
|
||||
onTap: _pickDob,
|
||||
child: InputDecorator(
|
||||
decoration:
|
||||
const InputDecoration(labelText: 'Дата рождения'),
|
||||
decoration: InputDecoration(labelText: l10n.birthDate),
|
||||
child: Text(
|
||||
_selectedDob != null
|
||||
? '${_selectedDob!.day.toString().padLeft(2, '0')}.'
|
||||
'${_selectedDob!.month.toString().padLeft(2, '0')}.'
|
||||
'${_selectedDob!.year}'
|
||||
: 'Не задано',
|
||||
: l10n.notSet,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
@@ -554,34 +564,34 @@ class _EditProfileSheetState extends ConsumerState<EditProfileSheet> {
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Gender
|
||||
Text('Пол', style: theme.textTheme.labelMedium),
|
||||
Text(l10n.gender, style: theme.textTheme.labelMedium),
|
||||
const SizedBox(height: 8),
|
||||
SegmentedButton<String>(
|
||||
segments: const [
|
||||
ButtonSegment(value: 'male', label: Text('Мужской')),
|
||||
ButtonSegment(value: 'female', label: Text('Женский')),
|
||||
segments: [
|
||||
ButtonSegment(value: 'male', label: Text(l10n.genderMale)),
|
||||
ButtonSegment(value: 'female', label: Text(l10n.genderFemale)),
|
||||
],
|
||||
selected: _gender != null ? {_gender!} : const {},
|
||||
emptySelectionAllowed: true,
|
||||
onSelectionChanged: (s) =>
|
||||
setState(() => _gender = s.isEmpty ? null : s.first),
|
||||
onSelectionChanged: (selection) =>
|
||||
setState(() => _gender = selection.isEmpty ? null : selection.first),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Goal
|
||||
Text('Цель', style: theme.textTheme.labelMedium),
|
||||
Text(l10n.goalLabel.replaceAll(':', '').trim(),
|
||||
style: theme.textTheme.labelMedium),
|
||||
const SizedBox(height: 8),
|
||||
SegmentedButton<String>(
|
||||
segments: const [
|
||||
ButtonSegment(value: 'lose', label: Text('Похудение')),
|
||||
ButtonSegment(
|
||||
value: 'maintain', label: Text('Поддержание')),
|
||||
ButtonSegment(value: 'gain', label: Text('Набор')),
|
||||
segments: [
|
||||
ButtonSegment(value: 'lose', label: Text(l10n.goalLoss)),
|
||||
ButtonSegment(value: 'maintain', label: Text(l10n.goalMaintain)),
|
||||
ButtonSegment(value: 'gain', label: Text(l10n.goalGain)),
|
||||
],
|
||||
selected: _goal != null ? {_goal!} : const {},
|
||||
emptySelectionAllowed: true,
|
||||
onSelectionChanged: (s) =>
|
||||
setState(() => _goal = s.isEmpty ? null : s.first),
|
||||
onSelectionChanged: (selection) =>
|
||||
setState(() => _goal = selection.isEmpty ? null : selection.first),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
@@ -589,21 +599,20 @@ class _EditProfileSheetState extends ConsumerState<EditProfileSheet> {
|
||||
Text('Активность', style: theme.textTheme.labelMedium),
|
||||
const SizedBox(height: 8),
|
||||
SegmentedButton<String>(
|
||||
segments: const [
|
||||
ButtonSegment(value: 'low', label: Text('Низкая')),
|
||||
ButtonSegment(
|
||||
value: 'moderate', label: Text('Средняя')),
|
||||
ButtonSegment(value: 'high', label: Text('Высокая')),
|
||||
segments: [
|
||||
ButtonSegment(value: 'low', label: Text(l10n.activityLow)),
|
||||
ButtonSegment(value: 'moderate', label: Text(l10n.activityMedium)),
|
||||
ButtonSegment(value: 'high', label: Text(l10n.activityHigh)),
|
||||
],
|
||||
selected: _activity != null ? {_activity!} : const {},
|
||||
emptySelectionAllowed: true,
|
||||
onSelectionChanged: (s) => setState(
|
||||
() => _activity = s.isEmpty ? null : s.first),
|
||||
onSelectionChanged: (selection) => setState(
|
||||
() => _activity = selection.isEmpty ? null : selection.first),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Meal types
|
||||
Text('Приёмы пищи', style: theme.textTheme.labelMedium),
|
||||
Text(l10n.mealTypes, style: theme.textTheme.labelMedium),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
@@ -613,7 +622,7 @@ class _EditProfileSheetState extends ConsumerState<EditProfileSheet> {
|
||||
_mealTypes.contains(mealTypeOption.id);
|
||||
return FilterChip(
|
||||
label: Text(
|
||||
'${mealTypeOption.emoji} ${mealTypeOption.label}'),
|
||||
'${mealTypeOption.emoji} ${mealTypeLabel(mealTypeOption.id, l10n)}'),
|
||||
selected: isSelected,
|
||||
onSelected: (selected) {
|
||||
setState(() {
|
||||
@@ -632,21 +641,20 @@ class _EditProfileSheetState extends ConsumerState<EditProfileSheet> {
|
||||
// Language
|
||||
ref.watch(supportedLanguagesProvider).when(
|
||||
data: (languages) => DropdownButtonFormField<String>(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Язык интерфейса'),
|
||||
decoration: InputDecoration(labelText: l10n.language),
|
||||
initialValue: _language,
|
||||
items: languages.entries
|
||||
.map((e) => DropdownMenuItem(
|
||||
value: e.key,
|
||||
child: Text(e.value),
|
||||
.map((entry) => DropdownMenuItem(
|
||||
value: entry.key,
|
||||
child: Text(entry.value),
|
||||
))
|
||||
.toList(),
|
||||
onChanged: (v) => setState(() => _language = v),
|
||||
onChanged: (value) => setState(() => _language = value),
|
||||
),
|
||||
loading: () => const Center(
|
||||
child: CircularProgressIndicator()),
|
||||
error: (_, __) =>
|
||||
const Text('Не удалось загрузить языки'),
|
||||
Text(l10n.historyLoadError),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
@@ -660,7 +668,7 @@ class _EditProfileSheetState extends ConsumerState<EditProfileSheet> {
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2, color: Colors.white),
|
||||
)
|
||||
: const Text('Сохранить'),
|
||||
: Text(l10n.save),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:food_ai/l10n/app_localizations.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../core/storage/local_preferences_provider.dart';
|
||||
@@ -17,6 +18,8 @@ class DishResultSheet extends ConsumerStatefulWidget {
|
||||
required this.onAdded,
|
||||
this.preselectedMealType,
|
||||
this.jobId,
|
||||
this.targetDate,
|
||||
this.createdAt,
|
||||
});
|
||||
|
||||
final DishResult dish;
|
||||
@@ -24,6 +27,13 @@ class DishResultSheet extends ConsumerStatefulWidget {
|
||||
final String? preselectedMealType;
|
||||
final String? jobId;
|
||||
|
||||
/// The diary date to add the entry to (YYYY-MM-DD).
|
||||
/// Falls back to [createdAt] date, then today.
|
||||
final String? targetDate;
|
||||
|
||||
/// Job creation timestamp used as fallback date when [targetDate] is null.
|
||||
final DateTime? createdAt;
|
||||
|
||||
@override
|
||||
ConsumerState<DishResultSheet> createState() => _DishResultSheetState();
|
||||
}
|
||||
@@ -32,6 +42,7 @@ class _DishResultSheetState extends ConsumerState<DishResultSheet> {
|
||||
late int _selectedIndex;
|
||||
late int _portionGrams;
|
||||
late String _mealType;
|
||||
late DateTime _selectedDiaryDate;
|
||||
bool _saving = false;
|
||||
|
||||
final TextEditingController _portionController = TextEditingController();
|
||||
@@ -43,9 +54,19 @@ class _DishResultSheetState extends ConsumerState<DishResultSheet> {
|
||||
_portionGrams = widget.dish.candidates.isNotEmpty
|
||||
? widget.dish.candidates.first.weightGrams
|
||||
: 300;
|
||||
_mealType = widget.preselectedMealType ??
|
||||
kAllMealTypes.first.id;
|
||||
_mealType = widget.preselectedMealType ?? kAllMealTypes.first.id;
|
||||
_portionController.text = '$_portionGrams';
|
||||
if (widget.targetDate != null) {
|
||||
final parts = widget.targetDate!.split('-');
|
||||
_selectedDiaryDate = DateTime(
|
||||
int.parse(parts[0]), int.parse(parts[1]), int.parse(parts[2]),
|
||||
);
|
||||
} else if (widget.createdAt != null) {
|
||||
final createdAtDate = widget.createdAt!;
|
||||
_selectedDiaryDate = DateTime(createdAtDate.year, createdAtDate.month, createdAtDate.day);
|
||||
} else {
|
||||
_selectedDiaryDate = DateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -79,6 +100,17 @@ class _DishResultSheetState extends ConsumerState<DishResultSheet> {
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _pickDate() async {
|
||||
final now = DateTime.now();
|
||||
final pickedDate = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: _selectedDiaryDate,
|
||||
firstDate: now.subtract(const Duration(days: 365)),
|
||||
lastDate: now,
|
||||
);
|
||||
if (pickedDate != null) setState(() => _selectedDiaryDate = pickedDate);
|
||||
}
|
||||
|
||||
void _onPortionEdited(String value) {
|
||||
final parsed = int.tryParse(value);
|
||||
if (parsed != null && parsed >= 10) {
|
||||
@@ -90,8 +122,8 @@ class _DishResultSheetState extends ConsumerState<DishResultSheet> {
|
||||
if (_saving) return;
|
||||
setState(() => _saving = true);
|
||||
|
||||
final selectedDate = ref.read(selectedDateProvider);
|
||||
final dateString = formatDateForDiary(selectedDate);
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final dateString = formatDateForDiary(_selectedDiaryDate);
|
||||
|
||||
final scaledCalories = _scale(_selected.calories);
|
||||
final scaledProtein = _scale(_selected.proteinG);
|
||||
@@ -120,7 +152,7 @@ class _DishResultSheetState extends ConsumerState<DishResultSheet> {
|
||||
if (mounted) {
|
||||
setState(() => _saving = false);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Не удалось добавить. Попробуйте ещё раз.')),
|
||||
SnackBar(content: Text(l10n.addFailed)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -128,6 +160,7 @@ class _DishResultSheetState extends ConsumerState<DishResultSheet> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
final hasCandidates = widget.dish.candidates.isNotEmpty;
|
||||
|
||||
@@ -150,7 +183,7 @@ class _DishResultSheetState extends ConsumerState<DishResultSheet> {
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 8, 0),
|
||||
child: Row(
|
||||
children: [
|
||||
Text('Распознано блюдо', style: theme.textTheme.titleMedium),
|
||||
Text(l10n.dishResultTitle, style: theme.textTheme.titleMedium),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
@@ -179,7 +212,7 @@ class _DishResultSheetState extends ConsumerState<DishResultSheet> {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'КБЖУ приблизительные — определены по фото.',
|
||||
l10n.nutritionApproximate,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
@@ -199,6 +232,11 @@ class _DishResultSheetState extends ConsumerState<DishResultSheet> {
|
||||
if (value != null) setState(() => _mealType = value);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
_DatePickerRow(
|
||||
date: _selectedDiaryDate,
|
||||
onTap: _pickDate,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
)
|
||||
@@ -207,13 +245,13 @@ class _DishResultSheetState extends ConsumerState<DishResultSheet> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'Блюдо не распознано',
|
||||
l10n.dishNotRecognized,
|
||||
style: theme.textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Попробовать снова'),
|
||||
child: Text(l10n.tryAgain),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -232,7 +270,7 @@ class _DishResultSheetState extends ConsumerState<DishResultSheet> {
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Text('Добавить в журнал'),
|
||||
: Text(l10n.addToJournal),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -241,6 +279,47 @@ class _DishResultSheetState extends ConsumerState<DishResultSheet> {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Date picker row
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class _DatePickerRow extends StatelessWidget {
|
||||
final DateTime date;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _DatePickerRow({required this.date, required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final now = DateTime.now();
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
final target = DateTime(date.year, date.month, date.day);
|
||||
|
||||
final String label;
|
||||
if (target == today) {
|
||||
label = l10n.today;
|
||||
} else if (target == today.subtract(const Duration(days: 1))) {
|
||||
label = l10n.yesterday;
|
||||
} else {
|
||||
label = '${date.day.toString().padLeft(2, '0')}.${date.month.toString().padLeft(2, '0')}.${date.year}';
|
||||
}
|
||||
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: InputDecorator(
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.dateLabel,
|
||||
border: const OutlineInputBorder(),
|
||||
suffixIcon: const Icon(Icons.calendar_today_outlined),
|
||||
),
|
||||
child: Text(label),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Candidates selector
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -258,11 +337,12 @@ class _CandidatesSection extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Выберите блюдо', style: theme.textTheme.titleMedium),
|
||||
Text(l10n.selectDish, style: theme.textTheme.titleMedium),
|
||||
const SizedBox(height: 8),
|
||||
...candidates.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
@@ -378,6 +458,7 @@ class _NutritionCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
return Card(
|
||||
child: Padding(
|
||||
@@ -388,7 +469,7 @@ class _NutritionCard extends StatelessWidget {
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'≈ ${calories.toInt()} ккал',
|
||||
'≈ ${calories.toInt()} ${l10n.caloriesUnit}',
|
||||
style: theme.textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.primary,
|
||||
@@ -396,7 +477,7 @@ class _NutritionCard extends StatelessWidget {
|
||||
),
|
||||
const Spacer(),
|
||||
Tooltip(
|
||||
message: 'Приблизительные значения на основе фото',
|
||||
message: l10n.nutritionApproximate,
|
||||
child: Text(
|
||||
'≈',
|
||||
style: theme.textTheme.titleLarge?.copyWith(
|
||||
@@ -411,18 +492,18 @@ class _NutritionCard extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
_MacroChip(
|
||||
label: 'Белки',
|
||||
value: '${proteinG.toStringAsFixed(1)} г',
|
||||
label: l10n.proteinLabel,
|
||||
value: '${proteinG.toStringAsFixed(1)} ${l10n.gramsUnit}',
|
||||
color: Colors.blue,
|
||||
),
|
||||
_MacroChip(
|
||||
label: 'Жиры',
|
||||
value: '${fatG.toStringAsFixed(1)} г',
|
||||
label: l10n.fatLabel,
|
||||
value: '${fatG.toStringAsFixed(1)} ${l10n.gramsUnit}',
|
||||
color: Colors.orange,
|
||||
),
|
||||
_MacroChip(
|
||||
label: 'Углеводы',
|
||||
value: '${carbsG.toStringAsFixed(1)} г',
|
||||
label: l10n.carbsLabel,
|
||||
value: '${carbsG.toStringAsFixed(1)} ${l10n.gramsUnit}',
|
||||
color: Colors.green,
|
||||
),
|
||||
],
|
||||
@@ -482,11 +563,12 @@ class _PortionRow extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Порция', style: theme.textTheme.titleSmall),
|
||||
Text(l10n.portion, style: theme.textTheme.titleSmall),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
@@ -502,9 +584,9 @@ class _PortionRow extends StatelessWidget {
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
onChanged: onChanged,
|
||||
decoration: const InputDecoration(
|
||||
suffixText: 'г',
|
||||
border: OutlineInputBorder(),
|
||||
decoration: InputDecoration(
|
||||
suffixText: l10n.gramsUnit,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -535,11 +617,12 @@ class _MealTypeDropdown extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Приём пищи', style: theme.textTheme.titleSmall),
|
||||
Text(l10n.mealType, style: theme.textTheme.titleSmall),
|
||||
const SizedBox(height: 8),
|
||||
DropdownButtonFormField<String>(
|
||||
initialValue: selected,
|
||||
@@ -550,7 +633,7 @@ class _MealTypeDropdown extends StatelessWidget {
|
||||
.map((mealTypeOption) => DropdownMenuItem(
|
||||
value: mealTypeOption.id,
|
||||
child: Text(
|
||||
'${mealTypeOption.emoji} ${mealTypeOption.label}'),
|
||||
'${mealTypeOption.emoji} ${mealTypeLabel(mealTypeOption.id, l10n)}'),
|
||||
))
|
||||
.toList(),
|
||||
onChanged: onChanged,
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:food_ai/l10n/app_localizations.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../home/home_provider.dart';
|
||||
import '../scan/dish_result_screen.dart';
|
||||
import 'recognition_service.dart';
|
||||
|
||||
/// Full-screen page showing all of today's unlinked dish recognition jobs.
|
||||
/// Full-screen page showing all dish recognition jobs.
|
||||
class RecognitionHistoryScreen extends ConsumerWidget {
|
||||
const RecognitionHistoryScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final jobsState = ref.watch(todayJobsProvider);
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final jobsState = ref.watch(allJobsProvider);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('История распознавания'),
|
||||
title: Text(l10n.historyTitle),
|
||||
),
|
||||
body: jobsState.when(
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
@@ -23,20 +25,18 @@ class RecognitionHistoryScreen extends ConsumerWidget {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('Не удалось загрузить историю'),
|
||||
Text(l10n.historyLoadError),
|
||||
const SizedBox(height: 12),
|
||||
FilledButton(
|
||||
onPressed: () => ref.invalidate(todayJobsProvider),
|
||||
child: const Text('Повторить'),
|
||||
onPressed: () => ref.invalidate(allJobsProvider),
|
||||
child: Text(l10n.retry),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
data: (jobs) {
|
||||
if (jobs.isEmpty) {
|
||||
return const Center(
|
||||
child: Text('Нет распознаваний за сегодня'),
|
||||
);
|
||||
return Center(child: Text(l10n.noHistory));
|
||||
}
|
||||
return ListView.separated(
|
||||
padding: const EdgeInsets.all(16),
|
||||
@@ -58,6 +58,7 @@ class _HistoryJobTile extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
|
||||
final isDone = job.status == 'done';
|
||||
@@ -84,11 +85,11 @@ class _HistoryJobTile extends ConsumerWidget {
|
||||
|
||||
final String titleText;
|
||||
if (isDone) {
|
||||
titleText = dishName ?? 'Блюдо распознано';
|
||||
titleText = dishName ?? l10n.dishRecognized;
|
||||
} else if (isProcessing) {
|
||||
titleText = 'Распознаётся…';
|
||||
titleText = l10n.recognizing;
|
||||
} else {
|
||||
titleText = 'Ошибка распознавания';
|
||||
titleText = l10n.recognitionError;
|
||||
}
|
||||
|
||||
final contextParts = [
|
||||
@@ -118,6 +119,8 @@ class _HistoryJobTile extends ConsumerWidget {
|
||||
dish: job.result!,
|
||||
preselectedMealType: job.targetMealType,
|
||||
jobId: job.id,
|
||||
targetDate: job.targetDate,
|
||||
createdAt: job.createdAt,
|
||||
onAdded: () => Navigator.pop(sheetContext),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -282,7 +282,16 @@ class RecognitionService {
|
||||
|
||||
/// Returns today's recognition jobs that have not yet been linked to a diary entry.
|
||||
Future<List<DishJobSummary>> listTodayUnlinkedJobs() async {
|
||||
final data = await _client.get('/ai/jobs') as List<dynamic>;
|
||||
final data = await _client.getList('/ai/jobs');
|
||||
return data
|
||||
.map((element) =>
|
||||
DishJobSummary.fromJson(element as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Returns all recognition jobs for the current user, newest first.
|
||||
Future<List<DishJobSummary>> listAllJobs() async {
|
||||
final data = await _client.getList('/ai/jobs/history');
|
||||
return data
|
||||
.map((element) =>
|
||||
DishJobSummary.fromJson(element as Map<String, dynamic>))
|
||||
|
||||
100
client/lib/l10n/app_ar.arb
Normal file
100
client/lib/l10n/app_ar.arb
Normal file
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"@@locale": "ar",
|
||||
"appTitle": "FoodAI",
|
||||
"greetingMorning": "صباح الخير",
|
||||
"greetingAfternoon": "مساء الخير",
|
||||
"greetingEvening": "مساء النور",
|
||||
"caloriesUnit": "سعرة",
|
||||
"gramsUnit": "غ",
|
||||
"goalLabel": "الهدف:",
|
||||
"consumed": "المستهلك",
|
||||
"remaining": "المتبقي",
|
||||
"exceeded": "تجاوز",
|
||||
"proteinLabel": "بروتين",
|
||||
"fatLabel": "دهون",
|
||||
"carbsLabel": "كربوهيدرات",
|
||||
"today": "اليوم",
|
||||
"yesterday": "أمس",
|
||||
"mealsSection": "الوجبات",
|
||||
"addDish": "إضافة طبق",
|
||||
"scanDish": "مسح",
|
||||
"menu": "القائمة",
|
||||
"dishHistory": "سجل الأطباق",
|
||||
"recommendCook": "نوصي بطهي",
|
||||
"camera": "الكاميرا",
|
||||
"gallery": "المعرض",
|
||||
"analyzingPhoto": "تحليل الصورة...",
|
||||
"inQueue": "أنت في قائمة الانتظار",
|
||||
"queuePosition": "الموضع {position}",
|
||||
"@queuePosition": {
|
||||
"placeholders": {
|
||||
"position": { "type": "int" }
|
||||
}
|
||||
},
|
||||
"processing": "جارٍ المعالجة...",
|
||||
"upgradePrompt": "تخطي قائمة الانتظار؟ ترقية →",
|
||||
"recognitionFailed": "فشل التعرف. حاول مرة أخرى.",
|
||||
"dishRecognition": "التعرف على الأطباق",
|
||||
"all": "الكل",
|
||||
"dishRecognized": "تم التعرف على الطبق",
|
||||
"recognizing": "جارٍ التعرف…",
|
||||
"recognitionError": "خطأ في التعرف",
|
||||
"dishResultTitle": "تم التعرف على الطبق",
|
||||
"selectDish": "اختر طبقًا",
|
||||
"dishNotRecognized": "لم يتم التعرف على الطبق",
|
||||
"tryAgain": "حاول مرة أخرى",
|
||||
"nutritionApproximate": "القيم الغذائية تقريبية — مقدَّرة من الصورة.",
|
||||
"portion": "الحصة",
|
||||
"mealType": "نوع الوجبة",
|
||||
"dateLabel": "التاريخ",
|
||||
"addToJournal": "إضافة إلى السجل",
|
||||
"addFailed": "فشل الإضافة. حاول مرة أخرى.",
|
||||
"historyTitle": "سجل التعرف",
|
||||
"historyLoadError": "فشل تحميل السجل",
|
||||
"retry": "إعادة المحاولة",
|
||||
"noHistory": "لا توجد تعرفات بعد",
|
||||
"profileTitle": "الملف الشخصي",
|
||||
"edit": "تعديل",
|
||||
"bodyParams": "معاملات الجسم",
|
||||
"goalActivity": "الهدف والنشاط",
|
||||
"nutrition": "التغذية",
|
||||
"settings": "الإعدادات",
|
||||
"height": "الطول",
|
||||
"weight": "الوزن",
|
||||
"age": "العمر",
|
||||
"gender": "الجنس",
|
||||
"genderMale": "ذكر",
|
||||
"genderFemale": "أنثى",
|
||||
"goalLoss": "خسارة الوزن",
|
||||
"goalMaintain": "الحفاظ على الوزن",
|
||||
"goalGain": "بناء العضلات",
|
||||
"activityLow": "منخفض",
|
||||
"activityMedium": "متوسط",
|
||||
"activityHigh": "مرتفع",
|
||||
"calorieGoal": "هدف السعرات",
|
||||
"mealTypes": "أنواع الوجبات",
|
||||
"formulaNote": "محسوب بمعادلة ميفلين سانت جيور",
|
||||
"language": "اللغة",
|
||||
"notSet": "غير محدد",
|
||||
"calorieHint": "أدخل معاملات الجسم لحساب هدف السعرات",
|
||||
"logout": "تسجيل الخروج",
|
||||
"editProfile": "تعديل الملف الشخصي",
|
||||
"cancel": "إلغاء",
|
||||
"save": "حفظ",
|
||||
"nameLabel": "الاسم",
|
||||
"heightCm": "الطول (سم)",
|
||||
"weightKg": "الوزن (كغ)",
|
||||
"birthDate": "تاريخ الميلاد",
|
||||
"nameRequired": "أدخل الاسم",
|
||||
"profileUpdated": "تم تحديث الملف الشخصي",
|
||||
"profileSaveFailed": "فشل الحفظ",
|
||||
"mealTypeBreakfast": "الإفطار",
|
||||
"mealTypeSecondBreakfast": "الإفطار الثاني",
|
||||
"mealTypeLunch": "الغداء",
|
||||
"mealTypeAfternoonSnack": "وجبة العصر",
|
||||
"mealTypeDinner": "العشاء",
|
||||
"mealTypeSnack": "وجبة خفيفة",
|
||||
"navHome": "الرئيسية",
|
||||
"navProducts": "المنتجات",
|
||||
"navRecipes": "الوصفات"
|
||||
}
|
||||
100
client/lib/l10n/app_de.arb
Normal file
100
client/lib/l10n/app_de.arb
Normal file
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"@@locale": "de",
|
||||
"appTitle": "FoodAI",
|
||||
"greetingMorning": "Guten Morgen",
|
||||
"greetingAfternoon": "Guten Tag",
|
||||
"greetingEvening": "Guten Abend",
|
||||
"caloriesUnit": "kcal",
|
||||
"gramsUnit": "g",
|
||||
"goalLabel": "Ziel:",
|
||||
"consumed": "Verzehrt",
|
||||
"remaining": "Verbleibend",
|
||||
"exceeded": "Überschritten",
|
||||
"proteinLabel": "Protein",
|
||||
"fatLabel": "Fett",
|
||||
"carbsLabel": "Kohlenhydrate",
|
||||
"today": "Heute",
|
||||
"yesterday": "Gestern",
|
||||
"mealsSection": "Mahlzeiten",
|
||||
"addDish": "Gericht hinzufügen",
|
||||
"scanDish": "Scannen",
|
||||
"menu": "Menü",
|
||||
"dishHistory": "Gerichtverlauf",
|
||||
"recommendCook": "Wir empfehlen zu kochen",
|
||||
"camera": "Kamera",
|
||||
"gallery": "Galerie",
|
||||
"analyzingPhoto": "Foto wird analysiert...",
|
||||
"inQueue": "Sie sind in der Warteschlange",
|
||||
"queuePosition": "Position {position}",
|
||||
"@queuePosition": {
|
||||
"placeholders": {
|
||||
"position": { "type": "int" }
|
||||
}
|
||||
},
|
||||
"processing": "Verarbeitung...",
|
||||
"upgradePrompt": "Warteschlange überspringen? Upgrade →",
|
||||
"recognitionFailed": "Erkennung fehlgeschlagen. Erneut versuchen.",
|
||||
"dishRecognition": "Gerichterkennung",
|
||||
"all": "Alle",
|
||||
"dishRecognized": "Gericht erkannt",
|
||||
"recognizing": "Wird erkannt…",
|
||||
"recognitionError": "Erkennungsfehler",
|
||||
"dishResultTitle": "Gericht erkannt",
|
||||
"selectDish": "Gericht auswählen",
|
||||
"dishNotRecognized": "Gericht nicht erkannt",
|
||||
"tryAgain": "Erneut versuchen",
|
||||
"nutritionApproximate": "Nährwerte sind ungefähr — aus dem Foto geschätzt.",
|
||||
"portion": "Portion",
|
||||
"mealType": "Mahlzeittyp",
|
||||
"dateLabel": "Datum",
|
||||
"addToJournal": "Zum Tagebuch hinzufügen",
|
||||
"addFailed": "Hinzufügen fehlgeschlagen. Erneut versuchen.",
|
||||
"historyTitle": "Erkennungsverlauf",
|
||||
"historyLoadError": "Verlauf konnte nicht geladen werden",
|
||||
"retry": "Wiederholen",
|
||||
"noHistory": "Noch keine Erkennungen",
|
||||
"profileTitle": "Profil",
|
||||
"edit": "Bearbeiten",
|
||||
"bodyParams": "KÖRPERPARAMETER",
|
||||
"goalActivity": "ZIEL & AKTIVITÄT",
|
||||
"nutrition": "ERNÄHRUNG",
|
||||
"settings": "EINSTELLUNGEN",
|
||||
"height": "Größe",
|
||||
"weight": "Gewicht",
|
||||
"age": "Alter",
|
||||
"gender": "Geschlecht",
|
||||
"genderMale": "Männlich",
|
||||
"genderFemale": "Weiblich",
|
||||
"goalLoss": "Gewichtsverlust",
|
||||
"goalMaintain": "Gewicht halten",
|
||||
"goalGain": "Muskelaufbau",
|
||||
"activityLow": "Niedrig",
|
||||
"activityMedium": "Mittel",
|
||||
"activityHigh": "Hoch",
|
||||
"calorieGoal": "Kalorienziel",
|
||||
"mealTypes": "Mahlzeittypen",
|
||||
"formulaNote": "Berechnet mit der Mifflin-St Jeor Formel",
|
||||
"language": "Sprache",
|
||||
"notSet": "Nicht festgelegt",
|
||||
"calorieHint": "Körperparameter eingeben, um das Kalorienziel zu berechnen",
|
||||
"logout": "Abmelden",
|
||||
"editProfile": "Profil bearbeiten",
|
||||
"cancel": "Abbrechen",
|
||||
"save": "Speichern",
|
||||
"nameLabel": "Name",
|
||||
"heightCm": "Größe (cm)",
|
||||
"weightKg": "Gewicht (kg)",
|
||||
"birthDate": "Geburtsdatum",
|
||||
"nameRequired": "Name eingeben",
|
||||
"profileUpdated": "Profil aktualisiert",
|
||||
"profileSaveFailed": "Speichern fehlgeschlagen",
|
||||
"mealTypeBreakfast": "Frühstück",
|
||||
"mealTypeSecondBreakfast": "Zweites Frühstück",
|
||||
"mealTypeLunch": "Mittagessen",
|
||||
"mealTypeAfternoonSnack": "Nachmittagssnack",
|
||||
"mealTypeDinner": "Abendessen",
|
||||
"mealTypeSnack": "Snack",
|
||||
"navHome": "Startseite",
|
||||
"navProducts": "Produkte",
|
||||
"navRecipes": "Rezepte"
|
||||
}
|
||||
100
client/lib/l10n/app_en.arb
Normal file
100
client/lib/l10n/app_en.arb
Normal file
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"@@locale": "en",
|
||||
"appTitle": "FoodAI",
|
||||
"greetingMorning": "Good morning",
|
||||
"greetingAfternoon": "Good afternoon",
|
||||
"greetingEvening": "Good evening",
|
||||
"caloriesUnit": "kcal",
|
||||
"gramsUnit": "g",
|
||||
"goalLabel": "goal:",
|
||||
"consumed": "Consumed",
|
||||
"remaining": "Remaining",
|
||||
"exceeded": "Exceeded",
|
||||
"proteinLabel": "Protein",
|
||||
"fatLabel": "Fat",
|
||||
"carbsLabel": "Carbs",
|
||||
"today": "Today",
|
||||
"yesterday": "Yesterday",
|
||||
"mealsSection": "Meals",
|
||||
"addDish": "Add dish",
|
||||
"scanDish": "Scan",
|
||||
"menu": "Menu",
|
||||
"dishHistory": "Dish history",
|
||||
"recommendCook": "We recommend cooking",
|
||||
"camera": "Camera",
|
||||
"gallery": "Gallery",
|
||||
"analyzingPhoto": "Analyzing photo...",
|
||||
"inQueue": "You are in queue",
|
||||
"queuePosition": "Position {position}",
|
||||
"@queuePosition": {
|
||||
"placeholders": {
|
||||
"position": { "type": "int" }
|
||||
}
|
||||
},
|
||||
"processing": "Processing...",
|
||||
"upgradePrompt": "Skip the queue? Upgrade →",
|
||||
"recognitionFailed": "Recognition failed. Try again.",
|
||||
"dishRecognition": "Dish recognition",
|
||||
"all": "All",
|
||||
"dishRecognized": "Dish recognized",
|
||||
"recognizing": "Recognizing…",
|
||||
"recognitionError": "Recognition error",
|
||||
"dishResultTitle": "Dish recognized",
|
||||
"selectDish": "Select dish",
|
||||
"dishNotRecognized": "Dish not recognized",
|
||||
"tryAgain": "Try again",
|
||||
"nutritionApproximate": "Nutrition is approximate — estimated from photo.",
|
||||
"portion": "Portion",
|
||||
"mealType": "Meal type",
|
||||
"dateLabel": "Date",
|
||||
"addToJournal": "Add to journal",
|
||||
"addFailed": "Failed to add. Try again.",
|
||||
"historyTitle": "Recognition history",
|
||||
"historyLoadError": "Failed to load history",
|
||||
"retry": "Retry",
|
||||
"noHistory": "No recognitions yet",
|
||||
"profileTitle": "Profile",
|
||||
"edit": "Edit",
|
||||
"bodyParams": "BODY PARAMS",
|
||||
"goalActivity": "GOAL & ACTIVITY",
|
||||
"nutrition": "NUTRITION",
|
||||
"settings": "SETTINGS",
|
||||
"height": "Height",
|
||||
"weight": "Weight",
|
||||
"age": "Age",
|
||||
"gender": "Gender",
|
||||
"genderMale": "Male",
|
||||
"genderFemale": "Female",
|
||||
"goalLoss": "Weight loss",
|
||||
"goalMaintain": "Maintenance",
|
||||
"goalGain": "Muscle gain",
|
||||
"activityLow": "Low",
|
||||
"activityMedium": "Medium",
|
||||
"activityHigh": "High",
|
||||
"calorieGoal": "Calorie goal",
|
||||
"mealTypes": "Meal types",
|
||||
"formulaNote": "Calculated using the Mifflin-St Jeor formula",
|
||||
"language": "Language",
|
||||
"notSet": "Not set",
|
||||
"calorieHint": "Enter body params to calculate calorie goal",
|
||||
"logout": "Log out",
|
||||
"editProfile": "Edit profile",
|
||||
"cancel": "Cancel",
|
||||
"save": "Save",
|
||||
"nameLabel": "Name",
|
||||
"heightCm": "Height (cm)",
|
||||
"weightKg": "Weight (kg)",
|
||||
"birthDate": "Date of birth",
|
||||
"nameRequired": "Enter name",
|
||||
"profileUpdated": "Profile updated",
|
||||
"profileSaveFailed": "Failed to save",
|
||||
"mealTypeBreakfast": "Breakfast",
|
||||
"mealTypeSecondBreakfast": "Second breakfast",
|
||||
"mealTypeLunch": "Lunch",
|
||||
"mealTypeAfternoonSnack": "Afternoon snack",
|
||||
"mealTypeDinner": "Dinner",
|
||||
"mealTypeSnack": "Snack",
|
||||
"navHome": "Home",
|
||||
"navProducts": "Products",
|
||||
"navRecipes": "Recipes"
|
||||
}
|
||||
100
client/lib/l10n/app_es.arb
Normal file
100
client/lib/l10n/app_es.arb
Normal file
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"@@locale": "es",
|
||||
"appTitle": "FoodAI",
|
||||
"greetingMorning": "Buenos días",
|
||||
"greetingAfternoon": "Buenas tardes",
|
||||
"greetingEvening": "Buenas noches",
|
||||
"caloriesUnit": "kcal",
|
||||
"gramsUnit": "g",
|
||||
"goalLabel": "meta:",
|
||||
"consumed": "Consumido",
|
||||
"remaining": "Restante",
|
||||
"exceeded": "Excedido",
|
||||
"proteinLabel": "Proteínas",
|
||||
"fatLabel": "Grasas",
|
||||
"carbsLabel": "Carbohidratos",
|
||||
"today": "Hoy",
|
||||
"yesterday": "Ayer",
|
||||
"mealsSection": "Comidas",
|
||||
"addDish": "Añadir plato",
|
||||
"scanDish": "Escanear",
|
||||
"menu": "Menú",
|
||||
"dishHistory": "Historial de platos",
|
||||
"recommendCook": "Recomendamos cocinar",
|
||||
"camera": "Cámara",
|
||||
"gallery": "Galería",
|
||||
"analyzingPhoto": "Analizando foto...",
|
||||
"inQueue": "Estás en la cola",
|
||||
"queuePosition": "Posición {position}",
|
||||
"@queuePosition": {
|
||||
"placeholders": {
|
||||
"position": { "type": "int" }
|
||||
}
|
||||
},
|
||||
"processing": "Procesando...",
|
||||
"upgradePrompt": "¿Saltar la cola? Actualiza →",
|
||||
"recognitionFailed": "Reconocimiento fallido. Inténtalo de nuevo.",
|
||||
"dishRecognition": "Reconocimiento de platos",
|
||||
"all": "Todos",
|
||||
"dishRecognized": "Plato reconocido",
|
||||
"recognizing": "Reconociendo…",
|
||||
"recognitionError": "Error de reconocimiento",
|
||||
"dishResultTitle": "Plato reconocido",
|
||||
"selectDish": "Selecciona un plato",
|
||||
"dishNotRecognized": "Plato no reconocido",
|
||||
"tryAgain": "Intentar de nuevo",
|
||||
"nutritionApproximate": "Los valores nutricionales son aproximados — estimados a partir de la foto.",
|
||||
"portion": "Porción",
|
||||
"mealType": "Tipo de comida",
|
||||
"dateLabel": "Fecha",
|
||||
"addToJournal": "Añadir al diario",
|
||||
"addFailed": "Error al añadir. Inténtalo de nuevo.",
|
||||
"historyTitle": "Historial de reconocimientos",
|
||||
"historyLoadError": "Error al cargar el historial",
|
||||
"retry": "Reintentar",
|
||||
"noHistory": "Sin reconocimientos aún",
|
||||
"profileTitle": "Perfil",
|
||||
"edit": "Editar",
|
||||
"bodyParams": "PARÁMETROS CORPORALES",
|
||||
"goalActivity": "OBJETIVO Y ACTIVIDAD",
|
||||
"nutrition": "NUTRICIÓN",
|
||||
"settings": "AJUSTES",
|
||||
"height": "Altura",
|
||||
"weight": "Peso",
|
||||
"age": "Edad",
|
||||
"gender": "Género",
|
||||
"genderMale": "Masculino",
|
||||
"genderFemale": "Femenino",
|
||||
"goalLoss": "Pérdida de peso",
|
||||
"goalMaintain": "Mantenimiento",
|
||||
"goalGain": "Ganancia muscular",
|
||||
"activityLow": "Baja",
|
||||
"activityMedium": "Media",
|
||||
"activityHigh": "Alta",
|
||||
"calorieGoal": "Objetivo calórico",
|
||||
"mealTypes": "Tipos de comida",
|
||||
"formulaNote": "Calculado con la fórmula de Mifflin-St Jeor",
|
||||
"language": "Idioma",
|
||||
"notSet": "No establecido",
|
||||
"calorieHint": "Introduce los parámetros corporales para calcular el objetivo calórico",
|
||||
"logout": "Cerrar sesión",
|
||||
"editProfile": "Editar perfil",
|
||||
"cancel": "Cancelar",
|
||||
"save": "Guardar",
|
||||
"nameLabel": "Nombre",
|
||||
"heightCm": "Altura (cm)",
|
||||
"weightKg": "Peso (kg)",
|
||||
"birthDate": "Fecha de nacimiento",
|
||||
"nameRequired": "Introduce el nombre",
|
||||
"profileUpdated": "Perfil actualizado",
|
||||
"profileSaveFailed": "Error al guardar",
|
||||
"mealTypeBreakfast": "Desayuno",
|
||||
"mealTypeSecondBreakfast": "Segundo desayuno",
|
||||
"mealTypeLunch": "Almuerzo",
|
||||
"mealTypeAfternoonSnack": "Merienda",
|
||||
"mealTypeDinner": "Cena",
|
||||
"mealTypeSnack": "Aperitivo",
|
||||
"navHome": "Inicio",
|
||||
"navProducts": "Productos",
|
||||
"navRecipes": "Recetas"
|
||||
}
|
||||
100
client/lib/l10n/app_fr.arb
Normal file
100
client/lib/l10n/app_fr.arb
Normal file
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"@@locale": "fr",
|
||||
"appTitle": "FoodAI",
|
||||
"greetingMorning": "Bonjour",
|
||||
"greetingAfternoon": "Bon après-midi",
|
||||
"greetingEvening": "Bonsoir",
|
||||
"caloriesUnit": "kcal",
|
||||
"gramsUnit": "g",
|
||||
"goalLabel": "objectif :",
|
||||
"consumed": "Consommé",
|
||||
"remaining": "Restant",
|
||||
"exceeded": "Dépassé",
|
||||
"proteinLabel": "Protéines",
|
||||
"fatLabel": "Lipides",
|
||||
"carbsLabel": "Glucides",
|
||||
"today": "Aujourd'hui",
|
||||
"yesterday": "Hier",
|
||||
"mealsSection": "Repas",
|
||||
"addDish": "Ajouter un plat",
|
||||
"scanDish": "Scanner",
|
||||
"menu": "Menu",
|
||||
"dishHistory": "Historique des plats",
|
||||
"recommendCook": "Nous recommandons de cuisiner",
|
||||
"camera": "Appareil photo",
|
||||
"gallery": "Galerie",
|
||||
"analyzingPhoto": "Analyse de la photo...",
|
||||
"inQueue": "Vous êtes en file d'attente",
|
||||
"queuePosition": "Position {position}",
|
||||
"@queuePosition": {
|
||||
"placeholders": {
|
||||
"position": { "type": "int" }
|
||||
}
|
||||
},
|
||||
"processing": "Traitement...",
|
||||
"upgradePrompt": "Passer la file ? Passez à Premium →",
|
||||
"recognitionFailed": "Reconnaissance échouée. Réessayez.",
|
||||
"dishRecognition": "Reconnaissance de plats",
|
||||
"all": "Tous",
|
||||
"dishRecognized": "Plat reconnu",
|
||||
"recognizing": "Reconnaissance en cours…",
|
||||
"recognitionError": "Erreur de reconnaissance",
|
||||
"dishResultTitle": "Plat reconnu",
|
||||
"selectDish": "Sélectionner un plat",
|
||||
"dishNotRecognized": "Plat non reconnu",
|
||||
"tryAgain": "Réessayer",
|
||||
"nutritionApproximate": "Les valeurs nutritionnelles sont approximatives — estimées à partir de la photo.",
|
||||
"portion": "Portion",
|
||||
"mealType": "Type de repas",
|
||||
"dateLabel": "Date",
|
||||
"addToJournal": "Ajouter au journal",
|
||||
"addFailed": "Échec de l'ajout. Réessayez.",
|
||||
"historyTitle": "Historique des reconnaissances",
|
||||
"historyLoadError": "Impossible de charger l'historique",
|
||||
"retry": "Réessayer",
|
||||
"noHistory": "Aucune reconnaissance pour l'instant",
|
||||
"profileTitle": "Profil",
|
||||
"edit": "Modifier",
|
||||
"bodyParams": "PARAMÈTRES CORPORELS",
|
||||
"goalActivity": "OBJECTIF & ACTIVITÉ",
|
||||
"nutrition": "NUTRITION",
|
||||
"settings": "PARAMÈTRES",
|
||||
"height": "Taille",
|
||||
"weight": "Poids",
|
||||
"age": "Âge",
|
||||
"gender": "Sexe",
|
||||
"genderMale": "Masculin",
|
||||
"genderFemale": "Féminin",
|
||||
"goalLoss": "Perte de poids",
|
||||
"goalMaintain": "Maintien",
|
||||
"goalGain": "Prise de masse",
|
||||
"activityLow": "Faible",
|
||||
"activityMedium": "Moyenne",
|
||||
"activityHigh": "Élevée",
|
||||
"calorieGoal": "Objectif calorique",
|
||||
"mealTypes": "Types de repas",
|
||||
"formulaNote": "Calculé avec la formule de Mifflin-St Jeor",
|
||||
"language": "Langue",
|
||||
"notSet": "Non défini",
|
||||
"calorieHint": "Saisissez les paramètres corporels pour calculer l'objectif calorique",
|
||||
"logout": "Se déconnecter",
|
||||
"editProfile": "Modifier le profil",
|
||||
"cancel": "Annuler",
|
||||
"save": "Enregistrer",
|
||||
"nameLabel": "Nom",
|
||||
"heightCm": "Taille (cm)",
|
||||
"weightKg": "Poids (kg)",
|
||||
"birthDate": "Date de naissance",
|
||||
"nameRequired": "Saisir le nom",
|
||||
"profileUpdated": "Profil mis à jour",
|
||||
"profileSaveFailed": "Échec de l'enregistrement",
|
||||
"mealTypeBreakfast": "Petit-déjeuner",
|
||||
"mealTypeSecondBreakfast": "Deuxième petit-déjeuner",
|
||||
"mealTypeLunch": "Déjeuner",
|
||||
"mealTypeAfternoonSnack": "Goûter",
|
||||
"mealTypeDinner": "Dîner",
|
||||
"mealTypeSnack": "Collation",
|
||||
"navHome": "Accueil",
|
||||
"navProducts": "Produits",
|
||||
"navRecipes": "Recettes"
|
||||
}
|
||||
100
client/lib/l10n/app_hi.arb
Normal file
100
client/lib/l10n/app_hi.arb
Normal file
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"@@locale": "hi",
|
||||
"appTitle": "FoodAI",
|
||||
"greetingMorning": "सुप्रभात",
|
||||
"greetingAfternoon": "नमस्ते",
|
||||
"greetingEvening": "शुभ संध्या",
|
||||
"caloriesUnit": "कैलोरी",
|
||||
"gramsUnit": "ग्रा",
|
||||
"goalLabel": "लक्ष्य:",
|
||||
"consumed": "सेवन किया",
|
||||
"remaining": "शेष",
|
||||
"exceeded": "अधिक",
|
||||
"proteinLabel": "प्रोटीन",
|
||||
"fatLabel": "वसा",
|
||||
"carbsLabel": "कार्बोहाइड्रेट",
|
||||
"today": "आज",
|
||||
"yesterday": "कल",
|
||||
"mealsSection": "भोजन",
|
||||
"addDish": "व्यंजन जोड़ें",
|
||||
"scanDish": "स्कैन करें",
|
||||
"menu": "मेनू",
|
||||
"dishHistory": "व्यंजन इतिहास",
|
||||
"recommendCook": "पकाने की सिफारिश",
|
||||
"camera": "कैमरा",
|
||||
"gallery": "गैलरी",
|
||||
"analyzingPhoto": "फ़ोटो का विश्लेषण हो रहा है...",
|
||||
"inQueue": "आप कतार में हैं",
|
||||
"queuePosition": "स्थिति {position}",
|
||||
"@queuePosition": {
|
||||
"placeholders": {
|
||||
"position": { "type": "int" }
|
||||
}
|
||||
},
|
||||
"processing": "प्रसंस्करण हो रहा है...",
|
||||
"upgradePrompt": "कतार छोड़ें? अपग्रेड करें →",
|
||||
"recognitionFailed": "पहचान विफल। पुनः प्रयास करें।",
|
||||
"dishRecognition": "व्यंजन पहचान",
|
||||
"all": "सभी",
|
||||
"dishRecognized": "व्यंजन पहचाना गया",
|
||||
"recognizing": "पहचान हो रही है…",
|
||||
"recognitionError": "पहचान में त्रुटि",
|
||||
"dishResultTitle": "व्यंजन पहचाना गया",
|
||||
"selectDish": "व्यंजन चुनें",
|
||||
"dishNotRecognized": "व्यंजन नहीं पहचाना",
|
||||
"tryAgain": "पुनः प्रयास करें",
|
||||
"nutritionApproximate": "पोषण मूल्य अनुमानित हैं — फ़ोटो से अनुमानित।",
|
||||
"portion": "हिस्सा",
|
||||
"mealType": "भोजन का प्रकार",
|
||||
"dateLabel": "तिथि",
|
||||
"addToJournal": "डायरी में जोड़ें",
|
||||
"addFailed": "जोड़ने में विफल। पुनः प्रयास करें।",
|
||||
"historyTitle": "पहचान इतिहास",
|
||||
"historyLoadError": "इतिहास लोड करने में विफल",
|
||||
"retry": "पुनः प्रयास",
|
||||
"noHistory": "अभी तक कोई पहचान नहीं",
|
||||
"profileTitle": "प्रोफ़ाइल",
|
||||
"edit": "संपादित करें",
|
||||
"bodyParams": "शरीर के पैरामीटर",
|
||||
"goalActivity": "लक्ष्य और गतिविधि",
|
||||
"nutrition": "पोषण",
|
||||
"settings": "सेटिंग्स",
|
||||
"height": "ऊंचाई",
|
||||
"weight": "वज़न",
|
||||
"age": "आयु",
|
||||
"gender": "लिंग",
|
||||
"genderMale": "पुरुष",
|
||||
"genderFemale": "महिला",
|
||||
"goalLoss": "वज़न घटाना",
|
||||
"goalMaintain": "वज़न बनाए रखना",
|
||||
"goalGain": "मांसपेशी बढ़ाना",
|
||||
"activityLow": "कम",
|
||||
"activityMedium": "मध्यम",
|
||||
"activityHigh": "अधिक",
|
||||
"calorieGoal": "कैलोरी लक्ष्य",
|
||||
"mealTypes": "भोजन के प्रकार",
|
||||
"formulaNote": "मिफ्लिन-सेंट जेओर सूत्र का उपयोग करके गणना",
|
||||
"language": "भाषा",
|
||||
"notSet": "सेट नहीं",
|
||||
"calorieHint": "कैलोरी लक्ष्य की गणना के लिए शरीर के पैरामीटर दर्ज करें",
|
||||
"logout": "लॉग आउट",
|
||||
"editProfile": "प्रोफ़ाइल संपादित करें",
|
||||
"cancel": "रद्द करें",
|
||||
"save": "सहेजें",
|
||||
"nameLabel": "नाम",
|
||||
"heightCm": "ऊंचाई (सेमी)",
|
||||
"weightKg": "वज़न (किग्रा)",
|
||||
"birthDate": "जन्म तिथि",
|
||||
"nameRequired": "नाम दर्ज करें",
|
||||
"profileUpdated": "प्रोफ़ाइल अपडेट हुई",
|
||||
"profileSaveFailed": "सहेजने में विफल",
|
||||
"mealTypeBreakfast": "नाश्ता",
|
||||
"mealTypeSecondBreakfast": "दूसरा नाश्ता",
|
||||
"mealTypeLunch": "दोपहर का भोजन",
|
||||
"mealTypeAfternoonSnack": "शाम का नाश्ता",
|
||||
"mealTypeDinner": "रात का खाना",
|
||||
"mealTypeSnack": "स्नैक",
|
||||
"navHome": "होम",
|
||||
"navProducts": "उत्पाद",
|
||||
"navRecipes": "रेसिपी"
|
||||
}
|
||||
100
client/lib/l10n/app_it.arb
Normal file
100
client/lib/l10n/app_it.arb
Normal file
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"@@locale": "it",
|
||||
"appTitle": "FoodAI",
|
||||
"greetingMorning": "Buongiorno",
|
||||
"greetingAfternoon": "Buon pomeriggio",
|
||||
"greetingEvening": "Buonasera",
|
||||
"caloriesUnit": "kcal",
|
||||
"gramsUnit": "g",
|
||||
"goalLabel": "obiettivo:",
|
||||
"consumed": "Consumato",
|
||||
"remaining": "Rimanente",
|
||||
"exceeded": "Superato",
|
||||
"proteinLabel": "Proteine",
|
||||
"fatLabel": "Grassi",
|
||||
"carbsLabel": "Carboidrati",
|
||||
"today": "Oggi",
|
||||
"yesterday": "Ieri",
|
||||
"mealsSection": "Pasti",
|
||||
"addDish": "Aggiungi piatto",
|
||||
"scanDish": "Scansiona",
|
||||
"menu": "Menu",
|
||||
"dishHistory": "Cronologia piatti",
|
||||
"recommendCook": "Consigliamo di cucinare",
|
||||
"camera": "Fotocamera",
|
||||
"gallery": "Galleria",
|
||||
"analyzingPhoto": "Analisi foto in corso...",
|
||||
"inQueue": "Sei in coda",
|
||||
"queuePosition": "Posizione {position}",
|
||||
"@queuePosition": {
|
||||
"placeholders": {
|
||||
"position": { "type": "int" }
|
||||
}
|
||||
},
|
||||
"processing": "Elaborazione...",
|
||||
"upgradePrompt": "Salta la coda? Aggiorna →",
|
||||
"recognitionFailed": "Riconoscimento fallito. Riprova.",
|
||||
"dishRecognition": "Riconoscimento piatti",
|
||||
"all": "Tutti",
|
||||
"dishRecognized": "Piatto riconosciuto",
|
||||
"recognizing": "Riconoscimento in corso…",
|
||||
"recognitionError": "Errore di riconoscimento",
|
||||
"dishResultTitle": "Piatto riconosciuto",
|
||||
"selectDish": "Seleziona un piatto",
|
||||
"dishNotRecognized": "Piatto non riconosciuto",
|
||||
"tryAgain": "Riprova",
|
||||
"nutritionApproximate": "I valori nutrizionali sono approssimativi — stimati dalla foto.",
|
||||
"portion": "Porzione",
|
||||
"mealType": "Tipo di pasto",
|
||||
"dateLabel": "Data",
|
||||
"addToJournal": "Aggiungi al diario",
|
||||
"addFailed": "Aggiunta fallita. Riprova.",
|
||||
"historyTitle": "Cronologia riconoscimenti",
|
||||
"historyLoadError": "Impossibile caricare la cronologia",
|
||||
"retry": "Riprova",
|
||||
"noHistory": "Nessun riconoscimento ancora",
|
||||
"profileTitle": "Profilo",
|
||||
"edit": "Modifica",
|
||||
"bodyParams": "PARAMETRI CORPOREI",
|
||||
"goalActivity": "OBIETTIVO & ATTIVITÀ",
|
||||
"nutrition": "NUTRIZIONE",
|
||||
"settings": "IMPOSTAZIONI",
|
||||
"height": "Altezza",
|
||||
"weight": "Peso",
|
||||
"age": "Età",
|
||||
"gender": "Sesso",
|
||||
"genderMale": "Maschio",
|
||||
"genderFemale": "Femmina",
|
||||
"goalLoss": "Perdita di peso",
|
||||
"goalMaintain": "Mantenimento",
|
||||
"goalGain": "Aumento muscolare",
|
||||
"activityLow": "Bassa",
|
||||
"activityMedium": "Media",
|
||||
"activityHigh": "Alta",
|
||||
"calorieGoal": "Obiettivo calorico",
|
||||
"mealTypes": "Tipi di pasto",
|
||||
"formulaNote": "Calcolato con la formula di Mifflin-St Jeor",
|
||||
"language": "Lingua",
|
||||
"notSet": "Non impostato",
|
||||
"calorieHint": "Inserisci i parametri corporei per calcolare l'obiettivo calorico",
|
||||
"logout": "Disconnetti",
|
||||
"editProfile": "Modifica profilo",
|
||||
"cancel": "Annulla",
|
||||
"save": "Salva",
|
||||
"nameLabel": "Nome",
|
||||
"heightCm": "Altezza (cm)",
|
||||
"weightKg": "Peso (kg)",
|
||||
"birthDate": "Data di nascita",
|
||||
"nameRequired": "Inserisci il nome",
|
||||
"profileUpdated": "Profilo aggiornato",
|
||||
"profileSaveFailed": "Salvataggio fallito",
|
||||
"mealTypeBreakfast": "Colazione",
|
||||
"mealTypeSecondBreakfast": "Seconda colazione",
|
||||
"mealTypeLunch": "Pranzo",
|
||||
"mealTypeAfternoonSnack": "Merenda",
|
||||
"mealTypeDinner": "Cena",
|
||||
"mealTypeSnack": "Spuntino",
|
||||
"navHome": "Home",
|
||||
"navProducts": "Prodotti",
|
||||
"navRecipes": "Ricette"
|
||||
}
|
||||
100
client/lib/l10n/app_ja.arb
Normal file
100
client/lib/l10n/app_ja.arb
Normal file
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"@@locale": "ja",
|
||||
"appTitle": "FoodAI",
|
||||
"greetingMorning": "おはようございます",
|
||||
"greetingAfternoon": "こんにちは",
|
||||
"greetingEvening": "こんばんは",
|
||||
"caloriesUnit": "kcal",
|
||||
"gramsUnit": "g",
|
||||
"goalLabel": "目標:",
|
||||
"consumed": "摂取済み",
|
||||
"remaining": "残り",
|
||||
"exceeded": "超過",
|
||||
"proteinLabel": "タンパク質",
|
||||
"fatLabel": "脂質",
|
||||
"carbsLabel": "炭水化物",
|
||||
"today": "今日",
|
||||
"yesterday": "昨日",
|
||||
"mealsSection": "食事",
|
||||
"addDish": "料理を追加",
|
||||
"scanDish": "スキャン",
|
||||
"menu": "メニュー",
|
||||
"dishHistory": "料理履歴",
|
||||
"recommendCook": "おすすめレシピ",
|
||||
"camera": "カメラ",
|
||||
"gallery": "ギャラリー",
|
||||
"analyzingPhoto": "写真を分析中...",
|
||||
"inQueue": "順番待ち中",
|
||||
"queuePosition": "{position}番目",
|
||||
"@queuePosition": {
|
||||
"placeholders": {
|
||||
"position": { "type": "int" }
|
||||
}
|
||||
},
|
||||
"processing": "処理中...",
|
||||
"upgradePrompt": "順番をスキップ?アップグレード →",
|
||||
"recognitionFailed": "認識に失敗しました。もう一度お試しください。",
|
||||
"dishRecognition": "料理認識",
|
||||
"all": "すべて",
|
||||
"dishRecognized": "料理を認識しました",
|
||||
"recognizing": "認識中…",
|
||||
"recognitionError": "認識エラー",
|
||||
"dishResultTitle": "料理を認識しました",
|
||||
"selectDish": "料理を選択",
|
||||
"dishNotRecognized": "料理を認識できませんでした",
|
||||
"tryAgain": "もう一度試す",
|
||||
"nutritionApproximate": "栄養値は概算です — 写真から推定されました。",
|
||||
"portion": "量",
|
||||
"mealType": "食事タイプ",
|
||||
"dateLabel": "日付",
|
||||
"addToJournal": "日記に追加",
|
||||
"addFailed": "追加に失敗しました。もう一度お試しください。",
|
||||
"historyTitle": "認識履歴",
|
||||
"historyLoadError": "履歴の読み込みに失敗しました",
|
||||
"retry": "再試行",
|
||||
"noHistory": "認識履歴がありません",
|
||||
"profileTitle": "プロフィール",
|
||||
"edit": "編集",
|
||||
"bodyParams": "身体パラメータ",
|
||||
"goalActivity": "目標 & 活動",
|
||||
"nutrition": "栄養",
|
||||
"settings": "設定",
|
||||
"height": "身長",
|
||||
"weight": "体重",
|
||||
"age": "年齢",
|
||||
"gender": "性別",
|
||||
"genderMale": "男性",
|
||||
"genderFemale": "女性",
|
||||
"goalLoss": "体重減少",
|
||||
"goalMaintain": "維持",
|
||||
"goalGain": "筋肉増量",
|
||||
"activityLow": "低い",
|
||||
"activityMedium": "普通",
|
||||
"activityHigh": "高い",
|
||||
"calorieGoal": "カロリー目標",
|
||||
"mealTypes": "食事タイプ",
|
||||
"formulaNote": "ミフリン・セントジョー式で計算",
|
||||
"language": "言語",
|
||||
"notSet": "未設定",
|
||||
"calorieHint": "カロリー目標を計算するために身体パラメータを入力してください",
|
||||
"logout": "ログアウト",
|
||||
"editProfile": "プロフィールを編集",
|
||||
"cancel": "キャンセル",
|
||||
"save": "保存",
|
||||
"nameLabel": "名前",
|
||||
"heightCm": "身長(cm)",
|
||||
"weightKg": "体重(kg)",
|
||||
"birthDate": "生年月日",
|
||||
"nameRequired": "名前を入力してください",
|
||||
"profileUpdated": "プロフィールを更新しました",
|
||||
"profileSaveFailed": "保存に失敗しました",
|
||||
"mealTypeBreakfast": "朝食",
|
||||
"mealTypeSecondBreakfast": "第二朝食",
|
||||
"mealTypeLunch": "昼食",
|
||||
"mealTypeAfternoonSnack": "おやつ",
|
||||
"mealTypeDinner": "夕食",
|
||||
"mealTypeSnack": "間食",
|
||||
"navHome": "ホーム",
|
||||
"navProducts": "食品",
|
||||
"navRecipes": "レシピ"
|
||||
}
|
||||
100
client/lib/l10n/app_ko.arb
Normal file
100
client/lib/l10n/app_ko.arb
Normal file
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"@@locale": "ko",
|
||||
"appTitle": "FoodAI",
|
||||
"greetingMorning": "좋은 아침이에요",
|
||||
"greetingAfternoon": "안녕하세요",
|
||||
"greetingEvening": "좋은 저녁이에요",
|
||||
"caloriesUnit": "kcal",
|
||||
"gramsUnit": "g",
|
||||
"goalLabel": "목표:",
|
||||
"consumed": "섭취",
|
||||
"remaining": "남은",
|
||||
"exceeded": "초과",
|
||||
"proteinLabel": "단백질",
|
||||
"fatLabel": "지방",
|
||||
"carbsLabel": "탄수화물",
|
||||
"today": "오늘",
|
||||
"yesterday": "어제",
|
||||
"mealsSection": "식사",
|
||||
"addDish": "요리 추가",
|
||||
"scanDish": "스캔",
|
||||
"menu": "메뉴",
|
||||
"dishHistory": "요리 기록",
|
||||
"recommendCook": "요리 추천",
|
||||
"camera": "카메라",
|
||||
"gallery": "갤러리",
|
||||
"analyzingPhoto": "사진 분석 중...",
|
||||
"inQueue": "대기열에 있습니다",
|
||||
"queuePosition": "{position}번째",
|
||||
"@queuePosition": {
|
||||
"placeholders": {
|
||||
"position": { "type": "int" }
|
||||
}
|
||||
},
|
||||
"processing": "처리 중...",
|
||||
"upgradePrompt": "대기열 건너뛰기? 업그레이드 →",
|
||||
"recognitionFailed": "인식 실패. 다시 시도하세요.",
|
||||
"dishRecognition": "요리 인식",
|
||||
"all": "전체",
|
||||
"dishRecognized": "요리가 인식되었습니다",
|
||||
"recognizing": "인식 중…",
|
||||
"recognitionError": "인식 오류",
|
||||
"dishResultTitle": "요리가 인식되었습니다",
|
||||
"selectDish": "요리 선택",
|
||||
"dishNotRecognized": "요리를 인식할 수 없습니다",
|
||||
"tryAgain": "다시 시도",
|
||||
"nutritionApproximate": "영양 정보는 근사치입니다 — 사진을 기반으로 추정되었습니다.",
|
||||
"portion": "양",
|
||||
"mealType": "식사 유형",
|
||||
"dateLabel": "날짜",
|
||||
"addToJournal": "일지에 추가",
|
||||
"addFailed": "추가 실패. 다시 시도하세요.",
|
||||
"historyTitle": "인식 기록",
|
||||
"historyLoadError": "기록 로드 실패",
|
||||
"retry": "재시도",
|
||||
"noHistory": "인식 기록이 없습니다",
|
||||
"profileTitle": "프로필",
|
||||
"edit": "편집",
|
||||
"bodyParams": "신체 매개변수",
|
||||
"goalActivity": "목표 & 활동",
|
||||
"nutrition": "영양",
|
||||
"settings": "설정",
|
||||
"height": "키",
|
||||
"weight": "체중",
|
||||
"age": "나이",
|
||||
"gender": "성별",
|
||||
"genderMale": "남성",
|
||||
"genderFemale": "여성",
|
||||
"goalLoss": "체중 감량",
|
||||
"goalMaintain": "유지",
|
||||
"goalGain": "근육 증가",
|
||||
"activityLow": "낮음",
|
||||
"activityMedium": "보통",
|
||||
"activityHigh": "높음",
|
||||
"calorieGoal": "칼로리 목표",
|
||||
"mealTypes": "식사 유형",
|
||||
"formulaNote": "Mifflin-St Jeor 공식으로 계산",
|
||||
"language": "언어",
|
||||
"notSet": "설정 안 됨",
|
||||
"calorieHint": "칼로리 목표를 계산하려면 신체 매개변수를 입력하세요",
|
||||
"logout": "로그아웃",
|
||||
"editProfile": "프로필 편집",
|
||||
"cancel": "취소",
|
||||
"save": "저장",
|
||||
"nameLabel": "이름",
|
||||
"heightCm": "키 (cm)",
|
||||
"weightKg": "체중 (kg)",
|
||||
"birthDate": "생년월일",
|
||||
"nameRequired": "이름을 입력하세요",
|
||||
"profileUpdated": "프로필이 업데이트되었습니다",
|
||||
"profileSaveFailed": "저장 실패",
|
||||
"mealTypeBreakfast": "아침",
|
||||
"mealTypeSecondBreakfast": "두 번째 아침",
|
||||
"mealTypeLunch": "점심",
|
||||
"mealTypeAfternoonSnack": "간식",
|
||||
"mealTypeDinner": "저녁",
|
||||
"mealTypeSnack": "스낵",
|
||||
"navHome": "홈",
|
||||
"navProducts": "식품",
|
||||
"navRecipes": "레시피"
|
||||
}
|
||||
738
client/lib/l10n/app_localizations.dart
Normal file
738
client/lib/l10n/app_localizations.dart
Normal file
@@ -0,0 +1,738 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
|
||||
import 'app_localizations_ar.dart';
|
||||
import 'app_localizations_de.dart';
|
||||
import 'app_localizations_en.dart';
|
||||
import 'app_localizations_es.dart';
|
||||
import 'app_localizations_fr.dart';
|
||||
import 'app_localizations_hi.dart';
|
||||
import 'app_localizations_it.dart';
|
||||
import 'app_localizations_ja.dart';
|
||||
import 'app_localizations_ko.dart';
|
||||
import 'app_localizations_pt.dart';
|
||||
import 'app_localizations_ru.dart';
|
||||
import 'app_localizations_zh.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// Callers can lookup localized strings with an instance of AppLocalizations
|
||||
/// returned by `AppLocalizations.of(context)`.
|
||||
///
|
||||
/// Applications need to include `AppLocalizations.delegate()` in their app's
|
||||
/// `localizationDelegates` list, and the locales they support in the app's
|
||||
/// `supportedLocales` list. For example:
|
||||
///
|
||||
/// ```dart
|
||||
/// import 'l10n/app_localizations.dart';
|
||||
///
|
||||
/// return MaterialApp(
|
||||
/// localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
/// supportedLocales: AppLocalizations.supportedLocales,
|
||||
/// home: MyApplicationHome(),
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// ## Update pubspec.yaml
|
||||
///
|
||||
/// Please make sure to update your pubspec.yaml to include the following
|
||||
/// packages:
|
||||
///
|
||||
/// ```yaml
|
||||
/// dependencies:
|
||||
/// # Internationalization support.
|
||||
/// flutter_localizations:
|
||||
/// sdk: flutter
|
||||
/// intl: any # Use the pinned version from flutter_localizations
|
||||
///
|
||||
/// # Rest of dependencies
|
||||
/// ```
|
||||
///
|
||||
/// ## iOS Applications
|
||||
///
|
||||
/// iOS applications define key application metadata, including supported
|
||||
/// locales, in an Info.plist file that is built into the application bundle.
|
||||
/// To configure the locales supported by your app, you’ll need to edit this
|
||||
/// file.
|
||||
///
|
||||
/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file.
|
||||
/// Then, in the Project Navigator, open the Info.plist file under the Runner
|
||||
/// project’s Runner folder.
|
||||
///
|
||||
/// Next, select the Information Property List item, select Add Item from the
|
||||
/// Editor menu, then select Localizations from the pop-up menu.
|
||||
///
|
||||
/// Select and expand the newly-created Localizations item then, for each
|
||||
/// locale your application supports, add a new item and select the locale
|
||||
/// you wish to add from the pop-up menu in the Value field. This list should
|
||||
/// be consistent with the languages listed in the AppLocalizations.supportedLocales
|
||||
/// property.
|
||||
abstract class AppLocalizations {
|
||||
AppLocalizations(String locale)
|
||||
: localeName = intl.Intl.canonicalizedLocale(locale.toString());
|
||||
|
||||
final String localeName;
|
||||
|
||||
static AppLocalizations? of(BuildContext context) {
|
||||
return Localizations.of<AppLocalizations>(context, AppLocalizations);
|
||||
}
|
||||
|
||||
static const LocalizationsDelegate<AppLocalizations> delegate =
|
||||
_AppLocalizationsDelegate();
|
||||
|
||||
/// A list of this localizations delegate along with the default localizations
|
||||
/// delegates.
|
||||
///
|
||||
/// Returns a list of localizations delegates containing this delegate along with
|
||||
/// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
|
||||
/// and GlobalWidgetsLocalizations.delegate.
|
||||
///
|
||||
/// Additional delegates can be added by appending to this list in
|
||||
/// MaterialApp. This list does not have to be used at all if a custom list
|
||||
/// of delegates is preferred or required.
|
||||
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
|
||||
<LocalizationsDelegate<dynamic>>[
|
||||
delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
];
|
||||
|
||||
/// A list of this localizations delegate's supported locales.
|
||||
static const List<Locale> supportedLocales = <Locale>[
|
||||
Locale('ar'),
|
||||
Locale('de'),
|
||||
Locale('en'),
|
||||
Locale('es'),
|
||||
Locale('fr'),
|
||||
Locale('hi'),
|
||||
Locale('it'),
|
||||
Locale('ja'),
|
||||
Locale('ko'),
|
||||
Locale('pt'),
|
||||
Locale('ru'),
|
||||
Locale('zh'),
|
||||
];
|
||||
|
||||
/// No description provided for @appTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'FoodAI'**
|
||||
String get appTitle;
|
||||
|
||||
/// No description provided for @greetingMorning.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Good morning'**
|
||||
String get greetingMorning;
|
||||
|
||||
/// No description provided for @greetingAfternoon.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Good afternoon'**
|
||||
String get greetingAfternoon;
|
||||
|
||||
/// No description provided for @greetingEvening.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Good evening'**
|
||||
String get greetingEvening;
|
||||
|
||||
/// No description provided for @caloriesUnit.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'kcal'**
|
||||
String get caloriesUnit;
|
||||
|
||||
/// No description provided for @gramsUnit.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'g'**
|
||||
String get gramsUnit;
|
||||
|
||||
/// No description provided for @goalLabel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'goal:'**
|
||||
String get goalLabel;
|
||||
|
||||
/// No description provided for @consumed.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Consumed'**
|
||||
String get consumed;
|
||||
|
||||
/// No description provided for @remaining.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Remaining'**
|
||||
String get remaining;
|
||||
|
||||
/// No description provided for @exceeded.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Exceeded'**
|
||||
String get exceeded;
|
||||
|
||||
/// No description provided for @proteinLabel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Protein'**
|
||||
String get proteinLabel;
|
||||
|
||||
/// No description provided for @fatLabel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Fat'**
|
||||
String get fatLabel;
|
||||
|
||||
/// No description provided for @carbsLabel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Carbs'**
|
||||
String get carbsLabel;
|
||||
|
||||
/// No description provided for @today.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Today'**
|
||||
String get today;
|
||||
|
||||
/// No description provided for @yesterday.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Yesterday'**
|
||||
String get yesterday;
|
||||
|
||||
/// No description provided for @mealsSection.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Meals'**
|
||||
String get mealsSection;
|
||||
|
||||
/// No description provided for @addDish.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Add dish'**
|
||||
String get addDish;
|
||||
|
||||
/// No description provided for @scanDish.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Scan'**
|
||||
String get scanDish;
|
||||
|
||||
/// No description provided for @menu.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Menu'**
|
||||
String get menu;
|
||||
|
||||
/// No description provided for @dishHistory.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Dish history'**
|
||||
String get dishHistory;
|
||||
|
||||
/// No description provided for @recommendCook.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'We recommend cooking'**
|
||||
String get recommendCook;
|
||||
|
||||
/// No description provided for @camera.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Camera'**
|
||||
String get camera;
|
||||
|
||||
/// No description provided for @gallery.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Gallery'**
|
||||
String get gallery;
|
||||
|
||||
/// No description provided for @analyzingPhoto.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Analyzing photo...'**
|
||||
String get analyzingPhoto;
|
||||
|
||||
/// No description provided for @inQueue.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'You are in queue'**
|
||||
String get inQueue;
|
||||
|
||||
/// No description provided for @queuePosition.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Position {position}'**
|
||||
String queuePosition(int position);
|
||||
|
||||
/// No description provided for @processing.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Processing...'**
|
||||
String get processing;
|
||||
|
||||
/// No description provided for @upgradePrompt.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Skip the queue? Upgrade →'**
|
||||
String get upgradePrompt;
|
||||
|
||||
/// No description provided for @recognitionFailed.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Recognition failed. Try again.'**
|
||||
String get recognitionFailed;
|
||||
|
||||
/// No description provided for @dishRecognition.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Dish recognition'**
|
||||
String get dishRecognition;
|
||||
|
||||
/// No description provided for @all.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'All'**
|
||||
String get all;
|
||||
|
||||
/// No description provided for @dishRecognized.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Dish recognized'**
|
||||
String get dishRecognized;
|
||||
|
||||
/// No description provided for @recognizing.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Recognizing…'**
|
||||
String get recognizing;
|
||||
|
||||
/// No description provided for @recognitionError.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Recognition error'**
|
||||
String get recognitionError;
|
||||
|
||||
/// No description provided for @dishResultTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Dish recognized'**
|
||||
String get dishResultTitle;
|
||||
|
||||
/// No description provided for @selectDish.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Select dish'**
|
||||
String get selectDish;
|
||||
|
||||
/// No description provided for @dishNotRecognized.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Dish not recognized'**
|
||||
String get dishNotRecognized;
|
||||
|
||||
/// No description provided for @tryAgain.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Try again'**
|
||||
String get tryAgain;
|
||||
|
||||
/// No description provided for @nutritionApproximate.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Nutrition is approximate — estimated from photo.'**
|
||||
String get nutritionApproximate;
|
||||
|
||||
/// No description provided for @portion.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Portion'**
|
||||
String get portion;
|
||||
|
||||
/// No description provided for @mealType.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Meal type'**
|
||||
String get mealType;
|
||||
|
||||
/// No description provided for @dateLabel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Date'**
|
||||
String get dateLabel;
|
||||
|
||||
/// No description provided for @addToJournal.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Add to journal'**
|
||||
String get addToJournal;
|
||||
|
||||
/// No description provided for @addFailed.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Failed to add. Try again.'**
|
||||
String get addFailed;
|
||||
|
||||
/// No description provided for @historyTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Recognition history'**
|
||||
String get historyTitle;
|
||||
|
||||
/// No description provided for @historyLoadError.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Failed to load history'**
|
||||
String get historyLoadError;
|
||||
|
||||
/// No description provided for @retry.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Retry'**
|
||||
String get retry;
|
||||
|
||||
/// No description provided for @noHistory.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No recognitions yet'**
|
||||
String get noHistory;
|
||||
|
||||
/// No description provided for @profileTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Profile'**
|
||||
String get profileTitle;
|
||||
|
||||
/// No description provided for @edit.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Edit'**
|
||||
String get edit;
|
||||
|
||||
/// No description provided for @bodyParams.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'BODY PARAMS'**
|
||||
String get bodyParams;
|
||||
|
||||
/// No description provided for @goalActivity.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'GOAL & ACTIVITY'**
|
||||
String get goalActivity;
|
||||
|
||||
/// No description provided for @nutrition.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'NUTRITION'**
|
||||
String get nutrition;
|
||||
|
||||
/// No description provided for @settings.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'SETTINGS'**
|
||||
String get settings;
|
||||
|
||||
/// No description provided for @height.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Height'**
|
||||
String get height;
|
||||
|
||||
/// No description provided for @weight.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Weight'**
|
||||
String get weight;
|
||||
|
||||
/// No description provided for @age.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Age'**
|
||||
String get age;
|
||||
|
||||
/// No description provided for @gender.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Gender'**
|
||||
String get gender;
|
||||
|
||||
/// No description provided for @genderMale.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Male'**
|
||||
String get genderMale;
|
||||
|
||||
/// No description provided for @genderFemale.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Female'**
|
||||
String get genderFemale;
|
||||
|
||||
/// No description provided for @goalLoss.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Weight loss'**
|
||||
String get goalLoss;
|
||||
|
||||
/// No description provided for @goalMaintain.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Maintenance'**
|
||||
String get goalMaintain;
|
||||
|
||||
/// No description provided for @goalGain.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Muscle gain'**
|
||||
String get goalGain;
|
||||
|
||||
/// No description provided for @activityLow.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Low'**
|
||||
String get activityLow;
|
||||
|
||||
/// No description provided for @activityMedium.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Medium'**
|
||||
String get activityMedium;
|
||||
|
||||
/// No description provided for @activityHigh.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'High'**
|
||||
String get activityHigh;
|
||||
|
||||
/// No description provided for @calorieGoal.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Calorie goal'**
|
||||
String get calorieGoal;
|
||||
|
||||
/// No description provided for @mealTypes.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Meal types'**
|
||||
String get mealTypes;
|
||||
|
||||
/// No description provided for @formulaNote.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Calculated using the Mifflin-St Jeor formula'**
|
||||
String get formulaNote;
|
||||
|
||||
/// No description provided for @language.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Language'**
|
||||
String get language;
|
||||
|
||||
/// No description provided for @notSet.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Not set'**
|
||||
String get notSet;
|
||||
|
||||
/// No description provided for @calorieHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enter body params to calculate calorie goal'**
|
||||
String get calorieHint;
|
||||
|
||||
/// No description provided for @logout.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Log out'**
|
||||
String get logout;
|
||||
|
||||
/// No description provided for @editProfile.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Edit profile'**
|
||||
String get editProfile;
|
||||
|
||||
/// No description provided for @cancel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Cancel'**
|
||||
String get cancel;
|
||||
|
||||
/// No description provided for @save.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Save'**
|
||||
String get save;
|
||||
|
||||
/// No description provided for @nameLabel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Name'**
|
||||
String get nameLabel;
|
||||
|
||||
/// No description provided for @heightCm.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Height (cm)'**
|
||||
String get heightCm;
|
||||
|
||||
/// No description provided for @weightKg.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Weight (kg)'**
|
||||
String get weightKg;
|
||||
|
||||
/// No description provided for @birthDate.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Date of birth'**
|
||||
String get birthDate;
|
||||
|
||||
/// No description provided for @nameRequired.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enter name'**
|
||||
String get nameRequired;
|
||||
|
||||
/// No description provided for @profileUpdated.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Profile updated'**
|
||||
String get profileUpdated;
|
||||
|
||||
/// No description provided for @profileSaveFailed.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Failed to save'**
|
||||
String get profileSaveFailed;
|
||||
|
||||
/// No description provided for @mealTypeBreakfast.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Breakfast'**
|
||||
String get mealTypeBreakfast;
|
||||
|
||||
/// No description provided for @mealTypeSecondBreakfast.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Second breakfast'**
|
||||
String get mealTypeSecondBreakfast;
|
||||
|
||||
/// No description provided for @mealTypeLunch.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Lunch'**
|
||||
String get mealTypeLunch;
|
||||
|
||||
/// No description provided for @mealTypeAfternoonSnack.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Afternoon snack'**
|
||||
String get mealTypeAfternoonSnack;
|
||||
|
||||
/// No description provided for @mealTypeDinner.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Dinner'**
|
||||
String get mealTypeDinner;
|
||||
|
||||
/// No description provided for @mealTypeSnack.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Snack'**
|
||||
String get mealTypeSnack;
|
||||
|
||||
/// No description provided for @navHome.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Home'**
|
||||
String get navHome;
|
||||
|
||||
/// No description provided for @navProducts.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Products'**
|
||||
String get navProducts;
|
||||
|
||||
/// No description provided for @navRecipes.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Recipes'**
|
||||
String get navRecipes;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
extends LocalizationsDelegate<AppLocalizations> {
|
||||
const _AppLocalizationsDelegate();
|
||||
|
||||
@override
|
||||
Future<AppLocalizations> load(Locale locale) {
|
||||
return SynchronousFuture<AppLocalizations>(lookupAppLocalizations(locale));
|
||||
}
|
||||
|
||||
@override
|
||||
bool isSupported(Locale locale) => <String>[
|
||||
'ar',
|
||||
'de',
|
||||
'en',
|
||||
'es',
|
||||
'fr',
|
||||
'hi',
|
||||
'it',
|
||||
'ja',
|
||||
'ko',
|
||||
'pt',
|
||||
'ru',
|
||||
'zh',
|
||||
].contains(locale.languageCode);
|
||||
|
||||
@override
|
||||
bool shouldReload(_AppLocalizationsDelegate old) => false;
|
||||
}
|
||||
|
||||
AppLocalizations lookupAppLocalizations(Locale locale) {
|
||||
// Lookup logic when only language code is specified.
|
||||
switch (locale.languageCode) {
|
||||
case 'ar':
|
||||
return AppLocalizationsAr();
|
||||
case 'de':
|
||||
return AppLocalizationsDe();
|
||||
case 'en':
|
||||
return AppLocalizationsEn();
|
||||
case 'es':
|
||||
return AppLocalizationsEs();
|
||||
case 'fr':
|
||||
return AppLocalizationsFr();
|
||||
case 'hi':
|
||||
return AppLocalizationsHi();
|
||||
case 'it':
|
||||
return AppLocalizationsIt();
|
||||
case 'ja':
|
||||
return AppLocalizationsJa();
|
||||
case 'ko':
|
||||
return AppLocalizationsKo();
|
||||
case 'pt':
|
||||
return AppLocalizationsPt();
|
||||
case 'ru':
|
||||
return AppLocalizationsRu();
|
||||
case 'zh':
|
||||
return AppLocalizationsZh();
|
||||
}
|
||||
|
||||
throw FlutterError(
|
||||
'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
|
||||
'an issue with the localizations generation tool. Please file an issue '
|
||||
'on GitHub with a reproducible sample app and the gen-l10n configuration '
|
||||
'that was used.',
|
||||
);
|
||||
}
|
||||
289
client/lib/l10n/app_localizations_ar.dart
Normal file
289
client/lib/l10n/app_localizations_ar.dart
Normal file
@@ -0,0 +1,289 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// The translations for Arabic (`ar`).
|
||||
class AppLocalizationsAr extends AppLocalizations {
|
||||
AppLocalizationsAr([String locale = 'ar']) : 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 => 'تخطي قائمة الانتظار؟ ترقية →';
|
||||
|
||||
@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 => 'الوصفات';
|
||||
}
|
||||
290
client/lib/l10n/app_localizations_de.dart
Normal file
290
client/lib/l10n/app_localizations_de.dart
Normal file
@@ -0,0 +1,290 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// The translations for German (`de`).
|
||||
class AppLocalizationsDe extends AppLocalizations {
|
||||
AppLocalizationsDe([String locale = 'de']) : super(locale);
|
||||
|
||||
@override
|
||||
String get appTitle => 'FoodAI';
|
||||
|
||||
@override
|
||||
String get greetingMorning => 'Guten Morgen';
|
||||
|
||||
@override
|
||||
String get greetingAfternoon => 'Guten Tag';
|
||||
|
||||
@override
|
||||
String get greetingEvening => 'Guten Abend';
|
||||
|
||||
@override
|
||||
String get caloriesUnit => 'kcal';
|
||||
|
||||
@override
|
||||
String get gramsUnit => 'g';
|
||||
|
||||
@override
|
||||
String get goalLabel => 'Ziel:';
|
||||
|
||||
@override
|
||||
String get consumed => 'Verzehrt';
|
||||
|
||||
@override
|
||||
String get remaining => 'Verbleibend';
|
||||
|
||||
@override
|
||||
String get exceeded => 'Überschritten';
|
||||
|
||||
@override
|
||||
String get proteinLabel => 'Protein';
|
||||
|
||||
@override
|
||||
String get fatLabel => 'Fett';
|
||||
|
||||
@override
|
||||
String get carbsLabel => 'Kohlenhydrate';
|
||||
|
||||
@override
|
||||
String get today => 'Heute';
|
||||
|
||||
@override
|
||||
String get yesterday => 'Gestern';
|
||||
|
||||
@override
|
||||
String get mealsSection => 'Mahlzeiten';
|
||||
|
||||
@override
|
||||
String get addDish => 'Gericht hinzufügen';
|
||||
|
||||
@override
|
||||
String get scanDish => 'Scannen';
|
||||
|
||||
@override
|
||||
String get menu => 'Menü';
|
||||
|
||||
@override
|
||||
String get dishHistory => 'Gerichtverlauf';
|
||||
|
||||
@override
|
||||
String get recommendCook => 'Wir empfehlen zu kochen';
|
||||
|
||||
@override
|
||||
String get camera => 'Kamera';
|
||||
|
||||
@override
|
||||
String get gallery => 'Galerie';
|
||||
|
||||
@override
|
||||
String get analyzingPhoto => 'Foto wird analysiert...';
|
||||
|
||||
@override
|
||||
String get inQueue => 'Sie sind in der Warteschlange';
|
||||
|
||||
@override
|
||||
String queuePosition(int position) {
|
||||
return 'Position $position';
|
||||
}
|
||||
|
||||
@override
|
||||
String get processing => 'Verarbeitung...';
|
||||
|
||||
@override
|
||||
String get upgradePrompt => 'Warteschlange überspringen? Upgrade →';
|
||||
|
||||
@override
|
||||
String get recognitionFailed => 'Erkennung fehlgeschlagen. Erneut versuchen.';
|
||||
|
||||
@override
|
||||
String get dishRecognition => 'Gerichterkennung';
|
||||
|
||||
@override
|
||||
String get all => 'Alle';
|
||||
|
||||
@override
|
||||
String get dishRecognized => 'Gericht erkannt';
|
||||
|
||||
@override
|
||||
String get recognizing => 'Wird erkannt…';
|
||||
|
||||
@override
|
||||
String get recognitionError => 'Erkennungsfehler';
|
||||
|
||||
@override
|
||||
String get dishResultTitle => 'Gericht erkannt';
|
||||
|
||||
@override
|
||||
String get selectDish => 'Gericht auswählen';
|
||||
|
||||
@override
|
||||
String get dishNotRecognized => 'Gericht nicht erkannt';
|
||||
|
||||
@override
|
||||
String get tryAgain => 'Erneut versuchen';
|
||||
|
||||
@override
|
||||
String get nutritionApproximate =>
|
||||
'Nährwerte sind ungefähr — aus dem Foto geschätzt.';
|
||||
|
||||
@override
|
||||
String get portion => 'Portion';
|
||||
|
||||
@override
|
||||
String get mealType => 'Mahlzeittyp';
|
||||
|
||||
@override
|
||||
String get dateLabel => 'Datum';
|
||||
|
||||
@override
|
||||
String get addToJournal => 'Zum Tagebuch hinzufügen';
|
||||
|
||||
@override
|
||||
String get addFailed => 'Hinzufügen fehlgeschlagen. Erneut versuchen.';
|
||||
|
||||
@override
|
||||
String get historyTitle => 'Erkennungsverlauf';
|
||||
|
||||
@override
|
||||
String get historyLoadError => 'Verlauf konnte nicht geladen werden';
|
||||
|
||||
@override
|
||||
String get retry => 'Wiederholen';
|
||||
|
||||
@override
|
||||
String get noHistory => 'Noch keine Erkennungen';
|
||||
|
||||
@override
|
||||
String get profileTitle => 'Profil';
|
||||
|
||||
@override
|
||||
String get edit => 'Bearbeiten';
|
||||
|
||||
@override
|
||||
String get bodyParams => 'KÖRPERPARAMETER';
|
||||
|
||||
@override
|
||||
String get goalActivity => 'ZIEL & AKTIVITÄT';
|
||||
|
||||
@override
|
||||
String get nutrition => 'ERNÄHRUNG';
|
||||
|
||||
@override
|
||||
String get settings => 'EINSTELLUNGEN';
|
||||
|
||||
@override
|
||||
String get height => 'Größe';
|
||||
|
||||
@override
|
||||
String get weight => 'Gewicht';
|
||||
|
||||
@override
|
||||
String get age => 'Alter';
|
||||
|
||||
@override
|
||||
String get gender => 'Geschlecht';
|
||||
|
||||
@override
|
||||
String get genderMale => 'Männlich';
|
||||
|
||||
@override
|
||||
String get genderFemale => 'Weiblich';
|
||||
|
||||
@override
|
||||
String get goalLoss => 'Gewichtsverlust';
|
||||
|
||||
@override
|
||||
String get goalMaintain => 'Gewicht halten';
|
||||
|
||||
@override
|
||||
String get goalGain => 'Muskelaufbau';
|
||||
|
||||
@override
|
||||
String get activityLow => 'Niedrig';
|
||||
|
||||
@override
|
||||
String get activityMedium => 'Mittel';
|
||||
|
||||
@override
|
||||
String get activityHigh => 'Hoch';
|
||||
|
||||
@override
|
||||
String get calorieGoal => 'Kalorienziel';
|
||||
|
||||
@override
|
||||
String get mealTypes => 'Mahlzeittypen';
|
||||
|
||||
@override
|
||||
String get formulaNote => 'Berechnet mit der Mifflin-St Jeor Formel';
|
||||
|
||||
@override
|
||||
String get language => 'Sprache';
|
||||
|
||||
@override
|
||||
String get notSet => 'Nicht festgelegt';
|
||||
|
||||
@override
|
||||
String get calorieHint =>
|
||||
'Körperparameter eingeben, um das Kalorienziel zu berechnen';
|
||||
|
||||
@override
|
||||
String get logout => 'Abmelden';
|
||||
|
||||
@override
|
||||
String get editProfile => 'Profil bearbeiten';
|
||||
|
||||
@override
|
||||
String get cancel => 'Abbrechen';
|
||||
|
||||
@override
|
||||
String get save => 'Speichern';
|
||||
|
||||
@override
|
||||
String get nameLabel => 'Name';
|
||||
|
||||
@override
|
||||
String get heightCm => 'Größe (cm)';
|
||||
|
||||
@override
|
||||
String get weightKg => 'Gewicht (kg)';
|
||||
|
||||
@override
|
||||
String get birthDate => 'Geburtsdatum';
|
||||
|
||||
@override
|
||||
String get nameRequired => 'Name eingeben';
|
||||
|
||||
@override
|
||||
String get profileUpdated => 'Profil aktualisiert';
|
||||
|
||||
@override
|
||||
String get profileSaveFailed => 'Speichern fehlgeschlagen';
|
||||
|
||||
@override
|
||||
String get mealTypeBreakfast => 'Frühstück';
|
||||
|
||||
@override
|
||||
String get mealTypeSecondBreakfast => 'Zweites Frühstück';
|
||||
|
||||
@override
|
||||
String get mealTypeLunch => 'Mittagessen';
|
||||
|
||||
@override
|
||||
String get mealTypeAfternoonSnack => 'Nachmittagssnack';
|
||||
|
||||
@override
|
||||
String get mealTypeDinner => 'Abendessen';
|
||||
|
||||
@override
|
||||
String get mealTypeSnack => 'Snack';
|
||||
|
||||
@override
|
||||
String get navHome => 'Startseite';
|
||||
|
||||
@override
|
||||
String get navProducts => 'Produkte';
|
||||
|
||||
@override
|
||||
String get navRecipes => 'Rezepte';
|
||||
}
|
||||
289
client/lib/l10n/app_localizations_en.dart
Normal file
289
client/lib/l10n/app_localizations_en.dart
Normal file
@@ -0,0 +1,289 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// The translations for English (`en`).
|
||||
class AppLocalizationsEn extends AppLocalizations {
|
||||
AppLocalizationsEn([String locale = 'en']) : super(locale);
|
||||
|
||||
@override
|
||||
String get appTitle => 'FoodAI';
|
||||
|
||||
@override
|
||||
String get greetingMorning => 'Good morning';
|
||||
|
||||
@override
|
||||
String get greetingAfternoon => 'Good afternoon';
|
||||
|
||||
@override
|
||||
String get greetingEvening => 'Good evening';
|
||||
|
||||
@override
|
||||
String get caloriesUnit => 'kcal';
|
||||
|
||||
@override
|
||||
String get gramsUnit => 'g';
|
||||
|
||||
@override
|
||||
String get goalLabel => 'goal:';
|
||||
|
||||
@override
|
||||
String get consumed => 'Consumed';
|
||||
|
||||
@override
|
||||
String get remaining => 'Remaining';
|
||||
|
||||
@override
|
||||
String get exceeded => 'Exceeded';
|
||||
|
||||
@override
|
||||
String get proteinLabel => 'Protein';
|
||||
|
||||
@override
|
||||
String get fatLabel => 'Fat';
|
||||
|
||||
@override
|
||||
String get carbsLabel => 'Carbs';
|
||||
|
||||
@override
|
||||
String get today => 'Today';
|
||||
|
||||
@override
|
||||
String get yesterday => 'Yesterday';
|
||||
|
||||
@override
|
||||
String get mealsSection => 'Meals';
|
||||
|
||||
@override
|
||||
String get addDish => 'Add dish';
|
||||
|
||||
@override
|
||||
String get scanDish => 'Scan';
|
||||
|
||||
@override
|
||||
String get menu => 'Menu';
|
||||
|
||||
@override
|
||||
String get dishHistory => 'Dish history';
|
||||
|
||||
@override
|
||||
String get recommendCook => 'We recommend cooking';
|
||||
|
||||
@override
|
||||
String get camera => 'Camera';
|
||||
|
||||
@override
|
||||
String get gallery => 'Gallery';
|
||||
|
||||
@override
|
||||
String get analyzingPhoto => 'Analyzing photo...';
|
||||
|
||||
@override
|
||||
String get inQueue => 'You are in queue';
|
||||
|
||||
@override
|
||||
String queuePosition(int position) {
|
||||
return 'Position $position';
|
||||
}
|
||||
|
||||
@override
|
||||
String get processing => 'Processing...';
|
||||
|
||||
@override
|
||||
String get upgradePrompt => 'Skip the queue? Upgrade →';
|
||||
|
||||
@override
|
||||
String get recognitionFailed => 'Recognition failed. Try again.';
|
||||
|
||||
@override
|
||||
String get dishRecognition => 'Dish recognition';
|
||||
|
||||
@override
|
||||
String get all => 'All';
|
||||
|
||||
@override
|
||||
String get dishRecognized => 'Dish recognized';
|
||||
|
||||
@override
|
||||
String get recognizing => 'Recognizing…';
|
||||
|
||||
@override
|
||||
String get recognitionError => 'Recognition error';
|
||||
|
||||
@override
|
||||
String get dishResultTitle => 'Dish recognized';
|
||||
|
||||
@override
|
||||
String get selectDish => 'Select dish';
|
||||
|
||||
@override
|
||||
String get dishNotRecognized => 'Dish not recognized';
|
||||
|
||||
@override
|
||||
String get tryAgain => 'Try again';
|
||||
|
||||
@override
|
||||
String get nutritionApproximate =>
|
||||
'Nutrition is approximate — estimated from photo.';
|
||||
|
||||
@override
|
||||
String get portion => 'Portion';
|
||||
|
||||
@override
|
||||
String get mealType => 'Meal type';
|
||||
|
||||
@override
|
||||
String get dateLabel => 'Date';
|
||||
|
||||
@override
|
||||
String get addToJournal => 'Add to journal';
|
||||
|
||||
@override
|
||||
String get addFailed => 'Failed to add. Try again.';
|
||||
|
||||
@override
|
||||
String get historyTitle => 'Recognition history';
|
||||
|
||||
@override
|
||||
String get historyLoadError => 'Failed to load history';
|
||||
|
||||
@override
|
||||
String get retry => 'Retry';
|
||||
|
||||
@override
|
||||
String get noHistory => 'No recognitions yet';
|
||||
|
||||
@override
|
||||
String get profileTitle => 'Profile';
|
||||
|
||||
@override
|
||||
String get edit => 'Edit';
|
||||
|
||||
@override
|
||||
String get bodyParams => 'BODY PARAMS';
|
||||
|
||||
@override
|
||||
String get goalActivity => 'GOAL & ACTIVITY';
|
||||
|
||||
@override
|
||||
String get nutrition => 'NUTRITION';
|
||||
|
||||
@override
|
||||
String get settings => 'SETTINGS';
|
||||
|
||||
@override
|
||||
String get height => 'Height';
|
||||
|
||||
@override
|
||||
String get weight => 'Weight';
|
||||
|
||||
@override
|
||||
String get age => 'Age';
|
||||
|
||||
@override
|
||||
String get gender => 'Gender';
|
||||
|
||||
@override
|
||||
String get genderMale => 'Male';
|
||||
|
||||
@override
|
||||
String get genderFemale => 'Female';
|
||||
|
||||
@override
|
||||
String get goalLoss => 'Weight loss';
|
||||
|
||||
@override
|
||||
String get goalMaintain => 'Maintenance';
|
||||
|
||||
@override
|
||||
String get goalGain => 'Muscle gain';
|
||||
|
||||
@override
|
||||
String get activityLow => 'Low';
|
||||
|
||||
@override
|
||||
String get activityMedium => 'Medium';
|
||||
|
||||
@override
|
||||
String get activityHigh => 'High';
|
||||
|
||||
@override
|
||||
String get calorieGoal => 'Calorie goal';
|
||||
|
||||
@override
|
||||
String get mealTypes => 'Meal types';
|
||||
|
||||
@override
|
||||
String get formulaNote => 'Calculated using the Mifflin-St Jeor formula';
|
||||
|
||||
@override
|
||||
String get language => 'Language';
|
||||
|
||||
@override
|
||||
String get notSet => 'Not set';
|
||||
|
||||
@override
|
||||
String get calorieHint => 'Enter body params to calculate calorie goal';
|
||||
|
||||
@override
|
||||
String get logout => 'Log out';
|
||||
|
||||
@override
|
||||
String get editProfile => 'Edit profile';
|
||||
|
||||
@override
|
||||
String get cancel => 'Cancel';
|
||||
|
||||
@override
|
||||
String get save => 'Save';
|
||||
|
||||
@override
|
||||
String get nameLabel => 'Name';
|
||||
|
||||
@override
|
||||
String get heightCm => 'Height (cm)';
|
||||
|
||||
@override
|
||||
String get weightKg => 'Weight (kg)';
|
||||
|
||||
@override
|
||||
String get birthDate => 'Date of birth';
|
||||
|
||||
@override
|
||||
String get nameRequired => 'Enter name';
|
||||
|
||||
@override
|
||||
String get profileUpdated => 'Profile updated';
|
||||
|
||||
@override
|
||||
String get profileSaveFailed => 'Failed to save';
|
||||
|
||||
@override
|
||||
String get mealTypeBreakfast => 'Breakfast';
|
||||
|
||||
@override
|
||||
String get mealTypeSecondBreakfast => 'Second breakfast';
|
||||
|
||||
@override
|
||||
String get mealTypeLunch => 'Lunch';
|
||||
|
||||
@override
|
||||
String get mealTypeAfternoonSnack => 'Afternoon snack';
|
||||
|
||||
@override
|
||||
String get mealTypeDinner => 'Dinner';
|
||||
|
||||
@override
|
||||
String get mealTypeSnack => 'Snack';
|
||||
|
||||
@override
|
||||
String get navHome => 'Home';
|
||||
|
||||
@override
|
||||
String get navProducts => 'Products';
|
||||
|
||||
@override
|
||||
String get navRecipes => 'Recipes';
|
||||
}
|
||||
290
client/lib/l10n/app_localizations_es.dart
Normal file
290
client/lib/l10n/app_localizations_es.dart
Normal file
@@ -0,0 +1,290 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// The translations for Spanish Castilian (`es`).
|
||||
class AppLocalizationsEs extends AppLocalizations {
|
||||
AppLocalizationsEs([String locale = 'es']) : super(locale);
|
||||
|
||||
@override
|
||||
String get appTitle => 'FoodAI';
|
||||
|
||||
@override
|
||||
String get greetingMorning => 'Buenos días';
|
||||
|
||||
@override
|
||||
String get greetingAfternoon => 'Buenas tardes';
|
||||
|
||||
@override
|
||||
String get greetingEvening => 'Buenas noches';
|
||||
|
||||
@override
|
||||
String get caloriesUnit => 'kcal';
|
||||
|
||||
@override
|
||||
String get gramsUnit => 'g';
|
||||
|
||||
@override
|
||||
String get goalLabel => 'meta:';
|
||||
|
||||
@override
|
||||
String get consumed => 'Consumido';
|
||||
|
||||
@override
|
||||
String get remaining => 'Restante';
|
||||
|
||||
@override
|
||||
String get exceeded => 'Excedido';
|
||||
|
||||
@override
|
||||
String get proteinLabel => 'Proteínas';
|
||||
|
||||
@override
|
||||
String get fatLabel => 'Grasas';
|
||||
|
||||
@override
|
||||
String get carbsLabel => 'Carbohidratos';
|
||||
|
||||
@override
|
||||
String get today => 'Hoy';
|
||||
|
||||
@override
|
||||
String get yesterday => 'Ayer';
|
||||
|
||||
@override
|
||||
String get mealsSection => 'Comidas';
|
||||
|
||||
@override
|
||||
String get addDish => 'Añadir plato';
|
||||
|
||||
@override
|
||||
String get scanDish => 'Escanear';
|
||||
|
||||
@override
|
||||
String get menu => 'Menú';
|
||||
|
||||
@override
|
||||
String get dishHistory => 'Historial de platos';
|
||||
|
||||
@override
|
||||
String get recommendCook => 'Recomendamos cocinar';
|
||||
|
||||
@override
|
||||
String get camera => 'Cámara';
|
||||
|
||||
@override
|
||||
String get gallery => 'Galería';
|
||||
|
||||
@override
|
||||
String get analyzingPhoto => 'Analizando foto...';
|
||||
|
||||
@override
|
||||
String get inQueue => 'Estás en la cola';
|
||||
|
||||
@override
|
||||
String queuePosition(int position) {
|
||||
return 'Posición $position';
|
||||
}
|
||||
|
||||
@override
|
||||
String get processing => 'Procesando...';
|
||||
|
||||
@override
|
||||
String get upgradePrompt => '¿Saltar la cola? Actualiza →';
|
||||
|
||||
@override
|
||||
String get recognitionFailed => 'Reconocimiento fallido. Inténtalo de nuevo.';
|
||||
|
||||
@override
|
||||
String get dishRecognition => 'Reconocimiento de platos';
|
||||
|
||||
@override
|
||||
String get all => 'Todos';
|
||||
|
||||
@override
|
||||
String get dishRecognized => 'Plato reconocido';
|
||||
|
||||
@override
|
||||
String get recognizing => 'Reconociendo…';
|
||||
|
||||
@override
|
||||
String get recognitionError => 'Error de reconocimiento';
|
||||
|
||||
@override
|
||||
String get dishResultTitle => 'Plato reconocido';
|
||||
|
||||
@override
|
||||
String get selectDish => 'Selecciona un plato';
|
||||
|
||||
@override
|
||||
String get dishNotRecognized => 'Plato no reconocido';
|
||||
|
||||
@override
|
||||
String get tryAgain => 'Intentar de nuevo';
|
||||
|
||||
@override
|
||||
String get nutritionApproximate =>
|
||||
'Los valores nutricionales son aproximados — estimados a partir de la foto.';
|
||||
|
||||
@override
|
||||
String get portion => 'Porción';
|
||||
|
||||
@override
|
||||
String get mealType => 'Tipo de comida';
|
||||
|
||||
@override
|
||||
String get dateLabel => 'Fecha';
|
||||
|
||||
@override
|
||||
String get addToJournal => 'Añadir al diario';
|
||||
|
||||
@override
|
||||
String get addFailed => 'Error al añadir. Inténtalo de nuevo.';
|
||||
|
||||
@override
|
||||
String get historyTitle => 'Historial de reconocimientos';
|
||||
|
||||
@override
|
||||
String get historyLoadError => 'Error al cargar el historial';
|
||||
|
||||
@override
|
||||
String get retry => 'Reintentar';
|
||||
|
||||
@override
|
||||
String get noHistory => 'Sin reconocimientos aún';
|
||||
|
||||
@override
|
||||
String get profileTitle => 'Perfil';
|
||||
|
||||
@override
|
||||
String get edit => 'Editar';
|
||||
|
||||
@override
|
||||
String get bodyParams => 'PARÁMETROS CORPORALES';
|
||||
|
||||
@override
|
||||
String get goalActivity => 'OBJETIVO Y ACTIVIDAD';
|
||||
|
||||
@override
|
||||
String get nutrition => 'NUTRICIÓN';
|
||||
|
||||
@override
|
||||
String get settings => 'AJUSTES';
|
||||
|
||||
@override
|
||||
String get height => 'Altura';
|
||||
|
||||
@override
|
||||
String get weight => 'Peso';
|
||||
|
||||
@override
|
||||
String get age => 'Edad';
|
||||
|
||||
@override
|
||||
String get gender => 'Género';
|
||||
|
||||
@override
|
||||
String get genderMale => 'Masculino';
|
||||
|
||||
@override
|
||||
String get genderFemale => 'Femenino';
|
||||
|
||||
@override
|
||||
String get goalLoss => 'Pérdida de peso';
|
||||
|
||||
@override
|
||||
String get goalMaintain => 'Mantenimiento';
|
||||
|
||||
@override
|
||||
String get goalGain => 'Ganancia muscular';
|
||||
|
||||
@override
|
||||
String get activityLow => 'Baja';
|
||||
|
||||
@override
|
||||
String get activityMedium => 'Media';
|
||||
|
||||
@override
|
||||
String get activityHigh => 'Alta';
|
||||
|
||||
@override
|
||||
String get calorieGoal => 'Objetivo calórico';
|
||||
|
||||
@override
|
||||
String get mealTypes => 'Tipos de comida';
|
||||
|
||||
@override
|
||||
String get formulaNote => 'Calculado con la fórmula de Mifflin-St Jeor';
|
||||
|
||||
@override
|
||||
String get language => 'Idioma';
|
||||
|
||||
@override
|
||||
String get notSet => 'No establecido';
|
||||
|
||||
@override
|
||||
String get calorieHint =>
|
||||
'Introduce los parámetros corporales para calcular el objetivo calórico';
|
||||
|
||||
@override
|
||||
String get logout => 'Cerrar sesión';
|
||||
|
||||
@override
|
||||
String get editProfile => 'Editar perfil';
|
||||
|
||||
@override
|
||||
String get cancel => 'Cancelar';
|
||||
|
||||
@override
|
||||
String get save => 'Guardar';
|
||||
|
||||
@override
|
||||
String get nameLabel => 'Nombre';
|
||||
|
||||
@override
|
||||
String get heightCm => 'Altura (cm)';
|
||||
|
||||
@override
|
||||
String get weightKg => 'Peso (kg)';
|
||||
|
||||
@override
|
||||
String get birthDate => 'Fecha de nacimiento';
|
||||
|
||||
@override
|
||||
String get nameRequired => 'Introduce el nombre';
|
||||
|
||||
@override
|
||||
String get profileUpdated => 'Perfil actualizado';
|
||||
|
||||
@override
|
||||
String get profileSaveFailed => 'Error al guardar';
|
||||
|
||||
@override
|
||||
String get mealTypeBreakfast => 'Desayuno';
|
||||
|
||||
@override
|
||||
String get mealTypeSecondBreakfast => 'Segundo desayuno';
|
||||
|
||||
@override
|
||||
String get mealTypeLunch => 'Almuerzo';
|
||||
|
||||
@override
|
||||
String get mealTypeAfternoonSnack => 'Merienda';
|
||||
|
||||
@override
|
||||
String get mealTypeDinner => 'Cena';
|
||||
|
||||
@override
|
||||
String get mealTypeSnack => 'Aperitivo';
|
||||
|
||||
@override
|
||||
String get navHome => 'Inicio';
|
||||
|
||||
@override
|
||||
String get navProducts => 'Productos';
|
||||
|
||||
@override
|
||||
String get navRecipes => 'Recetas';
|
||||
}
|
||||
290
client/lib/l10n/app_localizations_fr.dart
Normal file
290
client/lib/l10n/app_localizations_fr.dart
Normal file
@@ -0,0 +1,290 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// The translations for French (`fr`).
|
||||
class AppLocalizationsFr extends AppLocalizations {
|
||||
AppLocalizationsFr([String locale = 'fr']) : super(locale);
|
||||
|
||||
@override
|
||||
String get appTitle => 'FoodAI';
|
||||
|
||||
@override
|
||||
String get greetingMorning => 'Bonjour';
|
||||
|
||||
@override
|
||||
String get greetingAfternoon => 'Bon après-midi';
|
||||
|
||||
@override
|
||||
String get greetingEvening => 'Bonsoir';
|
||||
|
||||
@override
|
||||
String get caloriesUnit => 'kcal';
|
||||
|
||||
@override
|
||||
String get gramsUnit => 'g';
|
||||
|
||||
@override
|
||||
String get goalLabel => 'objectif :';
|
||||
|
||||
@override
|
||||
String get consumed => 'Consommé';
|
||||
|
||||
@override
|
||||
String get remaining => 'Restant';
|
||||
|
||||
@override
|
||||
String get exceeded => 'Dépassé';
|
||||
|
||||
@override
|
||||
String get proteinLabel => 'Protéines';
|
||||
|
||||
@override
|
||||
String get fatLabel => 'Lipides';
|
||||
|
||||
@override
|
||||
String get carbsLabel => 'Glucides';
|
||||
|
||||
@override
|
||||
String get today => 'Aujourd\'hui';
|
||||
|
||||
@override
|
||||
String get yesterday => 'Hier';
|
||||
|
||||
@override
|
||||
String get mealsSection => 'Repas';
|
||||
|
||||
@override
|
||||
String get addDish => 'Ajouter un plat';
|
||||
|
||||
@override
|
||||
String get scanDish => 'Scanner';
|
||||
|
||||
@override
|
||||
String get menu => 'Menu';
|
||||
|
||||
@override
|
||||
String get dishHistory => 'Historique des plats';
|
||||
|
||||
@override
|
||||
String get recommendCook => 'Nous recommandons de cuisiner';
|
||||
|
||||
@override
|
||||
String get camera => 'Appareil photo';
|
||||
|
||||
@override
|
||||
String get gallery => 'Galerie';
|
||||
|
||||
@override
|
||||
String get analyzingPhoto => 'Analyse de la photo...';
|
||||
|
||||
@override
|
||||
String get inQueue => 'Vous êtes en file d\'attente';
|
||||
|
||||
@override
|
||||
String queuePosition(int position) {
|
||||
return 'Position $position';
|
||||
}
|
||||
|
||||
@override
|
||||
String get processing => 'Traitement...';
|
||||
|
||||
@override
|
||||
String get upgradePrompt => 'Passer la file ? Passez à Premium →';
|
||||
|
||||
@override
|
||||
String get recognitionFailed => 'Reconnaissance échouée. Réessayez.';
|
||||
|
||||
@override
|
||||
String get dishRecognition => 'Reconnaissance de plats';
|
||||
|
||||
@override
|
||||
String get all => 'Tous';
|
||||
|
||||
@override
|
||||
String get dishRecognized => 'Plat reconnu';
|
||||
|
||||
@override
|
||||
String get recognizing => 'Reconnaissance en cours…';
|
||||
|
||||
@override
|
||||
String get recognitionError => 'Erreur de reconnaissance';
|
||||
|
||||
@override
|
||||
String get dishResultTitle => 'Plat reconnu';
|
||||
|
||||
@override
|
||||
String get selectDish => 'Sélectionner un plat';
|
||||
|
||||
@override
|
||||
String get dishNotRecognized => 'Plat non reconnu';
|
||||
|
||||
@override
|
||||
String get tryAgain => 'Réessayer';
|
||||
|
||||
@override
|
||||
String get nutritionApproximate =>
|
||||
'Les valeurs nutritionnelles sont approximatives — estimées à partir de la photo.';
|
||||
|
||||
@override
|
||||
String get portion => 'Portion';
|
||||
|
||||
@override
|
||||
String get mealType => 'Type de repas';
|
||||
|
||||
@override
|
||||
String get dateLabel => 'Date';
|
||||
|
||||
@override
|
||||
String get addToJournal => 'Ajouter au journal';
|
||||
|
||||
@override
|
||||
String get addFailed => 'Échec de l\'ajout. Réessayez.';
|
||||
|
||||
@override
|
||||
String get historyTitle => 'Historique des reconnaissances';
|
||||
|
||||
@override
|
||||
String get historyLoadError => 'Impossible de charger l\'historique';
|
||||
|
||||
@override
|
||||
String get retry => 'Réessayer';
|
||||
|
||||
@override
|
||||
String get noHistory => 'Aucune reconnaissance pour l\'instant';
|
||||
|
||||
@override
|
||||
String get profileTitle => 'Profil';
|
||||
|
||||
@override
|
||||
String get edit => 'Modifier';
|
||||
|
||||
@override
|
||||
String get bodyParams => 'PARAMÈTRES CORPORELS';
|
||||
|
||||
@override
|
||||
String get goalActivity => 'OBJECTIF & ACTIVITÉ';
|
||||
|
||||
@override
|
||||
String get nutrition => 'NUTRITION';
|
||||
|
||||
@override
|
||||
String get settings => 'PARAMÈTRES';
|
||||
|
||||
@override
|
||||
String get height => 'Taille';
|
||||
|
||||
@override
|
||||
String get weight => 'Poids';
|
||||
|
||||
@override
|
||||
String get age => 'Âge';
|
||||
|
||||
@override
|
||||
String get gender => 'Sexe';
|
||||
|
||||
@override
|
||||
String get genderMale => 'Masculin';
|
||||
|
||||
@override
|
||||
String get genderFemale => 'Féminin';
|
||||
|
||||
@override
|
||||
String get goalLoss => 'Perte de poids';
|
||||
|
||||
@override
|
||||
String get goalMaintain => 'Maintien';
|
||||
|
||||
@override
|
||||
String get goalGain => 'Prise de masse';
|
||||
|
||||
@override
|
||||
String get activityLow => 'Faible';
|
||||
|
||||
@override
|
||||
String get activityMedium => 'Moyenne';
|
||||
|
||||
@override
|
||||
String get activityHigh => 'Élevée';
|
||||
|
||||
@override
|
||||
String get calorieGoal => 'Objectif calorique';
|
||||
|
||||
@override
|
||||
String get mealTypes => 'Types de repas';
|
||||
|
||||
@override
|
||||
String get formulaNote => 'Calculé avec la formule de Mifflin-St Jeor';
|
||||
|
||||
@override
|
||||
String get language => 'Langue';
|
||||
|
||||
@override
|
||||
String get notSet => 'Non défini';
|
||||
|
||||
@override
|
||||
String get calorieHint =>
|
||||
'Saisissez les paramètres corporels pour calculer l\'objectif calorique';
|
||||
|
||||
@override
|
||||
String get logout => 'Se déconnecter';
|
||||
|
||||
@override
|
||||
String get editProfile => 'Modifier le profil';
|
||||
|
||||
@override
|
||||
String get cancel => 'Annuler';
|
||||
|
||||
@override
|
||||
String get save => 'Enregistrer';
|
||||
|
||||
@override
|
||||
String get nameLabel => 'Nom';
|
||||
|
||||
@override
|
||||
String get heightCm => 'Taille (cm)';
|
||||
|
||||
@override
|
||||
String get weightKg => 'Poids (kg)';
|
||||
|
||||
@override
|
||||
String get birthDate => 'Date de naissance';
|
||||
|
||||
@override
|
||||
String get nameRequired => 'Saisir le nom';
|
||||
|
||||
@override
|
||||
String get profileUpdated => 'Profil mis à jour';
|
||||
|
||||
@override
|
||||
String get profileSaveFailed => 'Échec de l\'enregistrement';
|
||||
|
||||
@override
|
||||
String get mealTypeBreakfast => 'Petit-déjeuner';
|
||||
|
||||
@override
|
||||
String get mealTypeSecondBreakfast => 'Deuxième petit-déjeuner';
|
||||
|
||||
@override
|
||||
String get mealTypeLunch => 'Déjeuner';
|
||||
|
||||
@override
|
||||
String get mealTypeAfternoonSnack => 'Goûter';
|
||||
|
||||
@override
|
||||
String get mealTypeDinner => 'Dîner';
|
||||
|
||||
@override
|
||||
String get mealTypeSnack => 'Collation';
|
||||
|
||||
@override
|
||||
String get navHome => 'Accueil';
|
||||
|
||||
@override
|
||||
String get navProducts => 'Produits';
|
||||
|
||||
@override
|
||||
String get navRecipes => 'Recettes';
|
||||
}
|
||||
290
client/lib/l10n/app_localizations_hi.dart
Normal file
290
client/lib/l10n/app_localizations_hi.dart
Normal file
@@ -0,0 +1,290 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// The translations for Hindi (`hi`).
|
||||
class AppLocalizationsHi extends AppLocalizations {
|
||||
AppLocalizationsHi([String locale = 'hi']) : 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 => 'कतार छोड़ें? अपग्रेड करें →';
|
||||
|
||||
@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 => 'रेसिपी';
|
||||
}
|
||||
290
client/lib/l10n/app_localizations_it.dart
Normal file
290
client/lib/l10n/app_localizations_it.dart
Normal file
@@ -0,0 +1,290 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// The translations for Italian (`it`).
|
||||
class AppLocalizationsIt extends AppLocalizations {
|
||||
AppLocalizationsIt([String locale = 'it']) : super(locale);
|
||||
|
||||
@override
|
||||
String get appTitle => 'FoodAI';
|
||||
|
||||
@override
|
||||
String get greetingMorning => 'Buongiorno';
|
||||
|
||||
@override
|
||||
String get greetingAfternoon => 'Buon pomeriggio';
|
||||
|
||||
@override
|
||||
String get greetingEvening => 'Buonasera';
|
||||
|
||||
@override
|
||||
String get caloriesUnit => 'kcal';
|
||||
|
||||
@override
|
||||
String get gramsUnit => 'g';
|
||||
|
||||
@override
|
||||
String get goalLabel => 'obiettivo:';
|
||||
|
||||
@override
|
||||
String get consumed => 'Consumato';
|
||||
|
||||
@override
|
||||
String get remaining => 'Rimanente';
|
||||
|
||||
@override
|
||||
String get exceeded => 'Superato';
|
||||
|
||||
@override
|
||||
String get proteinLabel => 'Proteine';
|
||||
|
||||
@override
|
||||
String get fatLabel => 'Grassi';
|
||||
|
||||
@override
|
||||
String get carbsLabel => 'Carboidrati';
|
||||
|
||||
@override
|
||||
String get today => 'Oggi';
|
||||
|
||||
@override
|
||||
String get yesterday => 'Ieri';
|
||||
|
||||
@override
|
||||
String get mealsSection => 'Pasti';
|
||||
|
||||
@override
|
||||
String get addDish => 'Aggiungi piatto';
|
||||
|
||||
@override
|
||||
String get scanDish => 'Scansiona';
|
||||
|
||||
@override
|
||||
String get menu => 'Menu';
|
||||
|
||||
@override
|
||||
String get dishHistory => 'Cronologia piatti';
|
||||
|
||||
@override
|
||||
String get recommendCook => 'Consigliamo di cucinare';
|
||||
|
||||
@override
|
||||
String get camera => 'Fotocamera';
|
||||
|
||||
@override
|
||||
String get gallery => 'Galleria';
|
||||
|
||||
@override
|
||||
String get analyzingPhoto => 'Analisi foto in corso...';
|
||||
|
||||
@override
|
||||
String get inQueue => 'Sei in coda';
|
||||
|
||||
@override
|
||||
String queuePosition(int position) {
|
||||
return 'Posizione $position';
|
||||
}
|
||||
|
||||
@override
|
||||
String get processing => 'Elaborazione...';
|
||||
|
||||
@override
|
||||
String get upgradePrompt => 'Salta la coda? Aggiorna →';
|
||||
|
||||
@override
|
||||
String get recognitionFailed => 'Riconoscimento fallito. Riprova.';
|
||||
|
||||
@override
|
||||
String get dishRecognition => 'Riconoscimento piatti';
|
||||
|
||||
@override
|
||||
String get all => 'Tutti';
|
||||
|
||||
@override
|
||||
String get dishRecognized => 'Piatto riconosciuto';
|
||||
|
||||
@override
|
||||
String get recognizing => 'Riconoscimento in corso…';
|
||||
|
||||
@override
|
||||
String get recognitionError => 'Errore di riconoscimento';
|
||||
|
||||
@override
|
||||
String get dishResultTitle => 'Piatto riconosciuto';
|
||||
|
||||
@override
|
||||
String get selectDish => 'Seleziona un piatto';
|
||||
|
||||
@override
|
||||
String get dishNotRecognized => 'Piatto non riconosciuto';
|
||||
|
||||
@override
|
||||
String get tryAgain => 'Riprova';
|
||||
|
||||
@override
|
||||
String get nutritionApproximate =>
|
||||
'I valori nutrizionali sono approssimativi — stimati dalla foto.';
|
||||
|
||||
@override
|
||||
String get portion => 'Porzione';
|
||||
|
||||
@override
|
||||
String get mealType => 'Tipo di pasto';
|
||||
|
||||
@override
|
||||
String get dateLabel => 'Data';
|
||||
|
||||
@override
|
||||
String get addToJournal => 'Aggiungi al diario';
|
||||
|
||||
@override
|
||||
String get addFailed => 'Aggiunta fallita. Riprova.';
|
||||
|
||||
@override
|
||||
String get historyTitle => 'Cronologia riconoscimenti';
|
||||
|
||||
@override
|
||||
String get historyLoadError => 'Impossibile caricare la cronologia';
|
||||
|
||||
@override
|
||||
String get retry => 'Riprova';
|
||||
|
||||
@override
|
||||
String get noHistory => 'Nessun riconoscimento ancora';
|
||||
|
||||
@override
|
||||
String get profileTitle => 'Profilo';
|
||||
|
||||
@override
|
||||
String get edit => 'Modifica';
|
||||
|
||||
@override
|
||||
String get bodyParams => 'PARAMETRI CORPOREI';
|
||||
|
||||
@override
|
||||
String get goalActivity => 'OBIETTIVO & ATTIVITÀ';
|
||||
|
||||
@override
|
||||
String get nutrition => 'NUTRIZIONE';
|
||||
|
||||
@override
|
||||
String get settings => 'IMPOSTAZIONI';
|
||||
|
||||
@override
|
||||
String get height => 'Altezza';
|
||||
|
||||
@override
|
||||
String get weight => 'Peso';
|
||||
|
||||
@override
|
||||
String get age => 'Età';
|
||||
|
||||
@override
|
||||
String get gender => 'Sesso';
|
||||
|
||||
@override
|
||||
String get genderMale => 'Maschio';
|
||||
|
||||
@override
|
||||
String get genderFemale => 'Femmina';
|
||||
|
||||
@override
|
||||
String get goalLoss => 'Perdita di peso';
|
||||
|
||||
@override
|
||||
String get goalMaintain => 'Mantenimento';
|
||||
|
||||
@override
|
||||
String get goalGain => 'Aumento muscolare';
|
||||
|
||||
@override
|
||||
String get activityLow => 'Bassa';
|
||||
|
||||
@override
|
||||
String get activityMedium => 'Media';
|
||||
|
||||
@override
|
||||
String get activityHigh => 'Alta';
|
||||
|
||||
@override
|
||||
String get calorieGoal => 'Obiettivo calorico';
|
||||
|
||||
@override
|
||||
String get mealTypes => 'Tipi di pasto';
|
||||
|
||||
@override
|
||||
String get formulaNote => 'Calcolato con la formula di Mifflin-St Jeor';
|
||||
|
||||
@override
|
||||
String get language => 'Lingua';
|
||||
|
||||
@override
|
||||
String get notSet => 'Non impostato';
|
||||
|
||||
@override
|
||||
String get calorieHint =>
|
||||
'Inserisci i parametri corporei per calcolare l\'obiettivo calorico';
|
||||
|
||||
@override
|
||||
String get logout => 'Disconnetti';
|
||||
|
||||
@override
|
||||
String get editProfile => 'Modifica profilo';
|
||||
|
||||
@override
|
||||
String get cancel => 'Annulla';
|
||||
|
||||
@override
|
||||
String get save => 'Salva';
|
||||
|
||||
@override
|
||||
String get nameLabel => 'Nome';
|
||||
|
||||
@override
|
||||
String get heightCm => 'Altezza (cm)';
|
||||
|
||||
@override
|
||||
String get weightKg => 'Peso (kg)';
|
||||
|
||||
@override
|
||||
String get birthDate => 'Data di nascita';
|
||||
|
||||
@override
|
||||
String get nameRequired => 'Inserisci il nome';
|
||||
|
||||
@override
|
||||
String get profileUpdated => 'Profilo aggiornato';
|
||||
|
||||
@override
|
||||
String get profileSaveFailed => 'Salvataggio fallito';
|
||||
|
||||
@override
|
||||
String get mealTypeBreakfast => 'Colazione';
|
||||
|
||||
@override
|
||||
String get mealTypeSecondBreakfast => 'Seconda colazione';
|
||||
|
||||
@override
|
||||
String get mealTypeLunch => 'Pranzo';
|
||||
|
||||
@override
|
||||
String get mealTypeAfternoonSnack => 'Merenda';
|
||||
|
||||
@override
|
||||
String get mealTypeDinner => 'Cena';
|
||||
|
||||
@override
|
||||
String get mealTypeSnack => 'Spuntino';
|
||||
|
||||
@override
|
||||
String get navHome => 'Home';
|
||||
|
||||
@override
|
||||
String get navProducts => 'Prodotti';
|
||||
|
||||
@override
|
||||
String get navRecipes => 'Ricette';
|
||||
}
|
||||
288
client/lib/l10n/app_localizations_ja.dart
Normal file
288
client/lib/l10n/app_localizations_ja.dart
Normal file
@@ -0,0 +1,288 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// The translations for Japanese (`ja`).
|
||||
class AppLocalizationsJa extends AppLocalizations {
|
||||
AppLocalizationsJa([String locale = 'ja']) : super(locale);
|
||||
|
||||
@override
|
||||
String get appTitle => 'FoodAI';
|
||||
|
||||
@override
|
||||
String get greetingMorning => 'おはようございます';
|
||||
|
||||
@override
|
||||
String get greetingAfternoon => 'こんにちは';
|
||||
|
||||
@override
|
||||
String get greetingEvening => 'こんばんは';
|
||||
|
||||
@override
|
||||
String get caloriesUnit => 'kcal';
|
||||
|
||||
@override
|
||||
String get gramsUnit => 'g';
|
||||
|
||||
@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 => '順番をスキップ?アップグレード →';
|
||||
|
||||
@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 => '身長(cm)';
|
||||
|
||||
@override
|
||||
String get weightKg => '体重(kg)';
|
||||
|
||||
@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 => 'レシピ';
|
||||
}
|
||||
288
client/lib/l10n/app_localizations_ko.dart
Normal file
288
client/lib/l10n/app_localizations_ko.dart
Normal file
@@ -0,0 +1,288 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// The translations for Korean (`ko`).
|
||||
class AppLocalizationsKo extends AppLocalizations {
|
||||
AppLocalizationsKo([String locale = 'ko']) : super(locale);
|
||||
|
||||
@override
|
||||
String get appTitle => 'FoodAI';
|
||||
|
||||
@override
|
||||
String get greetingMorning => '좋은 아침이에요';
|
||||
|
||||
@override
|
||||
String get greetingAfternoon => '안녕하세요';
|
||||
|
||||
@override
|
||||
String get greetingEvening => '좋은 저녁이에요';
|
||||
|
||||
@override
|
||||
String get caloriesUnit => 'kcal';
|
||||
|
||||
@override
|
||||
String get gramsUnit => 'g';
|
||||
|
||||
@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 => '대기열 건너뛰기? 업그레이드 →';
|
||||
|
||||
@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 => 'Mifflin-St Jeor 공식으로 계산';
|
||||
|
||||
@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 => '키 (cm)';
|
||||
|
||||
@override
|
||||
String get weightKg => '체중 (kg)';
|
||||
|
||||
@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 => '레시피';
|
||||
}
|
||||
290
client/lib/l10n/app_localizations_pt.dart
Normal file
290
client/lib/l10n/app_localizations_pt.dart
Normal file
@@ -0,0 +1,290 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// The translations for Portuguese (`pt`).
|
||||
class AppLocalizationsPt extends AppLocalizations {
|
||||
AppLocalizationsPt([String locale = 'pt']) : super(locale);
|
||||
|
||||
@override
|
||||
String get appTitle => 'FoodAI';
|
||||
|
||||
@override
|
||||
String get greetingMorning => 'Bom dia';
|
||||
|
||||
@override
|
||||
String get greetingAfternoon => 'Boa tarde';
|
||||
|
||||
@override
|
||||
String get greetingEvening => 'Boa noite';
|
||||
|
||||
@override
|
||||
String get caloriesUnit => 'kcal';
|
||||
|
||||
@override
|
||||
String get gramsUnit => 'g';
|
||||
|
||||
@override
|
||||
String get goalLabel => 'meta:';
|
||||
|
||||
@override
|
||||
String get consumed => 'Consumido';
|
||||
|
||||
@override
|
||||
String get remaining => 'Restante';
|
||||
|
||||
@override
|
||||
String get exceeded => 'Excedido';
|
||||
|
||||
@override
|
||||
String get proteinLabel => 'Proteínas';
|
||||
|
||||
@override
|
||||
String get fatLabel => 'Gorduras';
|
||||
|
||||
@override
|
||||
String get carbsLabel => 'Carboidratos';
|
||||
|
||||
@override
|
||||
String get today => 'Hoje';
|
||||
|
||||
@override
|
||||
String get yesterday => 'Ontem';
|
||||
|
||||
@override
|
||||
String get mealsSection => 'Refeições';
|
||||
|
||||
@override
|
||||
String get addDish => 'Adicionar prato';
|
||||
|
||||
@override
|
||||
String get scanDish => 'Escanear';
|
||||
|
||||
@override
|
||||
String get menu => 'Menu';
|
||||
|
||||
@override
|
||||
String get dishHistory => 'Histórico de pratos';
|
||||
|
||||
@override
|
||||
String get recommendCook => 'Recomendamos cozinhar';
|
||||
|
||||
@override
|
||||
String get camera => 'Câmera';
|
||||
|
||||
@override
|
||||
String get gallery => 'Galeria';
|
||||
|
||||
@override
|
||||
String get analyzingPhoto => 'Analisando foto...';
|
||||
|
||||
@override
|
||||
String get inQueue => 'Você está na fila';
|
||||
|
||||
@override
|
||||
String queuePosition(int position) {
|
||||
return 'Posição $position';
|
||||
}
|
||||
|
||||
@override
|
||||
String get processing => 'Processando...';
|
||||
|
||||
@override
|
||||
String get upgradePrompt => 'Pular a fila? Faça upgrade →';
|
||||
|
||||
@override
|
||||
String get recognitionFailed => 'Reconhecimento falhou. Tente novamente.';
|
||||
|
||||
@override
|
||||
String get dishRecognition => 'Reconhecimento de pratos';
|
||||
|
||||
@override
|
||||
String get all => 'Todos';
|
||||
|
||||
@override
|
||||
String get dishRecognized => 'Prato reconhecido';
|
||||
|
||||
@override
|
||||
String get recognizing => 'Reconhecendo…';
|
||||
|
||||
@override
|
||||
String get recognitionError => 'Erro de reconhecimento';
|
||||
|
||||
@override
|
||||
String get dishResultTitle => 'Prato reconhecido';
|
||||
|
||||
@override
|
||||
String get selectDish => 'Selecionar prato';
|
||||
|
||||
@override
|
||||
String get dishNotRecognized => 'Prato não reconhecido';
|
||||
|
||||
@override
|
||||
String get tryAgain => 'Tentar novamente';
|
||||
|
||||
@override
|
||||
String get nutritionApproximate =>
|
||||
'Os valores nutricionais são aproximados — estimados pela foto.';
|
||||
|
||||
@override
|
||||
String get portion => 'Porção';
|
||||
|
||||
@override
|
||||
String get mealType => 'Tipo de refeição';
|
||||
|
||||
@override
|
||||
String get dateLabel => 'Data';
|
||||
|
||||
@override
|
||||
String get addToJournal => 'Adicionar ao diário';
|
||||
|
||||
@override
|
||||
String get addFailed => 'Falha ao adicionar. Tente novamente.';
|
||||
|
||||
@override
|
||||
String get historyTitle => 'Histórico de reconhecimentos';
|
||||
|
||||
@override
|
||||
String get historyLoadError => 'Falha ao carregar o histórico';
|
||||
|
||||
@override
|
||||
String get retry => 'Tentar novamente';
|
||||
|
||||
@override
|
||||
String get noHistory => 'Nenhum reconhecimento ainda';
|
||||
|
||||
@override
|
||||
String get profileTitle => 'Perfil';
|
||||
|
||||
@override
|
||||
String get edit => 'Editar';
|
||||
|
||||
@override
|
||||
String get bodyParams => 'PARÂMETROS CORPORAIS';
|
||||
|
||||
@override
|
||||
String get goalActivity => 'OBJETIVO & ATIVIDADE';
|
||||
|
||||
@override
|
||||
String get nutrition => 'NUTRIÇÃO';
|
||||
|
||||
@override
|
||||
String get settings => 'CONFIGURAÇÕES';
|
||||
|
||||
@override
|
||||
String get height => 'Altura';
|
||||
|
||||
@override
|
||||
String get weight => 'Peso';
|
||||
|
||||
@override
|
||||
String get age => 'Idade';
|
||||
|
||||
@override
|
||||
String get gender => 'Gênero';
|
||||
|
||||
@override
|
||||
String get genderMale => 'Masculino';
|
||||
|
||||
@override
|
||||
String get genderFemale => 'Feminino';
|
||||
|
||||
@override
|
||||
String get goalLoss => 'Perda de peso';
|
||||
|
||||
@override
|
||||
String get goalMaintain => 'Manutenção';
|
||||
|
||||
@override
|
||||
String get goalGain => 'Ganho muscular';
|
||||
|
||||
@override
|
||||
String get activityLow => 'Baixa';
|
||||
|
||||
@override
|
||||
String get activityMedium => 'Média';
|
||||
|
||||
@override
|
||||
String get activityHigh => 'Alta';
|
||||
|
||||
@override
|
||||
String get calorieGoal => 'Meta calórica';
|
||||
|
||||
@override
|
||||
String get mealTypes => 'Tipos de refeição';
|
||||
|
||||
@override
|
||||
String get formulaNote => 'Calculado com a fórmula de Mifflin-St Jeor';
|
||||
|
||||
@override
|
||||
String get language => 'Idioma';
|
||||
|
||||
@override
|
||||
String get notSet => 'Não definido';
|
||||
|
||||
@override
|
||||
String get calorieHint =>
|
||||
'Insira os parâmetros corporais para calcular a meta calórica';
|
||||
|
||||
@override
|
||||
String get logout => 'Sair';
|
||||
|
||||
@override
|
||||
String get editProfile => 'Editar perfil';
|
||||
|
||||
@override
|
||||
String get cancel => 'Cancelar';
|
||||
|
||||
@override
|
||||
String get save => 'Salvar';
|
||||
|
||||
@override
|
||||
String get nameLabel => 'Nome';
|
||||
|
||||
@override
|
||||
String get heightCm => 'Altura (cm)';
|
||||
|
||||
@override
|
||||
String get weightKg => 'Peso (kg)';
|
||||
|
||||
@override
|
||||
String get birthDate => 'Data de nascimento';
|
||||
|
||||
@override
|
||||
String get nameRequired => 'Insira o nome';
|
||||
|
||||
@override
|
||||
String get profileUpdated => 'Perfil atualizado';
|
||||
|
||||
@override
|
||||
String get profileSaveFailed => 'Falha ao salvar';
|
||||
|
||||
@override
|
||||
String get mealTypeBreakfast => 'Café da manhã';
|
||||
|
||||
@override
|
||||
String get mealTypeSecondBreakfast => 'Segundo café da manhã';
|
||||
|
||||
@override
|
||||
String get mealTypeLunch => 'Almoço';
|
||||
|
||||
@override
|
||||
String get mealTypeAfternoonSnack => 'Lanche da tarde';
|
||||
|
||||
@override
|
||||
String get mealTypeDinner => 'Jantar';
|
||||
|
||||
@override
|
||||
String get mealTypeSnack => 'Petisco';
|
||||
|
||||
@override
|
||||
String get navHome => 'Início';
|
||||
|
||||
@override
|
||||
String get navProducts => 'Produtos';
|
||||
|
||||
@override
|
||||
String get navRecipes => 'Receitas';
|
||||
}
|
||||
289
client/lib/l10n/app_localizations_ru.dart
Normal file
289
client/lib/l10n/app_localizations_ru.dart
Normal file
@@ -0,0 +1,289 @@
|
||||
// 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 => 'Рецепты';
|
||||
}
|
||||
288
client/lib/l10n/app_localizations_zh.dart
Normal file
288
client/lib/l10n/app_localizations_zh.dart
Normal file
@@ -0,0 +1,288 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// The translations for Chinese (`zh`).
|
||||
class AppLocalizationsZh extends AppLocalizations {
|
||||
AppLocalizationsZh([String locale = 'zh']) : 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 => '跳过队列?升级 →';
|
||||
|
||||
@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 => '食谱';
|
||||
}
|
||||
100
client/lib/l10n/app_pt.arb
Normal file
100
client/lib/l10n/app_pt.arb
Normal file
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"@@locale": "pt",
|
||||
"appTitle": "FoodAI",
|
||||
"greetingMorning": "Bom dia",
|
||||
"greetingAfternoon": "Boa tarde",
|
||||
"greetingEvening": "Boa noite",
|
||||
"caloriesUnit": "kcal",
|
||||
"gramsUnit": "g",
|
||||
"goalLabel": "meta:",
|
||||
"consumed": "Consumido",
|
||||
"remaining": "Restante",
|
||||
"exceeded": "Excedido",
|
||||
"proteinLabel": "Proteínas",
|
||||
"fatLabel": "Gorduras",
|
||||
"carbsLabel": "Carboidratos",
|
||||
"today": "Hoje",
|
||||
"yesterday": "Ontem",
|
||||
"mealsSection": "Refeições",
|
||||
"addDish": "Adicionar prato",
|
||||
"scanDish": "Escanear",
|
||||
"menu": "Menu",
|
||||
"dishHistory": "Histórico de pratos",
|
||||
"recommendCook": "Recomendamos cozinhar",
|
||||
"camera": "Câmera",
|
||||
"gallery": "Galeria",
|
||||
"analyzingPhoto": "Analisando foto...",
|
||||
"inQueue": "Você está na fila",
|
||||
"queuePosition": "Posição {position}",
|
||||
"@queuePosition": {
|
||||
"placeholders": {
|
||||
"position": { "type": "int" }
|
||||
}
|
||||
},
|
||||
"processing": "Processando...",
|
||||
"upgradePrompt": "Pular a fila? Faça upgrade →",
|
||||
"recognitionFailed": "Reconhecimento falhou. Tente novamente.",
|
||||
"dishRecognition": "Reconhecimento de pratos",
|
||||
"all": "Todos",
|
||||
"dishRecognized": "Prato reconhecido",
|
||||
"recognizing": "Reconhecendo…",
|
||||
"recognitionError": "Erro de reconhecimento",
|
||||
"dishResultTitle": "Prato reconhecido",
|
||||
"selectDish": "Selecionar prato",
|
||||
"dishNotRecognized": "Prato não reconhecido",
|
||||
"tryAgain": "Tentar novamente",
|
||||
"nutritionApproximate": "Os valores nutricionais são aproximados — estimados pela foto.",
|
||||
"portion": "Porção",
|
||||
"mealType": "Tipo de refeição",
|
||||
"dateLabel": "Data",
|
||||
"addToJournal": "Adicionar ao diário",
|
||||
"addFailed": "Falha ao adicionar. Tente novamente.",
|
||||
"historyTitle": "Histórico de reconhecimentos",
|
||||
"historyLoadError": "Falha ao carregar o histórico",
|
||||
"retry": "Tentar novamente",
|
||||
"noHistory": "Nenhum reconhecimento ainda",
|
||||
"profileTitle": "Perfil",
|
||||
"edit": "Editar",
|
||||
"bodyParams": "PARÂMETROS CORPORAIS",
|
||||
"goalActivity": "OBJETIVO & ATIVIDADE",
|
||||
"nutrition": "NUTRIÇÃO",
|
||||
"settings": "CONFIGURAÇÕES",
|
||||
"height": "Altura",
|
||||
"weight": "Peso",
|
||||
"age": "Idade",
|
||||
"gender": "Gênero",
|
||||
"genderMale": "Masculino",
|
||||
"genderFemale": "Feminino",
|
||||
"goalLoss": "Perda de peso",
|
||||
"goalMaintain": "Manutenção",
|
||||
"goalGain": "Ganho muscular",
|
||||
"activityLow": "Baixa",
|
||||
"activityMedium": "Média",
|
||||
"activityHigh": "Alta",
|
||||
"calorieGoal": "Meta calórica",
|
||||
"mealTypes": "Tipos de refeição",
|
||||
"formulaNote": "Calculado com a fórmula de Mifflin-St Jeor",
|
||||
"language": "Idioma",
|
||||
"notSet": "Não definido",
|
||||
"calorieHint": "Insira os parâmetros corporais para calcular a meta calórica",
|
||||
"logout": "Sair",
|
||||
"editProfile": "Editar perfil",
|
||||
"cancel": "Cancelar",
|
||||
"save": "Salvar",
|
||||
"nameLabel": "Nome",
|
||||
"heightCm": "Altura (cm)",
|
||||
"weightKg": "Peso (kg)",
|
||||
"birthDate": "Data de nascimento",
|
||||
"nameRequired": "Insira o nome",
|
||||
"profileUpdated": "Perfil atualizado",
|
||||
"profileSaveFailed": "Falha ao salvar",
|
||||
"mealTypeBreakfast": "Café da manhã",
|
||||
"mealTypeSecondBreakfast": "Segundo café da manhã",
|
||||
"mealTypeLunch": "Almoço",
|
||||
"mealTypeAfternoonSnack": "Lanche da tarde",
|
||||
"mealTypeDinner": "Jantar",
|
||||
"mealTypeSnack": "Petisco",
|
||||
"navHome": "Início",
|
||||
"navProducts": "Produtos",
|
||||
"navRecipes": "Receitas"
|
||||
}
|
||||
100
client/lib/l10n/app_ru.arb
Normal file
100
client/lib/l10n/app_ru.arb
Normal file
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"@@locale": "ru",
|
||||
"appTitle": "FoodAI",
|
||||
"greetingMorning": "Доброе утро",
|
||||
"greetingAfternoon": "Добрый день",
|
||||
"greetingEvening": "Добрый вечер",
|
||||
"caloriesUnit": "ккал",
|
||||
"gramsUnit": "г",
|
||||
"goalLabel": "цель:",
|
||||
"consumed": "Потреблено",
|
||||
"remaining": "Осталось",
|
||||
"exceeded": "Превышение",
|
||||
"proteinLabel": "Белки",
|
||||
"fatLabel": "Жиры",
|
||||
"carbsLabel": "Углеводы",
|
||||
"today": "Сегодня",
|
||||
"yesterday": "Вчера",
|
||||
"mealsSection": "Приёмы пищи",
|
||||
"addDish": "Добавить блюдо",
|
||||
"scanDish": "Сканировать",
|
||||
"menu": "Меню",
|
||||
"dishHistory": "История блюд",
|
||||
"recommendCook": "Рекомендуем приготовить",
|
||||
"camera": "Камера",
|
||||
"gallery": "Галерея",
|
||||
"analyzingPhoto": "Анализируем фото...",
|
||||
"inQueue": "Вы в очереди",
|
||||
"queuePosition": "Позиция {position}",
|
||||
"@queuePosition": {
|
||||
"placeholders": {
|
||||
"position": { "type": "int" }
|
||||
}
|
||||
},
|
||||
"processing": "Обрабатываем...",
|
||||
"upgradePrompt": "Хотите без очереди? Upgrade →",
|
||||
"recognitionFailed": "Не удалось распознать. Попробуйте ещё раз.",
|
||||
"dishRecognition": "Распознавание блюд",
|
||||
"all": "Все",
|
||||
"dishRecognized": "Блюдо распознано",
|
||||
"recognizing": "Распознаётся…",
|
||||
"recognitionError": "Ошибка распознавания",
|
||||
"dishResultTitle": "Распознано блюдо",
|
||||
"selectDish": "Выберите блюдо",
|
||||
"dishNotRecognized": "Блюдо не распознано",
|
||||
"tryAgain": "Попробовать снова",
|
||||
"nutritionApproximate": "КБЖУ приблизительные — определены по фото.",
|
||||
"portion": "Порция",
|
||||
"mealType": "Приём пищи",
|
||||
"dateLabel": "Дата",
|
||||
"addToJournal": "Добавить в журнал",
|
||||
"addFailed": "Не удалось добавить. Попробуйте ещё раз.",
|
||||
"historyTitle": "История распознавания",
|
||||
"historyLoadError": "Не удалось загрузить историю",
|
||||
"retry": "Повторить",
|
||||
"noHistory": "Нет распознаваний",
|
||||
"profileTitle": "Профиль",
|
||||
"edit": "Изменить",
|
||||
"bodyParams": "ПАРАМЕТРЫ ТЕЛА",
|
||||
"goalActivity": "ЦЕЛЬ И АКТИВНОСТЬ",
|
||||
"nutrition": "ПИТАНИЕ",
|
||||
"settings": "НАСТРОЙКИ",
|
||||
"height": "Рост",
|
||||
"weight": "Вес",
|
||||
"age": "Возраст",
|
||||
"gender": "Пол",
|
||||
"genderMale": "Мужской",
|
||||
"genderFemale": "Женский",
|
||||
"goalLoss": "Похудение",
|
||||
"goalMaintain": "Поддержание",
|
||||
"goalGain": "Набор массы",
|
||||
"activityLow": "Низкая",
|
||||
"activityMedium": "Средняя",
|
||||
"activityHigh": "Высокая",
|
||||
"calorieGoal": "Норма калорий",
|
||||
"mealTypes": "Приёмы пищи",
|
||||
"formulaNote": "Рассчитано по формуле Миффлина-Сан Жеора",
|
||||
"language": "Язык",
|
||||
"notSet": "Не задано",
|
||||
"calorieHint": "Укажите параметры тела для расчёта нормы калорий",
|
||||
"logout": "Выйти из аккаунта",
|
||||
"editProfile": "Редактировать профиль",
|
||||
"cancel": "Отмена",
|
||||
"save": "Сохранить",
|
||||
"nameLabel": "Имя",
|
||||
"heightCm": "Рост (см)",
|
||||
"weightKg": "Вес (кг)",
|
||||
"birthDate": "Дата рождения",
|
||||
"nameRequired": "Введите имя",
|
||||
"profileUpdated": "Профиль обновлён",
|
||||
"profileSaveFailed": "Не удалось сохранить",
|
||||
"mealTypeBreakfast": "Завтрак",
|
||||
"mealTypeSecondBreakfast": "Второй завтрак",
|
||||
"mealTypeLunch": "Обед",
|
||||
"mealTypeAfternoonSnack": "Полдник",
|
||||
"mealTypeDinner": "Ужин",
|
||||
"mealTypeSnack": "Перекус",
|
||||
"navHome": "Главная",
|
||||
"navProducts": "Продукты",
|
||||
"navRecipes": "Рецепты"
|
||||
}
|
||||
100
client/lib/l10n/app_zh.arb
Normal file
100
client/lib/l10n/app_zh.arb
Normal file
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"@@locale": "zh",
|
||||
"appTitle": "FoodAI",
|
||||
"greetingMorning": "早上好",
|
||||
"greetingAfternoon": "下午好",
|
||||
"greetingEvening": "晚上好",
|
||||
"caloriesUnit": "千卡",
|
||||
"gramsUnit": "克",
|
||||
"goalLabel": "目标:",
|
||||
"consumed": "已摄入",
|
||||
"remaining": "剩余",
|
||||
"exceeded": "超出",
|
||||
"proteinLabel": "蛋白质",
|
||||
"fatLabel": "脂肪",
|
||||
"carbsLabel": "碳水化合物",
|
||||
"today": "今天",
|
||||
"yesterday": "昨天",
|
||||
"mealsSection": "餐食",
|
||||
"addDish": "添加菜品",
|
||||
"scanDish": "扫描",
|
||||
"menu": "菜单",
|
||||
"dishHistory": "菜品历史",
|
||||
"recommendCook": "推荐烹饪",
|
||||
"camera": "相机",
|
||||
"gallery": "相册",
|
||||
"analyzingPhoto": "正在分析照片...",
|
||||
"inQueue": "您在队列中",
|
||||
"queuePosition": "位置 {position}",
|
||||
"@queuePosition": {
|
||||
"placeholders": {
|
||||
"position": { "type": "int" }
|
||||
}
|
||||
},
|
||||
"processing": "处理中...",
|
||||
"upgradePrompt": "跳过队列?升级 →",
|
||||
"recognitionFailed": "识别失败。请重试。",
|
||||
"dishRecognition": "菜品识别",
|
||||
"all": "全部",
|
||||
"dishRecognized": "菜品已识别",
|
||||
"recognizing": "识别中…",
|
||||
"recognitionError": "识别错误",
|
||||
"dishResultTitle": "菜品已识别",
|
||||
"selectDish": "选择菜品",
|
||||
"dishNotRecognized": "未识别到菜品",
|
||||
"tryAgain": "重试",
|
||||
"nutritionApproximate": "营养值为近似值 — 根据照片估算。",
|
||||
"portion": "份量",
|
||||
"mealType": "餐食类型",
|
||||
"dateLabel": "日期",
|
||||
"addToJournal": "添加到日记",
|
||||
"addFailed": "添加失败。请重试。",
|
||||
"historyTitle": "识别历史",
|
||||
"historyLoadError": "加载历史失败",
|
||||
"retry": "重试",
|
||||
"noHistory": "暂无识别记录",
|
||||
"profileTitle": "个人资料",
|
||||
"edit": "编辑",
|
||||
"bodyParams": "身体参数",
|
||||
"goalActivity": "目标与活动",
|
||||
"nutrition": "营养",
|
||||
"settings": "设置",
|
||||
"height": "身高",
|
||||
"weight": "体重",
|
||||
"age": "年龄",
|
||||
"gender": "性别",
|
||||
"genderMale": "男",
|
||||
"genderFemale": "女",
|
||||
"goalLoss": "减重",
|
||||
"goalMaintain": "保持",
|
||||
"goalGain": "增肌",
|
||||
"activityLow": "低",
|
||||
"activityMedium": "中",
|
||||
"activityHigh": "高",
|
||||
"calorieGoal": "卡路里目标",
|
||||
"mealTypes": "餐食类型",
|
||||
"formulaNote": "使用米夫林-圣热尔公式计算",
|
||||
"language": "语言",
|
||||
"notSet": "未设置",
|
||||
"calorieHint": "输入身体参数以计算卡路里目标",
|
||||
"logout": "退出登录",
|
||||
"editProfile": "编辑资料",
|
||||
"cancel": "取消",
|
||||
"save": "保存",
|
||||
"nameLabel": "姓名",
|
||||
"heightCm": "身高(厘米)",
|
||||
"weightKg": "体重(千克)",
|
||||
"birthDate": "出生日期",
|
||||
"nameRequired": "请输入姓名",
|
||||
"profileUpdated": "资料已更新",
|
||||
"profileSaveFailed": "保存失败",
|
||||
"mealTypeBreakfast": "早餐",
|
||||
"mealTypeSecondBreakfast": "第二早餐",
|
||||
"mealTypeLunch": "午餐",
|
||||
"mealTypeAfternoonSnack": "下午茶",
|
||||
"mealTypeDinner": "晚餐",
|
||||
"mealTypeSnack": "零食",
|
||||
"navHome": "首页",
|
||||
"navProducts": "食品",
|
||||
"navRecipes": "食谱"
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
import 'dart:ui' show PlatformDispatcher;
|
||||
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'app.dart';
|
||||
import 'core/locale/language_provider.dart';
|
||||
import 'core/storage/local_preferences.dart';
|
||||
import 'core/storage/local_preferences_provider.dart';
|
||||
import 'firebase_options.dart';
|
||||
@@ -16,12 +19,22 @@ void main() async {
|
||||
|
||||
final sharedPreferences = await SharedPreferences.getInstance();
|
||||
|
||||
// Detect system language on first launch.
|
||||
const supportedLanguageCodes = {
|
||||
'en', 'ru', 'es', 'de', 'fr', 'it', 'pt', 'zh', 'ja', 'ko', 'ar', 'hi',
|
||||
};
|
||||
final systemLanguageCode = PlatformDispatcher.instance.locale.languageCode;
|
||||
final initialLanguage = supportedLanguageCodes.contains(systemLanguageCode)
|
||||
? systemLanguageCode
|
||||
: 'en';
|
||||
|
||||
runApp(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
localPreferencesProvider.overrideWithValue(
|
||||
LocalPreferences(sharedPreferences),
|
||||
),
|
||||
languageProvider.overrideWith((ref) => initialLanguage),
|
||||
],
|
||||
child: const App(),
|
||||
),
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import 'package:food_ai/l10n/app_localizations.dart';
|
||||
|
||||
/// A configurable meal type that the user tracks throughout the day.
|
||||
class MealTypeOption {
|
||||
final String id;
|
||||
final String label;
|
||||
final String emoji;
|
||||
|
||||
const MealTypeOption({
|
||||
required this.id,
|
||||
required this.label,
|
||||
required this.emoji,
|
||||
});
|
||||
}
|
||||
|
||||
/// All meal types available for selection.
|
||||
const kAllMealTypes = [
|
||||
MealTypeOption(id: 'breakfast', label: 'Завтрак', emoji: '🌅'),
|
||||
MealTypeOption(id: 'second_breakfast', label: 'Второй завтрак', emoji: '☕'),
|
||||
MealTypeOption(id: 'lunch', label: 'Обед', emoji: '🍽️'),
|
||||
MealTypeOption(id: 'afternoon_snack', label: 'Полдник', emoji: '🥗'),
|
||||
MealTypeOption(id: 'dinner', label: 'Ужин', emoji: '🌙'),
|
||||
MealTypeOption(id: 'snack', label: 'Перекус', emoji: '🍎'),
|
||||
MealTypeOption(id: 'breakfast', emoji: '🌅'),
|
||||
MealTypeOption(id: 'second_breakfast', emoji: '☕'),
|
||||
MealTypeOption(id: 'lunch', emoji: '🍽️'),
|
||||
MealTypeOption(id: 'afternoon_snack', emoji: '🥗'),
|
||||
MealTypeOption(id: 'dinner', emoji: '🌙'),
|
||||
MealTypeOption(id: 'snack', emoji: '🍎'),
|
||||
];
|
||||
|
||||
/// Default meal type IDs assigned to new users.
|
||||
@@ -27,3 +27,14 @@ const kDefaultMealTypeIds = ['breakfast', 'lunch', 'dinner'];
|
||||
/// Returns the [MealTypeOption] for the given [id], or null if not found.
|
||||
MealTypeOption? mealTypeById(String id) =>
|
||||
kAllMealTypes.where((option) => option.id == id).firstOrNull;
|
||||
|
||||
/// Returns the localised label for the given meal type [id].
|
||||
String mealTypeLabel(String id, AppLocalizations l10n) => switch (id) {
|
||||
'breakfast' => l10n.mealTypeBreakfast,
|
||||
'second_breakfast' => l10n.mealTypeSecondBreakfast,
|
||||
'lunch' => l10n.mealTypeLunch,
|
||||
'afternoon_snack' => l10n.mealTypeAfternoonSnack,
|
||||
'dinner' => l10n.mealTypeDinner,
|
||||
'snack' => l10n.mealTypeSnack,
|
||||
_ => id,
|
||||
};
|
||||
|
||||
@@ -125,10 +125,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
version: "1.4.1"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -350,6 +350,11 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
flutter_localizations:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -592,6 +597,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.20.2"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -676,26 +689,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
version: "0.12.19"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
version: "0.13.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
version: "1.17.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1057,10 +1070,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
||||
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.6"
|
||||
version: "0.7.10"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -9,6 +9,9 @@ environment:
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
intl: ^0.20.1
|
||||
|
||||
cupertino_icons: ^1.0.8
|
||||
|
||||
@@ -52,3 +55,4 @@ dev_dependencies:
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
generate: true
|
||||
|
||||
Reference in New Issue
Block a user