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:
parent
a6c040cd6a
commit
c0c59af0ac
@ -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)",
|
||||
|
||||
@ -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"
|
||||
}
|
||||
30
myApp/HaruDanim/Haru_DanimWatchWidgets/InfoPlist.xcstrings
Normal file
30
myApp/HaruDanim/Haru_DanimWatchWidgets/InfoPlist.xcstrings
Normal 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"
|
||||
}
|
||||
@ -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)")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user