mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-05 19:20:12 +08:00
feat(i18n): add localized content for game page
This commit is contained in:
1 parent
87df12ac85
commit
03fbae616d
9 files changed
+90
-20
No files matched your search
@@ -1,7 +1,15 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<font-awesome-icon :icon="['fas', 'list']" class="text-text-primary p-1" />
|
<font-awesome-icon :icon="['fas', 'list']" class="text-text-primary p-1" />
|
||||||
<h1 id="CommandBook" class="inline text-text-primary font-xl">指令之書</h1>
|
<h1 id="CommandBook" class="inline text-text-primary font-xl">
|
||||||
|
{{ t('game.command_book') }}
|
||||||
|
</h1>
|
||||||
<div id="cheatsheets">
|
<div id="cheatsheets">
|
||||||
<p class="text-text-secondary">-- --指令列表-- --</p>
|
<p class="text-text-secondary">-- --{{ t('game.cheatsheets') }}-- --</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useToast } from 'vue-toastification'
|
import { useToast } from 'vue-toastification'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
@@ -6,6 +7,8 @@ import { Vue3Lottie } from 'vue3-lottie'
|
|||||||
// import { surveyUrl } from '../config'
|
// import { surveyUrl } from '../config'
|
||||||
import CompleteAnimation from '../../img/complete-animation.json'
|
import CompleteAnimation from '../../img/complete-animation.json'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
const showModal = ref(true) // Controls whether the modal is visible or not
|
const showModal = ref(true) // Controls whether the modal is visible or not
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useToast } from 'vue-toastification'
|
import { useToast } from 'vue-toastification'
|
||||||
import { useSession } from '../../store/session'
|
import { useSession } from '../../store/session'
|
||||||
import { openBugReport, openQuestSurvey } from '../../utils/formbricks'
|
import { openBugReport, openQuestSurvey } from '../../utils/formbricks'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const sessionStore = useSession()
|
const sessionStore = useSession()
|
||||||
const reset = async () => {
|
const reset = async () => {
|
||||||
@@ -14,16 +17,17 @@ const continuePlay = async () => {
|
|||||||
try {
|
try {
|
||||||
router.push({ name: 'map' })
|
router.push({ name: 'map' })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
useToast().error('無法讀取地圖')
|
useToast().error(t('quest_map.load_quest_map_failed'))
|
||||||
console.error(err)
|
console.error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="mt-2 flex flex-row gap-3 justify-center">
|
<section class="mt-2 flex flex-row gap-2 justify-center">
|
||||||
<button
|
<button
|
||||||
id="survey"
|
id="survey"
|
||||||
|
:title="t('game_nav.survey')"
|
||||||
class="bg-bg-secondary rounded-lg p-2"
|
class="bg-bg-secondary rounded-lg p-2"
|
||||||
@click="openQuestSurvey"
|
@click="openQuestSurvey"
|
||||||
>
|
>
|
||||||
@@ -31,15 +35,18 @@ const continuePlay = async () => {
|
|||||||
:icon="['fas', 'file-invoice']"
|
:icon="['fas', 'file-invoice']"
|
||||||
class="text-text-primary px-1"
|
class="text-text-primary px-1"
|
||||||
/>
|
/>
|
||||||
<p class="text-text-primary inline md:flex">問卷</p>
|
<p class="text-text-primary text-xs">{{ t('game_nav.survey') }}</p>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
title="Bug Report"
|
id="bug-report"
|
||||||
|
:title="t('game_nav.bug_report')"
|
||||||
@click="openBugReport"
|
@click="openBugReport"
|
||||||
class="bg-bg-secondary rounded-lg p-2"
|
class="bg-bg-secondary rounded-lg p-2"
|
||||||
>
|
>
|
||||||
<font-awesome-icon :icon="['fas', 'bug']" class="text-text-primary" />
|
<font-awesome-icon :icon="['fas', 'bug']" class="text-text-primary" />
|
||||||
<p class="text-text-primary inline ml-1 md:flex">回報</p>
|
<p class="text-text-primary text-xs">
|
||||||
|
{{ t('game_nav.bug_report') }}
|
||||||
|
</p>
|
||||||
</button>
|
</button>
|
||||||
<!-- <button disabled id="solution" class="bg-bg-disabled rounded-lg p-2">
|
<!-- <button disabled id="solution" class="bg-bg-disabled rounded-lg p-2">
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
@@ -48,15 +55,23 @@ const continuePlay = async () => {
|
|||||||
/>
|
/>
|
||||||
<p class="text-text-disabled inline ml-1 md:flex">解答</p>
|
<p class="text-text-disabled inline ml-1 md:flex">解答</p>
|
||||||
</button> -->
|
</button> -->
|
||||||
<button id="reset" class="bg-bg-secondary rounded-lg p-2" @click="reset">
|
<button
|
||||||
|
id="reset"
|
||||||
|
:title="t('game.reset')"
|
||||||
|
class="bg-bg-secondary rounded-lg p-2"
|
||||||
|
@click="reset"
|
||||||
|
>
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
:icon="['fas', 'arrow-rotate-left']"
|
:icon="['fas', 'arrow-rotate-left']"
|
||||||
class="text-text-primary px-1"
|
class="text-text-primary px-1"
|
||||||
/>
|
/>
|
||||||
<p class="text-text-primary inline ml-1 md:flex">重來</p>
|
<p class="text-text-primary text-xs">
|
||||||
|
{{ t('game.reset') }}
|
||||||
|
</p>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="continue"
|
id="continue"
|
||||||
|
:title="t('game.continue')"
|
||||||
class="bg-bg-secondary rounded-lg p-2"
|
class="bg-bg-secondary rounded-lg p-2"
|
||||||
@click="continuePlay"
|
@click="continuePlay"
|
||||||
>
|
>
|
||||||
@@ -64,7 +79,9 @@ const continuePlay = async () => {
|
|||||||
:icon="['far', 'circle-right']"
|
:icon="['far', 'circle-right']"
|
||||||
class="text-text-primary px-1"
|
class="text-text-primary px-1"
|
||||||
/>
|
/>
|
||||||
<p class="text-text-primary inline ml-1 md:flex">繼續</p>
|
<p class="text-text-primary text-xs">
|
||||||
|
{{ t('game.continue') }}
|
||||||
|
</p>
|
||||||
</button>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import { useSession } from '../../store/session'
|
import { useSession } from '../../store/session'
|
||||||
import MarkdownText from '../MarkdownText.vue'
|
import MarkdownText from '../MarkdownText.vue'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
const sessionStore = useSession()
|
const sessionStore = useSession()
|
||||||
const current = ref(-1)
|
const current = ref(-1)
|
||||||
const hintDetails = ref<HTMLDetailsElement | null>(null)
|
const hintDetails = ref<HTMLDetailsElement | null>(null)
|
||||||
@@ -52,7 +55,9 @@ watch(response, () => {
|
|||||||
:icon="['far', 'lightbulb']"
|
:icon="['far', 'lightbulb']"
|
||||||
class="text-yellow-200 p-2 content-center"
|
class="text-yellow-200 p-2 content-center"
|
||||||
/>
|
/>
|
||||||
<h1 class="inline text-text w-1/2 font-xl p-2 m-1">說明</h1>
|
<h1 class="inline text-text w-1/2 font-xl p-2 m-1">
|
||||||
|
{{ t('game.description') }}
|
||||||
|
</h1>
|
||||||
<div v-if="current !== -1" class="flex w-full justify-end items-end">
|
<div v-if="current !== -1" class="flex w-full justify-end items-end">
|
||||||
<button
|
<button
|
||||||
class="p-2 m-1 w-1/8"
|
class="p-2 m-1 w-1/8"
|
||||||
@@ -98,7 +103,7 @@ watch(response, () => {
|
|||||||
class="text-text-primary font-xl whitespace-pre-wrap"
|
class="text-text-primary font-xl whitespace-pre-wrap"
|
||||||
>
|
>
|
||||||
<details ref="hintDetails">
|
<details ref="hintDetails">
|
||||||
<summary class="cursor-pointer">提示</summary>
|
<summary class="cursor-pointer">{{ t('game.hint') }}</summary>
|
||||||
<MarkdownText :content="response.hint" />
|
<MarkdownText :content="response.hint" />
|
||||||
</details>
|
</details>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import MarkdownText from '../MarkdownText.vue'
|
import MarkdownText from '../MarkdownText.vue'
|
||||||
import { useSession } from '../../store/session'
|
import { useSession } from '../../store/session'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
const sessionStore = useSession()
|
const sessionStore = useSession()
|
||||||
const tasks = computed(() => {
|
const tasks = computed(() => {
|
||||||
console.log('session', sessionStore.session)
|
console.log('session', sessionStore.session)
|
||||||
@@ -31,14 +34,14 @@ const tasks = computed(() => {
|
|||||||
:content="sessionStore.quest.instruction"
|
:content="sessionStore.quest.instruction"
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<p id="tasks" class="text-text">任務:</p>
|
<p id="tasks" class="text-text font-bold">{{ t('game.task') }}</p>
|
||||||
<ul v-if="sessionStore.session">
|
<ul v-if="sessionStore.session">
|
||||||
<li v-for="task in tasks" :key="task.id">
|
<li v-for="task in tasks" :key="task.id">
|
||||||
<MarkdownText :class="task.class" :content="task.content" />
|
<MarkdownText :class="task.class" :content="task.content" />
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-text" v-else>Loading...</p>
|
<p class="text-text" v-else>{{ t('game.loading') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useToast } from 'vue-toastification'
|
import { useToast } from 'vue-toastification'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useSession } from '../../store/session'
|
import { useSession } from '../../store/session'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const sessionStore = useSession()
|
const sessionStore = useSession()
|
||||||
|
|
||||||
@@ -31,7 +34,7 @@ const startSession = async () => {
|
|||||||
:icon="['fas', 'play']"
|
:icon="['fas', 'play']"
|
||||||
class="text-text-primary h-full px-3"
|
class="text-text-primary h-full px-3"
|
||||||
/>
|
/>
|
||||||
<p class="text-text-primary inline ml-1 md:flex">開始</p>
|
<p class="text-text-primary inline ml-1 md:flex">{{ t('game.start') }}</p>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
import { onMounted, onUnmounted, ref } from 'vue'
|
import { onMounted, onUnmounted, ref } from 'vue'
|
||||||
import { useSession, useTerminal } from '../../store/session'
|
import { useSession, useTerminal } from '../../store/session'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
const sessionStore = useSession()
|
const sessionStore = useSession()
|
||||||
|
|
||||||
const terminal = ref<HTMLElement | null>(null)
|
const terminal = ref<HTMLElement | null>(null)
|
||||||
@@ -24,14 +27,14 @@ onMounted(() => {
|
|||||||
<div class="flex flex-col h-full">
|
<div class="flex flex-col h-full">
|
||||||
<div class="flex items-center gap-2 p-1">
|
<div class="flex items-center gap-2 p-1">
|
||||||
<font-awesome-icon :icon="['fas', 'terminal']" class="text-text" />
|
<font-awesome-icon :icon="['fas', 'terminal']" class="text-text" />
|
||||||
<button id="Terminal" class="text-text">終端機</button>
|
<p id="Terminal" class="text-text">{{ t('game.terminal') }}</p>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
class="text-sm font-normal text-gray-200 ml-10"
|
class="text-xs font-normal text-gray-200 ml-10 bg-bg-primary rounded-lg px-2 py-1 hover:bg-bg-primary-hover transition-colors duration-300"
|
||||||
:href="`/terminal/${sessionStore.session?.containerName}?token=${sessionStore.session?.token}`"
|
:href="`/terminal/${sessionStore.session?.containerName}?token=${sessionStore.session?.token}`"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
Open Terminal in New Tab
|
{{ t('game.open_terminal_in_new_tab') }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -77,7 +77,8 @@
|
|||||||
},
|
},
|
||||||
"quest_map": {
|
"quest_map": {
|
||||||
"title": "Quest Map",
|
"title": "Quest Map",
|
||||||
"intro": "Embark on your Linux adventure!"
|
"intro": "Embark on your Linux adventure!",
|
||||||
|
"load_quest_map_failed": "Failed to Load Quest Map"
|
||||||
},
|
},
|
||||||
"quest": {
|
"quest": {
|
||||||
"start": "Start Adventure",
|
"start": "Start Adventure",
|
||||||
@@ -89,5 +90,18 @@
|
|||||||
"score": "Score",
|
"score": "Score",
|
||||||
"completed_quests": "Completed Quests",
|
"completed_quests": "Completed Quests",
|
||||||
"load_leaderboard_failed": "Failed to Load Leaderboard"
|
"load_leaderboard_failed": "Failed to Load Leaderboard"
|
||||||
|
},
|
||||||
|
"game": {
|
||||||
|
"start": "Start",
|
||||||
|
"task": "Task:",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"description": "Description",
|
||||||
|
"hint": "Hint",
|
||||||
|
"terminal": "Terminal",
|
||||||
|
"open_terminal_in_new_tab": "Open Terminal in New Tab",
|
||||||
|
"reset": "Reset",
|
||||||
|
"continue": "Continue",
|
||||||
|
"command_book": "Command Book",
|
||||||
|
"cheatsheets": "Cheat Sheets"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,8 @@
|
|||||||
},
|
},
|
||||||
"quest_map": {
|
"quest_map": {
|
||||||
"title": "關卡地圖",
|
"title": "關卡地圖",
|
||||||
"intro": "踏上你的Linux冒險之旅吧!"
|
"intro": "踏上你的Linux冒險之旅吧!",
|
||||||
|
"load_quest_map_failed": "無法讀取關卡地圖"
|
||||||
},
|
},
|
||||||
"quest": {
|
"quest": {
|
||||||
"start": "開始冒險",
|
"start": "開始冒險",
|
||||||
@@ -89,5 +90,18 @@
|
|||||||
"score": "分數",
|
"score": "分數",
|
||||||
"completed_quests": "完成關卡",
|
"completed_quests": "完成關卡",
|
||||||
"load_leaderboard_failed": "無法讀取排行榜"
|
"load_leaderboard_failed": "無法讀取排行榜"
|
||||||
|
},
|
||||||
|
"game": {
|
||||||
|
"start": "開始",
|
||||||
|
"task": "任務:",
|
||||||
|
"loading": "載入中...",
|
||||||
|
"description": "說明",
|
||||||
|
"hint": "提示",
|
||||||
|
"terminal": "終端機",
|
||||||
|
"open_terminal_in_new_tab": "在新分頁開啟終端機",
|
||||||
|
"reset": "重置",
|
||||||
|
"continue": "繼續",
|
||||||
|
"command_book": "指令之書",
|
||||||
|
"cheatsheets": "指令列表"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user