mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-05 19:20:12 +08:00
feat: support changing quest language
This commit is contained in:
1 parent
9b8d7109cf
commit
66b0b7ba68
18 files changed
+313
-109
No files matched your search
@@ -5,8 +5,9 @@ import { useToast } from 'vue-toastification'
|
||||
import { ref, defineProps, onMounted } from 'vue'
|
||||
import MarkdownText from './MarkdownText.vue'
|
||||
import { trpc } from '../utils/trpc'
|
||||
import { SUPPORTED_LOCALES } from '../../../packages/constants'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { t, locale } = useI18n()
|
||||
|
||||
const props = defineProps({
|
||||
questTitle: {
|
||||
@@ -52,8 +53,15 @@ const handleColour = () => {
|
||||
}
|
||||
}
|
||||
onMounted(async () => {
|
||||
const quest = await trpc.quests.getQuestDetail.query(questId)
|
||||
questInstruction.value = quest.instruction
|
||||
try {
|
||||
const quest = await trpc.quests.getQuestDetail.query({
|
||||
questId,
|
||||
locale: locale.value,
|
||||
})
|
||||
questInstruction.value = quest.instruction
|
||||
} catch (error) {
|
||||
toast.error('Failed to load quest detail:', error)
|
||||
}
|
||||
handleColour()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { ref, onMounted, watch, computed } from 'vue'
|
||||
import { useToast } from 'vue-toastification'
|
||||
import { SUPPORTED_LOCALES } from '../../../packages/constants'
|
||||
import { DAG } from '../../../packages/utils'
|
||||
import { trpc } from '../utils/trpc'
|
||||
import useUserProfile from '../store/userProfile'
|
||||
import QuestIntro from './QuestIntro.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { t, locale } = useI18n()
|
||||
const store = useUserProfile()
|
||||
const toast = useToast()
|
||||
|
||||
@@ -66,7 +67,8 @@ async function computeGraphData() {
|
||||
if (!store.progress) return
|
||||
|
||||
try {
|
||||
const quests = await trpc.quests.getQuests.query()
|
||||
const quests = await trpc.quests.getQuests.query({ locale: locale.value })
|
||||
|
||||
const dag = new DAG(quests)
|
||||
const nodesValues = dag.getNodes()
|
||||
|
||||
@@ -117,6 +119,11 @@ onMounted(async () => {
|
||||
})
|
||||
|
||||
watch(() => store.progress, computeGraphData, { deep: true })
|
||||
watch(locale, () => {
|
||||
opened.value = null
|
||||
// eslint-disable-next-line no-void
|
||||
void computeGraphData()
|
||||
})
|
||||
|
||||
const curvedPath = computed(() => {
|
||||
return (source: Node, target: Node) => {
|
||||
|
||||
@@ -4,15 +4,44 @@ import { useRouter } from 'vue-router'
|
||||
import { useToast } from 'vue-toastification'
|
||||
import { useSession } from '../../store/session'
|
||||
import { openBugReport, openQuestSurvey } from '../../utils/formbricks'
|
||||
import { trpc } from '../../utils/trpc'
|
||||
import type { QuestLocale } from '../../../../packages/constants'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { t, locale } = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
const router = useRouter()
|
||||
const sessionStore = useSession()
|
||||
const reset = async () => {
|
||||
console.log('resetting...')
|
||||
await sessionStore.createSession()
|
||||
const selectedLocale = locale.value as QuestLocale
|
||||
const questId = sessionStore.session?.quest
|
||||
|
||||
if (!questId) return
|
||||
|
||||
try {
|
||||
const quests = await trpc.quests.getQuests.query({
|
||||
locale: selectedLocale,
|
||||
})
|
||||
|
||||
console.log({
|
||||
selectedLocale,
|
||||
questId,
|
||||
availableQuestIds: quests.map((quest) => quest.id),
|
||||
})
|
||||
|
||||
if (!quests.some((quest) => quest.id === questId)) {
|
||||
toast.warning(t('game.language_unavailable'))
|
||||
return
|
||||
}
|
||||
|
||||
await sessionStore.createSession(selectedLocale)
|
||||
} catch (error) {
|
||||
console.error('Failed to reset quest:', error)
|
||||
toast.error(t('game.reset_failed'))
|
||||
}
|
||||
}
|
||||
|
||||
const continuePlay = async () => {
|
||||
try {
|
||||
router.push({ name: 'map' })
|
||||
|
||||
@@ -3,8 +3,9 @@ import { useI18n } from 'vue-i18n'
|
||||
import { useToast } from 'vue-toastification'
|
||||
import { computed } from 'vue'
|
||||
import { useSession } from '../../store/session'
|
||||
import { SUPPORTED_LOCALES } from '../../../../packages/constants'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { t, locale } = useI18n()
|
||||
|
||||
const toast = useToast()
|
||||
const sessionStore = useSession()
|
||||
@@ -13,7 +14,7 @@ const showCover = computed(() => sessionStore.session === null)
|
||||
|
||||
const startSession = async () => {
|
||||
try {
|
||||
await sessionStore.createSession()
|
||||
await sessionStore.createSession(locale.value)
|
||||
} catch (err: any) {
|
||||
console.error(err)
|
||||
toast.error(err.message)
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { setLocale, type SupportedLocale } from '../../i18n'
|
||||
import { setLocale } from '../../i18n'
|
||||
import type { QuestLocale } from '../../../../packages/constants'
|
||||
import {
|
||||
SUPPORTED_LOCALES,
|
||||
LOCALE_NAME_MAP,
|
||||
} from '../../../../packages/constants'
|
||||
|
||||
const { locale } = useI18n({ useScope: 'global' })
|
||||
const { t, locale } = useI18n({ useScope: 'global' })
|
||||
|
||||
function changeLanguage(event: Event) {
|
||||
const selectedLocale = (event.target as HTMLSelectElement)
|
||||
.value as SupportedLocale
|
||||
.value as QuestLocale
|
||||
|
||||
setLocale(selectedLocale)
|
||||
}
|
||||
@@ -15,12 +20,14 @@ function changeLanguage(event: Event) {
|
||||
<template>
|
||||
<select
|
||||
:value="locale"
|
||||
:title="t('language.change')"
|
||||
class="bg-bg-secondary px-3 py-2 text-sm text-text shadow-md outline-none"
|
||||
aria-label="Language"
|
||||
@change="changeLanguage"
|
||||
style="font-size: 2vh"
|
||||
>
|
||||
<option value="en">English</option>
|
||||
<option value="zh-TW">繁體中文</option>
|
||||
<option v-for="locale in SUPPORTED_LOCALES" :key="locale" :value="locale">
|
||||
{{ LOCALE_NAME_MAP[locale] }}
|
||||
</option>
|
||||
</select>
|
||||
</template>
|
||||
@@ -1,16 +1,14 @@
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import type { QuestLocale } from '../../../packages/constants'
|
||||
import { SUPPORTED_LOCALES } from '../../../packages/constants'
|
||||
import en from './locales/en.json'
|
||||
import zhTW from './locales/zh-TW.json'
|
||||
|
||||
export const supportedLocales = ['zh-TW', 'en'] as const
|
||||
|
||||
export type SupportedLocale = (typeof supportedLocales)[number]
|
||||
|
||||
function isSupportedLocale(locale: string): locale is SupportedLocale {
|
||||
return supportedLocales.includes(locale as SupportedLocale)
|
||||
function isSupportedLocale(locale: string): locale is QuestLocale {
|
||||
return SUPPORTED_LOCALES.includes(locale as QuestLocale)
|
||||
}
|
||||
|
||||
function getInitialLocale(): SupportedLocale {
|
||||
function getInitialLocale(): QuestLocale {
|
||||
const savedLocale = localStorage.getItem('locale')
|
||||
|
||||
if (savedLocale && isSupportedLocale(savedLocale)) {
|
||||
@@ -34,7 +32,7 @@ export const i18n = createI18n({
|
||||
},
|
||||
})
|
||||
|
||||
export function setLocale(locale: SupportedLocale) {
|
||||
export function setLocale(locale: QuestLocale) {
|
||||
i18n.global.locale.value = locale
|
||||
localStorage.setItem('locale', locale)
|
||||
document.documentElement.lang = locale
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
{
|
||||
"language": {
|
||||
"change": "Change language",
|
||||
"traditional_chinese": "繁體中文",
|
||||
"english": "English"
|
||||
"change": "Change language"
|
||||
},
|
||||
"landing_nav": {
|
||||
"intro": "Introduction",
|
||||
@@ -102,6 +100,9 @@
|
||||
"reset": "Reset",
|
||||
"continue": "Continue",
|
||||
"command_book": "Command Book",
|
||||
"cheatsheets": "Cheat Sheets"
|
||||
"cheatsheets": "Cheat Sheets",
|
||||
"reset_to_apply_language": "Reset quest to apply language changes",
|
||||
"reset_failed": "Reset failed",
|
||||
"language_unavailable": "This quest does not support the selected language, please choose another language"
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
{
|
||||
"language": {
|
||||
"change": "切換語言",
|
||||
"traditional_chinese": "繁體中文",
|
||||
"english": "English"
|
||||
"change": "切換語言"
|
||||
},
|
||||
"landing_nav": {
|
||||
"intro": "前言",
|
||||
@@ -102,6 +100,9 @@
|
||||
"reset": "重置",
|
||||
"continue": "繼續",
|
||||
"command_book": "指令之書",
|
||||
"cheatsheets": "指令列表"
|
||||
"cheatsheets": "指令列表",
|
||||
"reset_to_apply_language": "重置關卡以套用語言變更",
|
||||
"reset_failed": "重置失敗",
|
||||
"language_unavailable": "此關卡不支援所選語言,請選擇其他語言"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import type { QuestLocale } from '../../../packages/constants'
|
||||
import {
|
||||
FileGraph,
|
||||
type FileGraphUpdateEvent,
|
||||
@@ -41,11 +42,16 @@ export const useSession = defineStore('session', {
|
||||
quest: null,
|
||||
}),
|
||||
actions: {
|
||||
async setQuest(questId: string) {
|
||||
this.quest = await trpc.quests.getQuestDetail.query(questId)
|
||||
async setQuest(questId: string, locale: QuestLocale) {
|
||||
this.quest = await trpc.quests.getQuestDetail.query({
|
||||
questId,
|
||||
locale,
|
||||
})
|
||||
this.questId = questId
|
||||
},
|
||||
async setSession(session: SessionDetail) {
|
||||
await this.setQuest(session.quest, session.locale)
|
||||
|
||||
this.session = {
|
||||
...session,
|
||||
graph: new FileGraph(session.graph),
|
||||
@@ -59,19 +65,18 @@ export const useSession = defineStore('session', {
|
||||
)
|
||||
term.focus()
|
||||
},
|
||||
async createSession() {
|
||||
async createSession(locale: QuestLocale) {
|
||||
const session = await trpc.session.createSession.mutate({
|
||||
questId: this.questId,
|
||||
locale,
|
||||
})
|
||||
await this.setSession(session)
|
||||
term.send('echo start\n')
|
||||
},
|
||||
async getActiveSession() {
|
||||
const session = await trpc.session.getActiveSession.query({
|
||||
questId: this.questId,
|
||||
})
|
||||
async getActiveSession(questId: string) {
|
||||
const session = await trpc.session.getActiveSession.query({ questId })
|
||||
if (session) {
|
||||
this.setSession(session)
|
||||
await this.setSession(session)
|
||||
}
|
||||
},
|
||||
newUpdate(update: SessionUpdate) {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useToast } from 'vue-toastification'
|
||||
import { computed, onMounted, watch } from 'vue'
|
||||
import { useSession } from '../store/session'
|
||||
import useUserProfile from '../store/userProfile'
|
||||
@@ -13,6 +15,8 @@ import CompleteModal from '../components/game/CompleteModal.vue'
|
||||
import StartButton from '../components/game/StartButton.vue'
|
||||
import { openQuestSurvey } from '../utils/formbricks'
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
const toast = useToast()
|
||||
const sessionStore = useSession()
|
||||
const userStore = useUserProfile()
|
||||
|
||||
@@ -27,6 +31,13 @@ watch(completed, (newValue, oldValue) => {
|
||||
}
|
||||
})
|
||||
|
||||
watch(locale, (newLocale) => {
|
||||
const { session } = sessionStore
|
||||
if (session && newLocale !== session.locale) {
|
||||
toast.info(t('game.reset_to_apply_language'))
|
||||
}
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
questId: {
|
||||
type: String,
|
||||
@@ -37,8 +48,12 @@ const props = defineProps({
|
||||
onMounted(async () => {
|
||||
sessionStore.reset()
|
||||
sessionStore.setup()
|
||||
await sessionStore.setQuest(props.questId)
|
||||
await sessionStore.getActiveSession()
|
||||
|
||||
await sessionStore.getActiveSession(props.questId)
|
||||
|
||||
if (!sessionStore.session) {
|
||||
await sessionStore.setQuest(props.questId, locale.value)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
Reference in new issue
Block a user