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:
@@ -171,7 +171,7 @@ struct IndicatorQuickSheet: View {
|
||||
private var monitorGridSection: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack {
|
||||
sectionLabel("长期监测(进趋势)")
|
||||
sectionLabel(String(appLoc: "长期监测(进趋势)"))
|
||||
Spacer()
|
||||
if !hiddenSet.isEmpty {
|
||||
hiddenCountChip
|
||||
@@ -329,7 +329,7 @@ struct IndicatorQuickSheet: View {
|
||||
|
||||
private var labPresetSection: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
sectionLabel("化验项快捷(不进趋势)")
|
||||
sectionLabel(String(appLoc: "化验项快捷(不进趋势)"))
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(spacing: 8) {
|
||||
ForEach(labPresets) { p in
|
||||
@@ -345,14 +345,14 @@ struct IndicatorQuickSheet: View {
|
||||
private var bpFieldSection: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
HStack {
|
||||
sectionLabel("收缩 / 舒张")
|
||||
sectionLabel(String(appLoc: "收缩 / 舒张"))
|
||||
Spacer()
|
||||
bpRangeHint
|
||||
}
|
||||
HStack(spacing: 12) {
|
||||
bpField(label: "收缩压", value: $systolic, placeholder: "120")
|
||||
bpField(label: String(appLoc: "收缩压"), value: $systolic, placeholder: "120")
|
||||
Text("/").font(.system(size: 22, weight: .light)).foregroundStyle(Tj.Palette.text3)
|
||||
bpField(label: "舒张压", value: $diastolic, placeholder: "80")
|
||||
bpField(label: String(appLoc: "舒张压"), value: $diastolic, placeholder: "80")
|
||||
Text("mmHg").foregroundStyle(Tj.Palette.text3)
|
||||
}
|
||||
bpStatusChips
|
||||
@@ -396,10 +396,10 @@ struct IndicatorQuickSheet: View {
|
||||
private var bpStatusChips: some View {
|
||||
HStack(spacing: 8) {
|
||||
if let s = computedBPStatus(.systolic) {
|
||||
statusBadge("收缩 " + s.label, color: s.color)
|
||||
statusBadge(String(appLoc: "收缩 ") + s.label, color: s.color)
|
||||
}
|
||||
if let s = computedBPStatus(.diastolic) {
|
||||
statusBadge("舒张 " + s.label, color: s.color)
|
||||
statusBadge(String(appLoc: "舒张 ") + s.label, color: s.color)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
@@ -407,7 +407,7 @@ struct IndicatorQuickSheet: View {
|
||||
|
||||
private var nameSection: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
sectionLabel("指标名")
|
||||
sectionLabel(String(appLoc: "指标名"))
|
||||
TextField("例如:血红蛋白", text: $name)
|
||||
.textInputAutocapitalization(.never)
|
||||
.padding(.horizontal, 14)
|
||||
@@ -427,7 +427,7 @@ struct IndicatorQuickSheet: View {
|
||||
private var valueRow: some View {
|
||||
HStack(alignment: .top, spacing: 12) {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
sectionLabel("数值")
|
||||
sectionLabel(String(appLoc: "数值"))
|
||||
TextField(monitorFieldPlaceholder, text: $value)
|
||||
.keyboardType(.decimalPad)
|
||||
.font(.system(size: 18, weight: .semibold, design: .monospaced))
|
||||
@@ -437,7 +437,7 @@ struct IndicatorQuickSheet: View {
|
||||
.overlay(fieldBorder)
|
||||
}
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
sectionLabel("单位")
|
||||
sectionLabel(String(appLoc: "单位"))
|
||||
TextField("mmol/L", text: $unit)
|
||||
.textInputAutocapitalization(.never)
|
||||
.autocorrectionDisabled()
|
||||
@@ -455,7 +455,7 @@ struct IndicatorQuickSheet: View {
|
||||
private var rangeSection: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack {
|
||||
sectionLabel("参考范围")
|
||||
sectionLabel(String(appLoc: "参考范围"))
|
||||
Spacer()
|
||||
if let m = selectedMonitor, m != .bloodPressure {
|
||||
monitorRangeHint(m)
|
||||
@@ -486,11 +486,11 @@ struct IndicatorQuickSheet: View {
|
||||
|
||||
private var statusSection: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
sectionLabel("状态")
|
||||
sectionLabel(String(appLoc: "状态"))
|
||||
HStack(spacing: 8) {
|
||||
statusChip(.normal, label: "正常", color: Tj.Palette.leaf)
|
||||
statusChip(.high, label: "偏高 ↑", color: Tj.Palette.brick)
|
||||
statusChip(.low, label: "偏低 ↓", color: Tj.Palette.amber)
|
||||
statusChip(.normal, label: String(appLoc: "正常"), color: Tj.Palette.leaf)
|
||||
statusChip(.high, label: String(appLoc: "偏高 ↑"), color: Tj.Palette.brick)
|
||||
statusChip(.low, label: String(appLoc: "偏低 ↓"), color: Tj.Palette.amber)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -498,7 +498,7 @@ struct IndicatorQuickSheet: View {
|
||||
private var autoStatusHint: some View {
|
||||
let auto = computedSingleStatus
|
||||
return HStack(spacing: 8) {
|
||||
sectionLabel("状态(按数值自动判)")
|
||||
sectionLabel(String(appLoc: "状态(按数值自动判)"))
|
||||
if let s = auto {
|
||||
statusBadge(s.label, color: s.color)
|
||||
} else {
|
||||
@@ -511,7 +511,7 @@ struct IndicatorQuickSheet: View {
|
||||
|
||||
private var timeSection: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
sectionLabel("测量时间")
|
||||
sectionLabel(String(appLoc: "测量时间"))
|
||||
DatePicker("", selection: $capturedAt, in: ...Date.now)
|
||||
.datePickerStyle(.compact)
|
||||
.labelsHidden()
|
||||
@@ -520,7 +520,7 @@ struct IndicatorQuickSheet: View {
|
||||
|
||||
private var noteSection: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
sectionLabel("备注(可选)")
|
||||
sectionLabel(String(appLoc: "备注(可选)"))
|
||||
TextField("例如:空腹采血", text: $note, axis: .vertical)
|
||||
.lineLimit(1...3)
|
||||
.padding(.horizontal, 14)
|
||||
@@ -535,7 +535,7 @@ struct IndicatorQuickSheet: View {
|
||||
private var reminderSection: some View {
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
HStack {
|
||||
sectionLabel("周期提醒")
|
||||
sectionLabel(String(appLoc: "周期提醒"))
|
||||
Spacer()
|
||||
Toggle("", isOn: $reminderEnabled)
|
||||
.labelsHidden()
|
||||
@@ -570,13 +570,13 @@ struct IndicatorQuickSheet: View {
|
||||
}
|
||||
weekdayPickerRow
|
||||
HStack(spacing: 8) {
|
||||
quickFreqChip("每天") {
|
||||
quickFreqChip(String(appLoc: "每天")) {
|
||||
reminderWeekdays = Set(1...7)
|
||||
}
|
||||
quickFreqChip("工作日") {
|
||||
quickFreqChip(String(appLoc: "工作日")) {
|
||||
reminderWeekdays = Set([2, 3, 4, 5, 6])
|
||||
}
|
||||
quickFreqChip("周末") {
|
||||
quickFreqChip(String(appLoc: "周末")) {
|
||||
reminderWeekdays = Set([1, 7])
|
||||
}
|
||||
}
|
||||
@@ -600,15 +600,23 @@ struct IndicatorQuickSheet: View {
|
||||
}
|
||||
|
||||
private var reminderFrequencyLabel: String {
|
||||
if reminderWeekdays.count == 7 { return "每天" }
|
||||
if reminderWeekdays.isEmpty { return "未选" }
|
||||
let names = ["日", "一", "二", "三", "四", "五", "六"]
|
||||
if reminderWeekdays.count == 7 { return String(appLoc: "每天") }
|
||||
if reminderWeekdays.isEmpty { return String(appLoc: "未选") }
|
||||
let names = [
|
||||
String(appLoc: "日"), String(appLoc: "一"), String(appLoc: "二"),
|
||||
String(appLoc: "三"), String(appLoc: "四"), String(appLoc: "五"),
|
||||
String(appLoc: "六"),
|
||||
]
|
||||
let sorted = reminderWeekdays.sorted()
|
||||
return "每周 " + sorted.map { names[$0 - 1] }.joined()
|
||||
return String(appLoc: "每周 ") + sorted.map { names[$0 - 1] }.joined()
|
||||
}
|
||||
|
||||
private var weekdayPickerRow: some View {
|
||||
let names = ["一", "二", "三", "四", "五", "六", "日"]
|
||||
let names = [
|
||||
String(appLoc: "一"), String(appLoc: "二"), String(appLoc: "三"),
|
||||
String(appLoc: "四"), String(appLoc: "五"), String(appLoc: "六"),
|
||||
String(appLoc: "日"),
|
||||
]
|
||||
let weekdayValues = [2, 3, 4, 5, 6, 7, 1] // 周一到周日(Apple Calendar 编号)
|
||||
return HStack(spacing: 6) {
|
||||
ForEach(Array(weekdayValues.enumerated()), id: \.offset) { idx, w in
|
||||
@@ -1074,9 +1082,9 @@ struct IndicatorQuickSheet: View {
|
||||
private extension IndicatorStatus {
|
||||
var label: String {
|
||||
switch self {
|
||||
case .normal: return "正常"
|
||||
case .high: return "偏高 ↑"
|
||||
case .low: return "偏低 ↓"
|
||||
case .normal: return String(appLoc: "正常")
|
||||
case .high: return String(appLoc: "偏高 ↑")
|
||||
case .low: return String(appLoc: "偏低 ↓")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user