feat(i18n): 中英文框架(命名空间字典+语言切换)+profile 接入参考+注册3个新页面
这个提交包含在:
@@ -0,0 +1,120 @@
|
||||
// Lightweight i18n for WeChat Mini Program (no official i18n API).
|
||||
//
|
||||
// Usage in a page:
|
||||
// var i18n = require('../../i18n/index')
|
||||
// onShow: function () { i18n.bind(this) } // injects `i18n` + `locale` into page data
|
||||
// WXML: {{i18n.common.loading}} / {{i18n.profile.title}}
|
||||
// JS strings: i18n.t('common.loading')
|
||||
//
|
||||
// Locale is persisted in storage; switching locale notifies listeners and the
|
||||
// convention is that every page re-runs i18n.bind(this) in onShow, so tab pages
|
||||
// pick up the new language the next time they are shown.
|
||||
|
||||
var locales = require('./locales/index')
|
||||
|
||||
var STORAGE_KEY = 'app_locale'
|
||||
var DEFAULT_LOCALE = 'zh'
|
||||
var SUPPORTED = ['zh', 'en']
|
||||
var LOCALE_NAMES = { zh: '中文', en: 'English' }
|
||||
|
||||
var _listeners = []
|
||||
var _current = null
|
||||
|
||||
function getSystemLocale() {
|
||||
try {
|
||||
var sys = wx.getSystemInfoSync()
|
||||
var lang = (sys.language || '').toLowerCase()
|
||||
return lang.indexOf('en') === 0 ? 'en' : 'zh'
|
||||
} catch (e) {
|
||||
return DEFAULT_LOCALE
|
||||
}
|
||||
}
|
||||
|
||||
function getLocale() {
|
||||
if (_current) return _current
|
||||
var saved = ''
|
||||
try { saved = wx.getStorageSync(STORAGE_KEY) } catch (e) {}
|
||||
_current = SUPPORTED.indexOf(saved) !== -1 ? saved : getSystemLocale()
|
||||
return _current
|
||||
}
|
||||
|
||||
function setLocale(locale) {
|
||||
if (SUPPORTED.indexOf(locale) === -1 || locale === _current) return
|
||||
_current = locale
|
||||
try { wx.setStorageSync(STORAGE_KEY, locale) } catch (e) {}
|
||||
applyTabBar()
|
||||
for (var i = 0; i < _listeners.length; i++) {
|
||||
try { _listeners[i](locale) } catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
function onChange(fn) { if (typeof fn === 'function') _listeners.push(fn) }
|
||||
function offChange(fn) {
|
||||
_listeners = _listeners.filter(function (f) { return f !== fn })
|
||||
}
|
||||
|
||||
function getDict() {
|
||||
var loc = getLocale()
|
||||
return locales[loc] || locales[DEFAULT_LOCALE]
|
||||
}
|
||||
|
||||
function _lookup(dict, key) {
|
||||
if (!dict) return undefined
|
||||
var parts = key.split('.')
|
||||
var cur = dict
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
if (cur == null) return undefined
|
||||
cur = cur[parts[i]]
|
||||
}
|
||||
return cur
|
||||
}
|
||||
|
||||
// t('profile.title', {name: 'x'}) — falls back to zh, then to the raw key.
|
||||
function t(key, params) {
|
||||
var loc = getLocale()
|
||||
var val = _lookup(locales[loc], key)
|
||||
if (val === undefined) val = _lookup(locales[DEFAULT_LOCALE], key)
|
||||
if (typeof val !== 'string') return val === undefined ? key : val
|
||||
if (params) {
|
||||
val = val.replace(/\{(\w+)\}/g, function (m, k) {
|
||||
return params[k] !== undefined ? params[k] : m
|
||||
})
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// Inject the full dictionary + current locale into a page/component's data.
|
||||
function bind(page) {
|
||||
if (page && typeof page.setData === 'function') {
|
||||
page.setData({ i18n: getDict(), locale: getLocale() })
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh the bottom tabBar labels to the current locale.
|
||||
function applyTabBar() {
|
||||
var d = getDict()
|
||||
var tab = (d && d.common && d.common.tab) || {}
|
||||
var items = [
|
||||
{ index: 0, text: tab.home },
|
||||
{ index: 1, text: tab.records },
|
||||
{ index: 2, text: tab.mine }
|
||||
]
|
||||
items.forEach(function (it) {
|
||||
if (!it.text) return
|
||||
wx.setTabBarItem({ index: it.index, text: it.text, fail: function () {} })
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SUPPORTED: SUPPORTED,
|
||||
LOCALE_NAMES: LOCALE_NAMES,
|
||||
getLocale: getLocale,
|
||||
setLocale: setLocale,
|
||||
getSystemLocale: getSystemLocale,
|
||||
onChange: onChange,
|
||||
offChange: offChange,
|
||||
getDict: getDict,
|
||||
bind: bind,
|
||||
applyTabBar: applyTabBar,
|
||||
t: t
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Shared strings used across many pages.
|
||||
module.exports = {
|
||||
zh: {
|
||||
tab: { home: '首页', records: '记录', mine: '我的' },
|
||||
loading: '加载中...',
|
||||
confirm: '确定',
|
||||
cancel: '取消',
|
||||
save: '保存',
|
||||
saving: '保存中...',
|
||||
back: '返回',
|
||||
retry: '重试',
|
||||
close: '关闭',
|
||||
done: '完成',
|
||||
start: '开始',
|
||||
stop: '停止',
|
||||
unknown: '未知',
|
||||
saveSuccess: '保存成功',
|
||||
saveFailed: '保存失败',
|
||||
networkError: '网络异常,请重试',
|
||||
deviceNotConnected: '设备未连接',
|
||||
regions: {
|
||||
right: '右区',
|
||||
left: '左区',
|
||||
top: '上区',
|
||||
middle: '中区',
|
||||
bottom: '下区'
|
||||
},
|
||||
wavelengths: {
|
||||
red: '红光',
|
||||
infrared: '红外光',
|
||||
uv: '紫外光',
|
||||
yellow: '黄光'
|
||||
}
|
||||
},
|
||||
en: {
|
||||
tab: { home: 'Home', records: 'Records', mine: 'Me' },
|
||||
loading: 'Loading...',
|
||||
confirm: 'OK',
|
||||
cancel: 'Cancel',
|
||||
save: 'Save',
|
||||
saving: 'Saving...',
|
||||
back: 'Back',
|
||||
retry: 'Retry',
|
||||
close: 'Close',
|
||||
done: 'Done',
|
||||
start: 'Start',
|
||||
stop: 'Stop',
|
||||
unknown: 'Unknown',
|
||||
saveSuccess: 'Saved',
|
||||
saveFailed: 'Save failed',
|
||||
networkError: 'Network error, please retry',
|
||||
deviceNotConnected: 'Device not connected',
|
||||
regions: {
|
||||
right: 'Right',
|
||||
left: 'Left',
|
||||
top: 'Top',
|
||||
middle: 'Center',
|
||||
bottom: 'Bottom'
|
||||
},
|
||||
wavelengths: {
|
||||
red: 'Red',
|
||||
infrared: 'Infrared',
|
||||
uv: 'UV',
|
||||
yellow: 'Yellow'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Merges per-namespace dictionaries into { zh: {...}, en: {...} }.
|
||||
// Each namespace file exports { zh: {...}, en: {...} } and owns its own strings,
|
||||
// so parallel work on different features never touches the same file.
|
||||
|
||||
var common = require('./common')
|
||||
var profile = require('./profile')
|
||||
var lightInfo = require('./lightInfo')
|
||||
var manual = require('./manual')
|
||||
var report = require('./report')
|
||||
|
||||
var namespaces = {
|
||||
common: common,
|
||||
profile: profile,
|
||||
lightInfo: lightInfo,
|
||||
manual: manual,
|
||||
report: report
|
||||
}
|
||||
|
||||
var zh = {}
|
||||
var en = {}
|
||||
for (var ns in namespaces) {
|
||||
if (!namespaces.hasOwnProperty(ns)) continue
|
||||
zh[ns] = namespaces[ns].zh || {}
|
||||
en[ns] = namespaces[ns].en || {}
|
||||
}
|
||||
|
||||
module.exports = { zh: zh, en: en }
|
||||
@@ -0,0 +1,6 @@
|
||||
// Strings for the light-therapy efficacy intro page (pages/light-info).
|
||||
// Owned by the light-info feature. Keep zh/en keys in sync.
|
||||
module.exports = {
|
||||
zh: {},
|
||||
en: {}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Strings for the manual treatment (direct wavelength/function select) page
|
||||
// (pages/manual-treatment). Owned by the manual-mode feature. Keep zh/en in sync.
|
||||
module.exports = {
|
||||
zh: {},
|
||||
en: {}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// "我的" page strings + language switcher.
|
||||
module.exports = {
|
||||
zh: {
|
||||
notLoggedIn: '未登录',
|
||||
editProfile: '编辑资料',
|
||||
editProfileTitle: '编辑资料',
|
||||
changeAvatar: '点击更换头像',
|
||||
nickname: '昵称',
|
||||
nicknamePlaceholder: '请输入昵称',
|
||||
nicknameRequired: '昵称不能为空',
|
||||
smartMode: '智能模式',
|
||||
smartModeOpen: '已开放',
|
||||
free: '免费',
|
||||
inUse: '使用中',
|
||||
remainDays: '剩余 {days}天',
|
||||
notSubscribed: '未订阅',
|
||||
goSubscribe: '去订阅 ›',
|
||||
menuDevice: '我的设备',
|
||||
bound: '已绑定',
|
||||
unbound: '未绑定',
|
||||
menuHistory: '使用记录',
|
||||
menuSubscription: '订阅管理',
|
||||
menuHelp: '使用帮助',
|
||||
menuContact: '联系我们',
|
||||
menuLightInfo: '光疗功效介绍',
|
||||
language: '语言 / Language',
|
||||
logout: '退出登录',
|
||||
logoutConfirmTitle: '退出登录',
|
||||
logoutConfirmText: '确定要退出登录吗?',
|
||||
unbindConfirmTitle: '确认解绑',
|
||||
unbindConfirmText: '解绑后将无法使用该设备,确定要解绑吗?',
|
||||
unbindAction: '解绑当前设备',
|
||||
unbindDone: '已解绑',
|
||||
unbindFailed: '解绑失败'
|
||||
},
|
||||
en: {
|
||||
notLoggedIn: 'Not logged in',
|
||||
editProfile: 'Edit',
|
||||
editProfileTitle: 'Edit Profile',
|
||||
changeAvatar: 'Tap to change avatar',
|
||||
nickname: 'Nickname',
|
||||
nicknamePlaceholder: 'Enter nickname',
|
||||
nicknameRequired: 'Nickname is required',
|
||||
smartMode: 'Smart Mode',
|
||||
smartModeOpen: 'Available',
|
||||
free: 'Free',
|
||||
inUse: 'Active',
|
||||
remainDays: '{days} days left',
|
||||
notSubscribed: 'Not subscribed',
|
||||
goSubscribe: 'Subscribe ›',
|
||||
menuDevice: 'My Device',
|
||||
bound: 'Bound',
|
||||
unbound: 'Not bound',
|
||||
menuHistory: 'Usage Records',
|
||||
menuSubscription: 'Subscription',
|
||||
menuHelp: 'Help',
|
||||
menuContact: 'Contact Us',
|
||||
menuLightInfo: 'Light Therapy Guide',
|
||||
language: '语言 / Language',
|
||||
logout: 'Log Out',
|
||||
logoutConfirmTitle: 'Log Out',
|
||||
logoutConfirmText: 'Are you sure you want to log out?',
|
||||
unbindConfirmTitle: 'Confirm Unbind',
|
||||
unbindConfirmText: 'You will not be able to use this device after unbinding. Continue?',
|
||||
unbindAction: 'Unbind current device',
|
||||
unbindDone: 'Unbound',
|
||||
unbindFailed: 'Unbind failed'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Strings for the scan report page (pages/scan-report).
|
||||
module.exports = {
|
||||
zh: {
|
||||
title: '肌肤扫描报告',
|
||||
subtitle: '基于多光谱反射的肤质倾向参考',
|
||||
scannedAt: '扫描时间',
|
||||
overallTitle: '整体倾向',
|
||||
regionTitle: '分区分析',
|
||||
recommendTitle: '推荐护理',
|
||||
noProblem: '未见明显倾向',
|
||||
startTreatment: '开始推荐护理',
|
||||
manualInstead: '我自己选',
|
||||
disclaimer: '本报告依据光反射信号给出肤质倾向参考,不构成医疗诊断。实际护理请结合自身情况。',
|
||||
conceptNotice: '当前诊断依据尚在标定中,结果仅供参考',
|
||||
level: { low: '轻度', mid: '中度', high: '明显' },
|
||||
problems: {
|
||||
aging: '光老化 / 暗沉',
|
||||
acne: '痘痘 / 出油',
|
||||
pigment: '色素 / 暗黄',
|
||||
deep: '深层循环 / 疲劳'
|
||||
},
|
||||
regionNames: {
|
||||
right: '右脸',
|
||||
left: '左脸',
|
||||
top: '额头',
|
||||
middle: '鼻区',
|
||||
bottom: '下巴'
|
||||
}
|
||||
},
|
||||
en: {
|
||||
title: 'Skin Scan Report',
|
||||
subtitle: 'Skin-tendency reference from multispectral reflection',
|
||||
scannedAt: 'Scanned at',
|
||||
overallTitle: 'Overall Tendency',
|
||||
regionTitle: 'Zone Analysis',
|
||||
recommendTitle: 'Recommended Care',
|
||||
noProblem: 'No notable tendency',
|
||||
startTreatment: 'Start Recommended Care',
|
||||
manualInstead: 'Choose Myself',
|
||||
disclaimer: 'This report gives a skin-tendency reference from light-reflection signals and is not a medical diagnosis. Please consider your own condition.',
|
||||
conceptNotice: 'Diagnosis thresholds are still being calibrated; results are for reference only',
|
||||
level: { low: 'Mild', mid: 'Moderate', high: 'Notable' },
|
||||
problems: {
|
||||
aging: 'Photoaging / Dullness',
|
||||
acne: 'Acne / Oiliness',
|
||||
pigment: 'Pigment / Sallowness',
|
||||
deep: 'Deep circulation / Fatigue'
|
||||
},
|
||||
regionNames: {
|
||||
right: 'Right cheek',
|
||||
left: 'Left cheek',
|
||||
top: 'Forehead',
|
||||
middle: 'Nose',
|
||||
bottom: 'Chin'
|
||||
}
|
||||
}
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户