i8n과 광고 수정

This commit is contained in:
JiWoong Sul
2025-12-07 21:14:54 +09:00
parent 64da0c5fd3
commit bac4acf9a3
25 changed files with 640 additions and 382 deletions

View File

@@ -635,6 +635,8 @@ class NotificationService {
try {
final expirationDate = subscription.nextBillingDate;
final reminderDate = expirationDate.subtract(const Duration(days: 7));
final ctx = navigatorKey.currentContext;
final loc = ctx != null ? AppLocalizations.of(ctx) : null;
// tz.local 초기화 확인 및 재시도
tz.Location location;
@@ -656,8 +658,9 @@ class NotificationService {
await _notifications.zonedSchedule(
('${subscription.id}_expiration').hashCode,
'구독 만료 예정 알림',
'${subscription.serviceName} 구독이 7일 후 만료됩니다.',
loc?.expirationReminder ?? _paymentReminderTitle(_getLocaleCode()),
loc?.expirationReminderBody(subscription.serviceName, 7) ??
'${subscription.serviceName} subscription expires in 7 days.',
tz.TZDateTime.from(reminderDate, location),
const NotificationDetails(
android: AndroidNotificationDetails(
@@ -849,11 +852,14 @@ class NotificationService {
if (_isWeb || !_initialized) return;
try {
final locale = _getLocaleCode();
final title = _paymentReminderTitle(locale);
final ctx = navigatorKey.currentContext;
final loc = ctx != null ? AppLocalizations.of(ctx) : null;
final title = loc?.paymentReminder ?? _paymentReminderTitle(locale);
final amountText =
await CurrencyUtil.formatAmountWithLocale(10000.0, 'KRW', locale);
final body = '테스트 구독 • $amountText';
final body = loc?.testSubscriptionBody(amountText) ??
'Test subscription • $amountText';
await _notifications.show(
DateTime.now().millisecondsSinceEpoch.remainder(1 << 31),
@@ -880,7 +886,11 @@ class NotificationService {
}
static String getNotificationBody(String serviceName, double amount) {
return '$serviceName 구독료 ${amount.toStringAsFixed(0)}원이 결제되었습니다.';
final ctx = navigatorKey.currentContext;
final loc = ctx != null ? AppLocalizations.of(ctx) : null;
final amountText = amount.toStringAsFixed(0);
return loc?.paymentChargeNotification(serviceName, amountText) ??
'$serviceName subscription charge $amountText was completed.';
}
static Future<String> _buildPaymentBody(
@@ -925,6 +935,10 @@ class NotificationService {
}
static String _paymentReminderTitle(String locale) {
final ctx = navigatorKey.currentContext;
if (ctx != null) {
return AppLocalizations.of(ctx).paymentReminder;
}
switch (locale) {
case 'ko':
return '결제 예정 알림';

View File

@@ -10,6 +10,8 @@ import '../utils/platform_helper.dart';
import '../utils/business_day_util.dart';
import '../services/sms_scan/sms_scan_result.dart';
import '../models/payment_card_suggestion.dart';
import '../l10n/app_localizations.dart';
import '../navigator_key.dart';
class SmsScanner {
final SmsQuery _query = SmsQuery();
@@ -82,7 +84,9 @@ class SmsScanner {
return subscriptions;
} catch (e) {
Log.e('SmsScanner: 예외 발생', e);
throw Exception('SMS 스캔 중 오류 발생: $e');
final loc = _loc();
throw Exception(loc?.smsScanErrorWithMessage(e.toString()) ??
'Error occurred during SMS scan: $e');
}
}
@@ -116,7 +120,13 @@ class SmsScanner {
SmsScanResult? _parseSms(Map<String, dynamic> sms, int repeatCount) {
try {
final serviceName = sms['serviceName'] as String? ?? '알 수 없는 서비스';
final loc = _loc();
final unknownLabel = loc?.unknownService ?? 'Unknown service';
final serviceNameRaw = sms['serviceName'] as String?;
final serviceName =
(serviceNameRaw == null || serviceNameRaw.trim().isEmpty)
? unknownLabel
: serviceNameRaw;
final monthlyCost = (sms['monthlyCost'] as num?)?.toDouble() ?? 0.0;
final billingCycle = SubscriptionModel.normalizeBillingCycle(
sms['billingCycle'] as String? ?? 'monthly');
@@ -196,8 +206,9 @@ class SmsScanner {
if (issuer == null && last4 == null) {
return null;
}
final loc = _loc();
return PaymentCardSuggestion(
issuerName: issuer ?? '결제수단',
issuerName: issuer ?? loc?.paymentCard ?? 'Payment card',
last4: last4,
source: 'sms',
);
@@ -366,6 +377,12 @@ class SmsScanner {
// 기본값은 원화
return 'KRW';
}
AppLocalizations? _loc() {
final ctx = navigatorKey.currentContext;
if (ctx == null) return null;
return AppLocalizations.of(ctx);
}
}
const List<String> _paymentLikeKeywords = [
@@ -501,7 +518,7 @@ String _isoExtractServiceName(String body, String sender) {
String _isoExtractServiceNameFromSender(String sender) {
if (RegExp(r'^\d+$').hasMatch(sender)) {
return '알 수 없는 서비스';
return _unknownServiceLabel();
}
return sender.replaceAll(RegExp(r'[^\w\s가-힣]'), '').trim();
}
@@ -576,13 +593,14 @@ Map<String, List<Map<String, dynamic>>> _groupMessagesByIdentifier(
final address = (sms['address'] as String?)?.trim();
final sender = (sms['sender'] as String?)?.trim();
final unknownLabel = _unknownServiceLabel();
String key = (serviceName != null &&
serviceName.isNotEmpty &&
serviceName != '알 수 없는 서비스')
serviceName != unknownLabel)
? serviceName
: (address?.isNotEmpty == true
? address!
: (sender?.isNotEmpty == true ? sender! : 'unknown'));
: (sender?.isNotEmpty == true ? sender! : unknownLabel));
groups.putIfAbsent(key, () => []).add(sms);
}
@@ -602,6 +620,12 @@ class _RepeatDetectionResult {
enum _MatchType { none, monthly, yearly, identical }
String _unknownServiceLabel() {
final ctx = navigatorKey.currentContext;
if (ctx == null) return 'Unknown service';
return AppLocalizations.of(ctx).unknownService;
}
class _MatchedPair {
_MatchedPair(this.first, this.second, this.type);

View File

@@ -2,6 +2,8 @@ import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/services.dart';
import 'package:permission_handler/permission_handler.dart' as permission;
import '../utils/platform_helper.dart';
import '../l10n/app_localizations.dart';
import '../navigator_key.dart';
class SMSService {
static const platform = MethodChannel('com.submanager/sms');
@@ -37,14 +39,24 @@ class SMSService {
try {
if (!await hasSMSPermission()) {
throw Exception('SMS 권한이 없습니다.');
final loc = _loc();
throw Exception(
loc?.smsPermissionRequired ?? 'SMS permission required.');
}
final List<dynamic> result =
await platform.invokeMethod('scanSubscriptions');
return result.map((item) => item as Map<String, dynamic>).toList();
} on PlatformException catch (e) {
throw Exception('SMS 스캔 중 오류 발생: ${e.message}');
final loc = _loc();
throw Exception(loc?.smsScanErrorWithMessage(e.message ?? '') ??
'Error occurred during SMS scan: ${e.message}');
}
}
static AppLocalizations? _loc() {
final ctx = navigatorKey.currentContext;
if (ctx == null) return null;
return AppLocalizations.of(ctx);
}
}