i8n누락 사항 추가 적용

This commit is contained in:
JiWoong Sul
2025-11-17 19:26:14 +09:00
parent 2cd46a303e
commit b22df5daf3
5 changed files with 146 additions and 79 deletions

View File

@@ -11,6 +11,9 @@
"save": "Save", "save": "Save",
"cancel": "Cancel", "cancel": "Cancel",
"delete": "Delete", "delete": "Delete",
"deleteSubscriptionTitle": "Delete Subscription",
"deleteSubscriptionMessage": "Are you sure you want to delete @ subscription?",
"deleteIrreversibleWarning": "This action cannot be undone",
"edit": "Edit", "edit": "Edit",
"totalSubscriptions": "Total Subscriptions", "totalSubscriptions": "Total Subscriptions",
"totalMonthlyExpense": "Total Monthly Expense", "totalMonthlyExpense": "Total Monthly Expense",
@@ -267,6 +270,9 @@
"save": "저장", "save": "저장",
"cancel": "취소", "cancel": "취소",
"delete": "삭제", "delete": "삭제",
"deleteSubscriptionTitle": "구독 삭제",
"deleteSubscriptionMessage": "정말로 @ 구독을 삭제하시겠습니까?",
"deleteIrreversibleWarning": "이 작업은 되돌릴 수 없습니다",
"edit": "수정", "edit": "수정",
"totalSubscriptions": "총 구독", "totalSubscriptions": "총 구독",
"totalMonthlyExpense": "이번 달 총 지출", "totalMonthlyExpense": "이번 달 총 지출",
@@ -523,6 +529,9 @@
"save": "保存", "save": "保存",
"cancel": "キャンセル", "cancel": "キャンセル",
"delete": "削除", "delete": "削除",
"deleteSubscriptionTitle": "サブスクリプション削除",
"deleteSubscriptionMessage": "本当に@のサブスクリプションを削除しますか?",
"deleteIrreversibleWarning": "この操作は取り消せません",
"edit": "編集", "edit": "編集",
"totalSubscriptions": "総サブスクリプション", "totalSubscriptions": "総サブスクリプション",
"totalMonthlyExpense": "今月の総支出", "totalMonthlyExpense": "今月の総支出",
@@ -768,6 +777,9 @@
"save": "保存", "save": "保存",
"cancel": "取消", "cancel": "取消",
"delete": "删除", "delete": "删除",
"deleteSubscriptionTitle": "删除订阅",
"deleteSubscriptionMessage": "确定要删除@订阅吗?",
"deleteIrreversibleWarning": "此操作无法撤销",
"edit": "编辑", "edit": "编辑",
"totalSubscriptions": "订阅总数", "totalSubscriptions": "订阅总数",
"totalMonthlyExpense": "本月总支出", "totalMonthlyExpense": "本月总支出",

View File

@@ -36,6 +36,16 @@ class AppLocalizations {
String get save => _localizedStrings['save'] ?? 'Save'; String get save => _localizedStrings['save'] ?? 'Save';
String get cancel => _localizedStrings['cancel'] ?? 'Cancel'; String get cancel => _localizedStrings['cancel'] ?? 'Cancel';
String get delete => _localizedStrings['delete'] ?? 'Delete'; String get delete => _localizedStrings['delete'] ?? 'Delete';
String get deleteSubscriptionTitle =>
_localizedStrings['deleteSubscriptionTitle'] ?? 'Delete Subscription';
String get deleteSubscriptionMessageTemplate =>
_localizedStrings['deleteSubscriptionMessage'] ??
'Are you sure you want to delete @ subscription?';
String deleteSubscriptionMessage(String serviceName) =>
deleteSubscriptionMessageTemplate.replaceAll('@', serviceName);
String get deleteIrreversibleWarning =>
_localizedStrings['deleteIrreversibleWarning'] ??
'This action cannot be undone';
String get edit => _localizedStrings['edit'] ?? 'Edit'; String get edit => _localizedStrings['edit'] ?? 'Edit';
String get totalSubscriptions => String get totalSubscriptions =>
_localizedStrings['totalSubscriptions'] ?? 'Total Subscriptions'; _localizedStrings['totalSubscriptions'] ?? 'Total Subscriptions';

View File

@@ -233,24 +233,32 @@ class _SubscriptionPieChartCardState extends State<SubscriptionPieChartCard> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ThemedText.headline( Expanded(
child: ThemedText.headline(
text: AppLocalizations.of(context) text: AppLocalizations.of(context)
.subscriptionServiceRatio, .subscriptionServiceRatio,
style: const TextStyle( style: const TextStyle(
fontSize: 18, fontSize: 18,
), ),
), ),
FutureBuilder<String>( ),
Flexible(
child: FutureBuilder<String>(
future: CurrencyUtil.getExchangeRateInfoForLocale( future: CurrencyUtil.getExchangeRateInfoForLocale(
context context
.watch<LocaleProvider>() .watch<LocaleProvider>()
.locale .locale
.languageCode), .languageCode),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data!.isNotEmpty) { if (snapshot.hasData &&
return Container( snapshot.data!.isNotEmpty) {
return Padding(
padding: const EdgeInsets.only(left: 12),
child: Align(
alignment: Alignment.topRight,
child: Container(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 8, horizontal: 8,
vertical: 4, vertical: 4,
@@ -269,14 +277,15 @@ class _SubscriptionPieChartCardState extends State<SubscriptionPieChartCard> {
width: 1, width: 1,
), ),
), ),
child: Text( child: ThemedText(
AppLocalizations.of(context) AppLocalizations.of(context)
.exchangeRateFormat(snapshot.data!), .exchangeRateFormat(snapshot.data!),
style: TextStyle( style: const TextStyle(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: ),
Theme.of(context).colorScheme.primary, textAlign: TextAlign.right,
),
), ),
), ),
); );
@@ -284,6 +293,7 @@ class _SubscriptionPieChartCardState extends State<SubscriptionPieChartCard> {
return const SizedBox.shrink(); return const SizedBox.shrink();
}, },
), ),
),
], ],
), ),
const SizedBox(height: 8), const SizedBox(height: 8),

View File

@@ -265,23 +265,39 @@ class DetailHeaderSection extends StatelessWidget {
final loc = AppLocalizations.of(context); final loc = AppLocalizations.of(context);
switch (cycle.toLowerCase()) { switch (cycle.toLowerCase()) {
case '매월': case '매월':
case '월간':
case 'monthly': case 'monthly':
case '毎月': case '毎月':
case '月付':
case '月間':
case '每月': case '每月':
return loc.billingCycleMonthly; return loc.billingCycleMonthly;
case '분기별': case '분기별':
case '분기':
case 'quarterly': case 'quarterly':
case 'quarter':
case '季付':
case '季度付':
case '四半期': case '四半期':
case '每季度': case '每季度':
return loc.billingCycleQuarterly; return loc.billingCycleQuarterly;
case '반기별': case '반기별':
case 'half-yearly': case 'half-yearly':
case 'half yearly':
case 'semiannual':
case 'semi-annual':
case '半年付':
case '半年払い':
case '半年ごと': case '半年ごと':
case '每半年': case '每半年':
return loc.billingCycleHalfYearly; return loc.billingCycleHalfYearly;
case '매년': case '매년':
case '연간':
case 'yearly': case 'yearly':
case 'annual':
case 'annually':
case '年間': case '年間':
case '年付':
case '每年': case '每年':
return loc.billingCycleYearly; return loc.billingCycleYearly;
default: default:

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
// Material 3 기반 다이얼로그 // Material 3 기반 다이얼로그
import '../common/buttons/primary_button.dart'; import '../common/buttons/primary_button.dart';
import '../common/buttons/secondary_button.dart'; import '../common/buttons/secondary_button.dart';
import '../../l10n/app_localizations.dart';
/// 삭제 확인 다이얼로그 /// 삭제 확인 다이얼로그
/// 글래스모피즘 스타일의 삭제 확인 다이얼로그입니다. /// 글래스모피즘 스타일의 삭제 확인 다이얼로그입니다.
@@ -15,8 +16,20 @@ class DeleteConfirmationDialog extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final loc = AppLocalizations.of(context);
final textThemeColor = Theme.of(context).colorScheme;
final baseMessageStyle = TextStyle(
fontSize: 16,
color: textThemeColor.onSurfaceVariant,
height: 1.5,
);
final highlightStyle = baseMessageStyle.copyWith(
fontWeight: FontWeight.w600,
color: textThemeColor.onSurface,
);
return Dialog( return Dialog(
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: textThemeColor.surface,
elevation: 6, elevation: 6,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
child: Container( child: Container(
@@ -44,11 +57,11 @@ class DeleteConfirmationDialog extends StatelessWidget {
// 타이틀 // 타이틀
Text( Text(
'구독 삭제', loc.deleteSubscriptionTitle,
style: TextStyle( style: TextStyle(
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: Theme.of(context).colorScheme.onSurface, color: textThemeColor.onSurface,
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
@@ -57,22 +70,12 @@ class DeleteConfirmationDialog extends StatelessWidget {
RichText( RichText(
textAlign: TextAlign.center, textAlign: TextAlign.center,
text: TextSpan( text: TextSpan(
style: TextStyle( style: baseMessageStyle,
fontSize: 16, children: _buildLocalizedMessageSpans(
color: Theme.of(context).colorScheme.onSurfaceVariant, loc.deleteSubscriptionMessageTemplate,
height: 1.5, serviceName,
highlightStyle,
), ),
children: [
const TextSpan(text: '정말로 '),
TextSpan(
text: serviceName,
style: TextStyle(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
),
const TextSpan(text: ' 구독을\n삭제하시겠습니까?'),
],
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -84,14 +87,10 @@ class DeleteConfirmationDialog extends StatelessWidget {
vertical: 12, vertical: 12,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: color: textThemeColor.error.withValues(alpha: 0.05),
Theme.of(context).colorScheme.error.withValues(alpha: 0.05),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all( border: Border.all(
color: Theme.of(context) color: textThemeColor.error.withValues(alpha: 0.2),
.colorScheme
.error
.withValues(alpha: 0.2),
width: 1, width: 1,
), ),
), ),
@@ -100,18 +99,15 @@ class DeleteConfirmationDialog extends StatelessWidget {
children: [ children: [
Icon( Icon(
Icons.warning_amber_rounded, Icons.warning_amber_rounded,
color: Theme.of(context) color: textThemeColor.error.withValues(alpha: 0.8),
.colorScheme
.error
.withValues(alpha: 0.8),
size: 20, size: 20,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
'이 작업은 되돌릴 수 없습니다', loc.deleteIrreversibleWarning,
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: Theme.of(context).colorScheme.error, color: textThemeColor.error,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
@@ -125,7 +121,7 @@ class DeleteConfirmationDialog extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: SecondaryButton( child: SecondaryButton(
text: '취소', text: loc.cancel,
onPressed: () { onPressed: () {
Navigator.of(context).pop(false); Navigator.of(context).pop(false);
}, },
@@ -134,12 +130,12 @@ class DeleteConfirmationDialog extends StatelessWidget {
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: PrimaryButton( child: PrimaryButton(
text: '삭제', text: loc.delete,
icon: Icons.delete_rounded, icon: Icons.delete_rounded,
onPressed: () { onPressed: () {
Navigator.of(context).pop(true); Navigator.of(context).pop(true);
}, },
backgroundColor: Theme.of(context).colorScheme.error, backgroundColor: textThemeColor.error,
), ),
), ),
], ],
@@ -166,4 +162,27 @@ class DeleteConfirmationDialog extends StatelessWidget {
return result ?? false; return result ?? false;
} }
List<TextSpan> _buildLocalizedMessageSpans(
String template,
String serviceName,
TextStyle highlightStyle,
) {
final parts = template.split('@');
if (parts.length == 1) {
return [TextSpan(text: template)];
}
final spans = <TextSpan>[];
for (int i = 0; i < parts.length; i++) {
final segment = parts[i];
if (segment.isNotEmpty) {
spans.add(TextSpan(text: segment));
}
if (i < parts.length - 1) {
spans.add(TextSpan(text: serviceName, style: highlightStyle));
}
}
return spans;
}
} }