mycode/myApp/HaruDanim/Shared/Formatters.swift
songyc macbook 14b18bc22a fix(review): 전체 코드 리뷰 2차 완료 — 횟수 단위 en 단복수 + 명세 문서화
1차(c18e3c7)에 이어 나머지 전 영역을 세션 본체가 직접 정독 완료:
위젯 10파일·인텐트, 앱 셸(ContentView/Settings/Help/Radial/SymbolCatalog),
워치 전체(페이로드·동기화·앱·컴플리케이션), 일기 전체(6파일 ~4.2k줄),
내보내기 뷰부·CSV, MainView·ActionViews 잔여 — 추가 결함 1건만 발견.

[L] 모음 탭 횟수 셀: 큰 숫자와 단위("회")를 분리 렌더링하는 유일한
    지점이라 en에서 1일 때 "1 times"가 됨 — Format.countUnit(단복수
    분기, 명시 키 count.unit.one/other)로 수정. 단독 "회" 키는 사용처
    소멸로 정리 (ja 「回」는 단복수 무관)

문서(CLAUDE.md): 이번 리뷰로 확정된 동작 규칙 4건을 명세에 반영 —
세션 편집 원본 초 보존·0길이 차단(§6.5), 타임테이블 HH:mm 라벨(§6.5),
필터 활성 실재 행동 기준(§6.5), 통계 동명 구분 Format.disambiguated(§6.6)

정독 제외(사유 명시): DebugSeed·WidgetPreviewScreen·ComplicationPreview
(DEBUG 전용), 워치 엔트리 2파일(41줄), HelpView 본문 문구부(카탈로그
검증으로 커버), ExportImageView PhotoSaver(~40줄)

검증: Debug/Store 빌드, 카탈로그 missing/stale 0

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 21:17:38 +09:00

95 lines
4.0 KiB
Swift

//
// Formatters.swift
// Haru_Danim
//
import Foundation
enum Format {
/// 1:23:45 23:45 ( )
static func timer(_ interval: TimeInterval) -> String {
let total = Int(interval.rounded(.down))
let h = total / 3600
let m = (total % 3600) / 60
let s = total % 60
if h > 0 { return String(format: "%d:%02d:%02d", h, m, s) }
return String(format: "%d:%02d", m, s)
}
/// "2 30" / "45" / "30"
static func durationShort(_ interval: TimeInterval) -> String {
let total = Int(interval.rounded())
let h = total / 3600
let m = (total % 3600) / 60
if h > 0 {
return m > 0
? String(localized: "\(h)시간 \(m)")
: String(localized: "\(h)시간")
}
if m > 0 { return String(localized: "\(m)") }
return String(localized: "\(total)")
}
static func weekdayShort(_ weekday: Int) -> String {
let names = [
String(localized: "", comment: "요일 축약(일요일)"),
String(localized: "", comment: "요일 축약(월요일)"),
String(localized: "", comment: "요일 축약(화요일)"),
String(localized: "", comment: "요일 축약(수요일)"),
String(localized: "", comment: "요일 축약(목요일)"),
String(localized: "", comment: "요일 축약(금요일)"),
String(localized: "", comment: "요일 축약(토요일)"),
]
let index = (weekday - 1) % 7
return names[max(0, index)]
}
static func shortDate(_ date: Date) -> String {
date.formatted(.dateTime.month(.defaultDigits).day())
}
static func fullDate(_ date: Date) -> String {
date.formatted(.dateTime.year().month().day().weekday(.short))
}
static func time(_ date: Date) -> String {
date.formatted(.dateTime.hour().minute())
}
static func percent(_ ratio: Double) -> String {
"\(Int((ratio * 100).rounded()))%"
}
/// ("3.5"). String(format:)
/// · '' .
static func countAverage(_ value: Double) -> String {
let rounded = (value * 10).rounded() / 10
return String(localized: "\(rounded.formatted(.number.precision(.fractionLength(1))))",
comment: "횟수 평균 (소수 1자리)")
}
/// ("") ,
/// . "" (en "times") 1 "1 times"
/// (time/times) . : ko ""
///
static func countUnit(_ count: Int) -> String {
count == 1
? String(localized: "count.unit.one", defaultValue: "")
: String(localized: "count.unit.other", defaultValue: "")
}
/// ··Identifiable id
/// ( · · ) /
/// id . ,
/// " (2)", " (3)" ( .
/// · )
static func disambiguated(_ names: [String]) -> [String] {
var counts: [String: Int] = [:]
return names.map { name in
let n = (counts[name] ?? 0) + 1
counts[name] = n
return n == 1 ? name : "\(name) (\(n))"
}
}
}