feat: 国际化(i18n) en/ja/ko + App 内语言切换
主体:多语言支持(简体中文源 + 英/日/韩)
- 基础设施:Localizable.xcstrings(String Catalog,sourceLanguage=zh-Hans)
+ pbxproj developmentRegion/knownRegions 注册 en/ja/ko
- 全部硬编码 Locale("zh_CN") → Locale.current;中文 dateFormat → Date.FormatStyle(跟随系统)
- UI 中文字面量统一为 String(appLoc:)(显式绑定所选语言 bundle+locale,即时切换)
Text 字面量走环境 \.locale + Bundle 重定向
- 549 个 catalog key 全部 en/ja/ko 翻译完成(0 未翻译)
- App 内语言切换:我的 → 语言(LanguageManager + 即时生效,无需重启)
- 双用预设(症状/监测指标/慢病)本地化:static→computed 避免缓存
注:本提交为 WIP,一并打包了并行进行的功能模块
(HealthExport 健康导出、Security/Face ID 锁、DiaryAssist 日记 AI 辅助)
及 App 图标、CLAUDE.md、docs/scripts。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,16 +3,31 @@ import SwiftUI
|
||||
struct CalendarMonthGrid: View {
|
||||
let monthAnchor: Date
|
||||
let data: CalendarData
|
||||
let selectedDate: Date?
|
||||
let onTapDay: (Date) -> Void
|
||||
|
||||
init(monthAnchor: Date,
|
||||
data: CalendarData,
|
||||
selectedDate: Date? = nil,
|
||||
onTapDay: @escaping (Date) -> Void) {
|
||||
self.monthAnchor = monthAnchor
|
||||
self.data = data
|
||||
self.selectedDate = selectedDate
|
||||
self.onTapDay = onTapDay
|
||||
}
|
||||
|
||||
private let calendar: Calendar = {
|
||||
var c = Calendar(identifier: .gregorian)
|
||||
c.firstWeekday = 2 // 周一开始
|
||||
c.locale = Locale(identifier: "zh_CN")
|
||||
c.locale = Locale.current
|
||||
return c
|
||||
}()
|
||||
|
||||
private let weekdayLabels = ["一", "二", "三", "四", "五", "六", "日"]
|
||||
private let weekdayLabels = [
|
||||
String(appLoc: "一"), String(appLoc: "二"), String(appLoc: "三"),
|
||||
String(appLoc: "四"), String(appLoc: "五"), String(appLoc: "六"),
|
||||
String(appLoc: "日")
|
||||
]
|
||||
private let columns = Array(repeating: GridItem(.flexible(), spacing: 4), count: 7)
|
||||
|
||||
private var days: [DayCell] {
|
||||
@@ -64,6 +79,9 @@ struct CalendarMonthGrid: View {
|
||||
ranges: data.ranges(touching: cell.date, calendar: calendar),
|
||||
marks: data.marks(for: cell.date, calendar: calendar),
|
||||
isToday: calendar.isDateInToday(cell.date),
|
||||
isSelected: selectedDate.map {
|
||||
calendar.isDate(cell.date, inSameDayAs: $0)
|
||||
} ?? false,
|
||||
calendar: calendar
|
||||
)
|
||||
.onTapGesture { onTapDay(cell.date) }
|
||||
@@ -84,6 +102,7 @@ private struct DayCellView: View {
|
||||
let ranges: [SymptomRange]
|
||||
let marks: DayMarks
|
||||
let isToday: Bool
|
||||
let isSelected: Bool
|
||||
let calendar: Calendar
|
||||
|
||||
private var dayNumber: Int {
|
||||
@@ -92,14 +111,20 @@ private struct DayCellView: View {
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .top) {
|
||||
// 背景:今天高亮
|
||||
// 背景层:selected > today
|
||||
RoundedRectangle(cornerRadius: 6, style: .continuous)
|
||||
.fill(isToday ? Tj.Palette.sand2 : Color.clear)
|
||||
.fill(backgroundFill)
|
||||
|
||||
// 选中描边
|
||||
if isSelected {
|
||||
RoundedRectangle(cornerRadius: 6, style: .continuous)
|
||||
.strokeBorder(Tj.Palette.brick, lineWidth: 1.5)
|
||||
}
|
||||
|
||||
VStack(spacing: 2) {
|
||||
Text("\(dayNumber)")
|
||||
.font(.system(size: 13,
|
||||
weight: isToday ? .bold : .regular,
|
||||
weight: (isToday || isSelected) ? .bold : .regular,
|
||||
design: .default))
|
||||
.foregroundStyle(textColor)
|
||||
.padding(.top, 4)
|
||||
@@ -145,10 +170,17 @@ private struct DayCellView: View {
|
||||
|
||||
private var textColor: Color {
|
||||
if !cell.inCurrentMonth { return Tj.Palette.text3.opacity(0.5) }
|
||||
if isSelected { return Tj.Palette.brick }
|
||||
if isToday { return Tj.Palette.ink }
|
||||
return Tj.Palette.text
|
||||
}
|
||||
|
||||
private var backgroundFill: Color {
|
||||
if isSelected { return Tj.Palette.brickSoft.opacity(0.5) }
|
||||
if isToday { return Tj.Palette.sand2 }
|
||||
return .clear
|
||||
}
|
||||
|
||||
private func symptomBar(_ range: SymptomRange) -> some View {
|
||||
let pos = range.position(cell.date, calendar: calendar)
|
||||
let leadingRadius: CGFloat = (pos == .start || pos == .single) ? 3 : 0
|
||||
|
||||
Reference in New Issue
Block a user