feat(studio-ds): P2 i18n 脚手架——vue-i18n@10 + zh/en locales + 防闪烁早设脚本 + useAppearance(主题/语言持久化)
P2 of HJ-FE-DS-001 金样板。后续 P3 组件库 / P4 Feed+Create 落地。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2cd9bff52c
commit
ecae9025a9
@ -1,10 +1,21 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>game-studio</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||
<title>造梦AI</title>
|
||||
<!-- 防闪烁(FOUC):首屏渲染前按已持久化外观设 <html data-theme/lang>,避免暗→亮闪烁 -->
|
||||
<script>
|
||||
(function () {
|
||||
try {
|
||||
var t = localStorage.getItem('studio.theme');
|
||||
if (t === 'light' || t === 'dim') document.documentElement.setAttribute('data-theme', t);
|
||||
var l = localStorage.getItem('studio.lang');
|
||||
if (l === 'en-US' || l === 'zh-CN') document.documentElement.setAttribute('lang', l);
|
||||
} catch (e) { /* 隐私模式忽略 */ }
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
"pinia": "^3.0.4",
|
||||
"vant": "^4.9.24",
|
||||
"vue": "^3.5.34",
|
||||
"vue-i18n": "^10.0.0",
|
||||
"vue-router": "^4.6.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
64
game-studio/src/composables/useAppearance.ts
Normal file
64
game-studio/src/composables/useAppearance.ts
Normal file
@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 外观(主题 + 语言)统一管理:持久化 + <html data-theme/lang> 同步 + i18n locale 切换。
|
||||
* 全局单例响应式状态,多组件共享(设置页切换 → 全局即时生效)。
|
||||
* 与 index.html 的防闪烁脚本配套:脚本在首屏前先设属性,本模块在挂载前 initAppearance() 再对齐 i18n。
|
||||
*/
|
||||
import { ref } from 'vue'
|
||||
import { i18n } from '../i18n'
|
||||
|
||||
export type ThemeMode = 'dark' | 'light' | 'dim'
|
||||
export type LangCode = 'zh-CN' | 'en-US'
|
||||
|
||||
const THEME_KEY = 'studio.theme'
|
||||
const LANG_KEY = 'studio.lang'
|
||||
|
||||
// 读持久化主题(隐私模式 localStorage 抛错时回落暗色)
|
||||
function readTheme(): ThemeMode {
|
||||
try {
|
||||
const v = localStorage.getItem(THEME_KEY)
|
||||
if (v === 'dark' || v === 'light' || v === 'dim') return v
|
||||
} catch { /* 忽略 */ }
|
||||
return 'dark'
|
||||
}
|
||||
function readLang(): LangCode {
|
||||
try {
|
||||
const v = localStorage.getItem(LANG_KEY)
|
||||
if (v === 'zh-CN' || v === 'en-US') return v
|
||||
} catch { /* 忽略 */ }
|
||||
return 'zh-CN'
|
||||
}
|
||||
|
||||
// 全局单例(模块级,跨组件共享同一引用)
|
||||
const theme = ref<ThemeMode>(readTheme())
|
||||
const lang = ref<LangCode>(readLang())
|
||||
|
||||
// 暗色为默认(:root),不写 data-theme;浅/柔写属性触发语义层覆盖
|
||||
function applyTheme(t: ThemeMode): void {
|
||||
const el = document.documentElement
|
||||
if (t === 'dark') el.removeAttribute('data-theme')
|
||||
else el.setAttribute('data-theme', t)
|
||||
}
|
||||
function applyLang(l: LangCode): void {
|
||||
document.documentElement.setAttribute('lang', l)
|
||||
i18n.global.locale.value = l
|
||||
}
|
||||
|
||||
export function useAppearance() {
|
||||
function setTheme(t: ThemeMode): void {
|
||||
theme.value = t
|
||||
try { localStorage.setItem(THEME_KEY, t) } catch { /* 忽略 */ }
|
||||
applyTheme(t)
|
||||
}
|
||||
function setLang(l: LangCode): void {
|
||||
lang.value = l
|
||||
try { localStorage.setItem(LANG_KEY, l) } catch { /* 忽略 */ }
|
||||
applyLang(l)
|
||||
}
|
||||
return { theme, lang, setTheme, setLang }
|
||||
}
|
||||
|
||||
// main.ts 挂载前调用一次:把持久化外观应用到 DOM + i18n
|
||||
export function initAppearance(): void {
|
||||
applyTheme(theme.value)
|
||||
applyLang(lang.value)
|
||||
}
|
||||
18
game-studio/src/i18n/index.ts
Normal file
18
game-studio/src/i18n/index.ts
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* vue-i18n 初始化(组合式 API,legacy:false)。
|
||||
* zh-CN 为基线;缺 key 回落 zh-CN。实际生效语言由 useAppearance.initAppearance()
|
||||
* 在挂载前从持久化读取并应用(与 index.html 防闪烁脚本一致)。
|
||||
*/
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import zhCN from '../locales/zh-CN'
|
||||
import enUS from '../locales/en-US'
|
||||
|
||||
export const i18n = createI18n({
|
||||
legacy: false, // 组合式 API(useI18n / $t),关闭 legacy 选项式
|
||||
globalInjection: true, // 模板内可直接用 $t
|
||||
locale: 'zh-CN',
|
||||
fallbackLocale: 'zh-CN',
|
||||
messages: { 'zh-CN': zhCN, 'en-US': enUS },
|
||||
})
|
||||
|
||||
export default i18n
|
||||
17
game-studio/src/locales/en-US.ts
Normal file
17
game-studio/src/locales/en-US.ts
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 英文文案(与 zh-CN 同构)。缺 key 时由 i18n 回落 zh-CN。
|
||||
*/
|
||||
export default {
|
||||
common: {
|
||||
brand: 'ZaoMeng AI',
|
||||
nav: { feed: 'Feed', create: 'Create', message: 'Inbox', me: 'Me', discover: 'Discover' },
|
||||
action: { play: 'Play now', share: 'Share', save: 'Save', submit: 'Submit', cancel: 'Cancel', confirm: 'Confirm', back: 'Back', retry: 'Retry', more: 'More' },
|
||||
state: { loading: 'Loading', empty: 'Nothing here yet', saved: 'Saved', failed: 'Failed', success: 'Success' },
|
||||
status: { draft: 'Draft', review: 'In review', published: 'Published', rejected: 'Rejected' },
|
||||
},
|
||||
appearance: {
|
||||
title: 'Appearance',
|
||||
theme: 'Theme', themeDark: 'Dark', themeLight: 'Light', themeDim: 'Dim',
|
||||
language: 'Language', langZh: '中文', langEn: 'English',
|
||||
},
|
||||
}
|
||||
18
game-studio/src/locales/zh-CN.ts
Normal file
18
game-studio/src/locales/zh-CN.ts
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* 中文(基线)文案。命名空间:common 通用 / appearance 外观设置。
|
||||
* P4 起按页域增 feed / create / profile 等命名空间;新组件文案一律走此处,禁裸中文。
|
||||
*/
|
||||
export default {
|
||||
common: {
|
||||
brand: '造梦AI',
|
||||
nav: { feed: '游戏流', create: '创作', message: '消息', me: '我的', discover: '发现' },
|
||||
action: { play: '立即玩', share: '分享', save: '保存', submit: '提交', cancel: '取消', confirm: '确定', back: '返回', retry: '重试', more: '更多' },
|
||||
state: { loading: '加载中', empty: '这里还没有内容', saved: '已保存', failed: '失败', success: '成功' },
|
||||
status: { draft: '草稿', review: '审核中', published: '已发布', rejected: '已驳回' },
|
||||
},
|
||||
appearance: {
|
||||
title: '外观',
|
||||
theme: '主题', themeDark: '暗色', themeLight: '浅色', themeDim: '柔和',
|
||||
language: '语言', langZh: '中文', langEn: 'English',
|
||||
},
|
||||
}
|
||||
@ -16,11 +16,19 @@ import App from './App.vue'
|
||||
import router from './router'
|
||||
import { pinia } from './store'
|
||||
|
||||
// —— i18n(中英双语)+ 外观(主题/语言)初始化 ——
|
||||
import i18n from './i18n'
|
||||
import { initAppearance } from './composables/useAppearance'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
// 注册顺序:Pinia(状态)→ Router(路由)→ Vant(UI 组件 + 指令)
|
||||
app.use(pinia)
|
||||
app.use(router)
|
||||
app.use(Vant)
|
||||
app.use(i18n)
|
||||
|
||||
// 挂载前应用持久化的主题/语言到 DOM 与 i18n(防首屏不一致/闪烁)
|
||||
initAppearance()
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user