fix(watch): 컴플리케이션 페이스 목록 미노출 수정 + 워치 앱 이름 다국어화

- 컴플리케이션이 페이스 편집 목록에 아예 안 나오던 원인: 추천 구성
  (recommendations)의 description에 문자열 보간을 사용해 WidgetKit이
  "Formatted text for AppIntentRecommendation is not supported" assertion으로
  익스텐션을 즉사시킴 → 시스템이 위젯 목록 조회 자체를 실패
  → Text(verbatim:)으로 교체. 시뮬레이터 chronod 로그로 3종 kind
  (HaruWatchStatus/GoalRate/QuestRate) 정상 등록 확인 (크래시 0)
- 추천 구성을 캐시된 목표/다짐 기반으로 개선 (모든 파라미터 채움)
- 워치 앱·워치 위젯 익스텐션 표시 이름 현지화: InfoPlist.xcstrings 추가
  (ko 하루다님 / en Haru Danim / ja ハルダニム)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
This commit is contained in:
songyc macbook 2026-07-10 15:42:37 +09:00
parent a6c040cd6a
commit c0c59af0ac
4 changed files with 103 additions and 14 deletions

View File

@ -116,6 +116,7 @@
Assets.xcassets,
Haru_DanimWatchWidgetsBundle.swift,
Info.plist,
InfoPlist.xcstrings,
Localizable.xcstrings,
);
target = 7263D0DF3000433300EA47FC /* Haru_DanimWatch Watch App */;
@ -670,7 +671,7 @@
DEVELOPMENT_TEAM = 5GNS4Z2SYT;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = Haru_DanimWatch;
INFOPLIST_KEY_CFBundleDisplayName = "하루다님";
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.yechan.HaruDanim;
LD_RUNPATH_SEARCH_PATHS = (
@ -706,7 +707,7 @@
DEVELOPMENT_TEAM = 5GNS4Z2SYT;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = Haru_DanimWatch;
INFOPLIST_KEY_CFBundleDisplayName = "하루다님";
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.yechan.HaruDanim;
LD_RUNPATH_SEARCH_PATHS = (
@ -742,7 +743,7 @@
DEVELOPMENT_TEAM = 5GNS4Z2SYT;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Haru_DanimWatchWidgets/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Haru_DanimWatchWidgets;
INFOPLIST_KEY_CFBundleDisplayName = "하루다님";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -778,7 +779,7 @@
DEVELOPMENT_TEAM = 5GNS4Z2SYT;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Haru_DanimWatchWidgets/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Haru_DanimWatchWidgets;
INFOPLIST_KEY_CFBundleDisplayName = "하루다님";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",

View File

@ -0,0 +1,30 @@
{
"sourceLanguage": "ko",
"strings": {
"CFBundleDisplayName": {
"comment": "Bundle display name",
"extractionState": "manual",
"localizations": {
"ko": {
"stringUnit": {
"state": "translated",
"value": "하루다님"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Haru Danim"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "ハルダニム"
}
}
}
}
},
"version": "1.0"
}

View File

@ -0,0 +1,30 @@
{
"sourceLanguage": "ko",
"strings": {
"CFBundleDisplayName": {
"comment": "Bundle display name",
"extractionState": "manual",
"localizations": {
"ko": {
"stringUnit": {
"state": "translated",
"value": "하루다님"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Haru Danim"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "ハルダニム"
}
}
}
}
},
"version": "1.0"
}

View File

@ -68,12 +68,27 @@ struct GoalRateProvider: AppIntentTimelineProvider {
Timeline(entries: [Self.makeEntry(configuration)], policy: .after(.now.addingTimeInterval(15 * 60)))
}
/// (//)
/// ( × //)
/// : description ( ) WidgetKit assertion
/// .
/// Text(verbatim:) .
func recommendations() -> [AppIntentRecommendation<GoalRateConfigIntent>] {
WatchSpanOption.allCases.map { span in
let intent = GoalRateConfigIntent()
intent.span = span
return AppIntentRecommendation(intent: intent, description: "목표 달성률 (\(span.label))")
let cached = (ComplicationStore.snapshot()?.goals ?? []).prefix(2)
.map { WatchGoalEntity(id: $0.id, title: $0.title) }
// ( )
let goals = cached.isEmpty
? [WatchGoalEntity(id: UUID(), title: String(localized: "목표"))]
: cached
return goals.flatMap { goal in
WatchSpanOption.allCases.map { span in
let intent = GoalRateConfigIntent()
intent.goal = goal
intent.span = span
return AppIntentRecommendation(
intent: intent,
description: Text(verbatim: "\(goal.title) · \(span.label)")
)
}
}
}
}
@ -196,12 +211,25 @@ struct QuestRateProvider: AppIntentTimelineProvider {
Timeline(entries: [Self.makeEntry(configuration)], policy: .after(.now.addingTimeInterval(15 * 60)))
}
/// (//)
/// ( × //)
/// : description Text(verbatim:) (GoalRate )
func recommendations() -> [AppIntentRecommendation<QuestRateConfigIntent>] {
WatchSpanOption.allCases.map { span in
let intent = QuestRateConfigIntent()
intent.span = span
return AppIntentRecommendation(intent: intent, description: "다짐 달성률 (\(span.label))")
let cached = (ComplicationStore.snapshot()?.goals ?? [])
.flatMap { goal in goal.quests.map { WatchQuestEntity(id: $0.id, title: "\(goal.title) · \($0.name)") } }
.prefix(2)
let quests = cached.isEmpty
? [WatchQuestEntity(id: UUID(), title: String(localized: "다짐"))]
: Array(cached)
return quests.flatMap { quest in
WatchSpanOption.allCases.map { span in
let intent = QuestRateConfigIntent()
intent.quest = quest
intent.span = span
return AppIntentRecommendation(
intent: intent,
description: Text(verbatim: "\(quest.title) · \(span.label)")
)
}
}
}
}