fix(quest): bound streaks by the goal's start date

'이하 유지' 다짐은 기록 없는 날도 달성으로 집계되므로 연속 계산이
목표가 생기기 전 과거(스캔 상한 400일)까지 거슬러 올라가 '연속 401일'로
표시되던 버그 수정. 연속은 행동이 아니라 '목표 안 다짐'의 달성이므로
하한 = 목표 시작일:
- 하루 단위: 시작일이 속한 논리적 하루부터만 센다
- 주간/월간: 시작일이 걸친 부분 주기까지 포함, 그 이전 주기에서 중단

검증: 시드에 재현 목표(스크린 타임 관리, 3일 전 시작 + 초과 없는 이하
유지 다짐) 추가 → 기대값 연속 4일 확인. -streakDump 런치 인자 신설
(전 다짐의 연속 계산 결과를 Documents/streak-dump.txt로 덤프).

부수: 도움말에 '연속 달성 표시' 항목 추가(ko/en/ja). 지난 기능들의
문자열이 위젯·워치·InfoPlist 카탈로그에 미번역으로 남아 있던 것을
채움(제어 센터·연속 표기·즐겨찾기·캘린더 권한 문구) — l10n 루틴이
IOS 카탈로그만 확인하던 함정을 CLAUDE.md §2.3에 명시.

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-14 01:42:13 +09:00
parent ce8ca50feb
commit 379ea6ebf4
9 changed files with 667 additions and 227 deletions

View File

@ -68,6 +68,8 @@ xcrun xcstringstool sync IOS/Localizable.xcstrings --stringsdata "${files[@]}"
# 3) python으로 en/ja 번역 채움 → 4) sync 재실행으로 포맷 정규화 → 5) missing 확인
```
⚠️ **카탈로그는 타깃별로 따로다** — Shared 파일의 문자열이라도 위젯·워치 타깃에서 쓰이면 Xcode가 빌드 시 그 타깃의 카탈로그(`Widgets/Localizable.xcstrings`, 워치 앱, `IOS/InfoPlist.xcstrings`)에도 추출한다. IOS 카탈로그만 채우면 위젯·워치 화면은 미번역으로 남으므로, 빌드 후 **모든 카탈로그의 missing을 확인**할 것.
용어 통일: 다짐=Quest/クエスト, 행동=Action, 꼬리표=Tag. `String(format:)`은 카탈로그로 추출되지 않으므로 사용자 노출 문구에 금지 (`Format.countAverage` 참고).
---
@ -168,7 +170,7 @@ xcrun xcstringstool sync IOS/Localizable.xcstrings --stringsdata "${files[@]}"
- 목표 편집: 내용·아이콘·색·시작일(과거 가능)·종료일(선택)·**달성 판정 기준 슬라이더(10~100%, 5% 단위, 기본 100%)**
- 목표 상세: 다짐 목록(탭=수정, 스와이프 삭제, 순서 모드), 다짐 추가(무료 한도 3개), 종료일 없으면 수동 종료(기준<100% 안내 문구에 기준 표기), 삭제
- 다짐 편집 3단계: ①대상(행동 또는 꼬리표+측정 기준) ②주기(하루[매일/요일/날짜/n번째 요일]·주·월·특정 기간 — ordinalWeekday 구현은 weekdayOrdinal 즉 "그 달의 n번째 ◯요일"이며 UI 문구도 그렇게 표기) ③목표량(시간 휠 또는 횟수 스테퍼)+방향(이상 달성/이하 유지)
- **연속 달성**(`QuestProgress.streak``QuestStreakInfo`): 하루 단위=적용일만 연속 계산("연속 N일" — 매주 수요일 다짐을 4주 연속이면 연속 4일), 주간/월간="연속 N주/N달", 특정 기간=nil. 진행 중인 오늘/이번 주기는 atLeast면 미달이어도 끊기지 않고(건너뜀) 채웠으면 포함, atMost는 넘는 순간 끊김. 소급 상한 400일(성능). 기록을 하루 키로 1회 버킷팅해 O(기록+일수). 표기: 목표 탭 다짐 행 + 위젯 ②다짐 줄·③링 셀·④현황 그리드 (불꽃+노랑, 0이면 숨김). '스트릭'이라는 단어는 한국어 UI에 쓰지 않는다
- **연속 달성**(`QuestProgress.streak``QuestStreakInfo`): 하루 단위=적용일만 연속 계산("연속 N일" — 매주 수요일 다짐을 4주 연속이면 연속 4일), 주간/월간="연속 N주/N달", 특정 기간=nil. 진행 중인 오늘/이번 주기는 atLeast면 미달이어도 끊기지 않고(건너뜀) 채웠으면 포함, atMost는 넘는 순간 끊김. **하한 = 목표 시작일** — 행동이 아니라 "목표 안 다짐"의 연속이므로 목표 시작 전 날짜/주기는 세지 않음(주·월은 시작일이 걸친 부분 주기까지 포함). 특히 atMost는 기록 없는 날이 전부 달성이라 이 하한이 없으면 과거 전체가 연속으로 잡힘. 소급 상한 400일(성능). 기록을 하루 키로 1회 버킷팅해 O(기록+일수). 표기: 목표 탭 다짐 행 + 위젯 ②다짐 줄·③링 셀·④현황 그리드 (불꽃+노랑, 0이면 숨김). '스트릭'이라는 단어는 한국어 UI에 쓰지 않는다
- 목록 진입 시 `evaluateIfEnded` 일괄 실행
### 6.5 기록 탭 (`HistoryView`, `RecordEditors`)
@ -288,13 +290,13 @@ xcrun xcstringstool sync IOS/Localizable.xcstrings --stringsdata "${files[@]}"
## 14. DEBUG 런치 인자 (검증용, 모두 DEBUG 빌드 전용)
공통: `-seedDemo YES`(데모 데이터: 태그3·행동6·기록 7일치·목표6·자정 걸친 세션), `-premium YES/NO`, `-startTab <main|action|tag|goal|history|stats|diary|settings>`, `-navStyle radial`, `-themeAutoToggle YES`
공통: `-seedDemo YES`(데모 데이터: 태그3·행동6·기록 7일치·목표7·자정 걸친 세션), `-premium YES/NO`, `-startTab <main|action|tag|goal|history|stats|diary|settings>`, `-navStyle radial`, `-themeAutoToggle YES`
| 영역 | 인자 |
|---|---|
| 모음 | `-startEditing` `-expandGoalCard` `-pinGoals <N>` `-openStatsFor "이름"` `-openHistoryFor "이름"` `-autoStart "이름"` |
| 측정 알림 | `-settings.longSessionAlertHours <N>`(설정 주입) `-longSessionAlertTestSeconds <N>`(시간 대신 N초 발화, provisional 권한 자동) `-alertDump`(예약 상태 → Documents/session-alerts.txt) |
| 목표 | `-goalShowEditor` `-goalShowFinished` `-goalReorder` `-goalScrollBottom` `-endGoalYesterday "제목"`(종료일을 어제로 — 실행 시점 자동 판정 검증) |
| 목표 | `-goalShowEditor` `-goalShowFinished` `-goalReorder` `-goalScrollBottom` `-endGoalYesterday "제목"`(종료일을 어제로 — 실행 시점 자동 판정 검증) `-streakDump`(모든 다짐의 연속 계산 결과 → Documents/streak-dump.txt) |
| 기록/통계 | `-historyMode timetable` `-historyWeekly` `-excludeActions "이름,이름"` `-historyShowFilter` `-statShowFilter` `-statSpan <day|week|month>` `-statScrollBottom` `-showExport` `-exportDump`(Documents/export-dump.png) |
| 설정/기타 | `-settingsScrollGoal` `-settingsScrollPremium` `-premiumPreview` `-helpPreview` `-dataExportPreview`(+`-dataExportRun`이면 CSV 자동 생성 후 Documents 복사) `-widgetPreview YES|lock` `-widgetPreviewScroll <앵커>` `cloudSync`(standard bool로 강제) `-intentSmokeTest`(시리 인텐트 10개 시나리오 실행 → Documents/intent-smoke-test.txt, -seedDemo·-premium과 함께) |
| 버블 | `-radialExpanded` |

View File

@ -473,6 +473,22 @@
}
}
},
"즐겨찾기": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Favorites"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "お気に入り"
}
}
}
},
"초과": {
"localizations": {
"en": {

View File

@ -47,6 +47,7 @@ struct ContentView: View {
DebugSeed.autoStartIfRequested(context: context)
DebugSeed.pinGoalsIfRequested(context: context)
DebugSeed.endGoalYesterdayIfRequested(context: context)
DebugSeed.dumpStreaksIfRequested(context: context)
await DebugSeed.runIntentSmokeTestIfRequested()
// : -themeAutoToggle YES 3 (App Group defaults)
// ' '

View File

@ -165,6 +165,25 @@ enum DebugSeed {
try? context.save()
}
/// `-streakDump` :
/// Documents/streak-dump.txt (CLI )
static func dumpStreaksIfRequested(context: ModelContext) {
guard UserDefaults.standard.bool(forKey: "streakDump") else { return }
let quests = (try? context.fetch(FetchDescriptor<Quest>())) ?? []
var lines: [String] = []
for quest in quests {
let goal = quest.goal
let streak = QuestProgress(quest: quest).streak()
lines.append(
"goal=\(goal?.title ?? "-") start=\(goal?.startDate.formatted(date: .numeric, time: .omitted) ?? "-") "
+ "quest=\(quest.targetName) period=\(quest.period) direction=\(quest.direction) "
+ "streak=\(streak.map { "\($0.count) (\($0.unit))" } ?? "nil")"
)
}
let url = URL.documentsDirectory.appending(path: "streak-dump.txt")
try? lines.joined(separator: "\n").write(to: url, atomically: true, encoding: .utf8)
}
/// `-pinGoals <N>` : N ( )
static func pinGoalsIfRequested(context: ModelContext) {
let n = UserDefaults.standard.integer(forKey: "pinGoals")
@ -348,8 +367,26 @@ enum DebugSeed {
weeklyRunQuest.direction = .atLeast
context.insert(weeklyRunQuest)
// : + ' '
// ( 4, 401)
let screen = Goal(
title: "스크린 타임 관리",
symbolName: "iphone",
colorHex: "#4A7A9D",
startDate: cal.date(byAdding: .day, value: -3, to: now)!,
endDate: nil
)
context.insert(screen)
let screenQuest = Quest(goal: screen)
screenQuest.targetAction = video
screenQuest.measure = .time
screenQuest.period = .daily
screenQuest.targetSeconds = 3 * 3600 // 90
screenQuest.direction = .atMost
context.insert(screenQuest)
//
for (index, goal) in [toeic, health, habit, run].enumerated() {
for (index, goal) in [toeic, health, habit, run, screen].enumerated() {
goal.sortOrder = index
}
@ -362,7 +399,7 @@ enum DebugSeed {
endDate: cal.date(byAdding: .day, value: -30, to: now)!
)
sleep.status = .achieved
sleep.sortOrder = 4
sleep.sortOrder = 5
context.insert(sleep)
let diet = Goal(
title: "한 달 다이어트",
@ -372,7 +409,7 @@ enum DebugSeed {
endDate: cal.date(byAdding: .day, value: -20, to: now)!
)
diet.status = .notAchieved
diet.sortOrder = 5
diet.sortOrder = 6
context.insert(diet)
// UI : , (LocalPrefs )

View File

@ -37,6 +37,30 @@
},
"shouldTranslate": false
},
"NSCalendarsFullAccessUsageDescription": {
"comment": "Privacy - Calendars Full Access Usage Description",
"extractionState": "extracted_with_value",
"localizations": {
"ko": {
"stringUnit": {
"state": "translated",
"value": "일기 타임테이블에 캘린더 일정을 함께 표시하기 위해 필요해요."
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Needed to show your calendar events alongside the diary timetable."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "日記のタイムテーブルにカレンダーの予定を一緒に表示するために必要です。"
}
}
}
},
"NSPhotoLibraryAddUsageDescription": {
"comment": "이미지 내보내기 → 사진에 저장 권한 문구",
"extractionState": "manual",

View File

@ -815,12 +815,6 @@
},
"'%@' 측정이 %lld시간을 넘겼어요. 종료를 잊으셨다면 열어서 꺼 주세요.": {
"localizations": {
"ko": {
"stringUnit": {
"state": "new",
"value": "'%1$@' 측정이 %2$lld시간을 넘겼어요. 종료를 잊으셨다면 열어서 꺼 주세요."
}
},
"en": {
"stringUnit": {
"state": "translated",
@ -832,6 +826,12 @@
"state": "translated",
"value": "「%@」の計測が%lld時間を超えました。終了し忘れた場合はアプリで停止してください。"
}
},
"ko": {
"stringUnit": {
"state": "new",
"value": "'%1$@' 측정이 %2$lld시간을 넘겼어요. 종료를 잊으셨다면 열어서 꺼 주세요."
}
}
}
},
@ -3021,6 +3021,22 @@
}
}
},
"다짐을 끊기지 않고 지키면 불꽃과 함께 '연속 3일'처럼 표시돼요. 요일이나 날짜를 정한 다짐은 그 수행일만 세고, 주간·월간 다짐은 '연속 N주·N달'로 세요. 계산은 목표 시작일부터 시작하고, 아직 끝나지 않은 오늘(이번 주기)은 못 채웠어도 끊기지 않아요. '이하 유지' 다짐은 한도를 넘는 순간 끊겨요.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Keep a quest going without a break and a flame appears with a streak like \"3-day streak\". Quests set for specific weekdays or dates count only their scheduled days, and weekly/monthly quests count as \"N-week / N-month streaks\". Counting starts from the goal's start date, and today (or the current period) never breaks the streak while it's still in progress. \"Stay under\" quests break the moment you exceed the limit."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "クエストを途切れず守ると、炎マークと「連続3日」のような表示が付きます。曜日や日付を決めたクエストは実行日だけを数え、週間・月間クエストは「連続N週・Nか月」で数えます。計算は目標の開始日から始まり、まだ終わっていない今日今の期間は未達でも途切れません。「以下をキープ」のクエストは上限を超えた瞬間に途切れます。"
}
}
}
},
"다짐을 찾을 수 없어요. 앱에서 삭제됐을 수 있어요.": {
"localizations": {
"en": {
@ -6442,6 +6458,22 @@
}
}
},
"연속 달성 표시": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Streak Badges"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "連続達成の表示"
}
}
}
},
"영상 시청": {
"localizations": {
"en": {

View File

@ -127,6 +127,11 @@ struct HelpView: View {
title: "진행률 읽는 법",
body: "다짐마다 하루·주간·월간 진행률이 표시돼요. 주간·월간 진행률은 하루 초과 달성이 다른 날의 미달을 가리지 않도록 하루 목표량까지만 인정해 합산해요. '이하 유지' 다짐은 한도를 넘지 않는 동안 100%로 표시돼요."
),
HelpTopic(
symbol: "flame.fill",
title: "연속 달성 표시",
body: "다짐을 끊기지 않고 지키면 불꽃과 함께 '연속 3일'처럼 표시돼요. 요일이나 날짜를 정한 다짐은 그 수행일만 세고, 주간·월간 다짐은 '연속 N주·N달'로 세요. 계산은 목표 시작일부터 시작하고, 아직 끝나지 않은 오늘(이번 주기)은 못 채웠어도 끊기지 않아요. '이하 유지' 다짐은 한도를 넘는 순간 끊겨요."
),
HelpTopic(
symbol: "checkmark.seal.fill",
title: "목표 종료와 판정",

View File

@ -253,6 +253,10 @@ extension QuestProgress {
/// - : nil ( ).
/// / ' ' (),
/// . ' ' , .
///
/// = : " "
/// / . ( ' '
/// )
func streak(now: Date = .now) -> QuestStreakInfo? {
guard quest.targetValue > 0 else { return nil }
switch quest.period {
@ -301,7 +305,10 @@ extension QuestProgress {
private func dayStreak(now: Date) -> QuestStreakInfo {
let cal = math.calendar
let todayKey = math.dayKey(for: now)
let lowerKey = cal.date(byAdding: .day, value: -Self.streakScanDays, to: todayKey)!
var lowerKey = cal.date(byAdding: .day, value: -Self.streakScanDays, to: todayKey)!
if let goalStart = quest.goal?.startDate {
lowerKey = max(lowerKey, math.dayKey(for: goalStart))
}
let values = perDayValues(since: math.dayRange(forKey: lowerKey).lowerBound, now: now)
var count = 0
@ -336,7 +343,10 @@ extension QuestProgress {
var count = 0
var range = currentRange
var isCurrentPeriod = true
// ( ) ,
let goalStart = quest.goal?.startDate
while range.lowerBound >= lowerBound {
if let goalStart, range.upperBound <= goalStart { break }
if isAchievedValue(value(in: range)) {
count += 1
} else if isCurrentPeriod && quest.direction == .atLeast {

View File

@ -1,6 +1,98 @@
{
"sourceLanguage": "ko",
"strings": {
"'%@ · %@' %@ 진행률: %@": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "'%@ · %@' %@ progress: %@"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "「%@ · %@」の%@進捗率: %@"
}
},
"ko": {
"stringUnit": {
"state": "new",
"value": "'%1$@ · %2$@' %3$@ 진행률: %4$@"
}
}
}
},
"'%@' %@ 진행률은 %lld%%예요.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "'%@' %@ progress is %lld%%."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "「%@」の%@進捗率は%lld%%です。"
}
},
"ko": {
"stringUnit": {
"state": "new",
"value": "'%1$@' %2$@ 진행률은 %3$lld%%예요."
}
}
}
},
"'%@' 목표에는 아직 다짐이 없어요.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "The goal '%@' has no quests yet."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "目標「%@」にはまだクエストがありません。"
}
}
}
},
"'%@'은(는) 시간 측정 행동이에요. 측정 시작/종료를 사용해 주세요.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "'%@' is a time-tracking action. Use start/stop tracking instead."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "「%@」は時間計測タイプです。計測の開始/終了を使ってください。"
}
}
}
},
"'%@'은(는) 횟수 기록 행동이에요. 횟수 추가를 사용해 주세요.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "'%@' is a count action. Use add count instead."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "「%@」は回数記録タイプです。回数の追加を使ってください。"
}
}
}
},
"%@": {
"localizations": {
"en": {
@ -17,6 +109,28 @@
}
}
},
"%@ · %@": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "%@ · %@"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "%@ · %@"
}
},
"ko": {
"stringUnit": {
"state": "new",
"value": "%1$@ · %2$@"
}
}
}
},
"%@ %@": {
"localizations": {
"en": {
@ -127,28 +241,6 @@
}
}
},
"%@ · %@": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "%@ · %@"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "%@ · %@"
}
},
"ko": {
"stringUnit": {
"state": "new",
"value": "%1$@ · %2$@"
}
}
}
},
"%@ 측정을 시작했어요.": {
"localizations": {
"en": {
@ -267,6 +359,23 @@
}
}
},
"%@회": {
"comment": "횟수 평균 (소수 1자리)",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "%@ times"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "%@回"
}
}
}
},
"%lld": {
"localizations": {
"en": {
@ -401,98 +510,6 @@
}
}
},
"'%@ · %@' %@ 진행률: %@": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "'%@ · %@' %@ progress: %@"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "「%@ · %@」の%@進捗率: %@"
}
},
"ko": {
"stringUnit": {
"state": "new",
"value": "'%1$@ · %2$@' %3$@ 진행률: %4$@"
}
}
}
},
"'%@' %@ 진행률은 %lld%%예요.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "'%@' %@ progress is %lld%%."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "「%@」の%@進捗率は%lld%%です。"
}
},
"ko": {
"stringUnit": {
"state": "new",
"value": "'%1$@' %2$@ 진행률은 %3$lld%%예요."
}
}
}
},
"'%@' 목표에는 아직 다짐이 없어요.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "The goal '%@' has no quests yet."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "目標「%@」にはまだクエストがありません。"
}
}
}
},
"'%@'은(는) 시간 측정 행동이에요. 측정 시작/종료를 사용해 주세요.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "'%@' is a time-tracking action. Use start/stop tracking instead."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "「%@」は時間計測タイプです。計測の開始/終了を使ってください。"
}
}
}
},
"'%@'은(는) 횟수 기록 행동이에요. 횟수 추가를 사용해 주세요.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "'%@' is a count action. Use add count instead."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "「%@」は回数記録タイプです。回数の追加を使ってください。"
}
}
}
},
"+%lld": {
"localizations": {
"en": {
@ -525,22 +542,6 @@
}
}
},
"10초": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "10 sec"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "10秒"
}
}
}
},
"1분": {
"localizations": {
"en": {
@ -557,18 +558,50 @@
}
}
},
"30초": {
"1시간": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "30 sec"
"value": "1 hour"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "30秒"
"value": "1時間"
}
}
}
},
"2시간": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "2 hours"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "2時間"
}
}
}
},
"3시간": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "3 hours"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "3時間"
}
}
}
@ -605,6 +638,70 @@
}
}
},
"6시간": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "6 hours"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "6時間"
}
}
}
},
"10초": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "10 sec"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "10秒"
}
}
}
},
"12시간": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "12 hours"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "12時間"
}
}
}
},
"30초": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "30 sec"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "30秒"
}
}
}
},
"가장 나중에 시작한 행동": {
"localizations": {
"en": {
@ -1198,7 +1295,30 @@
}
}
},
"매달 %lld번째 %@요일": {
"localizations": {
"ko": {
"stringUnit": {
"state": "new",
"value": "매달 %1$lld번째 %2$@요일"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "#%lld %@ of every month"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "毎月第%lld%@曜日"
}
}
}
},
"매달 %lld째 주 %@요일": {
"extractionState": "stale",
"localizations": {
"en": {
"stringUnit": {
@ -1268,7 +1388,24 @@
}
}
},
"몇 번째 요일": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Nth weekday"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "何番目の曜日"
}
}
}
},
"몇째 주 요일": {
"extractionState": "stale",
"localizations": {
"en": {
"stringUnit": {
@ -1317,6 +1454,22 @@
}
}
},
"목표 1개 · 다짐 16개": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "1 goal · 16 quests"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "目標1件・クエスト16件"
}
}
}
},
"목표 1개 + 다짐 진행률": {
"localizations": {
"en": {
@ -1349,22 +1502,6 @@
}
}
},
"목표 1개 · 다짐 16개": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "1 goal · 16 quests"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "目標1件・クエスト16件"
}
}
}
},
"목표 2개": {
"localizations": {
"en": {
@ -1557,22 +1694,6 @@
}
}
},
"목표의 하루/주간/월간 진행률(%)을 가져와요. 소속 다짐들의 진행률 평균이에요.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Gets a goal's daily/weekly/monthly progress (%), averaged across its quests."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "目標の日/週/月の進捗率(%)を取得します。所属クエストの平均です。"
}
}
}
},
"목표의 하루·주간·월간 진행률을 진행바로 확인해요. (프리미엄)": {
"localizations": {
"en": {
@ -1589,6 +1710,22 @@
}
}
},
"목표의 하루/주간/월간 진행률(%)을 가져와요. 소속 다짐들의 진행률 평균이에요.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Gets a goal's daily/weekly/monthly progress (%), averaged across its quests."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "目標の日/週/月の進捗率(%)を取得します。所属クエストの平均です。"
}
}
}
},
"물 마시기": {
"localizations": {
"en": {
@ -1830,6 +1967,38 @@
}
}
},
"시작": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Start"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "開始"
}
}
}
},
"실행 상태": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Running state"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "実行状態"
}
}
}
},
"실행할 행동들과 누적값 표시 방식, 테마를 선택하세요. 선택하지 않은 칸은 비워 둬요.": {
"localizations": {
"en": {
@ -1878,6 +2047,54 @@
}
}
},
"연속 %lld달": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "%lld-month streak"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "%lldか月連続"
}
}
}
},
"연속 %lld일": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "%lld-day streak"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "%lld日連続"
}
}
}
},
"연속 %lld주": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "%lld-week streak"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "%lld週連続"
}
}
}
},
"영상 시청": {
"localizations": {
"en": {
@ -2007,6 +2224,55 @@
}
}
},
"이번 주 누적": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "This week's total"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "今週の累計"
}
}
}
},
"이상 달성": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Reach at least"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "以上を達成"
}
}
}
},
"일": {
"comment": "요일 축약(일요일)",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Sun"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "日"
}
}
}
},
"이번 달": {
"localizations": {
"en": {
@ -2055,38 +2321,6 @@
}
}
},
"이번 주 누적": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "This week's total"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "今週の累計"
}
}
}
},
"이상 달성": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Reach at least"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "以上を達成"
}
}
}
},
"이하 유지": {
"localizations": {
"en": {
@ -2103,23 +2337,6 @@
}
}
},
"일": {
"comment": "요일 축약(일요일)",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Sun"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "日"
}
}
}
},
"일주일 (하루 단위)": {
"localizations": {
"en": {
@ -2248,6 +2465,22 @@
}
}
},
"제어 센터 버튼으로 실행할 행동을 선택하세요.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Choose the action to run from the Control Center button."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "コントロールセンターのボタンで実行するアクションを選んでください。"
}
}
}
},
"주간": {
"localizations": {
"en": {
@ -2408,6 +2641,22 @@
}
}
},
"탭해서 +1": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Tap for +1"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "タップで+1"
}
}
}
},
"테마": {
"localizations": {
"en": {
@ -2569,6 +2818,22 @@
}
}
},
"프리미엄 필요": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Premium required"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "プレミアムが必要"
}
}
}
},
"하루": {
"localizations": {
"en": {
@ -2793,6 +3058,22 @@
}
}
},
"행동 선택": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Choose Action"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "アクションを選択"
}
}
}
},
"행동 실행": {
"localizations": {
"en": {
@ -2825,6 +3106,22 @@
}
}
},
"행동 없음": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "No actions"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "アクションなし"
}
}
}
},
"행동 통계": {
"localizations": {
"en": {
@ -2841,6 +3138,22 @@
}
}
},
"행동 하나를 골라 제어 센터에서 바로 실행해요. 시간 측정은 시작/종료 토글, 횟수 기록은 탭마다 +1. (프리미엄)": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Pick one action and run it right from Control Center. Time tracking toggles start/stop; counts add +1 per tap. (Premium)"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "アクションを1つ選んでコントロールセンターから直接実行。時間計測は開始/終了のトグル、回数はタップごとに+1。プレミアム"
}
}
}
},
"행동을 위젯에서 바로 실행하고 누적값을 확인해요. (프리미엄)": {
"localizations": {
"en": {