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

@@ -747,6 +747,60 @@ class SubscriptionUrlMatcher {
return _getCategoryForLegacyService(serviceName);
}
/// 현재 로케일에 따라 서비스 표시명 가져오기
static Future<String> getServiceDisplayName({
required String serviceName,
required String locale,
}) async {
await initialize();
if (_servicesData == null) {
return serviceName;
}
final lowerName = serviceName.toLowerCase().trim();
final categories = _servicesData!['categories'] as Map<String, dynamic>;
// JSON에서 서비스 찾기
for (final categoryData in categories.values) {
final services = (categoryData as Map<String, dynamic>)['services'] as Map<String, dynamic>;
for (final serviceData in services.values) {
final data = serviceData as Map<String, dynamic>;
final names = List<String>.from(data['names'] ?? []);
// names 배열에 있는지 확인
for (final name in names) {
if (lowerName == name.toLowerCase() ||
lowerName.contains(name.toLowerCase()) ||
name.toLowerCase().contains(lowerName)) {
// 로케일에 따라 적절한 이름 반환
if (locale == 'ko' || locale == 'kr') {
return data['nameKr'] ?? serviceName;
} else {
return data['nameEn'] ?? serviceName;
}
}
}
// nameKr/nameEn에 직접 매칭 확인
final nameKr = (data['nameKr'] ?? '').toString().toLowerCase();
final nameEn = (data['nameEn'] ?? '').toString().toLowerCase();
if (lowerName == nameKr || lowerName == nameEn) {
if (locale == 'ko' || locale == 'kr') {
return data['nameKr'] ?? serviceName;
} else {
return data['nameEn'] ?? serviceName;
}
}
}
}
// 찾지 못한 경우 원래 이름 반환
return serviceName;
}
/// 카테고리 키를 실제 카테고리 ID로 매핑
static String _getCategoryIdByKey(String key) {
// 여기에 실제 앱의 카테고리 ID 매핑을 추가