From e8dee338ed2fbe09ef112a170a6f00c5b166afce Mon Sep 17 00:00:00 2001 From: Zikai Date: Sun, 12 Jul 2026 15:03:04 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20EN=E5=88=87=E6=8D=A2=E5=A4=B1=E6=95=88?= =?UTF-8?q?=E3=80=81=E5=88=A0=E9=99=A4=E8=B4=A1=E7=8C=AE=E8=80=85=E6=8F=8F?= =?UTF-8?q?=E8=BF=B0=E3=80=81=E7=89=88=E6=9C=AC=E6=8C=89=E6=BC=94=E8=BF=9B?= =?UTF-8?q?=E7=BA=BF=E5=88=86=E7=BB=84(2D->3D)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix(i18n): composition 模式下响应式 locale 在 i18n.global 上(非 i18n.locale), loadLocaleAsync 与 useI18nLazy 改用 i18n.global.locale; useI18nLazy.current 改为派生自全局 locale 的 computed(单一数据源); 各组件 useI18n() 加 useScope:'global' 确保读到同一 composer。 headless 验证:默认中文(不下载EN chunk)->点EN加载en.[hash].js并切英文->切回中文 全通过 - content: 删除 MobileGame 概览中「主要贡献者 Zikai Wang... Hongxiang He...」句子 - timeline: 版本数据改为多演进线并行(zikai-ui/main/doby/roll-a-ball), 每线带 2D/3D 形态标记,3D 线前显示「形态转变:2D -> 3D 滚球」转折标记 --- package.json | 4 +- src/components/LanguageSwitcher.vue | 1 + src/composables/useI18nLazy.js | 10 +- src/i18n/index.js | 11 +- src/modules/mobile-game/MobileGame.vue | 10 +- .../mobile-game/components/HeroSection.vue | 2 +- .../mobile-game/components/Leaderboard.vue | 2 +- .../components/ScreenshotCarousel.vue | 2 +- .../mobile-game/components/TeamCard.vue | 2 +- .../components/VersionTimeline.vue | 219 +++++++++++++----- src/modules/mobile-game/data/versions.js | 115 ++++++--- 11 files changed, 273 insertions(+), 105 deletions(-) diff --git a/package.json b/package.json index aab5da5..d13cc1f 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ }, "dependencies": { "vue": "^3.4.21", - "vue-router": "^4.3.0", - "vue-i18n": "^9.13.1" + "vue-i18n": "^9.13.1", + "vue-router": "^4.3.0" }, "devDependencies": { "@vitejs/plugin-vue": "^5.0.4", diff --git a/src/components/LanguageSwitcher.vue b/src/components/LanguageSwitcher.vue index 0eb83c2..5177c9d 100644 --- a/src/components/LanguageSwitcher.vue +++ b/src/components/LanguageSwitcher.vue @@ -2,6 +2,7 @@ import { useI18nLazy } from '../composables/useI18nLazy.js' const { current, switching, switchLocale } = useI18nLazy() +// current 直接派生自全局 locale,无需 useI18n const locales = [ { code: 'zh-CN', label: '中' }, diff --git a/src/composables/useI18nLazy.js b/src/composables/useI18nLazy.js index 45396a8..581ed2f 100644 --- a/src/composables/useI18nLazy.js +++ b/src/composables/useI18nLazy.js @@ -1,17 +1,19 @@ -import { ref } from 'vue' -import { loadLocaleAsync } from '../i18n/index.js' +import { ref, computed } from 'vue' +import i18n, { loadLocaleAsync } from '../i18n/index.js' -const current = ref('zh-CN') const switching = ref(false) // 语言懒加载切换逻辑 +// current 派生自全局 i18n composer 的 locale(composition 模式下在 i18n.global), +// 与各组件内 useI18n({ useScope: 'global' }) 读到的是同一个 ref。 export function useI18nLazy() { + const current = computed(() => i18n.global.locale.value) + async function switchLocale(locale) { if (switching.value || current.value === locale) return switching.value = true try { await loadLocaleAsync(locale) - current.value = locale } finally { switching.value = false } diff --git a/src/i18n/index.js b/src/i18n/index.js index 7d72872..0b22a46 100644 --- a/src/i18n/index.js +++ b/src/i18n/index.js @@ -22,9 +22,10 @@ const loadedLocales = new Set(['zh-CN']) * 之后切换仅在已加载集合内完成(无网络请求)。 */ export async function loadLocaleAsync(locale) { - if (i18n.locale.value === locale) return + // composition 模式下响应式 locale 在 i18n.global 上 + if (i18n.global.locale.value === locale) return if (loadedLocales.has(locale)) { - i18n.locale.value = locale + i18n.global.locale.value = locale document.documentElement.setAttribute('lang', locale) return } @@ -35,14 +36,14 @@ export async function loadLocaleAsync(locale) { messages = (await import('./locales/en.js')).default } else { // 未知语言回退到中文 - i18n.locale.value = 'zh-CN' + i18n.global.locale.value = 'zh-CN' document.documentElement.setAttribute('lang', 'zh-CN') return } - i18n.mergeLocaleMessage(locale, messages) + i18n.global.mergeLocaleMessage(locale, messages) loadedLocales.add(locale) - i18n.locale.value = locale + i18n.global.locale.value = locale document.documentElement.setAttribute('lang', locale) } diff --git a/src/modules/mobile-game/MobileGame.vue b/src/modules/mobile-game/MobileGame.vue index 565aa50..276920b 100644 --- a/src/modules/mobile-game/MobileGame.vue +++ b/src/modules/mobile-game/MobileGame.vue @@ -6,7 +6,7 @@ import VersionTimeline from './components/VersionTimeline.vue' import TeamCard from './components/TeamCard.vue' import Leaderboard from './components/Leaderboard.vue' -const { locale } = useI18n() +const { locale } = useI18n({ useScope: 'global' }) const L = (zh, en) => (locale.value === 'zh-CN' ? zh : en) // 站点根绝对路径,用绑定避免被 Vite 当作构建期静态资源解析 const videoPoster = '/448/2.jpg' @@ -28,14 +28,14 @@ const videoSrc = '/448/ml/449ml.mp4'

{{ L( - '项目跨越 448 与 449 两门课程。448 用 Unity 开发游戏本体,产出可浏览器试玩的 WebGL 构建与可安装的 Android APK;449 搭建展示网站与基于 PHP+MySQL 的玩家分数排行榜。主要贡献者 Zikai Wang(zikai-ui / main / Roll-a-Ball 系列)与 Hongxiang He(2D DobyAdventure)。', - 'The project spans courses 448 and 449. 448 built the game with Unity, producing browser-playable WebGL builds and installable Android APKs; 449 built the showcase site and a PHP+MySQL player leaderboard. Main contributors: Zikai Wang (zikai-ui / main / Roll-a-Ball series) and Hongxiang He (2D DobyAdventure).' + '项目跨越 448 与 449 两门课程。448 用 Unity 开发游戏本体,产出可浏览器试玩的 WebGL 构建与可安装的 Android APK;449 搭建展示网站与基于 PHP+MySQL 的玩家分数排行榜。游戏经历多条并行演进线,从 2D 角色控制器与平台跳跃,最终转向手机陀螺仪操控的 3D 滚球,并尝试用 ML-Agents 做强化学习实验。', + 'The project spans courses 448 and 449. 448 built the game with Unity, producing browser-playable WebGL builds and installable Android APKs; 449 built the showcase site and a PHP+MySQL player leaderboard. The game evolved along several parallel lines, from 2D character controllers and platformers, ultimately shifting to a gyroscope-controlled 3D rolling ball, with ML-Agents reinforcement-learning experiments.' ) }}

{{ L( - '玩法经过多版本迭代:早期是键盘控制生命/能量的角色控制器,最终版改为手机陀螺仪倾斜操控的 3D 滚球,含雪花砖掉落、黄色砖弹跳等机制。还用 Unity ML-Agents 做了强化学习实验。', - 'Gameplay iterated across versions: early builds were keyboard-controlled character controllers with health/power stats; the final version became a gyroscope-tilt 3D rolling-ball game with falling snowflake bricks and bouncing yellow bricks. ML-Agents reinforcement-learning experiments were also conducted.' + '玩法经过多版本迭代:早期是键盘控制生命/能量的 2D 角色控制器,最终版改为手机陀螺仪倾斜操控的 3D 滚球,含雪花砖掉落、黄色砖弹跳等机制。', + 'Gameplay iterated across versions: early builds were keyboard-controlled 2D character controllers with health/power stats; the final version became a gyroscope-tilt 3D rolling-ball game with falling snowflake bricks and bouncing yellow bricks.' ) }}

diff --git a/src/modules/mobile-game/components/HeroSection.vue b/src/modules/mobile-game/components/HeroSection.vue index 7e9ff63..53516f1 100644 --- a/src/modules/mobile-game/components/HeroSection.vue +++ b/src/modules/mobile-game/components/HeroSection.vue @@ -2,7 +2,7 @@ import Tag from '../../../components/ui/Tag.vue' import { useI18n } from 'vue-i18n' -const { locale } = useI18n() +const { locale } = useI18n({ useScope: 'global' }) const meta = { course: { zh: '迈阿密大学 · 毕设 448 / 449', en: 'Miami University · Capstone 448 / 449' }, diff --git a/src/modules/mobile-game/components/Leaderboard.vue b/src/modules/mobile-game/components/Leaderboard.vue index ba2f6f8..188ad55 100644 --- a/src/modules/mobile-game/components/Leaderboard.vue +++ b/src/modules/mobile-game/components/Leaderboard.vue @@ -2,7 +2,7 @@ import { ref, onMounted } from 'vue' import { useI18n } from 'vue-i18n' -const { locale, t } = useI18n() +const { locale, t } = useI18n({ useScope: 'global' }) const rows = ref([]) const state = ref('loading') // loading | ok | error diff --git a/src/modules/mobile-game/components/ScreenshotCarousel.vue b/src/modules/mobile-game/components/ScreenshotCarousel.vue index fcc693b..32df863 100644 --- a/src/modules/mobile-game/components/ScreenshotCarousel.vue +++ b/src/modules/mobile-game/components/ScreenshotCarousel.vue @@ -3,7 +3,7 @@ import { ref, onMounted, onUnmounted } from 'vue' import { useI18n } from 'vue-i18n' import { screenshots } from '../data/versions.js' -const { locale } = useI18n() +const { locale } = useI18n({ useScope: 'global' }) const current = ref(0) let timer = null diff --git a/src/modules/mobile-game/components/TeamCard.vue b/src/modules/mobile-game/components/TeamCard.vue index f4a5d5e..867a090 100644 --- a/src/modules/mobile-game/components/TeamCard.vue +++ b/src/modules/mobile-game/components/TeamCard.vue @@ -2,7 +2,7 @@ import { useI18n } from 'vue-i18n' import { team } from '../data/team.js' -const { locale } = useI18n() +const { locale } = useI18n({ useScope: 'global' }) const L = (obj) => (locale.value === 'zh-CN' ? obj.zh : obj.en) diff --git a/src/modules/mobile-game/components/VersionTimeline.vue b/src/modules/mobile-game/components/VersionTimeline.vue index ee7d05e..2aa6a35 100644 --- a/src/modules/mobile-game/components/VersionTimeline.vue +++ b/src/modules/mobile-game/components/VersionTimeline.vue @@ -1,49 +1,90 @@