feat: localise scan screen (12 languages)

Replace all hardcoded Russian strings in ScanScreen and _LoadingDialog
with AppLocalizations keys; add addFromReceiptOrPhoto, chooseMethod,
photoReceipt, photoReceiptSubtitle, photoProducts, photoProductsSubtitle,
recognizing keys to all 12 ARB files and regenerate AppLocalizations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
dbastrikin
2026-03-19 23:05:11 +02:00
parent 54b10d51e2
commit 9e7fc09f4b
27 changed files with 373 additions and 46 deletions

View File

@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
import '../../l10n/app_localizations.dart';
import 'recognition_service.dart';
/// Entry screen — lets the user choose how to add products.
@@ -16,38 +17,32 @@ class ScanScreen extends ConsumerStatefulWidget {
class _ScanScreenState extends ConsumerState<ScanScreen> {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: const Text('Добавить продукты')),
appBar: AppBar(title: Text(l10n.addFromReceiptOrPhoto)),
body: ListView(
padding: const EdgeInsets.all(20),
children: [
const SizedBox(height: 16),
Text(
'Выберите способ',
l10n.chooseMethod,
style: Theme.of(context).textTheme.titleLarge,
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
_ModeCard(
emoji: '🧾',
title: 'Сфотографировать чек',
subtitle: 'Распознаем все продукты из чека',
title: l10n.photoReceipt,
subtitle: l10n.photoReceiptSubtitle,
onTap: () => _pickAndRecognize(context, _Mode.receipt),
),
const SizedBox(height: 16),
_ModeCard(
emoji: '🥦',
title: 'Сфотографировать продукты',
subtitle: 'Холодильник, стол, полка — до 3 фото',
title: l10n.photoProducts,
subtitle: l10n.photoProductsSubtitle,
onTap: () => _pickAndRecognize(context, _Mode.products),
),
const SizedBox(height: 16),
_ModeCard(
emoji: '✏️',
title: 'Добавить вручную',
subtitle: 'Ввести название, количество и срок',
onTap: () => context.push('/products/add'),
),
],
),
);
@@ -85,12 +80,13 @@ class _ScanScreenState extends ConsumerState<ScanScreen> {
if (!context.mounted) return;
final service = ref.read(recognitionServiceProvider);
final l10n = AppLocalizations.of(context)!;
// Show loading overlay while the AI processes.
showDialog(
context: context,
barrierDismissible: false,
builder: (_) => const _LoadingDialog(),
builder: (dialogContext) => _LoadingDialog(label: l10n.recognizing),
);
try {
@@ -113,15 +109,14 @@ class _ScanScreenState extends ConsumerState<ScanScreen> {
if (context.mounted) {
Navigator.pop(context); // close loading
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Не удалось распознать. Попробуйте ещё раз.'),
),
SnackBar(content: Text(l10n.recognitionFailed)),
);
}
}
}
Future<ImageSource?> _chooseSource(BuildContext context) async {
final l10n = AppLocalizations.of(context)!;
return showModalBottomSheet<ImageSource>(
context: context,
builder: (_) => SafeArea(
@@ -130,12 +125,12 @@ class _ScanScreenState extends ConsumerState<ScanScreen> {
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),
),
],
@@ -186,17 +181,19 @@ class _ModeCard extends StatelessWidget {
}
class _LoadingDialog extends StatelessWidget {
const _LoadingDialog();
const _LoadingDialog({required this.label});
final String label;
@override
Widget build(BuildContext context) {
return const AlertDialog(
return AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('Распознаём...'),
const CircularProgressIndicator(),
const SizedBox(height: 16),
Text(label),
],
),
);