diff --git a/myApp/HaruDanim/CLAUDE.md b/myApp/HaruDanim/CLAUDE.md index b765324..3761c22 100644 --- a/myApp/HaruDanim/CLAUDE.md +++ b/myApp/HaruDanim/CLAUDE.md @@ -2,7 +2,7 @@ > **한 줄 소개**: 하루의 습관과 시간을 추적하는 iOS 앱. "행동"의 **시간을 측정**하거나 **횟수를 기록**하고, 이를 바탕으로 **목표(Goal)와 다짐(Quest)** 의 달성률을 관리한다. 프리미엄(StoreKit 2)으로 위젯·워치·iCloud 동기화·iPad 일기를 제공한다. > -> 문서 버전: v2.2 (2026-07-17) — **실제 구현 코드를 기준으로 작성된 현행 명세**. 새 세션에서 이 문서만 읽어도 앱 전체를 파악할 수 있도록 유지한다. 기능이 바뀌면 이 문서도 함께 갱신할 것. +> 문서 버전: v2.3 (2026-07-21) — **실제 구현 코드를 기준으로 작성된 현행 명세**. 새 세션에서 이 문서만 읽어도 앱 전체를 파악할 수 있도록 유지한다. 기능이 바뀌면 이 문서도 함께 갱신할 것. --- @@ -16,7 +16,7 @@ | 번들 ID | `com.yechan.HaruDanim` / App Group `group.com.yechan.HaruDanim` / CloudKit `iCloud.com.yechan.HaruDanim` | | 수익 모델 | 부분 유료화. 무료 한도(행동 10·목표 2·목표당 다짐 3) + 프리미엄 결제 시 해제 | | 결제 상품 | `com.yechan.HaruDanim.premium.monthly`(월 5,000원) / `.yearly`(년 50,000원) / `.lifetime`(80,000원) — 임시 가격, App Store Connect 등록은 아직 안 됨 | -| 현재 상태 (2026-07-17) | **기능 동결(release freeze)** — 첫 배포 후보(RC)는 커밋 `f05e611`(버전 1.0 (1)). 이후 실사용 보고 기반 수정 라운드가 진행됨(전부 커밋·푸시·실기기 확인 완료): l10n 누락 일괄·ja 앱 이름 'Haru Danim' 통일·워치 컴플리케이션 추천 캡 제거(§11)·⑥ 위젯 종료 버튼(§10)·진행률 환산 캡+수행일 집계+수행일 아님 표기(§4.2)·파일명/CSV 헤더 현지화(§7)·워치 컴플리케이션 목표 필터(§6.4·§11, 사용자 승인 소기능)·기록 시각 입력 상시 휠 다이얼+분 절사(§6.5)·일기 달력 기분 사진 백레이어(§6.7)·다짐 없는 목표 '다짐 없음' 표기(§4.3)·워치 측정 중 행 탭 종료(§11)·도움말 예시 스크린샷 18장(§6.8)·설정 하루 시작 시간 접힘 행(§6.5). **App Store 등록 자료는 `Marketing/`에 완비**(설명·키워드·구독 메타·스크린샷 원본/합성판, README에 재생성 절차 — 앱 변경이 자료에 영향 주면 요청 없이 함께 갱신할 것). **사업자 등록 완료, 통신판매업 신고 처리 대기** — 처리되면 ASC 등록·유료 계약 진행(순서는 Marketing/README 체크리스트). 사용자는 계속 실사용 테스트 중이며 버그 보고 시: 최소 변경 + 시뮬레이터 재검증 + l10n 루틴(§2.3) + Debug/Store(워치 파일이면 워치까지) 빌드 + 커밋·푸시 | +| 현재 상태 (2026-07-21) | **기능 동결(release freeze)** — 첫 배포 후보(RC)는 커밋 `f05e611`(버전 1.0 (1)). 이후 실사용 보고 기반 수정 라운드가 진행됨(전부 커밋·푸시·실기기 확인 완료): l10n 누락 일괄·ja 앱 이름 'Haru Danim' 통일·워치 컴플리케이션 추천 캡 제거(§11)·⑥ 위젯 종료 버튼(§10)·진행률 환산 캡+수행일 집계+수행일 아님 표기(§4.2)·파일명/CSV 헤더 현지화(§7)·워치 컴플리케이션 목표 필터(§6.4·§11, 사용자 승인 소기능)·기록 시각 입력 상시 휠 다이얼+분 절사(§6.5)·일기 달력 기분 사진 백레이어(§6.7)·다짐 없는 목표 '다짐 없음' 표기(§4.3)·워치 측정 중 행 탭 종료(§11)·도움말 예시 스크린샷 18장(§6.8)·설정 하루 시작 시간 접힘 행(§6.5)·**모음 탭 즐겨찾기 빠른 실행 영역 추가**(§6.1, 행동 많아질 때 혼잡도 문제의 1차 대응 — 사용자 승인 소기능, 시뮬레이션 기반 대안 A~F 중 A만 채택). **App Store 등록 자료는 `Marketing/`에 완비**(설명·키워드·구독 메타·스크린샷 원본/합성판, README에 재생성 절차 — 앱 변경이 자료에 영향 주면 요청 없이 함께 갱신할 것). **사업자 등록 완료, 통신판매업 신고 처리 대기** — 처리되면 ASC 등록·유료 계약 진행(순서는 Marketing/README 체크리스트). 사용자는 계속 실사용 테스트 중이며 버그 보고 시: 최소 변경 + 시뮬레이터 재검증 + l10n 루틴(§2.3) + Debug/Store(워치 파일이면 워치까지) 빌드 + 커밋·푸시 | | 남은 배포 작업 | App Store Connect 상품 3종 등록(구독 그룹 + 비소모성, 유료 앱 계약), **CloudKit 프로덕션 스키마 배포**(대시보드 Deploy Schema to Production — DiaryTemplate 등 새 레코드 타입/필드는 개발 환경에만 자동 생성되므로 출시 전 필수), 실기기 검증(펜슬 필기감·캘린더 권한), App Store 배포. 코드 측 잔여 없음 — `ITSAppUsesNonExemptEncryption=NO` 등록 완료, **개인정보 매니페스트(PrivacyInfo.xcprivacy) 4개 번들 완비**(UserDefaults CA92.1·1C8F.1, 수집·트래킹 없음 — 위젯·워치위젯 폴더 것은 앱·워치 앱 타깃에서 membershipException으로 중복 복사 제외), 결제 화면 약관 링크 완료(개인정보처리방침 `Docs/PRIVACY.md` ko/en/ja는 `github.com/notalentprogrammer/HaruDanim` main의 PRIVACY.md로 게시됨, 이용약관은 Apple 표준 EULA URL). ASC 등록 시 앱 설명에도 두 링크 기재 + '앱 개인정보 보호'는 "데이터 수집 안 함" 선택 | --- @@ -175,9 +175,11 @@ xcrun xcstringstool sync Widgets/Localizable.xcstrings --stringsdata "${widget_f ### 6.1 모음 탭 (`MainView`) - 상단: 노출 목표 진행 현황 카드(1개=단독, 2개+=스냅 페이징 가로 스크롤 + 페이지 점, iPad=그리드). 카드 스타일 설정: 다짐별 각각(perQuest, 접힘 시 3개+더보기) / 전체 합산(combined) - "현재 진행 중" 영역: 측정 중 세션 나열 + 실시간 타이머 + 종료 버튼 +- **즐겨찾기 빠른 실행 영역**(`favoritesArea`, `runningArea` 아래·전체 그리드 위, `!favoriteActions.isEmpty && !isEditing`일 때만): `Action.isFavorite`(행동 탭과 동일 필드, §6.2) 켠 행동을 기기별 배치 순서(`orderedActions` 기준) 그대로 가로 스크롤 한 줄로 노출 — 행동이 많아져도 즐겨찾기는 스크롤 없이 바로 접근 가능하게 하는 목적(2026-07-21, 실사용 크라우딩 피드백 대응, §15 하단 이력 참고). **추가만 하고 전체 그리드에서 제외하지 않음**(즐겨찾기 아닌 행동을 찾기 더 어려워지는 트레이드오프 방지가 의도된 설계 — 그리드에서 숨기는 방식은 검토 후 기각됨). 셀은 `ActionButtonCell(compact: true)` 고정 폭(108pt) 재사용, 탭 동작(`handleTap`)과 롱프레스 메뉴(`actionContextMenu(for:)` — 기록 확인/통계 보기/기록 직접 입력·수정/**즐겨찾기 지정·해제**/행동 설정 수정/삭제)는 전체 그리드 셀과 완전히 공유. 즐겨찾기 지정·해제는 이 메뉴 또는 행동 탭에서 가능(§6.2) - 행동 버튼 그리드: 꼬리표 색 그라데이션 배경 + 아이콘 + 이름 + 오늘 누적(시간형) 또는 오늘 횟수(횟수형, 숫자 전환 애니메이션). 측정 중이면 노란 테두리 + record 점. 탭 = 시작/종료 토글 또는 +1 (스프링 눌림 + 햅틱) - 한 줄 개수: iPhone 2~6개 설정(5개 이상은 compact 셀), iPad는 화면 폭 자동 채움 — **iPad의 목표 카드·행동 그리드는 LazyVGrid가 아니라 비-lazy `AdaptiveColumnsLayout`**(MainView 내 커스텀 Layout, LazyVGrid(.adaptive)와 같은 열 계산). lazy 높이 추정이 높이 제각각인 목표 카드 + 1초 타이머 갱신과 결합해 맨 아래 스크롤 시 화면이 무한 진동(터치 불능)하는 실기기 버그의 수정 — iPhone(고정 열·페이징 높이 고정)은 LazyVGrid 유지. iPad 모음 그리드를 다시 lazy로 되돌리지 말 것. **행동 셀 높이 통일 규칙**: 시간형·횟수형 푸터 높이가 달라(횟수형 27pt 숫자) 셀 자연 높이가 달랐음 — 푸터에 숫자 크기의 hidden 템플릿(Text "0")을 깔아 두 유형의 자연 높이를 맞추고, AdaptiveColumnsLayout.place가 LazyVGrid처럼 행 높이를 제안해 행 안 셀도 균일(측정 패스는 불변이라 진동 버그와 무관, 고정 높이 목표 카드 무영향) -- 롱프레스 메뉴: 기록 확인(기록 탭 필터 이동) / 통계 보기 / 기록 직접 입력·수정 / 행동 설정 수정 / 삭제 +- 롱프레스 메뉴: 기록 확인(기록 탭 필터 이동) / 통계 보기 / 기록 직접 입력·수정 / **즐겨찾기 지정·해제** / 행동 설정 수정 / 삭제 +- **행동이 많아질 때의 혼잡도 문제**(2026-07-21 실사용 피드백): 22개 시드로 iPhone(3열·6열)·iPad·맥 4가지를 직접 시뮬레이션해 6가지 대안(A~F)을 검토 — 이번에 구현한 즐겨찾기 빠른 실행(A)에 더해, 향후 후보로 ⓑ꼬리표별 접기(출시 후), ⓒ검색/필터(출시 후), ⓓ한 줄 개수 늘릴 때 경고 문구(미착수, 저위험)가 있음. **명시적으로 기각**: 즐겨찾기 등을 전체 그리드에서 숨기는 옵트인 방식(행동이 눌러도 안 보이게 될 위험 > 이득) — 다시 제안하지 말 것 - 배치 편집: 지글 애니메이션 + 드래그 재정렬 → `local.actionOrder` 저장 (CloudKit 모델에 쓰지 않음) - "짧은 기록 무시"(minSessionSeconds): 설정보다 짧은 세션은 종료 시 삭제. 메모 창 옵션 켠 행동은 종료/+1 시 메모 시트(건너뛰기 가능) - 툴바: 새로고침(좌) / 배치 편집(우) diff --git a/myApp/HaruDanim/IOS/Localizable.xcstrings b/myApp/HaruDanim/IOS/Localizable.xcstrings index bff32b5..9547805 100644 --- a/myApp/HaruDanim/IOS/Localizable.xcstrings +++ b/myApp/HaruDanim/IOS/Localizable.xcstrings @@ -1,276 +1,6 @@ { "sourceLanguage" : "ko", "strings" : { - "'%@ · %@' %@ 진행률: %@" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "'%@ · %@' %@ progress: %@" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "「%@ · %@」の%@進捗率: %@" - } - }, - "ko" : { - "stringUnit" : { - "state" : "new", - "value" : "'%1$@ · %2$@' %3$@ 진행률: %4$@" - } - } - } - }, - "'%@ · %@' 오늘은 수행일이 아니에요." : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "'%1$@ · %2$@' isn't scheduled for today." - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "「%1$@ · %2$@」は今日は実行日ではありません。" - } - }, - "ko" : { - "stringUnit" : { - "state" : "new", - "value" : "'%1$@ · %2$@' 오늘은 수행일이 아니에요." - } - } - } - }, - "'%@' %@ 진행률은 %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" : "Delete the tag ‘%@’? Its actions won't be deleted." - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "タグ「%@」を削除しますか?アクションは削除されません。" - } - } - } - }, - "‘%@’ 목표를 달성했나요?" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Did you achieve the goal ‘%@’?" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "目標「%@」を達成しましたか?" - } - } - } - }, - "‘%@’ 목표를 삭제할까요? 다짐도 함께 삭제됩니다." : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Delete the goal ‘%@’? Its quests will also be deleted." - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "目標「%@」を削除しますか?クエストも一緒に削除されます。" - } - } - } - }, - "'%@' 목표에는 아직 다짐이 없어요." : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The goal '%@' has no quests yet." - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "目標「%@」にはまだクエストがありません。" - } - } - } - }, - "‘%@’ 양식을 삭제할까요?" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Delete the \"%@\" template?" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "「%@」のテンプレートを削除しますか?" - } - } - } - }, - "‘%@’ 양식을 삭제할까요? 이 양식을 쓰는 노트 페이지 %lld개는 빈 캔버스로 표시돼요. (필기와 요소는 그대로 남아요)" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Delete the \"%1$@\" template? %2$lld note pages using it will show as a blank canvas. (Your handwriting and items are kept.)" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "「%1$@」のテンプレートを削除しますか?このテンプレートを使う%2$lld枚のノートページは白紙のキャンバスとして表示されます。(手書きや要素はそのまま残ります)" - } - }, - "ko" : { - "stringUnit" : { - "state" : "new", - "value" : "‘%1$@’ 양식을 삭제할까요? 이 양식을 쓰는 노트 페이지 %2$lld개는 빈 캔버스로 표시돼요. (필기와 요소는 그대로 남아요)" - } - } - } - }, - "'%@' 측정이 %lld시간을 넘겼어요. 종료를 잊으셨다면 열어서 꺼 주세요." : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tracking '%@' has passed %lld hours. If you forgot to stop it, open the app and turn it off." - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "「%@」の計測が%lld時間を超えました。終了し忘れた場合はアプリで停止してください。" - } - }, - "ko" : { - "stringUnit" : { - "state" : "new", - "value" : "'%1$@' 측정이 %2$lld시간을 넘겼어요. 종료를 잊으셨다면 열어서 꺼 주세요." - } - } - } - }, - "‘%@’ 행동을 삭제할까요? 기록도 함께 삭제됩니다." : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Delete the action ‘%@’? Its records will also be deleted." - } - }, - "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" : { - "stringUnit" : { - "state" : "translated", - "value" : "Choose 'Glass Bubble Menu' to replace the bottom tab bar: tap the button at the bottom center and glass-orb tab buttons fan out in an arc. Every tab stays within thumb reach for easy one-handed use. (Experimental)" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "「ガラスバブルメニュー」を選ぶと、下部タブバーの代わりに画面下中央のボタンをタップしたとき、ガラス玉のタブボタンが扇形に広がります。すべてのタブが親指の届く範囲にあり、片手でも操作しやすいです。(実験的機能)" - } - } - } - }, - "‘글래스 버블 메뉴’를 고르면 하단 탭바 대신 화면 하단 중앙의 버튼을 눌렀을 때 유리 구슬 모양의 탭 버튼들이 부채꼴로 퍼져요. 모든 탭이 엄지 반경 안에 있어 한 손으로 조작하기 편해요. (실험적 기능)\n‘두 번 눌러 바로 이동’을 켜면 메뉴가 닫혀 있을 때 버튼을 빠르게 두 번 눌러 선택한 탭으로 바로 이동해요." : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Choose ‘Glass Bubble Menu’ to replace the bottom tab bar with a single button at the bottom center — tap it and glass-orb tab buttons fan out. Every tab stays within thumb reach for one-handed use. (Experimental)\nTurn on ‘Double-Tap to Jump’ to jump straight to a chosen tab by quickly tapping the button twice while the menu is closed." - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "「ガラスバブルメニュー」を選ぶと、下部タブバーの代わりに画面下中央のボタン1つになり、押すとガラス玉のタブボタンが扇状に広がります。すべてのタブが親指の届く範囲にあり、片手で操作しやすいです。(実験的機能)\n「ダブルタップでジャンプ」をオンにすると、メニューが閉じているときにボタンを素早く2回押して、選んだタブへすぐ移動できます。" - } - } - } - }, "\"시리야, 하루 다님에서 측정 시작\" — 말과 단축어 자동화로 기록해요." : { "localizations" : { "en" : { @@ -303,94 +33,6 @@ } } }, - "%@ · %@" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ · %@" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ · %@" - } - }, - "ko" : { - "stringUnit" : { - "state" : "new", - "value" : "%1$@ · %2$@" - } - } - } - }, - "%@ · %@ %@" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%1$@ · %3$@ %2$@" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ · %@ %@" - } - }, - "ko" : { - "stringUnit" : { - "state" : "new", - "value" : "%1$@ · %2$@ %3$@" - } - } - } - }, - "%@ / %@" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%1$@ / %2$@" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "%1$@ / %2$@" - } - }, - "ko" : { - "stringUnit" : { - "state" : "new", - "value" : "%1$@ / %2$@" - } - } - } - }, - "%@ / 한도 %@" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%1$@ / limit %2$@" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "%1$@ / 上限%2$@" - } - }, - "ko" : { - "stringUnit" : { - "state" : "new", - "value" : "%1$@ / 한도 %2$@" - } - } - } - }, "%@ %@" : { "localizations" : { "en" : { @@ -501,6 +143,50 @@ } } }, + "%@ / %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ / %2$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ / %2$@" + } + }, + "ko" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@ / %2$@" + } + } + } + }, + "%@ / 한도 %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ / limit %2$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ / 上限%2$@" + } + }, + "ko" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@ / 한도 %2$@" + } + } + } + }, "%@ ~ %@" : { "localizations" : { "en" : { @@ -523,6 +209,50 @@ } } }, + "%@ · %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ · %@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ · %@" + } + }, + "ko" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@ · %2$@" + } + } + } + }, + "%@ · %@ %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · %3$@ %2$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ · %@ %@" + } + }, + "ko" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@ · %2$@ %3$@" + } + } + } + }, "%@ 기록" : { "localizations" : { "en" : { @@ -786,28 +516,6 @@ } } }, - "%lld/%lld" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld/%lld" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld/%lld" - } - }, - "ko" : { - "stringUnit" : { - "state" : "new", - "value" : "%1$lld/%2$lld" - } - } - } - }, "%lld%%" : { "localizations" : { "en" : { @@ -840,6 +548,28 @@ } } }, + "%lld/%lld" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld/%lld" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld/%lld" + } + }, + "ko" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$lld/%2$lld" + } + } + } + }, "%lld개" : { "localizations" : { "en" : { @@ -1038,6 +768,142 @@ } } }, + "'%@ · %@' %@ 진행률: %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "'%@ · %@' %@ progress: %@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "「%@ · %@」の%@進捗率: %@" + } + }, + "ko" : { + "stringUnit" : { + "state" : "new", + "value" : "'%1$@ · %2$@' %3$@ 진행률: %4$@" + } + } + } + }, + "'%@ · %@' 오늘은 수행일이 아니에요." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "'%1$@ · %2$@' isn't scheduled for today." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "「%1$@ · %2$@」は今日は実行日ではありません。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "new", + "value" : "'%1$@ · %2$@' 오늘은 수행일이 아니에요." + } + } + } + }, + "'%@' %@ 진행률은 %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" : "目標「%@」にはまだクエストがありません。" + } + } + } + }, + "'%@' 측정이 %lld시간을 넘겼어요. 종료를 잊으셨다면 열어서 꺼 주세요." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tracking '%@' has passed %lld hours. If you forgot to stop it, open the app and turn it off." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "「%@」の計測が%lld時間を超えました。終了し忘れた場合はアプリで停止してください。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "new", + "value" : "'%1$@' 측정이 %2$lld시간을 넘겼어요. 종료를 잊으셨다면 열어서 꺼 주세요." + } + } + } + }, + "'%@'은(는) 시간 측정 행동이에요. 측정 시작/종료를 사용해 주세요." : { + "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" : { @@ -1102,6 +968,38 @@ } } }, + "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時間" + } + } + } + }, "1분" : { "localizations" : { "en" : { @@ -1150,6 +1048,22 @@ } } }, + "30초" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "30 sec" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "30秒" + } + } + } + }, "3시간" : { "localizations" : { "en" : { @@ -1214,102 +1128,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" : { - "stringUnit" : { - "state" : "translated", - "value" : "① Choose a target" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "① 対象を選択" - } - } - } - }, - "② 주기 선택" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "② Choose a cycle" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "② 周期を選択" - } - } - } - }, - "③ 목표량과 방향" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "③ Amount & direction" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "③ 目標量と方向" - } - } - } - }, "CSV 만들기" : { "localizations" : { "en" : { @@ -1342,54 +1160,6 @@ } } }, - "iCloud 동기화는 네트워크 상황에 따라 몇 초에서 몇 분까지 걸릴 수 있어요. 다른 기기의 기록이 안 보이면 모음 탭 왼쪽 위 새로고침 버튼을 눌러 주세요. 그래도 안 보이면 잠시 후 다시 시도하거나 두 기기 모두 인터넷 연결을 확인해 주세요." : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "iCloud sync can take from a few seconds to a few minutes depending on the network. If records from another device are missing, tap the refresh button at the top left of the Home tab. If they still don’t appear, try again shortly or check the internet connection on both devices." - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "iCloud同期はネットワーク状況により数秒〜数分かかることがあります。ほかのデバイスの記録が見えないときは、ホームタブ左上の更新ボタンをタップしてください。それでも表示されない場合は、しばらくしてから再試行するか、両方のデバイスのインターネット接続を確認してください。" - } - } - } - }, - "iCloud로 기기 간 데이터가 동기화돼요." : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Your data syncs across devices via iCloud." - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "iCloudでデバイス間のデータが同期されます。" - } - } - } - }, - "iCloud로 아이패드 등 다른 기기와 데이터가 동기화돼요. 변경 사항은 앱을 완전히 종료했다가 다시 실행하면 적용돼요." : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sync your data with iPad and other devices via iCloud. Changes take effect after fully quitting and relaunching the app." - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "iCloudでiPadなどの他のデバイスとデータを同期します。変更はアプリを完全に終了して再起動すると反映されます。" - } - } - } - }, "PDF 가져오기" : { "localizations" : { "en" : { @@ -1438,18 +1208,232 @@ } } }, - "값" : { + "iCloud 동기화는 네트워크 상황에 따라 몇 초에서 몇 분까지 걸릴 수 있어요. 다른 기기의 기록이 안 보이면 모음 탭 왼쪽 위 새로고침 버튼을 눌러 주세요. 그래도 안 보이면 잠시 후 다시 시도하거나 두 기기 모두 인터넷 연결을 확인해 주세요." : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Value" + "value" : "iCloud sync can take from a few seconds to a few minutes depending on the network. If records from another device are missing, tap the refresh button at the top left of the Home tab. If they still don’t appear, try again shortly or check the internet connection on both devices." } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "値" + "value" : "iCloud同期はネットワーク状況により数秒〜数分かかることがあります。ほかのデバイスの記録が見えないときは、ホームタブ左上の更新ボタンをタップしてください。それでも表示されない場合は、しばらくしてから再試行するか、両方のデバイスのインターネット接続を確認してください。" + } + } + } + }, + "iCloud로 기기 간 데이터가 동기화돼요." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Your data syncs across devices via iCloud." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "iCloudでデバイス間のデータが同期されます。" + } + } + } + }, + "iCloud로 아이패드 등 다른 기기와 데이터가 동기화돼요. 변경 사항은 앱을 완전히 종료했다가 다시 실행하면 적용돼요." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sync your data with iPad and other devices via iCloud. Changes take effect after fully quitting and relaunching the app." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "iCloudでiPadなどの他のデバイスとデータを同期します。変更はアプリを完全に終了して再起動すると反映されます。" + } + } + } + }, + "‘%@’ 꼬리표를 삭제할까요? 행동은 삭제되지 않아요." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delete the tag ‘%@’? Its actions won't be deleted." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "タグ「%@」を削除しますか?アクションは削除されません。" + } + } + } + }, + "‘%@’ 목표를 달성했나요?" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Did you achieve the goal ‘%@’?" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "目標「%@」を達成しましたか?" + } + } + } + }, + "‘%@’ 목표를 삭제할까요? 다짐도 함께 삭제됩니다." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delete the goal ‘%@’? Its quests will also be deleted." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "目標「%@」を削除しますか?クエストも一緒に削除されます。" + } + } + } + }, + "‘%@’ 양식을 삭제할까요?" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delete the \"%@\" template?" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "「%@」のテンプレートを削除しますか?" + } + } + } + }, + "‘%@’ 양식을 삭제할까요? 이 양식을 쓰는 노트 페이지 %lld개는 빈 캔버스로 표시돼요. (필기와 요소는 그대로 남아요)" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delete the \"%1$@\" template? %2$lld note pages using it will show as a blank canvas. (Your handwriting and items are kept.)" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "「%1$@」のテンプレートを削除しますか?このテンプレートを使う%2$lld枚のノートページは白紙のキャンバスとして表示されます。(手書きや要素はそのまま残ります)" + } + }, + "ko" : { + "stringUnit" : { + "state" : "new", + "value" : "‘%1$@’ 양식을 삭제할까요? 이 양식을 쓰는 노트 페이지 %2$lld개는 빈 캔버스로 표시돼요. (필기와 요소는 그대로 남아요)" + } + } + } + }, + "‘%@’ 행동을 삭제할까요? 기록도 함께 삭제됩니다." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delete the action ‘%@’? Its records will also be deleted." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アクション「%@」を削除しますか?記録も一緒に削除されます。" + } + } + } + }, + "‘글래스 버블 메뉴’를 고르면 하단 탭바 대신 화면 하단 중앙의 버튼을 눌렀을 때 유리 구슬 모양의 탭 버튼들이 부채꼴로 퍼져요. 모든 탭이 엄지 반경 안에 있어 한 손으로 조작하기 편해요. (실험적 기능)" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Choose 'Glass Bubble Menu' to replace the bottom tab bar: tap the button at the bottom center and glass-orb tab buttons fan out in an arc. Every tab stays within thumb reach for easy one-handed use. (Experimental)" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "「ガラスバブルメニュー」を選ぶと、下部タブバーの代わりに画面下中央のボタンをタップしたとき、ガラス玉のタブボタンが扇形に広がります。すべてのタブが親指の届く範囲にあり、片手でも操作しやすいです。(実験的機能)" + } + } + } + }, + "‘글래스 버블 메뉴’를 고르면 하단 탭바 대신 화면 하단 중앙의 버튼을 눌렀을 때 유리 구슬 모양의 탭 버튼들이 부채꼴로 퍼져요. 모든 탭이 엄지 반경 안에 있어 한 손으로 조작하기 편해요. (실험적 기능)\n‘두 번 눌러 바로 이동’을 켜면 메뉴가 닫혀 있을 때 버튼을 빠르게 두 번 눌러 선택한 탭으로 바로 이동해요." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Choose ‘Glass Bubble Menu’ to replace the bottom tab bar with a single button at the bottom center — tap it and glass-orb tab buttons fan out. Every tab stays within thumb reach for one-handed use. (Experimental)\nTurn on ‘Double-Tap to Jump’ to jump straight to a chosen tab by quickly tapping the button twice while the menu is closed." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "「ガラスバブルメニュー」を選ぶと、下部タブバーの代わりに画面下中央のボタン1つになり、押すとガラス玉のタブボタンが扇状に広がります。すべてのタブが親指の届く範囲にあり、片手で操作しやすいです。(実験的機能)\n「ダブルタップでジャンプ」をオンにすると、メニューが閉じているときにボタンを素早く2回押して、選んだタブへすぐ移動できます。" + } + } + } + }, + "① 대상 선택" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "① Choose a target" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "① 対象を選択" + } + } + } + }, + "② 주기 선택" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "② Choose a cycle" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "② 周期を選択" + } + } + } + }, + "③ 목표량과 방향" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "③ Amount & direction" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "③ 目標量と方向" } } } @@ -1518,6 +1502,22 @@ } } }, + "값" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Value" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "値" + } + } + } + }, "개수 제한 해제" : { "localizations" : { "en" : { @@ -2407,22 +2407,6 @@ } } }, - "꼬리표 — 색으로 분류" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tags — Organize by Color" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "タグ — 色で分類" - } - } - } - }, "꼬리표 (복수 선택 가능)" : { "localizations" : { "en" : { @@ -2439,6 +2423,22 @@ } } }, + "꼬리표 — 색으로 분류" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tags — Organize by Color" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "タグ — 色で分類" + } + } + } + }, "꼬리표 삭제" : { "localizations" : { "en" : { @@ -3096,22 +3096,6 @@ } } }, - "다짐 (하루)" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Quests (daily)" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "クエスト(1日)" - } - } - } - }, "다짐 %lld개" : { "localizations" : { "en" : { @@ -3144,6 +3128,22 @@ } } }, + "다짐 (하루)" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Quests (daily)" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "クエスト(1日)" + } + } + } + }, "다짐 수정" : { "localizations" : { "en" : { @@ -4385,23 +4385,6 @@ } } }, - "목" : { - "comment" : "요일 축약(목요일)", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Thu" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "木" - } - } - } - }, "모음" : { "localizations" : { "en" : { @@ -4466,6 +4449,23 @@ } } }, + "목" : { + "comment" : "요일 축약(목요일)", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thu" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "木" + } + } + } + }, "목록" : { "localizations" : { "en" : { @@ -4498,22 +4498,6 @@ } } }, - "목표 1개 · 다짐 16개" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "1 goal · 16 quests" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "目標1件・クエスト16件" - } - } - } - }, "목표 1개 + 다짐 진행률" : { "localizations" : { "en" : { @@ -4546,6 +4530,22 @@ } } }, + "목표 1개 · 다짐 16개" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "1 goal · 16 quests" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "目標1件・クエスト16件" + } + } + } + }, "목표 2개" : { "localizations" : { "en" : { @@ -4882,22 +4882,6 @@ } } }, - "목표·다짐" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Goals & Quests" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "目標・クエスト" - } - } - } - }, "목표+다짐 2개 (위아래)" : { "localizations" : { "en" : { @@ -4914,6 +4898,22 @@ } } }, + "목표·다짐" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Goals & Quests" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "目標・クエスト" + } + } + } + }, "목표가 끝날 때 다짐들의 평균 달성률이 이 값 이상이면 '달성 완료'로 판정해요. 100%면 모든 다짐을 채워야 달성이에요. 다짐이 없는 목표는 지금처럼 직접 선택해요." : { "localizations" : { "en" : { @@ -5026,22 +5026,6 @@ } } }, - "목표의 하루·주간·월간 진행률을 진행바로 확인해요. (프리미엄)" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Track a goal's daily, weekly, and monthly progress with bars. (Premium)" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "目標の日・週・月の進捗をバーで確認。(プレミアム)" - } - } - } - }, "목표의 하루/주간/월간 진행률(%)을 가져와요. 소속 다짐들의 진행률 평균이에요." : { "localizations" : { "en" : { @@ -5058,6 +5042,22 @@ } } }, + "목표의 하루·주간·월간 진행률을 진행바로 확인해요. (프리미엄)" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Track a goal's daily, weekly, and monthly progress with bars. (Premium)" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "目標の日・週・月の進捗をバーで確認。(プレミアム)" + } + } + } + }, "무료 버전에서는 목표당 다짐을 최대 %lld개까지 만들 수 있어요. 프리미엄으로 업그레이드하면 무제한으로 사용할 수 있습니다." : { "localizations" : { "en" : { @@ -5273,6 +5273,22 @@ } } }, + "미리 설정된 색 %lld" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Preset color %lld" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プリセットカラー %lld" + } + } + } + }, "반복 주기" : { "localizations" : { "en" : { @@ -5817,38 +5833,6 @@ } } }, - "새로고침 버튼" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Refresh Button" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "更新ボタン" - } - } - } - }, - "색" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Color" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "色" - } - } - } - }, "새로고침" : { "localizations" : { "en" : { @@ -5865,6 +5849,22 @@ } } }, + "새로고침 버튼" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Refresh Button" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "更新ボタン" + } + } + } + }, "새해 다짐 정리하기" : { "extractionState" : "manual", "localizations" : { @@ -5882,6 +5882,22 @@ } } }, + "색" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Color" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "色" + } + } + } + }, "생활" : { "localizations" : { "en" : { @@ -7440,22 +7456,6 @@ } } }, - "오른쪽 위 ‘순서’를 누르면 다짐 순서를 바꿀 수 있어요." : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tap ‘Reorder’ in the top right to reorder quests." - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "右上の「順序」からクエストを並べ替えられます。" - } - } - } - }, "오른쪽 위 + 버튼으로 꼬리표를 추가하세요." : { "localizations" : { "en" : { @@ -7488,6 +7488,22 @@ } } }, + "오른쪽 위 ‘순서’를 누르면 다짐 순서를 바꿀 수 있어요." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tap ‘Reorder’ in the top right to reorder quests." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "右上の「順序」からクエストを並べ替えられます。" + } + } + } + }, "오른쪽 핸들을 끌어서 순서를 바꾼 뒤 ‘완료’를 누르세요. 순서는 저장되어 유지돼요." : { "localizations" : { "en" : { @@ -7520,6 +7536,22 @@ } } }, + "완료 표시 해제" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mark as not done" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "完了を解除" + } + } + } + }, "완료된 목표" : { "localizations" : { "en" : { @@ -7568,6 +7600,22 @@ } } }, + "완료로 표시" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mark as done" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "完了にする" + } + } + } + }, "외 %lld개 측정 중" : { "localizations" : { "en" : { @@ -7680,39 +7728,6 @@ } } }, - "워치에서 기록하기" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tracking on the Watch" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "Watchで記録する" - } - } - } - }, - "월" : { - "comment" : "요일 축약(월요일)", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mon" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "月" - } - } - } - }, "워치 앱을 열면 꼬리표 목록이 보여요. 꼬리표를 탭하면 그 안의 행동들이 나오고, 행동을 탭하면 시간형은 측정 시작·종료, 횟수형은 +1이 기록돼요. 아이폰 앱에서 즐겨찾기한 행동은 첫 화면 맨 위 '즐겨찾기'에서 한 탭 만에 열 수 있어요. 측정 중일 때는 첫 화면 맨 위에 측정 중인 행동과 시간이 보이고, 그 줄을 탭하면 바로 측정이 종료돼요. 기록은 아이폰과 자동으로 동기화돼요." : { "localizations" : { "en" : { @@ -7745,6 +7760,22 @@ } } }, + "워치에서 기록하기" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tracking on the Watch" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Watchで記録する" + } + } + } + }, "워치에서 추적하고 컴플리케이션으로 확인해요." : { "localizations" : { "en" : { @@ -7841,6 +7872,23 @@ } } }, + "월" : { + "comment" : "요일 축약(월요일)", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mon" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "月" + } + } + } + }, "월 구독" : { "localizations" : { "en" : { @@ -8129,39 +8177,6 @@ } } }, - "이번 달 누적" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "This month's total" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "今月の累計" - } - } - } - }, - "일" : { - "comment" : "요일 축약(일요일)", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sun" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "日" - } - } - } - }, "이동할 탭" : { "localizations" : { "en" : { @@ -8322,6 +8337,22 @@ } } }, + "이번 달 누적" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "This month's total" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "今月の累計" + } + } + } + }, "이번 달에 쓴 일기 %lld편" : { "localizations" : { "en" : { @@ -8530,6 +8561,23 @@ } } }, + "일" : { + "comment" : "요일 축약(일요일)", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sun" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "日" + } + } + } + }, "일·업무" : { "localizations" : { "en" : { @@ -9597,6 +9645,22 @@ } } }, + "즐겨찾기 빠른 실행" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Favorites quick access" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "お気に入りクイック実行" + } + } + } + }, "즐겨찾기 해제" : { "localizations" : { "en" : { @@ -11222,22 +11286,6 @@ } } }, - "하루 다님 — 하루 습관 및 시간 추적" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Haru Danim — Daily habit & time tracking" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "Haru Danim — 毎日の習慣・時間トラッキング" - } - } - } - }, "하루 다님 · 목표 없음" : { "localizations" : { "en" : { @@ -11270,6 +11318,22 @@ } } }, + "하루 다님 — 하루 습관 및 시간 추적" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Haru Danim — Daily habit & time tracking" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Haru Danim — 毎日の習慣・時間トラッキング" + } + } + } + }, "하루 다님 일기" : { "localizations" : { "en" : { @@ -11767,22 +11831,6 @@ } } }, - "행동 — 추적의 최소 단위" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Actions — the Unit of Tracking" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "アクション — 記録の最小単位" - } - } - } - }, "행동 %lld/%lld개 표시 중" : { "localizations" : { "en" : { @@ -11859,6 +11907,22 @@ } } }, + "행동 — 추적의 최소 단위" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actions — the Unit of Tracking" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アクション — 記録の最小単位" + } + } + } + }, "행동 누적값 가져오기" : { "localizations" : { "en" : { @@ -11875,18 +11939,18 @@ } } }, - "행동 버튼을 길게 누르면 기록 직접 입력·수정, 메모, 기록 확인, 통계 보기, 행동 설정 수정, 삭제를 할 수 있어요. 시간형은 시작·종료 시각을, 횟수형은 횟수와 시각을 직접 고칠 수 있어요." : { + "행동 버튼을 길게 누르면 기록 직접 입력·수정, 즐겨찾기 지정·해제, 기록 확인, 통계 보기, 행동 설정 수정, 삭제를 할 수 있어요. 시간형은 시작·종료 시각을, 횟수형은 횟수와 시각을 직접 고칠 수 있어요." : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Long-press an action button to enter or edit records directly, add memos, view records or stats, edit the action, or delete it. Time actions let you fix start and end times; count actions let you fix the count and timestamp." + "value" : "Long-press an action button to manually enter or edit records, toggle Favorite, view records, view stats, edit action settings, or delete it. For time-based actions you can edit the start and end times directly; for count-based actions you can edit the count and time." } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "アクションボタンを長押しすると、記録の直接入力・修正、メモ、記録の確認、統計の表示、アクション設定の修正、削除ができます。時間型は開始・終了時刻を、回数型は回数と時刻を直接修正できます。" + "value" : "行動ボタンを長押しすると、記録の直接入力・編集、お気に入りの設定・解除、記録の確認、統計の表示、行動設定の編集、削除ができます。時間型は開始・終了時刻を、回数型は回数と時刻を直接編集できます。" } } } @@ -12131,6 +12195,22 @@ } } }, + "행동id" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "actionId" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アクションID" + } + } + } + }, "행동·목표·다짐을 무제한으로 만들 수 있어요." : { "localizations" : { "en" : { @@ -12163,22 +12243,6 @@ } } }, - "행동id" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "actionId" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "アクションID" - } - } - } - }, "행동별 시간 (일별)" : { "localizations" : { "en" : { @@ -12323,6 +12387,22 @@ } } }, + "행동을 즐겨찾기로 지정하면 모음 탭 상단에 '즐겨찾기' 줄이 생겨 가로로 훑어 바로 실행할 수 있어요. 행동이 많아져 아래 전체 목록을 찾기 번거로울 때 자주 쓰는 행동만 모아 두는 용도예요 — 즐겨찾기해도 전체 목록에서는 사라지지 않아요. 지정은 행동 버튼을 길게 눌러 '즐겨찾기'를 선택하거나, 행동 탭에서 행동을 열어 별 아이콘을 눌러도 돼요." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mark an action as a favorite and a 'Favorites' row appears at the top of the Home tab, letting you swipe through and run it right away. It's meant for keeping your most-used actions handy once the full list below gets long to scroll through — favoriting doesn't remove the action from the full list. Long-press an action button and choose 'Favorite', or open it from the Actions tab and tap the star icon." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "行動をお気に入りに指定すると、モーム(集約)タブの上部に「お気に入り」の行が表示され、横にスワイプしてすぐに実行できます。下の全リストが長くなって探しにくいときに、よく使う行動だけをまとめておくためのものです — お気に入りにしても全リストから消えることはありません。指定は行動ボタンを長押しして「お気に入り」を選ぶか、行動タブで行動を開いて星アイコンをタップしても行えます。" + } + } + } + }, "행동을 찾을 수 없어요. 앱에서 삭제됐을 수 있어요." : { "localizations" : { "en" : { diff --git a/myApp/HaruDanim/IOS/Views/DiaryView.swift b/myApp/HaruDanim/IOS/Views/DiaryView.swift index 996e631..91d1f46 100644 --- a/myApp/HaruDanim/IOS/Views/DiaryView.swift +++ b/myApp/HaruDanim/IOS/Views/DiaryView.swift @@ -1965,6 +1965,7 @@ private struct DiaryTodoRow: View { } .buttonStyle(.plain) .disabled(readOnly) + .accessibilityLabel(todo.isDone ? String(localized: "완료 표시 해제") : String(localized: "완료로 표시")) TextField("할 일", text: $todo.text) .font(.subheadline) diff --git a/myApp/HaruDanim/IOS/Views/HelpView.swift b/myApp/HaruDanim/IOS/Views/HelpView.swift index 8826c97..0abfcbd 100644 --- a/myApp/HaruDanim/IOS/Views/HelpView.swift +++ b/myApp/HaruDanim/IOS/Views/HelpView.swift @@ -186,10 +186,15 @@ struct HelpView: View { title: "짧은 기록 무시", body: "설정 → 측정에서 '짧은 기록 무시'를 켜면 설정한 시간보다 짧게 측정하고 끝낸 기록은 저장하지 않아요. 행동 버튼을 실수로 눌렀다가 바로 껐을 때 의미 없는 기록이 쌓이는 것을 막아 줘요. 기준 시간은 3초부터 1분까지 고를 수 있고, 위젯·워치·시리로 종료할 때도 적용돼요. 이 설정은 기기마다 따로 저장되고 측정을 끝낸 기기의 설정이 적용되니, 아이폰과 아이패드를 함께 쓴다면 두 기기 모두 설정해 주세요. 애플워치에서 끝낸 측정은 페어링된 아이폰의 설정을 따라요." ), + HelpTopic( + symbol: "star.fill", + title: "즐겨찾기 빠른 실행", + body: "행동을 즐겨찾기로 지정하면 모음 탭 상단에 '즐겨찾기' 줄이 생겨 가로로 훑어 바로 실행할 수 있어요. 행동이 많아져 아래 전체 목록을 찾기 번거로울 때 자주 쓰는 행동만 모아 두는 용도예요 — 즐겨찾기해도 전체 목록에서는 사라지지 않아요. 지정은 행동 버튼을 길게 눌러 '즐겨찾기'를 선택하거나, 행동 탭에서 행동을 열어 별 아이콘을 눌러도 돼요." + ), HelpTopic( symbol: "hand.tap.fill", title: "길게 누르기 메뉴", - body: "행동 버튼을 길게 누르면 기록 직접 입력·수정, 메모, 기록 확인, 통계 보기, 행동 설정 수정, 삭제를 할 수 있어요. 시간형은 시작·종료 시각을, 횟수형은 횟수와 시각을 직접 고칠 수 있어요." + body: "행동 버튼을 길게 누르면 기록 직접 입력·수정, 즐겨찾기 지정·해제, 기록 확인, 통계 보기, 행동 설정 수정, 삭제를 할 수 있어요. 시간형은 시작·종료 시각을, 횟수형은 횟수와 시각을 직접 고칠 수 있어요." ), HelpTopic( symbol: "note.text", diff --git a/myApp/HaruDanim/IOS/Views/MainView.swift b/myApp/HaruDanim/IOS/Views/MainView.swift index e697945..0c78eba 100644 --- a/myApp/HaruDanim/IOS/Views/MainView.swift +++ b/myApp/HaruDanim/IOS/Views/MainView.swift @@ -25,6 +25,13 @@ struct MainView: View { LocalPrefs.orderedActions(actions, raw: actionOrderRaw) } + /// 즐겨찾기 빠른 실행 줄에 노출할 행동 (기기별 배치 순서 유지). 전체 그리드에서 제외하지 않고 + /// 추가로만 보여준다 — 행동이 많아져도 즐겨찾기는 항상 스크롤 없이 접근 가능하게 하는 목적 + /// (전체 그리드에서 숨기면 즐겨찾기 아닌 행동을 찾기 더 어려워지는 트레이드오프 방지, CLAUDE.md §6.1) + private var favoriteActions: [Action] { + orderedActions.filter(\.isFavorite) + } + private var pinnedGoals: [Goal] { allGoals.filter { LocalPrefs.contains($0.uuid, in: pinnedGoalsRaw) } } @@ -63,6 +70,9 @@ struct MainView: View { if anyRunning && !isEditing { runningArea } + if !favoriteActions.isEmpty && !isEditing { + favoritesArea + } if isEditing { layoutControl } @@ -241,6 +251,31 @@ struct MainView: View { .background(AppTheme.surface, in: RoundedRectangle(cornerRadius: 18, style: .continuous)) } + // MARK: 즐겨찾기 빠른 실행 영역 (행동이 많아져도 항상 가로 스크롤 한 줄로 접근) + + private var favoritesArea: some View { + VStack(alignment: .leading, spacing: 8) { + Label("즐겨찾기", systemImage: "star.fill") + .font(.subheadline.weight(.semibold)) + .foregroundStyle(AppTheme.green) + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 10) { + ForEach(favoriteActions) { action in + ActionButtonCell(action: action, isEditing: false, compact: true) { + handleTap(action) + } + .frame(width: 108) + .contextMenu { + actionContextMenu(for: action) + } + } + } + } + } + .padding(14) + .background(AppTheme.surface, in: RoundedRectangle(cornerRadius: 18, style: .continuous)) + } + // MARK: 배치 편집 컨트롤 (한 줄 개수 선택) private var layoutControl: some View { @@ -329,46 +364,61 @@ struct MainView: View { } else { base .contextMenu { - Button { - // 기록 탭으로 이동해 이 행동만 필터링된 상태로 표시 - router.openHistory( - filtering: action.persistentModelID, - visibleTabsRaw: visibleTabsRaw - ) - } label: { - Label("기록 확인", systemImage: "list.bullet.rectangle") - } - Button { - // 통계 탭으로 이동해 이 행동만 필터링된 상태로 표시 - router.openStats( - filtering: action.persistentModelID, - visibleTabsRaw: visibleTabsRaw - ) - } label: { - Label("통계 보기", systemImage: "chart.xyaxis.line") - } - Button { - recordsAction = action - } label: { - Label( - action.trackingType == .time ? String(localized: "시작·종료 시각 수동 입력") : String(localized: "횟수 직접 입력·수정"), - systemImage: "square.and.pencil" - ) - } - Button { - settingsEditingAction = action - } label: { - Label("행동 설정 수정", systemImage: "slider.horizontal.3") - } - Button(role: .destructive) { - deletingAction = action - } label: { - Label("행동 삭제", systemImage: "trash") - } + actionContextMenu(for: action) } } } + /// 행동 셀 롱프레스 메뉴 — 모음 탭 전체 그리드와 즐겨찾기 빠른 실행 줄이 공유 + @ViewBuilder + private func actionContextMenu(for action: Action) -> some View { + Button { + // 기록 탭으로 이동해 이 행동만 필터링된 상태로 표시 + router.openHistory( + filtering: action.persistentModelID, + visibleTabsRaw: visibleTabsRaw + ) + } label: { + Label("기록 확인", systemImage: "list.bullet.rectangle") + } + Button { + // 통계 탭으로 이동해 이 행동만 필터링된 상태로 표시 + router.openStats( + filtering: action.persistentModelID, + visibleTabsRaw: visibleTabsRaw + ) + } label: { + Label("통계 보기", systemImage: "chart.xyaxis.line") + } + Button { + recordsAction = action + } label: { + Label( + action.trackingType == .time ? String(localized: "시작·종료 시각 수동 입력") : String(localized: "횟수 직접 입력·수정"), + systemImage: "square.and.pencil" + ) + } + Button { + action.isFavorite.toggle() + DataChange.commit(context: context) + } label: { + Label( + action.isFavorite ? String(localized: "즐겨찾기 해제") : String(localized: "즐겨찾기"), + systemImage: action.isFavorite ? "star.slash" : "star.fill" + ) + } + Button { + settingsEditingAction = action + } label: { + Label("행동 설정 수정", systemImage: "slider.horizontal.3") + } + Button(role: .destructive) { + deletingAction = action + } label: { + Label("행동 삭제", systemImage: "trash") + } + } + private var emptyState: some View { VStack(spacing: 12) { Image(systemName: "square.grid.2x2") diff --git a/myApp/HaruDanim/IOS/Views/SymbolPickerView.swift b/myApp/HaruDanim/IOS/Views/SymbolPickerView.swift index 00353cd..ec6c898 100644 --- a/myApp/HaruDanim/IOS/Views/SymbolPickerView.swift +++ b/myApp/HaruDanim/IOS/Views/SymbolPickerView.swift @@ -50,6 +50,7 @@ struct SymbolPickerView: View { ) } .buttonStyle(.plain) + .accessibilityLabel(Text(verbatim: symbol)) } } .padding() diff --git a/myApp/HaruDanim/IOS/Views/TagViews.swift b/myApp/HaruDanim/IOS/Views/TagViews.swift index 144b7d8..940e380 100644 --- a/myApp/HaruDanim/IOS/Views/TagViews.swift +++ b/myApp/HaruDanim/IOS/Views/TagViews.swift @@ -125,7 +125,7 @@ struct TagEditorView: View { Section("색") { ColorPicker("직접 선택", selection: $color, supportsOpacity: false) LazyVGrid(columns: [GridItem(.adaptive(minimum: 40), spacing: 10)], spacing: 10) { - ForEach(AppTheme.tagPresets, id: \.self) { hex in + ForEach(Array(AppTheme.tagPresets.enumerated()), id: \.offset) { index, hex in Button { color = Color(hex: hex) } label: { @@ -141,6 +141,8 @@ struct TagEditorView: View { } } .buttonStyle(.plain) + .accessibilityLabel(String(localized: "미리 설정된 색 \(index + 1)")) + .accessibilityAddTraits(color.hexString == hex ? .isSelected : []) } } .padding(.vertical, 4)