refactor: separate story locales (public/) from UI locales (src/), async fetch for story messages

This commit is contained in:
2026-06-10 12:10:03 +08:00
parent 0071a34acf
commit 4cf2263c78
7 changed files with 219 additions and 186 deletions

56
public/locales/en.json Normal file
View File

@@ -0,0 +1,56 @@
{
"intro": {
"choice": {
"left_door": "Walk toward the glowing door on the left",
"right_door": "Walk toward the ordinary door on the right",
"search": "Search the room carefully",
"stay": "Stay where you are, do nothing"
}
},
"left_door": {
"choice": {
"handshake": "Shake hands with the stranger",
"reject": "Refuse to shake, stay alert"
}
},
"right_door": {
"choice": {
"continue": "Keep moving forward",
"back": "Turn back"
}
},
"trust_ending": {
"choice": {
"journey": "Embark on a journey of trust (requires trust >= 80)",
"leave": "Leave this place"
}
},
"investigation_site": {
"choice": {
"leave": "Leave the room"
}
},
"desk_detail": {
"choice": {
"return": "Return to the crime scene",
"leave": "Leave"
}
},
"stay": {
"choice": {
"stand": "Stand up and leave"
}
},
"qte_success": {
"choice": {
"continue": "Keep moving forward",
"back": "Turn back"
}
},
"qte_fail": {
"choice": {
"continue": "Keep moving forward",
"back": "Turn back"
}
}
}

56
public/locales/ja.json Normal file
View File

@@ -0,0 +1,56 @@
{
"intro": {
"choice": {
"left_door": "左にある光るドアへ向かう",
"right_door": "右にある普通のドアへ向かう",
"search": "部屋を念入りに調べる",
"stay": "その場に留まる"
}
},
"left_door": {
"choice": {
"handshake": "見知らぬ人と握手する",
"reject": "握手を断り、警戒を続ける"
}
},
"right_door": {
"choice": {
"continue": "先へ進む",
"back": "引き返す"
}
},
"trust_ending": {
"choice": {
"journey": "信頼の旅に出るtrust >= 80 が必要)",
"leave": "ここを去る"
}
},
"investigation_site": {
"choice": {
"leave": "部屋を出る"
}
},
"desk_detail": {
"choice": {
"return": "調査現場に戻る",
"leave": "立ち去る"
}
},
"stay": {
"choice": {
"stand": "立ち上がって去る"
}
},
"qte_success": {
"choice": {
"continue": "先へ進む",
"back": "引き返す"
}
},
"qte_fail": {
"choice": {
"continue": "先へ進む",
"back": "引き返す"
}
}
}

56
public/locales/zh.json Normal file
View File

@@ -0,0 +1,56 @@
{
"intro": {
"choice": {
"left_door": "走向左边那扇发光的门",
"right_door": "走向右边那扇普通的门",
"search": "仔细搜索房间",
"stay": "留在原地,什么也不做"
}
},
"left_door": {
"choice": {
"handshake": "与陌生人握手",
"reject": "拒绝握手,保持警惕"
}
},
"right_door": {
"choice": {
"continue": "继续前进",
"back": "回头"
}
},
"trust_ending": {
"choice": {
"journey": "开启信任的旅程(需要 trust >= 80",
"leave": "离开这里"
}
},
"investigation_site": {
"choice": {
"leave": "离开房间"
}
},
"desk_detail": {
"choice": {
"return": "返回调查现场",
"leave": "离开"
}
},
"stay": {
"choice": {
"stand": "站起来离开"
}
},
"qte_success": {
"choice": {
"continue": "继续前进",
"back": "回头"
}
},
"qte_fail": {
"choice": {
"continue": "继续前进",
"back": "回头"
}
}
}

View File

@@ -1,29 +1,62 @@
import { ref } from 'vue' import { ref } from 'vue'
import zh from '@/locales/zh.json' import zhUI from '@/locales/zh.json'
import en from '@/locales/en.json' import enUI from '@/locales/en.json'
import ja from '@/locales/ja.json' import jaUI from '@/locales/ja.json'
const messages = { zh, en, ja } as const const uiMessages = { zh: zhUI, en: enUI, ja: jaUI } as const
type Lang = 'zh' | 'en' | 'ja' type Lang = 'zh' | 'en' | 'ja'
const currentLang = ref<Lang>( const currentLang = ref<Lang>(
(localStorage.getItem('lang') as Lang) || 'zh' (localStorage.getItem('lang') as Lang) || 'zh',
) )
export function useI18n() { const storyMessages = ref<Record<string, Record<string, any>>>({})
function t(key: string): string { const storyLoading = new Set<Lang>()
const parts = key.split('.')
let result: any = messages[currentLang.value] async function loadStoryMessages(lang: Lang) {
for (const p of parts) { if (storyMessages.value[lang] || storyLoading.has(lang)) return
result = result?.[p] storyLoading.add(lang)
try {
const resp = await fetch(`/locales/${lang}.json`)
if (resp.ok) {
storyMessages.value = {
...storyMessages.value,
[lang]: await resp.json(),
}
} }
return typeof result === 'string' ? result : key } catch {
/* story locales optional */
} finally {
storyLoading.delete(lang)
} }
}
function setLang(lang: Lang) { loadStoryMessages(currentLang.value)
currentLang.value = lang
localStorage.setItem('lang', lang) function t(key: string): string {
const parts = key.split('.')
let result: any = storyMessages.value[currentLang.value]
for (const p of parts) {
result = result?.[p]
} }
if (typeof result === 'string') return result
let fallback: any = uiMessages[currentLang.value]
for (const p of parts) {
fallback = fallback?.[p]
}
if (typeof fallback === 'string') return fallback
return key
}
async function setLang(lang: Lang) {
await loadStoryMessages(lang)
currentLang.value = lang
localStorage.setItem('lang', lang)
}
export function useI18n() {
return { t, currentLang, setLang } return { t, currentLang, setLang }
} }

View File

@@ -19,61 +19,5 @@
"speed": "Speed", "speed": "Speed",
"noAutoSave": "No auto save yet", "noAutoSave": "No auto save yet",
"autoSaveHint": "Game auto-saves to slot 0 at each scene change" "autoSaveHint": "Game auto-saves to slot 0 at each scene change"
},
"scene": {
"intro": {
"choice": {
"left_door": "Walk toward the glowing door on the left",
"right_door": "Walk toward the ordinary door on the right",
"search": "Search the room carefully",
"stay": "Stay where you are, do nothing"
}
},
"left_door": {
"choice": {
"handshake": "Shake hands with the stranger",
"reject": "Refuse to shake, stay alert"
}
},
"right_door": {
"choice": {
"continue": "Keep moving forward",
"back": "Turn back"
}
},
"trust_ending": {
"choice": {
"journey": "Embark on a journey of trust (requires trust >= 80)",
"leave": "Leave this place"
}
},
"investigation_site": {
"choice": {
"leave": "Leave the room"
}
},
"desk_detail": {
"choice": {
"return": "Return to the crime scene",
"leave": "Leave"
}
},
"stay": {
"choice": {
"stand": "Stand up and leave"
}
},
"qte_success": {
"choice": {
"continue": "Keep moving forward",
"back": "Turn back"
}
},
"qte_fail": {
"choice": {
"continue": "Keep moving forward",
"back": "Turn back"
}
}
} }
} }

View File

@@ -19,61 +19,5 @@
"speed": "倍速", "speed": "倍速",
"noAutoSave": "オートセーブがありません", "noAutoSave": "オートセーブがありません",
"autoSaveHint": "シーン切替時にスロット0に自動保存されます" "autoSaveHint": "シーン切替時にスロット0に自動保存されます"
},
"scene": {
"intro": {
"choice": {
"left_door": "左にある光るドアへ向かう",
"right_door": "右にある普通のドアへ向かう",
"search": "部屋を念入りに調べる",
"stay": "その場に留まる"
}
},
"left_door": {
"choice": {
"handshake": "見知らぬ人と握手する",
"reject": "握手を断り、警戒を続ける"
}
},
"right_door": {
"choice": {
"continue": "先へ進む",
"back": "引き返す"
}
},
"trust_ending": {
"choice": {
"journey": "信頼の旅に出るtrust >= 80 が必要)",
"leave": "ここを去る"
}
},
"investigation_site": {
"choice": {
"leave": "部屋を出る"
}
},
"desk_detail": {
"choice": {
"return": "調査現場に戻る",
"leave": "立ち去る"
}
},
"stay": {
"choice": {
"stand": "立ち上がって去る"
}
},
"qte_success": {
"choice": {
"continue": "先へ進む",
"back": "引き返す"
}
},
"qte_fail": {
"choice": {
"continue": "先へ進む",
"back": "引き返す"
}
}
} }
} }

View File

@@ -19,61 +19,5 @@
"speed": "倍速", "speed": "倍速",
"noAutoSave": "暂无自动存档", "noAutoSave": "暂无自动存档",
"autoSaveHint": "游戏会在每次场景切换时自动保存到槽位 0" "autoSaveHint": "游戏会在每次场景切换时自动保存到槽位 0"
},
"scene": {
"intro": {
"choice": {
"left_door": "走向左边那扇发光的门",
"right_door": "走向右边那扇普通的门",
"search": "仔细搜索房间",
"stay": "留在原地,什么也不做"
}
},
"left_door": {
"choice": {
"handshake": "与陌生人握手",
"reject": "拒绝握手,保持警惕"
}
},
"right_door": {
"choice": {
"continue": "继续前进",
"back": "回头"
}
},
"trust_ending": {
"choice": {
"journey": "开启信任的旅程(需要 trust >= 80",
"leave": "离开这里"
}
},
"investigation_site": {
"choice": {
"leave": "离开房间"
}
},
"desk_detail": {
"choice": {
"return": "返回调查现场",
"leave": "离开"
}
},
"stay": {
"choice": {
"stand": "站起来离开"
}
},
"qte_success": {
"choice": {
"continue": "继续前进",
"back": "回头"
}
},
"qte_fail": {
"choice": {
"continue": "继续前进",
"back": "回头"
}
}
} }
} }