feat: async product/receipt recognition via Kafka
Backend:
- Migration 002: product_recognition_jobs table with JSONB images column
and job_type CHECK ('receipt' | 'products')
- New Kafka topics: ai.products.paid / ai.products.free
- ProductJob model, ProductJobRepository (mirrors dish job pattern)
- itemEnricher extracted from Handler — shared by HTTP handler and worker
- ProductSSEBroker: PG LISTEN on product_job_update channel
- ProductWorkerPool: 5 workers, branches on job_type to call
RecognizeReceipt or RecognizeProducts per image in parallel
- Handler: RecognizeReceipt and RecognizeProducts now return 202 Accepted
instead of blocking; 4 new endpoints: GET /ai/product-jobs,
/product-jobs/history, /product-jobs/{id}, /product-jobs/{id}/stream
- cmd/worker: extended to run ProductWorkerPool alongside dish WorkerPool
- cmd/server: wires productJobRepository + productSSEBroker; both SSE
brokers started in App.Start()
Flutter client:
- ProductJobCreated, ProductJobResult, ProductJobSummary, ProductJobEvent
models + submitReceiptRecognition/submitProductsRecognition/stream methods
- Shared _openSseStream helper eliminates duplicate SSE parsing loop
- ScanScreen: replace blocking AI calls with async submit + navigate to
ProductJobWatchScreen
- ProductJobWatchScreen: watches SSE stream, navigates to /scan/confirm
when done, shows error on failure
- ProductsScreen: prepends _RecentScansSection (hidden when empty); compact
horizontal list of recent scans with "See all" → history
- ProductJobHistoryScreen: full list of all product recognition jobs
- New routes: /scan/product-job-watch, /products/job-history
- L10n: 7 new keys in all 12 ARB files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -165,5 +165,12 @@
|
||||
"planProductsSkip": "تخطي اختيار المنتجات",
|
||||
"planProductsSkipNoProducts": "التخطيط بدون منتجات",
|
||||
"planProductsSelectAll": "تحديد الكل",
|
||||
"planProductsDeselectAll": "إلغاء تحديد الكل"
|
||||
"planProductsDeselectAll": "إلغاء تحديد الكل",
|
||||
"recentScans": "عمليات المسح الأخيرة",
|
||||
"seeAllScans": "عرض الكل",
|
||||
"productJobHistoryTitle": "سجل المسح",
|
||||
"jobTypeReceipt": "إيصال",
|
||||
"jobTypeProducts": "منتجات",
|
||||
"scanSubmitting": "جارٍ الإرسال...",
|
||||
"processingProducts": "جارٍ المعالجة..."
|
||||
}
|
||||
|
||||
@@ -165,5 +165,12 @@
|
||||
"planProductsSkip": "Produktauswahl überspringen",
|
||||
"planProductsSkipNoProducts": "Ohne Produkte planen",
|
||||
"planProductsSelectAll": "Alle auswählen",
|
||||
"planProductsDeselectAll": "Alle abwählen"
|
||||
"planProductsDeselectAll": "Alle abwählen",
|
||||
"recentScans": "Letzte Scans",
|
||||
"seeAllScans": "Alle",
|
||||
"productJobHistoryTitle": "Scan-Verlauf",
|
||||
"jobTypeReceipt": "Kassenbon",
|
||||
"jobTypeProducts": "Produkte",
|
||||
"scanSubmitting": "Wird gesendet...",
|
||||
"processingProducts": "Verarbeitung..."
|
||||
}
|
||||
|
||||
@@ -28,7 +28,9 @@
|
||||
"queuePosition": "Position {position}",
|
||||
"@queuePosition": {
|
||||
"placeholders": {
|
||||
"position": { "type": "int" }
|
||||
"position": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"processing": "Processing...",
|
||||
@@ -116,7 +118,9 @@
|
||||
"noResultsForQuery": "Nothing found for \"{query}\"",
|
||||
"@noResultsForQuery": {
|
||||
"placeholders": {
|
||||
"query": { "type": "String" }
|
||||
"query": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"servingsLabel": "Servings",
|
||||
@@ -125,7 +129,9 @@
|
||||
"planningForDate": "Planning for {date}",
|
||||
"@planningForDate": {
|
||||
"placeholders": {
|
||||
"date": { "type": "String" }
|
||||
"date": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"markAsEaten": "Mark as eaten",
|
||||
@@ -134,7 +140,6 @@
|
||||
"generateWeekSubtitle": "AI will create a menu with breakfast, lunch and dinner for the whole week",
|
||||
"generatingMenu": "Generating menu...",
|
||||
"dayPlannedLabel": "Day planned",
|
||||
|
||||
"planMenuButton": "Plan meals",
|
||||
"planMenuTitle": "What to plan?",
|
||||
"planOptionSingleMeal": "Single meal",
|
||||
@@ -149,16 +154,23 @@
|
||||
"planSelectMealType": "Meal type",
|
||||
"planSelectRange": "Select period",
|
||||
"planGenerateButton": "Plan",
|
||||
"planGenerating": "Generating plan\u2026",
|
||||
"planGenerating": "Generating plan…",
|
||||
"planSuccess": "Menu planned!",
|
||||
"planProductsTitle": "Products for the menu",
|
||||
"planProductsSubtitle": "AI will take the selected products into account when generating recipes",
|
||||
"planProductsEmpty": "No products added",
|
||||
"planProductsEmptyMessage": "Add products you have at home \u2014 AI will suggest recipes from what you already have",
|
||||
"planProductsEmptyMessage": "Add products you have at home — AI will suggest recipes from what you already have",
|
||||
"planProductsAddProducts": "Add products",
|
||||
"planProductsContinue": "Continue",
|
||||
"planProductsSkip": "Skip product selection",
|
||||
"planProductsSkipNoProducts": "Plan without products",
|
||||
"planProductsSelectAll": "Select all",
|
||||
"planProductsDeselectAll": "Deselect all"
|
||||
"planProductsDeselectAll": "Deselect all",
|
||||
"recentScans": "Recent scans",
|
||||
"seeAllScans": "See all",
|
||||
"productJobHistoryTitle": "Scan history",
|
||||
"jobTypeReceipt": "Receipt",
|
||||
"jobTypeProducts": "Products",
|
||||
"scanSubmitting": "Submitting...",
|
||||
"processingProducts": "Processing..."
|
||||
}
|
||||
|
||||
@@ -165,5 +165,12 @@
|
||||
"planProductsSkip": "Omitir selección de productos",
|
||||
"planProductsSkipNoProducts": "Planificar sin productos",
|
||||
"planProductsSelectAll": "Seleccionar todo",
|
||||
"planProductsDeselectAll": "Deseleccionar todo"
|
||||
"planProductsDeselectAll": "Deseleccionar todo",
|
||||
"recentScans": "Escaneos recientes",
|
||||
"seeAllScans": "Ver todos",
|
||||
"productJobHistoryTitle": "Historial de escaneos",
|
||||
"jobTypeReceipt": "Ticket",
|
||||
"jobTypeProducts": "Productos",
|
||||
"scanSubmitting": "Enviando...",
|
||||
"processingProducts": "Procesando..."
|
||||
}
|
||||
|
||||
@@ -165,5 +165,12 @@
|
||||
"planProductsSkip": "Ignorer la sélection des produits",
|
||||
"planProductsSkipNoProducts": "Planifier sans produits",
|
||||
"planProductsSelectAll": "Tout sélectionner",
|
||||
"planProductsDeselectAll": "Tout désélectionner"
|
||||
"planProductsDeselectAll": "Tout désélectionner",
|
||||
"recentScans": "Scans récents",
|
||||
"seeAllScans": "Tout voir",
|
||||
"productJobHistoryTitle": "Historique des scans",
|
||||
"jobTypeReceipt": "Reçu",
|
||||
"jobTypeProducts": "Produits",
|
||||
"scanSubmitting": "Envoi...",
|
||||
"processingProducts": "Traitement..."
|
||||
}
|
||||
|
||||
@@ -165,5 +165,12 @@
|
||||
"planProductsSkip": "उत्पाद चयन छोड़ें",
|
||||
"planProductsSkipNoProducts": "उत्पादों के बिना योजना बनाएं",
|
||||
"planProductsSelectAll": "सभी चुनें",
|
||||
"planProductsDeselectAll": "सभी हटाएं"
|
||||
"planProductsDeselectAll": "सभी हटाएं",
|
||||
"recentScans": "हाल के स्कैन",
|
||||
"seeAllScans": "सभी देखें",
|
||||
"productJobHistoryTitle": "स्कैन इतिहास",
|
||||
"jobTypeReceipt": "रसीद",
|
||||
"jobTypeProducts": "उत्पाद",
|
||||
"scanSubmitting": "सबमिट हो रहा है...",
|
||||
"processingProducts": "प्रोसेस हो रहा है..."
|
||||
}
|
||||
|
||||
@@ -165,5 +165,12 @@
|
||||
"planProductsSkip": "Salta la selezione dei prodotti",
|
||||
"planProductsSkipNoProducts": "Pianifica senza prodotti",
|
||||
"planProductsSelectAll": "Seleziona tutto",
|
||||
"planProductsDeselectAll": "Deseleziona tutto"
|
||||
"planProductsDeselectAll": "Deseleziona tutto",
|
||||
"recentScans": "Scansioni recenti",
|
||||
"seeAllScans": "Vedi tutto",
|
||||
"productJobHistoryTitle": "Cronologia scansioni",
|
||||
"jobTypeReceipt": "Scontrino",
|
||||
"jobTypeProducts": "Prodotti",
|
||||
"scanSubmitting": "Invio...",
|
||||
"processingProducts": "Elaborazione..."
|
||||
}
|
||||
|
||||
@@ -165,5 +165,12 @@
|
||||
"planProductsSkip": "食材選択をスキップ",
|
||||
"planProductsSkipNoProducts": "食材なしでプランニング",
|
||||
"planProductsSelectAll": "すべて選択",
|
||||
"planProductsDeselectAll": "すべて解除"
|
||||
"planProductsDeselectAll": "すべて解除",
|
||||
"recentScans": "最近のスキャン",
|
||||
"seeAllScans": "すべて表示",
|
||||
"productJobHistoryTitle": "スキャン履歴",
|
||||
"jobTypeReceipt": "レシート",
|
||||
"jobTypeProducts": "商品",
|
||||
"scanSubmitting": "送信中...",
|
||||
"processingProducts": "処理中..."
|
||||
}
|
||||
|
||||
@@ -165,5 +165,12 @@
|
||||
"planProductsSkip": "재료 선택 건너뛰기",
|
||||
"planProductsSkipNoProducts": "재료 없이 계획하기",
|
||||
"planProductsSelectAll": "모두 선택",
|
||||
"planProductsDeselectAll": "모두 해제"
|
||||
"planProductsDeselectAll": "모두 해제",
|
||||
"recentScans": "최근 스캔",
|
||||
"seeAllScans": "전체 보기",
|
||||
"productJobHistoryTitle": "스캔 기록",
|
||||
"jobTypeReceipt": "영수증",
|
||||
"jobTypeProducts": "제품",
|
||||
"scanSubmitting": "제출 중...",
|
||||
"processingProducts": "처리 중..."
|
||||
}
|
||||
|
||||
@@ -987,6 +987,48 @@ abstract class AppLocalizations {
|
||||
/// In en, this message translates to:
|
||||
/// **'Deselect all'**
|
||||
String get planProductsDeselectAll;
|
||||
|
||||
/// No description provided for @recentScans.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Recent scans'**
|
||||
String get recentScans;
|
||||
|
||||
/// No description provided for @seeAllScans.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'See all'**
|
||||
String get seeAllScans;
|
||||
|
||||
/// No description provided for @productJobHistoryTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Scan history'**
|
||||
String get productJobHistoryTitle;
|
||||
|
||||
/// No description provided for @jobTypeReceipt.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Receipt'**
|
||||
String get jobTypeReceipt;
|
||||
|
||||
/// No description provided for @jobTypeProducts.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Products'**
|
||||
String get jobTypeProducts;
|
||||
|
||||
/// No description provided for @scanSubmitting.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Submitting...'**
|
||||
String get scanSubmitting;
|
||||
|
||||
/// No description provided for @processingProducts.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Processing...'**
|
||||
String get processingProducts;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
||||
@@ -452,4 +452,25 @@ class AppLocalizationsAr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get planProductsDeselectAll => 'إلغاء تحديد الكل';
|
||||
|
||||
@override
|
||||
String get recentScans => 'عمليات المسح الأخيرة';
|
||||
|
||||
@override
|
||||
String get seeAllScans => 'عرض الكل';
|
||||
|
||||
@override
|
||||
String get productJobHistoryTitle => 'سجل المسح';
|
||||
|
||||
@override
|
||||
String get jobTypeReceipt => 'إيصال';
|
||||
|
||||
@override
|
||||
String get jobTypeProducts => 'منتجات';
|
||||
|
||||
@override
|
||||
String get scanSubmitting => 'جارٍ الإرسال...';
|
||||
|
||||
@override
|
||||
String get processingProducts => 'جارٍ المعالجة...';
|
||||
}
|
||||
|
||||
@@ -454,4 +454,25 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get planProductsDeselectAll => 'Alle abwählen';
|
||||
|
||||
@override
|
||||
String get recentScans => 'Letzte Scans';
|
||||
|
||||
@override
|
||||
String get seeAllScans => 'Alle';
|
||||
|
||||
@override
|
||||
String get productJobHistoryTitle => 'Scan-Verlauf';
|
||||
|
||||
@override
|
||||
String get jobTypeReceipt => 'Kassenbon';
|
||||
|
||||
@override
|
||||
String get jobTypeProducts => 'Produkte';
|
||||
|
||||
@override
|
||||
String get scanSubmitting => 'Wird gesendet...';
|
||||
|
||||
@override
|
||||
String get processingProducts => 'Verarbeitung...';
|
||||
}
|
||||
|
||||
@@ -452,4 +452,25 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get planProductsDeselectAll => 'Deselect all';
|
||||
|
||||
@override
|
||||
String get recentScans => 'Recent scans';
|
||||
|
||||
@override
|
||||
String get seeAllScans => 'See all';
|
||||
|
||||
@override
|
||||
String get productJobHistoryTitle => 'Scan history';
|
||||
|
||||
@override
|
||||
String get jobTypeReceipt => 'Receipt';
|
||||
|
||||
@override
|
||||
String get jobTypeProducts => 'Products';
|
||||
|
||||
@override
|
||||
String get scanSubmitting => 'Submitting...';
|
||||
|
||||
@override
|
||||
String get processingProducts => 'Processing...';
|
||||
}
|
||||
|
||||
@@ -454,4 +454,25 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get planProductsDeselectAll => 'Deseleccionar todo';
|
||||
|
||||
@override
|
||||
String get recentScans => 'Escaneos recientes';
|
||||
|
||||
@override
|
||||
String get seeAllScans => 'Ver todos';
|
||||
|
||||
@override
|
||||
String get productJobHistoryTitle => 'Historial de escaneos';
|
||||
|
||||
@override
|
||||
String get jobTypeReceipt => 'Ticket';
|
||||
|
||||
@override
|
||||
String get jobTypeProducts => 'Productos';
|
||||
|
||||
@override
|
||||
String get scanSubmitting => 'Enviando...';
|
||||
|
||||
@override
|
||||
String get processingProducts => 'Procesando...';
|
||||
}
|
||||
|
||||
@@ -455,4 +455,25 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get planProductsDeselectAll => 'Tout désélectionner';
|
||||
|
||||
@override
|
||||
String get recentScans => 'Scans récents';
|
||||
|
||||
@override
|
||||
String get seeAllScans => 'Tout voir';
|
||||
|
||||
@override
|
||||
String get productJobHistoryTitle => 'Historique des scans';
|
||||
|
||||
@override
|
||||
String get jobTypeReceipt => 'Reçu';
|
||||
|
||||
@override
|
||||
String get jobTypeProducts => 'Produits';
|
||||
|
||||
@override
|
||||
String get scanSubmitting => 'Envoi...';
|
||||
|
||||
@override
|
||||
String get processingProducts => 'Traitement...';
|
||||
}
|
||||
|
||||
@@ -453,4 +453,25 @@ class AppLocalizationsHi extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get planProductsDeselectAll => 'सभी हटाएं';
|
||||
|
||||
@override
|
||||
String get recentScans => 'हाल के स्कैन';
|
||||
|
||||
@override
|
||||
String get seeAllScans => 'सभी देखें';
|
||||
|
||||
@override
|
||||
String get productJobHistoryTitle => 'स्कैन इतिहास';
|
||||
|
||||
@override
|
||||
String get jobTypeReceipt => 'रसीद';
|
||||
|
||||
@override
|
||||
String get jobTypeProducts => 'उत्पाद';
|
||||
|
||||
@override
|
||||
String get scanSubmitting => 'सबमिट हो रहा है...';
|
||||
|
||||
@override
|
||||
String get processingProducts => 'प्रोसेस हो रहा है...';
|
||||
}
|
||||
|
||||
@@ -454,4 +454,25 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get planProductsDeselectAll => 'Deseleziona tutto';
|
||||
|
||||
@override
|
||||
String get recentScans => 'Scansioni recenti';
|
||||
|
||||
@override
|
||||
String get seeAllScans => 'Vedi tutto';
|
||||
|
||||
@override
|
||||
String get productJobHistoryTitle => 'Cronologia scansioni';
|
||||
|
||||
@override
|
||||
String get jobTypeReceipt => 'Scontrino';
|
||||
|
||||
@override
|
||||
String get jobTypeProducts => 'Prodotti';
|
||||
|
||||
@override
|
||||
String get scanSubmitting => 'Invio...';
|
||||
|
||||
@override
|
||||
String get processingProducts => 'Elaborazione...';
|
||||
}
|
||||
|
||||
@@ -449,4 +449,25 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get planProductsDeselectAll => 'すべて解除';
|
||||
|
||||
@override
|
||||
String get recentScans => '最近のスキャン';
|
||||
|
||||
@override
|
||||
String get seeAllScans => 'すべて表示';
|
||||
|
||||
@override
|
||||
String get productJobHistoryTitle => 'スキャン履歴';
|
||||
|
||||
@override
|
||||
String get jobTypeReceipt => 'レシート';
|
||||
|
||||
@override
|
||||
String get jobTypeProducts => '商品';
|
||||
|
||||
@override
|
||||
String get scanSubmitting => '送信中...';
|
||||
|
||||
@override
|
||||
String get processingProducts => '処理中...';
|
||||
}
|
||||
|
||||
@@ -449,4 +449,25 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get planProductsDeselectAll => '모두 해제';
|
||||
|
||||
@override
|
||||
String get recentScans => '최근 스캔';
|
||||
|
||||
@override
|
||||
String get seeAllScans => '전체 보기';
|
||||
|
||||
@override
|
||||
String get productJobHistoryTitle => '스캔 기록';
|
||||
|
||||
@override
|
||||
String get jobTypeReceipt => '영수증';
|
||||
|
||||
@override
|
||||
String get jobTypeProducts => '제품';
|
||||
|
||||
@override
|
||||
String get scanSubmitting => '제출 중...';
|
||||
|
||||
@override
|
||||
String get processingProducts => '처리 중...';
|
||||
}
|
||||
|
||||
@@ -454,4 +454,25 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get planProductsDeselectAll => 'Desmarcar tudo';
|
||||
|
||||
@override
|
||||
String get recentScans => 'Scans recentes';
|
||||
|
||||
@override
|
||||
String get seeAllScans => 'Ver tudo';
|
||||
|
||||
@override
|
||||
String get productJobHistoryTitle => 'Histórico de scans';
|
||||
|
||||
@override
|
||||
String get jobTypeReceipt => 'Recibo';
|
||||
|
||||
@override
|
||||
String get jobTypeProducts => 'Produtos';
|
||||
|
||||
@override
|
||||
String get scanSubmitting => 'Enviando...';
|
||||
|
||||
@override
|
||||
String get processingProducts => 'Processando...';
|
||||
}
|
||||
|
||||
@@ -452,4 +452,25 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get planProductsDeselectAll => 'Снять всё';
|
||||
|
||||
@override
|
||||
String get recentScans => 'Последние сканирования';
|
||||
|
||||
@override
|
||||
String get seeAllScans => 'Все';
|
||||
|
||||
@override
|
||||
String get productJobHistoryTitle => 'История сканирования';
|
||||
|
||||
@override
|
||||
String get jobTypeReceipt => 'Чек';
|
||||
|
||||
@override
|
||||
String get jobTypeProducts => 'Продукты';
|
||||
|
||||
@override
|
||||
String get scanSubmitting => 'Отправка...';
|
||||
|
||||
@override
|
||||
String get processingProducts => 'Обработка...';
|
||||
}
|
||||
|
||||
@@ -448,4 +448,25 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get planProductsDeselectAll => '取消全选';
|
||||
|
||||
@override
|
||||
String get recentScans => '最近扫描';
|
||||
|
||||
@override
|
||||
String get seeAllScans => '全部';
|
||||
|
||||
@override
|
||||
String get productJobHistoryTitle => '扫描历史';
|
||||
|
||||
@override
|
||||
String get jobTypeReceipt => '收据';
|
||||
|
||||
@override
|
||||
String get jobTypeProducts => '产品';
|
||||
|
||||
@override
|
||||
String get scanSubmitting => '提交中...';
|
||||
|
||||
@override
|
||||
String get processingProducts => '处理中...';
|
||||
}
|
||||
|
||||
@@ -165,5 +165,12 @@
|
||||
"planProductsSkip": "Pular seleção de produtos",
|
||||
"planProductsSkipNoProducts": "Planejar sem produtos",
|
||||
"planProductsSelectAll": "Selecionar tudo",
|
||||
"planProductsDeselectAll": "Desmarcar tudo"
|
||||
"planProductsDeselectAll": "Desmarcar tudo",
|
||||
"recentScans": "Scans recentes",
|
||||
"seeAllScans": "Ver tudo",
|
||||
"productJobHistoryTitle": "Histórico de scans",
|
||||
"jobTypeReceipt": "Recibo",
|
||||
"jobTypeProducts": "Produtos",
|
||||
"scanSubmitting": "Enviando...",
|
||||
"processingProducts": "Processando..."
|
||||
}
|
||||
|
||||
@@ -165,5 +165,12 @@
|
||||
"planProductsSkip": "Пропустить выбор продуктов",
|
||||
"planProductsSkipNoProducts": "Планировать без продуктов",
|
||||
"planProductsSelectAll": "Выбрать все",
|
||||
"planProductsDeselectAll": "Снять всё"
|
||||
"planProductsDeselectAll": "Снять всё",
|
||||
"recentScans": "Последние сканирования",
|
||||
"seeAllScans": "Все",
|
||||
"productJobHistoryTitle": "История сканирования",
|
||||
"jobTypeReceipt": "Чек",
|
||||
"jobTypeProducts": "Продукты",
|
||||
"scanSubmitting": "Отправка...",
|
||||
"processingProducts": "Обработка..."
|
||||
}
|
||||
|
||||
@@ -165,5 +165,12 @@
|
||||
"planProductsSkip": "跳过食材选择",
|
||||
"planProductsSkipNoProducts": "不选食材直接规划",
|
||||
"planProductsSelectAll": "全选",
|
||||
"planProductsDeselectAll": "取消全选"
|
||||
"planProductsDeselectAll": "取消全选",
|
||||
"recentScans": "最近扫描",
|
||||
"seeAllScans": "全部",
|
||||
"productJobHistoryTitle": "扫描历史",
|
||||
"jobTypeReceipt": "收据",
|
||||
"jobTypeProducts": "产品",
|
||||
"scanSubmitting": "提交中...",
|
||||
"processingProducts": "处理中..."
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user