Files
digital-pilates/i18n/index.ts
richarjiang 6f2b7eb45e feat(medications): 简化药品添加流程并优化AI相机交互体验
- 移除药品添加选项底部抽屉,直接跳转至AI识别相机
- 优化AI相机拍摄完成后的按钮交互,展开为"拍照"和"完成"两个按钮
- 添加相机引导提示本地存储,避免重复显示
- 修复相机页面布局跳动问题,固定相机高度
- 为医疗免责声明组件添加触觉反馈错误处理
- 实现活动热力图的国际化支持,包括月份标签和统计文本
2025-11-25 14:09:24 +08:00

1813 lines
57 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import * as Localization from 'expo-localization';
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import { getItemSync, setItem } from '@/utils/kvStore';
export const LANGUAGE_PREFERENCE_KEY = 'app_language_preference';
export const SUPPORTED_LANGUAGES = ['zh', 'en'] as const;
export type AppLanguage = typeof SUPPORTED_LANGUAGES[number];
const fallbackLanguage: AppLanguage = 'zh';
const personalScreenResources = {
edit: '编辑',
login: '登录',
memberNumber: '会员编号: {{number}}',
aiUsage: '免费AI次数: {{value}}',
aiUsageUnlimited: '无限',
fishRecord: '能量记录',
badgesPreview: {
title: '我的勋章',
subtitle: '记录你的荣耀时刻',
cta: '查看全部',
loading: '正在同步勋章...',
empty: '完成睡眠或挑战任务即可解锁首枚勋章',
lockedHint: '坚持训练即可点亮更多勋章',
},
stats: {
height: '身高',
weight: '体重',
age: '年龄',
ageSuffix: '岁',
},
membership: {
badge: '尊享会员',
planFallback: 'VIP 会员',
expiryLabel: '会员有效期',
changeButton: '更改会员套餐',
validForever: '长期有效',
dateFormat: 'YYYY年MM月DD日',
},
sections: {
notifications: '通知',
developer: '开发者',
other: '其他',
account: '账号与安全',
language: '语言',
healthData: '健康数据授权',
medicalSources: '医学建议来源',
customization: '个性化',
},
menu: {
notificationSettings: '通知设置',
developerOptions: '开发者选项',
pushSettings: '推送通知设置',
privacyPolicy: '隐私政策',
feedback: '意见反馈',
userAgreement: '用户协议',
logout: '退出登录',
deleteAccount: '注销帐号',
healthDataPermissions: '健康数据授权说明',
whoSource: '世界卫生组织 (WHO)',
tabBarConfig: '底部栏配置',
},
language: {
title: '语言',
menuTitle: '界面语言',
modalTitle: '选择语言',
modalSubtitle: '选择后界面会立即更新',
cancel: '取消',
options: {
zh: {
label: '中文',
description: '推荐中文用户使用',
},
en: {
label: '英文',
description: '使用英文界面',
},
},
},
tabBarConfig: {
title: '底部栏配置',
subtitle: '自定义你的底部导航栏',
description: '使用开关控制标签的显示和隐藏',
resetButton: '恢复默认',
cannotDisable: '此标签不可关闭',
resetConfirm: {
title: '恢复默认设置?',
message: '将重置所有底部栏配置和显示状态',
cancel: '取消',
confirm: '确认恢复',
},
resetSuccess: '已恢复默认设置',
},
};
const badgesScreenResources = {
title: '勋章馆',
subtitle: '点亮每一次坚持',
hero: {
highlight: '保持连续打卡即可解锁更多稀有勋章',
earnedLabel: '已获得',
totalLabel: '总数',
progressLabel: '解锁进度',
},
categories: {
all: '全部',
sleep: '睡眠',
exercise: '运动',
diet: '饮食',
challenge: '挑战',
social: '社交',
special: '特别',
},
rarities: {
common: '普通',
uncommon: '少见',
rare: '稀有',
epic: '史诗',
legendary: '传说',
},
status: {
earned: '已获得',
locked: '待解锁',
earnedAt: '{{date}} 获得',
},
legend: '稀有度说明',
filterLabel: '勋章分类',
empty: {
title: '还没有勋章',
description: '完成睡眠、运动、挑战等任务即可点亮你的第一枚勋章。',
action: '去探索计划',
},
};
const badgesScreenResourcesEn = {
title: 'Badge Gallery',
subtitle: 'Celebrate every effort',
hero: {
highlight: 'Keep checking in to unlock rarer badges.',
earnedLabel: 'Earned',
totalLabel: 'Total',
progressLabel: 'Progress',
},
categories: {
all: 'All',
sleep: 'Sleep',
exercise: 'Exercise',
diet: 'Nutrition',
challenge: 'Challenge',
social: 'Social',
special: 'Special',
},
rarities: {
common: 'Common',
uncommon: 'Uncommon',
rare: 'Rare',
epic: 'Epic',
legendary: 'Legendary',
},
status: {
earned: 'Unlocked',
locked: 'Locked',
earnedAt: 'Unlocked on {{date}}',
},
legend: 'Rarity legend',
filterLabel: 'Badge categories',
empty: {
title: 'No badges yet',
description: 'Complete sleep, workout, or challenge tasks to earn your first badge.',
action: 'Explore plans',
},
};
const editProfileResources = {
title: '编辑资料',
fields: {
name: '昵称',
gender: '性别',
height: '身高',
weight: '体重',
activityLevel: '活动水平',
birthDate: '出生日期',
maxHeartRate: '最大心率',
},
gender: {
male: '男',
female: '女',
notSet: '未设置',
},
height: {
unit: '厘米',
placeholder: '170厘米',
},
weight: {
unit: '公斤',
placeholder: '55公斤',
},
activityLevels: {
1: '久坐',
2: '轻度活跃',
3: '中度活跃',
4: '非常活跃',
descriptions: {
1: '很少运动',
2: '每周1-3次运动',
3: '每周3-5次运动',
4: '每周6-7次运动',
},
},
birthDate: {
placeholder: '1995年1月1日',
format: '{{year}}年{{month}}月{{day}}日',
},
maxHeartRate: {
unit: '次/分钟',
notAvailable: '未获取',
alert: {
title: '提示',
message: '最大心率数据从健康应用自动获取',
},
},
alerts: {
notLoggedIn: {
title: '未登录',
message: '请先登录后再尝试保存',
},
saveFailed: {
title: '保存失败',
message: '请稍后重试',
},
avatarPermissions: {
title: '权限不足',
message: '需要相册权限以选择头像',
},
avatarUploadFailed: {
title: '上传失败',
message: '头像上传失败,请重试',
},
avatarError: {
title: '发生错误',
message: '选择头像失败,请重试',
},
avatarSuccess: {
title: '成功',
message: '头像更新成功',
},
},
modals: {
cancel: '取消',
confirm: '确定',
save: '保存',
input: {
namePlaceholder: '输入昵称',
weightPlaceholder: '输入体重',
weightUnit: '公斤 (kg)',
},
selectHeight: '选择身高',
selectGender: '选择性别',
selectActivityLevel: '选择活动水平',
female: '女性',
male: '男性',
},
defaultValues: {
name: '今晚要吃肉',
height: 170,
weight: 55,
birthDate: '1995-01-01',
activityLevel: 1,
},
};
const healthPermissionsResources = {
title: '健康数据授权说明',
subtitle: '我们通过 Apple Health 的 HealthKit/CareKit 接口同步必要的数据,让训练、恢复和提醒更贴合你的身体状态。',
cards: {
usage: {
title: '我们会读取 / 写入的数据',
items: [
'运动与活动:步数、活动能量、锻炼记录用于生成训练表现和热力图。',
'身体指标:身高、体重、体脂率帮助制定个性化训练与营养建议。',
'睡眠与恢复:睡眠时长与阶段用于智能提醒与恢复建议。',
'水分摄入读取与写入饮水记录保持与「健康」App 一致。',
],
},
purpose: {
title: '使用这些数据的目的',
items: [
'提供个性化训练计划、挑战与恢复建议。',
'在统计页展示长期趋势,帮助你理解身体变化。',
'减少重复输入,在提醒与挑战中自动同步进度。',
],
},
control: {
title: '你的控制权',
items: [
'授权流程完全由 Apple Health 控制,你可随时在 iOS 设置 > 健康 > 数据访问与设备 中更改权限。',
'未授权的数据不会被访问,撤销授权后我们会清理相关缓存。',
'核心功能依旧可用,并提供手动输入等替代方案。',
],
},
privacy: {
title: '数据存储与隐私',
items: [
'健康数据仅存储在你的设备上,我们不会上传服务器或共享给第三方。',
'只有在需要同步的功能中才会保存聚合后的匿名统计值。',
'我们遵循 Apple 的审核要求,任何变更都会提前告知。',
],
},
},
callout: {
title: '未授权会怎样?',
items: [
'相关模块会提示你授权,并提供手动记录入口。',
'拒绝授权不会影响其它与健康数据无关的功能。',
],
},
contact: {
title: '需要更多帮助?',
description: '如果你对 HealthKit / CareKit 的使用方式有疑问,可通过以下邮箱或在个人中心提交反馈:',
email: 'richardwei1995@gmail.com',
},
};
const statisticsResources = {
title: 'Out Live',
sections: {
bodyMetrics: '身体指标',
},
components: {
diet: {
title: '饮食分析',
loading: '加载中...',
updated: '更新: {{time}}',
remaining: '还能吃',
calories: '热量',
protein: '蛋白质',
carb: '碳水',
fat: '脂肪',
fiber: '纤维',
sodium: '钠',
basal: '基代',
exercise: '运动',
diet: '饮食',
kcal: '千卡',
aiRecognition: 'AI识别',
foodLibrary: '食物库',
voiceRecord: '一句话记录',
nutritionLabel: '成分表分析',
},
fitness: {
kcal: '千卡',
minutes: '分钟',
hours: '小时',
},
steps: {
title: '步数',
},
mood: {
title: '心情',
empty: '点击记录心情',
},
stress: {
title: '压力',
unit: 'ms',
},
water: {
title: '喝水',
unit: 'ml',
addButton: '+ {{amount}}ml',
},
metabolism: {
title: '基础代谢',
loading: '加载中...',
unit: '千卡/日',
status: {
high: '高代谢',
normal: '正常',
low: '偏低',
veryLow: '较低',
unknown: '未知',
},
},
sleep: {
title: '睡眠',
loading: '加载中...',
},
oxygen: {
title: '血氧饱和度',
},
circumference: {
title: '围度 (cm)',
setTitle: '设置{{label}}',
confirm: '确认',
measurements: {
chest: '胸围',
waist: '腰围',
hip: '上臀围',
arm: '臂围',
thigh: '大腿围',
calf: '小腿围',
},
},
workout: {
title: '近期锻炼',
minutes: '分钟',
kcal: '千卡',
noData: '尚无锻炼数据',
syncing: '等待同步',
sourceWaiting: '来源:等待同步',
sourceUnknown: '来源:未知',
sourceFormat: '来源:{{source}}',
sourceFormatMultiple: '来源:{{source}} 等',
lastWorkout: '最近锻炼',
updated: '更新',
},
weight: {
title: '体重记录',
addButton: '记录体重',
bmi: 'BMI',
weight: '体重',
days: '天',
range: '范围',
unit: 'kg',
bmiModal: {
title: 'BMI 指数说明',
description: 'BMI身体质量指数是评估体重与身高关系的国际通用健康指标',
formula: '计算公式:体重(kg) ÷ 身高²(m)',
classificationTitle: 'BMI 分类标准',
healthTipsTitle: '健康建议',
tips: {
nutrition: '保持均衡饮食,控制热量摄入',
exercise: '每周至少150分钟中等强度运动',
sleep: '保证7-9小时充足睡眠',
monitoring: '定期监测体重变化,及时调整',
},
disclaimer: 'BMI 仅供参考,不能反映肌肉量、骨密度等指标。如有健康疑问,请咨询专业医生。',
continueButton: '继续',
},
},
fitnessRings: {
title: '健身圆环',
activeCalories: '活动卡路里',
exerciseMinutes: '锻炼分钟',
standHours: '站立小时',
goal: '/{{goal}}',
ringLabels: {
active: '活动',
exercise: '锻炼',
stand: '站立',
},
},
},
tabs: {
health: '健康',
medications: '用药',
fasting: '断食',
challenges: '挑战',
personal: '个人',
},
activityHeatMap: {
subtitle: '最近6个月活跃 {{days}} 天',
activeRate: '{{rate}}%',
popover: {
title: '能量值的积攒后续可以用来兑换 AI 相关权益',
subtitle: '获取说明',
rules: {
login: '1. 每日登录获得能量值+1',
mood: '2. 每日记录心情获得能量值+1',
diet: '3. 记饮食获得能量值+1',
goal: '4. 完成一次目标获得能量值+1',
},
},
months: {
1: '1月',
2: '2月',
3: '3月',
4: '4月',
5: '5月',
6: '6月',
7: '7月',
8: '8月',
9: '9月',
10: '10月',
11: '11月',
12: '12月',
},
legend: {
less: '少',
more: '多',
},
},
};
const medicationsResources = {
greeting: '你好,{{name}}',
welcome: '欢迎来到用药助手!',
todayMedications: '今日用药',
filters: {
all: '全部',
taken: '已服用',
missed: '未服用',
},
emptyState: {
title: '今日暂无用药安排',
subtitle: '还未添加任何用药计划,快来补充吧。',
},
stack: {
completed: '已完成 ({{count}})',
},
dateFormats: {
today: '今天,{{date}}',
other: '{{date}}',
},
// MedicationCard 组件翻译
card: {
status: {
missed: '已错过',
timeToTake: '到服药时间',
remaining: '剩余 {{time}}',
},
action: {
takeNow: '立即服用',
taken: '已服用',
skipped: '已跳过',
skip: '跳过',
submitting: '提交中...',
},
skipAlert: {
title: '确认跳过',
message: '确定要跳过本次用药吗?\n\n跳过后将不会记录为已服用。',
cancel: '取消',
confirm: '确认跳过',
},
earlyTakeAlert: {
title: '尚未到服药时间',
message: '该用药计划在 {{time}}现在还早于1小时以上。\n\n是否确认已服用此药物',
cancel: '取消',
confirm: '确认已服用',
},
takeError: {
title: '操作失败',
message: '记录服药时发生错误,请稍后重试',
confirm: '确定',
},
skipError: {
title: '操作失败',
message: '跳过操作失败,请稍后重试',
confirm: '确定',
},
},
// 添加药物页面翻译
add: {
title: '添加药物',
steps: {
name: '药品名称',
dosage: '剂型与剂量',
frequency: '服药频率',
time: '服药时间',
note: '备注',
},
descriptions: {
name: '为药物命名并上传包装照片,方便识别',
dosage: '选择药片类型并填写每次的用药剂量',
frequency: '设置用药频率以及每日次数',
time: '添加并管理每天的提醒时间',
note: '填写备注或医生叮嘱(可选)',
},
name: {
placeholder: '输入或搜索药品名称',
},
photo: {
title: '上传药品图片',
subtitle: '拍照或从相册选择,辅助识别药品包装',
selectTitle: '选择图片',
selectMessage: '请选择图片来源',
camera: '拍照',
album: '从相册选择',
cancel: '取消',
retake: '重新选择',
uploading: '上传中…',
uploadingText: '正在上传',
remove: '删除',
cameraPermission: '需要相机权限以拍摄药品照片',
albumPermission: '需要相册权限以选择药品照片',
uploadFailed: '上传失败',
uploadFailedMessage: '图片上传失败,请稍后重试',
cameraFailed: '拍照失败',
cameraFailedMessage: '无法打开相机,请稍后再试',
selectFailed: '选择失败',
selectFailedMessage: '无法打开相册,请稍后再试',
},
dosage: {
label: '每次剂量',
placeholder: '0.5',
type: '类型',
unitSelector: '选择剂量单位',
},
frequency: {
label: '每日次数',
value: '{{count}} 次/日',
period: '用药周期',
start: '开始',
end: '结束',
longTerm: '长期',
startDateInvalid: '日期无效',
startDateInvalidMessage: '开始日期不能早于今天',
endDateInvalid: '日期无效',
endDateInvalidMessage: '结束日期不能早于开始日期',
},
time: {
label: '每日提醒时间',
addTime: '添加时间',
editTime: '修改提醒时间',
addTimeButton: '添加时间',
},
note: {
label: '备注',
placeholder: '记录注意事项、医生叮嘱或自定义提醒',
voiceNotSupported: '当前设备暂不支持语音转文字,可直接输入备注',
voiceError: '语音识别不可用',
voiceErrorMessage: '无法使用语音输入,请检查权限设置后重试',
voiceStartError: '无法启动语音输入',
voiceStartErrorMessage: '请检查麦克风与语音识别权限后重试',
},
actions: {
previous: '上一步',
next: '下一步',
complete: '完成',
},
success: {
title: '添加成功',
message: '已成功添加药物"{{name}}"',
confirm: '确定',
},
error: {
title: '添加失败',
message: '创建药物时发生错误,请稍后重试',
confirm: '确定',
},
datePickers: {
startDate: '选择开始日期',
endDate: '选择结束日期',
time: '选择时间',
cancel: '取消',
confirm: '确定',
},
pickers: {
timesPerDay: '选择每日次数',
dosageUnit: '选择剂量单位',
cancel: '取消',
confirm: '确定',
},
},
// 药物管理页面翻译
manage: {
title: '药品管理',
subtitle: '管理所有药品的状态与提醒',
filters: {
all: '全部',
active: '进行中',
inactive: '已停用',
},
loading: '正在载入药品信息...',
empty: {
title: '暂无药品',
subtitle: '还没有相关药品记录,点击右上角添加',
},
deactivate: {
title: '停用 {{name}}',
description: '停用后,当天已生成的用药计划会一并删除,且无法恢复。',
confirm: '确认停用',
cancel: '取消',
error: {
title: '操作失败',
message: '停用药物时发生问题,请稍后重试。',
},
},
toggleError: {
title: '操作失败',
message: '切换药物状态时发生问题,请稍后重试。',
},
formLabels: {
capsule: '胶囊',
pill: '药片',
tablet: '药片',
injection: '注射',
spray: '喷雾',
drop: '滴剂',
syrup: '糖浆',
other: '其他',
},
frequency: {
daily: '每日',
weekly: '每周',
custom: '自定义',
},
cardMeta: '开始于 {{date}} 提醒:{{reminder}}',
reminderNotSet: '尚未设置',
unknownDate: '未知日期',
},
// 药物详情页面翻译
detail: {
title: '药品详情',
notFound: {
title: '未找到药品信息',
subtitle: '请从用药列表重新进入此页面。',
},
loading: '正在载入...',
error: {
title: '暂时无法获取该药品的信息,请稍后重试。',
subtitle: '请检查网络后重试,或返回上一页。',
},
sections: {
plan: '服药计划',
dosage: '剂量与形式',
note: '备注',
overview: '服药概览',
aiAnalysis: 'AI 用药分析',
},
plan: {
period: '服药周期',
time: '用药时间',
frequency: '频率',
expiryDate: '药品有效期',
longTerm: '长期',
periodMessage: '开始服药日期:{{startDate}}\n{{endDateInfo}}',
longTermPlan: '服药计划:长期服药',
timeMessage: '设置的时间:{{times}}',
},
dosage: {
label: '每次剂量',
form: '剂型',
selectDosage: '选择剂量',
selectForm: '选择剂型',
dosageValue: '剂量值',
unit: '单位',
},
note: {
label: '药品备注',
placeholder: '记录注意事项、医生叮嘱或自定义提醒',
edit: '编辑备注',
noNote: '暂无备注信息',
voiceNotSupported: '当前设备暂不支持语音转文字,可直接输入备注',
save: '保存',
saveError: {
title: '保存失败',
message: '提交备注时出现问题,请稍后重试。',
},
},
overview: {
calculating: '统计中...',
takenCount: '累计服药 {{count}} 次',
calculatingDays: '正在计算坚持天数',
startedDays: '已坚持 {{days}} 天',
startDate: '开始于 {{date}}',
noStartDate: '暂无开始日期',
},
aiAnalysis: {
analyzing: '正在分析用药信息...',
analyzingButton: '分析中...',
button: 'AI 分析',
error: {
title: '分析失败',
message: 'AI 分析失败,请稍后重试',
networkError: '发起分析请求失败,请检查网络连接',
unauthorized: '请先登录',
forbidden: '无权访问此药物',
notFound: '药物不存在',
},
},
status: {
enabled: '提醒已开启',
disabled: '提醒已关闭',
},
delete: {
title: '删除 {{name}}',
description: '删除后将清除与该药品相关的提醒与历史记录,且无法恢复。',
confirm: '删除',
cancel: '取消',
error: {
title: '删除失败',
message: '移除该药品时出现问题,请稍后再试。',
},
},
deactivate: {
title: '停用 {{name}}',
description: '停用后,当天已生成的用药计划会一并删除,且无法恢复。',
confirm: '确认停用',
cancel: '取消',
error: {
title: '操作失败',
message: '停用药物时发生问题,请稍后重试。',
},
},
toggleError: {
title: '操作失败',
message: '切换提醒状态时出现问题,请稍后重试。',
},
updateErrors: {
dosage: '更新失败',
dosageMessage: '更新剂量时出现问题,请稍后重试。',
form: '更新失败',
formMessage: '更新剂型时出现问题,请稍后重试。',
},
imageViewer: {
close: '关闭',
},
pickers: {
cancel: '取消',
confirm: '确定',
},
},
// 编辑频率页面翻译
editFrequency: {
title: '编辑服药频率',
missingParams: '缺少必要参数',
medicationName: '正在编辑:{{name}}',
sections: {
frequency: '服药频率',
frequencyDescription: '设置每日服药次数',
time: '每日提醒时间',
timeDescription: '添加并管理每天的提醒时间',
},
frequency: {
repeatPattern: '重复模式',
timesPerDay: '每日次数',
daily: '每日',
weekly: '每周',
custom: '自定义',
timesLabel: '{{count}} 次',
summary: '{{pattern}} {{count}} 次',
},
time: {
addTime: '添加时间',
editTime: '修改提醒时间',
addTimeButton: '添加时间',
},
actions: {
save: '保存修改',
},
error: {
title: '更新失败',
message: '更新服药频率时出现问题,请稍后重试。',
},
pickers: {
cancel: '取消',
confirm: '确定',
},
},
};
const notificationSettingsResources = {
title: '通知设置',
loading: '加载中...',
sections: {
notifications: '通知设置',
medicationReminder: '药品提醒',
nutritionReminder: '营养提醒',
moodReminder: '心情提醒',
description: '说明',
},
items: {
pushNotifications: {
title: '消息推送',
description: '开启后将接收应用通知',
},
medicationReminder: {
title: '药品通知提醒',
description: '在用药时间接收提醒通知',
},
nutritionReminder: {
title: '营养记录提醒',
description: '在用餐时间接收营养记录提醒',
},
moodReminder: {
title: '心情记录提醒',
description: '在晚间接收心情记录提醒',
},
},
description: {
text: '• 消息推送是所有通知的总开关\n• 各类提醒需要在消息推送开启后才能使用\n• 您可以在系统设置中管理通知权限\n• 关闭消息推送将停止所有应用通知',
},
alerts: {
permissionDenied: {
title: '权限被拒绝',
message: '请在系统设置中开启通知权限,然后再尝试开启推送功能',
cancel: '取消',
goToSettings: '去设置',
},
error: {
title: '错误',
message: '请求通知权限失败',
saveFailed: '保存设置失败',
medicationReminderFailed: '设置药品提醒失败',
nutritionReminderFailed: '设置营养提醒失败',
moodReminderFailed: '设置心情提醒失败',
},
notificationsEnabled: {
title: '通知已开启',
body: '您将收到应用通知和提醒',
},
medicationReminderEnabled: {
title: '药品提醒已开启',
body: '您将在用药时间收到提醒通知',
},
nutritionReminderEnabled: {
title: '营养提醒已开启',
body: '您将在用餐时间收到营养记录提醒',
},
moodReminderEnabled: {
title: '心情提醒已开启',
body: '您将在晚间收到心情记录提醒',
},
},
};
const resources = {
zh: {
translation: {
personal: personalScreenResources,
badges: badgesScreenResources,
editProfile: editProfileResources,
healthPermissions: healthPermissionsResources,
statistics: statisticsResources,
medications: medicationsResources,
notificationSettings: notificationSettingsResources,
},
},
en: {
translation: {
personal: {
edit: 'Edit',
login: 'Log in',
memberNumber: 'Member ID: {{number}}',
aiUsage: 'Free AI credits: {{value}}',
aiUsageUnlimited: 'Unlimited',
fishRecord: 'Energy log',
badgesPreview: {
title: 'My badges',
subtitle: 'Celebrate every milestone',
cta: 'View all',
loading: 'Syncing your badges…',
empty: 'Complete sleep or challenge tasks to unlock your first badge.',
lockedHint: 'Keep building the habit to unlock more.',
},
stats: {
height: 'Height',
weight: 'Weight',
age: 'Age',
ageSuffix: ' yrs',
},
membership: {
badge: 'Premium member',
planFallback: 'VIP Membership',
expiryLabel: 'Valid until',
changeButton: 'Change plan',
validForever: 'No expiry',
dateFormat: 'YYYY-MM-DD',
},
sections: {
notifications: 'Notifications',
developer: 'Developer',
other: 'Other',
account: 'Account & Security',
language: 'Language',
healthData: 'Health data permissions',
medicalSources: 'Medical Advice Sources',
customization: 'Customization',
},
menu: {
notificationSettings: 'Notification settings',
developerOptions: 'Developer options',
pushSettings: 'Push notification settings',
privacyPolicy: 'Privacy policy',
feedback: 'Feedback',
userAgreement: 'User agreement',
logout: 'Log out',
deleteAccount: 'Delete account',
healthDataPermissions: 'Health data disclosure',
whoSource: 'World Health Organization (WHO)',
tabBarConfig: 'Tab Bar Settings',
},
language: {
title: 'Language',
menuTitle: 'Display language',
modalTitle: 'Choose language',
modalSubtitle: 'Your selection applies immediately',
cancel: 'Cancel',
options: {
zh: {
label: 'Chinese',
description: 'Use the Chinese interface',
},
en: {
label: 'English',
description: 'Use the app in English',
},
},
},
},
badges: badgesScreenResourcesEn,
editProfile: {
title: 'Edit Profile',
fields: {
name: 'Nickname',
gender: 'Gender',
height: 'Height',
weight: 'Weight',
activityLevel: 'Activity Level',
birthDate: 'Birth Date',
maxHeartRate: 'Max Heart Rate',
},
gender: {
male: 'Male',
female: 'Female',
notSet: 'Not set',
},
height: {
unit: 'cm',
placeholder: '170cm',
},
weight: {
unit: 'kg',
placeholder: '55kg',
},
activityLevels: {
1: 'Sedentary',
2: 'Lightly active',
3: 'Moderately active',
4: 'Very active',
descriptions: {
1: 'Rarely exercise',
2: 'Exercise 1-3 times per week',
3: 'Exercise 3-5 times per week',
4: 'Exercise 6-7 times per week',
},
},
birthDate: {
placeholder: 'January 1, 1995',
format: '{{month}} {{day}}, {{year}}',
},
maxHeartRate: {
unit: 'bpm',
notAvailable: 'Not available',
alert: {
title: 'Notice',
message: 'Max heart rate data is automatically retrieved from Health app',
},
},
alerts: {
notLoggedIn: {
title: 'Not logged in',
message: 'Please log in before trying to save',
},
saveFailed: {
title: 'Save failed',
message: 'Please try again later',
},
avatarPermissions: {
title: 'Insufficient permissions',
message: 'Photo album permission is required to select avatar',
},
avatarUploadFailed: {
title: 'Upload failed',
message: 'Avatar upload failed, please try again',
},
avatarError: {
title: 'Error occurred',
message: 'Failed to select avatar, please try again',
},
avatarSuccess: {
title: 'Success',
message: 'Avatar updated successfully',
},
},
modals: {
cancel: 'Cancel',
confirm: 'Confirm',
save: 'Save',
input: {
namePlaceholder: 'Enter nickname',
weightPlaceholder: 'Enter weight',
weightUnit: 'kg',
},
selectHeight: 'Select Height',
selectGender: 'Select Gender',
selectActivityLevel: 'Select Activity Level',
female: 'Female',
male: 'Male',
},
defaultValues: {
name: 'TonightEatMeat',
height: 170,
weight: 55,
birthDate: '1995-01-01',
activityLevel: 1,
},
},
healthPermissions: {
title: 'Health data disclosure',
subtitle: 'We integrate with Apple Health through HealthKit and CareKit to deliver precise training, recovery, and reminder experiences.',
cards: {
usage: {
title: 'Data we read or write',
items: [
'Activity: steps, active energy, and workouts fuel performance charts and rings.',
'Body metrics: height, weight, and body fat keep plans and nutrition tips personalized.',
'Sleep & recovery: duration and stages unlock recovery advice and reminders.',
'Hydration: we read and write water intake so Health and the app stay in sync.',
],
},
purpose: {
title: 'Why we need it',
items: [
'Generate adaptive training plans, challenges, and recovery nudges.',
'Display long-term trends so you can understand progress at a glance.',
'Reduce manual input by syncing reminders and challenge progress automatically.',
],
},
control: {
title: 'Your control',
items: [
'Permissions are granted inside Apple Health; change them anytime under iOS Settings > Health > Data Access & Devices.',
'We never access data you do not authorize, and cached values are removed if you revoke access.',
'Core functionality keeps working and offers manual input alternatives.',
],
},
privacy: {
title: 'Storage & privacy',
items: [
'Health data stays on your device — we do not upload it or share it with third parties.',
'Only aggregated, anonymized stats are synced when absolutely necessary.',
"We follow Apple's review requirements and will notify you before any changes.",
],
},
},
callout: {
title: 'What if I skip authorization?',
items: [
'The related modules will ask for permission and provide manual logging options.',
'Declining does not break other areas of the app that do not rely on Health data.',
],
},
contact: {
title: 'Need help?',
description: 'Questions about HealthKit or CareKit? Reach out via email or the in-app feedback form:',
email: 'richardwei1995@gmail.com',
},
},
statistics: {
title: 'Out Live',
sections: {
bodyMetrics: 'Body Metrics',
},
components: {
diet: {
title: 'Diet Analysis',
loading: 'Loading...',
updated: 'Updated: {{time}}',
remaining: 'Can Still Eat',
calories: 'Calories',
protein: 'Protein',
carb: 'Carbs',
fat: 'Fat',
fiber: 'Fiber',
sodium: 'Sodium',
basal: 'Basal',
exercise: 'Exercise',
diet: 'Diet',
kcal: 'kcal',
aiRecognition: 'AI Scan',
foodLibrary: 'Food Library',
voiceRecord: 'Voice Log',
nutritionLabel: 'Nutrition Label',
},
fitness: {
kcal: 'kcal',
minutes: 'min',
hours: 'hrs',
},
steps: {
title: 'Steps',
},
mood: {
title: 'Mood',
empty: 'Tap to record mood',
},
stress: {
title: 'Stress',
unit: 'ms',
},
water: {
title: 'Water',
unit: 'ml',
addButton: '+ {{amount}}ml',
},
metabolism: {
title: 'Basal Metabolism',
loading: 'Loading...',
unit: 'kcal/day',
status: {
high: 'High',
normal: 'Normal',
low: 'Low',
veryLow: 'Very Low',
unknown: 'Unknown',
},
},
sleep: {
title: 'Sleep',
loading: 'Loading...',
},
oxygen: {
title: 'Blood Oxygen',
},
circumference: {
title: 'Circumference (cm)',
setTitle: 'Set {{label}}',
confirm: 'Confirm',
measurements: {
chest: 'Chest',
waist: 'Waist',
hip: 'Hip',
arm: 'Arm',
thigh: 'Thigh',
calf: 'Calf',
},
},
workout: {
title: 'Recent Workout',
minutes: 'min',
kcal: 'kcal',
noData: 'No workout data',
syncing: 'Syncing...',
sourceWaiting: 'Source: Syncing...',
sourceUnknown: 'Source: Unknown',
sourceFormat: 'Source: {{source}}',
sourceFormatMultiple: 'Source: {{source}} et al.',
lastWorkout: 'Latest Workout',
updated: 'Updated',
},
weight: {
title: 'Weight Records',
addButton: 'Record Weight',
bmi: 'BMI',
weight: 'Weight',
days: 'days',
range: 'Range',
unit: 'kg',
bmiModal: {
title: 'BMI Index Explanation',
description: 'BMI (Body Mass Index) is an internationally recognized health indicator for assessing weight relative to height',
formula: 'Formula: weight(kg) ÷ height²(m)',
classificationTitle: 'BMI Classification Standards',
healthTipsTitle: 'Health Tips',
tips: {
nutrition: 'Maintain a balanced diet and control calorie intake',
exercise: 'At least 150 minutes of moderate-intensity exercise per week',
sleep: 'Ensure 7-9 hours of adequate sleep',
monitoring: 'Regularly monitor weight changes and adjust promptly',
},
disclaimer: 'BMI is for reference only and cannot reflect muscle mass, bone density, etc. If you have health concerns, please consult a professional doctor.',
continueButton: 'Continue',
},
},
fitnessRings: {
title: 'Fitness Rings',
activeCalories: 'Active Calories',
exerciseMinutes: 'Exercise Minutes',
standHours: 'Stand Hours',
goal: '/{{goal}}',
ringLabels: {
active: 'Active',
exercise: 'Exercise',
stand: 'Stand',
},
},
},
tabs: {
health: 'Health',
medications: 'Meds',
fasting: 'Fasting',
challenges: 'Challenges',
personal: 'Me',
},
activityHeatMap: {
subtitle: 'Active {{days}} days in the last 6 months',
activeRate: '{{rate}}%',
popover: {
title: 'Accumulated energy can be redeemed for AI-related benefits',
subtitle: 'How to earn',
rules: {
login: '1. Daily login earns energy +1',
mood: '2. Daily mood record earns energy +1',
diet: '3. Diet record earns energy +1',
goal: '4. Complete a goal earns energy +1',
},
},
months: {
1: 'Jan',
2: 'Feb',
3: 'Mar',
4: 'Apr',
5: 'May',
6: 'Jun',
7: 'Jul',
8: 'Aug',
9: 'Sep',
10: 'Oct',
11: 'Nov',
12: 'Dec',
},
legend: {
less: 'Less',
more: 'More',
},
},
},
medications: {
greeting: 'Hello, {{name}}',
welcome: 'Welcome to Medication Assistant!',
todayMedications: 'Today\'s Medications',
filters: {
all: 'All',
taken: 'Taken',
missed: 'Missed',
},
emptyState: {
title: 'No medications scheduled for today',
subtitle: 'No medication plans added yet. Let\'s add some.',
},
stack: {
completed: 'Completed ({{count}})',
},
dateFormats: {
today: 'Today, {{date}}',
other: '{{date}}',
},
// MedicationCard 组件翻译
card: {
status: {
missed: 'Missed',
timeToTake: 'Time to take',
remaining: '{{time}} remaining',
},
action: {
takeNow: 'Take Now',
taken: 'Taken',
skipped: 'Skipped',
skip: 'Skip',
submitting: 'Submitting...',
},
skipAlert: {
title: 'Confirm Skip',
message: 'Are you sure you want to skip this medication?\n\nIt will not be recorded as taken.',
cancel: 'Cancel',
confirm: 'Confirm Skip',
},
earlyTakeAlert: {
title: 'Not yet time to take medication',
message: 'This medication is scheduled for {{time}}, which is more than 1 hour from now.\n\nHave you already taken this medication?',
cancel: 'Cancel',
confirm: 'Confirm Taken',
},
takeError: {
title: 'Operation Failed',
message: 'An error occurred while recording medication, please try again later',
confirm: 'OK',
},
skipError: {
title: 'Operation Failed',
message: 'Skip operation failed, please try again later',
confirm: 'OK',
},
},
// 添加药物页面翻译
add: {
title: 'Add Medication',
steps: {
name: 'Medication Name',
dosage: 'Dosage & Form',
frequency: 'Frequency',
time: 'Reminder Time',
note: 'Notes',
},
descriptions: {
name: 'Name the medication and upload package photo for easy identification',
dosage: 'Select tablet type and fill in dosage per administration',
frequency: 'Set medication frequency and daily times',
time: 'Add and manage daily reminder times',
note: 'Fill in notes or doctor instructions (optional)',
},
name: {
placeholder: 'Enter or search medication name',
},
photo: {
title: 'Upload Medication Photo',
subtitle: 'Take a photo or select from album to help identify medication packaging',
selectTitle: 'Select Image',
selectMessage: 'Please select image source',
camera: 'Camera',
album: 'From Album',
cancel: 'Cancel',
retake: 'Retake',
uploading: 'Uploading...',
uploadingText: 'Uploading',
remove: 'Remove',
cameraPermission: 'Camera permission is required to take medication photos',
albumPermission: 'Album permission is required to select medication photos',
uploadFailed: 'Upload Failed',
uploadFailedMessage: 'Image upload failed, please try again later',
cameraFailed: 'Camera Failed',
cameraFailedMessage: 'Unable to open camera, please try again later',
selectFailed: 'Selection Failed',
selectFailedMessage: 'Unable to open album, please try again later',
},
dosage: {
label: 'Dosage per administration',
placeholder: '0.5',
type: 'Type',
unitSelector: 'Select dosage unit',
},
frequency: {
label: 'Times per day',
value: '{{count}} times/day',
period: 'Medication period',
start: 'Start',
end: 'End',
longTerm: 'Long-term',
startDateInvalid: 'Invalid date',
startDateInvalidMessage: 'Start date cannot be earlier than today',
endDateInvalid: 'Invalid date',
endDateInvalidMessage: 'End date cannot be earlier than start date',
},
time: {
label: 'Daily reminder times',
addTime: 'Add Time',
editTime: 'Edit Reminder Time',
addTimeButton: 'Add Time',
},
note: {
label: 'Notes',
placeholder: 'Record precautions, doctor instructions or custom reminders',
voiceNotSupported: 'Voice-to-text is not supported on this device, you can type notes directly',
voiceError: 'Voice recognition unavailable',
voiceErrorMessage: 'Unable to use voice input, please check permission settings and try again',
voiceStartError: 'Unable to start voice input',
voiceStartErrorMessage: 'Please check microphone and voice recognition permissions and try again',
},
actions: {
previous: 'Previous',
next: 'Next',
complete: 'Complete',
},
success: {
title: 'Added Successfully',
message: 'Successfully added medication "{{name}}"',
confirm: 'OK',
},
error: {
title: 'Add Failed',
message: 'An error occurred while creating medication, please try again later',
confirm: 'OK',
},
datePickers: {
startDate: 'Select Start Date',
endDate: 'Select End Date',
time: 'Select Time',
cancel: 'Cancel',
confirm: 'Confirm',
},
pickers: {
timesPerDay: 'Select Times Per Day',
dosageUnit: 'Select Dosage Unit',
cancel: 'Cancel',
confirm: 'Confirm',
},
},
// 药物管理页面翻译
manage: {
title: 'Medication Management',
subtitle: 'Manage status and reminders for all medications',
filters: {
all: 'All',
active: 'Active',
inactive: 'Inactive',
},
loading: 'Loading medication information...',
empty: {
title: 'No Medications',
subtitle: 'No medication records yet, click the top right to add',
},
deactivate: {
title: 'Deactivate {{name}}?',
description: 'After deactivation, medication plans generated for the day will be deleted and cannot be recovered.',
confirm: 'Confirm Deactivation',
cancel: 'Cancel',
error: {
title: 'Operation Failed',
message: 'An error occurred while deactivating medication, please try again later.',
},
},
toggleError: {
title: 'Operation Failed',
message: 'An error occurred while toggling medication status, please try again later.',
},
formLabels: {
capsule: 'Capsule',
pill: 'Tablet',
tablet: 'Tablet',
injection: 'Injection',
spray: 'Spray',
drop: 'Drops',
syrup: 'Syrup',
other: 'Other',
},
frequency: {
daily: 'Daily',
weekly: 'Weekly',
custom: 'Custom',
},
cardMeta: 'Started {{date}} Reminder: {{reminder}}',
reminderNotSet: 'Not set',
unknownDate: 'Unknown date',
},
// 药物详情页面翻译
detail: {
title: 'Medication Details',
notFound: {
title: 'Medication information not found',
subtitle: 'Please re-enter this page from the medication list.',
},
loading: 'Loading...',
error: {
title: 'Unable to retrieve medication information at this time, please try again later.',
subtitle: 'Please check your network and try again, or return to the previous page.',
},
sections: {
plan: 'Medication Plan',
dosage: 'Dosage & Form',
note: 'Notes',
overview: 'Medication Overview',
aiAnalysis: 'AI Medication Analysis',
},
plan: {
period: 'Medication Period',
time: 'Medication Time',
frequency: 'Frequency',
expiryDate: 'Expiry Date',
longTerm: 'Long-term',
periodMessage: 'Start date: {{startDate}}\n{{endDateInfo}}',
longTermPlan: 'Medication plan: Long-term medication',
timeMessage: 'Set times: {{times}}',
},
dosage: {
label: 'Dosage per administration',
form: 'Form',
selectDosage: 'Select Dosage',
selectForm: 'Select Form',
dosageValue: 'Dosage Value',
unit: 'Unit',
},
note: {
label: 'Medication Notes',
placeholder: 'Record precautions, doctor instructions or custom reminders',
edit: 'Edit Notes',
noNote: 'No notes',
voiceNotSupported: 'Voice-to-text is not supported on this device, you can type notes directly',
save: 'Save',
saveError: {
title: 'Save Failed',
message: 'An error occurred while submitting notes, please try again later.',
},
},
overview: {
calculating: 'Calculating...',
takenCount: 'Taken {{count}} times in total',
calculatingDays: 'Calculating adherence days',
startedDays: 'Adhered for {{days}} days',
startDate: 'Started {{date}}',
noStartDate: 'No start date',
},
aiAnalysis: {
analyzing: 'Analyzing medication information...',
analyzingButton: 'Analyzing...',
button: 'AI Analysis',
error: {
title: 'Analysis Failed',
message: 'AI analysis failed, please try again later',
networkError: 'Failed to initiate analysis request, please check network connection',
unauthorized: 'Please log in first',
forbidden: 'No access to this medication',
notFound: 'Medication not found',
},
},
status: {
enabled: 'Reminders Enabled',
disabled: 'Reminders Disabled',
},
delete: {
title: 'Delete {{name}}?',
description: 'After deletion, reminders and history related to this medication will be cleared and cannot be recovered.',
confirm: 'Delete',
cancel: 'Cancel',
error: {
title: 'Delete Failed',
message: 'An error occurred while removing this medication, please try again later.',
},
},
deactivate: {
title: 'Deactivate {{name}}?',
description: 'After deactivation, medication plans generated for the day will be deleted and cannot be recovered.',
confirm: 'Confirm Deactivation',
cancel: 'Cancel',
error: {
title: 'Operation Failed',
message: 'An error occurred while deactivating medication, please try again later.',
},
},
toggleError: {
title: 'Operation Failed',
message: 'An error occurred while toggling reminder status, please try again later.',
},
updateErrors: {
dosage: 'Update Failed',
dosageMessage: 'An error occurred while updating dosage, please try again later.',
form: 'Update Failed',
formMessage: 'An error occurred while updating form, please try again later.',
},
imageViewer: {
close: 'Close',
},
pickers: {
cancel: 'Cancel',
confirm: 'Confirm',
},
},
// 编辑频率页面翻译
editFrequency: {
title: 'Edit Medication Frequency',
missingParams: 'Missing required parameters',
medicationName: 'Editing: {{name}}',
sections: {
frequency: 'Medication Frequency',
frequencyDescription: 'Set daily medication frequency',
time: 'Daily Reminder Times',
timeDescription: 'Add and manage daily reminder times',
},
frequency: {
repeatPattern: 'Repeat Pattern',
timesPerDay: 'Times Per Day',
daily: 'Daily',
weekly: 'Weekly',
custom: 'Custom',
timesLabel: '{{count}} times',
summary: '{{pattern}} {{count}} times',
},
time: {
addTime: 'Add Time',
editTime: 'Edit Reminder Time',
addTimeButton: 'Add Time',
},
actions: {
save: 'Save Changes',
},
error: {
title: 'Update Failed',
message: 'An error occurred while updating medication frequency, please try again later.',
},
pickers: {
cancel: 'Cancel',
confirm: 'Confirm',
},
},
},
notificationSettings: {
title: 'Notification Settings',
loading: 'Loading...',
sections: {
notifications: 'Notification Settings',
medicationReminder: 'Medication Reminder',
nutritionReminder: 'Nutrition Reminder',
moodReminder: 'Mood Reminder',
description: 'Description',
},
items: {
pushNotifications: {
title: 'Push Notifications',
description: 'Receive app notifications when enabled',
},
medicationReminder: {
title: 'Medication Reminder',
description: 'Receive reminder notifications at medication time',
},
nutritionReminder: {
title: 'Nutrition Record Reminder',
description: 'Receive nutrition record reminders at meal times',
},
moodReminder: {
title: 'Mood Record Reminder',
description: 'Receive mood record reminders in the evening',
},
},
description: {
text: '• Push notifications is the master switch for all notifications\n• Various reminders require push notifications to be enabled\n• You can manage notification permissions in system settings\n• Disabling push notifications will stop all app notifications',
},
alerts: {
permissionDenied: {
title: 'Permission Denied',
message: 'Please enable notification permission in system settings, then try to enable push notifications',
cancel: 'Cancel',
goToSettings: 'Go to Settings',
},
error: {
title: 'Error',
message: 'Failed to request notification permission',
saveFailed: 'Failed to save settings',
medicationReminderFailed: 'Failed to set medication reminder',
nutritionReminderFailed: 'Failed to set nutrition reminder',
moodReminderFailed: 'Failed to set mood reminder',
},
notificationsEnabled: {
title: 'Notifications Enabled',
body: 'You will receive app notifications and reminders',
},
medicationReminderEnabled: {
title: 'Medication Reminder Enabled',
body: 'You will receive reminder notifications at medication time',
},
nutritionReminderEnabled: {
title: 'Nutrition Reminder Enabled',
body: 'You will receive nutrition record reminders at meal times',
},
moodReminderEnabled: {
title: 'Mood Reminder Enabled',
body: 'You will receive mood record reminders in the evening',
},
},
},
tabBarConfig: {
title: 'Tab Bar Settings',
subtitle: 'Customize your bottom navigation',
description: 'Use toggle to show or hide tabs',
resetButton: 'Reset to Default',
cannotDisable: 'This tab cannot be disabled',
resetConfirm: {
title: 'Reset to default?',
message: 'This will reset all tab bar settings and visibility',
cancel: 'Cancel',
confirm: 'Reset',
},
resetSuccess: 'Settings reset to default',
},
},
},
};
export const isSupportedLanguage = (language?: string | null): language is AppLanguage => {
if (!language) return false;
return SUPPORTED_LANGUAGES.some((code) => language === code || language.startsWith(`${code}-`));
};
export const getNormalizedLanguage = (language?: string | null): AppLanguage => {
if (!language) return fallbackLanguage;
const normalized = SUPPORTED_LANGUAGES.find((code) => language === code || language.startsWith(`${code}-`));
return normalized ?? fallbackLanguage;
};
const getStoredLanguage = (): AppLanguage | null => {
try {
const stored = getItemSync?.(LANGUAGE_PREFERENCE_KEY) as AppLanguage | null | undefined;
if (stored && isSupportedLanguage(stored)) {
return stored;
}
} catch (error) {
// ignore storage errors and fall back to device preference
}
return null;
};
const getDeviceLanguage = (): AppLanguage | null => {
try {
const locales = Localization.getLocales();
const preferred = locales.find((locale) => locale.languageCode && isSupportedLanguage(locale.languageCode));
return preferred?.languageCode as AppLanguage | undefined || null;
} catch (error) {
return null;
}
};
const initialLanguage = getStoredLanguage() ?? getDeviceLanguage() ?? fallbackLanguage;
void i18n.use(initReactI18next).init({
compatibilityJSON: 'v4',
resources,
lng: initialLanguage,
fallbackLng: fallbackLanguage,
interpolation: {
escapeValue: false,
},
returnNull: false,
});
export const changeAppLanguage = async (language: AppLanguage) => {
const nextLanguage = isSupportedLanguage(language) ? language : fallbackLanguage;
await i18n.changeLanguage(nextLanguage);
await setItem(LANGUAGE_PREFERENCE_KEY, nextLanguage);
};
export default i18n;