Files
food-ai/client/lib/l10n/app_localizations_ko.dart
dbastrikin c7317c4335 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>
2026-03-23 23:01:30 +02:00

474 lines
9.7 KiB
Dart

// 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 => '레시피';
@override
String get addFromReceiptOrPhoto => '영수증 또는 사진으로 추가';
@override
String get chooseMethod => '방법 선택';
@override
String get photoReceipt => '영수증 촬영';
@override
String get photoReceiptSubtitle => '영수증의 모든 상품 인식';
@override
String get photoProducts => '식품 촬영';
@override
String get photoProductsSubtitle => '냉장고, 테이블, 선반 — 최대 3장';
@override
String get addPackagedFood => '포장 식품 추가';
@override
String get scanBarcode => '바코드 스캔';
@override
String get portionWeightG => '1회 제공량 (g)';
@override
String get productNotFound => '제품을 찾을 수 없습니다';
@override
String get enterManually => '직접 입력';
@override
String get perHundredG => '100g당';
@override
String get searchFoodHint => '식품 및 요리 검색...';
@override
String get recentlyUsedLabel => '최근 사용';
@override
String get productsSection => '식품';
@override
String get dishesSection => '요리';
@override
String noResultsForQuery(String query) {
return '\"$query\"에 대한 결과 없음';
}
@override
String get servingsLabel => '인분';
@override
String get addToDiary => '일기에 추가';
@override
String get scanDishPhoto => '사진 스캔';
@override
String planningForDate(String date) {
return '';
}
@override
String get markAsEaten => '먹은 것으로 표시';
@override
String get plannedMealLabel => '계획됨';
@override
String get generateWeekLabel => '주간 계획하기';
@override
String get generateWeekSubtitle => 'AI가 한 주 동안 아침, 점심, 저녁 식사 메뉴를 만들어 드립니다';
@override
String get generatingMenu => '메뉴 생성 중...';
@override
String get dayPlannedLabel => '일일 계획 완료';
@override
String get planMenuButton => '식사 계획하기';
@override
String get planMenuTitle => '무엇을 계획하시겠어요?';
@override
String get planOptionSingleMeal => '식사 1회';
@override
String get planOptionSingleMealDesc => '날짜와 식사 유형 선택';
@override
String get planOptionDay => '하루';
@override
String get planOptionDayDesc => '하루 전체 식사';
@override
String get planOptionDays => '며칠';
@override
String get planOptionDaysDesc => '기간 직접 설정';
@override
String get planOptionWeek => '일주일';
@override
String get planOptionWeekDesc => '7일 한 번에';
@override
String get planSelectDate => '날짜 선택';
@override
String get planSelectMealType => '식사 유형';
@override
String get planSelectRange => '기간 선택';
@override
String get planGenerateButton => '계획하기';
@override
String get planGenerating => '플랜 생성 중…';
@override
String get planSuccess => '메뉴가 계획되었습니다!';
@override
String get planProductsTitle => '메뉴 재료';
@override
String get planProductsSubtitle => 'AI가 레시피 생성 시 선택한 재료를 고려합니다';
@override
String get planProductsEmpty => '추가된 재료가 없습니다';
@override
String get planProductsEmptyMessage =>
'집에 있는 재료를 추가하세요 — AI가 이미 있는 재료로 레시피를 제안합니다';
@override
String get planProductsAddProducts => '재료 추가';
@override
String get planProductsContinue => '계속';
@override
String get planProductsSkip => '재료 선택 건너뛰기';
@override
String get planProductsSkipNoProducts => '재료 없이 계획하기';
@override
String get planProductsSelectAll => '모두 선택';
@override
String get planProductsDeselectAll => '모두 해제';
@override
String get recentScans => '최근 스캔';
@override
String get seeAllScans => '전체 보기';
@override
String get productJobHistoryTitle => '스캔 기록';
@override
String get jobTypeReceipt => '영수증';
@override
String get jobTypeProducts => '제품';
@override
String get scanSubmitting => '제출 중...';
@override
String get processingProducts => '처리 중...';
}