@liujitcn/kratos-taro-app-core 0.0.19 → 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,28 +3,30 @@ import { useLoad } from '@tarojs/taro'
3
3
  import defaultLogo from '@liujitcn/kratos-taro-app-core/static/images/logo_icon.png'
4
4
  import { useSettingStore } from '../../../stores'
5
5
  import './index.scss'
6
+ import { useI18n } from '../../../locales'
6
7
 
7
8
  /** 应用首页。 */
8
9
  export default function HomePage() {
10
+ const { t } = useI18n()
9
11
  const settings = useSettingStore((state) => state.data)
10
12
  const loadData = useSettingStore((state) => state.loadData)
11
- const mainTitle = settings?.get('mainTitle') || '应用框架示例'
12
- const subTitle = settings?.get('subTitle') || '保留通用导航与个人中心体验'
13
+ const mainTitle = settings?.get('mainTitle') || t('core.home.mainTitle')
14
+ const subTitle = settings?.get('subTitle') || t('core.home.subTitle')
13
15
  const appLogo = settings?.get('appLogo') || defaultLogo
14
16
  useLoad(() => {
15
17
  void loadData().catch(() => undefined)
16
18
  })
17
19
 
18
20
  const stack = [
19
- ['应用框架', 'Taro + React 18'],
20
- ['开发语言', 'TypeScript'],
21
- ['状态管理', 'Zustand'],
22
- ['样式方案', 'Sass + px'],
21
+ [t('core.home.framework'), 'Taro + React 18'],
22
+ [t('core.home.language'), 'TypeScript'],
23
+ [t('core.home.stateManagement'), 'Zustand'],
24
+ [t('core.home.styleSolution'), 'Sass + px'],
23
25
  ]
24
26
  const demos = [
25
- ['跨端页面', '同一套页面代码适配 H5 和微信小程序'],
26
- ['账户能力', '个人资料、应用设置与智能助手入口已保留'],
27
- ['静态首页', '当前首页用于展示框架信息,不依赖业务接口'],
27
+ [t('core.home.crossPlatform'), t('core.home.crossPlatformDescription')],
28
+ [t('core.home.accountCapability'), t('core.home.accountCapabilityDescription')],
29
+ [t('core.home.staticHome'), t('core.home.staticHomeDescription')],
28
30
  ]
29
31
 
30
32
  return (
@@ -42,7 +44,7 @@ export default function HomePage() {
42
44
  </View>
43
45
 
44
46
  <View className='home-section'>
45
- <Text className='home-section__title'>当前技术栈</Text>
47
+ <Text className='home-section__title'>{t('core.home.techStack')}</Text>
46
48
  <View className='home-info-list'>
47
49
  {stack.map(([label, value]) => (
48
50
  <View className='home-info-row' key={label}>
@@ -54,7 +56,7 @@ export default function HomePage() {
54
56
  </View>
55
57
 
56
58
  <View className='home-section'>
57
- <Text className='home-section__title'>演示范围</Text>
59
+ <Text className='home-section__title'>{t('core.home.demoScope')}</Text>
58
60
  <View className='home-demo-list'>
59
61
  {demos.map(([title, description], index) => (
60
62
  <View className='home-demo-item' key={title}>
@@ -1,6 +1,6 @@
1
- import { Button, Image, Input, Text, View } from '@tarojs/components'
1
+ import { Button, Image, Input, Picker, Text, View } from '@tarojs/components'
2
2
  import Taro, { useLoad } from '@tarojs/taro'
3
- import { useMemo, useRef, useState } from 'react'
3
+ import { useEffect, useMemo, useRef, useState } from 'react'
4
4
  import defaultLogo from '@liujitcn/kratos-taro-app-core/static/images/logo_icon.png'
5
5
  import { defLoginService } from '../../../api/base/login'
6
6
  import { navigateAppRoute } from '../../../navigation'
@@ -14,6 +14,12 @@ import type {
14
14
  BehaviorCaptchaPoint,
15
15
  } from './components/types'
16
16
  import './login.scss'
17
+ import {
18
+ t as translate,
19
+ useLocaleStore,
20
+ useI18n,
21
+ type SupportedLocale,
22
+ } from '../../../locales'
17
23
 
18
24
  const behaviorCaptchaTypes = new Set(['slide', 'click', 'rotate'])
19
25
  const wechatMiniProvider = 'wechatmini'
@@ -43,11 +49,9 @@ function isWechatUnboundError(error: unknown): boolean {
43
49
  }
44
50
  }
45
51
  const reason = response.data?.reason ?? response.data?.error?.reason
46
- const message = response.data?.message ?? response.data?.error?.message
47
52
  return (
48
53
  response.data?.binding_required === true ||
49
- String(reason || '') === 'UNAUTHENTICATED' ||
50
- message === '微信账号未绑定,请先绑定已有账号'
54
+ String(reason || '') === 'UNAUTHENTICATED'
51
55
  )
52
56
  }
53
57
 
@@ -62,7 +66,7 @@ async function resolveMiniCaptchaImage(payload: string, captchaId: string): Prom
62
66
  data: payload.slice(commaIndex + 1),
63
67
  encoding: 'base64',
64
68
  success: () => resolve(),
65
- fail: () => reject(new Error('验证码图片写入失败')),
69
+ fail: () => reject(new Error(translate('core.login.captchaWriteFailed'))),
66
70
  })
67
71
  })
68
72
  return filePath
@@ -70,6 +74,8 @@ async function resolveMiniCaptchaImage(payload: string, captchaId: string): Prom
70
74
 
71
75
  /** 账号和微信授权登录页。 */
72
76
  export default function LoginPage() {
77
+ const { locale, setLocale, t } = useI18n()
78
+ const languageOptions = useLocaleStore((state) => state.languageOptions)
73
79
  const settings = useSettingStore((state) => state.data)
74
80
  const loadSettings = useSettingStore((state) => state.loadData)
75
81
  const userStore = useUserStore.getState()
@@ -91,8 +97,8 @@ export default function LoginPage() {
91
97
  const [miniForm, setMiniForm] = useState(emptyLoginForm)
92
98
  const [miniPassword, setMiniPassword] = useState('')
93
99
 
94
- const mainTitle = settings?.get('mainTitle') || '应用框架示例'
95
- const subTitle = settings?.get('subTitle') || '欢迎使用本应用'
100
+ const mainTitle = settings?.get('mainTitle') || t('core.home.mainTitle')
101
+ const subTitle = settings?.get('subTitle') || t('core.login.defaultSubTitle')
96
102
  const appLogo = settings?.get('appLogo') || defaultLogo
97
103
  const showTenantCode = settings?.get('showTenantCode') !== 'false'
98
104
  const configuredCaptchaType = settings?.get('captchaType') || ''
@@ -112,7 +118,7 @@ export default function LoginPage() {
112
118
  verticalPadding: 0,
113
119
  horizontalPadding: 0,
114
120
  iconSize: 20,
115
- title: '拖动滑块,将内圈图片转正',
121
+ title: t('core.login.behaviorRotate'),
116
122
  }
117
123
  }
118
124
  return {
@@ -123,10 +129,10 @@ export default function LoginPage() {
123
129
  showTheme: false,
124
130
  verticalPadding: 0,
125
131
  horizontalPadding: 0,
126
- buttonText: '确认',
132
+ buttonText: t('common.action.confirm'),
127
133
  iconSize: 20,
128
134
  dotSize: 20,
129
- title: captchaType === 'click' ? '请按顺序点击文字' : '请拖动滑块完成拼图',
135
+ title: captchaType === 'click' ? t('core.login.behaviorClick') : t('core.login.behaviorPuzzle'),
130
136
  }
131
137
  }, [
132
138
  behaviorData.thumbHeight,
@@ -134,8 +140,13 @@ export default function LoginPage() {
134
140
  behaviorHeight,
135
141
  behaviorWidth,
136
142
  captchaType,
143
+ locale,
137
144
  ]) as unknown as Record<string, string | number | boolean>
138
145
 
146
+ useEffect(() => {
147
+ void Taro.setNavigationBarTitle({ title: t('common.action.login') })
148
+ }, [locale])
149
+
139
150
  const updateForm = (key: keyof LoginRequest, value: string) => {
140
151
  setForm((current) => ({ ...current, [key]: value }))
141
152
  }
@@ -144,9 +155,9 @@ export default function LoginPage() {
144
155
  }
145
156
 
146
157
  const refreshCaptcha = async (requestedType = captchaType || configuredCaptchaType) => {
147
- if (!requestedType) throw new Error('登录验证码类型未配置')
158
+ if (!requestedType) throw new Error(t('core.login.captchaTypeMissing'))
148
159
  const data = await defLoginService.Captcha({ type: requestedType })
149
- if (!data.type) throw new Error('验证码接口未返回类型')
160
+ if (!data.type) throw new Error(t('core.login.captchaTypeResponseMissing'))
150
161
  setCaptchaType(data.type)
151
162
  setForm((current) => ({ ...current, captcha_id: data.captcha_id, captcha_code: '' }))
152
163
  if (!behaviorCaptchaTypes.has(data.type)) {
@@ -189,7 +200,7 @@ export default function LoginPage() {
189
200
  captcha_code: '',
190
201
  }))
191
202
  } catch {
192
- await Taro.showToast({ icon: 'none', title: '验证码加载失败' })
203
+ await Taro.showToast({ icon: 'none', title: t('core.login.captchaLoadFailed') })
193
204
  }
194
205
  }
195
206
 
@@ -201,7 +212,7 @@ export default function LoginPage() {
201
212
  await loadSettings()
202
213
  const nextCaptchaType = useSettingStore.getState().getData('captchaType')
203
214
  if (process.env.TARO_ENV === 'h5') {
204
- if (!nextCaptchaType) throw new Error('登录验证码类型未配置')
215
+ if (!nextCaptchaType) throw new Error(t('core.login.captchaTypeMissing'))
205
216
  setCaptchaType(nextCaptchaType)
206
217
  if (!behaviorCaptchaTypes.has(nextCaptchaType)) await refreshCaptcha(nextCaptchaType)
207
218
  } else {
@@ -212,7 +223,7 @@ export default function LoginPage() {
212
223
  } catch (error) {
213
224
  await Taro.showToast({
214
225
  icon: 'none',
215
- title: error instanceof Error ? error.message : '移动端配置加载失败',
226
+ title: error instanceof Error ? error.message : t('core.protocol.loadFailed'),
216
227
  })
217
228
  settingsPromise.current = undefined
218
229
  return false
@@ -229,17 +240,17 @@ export default function LoginPage() {
229
240
  if (!(await loadLoginSettings())) return false
230
241
  const currentSettings = useSettingStore.getState()
231
242
  if (!currentSettings.getData('serviceProtocol') || !currentSettings.getData('privacyProtocol')) {
232
- await Taro.showToast({ icon: 'none', title: '服务条款和隐私协议未配置' })
243
+ await Taro.showToast({ icon: 'none', title: t('core.login.protocolMissing') })
233
244
  return false
234
245
  }
235
246
  if (agreed) return true
236
247
  setShake(true)
237
248
  setTimeout(() => setShake(false), 500)
238
249
  const result = await Taro.showModal({
239
- title: '提示',
240
- content: '请先阅读并勾选协议内容,点击确定后将自动勾选并继续登录',
241
- confirmText: '确定',
242
- cancelText: '取消',
250
+ title: t('common.title.notice'),
251
+ content: t('core.login.protocolPrompt'),
252
+ confirmText: t('common.action.confirm'),
253
+ cancelText: t('common.action.cancel'),
243
254
  })
244
255
  if (result.confirm) setAgreed(true)
245
256
  return result.confirm
@@ -248,7 +259,7 @@ export default function LoginPage() {
248
259
  /** 完成用户资料加载并恢复登录前页面。 */
249
260
  const loginSuccess = async () => {
250
261
  await userStore.getUserProfile()
251
- await Taro.showToast({ icon: 'success', title: '登录成功' })
262
+ await Taro.showToast({ icon: 'success', title: t('core.login.loginSuccess') })
252
263
  await new Promise<void>((resolve) => setTimeout(resolve, 500))
253
264
  await restoreLoginRedirect()
254
265
  }
@@ -256,10 +267,10 @@ export default function LoginPage() {
256
267
  const validateLoginForm = async () => {
257
268
  if (!(await loadLoginSettings())) return false
258
269
  const validations: Array<[boolean, string]> = [
259
- [!showTenantCode || Boolean(form.tenant_code), '请输入租户编号'],
260
- [Boolean(form.user_name), '请输入用户名或手机号'],
261
- [Boolean(password), '请输入密码'],
262
- [isBehaviorCaptcha || Boolean(form.captcha_code), '请输入验证码'],
270
+ [!showTenantCode || Boolean(form.tenant_code), t('core.login.tenant')],
271
+ [Boolean(form.user_name), t('core.login.userName')],
272
+ [Boolean(password), t('core.login.password')],
273
+ [isBehaviorCaptcha || Boolean(form.captcha_code), t('core.login.captcha')],
263
274
  ]
264
275
  const failed = validations.find(([valid]) => !valid)
265
276
  if (failed) {
@@ -298,7 +309,7 @@ export default function LoginPage() {
298
309
  } catch (error) {
299
310
  await refreshCaptcha().catch(() => undefined)
300
311
  if (error instanceof Error) {
301
- await Taro.showToast({ icon: 'none', title: error.message || '登录失败,请重试' })
312
+ await Taro.showToast({ icon: 'none', title: error.message || t('core.login.loginFailed') })
302
313
  }
303
314
  } finally {
304
315
  setLoading(false)
@@ -359,7 +370,7 @@ export default function LoginPage() {
359
370
  setMiniBinding(true)
360
371
  await refreshMiniCaptcha()
361
372
  } else {
362
- await Taro.showToast({ icon: 'none', title: '微信登录失败,请稍后重试' })
373
+ await Taro.showToast({ icon: 'none', title: t('core.login.wechatFailed') })
363
374
  }
364
375
  } finally {
365
376
  setLoading(false)
@@ -369,9 +380,9 @@ export default function LoginPage() {
369
380
  const bindMiniAccount = async () => {
370
381
  if (loading || !(await checkedAgreePrivacy())) return
371
382
  const failed = [
372
- [showTenantCode && !miniForm.tenant_code, '请输入租户编号'],
373
- [!miniForm.user_name || !miniPassword, '请输入用户名和密码'],
374
- [!miniForm.captcha_id || !miniForm.captcha_code, '请输入验证码'],
383
+ [showTenantCode && !miniForm.tenant_code, t('core.login.tenant')],
384
+ [!miniForm.user_name || !miniPassword, t('core.login.userNamePassword')],
385
+ [!miniForm.captcha_id || !miniForm.captcha_code, t('core.login.captcha')],
375
386
  ].find(([invalid]) => invalid)
376
387
  if (failed) {
377
388
  await Taro.showToast({ icon: 'none', title: String(failed[1]) })
@@ -403,7 +414,7 @@ export default function LoginPage() {
403
414
  <View className={`login-tips${shake ? ' login-tips--shake' : ''}`}>
404
415
  <View className='login-agreement' onClick={() => setAgreed((value) => !value)}>
405
416
  <View className={`login-agree-icon${agreed ? ' checked' : ''}`} />
406
- <Text className='login-agree-desc'>我已阅读并同意</Text>
417
+ <Text className='login-agree-desc'>{t('core.login.agreePrefix')}</Text>
407
418
  <Text
408
419
  className='login-agree-link'
409
420
  onClick={(event) => {
@@ -411,9 +422,9 @@ export default function LoginPage() {
411
422
  navigateAppRoute('app/protocol/service')
412
423
  }}
413
424
  >
414
- 《服务条款》
425
+ {t('core.login.service')}
415
426
  </Text>
416
- <Text className='login-agree-separator'>和</Text>
427
+ <Text className='login-agree-separator'>{t('core.login.agreeSeparator')}</Text>
417
428
  <Text
418
429
  className='login-agree-link'
419
430
  onClick={(event) => {
@@ -421,7 +432,7 @@ export default function LoginPage() {
421
432
  navigateAppRoute('app/protocol/privacy')
422
433
  }}
423
434
  >
424
- 《隐私协议》
435
+ {t('core.login.privacy')}
425
436
  </Text>
426
437
  </View>
427
438
  </View>
@@ -429,6 +440,18 @@ export default function LoginPage() {
429
440
 
430
441
  return (
431
442
  <View className='login-page'>
443
+ <Picker
444
+ className='login-locale'
445
+ mode='selector'
446
+ range={languageOptions.map((item) => item.language_name)}
447
+ value={Math.max(0, languageOptions.findIndex((item) => item.language_code === locale))}
448
+ onChange={(event) => {
449
+ const nextLocale = languageOptions[Number(event.detail.value)]?.language_code as SupportedLocale | undefined
450
+ if (nextLocale) void setLocale(nextLocale)
451
+ }}
452
+ >
453
+ <View className='login-locale__value'>{t(`common.language.${locale}`)}</View>
454
+ </Picker>
432
455
  <View className='login-hero'>
433
456
  <View className='login-logo-shell'>
434
457
  <Image className='login-logo' src={appLogo} />
@@ -442,24 +465,24 @@ export default function LoginPage() {
442
465
  {process.env.TARO_ENV === 'h5' ? (
443
466
  <View className='login-form'>
444
467
  {showTenantCode ? (
445
- <Input className='login-input' placeholder='请输入租户编号' value={form.tenant_code} onInput={(event) => updateForm('tenant_code', event.detail.value)} />
468
+ <Input className='login-input' placeholder={t('core.login.tenant')} value={form.tenant_code} onInput={(event) => updateForm('tenant_code', event.detail.value)} />
446
469
  ) : null}
447
- <Input className='login-input' placeholder='请输入用户名/手机号码' value={form.user_name} onInput={(event) => updateForm('user_name', event.detail.value)} />
448
- <Input className='login-input' password placeholder='请输入密码' value={password} onInput={(event) => setPassword(event.detail.value)} onConfirm={() => void onSubmit()} />
470
+ <Input className='login-input' placeholder={t('core.login.userNameMobile')} value={form.user_name} onInput={(event) => updateForm('user_name', event.detail.value)} />
471
+ <Input className='login-input' password placeholder={t('core.login.password')} value={password} onInput={(event) => setPassword(event.detail.value)} onConfirm={() => void onSubmit()} />
449
472
  {!isBehaviorCaptcha ? (
450
473
  <View className='captcha-row'>
451
- <Input className='login-input captcha-input' placeholder='请输入验证码' value={form.captcha_code} onInput={(event) => updateForm('captcha_code', event.detail.value)} onConfirm={() => void onSubmit()} />
474
+ <Input className='login-input captcha-input' placeholder={t('core.login.captcha')} value={form.captcha_code} onInput={(event) => updateForm('captcha_code', event.detail.value)} onConfirm={() => void onSubmit()} />
452
475
  <View className='captcha-divider' />
453
476
  <View className='captcha-trigger' onClick={() => void refreshCaptcha()}>
454
477
  <Image className='captcha-image' src={captchaImage} mode='aspectFit' />
455
478
  </View>
456
479
  </View>
457
480
  ) : null}
458
- <Button className='login-button login-button-primary' loading={loading} onClick={() => void onSubmit()}>登录</Button>
481
+ <Button className='login-button login-button-primary' loading={loading} onClick={() => void onSubmit()}>{t('common.action.login')}</Button>
459
482
  {behaviorVisible ? (
460
483
  <View className='login-behavior-mask'>
461
484
  <View className='login-behavior-panel'>
462
- {behaviorLoading ? <View className='login-behavior-loading'>加载中...</View> : null}
485
+ {behaviorLoading ? <View className='login-behavior-loading'>{t('core.login.behaviorLoading')}</View> : null}
463
486
  <BehaviorCaptcha type={captchaType} data={behaviorData} config={behaviorConfig} onConfirm={(value, reset) => void verifyBehaviorCaptcha(value, reset)} onRefresh={() => void refreshCaptcha()} onClose={() => setBehaviorVisible(false)} />
464
487
  </View>
465
488
  </View>
@@ -467,20 +490,20 @@ export default function LoginPage() {
467
490
  </View>
468
491
  ) : !miniBinding ? (
469
492
  <Button className='login-button login-button-primary' loading={loading} onClick={() => void wxLogin()}>
470
- <Text className='login-phone-icon'>☎</Text>微信一键登录
493
+ <Text className='login-phone-icon'>☎</Text>{t('core.login.wechat')}
471
494
  </Button>
472
495
  ) : (
473
496
  <View className='login-form'>
474
- <View className='login-bind-tip'>该微信尚未绑定,请登录已有账号完成绑定</View>
475
- {showTenantCode ? <Input className='login-input' placeholder='请输入租户编号' value={miniForm.tenant_code} onInput={(event) => updateMiniForm('tenant_code', event.detail.value)} /> : null}
476
- <Input className='login-input' placeholder='请输入用户名/手机号码' value={miniForm.user_name} onInput={(event) => updateMiniForm('user_name', event.detail.value)} />
477
- <Input className='login-input' password placeholder='请输入密码' value={miniPassword} onInput={(event) => setMiniPassword(event.detail.value)} />
497
+ <View className='login-bind-tip'>{t('core.login.wechatBindTip')}</View>
498
+ {showTenantCode ? <Input className='login-input' placeholder={t('core.login.tenant')} value={miniForm.tenant_code} onInput={(event) => updateMiniForm('tenant_code', event.detail.value)} /> : null}
499
+ <Input className='login-input' placeholder={t('core.login.userNameMobile')} value={miniForm.user_name} onInput={(event) => updateMiniForm('user_name', event.detail.value)} />
500
+ <Input className='login-input' password placeholder={t('core.login.password')} value={miniPassword} onInput={(event) => setMiniPassword(event.detail.value)} />
478
501
  <View className='captcha-row'>
479
- <Input className='login-input captcha-input' placeholder='请输入验证码' value={miniForm.captcha_code} onInput={(event) => updateMiniForm('captcha_code', event.detail.value)} />
502
+ <Input className='login-input captcha-input' placeholder={t('core.login.captcha')} value={miniForm.captcha_code} onInput={(event) => updateMiniForm('captcha_code', event.detail.value)} />
480
503
  <View className='captcha-divider' />
481
504
  <View className='captcha-trigger' onClick={() => void refreshMiniCaptcha()}><Image className='captcha-image' src={miniCaptchaImage} mode='aspectFit' /></View>
482
505
  </View>
483
- <Button className='login-button login-button-primary' loading={loading} onClick={() => void bindMiniAccount()}>登录并绑定微信</Button>
506
+ <Button className='login-button login-button-primary' loading={loading} onClick={() => void bindMiniAccount()}>{t('core.login.bindWechat')}</Button>
484
507
  </View>
485
508
  )}
486
509
  {agreement}
@@ -2,6 +2,7 @@ import { RichText, View } from '@tarojs/components'
2
2
  import Taro, { useLoad } from '@tarojs/taro'
3
3
  import { useState } from 'react'
4
4
  import { useSettingStore } from '../../../stores'
5
+ import { useI18n } from '../../../locales'
5
6
  import './protocal.scss'
6
7
 
7
8
  const protocolClassMap: Record<string, string> = {
@@ -39,25 +40,26 @@ function decorateProtocolContent(html: string): string {
39
40
 
40
41
  /** 服务条款与隐私协议页。 */
41
42
  export default function ProtocolPage() {
43
+ const { t } = useI18n()
42
44
  const [content, setContent] = useState('')
43
45
 
44
46
  useLoad((query) => {
45
47
  const isPrivacy = query?.type === 'privacy'
46
- const title = isPrivacy ? '隐私协议' : '服务条款'
48
+ const title = isPrivacy ? t('core.protocol.privacy') : t('core.protocol.service')
47
49
  const key = isPrivacy ? 'privacyProtocol' : 'serviceProtocol'
48
50
  const settingStore = useSettingStore.getState()
49
51
  void settingStore
50
52
  .loadData()
51
53
  .then(() => {
52
54
  const protocol = settingStore.getData(key)
53
- if (!protocol) throw new Error(`${title}未配置,暂时无法查看`)
55
+ if (!protocol) throw new Error(t('core.protocol.notConfigured', { title }))
54
56
  void Taro.setNavigationBarTitle({ title })
55
57
  setContent(decorateProtocolContent(protocol))
56
58
  })
57
59
  .catch(async (error: unknown) => {
58
60
  await Taro.showToast({
59
61
  icon: 'none',
60
- title: error instanceof Error ? error.message : '移动端配置加载失败',
62
+ title: error instanceof Error ? error.message : t('core.protocol.loadFailed'),
61
63
  })
62
64
  setTimeout(() => void Taro.navigateBack(), 300)
63
65
  })
@@ -7,18 +7,11 @@ import {
7
7
  navigateAppRoute,
8
8
  } from '../../../navigation'
9
9
  import type { BootstrapViewKey } from '../../../module'
10
-
11
- const stateTitles: Record<BootstrapViewKey, string> = {
12
- BOOTSTRAP_LOADING: '正在加载',
13
- FORBIDDEN: '暂无访问权限',
14
- NOT_FOUND: '页面不存在',
15
- OFFLINE: '网络不可用',
16
- CONFIG_ERROR: '导航配置无效',
17
- PAGE_UNAVAILABLE: '页面暂不可用',
18
- }
10
+ import { useI18n } from '../../../locales'
19
11
 
20
12
  /** 启动与错误状态页。 */
21
13
  export default function StatusPage() {
14
+ const { t } = useI18n()
22
15
  const [state, setState] = useState<BootstrapViewKey>('BOOTSTRAP_LOADING')
23
16
  const [detail, setDetail] = useState('')
24
17
 
@@ -38,5 +31,6 @@ export default function StatusPage() {
38
31
  })
39
32
  })
40
33
 
41
- return <BootstrapStatus title={stateTitles[state]} detail={detail} />
34
+ const title = state === 'BOOTSTRAP_LOADING' ? t('core.status.loading') : t(`core.status.${state}`)
35
+ return <BootstrapStatus title={title} detail={detail} />
42
36
  }
@@ -2,9 +2,11 @@ import { Button, Text, View, WebView } from '@tarojs/components'
2
2
  import Taro, { useLoad } from '@tarojs/taro'
3
3
  import { useEffect, useState } from 'react'
4
4
  import './webview.scss'
5
+ import { useI18n } from '../../../locales'
5
6
 
6
7
  /** 统一外链承载页。 */
7
8
  export default function WebViewPage() {
9
+ const { t } = useI18n()
8
10
  const [url, setUrl] = useState('')
9
11
  const [iframeLoaded, setIframeLoaded] = useState(false)
10
12
  const [iframeTimedOut, setIframeTimedOut] = useState(false)
@@ -27,8 +29,8 @@ export default function WebViewPage() {
27
29
 
28
30
  const showFallback = !url || (isH5 && iframeTimedOut && !iframeLoaded)
29
31
  const emptyDescription = url
30
- ? '当前 H5 页面可能被目标站点限制嵌入'
31
- : '缺少有效链接地址'
32
+ ? t('core.webview.embedBlocked')
33
+ : t('core.webview.invalidUrl')
32
34
 
33
35
  return (
34
36
  <View className='webview-container'>
@@ -41,7 +43,7 @@ export default function WebViewPage() {
41
43
  ) : null}
42
44
  {showFallback ? (
43
45
  <View className='webview-empty'>
44
- <Text className='webview-empty__title'>链接无法打开</Text>
46
+ <Text className='webview-empty__title'>{t('core.webview.openFailed')}</Text>
45
47
  <Text className='webview-empty__desc'>{emptyDescription}</Text>
46
48
  </View>
47
49
  ) : null}
@@ -50,7 +52,7 @@ export default function WebViewPage() {
50
52
  className='webview-open-button'
51
53
  onClick={() => window.open(url, '_blank', 'noopener,noreferrer')}
52
54
  >
53
- 新窗口打开
55
+ {t('common.action.openInNewWindow')}
54
56
  </Button>
55
57
  ) : null}
56
58
  </View>