feat: 다국어 지원 및 다중 통화 환율 변환 기능 확대

- ExchangeRateService에 JPY, CNY 환율 지원 추가
- 구독 서비스별 다국어 표시 이름 지원
- 분석 화면 차트 및 UI/UX 개선
- 설정 화면 전면 리팩토링
- SMS 스캔 기능 사용성 개선
- 전체 앱 다국어 번역 확대

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
JiWoong Sul
2025-07-16 17:34:32 +09:00
parent 4d1c0f5dab
commit 0f0b02bf08
55 changed files with 4100 additions and 1197 deletions

View File

@@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import '../../controllers/add_subscription_controller.dart';
import '../../providers/category_provider.dart';
import '../../l10n/app_localizations.dart';
import '../common/form_fields/base_text_field.dart';
import '../common/form_fields/currency_input_field.dart';
import '../common/form_fields/date_picker_field.dart';
@@ -67,9 +68,9 @@ class AddSubscriptionForm extends StatelessWidget {
),
),
const SizedBox(width: 12),
const Text(
'서비스 정보',
style: TextStyle(
Text(
AppLocalizations.of(context).serviceInfo,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
letterSpacing: -0.5,
@@ -84,15 +85,15 @@ class AddSubscriptionForm extends StatelessWidget {
BaseTextField(
controller: controller.serviceNameController,
focusNode: controller.serviceNameFocus,
label: '서비스명',
hintText: '예: Netflix, Spotify',
label: AppLocalizations.of(context).labelServiceName,
hintText: AppLocalizations.of(context).hintServiceName,
textInputAction: TextInputAction.next,
onEditingComplete: () {
controller.monthlyCostFocus.requestFocus();
},
validator: (value) {
if (value == null || value.isEmpty) {
return '서비스명을 입력해주세요';
return AppLocalizations.of(context).serviceNameRequired;
}
return null;
},
@@ -108,7 +109,7 @@ class AddSubscriptionForm extends StatelessWidget {
child: CurrencyInputField(
controller: controller.monthlyCostController,
currency: controller.currency,
label: '월 지출',
label: AppLocalizations.of(context).labelMonthlyExpense,
focusNode: controller.monthlyCostFocus,
textInputAction: TextInputAction.next,
onEditingComplete: () {
@@ -116,7 +117,7 @@ class AddSubscriptionForm extends StatelessWidget {
},
validator: (value) {
if (value == null || value.isEmpty) {
return '금액을 입력해주세요';
return AppLocalizations.of(context).amountRequired;
}
return null;
},
@@ -127,9 +128,9 @@ class AddSubscriptionForm extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'통화',
style: TextStyle(
Text(
AppLocalizations.of(context).currency,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
@@ -155,9 +156,10 @@ class AddSubscriptionForm extends StatelessWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'결제 주기',
style: TextStyle(
Text(
AppLocalizations.of(context).billingCycle,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: 16,
fontWeight: FontWeight.w600,
),
@@ -185,7 +187,7 @@ class AddSubscriptionForm extends StatelessWidget {
controller.nextBillingDate = date;
});
},
label: '다음 결제일',
label: AppLocalizations.of(context).nextBillingDate,
firstDate: DateTime.now(),
lastDate: DateTime.now().add(const Duration(days: 365 * 2)),
primaryColor: controller.gradientColors[0],
@@ -196,8 +198,8 @@ class AddSubscriptionForm extends StatelessWidget {
BaseTextField(
controller: controller.websiteUrlController,
focusNode: controller.websiteUrlFocus,
label: '웹사이트 URL (선택)',
hintText: 'https://example.com',
label: AppLocalizations.of(context).websiteUrlOptional,
hintText: AppLocalizations.of(context).hintWebsiteUrl,
keyboardType: TextInputType.url,
prefixIcon: Icon(
Icons.link_rounded,
@@ -212,9 +214,9 @@ class AddSubscriptionForm extends StatelessWidget {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'카테고리',
style: TextStyle(
Text(
AppLocalizations.of(context).category,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
@@ -243,4 +245,3 @@ class AddSubscriptionForm extends StatelessWidget {
);
}
}