diff --git a/app/src/components/QuestIntro.vue b/app/src/components/QuestIntro.vue
index 20dac85..c76923c 100644
--- a/app/src/components/QuestIntro.vue
+++ b/app/src/components/QuestIntro.vue
@@ -6,7 +6,7 @@ import { ref, defineProps, onMounted } from 'vue'
import MarkdownText from './MarkdownText.vue'
import { trpc } from '../utils/trpc'
-const { t } = useI18n()
+const { t, locale } = useI18n()
const props = defineProps({
questTitle: {
@@ -52,8 +52,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()
})
@@ -83,7 +90,7 @@ onMounted(async () => {
target="_blank"
@click="handleQuests(questId)"
:style="{ backgroundColor: questColor, color: questTextColor }"
- class="inline-flex justify-center rounded-lg text-base font-black py-3 mt-5 w-full text-lg"
+ class="inline-flex justify-center rounded-lg text-base font-black py-3 mt-5 w-full"
>
{{ t('quest.start') }}
diff --git a/app/src/components/QuestMap.vue b/app/src/components/QuestMap.vue
index 7237031..bf23cd0 100644
--- a/app/src/components/QuestMap.vue
+++ b/app/src/components/QuestMap.vue
@@ -7,7 +7,7 @@ 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 +66,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 +118,10 @@ onMounted(async () => {
})
watch(() => store.progress, computeGraphData, { deep: true })
+watch(locale, () => {
+ opened.value = null
+ computeGraphData()
+})
const curvedPath = computed(() => {
return (source: Node, target: Node) => {
diff --git a/app/src/components/game/ControlPalette.vue b/app/src/components/game/ControlPalette.vue
index dd80732..9bd2542 100644
--- a/app/src/components/game/ControlPalette.vue
+++ b/app/src/components/game/ControlPalette.vue
@@ -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()
+ } catch (error) {
+ console.error('Failed to reset quest:', error)
+ toast.error(t('game.reset_failed'))
+ }
}
+
const continuePlay = async () => {
try {
router.push({ name: 'map' })
diff --git a/app/src/components/header/LanguageSelector.vue b/app/src/components/header/LanguageSelector.vue
index 5b2ad59..81011ac 100644
--- a/app/src/components/header/LanguageSelector.vue
+++ b/app/src/components/header/LanguageSelector.vue
@@ -1,12 +1,17 @@
diff --git a/packages/constants/index.ts b/packages/constants/index.ts
index c555e8e..75eefca 100644
--- a/packages/constants/index.ts
+++ b/packages/constants/index.ts
@@ -16,3 +16,10 @@ export const getSessionsRequestSchema = z.object({
})
export type GetSessionsRequest = z.infer
+
+export const SUPPORTED_LOCALES = ['zh-TW', 'en'] as const
+export const LOCALE_NAME_MAP: Record = {
+ 'zh-TW': '繁體中文',
+ en: 'English',
+}
+export type QuestLocale = (typeof SUPPORTED_LOCALES)[number]
diff --git a/packages/models/session.ts b/packages/models/session.ts
index 30f7d4e..9872be8 100644
--- a/packages/models/session.ts
+++ b/packages/models/session.ts
@@ -1,4 +1,6 @@
import { model, Schema, Types } from 'mongoose'
+import type { QuestLocale } from '../constants'
+import { SUPPORTED_LOCALES } from '../constants'
export interface INode {
path: string
@@ -27,6 +29,7 @@ export interface ISession {
_id: Types.ObjectId
user: Types.ObjectId
quest: string
+ locale: QuestLocale
containerId: string | null
containerName: string | null
status: 'active' | 'finished' | 'inactive'
@@ -52,6 +55,11 @@ export const Session = model(
ref: 'Quest',
required: true,
},
+ locale: {
+ type: String,
+ enum: SUPPORTED_LOCALES,
+ required: true,
+ },
containerId: String,
containerName: String,
status: {
diff --git a/quests/checkpoint-01/answer.sh b/quests/checkpoint-01/en/answer.sh
similarity index 100%
rename from quests/checkpoint-01/answer.sh
rename to quests/checkpoint-01/en/answer.sh
diff --git a/quests/checkpoint-01/en/game.yml b/quests/checkpoint-01/en/game.yml
new file mode 100644
index 0000000..3715866
--- /dev/null
+++ b/quests/checkpoint-01/en/game.yml
@@ -0,0 +1,92 @@
+id: checkpoint-01
+title: Checkpoint 1
+image: base
+instruction: |
+ This quest tests your understanding of:
+ 1. `echo`: print text
+ 2. `ls`: list the contents of a folder
+ 3. `cd`: move between folders
+ 4. `pwd`: show your current path
+
+requirements:
+ - path
+
+stages:
+ - id: start
+ name: Click `Start` to begin
+ condition:
+ command: echo start
+ response:
+ content: |
+ This quest checks what you have learned in the previous lessons.
+ You will need to choose the commands yourself.
+ Think about which commands can help you complete each task.
+
+ To continue, make the terminal print `next`.
+
+ - id: next
+ name: Make the terminal print `next`
+ requirements:
+ - start
+ condition:
+ output: next
+ response:
+ content: |
+ Now list the contents of your current folder.
+
+ - id: ls-home
+ name: List the contents of the current folder
+ requirements:
+ - next
+ condition:
+ pwd: /home/commander
+ command: ls
+ response:
+ content: |
+ Enter the `folder03` folder.
+
+ - id: cd-folder03
+ name: Enter `folder03`
+ requirements:
+ - ls-home
+ condition:
+ pwd: /home/commander/folder03
+ response:
+ content: |
+ Show your current path in the terminal.
+
+ - id: pwd-folder03
+ name: Show your current path
+ requirements:
+ - cd-folder03
+ condition:
+ command: pwd
+ output: /home/commander/folder03
+ response:
+ content: |
+ Now enter the `folder02` folder
+ and list its contents.
+
+ - id: ls-folder02
+ name: Enter `folder02` and list its contents
+ requirements:
+ - pwd-folder03
+ condition:
+ pwd: /home/commander/folder02
+ command: ls
+ response:
+ content: |
+ Finally, stay in `folder02`
+ and list the contents of `folder01`.
+
+ - id: finish
+ name: Stay in `folder02` and list the contents of `folder01`
+ requirements:
+ - ls-folder02
+ condition:
+ pwd: /home/commander/folder02
+ output: file1.txt
+ response:
+ content: |
+ Congratulations! You passed this checkpoint!
+ You are ready to move on to the next lessons.
diff --git a/quests/checkpoint-01/home/folder01/file1.txt b/quests/checkpoint-01/en/home/folder01/file1.txt
similarity index 100%
rename from quests/checkpoint-01/home/folder01/file1.txt
rename to quests/checkpoint-01/en/home/folder01/file1.txt
diff --git a/quests/checkpoint-01/home/folder02/file2.txt b/quests/checkpoint-01/en/home/folder02/file2.txt
similarity index 100%
rename from quests/checkpoint-01/home/folder02/file2.txt
rename to quests/checkpoint-01/en/home/folder02/file2.txt
diff --git a/quests/checkpoint-01/home/folder03/file3.txt b/quests/checkpoint-01/en/home/folder03/file3.txt
similarity index 100%
rename from quests/checkpoint-01/home/folder03/file3.txt
rename to quests/checkpoint-01/en/home/folder03/file3.txt
diff --git a/quests/checkpoint-01/zh-TW/answer.sh b/quests/checkpoint-01/zh-TW/answer.sh
new file mode 100644
index 0000000..cee7368
--- /dev/null
+++ b/quests/checkpoint-01/zh-TW/answer.sh
@@ -0,0 +1,7 @@
+echo start
+echo next
+ls
+cd folder03
+pwd
+cd ../folder02
+ls ../folder01
\ No newline at end of file
diff --git a/quests/checkpoint-01/game.yml b/quests/checkpoint-01/zh-TW/game.yml
similarity index 100%
rename from quests/checkpoint-01/game.yml
rename to quests/checkpoint-01/zh-TW/game.yml
diff --git a/quests/explore/home/folder1/file1.txt b/quests/checkpoint-01/zh-TW/home/folder01/file1.txt
similarity index 100%
rename from quests/explore/home/folder1/file1.txt
rename to quests/checkpoint-01/zh-TW/home/folder01/file1.txt
diff --git a/quests/explore/home/folder1/file2.txt b/quests/checkpoint-01/zh-TW/home/folder02/file2.txt
similarity index 100%
rename from quests/explore/home/folder1/file2.txt
rename to quests/checkpoint-01/zh-TW/home/folder02/file2.txt
diff --git a/quests/explore/home/folder2/file3.txt b/quests/checkpoint-01/zh-TW/home/folder03/file3.txt
similarity index 100%
rename from quests/explore/home/folder2/file3.txt
rename to quests/checkpoint-01/zh-TW/home/folder03/file3.txt
diff --git a/quests/checkpoint-02/answer.sh b/quests/checkpoint-02/en/answer.sh
similarity index 100%
rename from quests/checkpoint-02/answer.sh
rename to quests/checkpoint-02/en/answer.sh
diff --git a/quests/checkpoint-02/en/game.yml b/quests/checkpoint-02/en/game.yml
new file mode 100644
index 0000000..4c3a733
--- /dev/null
+++ b/quests/checkpoint-02/en/game.yml
@@ -0,0 +1,97 @@
+id: checkpoint-02
+title: Checkpoint 2
+image: base
+instruction: |
+ This quest tests your understanding of:
+ 1. `cat`: display the contents of a file
+ 2. `touch`: create a file
+ 3. `mkdir`: create a folder
+ 4. Use the `Tab` key for autocompletion
+ 5. Topics covered in the previous checkpoint
+
+requirements:
+ - create
+
+stages:
+ - id: start
+ name: Click `Start` to begin
+ condition:
+ command: echo start
+ response:
+ content: |
+ This quest checks what you have learned in the previous lessons.
+ You will need to choose the commands yourself.
+ Think about which commands can help you complete each task.
+
+ To continue, make the terminal print `next`.
+
+ - id: next
+ name: Make the terminal print `next`
+ requirements:
+ - start
+ condition:
+ output: next
+ response:
+ content: |
+ Now list the contents of your current folder.
+
+ - id: ls-home
+ name: List the contents of the current folder
+ requirements:
+ - next
+ condition:
+ pwd: /home/commander
+ command: ls
+ response:
+ content: |
+ Display the contents of `hello.txt`.
+
+ - id: cat-hello
+ name: Display the contents of `hello.txt`
+ requirements:
+ - ls-home
+ condition:
+ command: cat hello.txt
+ output: This is `hello.txt`.
+ response:
+ content: |
+ Display the contents of the other file, which has a very long name.
+
+ - id: cat-long-filename
+ name: Display the contents of the other file
+ requirements:
+ - cat-hello
+ condition:
+ command: cat pneumonoultramicroscopicsilicovolcanoconiosis.txt
+ output: A name that has been invented for a lung disease caused by breathing in very small pieces of ash or dust.
+ response:
+ content: |
+ Now create a folder named `folder`.
+
+ - id: mkdir-folder
+ name: Create a folder named `folder`
+ requirements:
+ - cat-long-filename
+ condition:
+ files:
+ - path: /home/commander/folder
+ type: directory
+ exists: true
+ response:
+ content: |
+ Finally, inside the `folder` folder,
+ create a file named `file.txt`.
+
+ - id: finish
+ name: Create `file.txt` inside `folder`
+ requirements:
+ - mkdir-folder
+ condition:
+ files:
+ - path: /home/commander/folder/file.txt
+ type: file
+ exists: true
+ response:
+ content: |
+ Congratulations! You passed this checkpoint!
+ You are ready to move on to the next lessons.
diff --git a/quests/checkpoint-02/home/hello.txt b/quests/checkpoint-02/en/home/hello.txt
similarity index 100%
rename from quests/checkpoint-02/home/hello.txt
rename to quests/checkpoint-02/en/home/hello.txt
diff --git a/quests/checkpoint-02/home/pneumonoultramicroscopicsilicovolcanoconiosis.txt b/quests/checkpoint-02/en/home/pneumonoultramicroscopicsilicovolcanoconiosis.txt
similarity index 100%
rename from quests/checkpoint-02/home/pneumonoultramicroscopicsilicovolcanoconiosis.txt
rename to quests/checkpoint-02/en/home/pneumonoultramicroscopicsilicovolcanoconiosis.txt
diff --git a/quests/checkpoint-02/zh-TW/answer.sh b/quests/checkpoint-02/zh-TW/answer.sh
new file mode 100644
index 0000000..314821b
--- /dev/null
+++ b/quests/checkpoint-02/zh-TW/answer.sh
@@ -0,0 +1,7 @@
+echo start
+echo next
+ls
+cat hello.txt
+cat pneumonoultramicroscopicsilicovolcanoconiosis.txt
+mkdir folder
+touch folder/file.txt
\ No newline at end of file
diff --git a/quests/checkpoint-02/game.yml b/quests/checkpoint-02/zh-TW/game.yml
similarity index 100%
rename from quests/checkpoint-02/game.yml
rename to quests/checkpoint-02/zh-TW/game.yml
diff --git a/quests/checkpoint-03/home/folder/hello.txt b/quests/checkpoint-02/zh-TW/home/hello.txt
similarity index 100%
rename from quests/checkpoint-03/home/folder/hello.txt
rename to quests/checkpoint-02/zh-TW/home/hello.txt
diff --git a/quests/checkpoint-02/zh-TW/home/pneumonoultramicroscopicsilicovolcanoconiosis.txt b/quests/checkpoint-02/zh-TW/home/pneumonoultramicroscopicsilicovolcanoconiosis.txt
new file mode 100644
index 0000000..7689edb
--- /dev/null
+++ b/quests/checkpoint-02/zh-TW/home/pneumonoultramicroscopicsilicovolcanoconiosis.txt
@@ -0,0 +1 @@
+A name that has been invented for a lung disease caused by breathing in very small pieces of ash or dust.
diff --git a/quests/checkpoint-03/answer.sh b/quests/checkpoint-03/en/answer.sh
similarity index 100%
rename from quests/checkpoint-03/answer.sh
rename to quests/checkpoint-03/en/answer.sh
diff --git a/quests/checkpoint-03/en/game.yml b/quests/checkpoint-03/en/game.yml
new file mode 100644
index 0000000..32ca74a
--- /dev/null
+++ b/quests/checkpoint-03/en/game.yml
@@ -0,0 +1,140 @@
+id: checkpoint-03
+title: Checkpoint 3
+image: base
+instruction: |
+ This quest tests your understanding of:
+ 1. `cp`: copy files and folders
+ 2. `mv`: move or rename files and folders
+ 3. `rm`: delete files and folders
+ 4. `rmdir`: delete empty folders
+ 5. Use options to change a command's behavior, such as `rm -r`
+ 6. Topics covered in the previous checkpoints
+
+requirements:
+ - remove
+
+stages:
+ - id: start
+ name: Click `Start` to begin
+ condition:
+ command: echo start
+ response:
+ content: |
+ This quest checks what you have learned in the previous lessons.
+ You will need to choose the commands yourself.
+ Think about which commands can help you complete each task.
+
+ To continue, make the terminal print `next`.
+
+ - id: next
+ name: Make the terminal print `next`
+ requirements:
+ - start
+ condition:
+ output: next
+ response:
+ content: |
+ Now list the contents of your current folder.
+
+ - id: ls-home
+ name: List the contents of the current folder
+ requirements:
+ - next
+ condition:
+ pwd: /home/commander
+ command: ls
+ response:
+ content: |
+ Copy the file inside `folder` to a new file named `world.txt` in the same folder.
+
+ - id: cp-hello-world
+ name: Copy the file inside `folder` as `world.txt`
+ requirements:
+ - ls-home
+ condition:
+ files:
+ - path: /home/commander/folder/hello.txt
+ type: file
+ exists: true
+ - path: /home/commander/folder/world.txt
+ type: file
+ exists: true
+ response:
+ content: |
+ Delete the `hello.txt` file inside `folder`.
+
+ - id: rm-hello
+ name: Delete `hello.txt` inside `folder`
+ requirements:
+ - cp-hello-world
+ condition:
+ files:
+ - path: /home/commander/folder/hello.txt
+ type: file
+ exists: false
+ response:
+ content: |
+ Now move `world.txt` out of `folder`.
+ (Move it into your home folder, `/home/commander`.)
+
+ - id: mv-world-out
+ name: Move `world.txt` out of `folder`
+ requirements:
+ - rm-hello
+ condition:
+ files:
+ - path: /home/commander/folder/world.txt
+ type: file
+ exists: false
+ - path: /home/commander/world.txt
+ type: file
+ exists: true
+ response:
+ content: |
+ Rename `world.txt` back to `hello.txt`
+ and display its contents.
+
+ - id: cat-hello
+ name: Rename `world.txt` to `hello.txt` and display its contents
+ requirements:
+ - mv-world-out
+ condition:
+ command: cat hello.txt
+ files:
+ - path: /home/commander/world.txt
+ type: file
+ exists: false
+ - path: /home/commander/hello.txt
+ type: file
+ exists: true
+ output: This is `hello.txt`.
+ response:
+ content: |
+ Delete the `folder` folder.
+
+ - id: rmdir-folder
+ name: Delete `folder`
+ requirements:
+ - cat-hello
+ condition:
+ files:
+ - path: /home/commander/folder
+ type: directory
+ exists: false
+ response:
+ content: |
+ Delete the `trash_can` folder.
+
+ - id: finish
+ name: Delete `trash_can`
+ requirements:
+ - rmdir-folder
+ condition:
+ files:
+ - path: /home/commander/trash_can
+ type: directory
+ exists: false
+ response:
+ content: |
+ Congratulations! You passed this checkpoint!
+ You are ready to move on to the next lessons.
diff --git a/quests/checkpoint-03/en/home/folder/hello.txt b/quests/checkpoint-03/en/home/folder/hello.txt
new file mode 100644
index 0000000..3bd5213
--- /dev/null
+++ b/quests/checkpoint-03/en/home/folder/hello.txt
@@ -0,0 +1 @@
+This is `hello.txt`.
diff --git a/quests/checkpoint-03/home/trash_can/garbage.txt b/quests/checkpoint-03/en/home/trash_can/garbage.txt
similarity index 100%
rename from quests/checkpoint-03/home/trash_can/garbage.txt
rename to quests/checkpoint-03/en/home/trash_can/garbage.txt
diff --git a/quests/checkpoint-03/home/trash_can/rubbish.txt b/quests/checkpoint-03/en/home/trash_can/rubbish.txt
similarity index 100%
rename from quests/checkpoint-03/home/trash_can/rubbish.txt
rename to quests/checkpoint-03/en/home/trash_can/rubbish.txt
diff --git a/quests/checkpoint-03/home/trash_can/secret/letter.txt b/quests/checkpoint-03/en/home/trash_can/secret/letter.txt
similarity index 100%
rename from quests/checkpoint-03/home/trash_can/secret/letter.txt
rename to quests/checkpoint-03/en/home/trash_can/secret/letter.txt
diff --git a/quests/checkpoint-03/home/trash_can/trash.txt b/quests/checkpoint-03/en/home/trash_can/trash.txt
similarity index 100%
rename from quests/checkpoint-03/home/trash_can/trash.txt
rename to quests/checkpoint-03/en/home/trash_can/trash.txt
diff --git a/quests/checkpoint-03/zh-TW/answer.sh b/quests/checkpoint-03/zh-TW/answer.sh
new file mode 100644
index 0000000..3b39b6d
--- /dev/null
+++ b/quests/checkpoint-03/zh-TW/answer.sh
@@ -0,0 +1,12 @@
+echo start
+echo next
+ls
+cd folder
+cp hello.txt world.txt
+rm hello.txt
+mv world.txt ..
+cd ..
+mv world.txt hello.txt
+cat hello.txt
+rmdir folder
+rm -r trash_can
\ No newline at end of file
diff --git a/quests/checkpoint-03/game.yml b/quests/checkpoint-03/zh-TW/game.yml
similarity index 100%
rename from quests/checkpoint-03/game.yml
rename to quests/checkpoint-03/zh-TW/game.yml
diff --git a/quests/checkpoint-03/zh-TW/home/folder/hello.txt b/quests/checkpoint-03/zh-TW/home/folder/hello.txt
new file mode 100644
index 0000000..3bd5213
--- /dev/null
+++ b/quests/checkpoint-03/zh-TW/home/folder/hello.txt
@@ -0,0 +1 @@
+This is `hello.txt`.
diff --git a/quests/remove/home/trash/garbage.txt b/quests/checkpoint-03/zh-TW/home/trash_can/garbage.txt
similarity index 100%
rename from quests/remove/home/trash/garbage.txt
rename to quests/checkpoint-03/zh-TW/home/trash_can/garbage.txt
diff --git a/quests/remove/home/trash/rubbish.txt b/quests/checkpoint-03/zh-TW/home/trash_can/rubbish.txt
similarity index 100%
rename from quests/remove/home/trash/rubbish.txt
rename to quests/checkpoint-03/zh-TW/home/trash_can/rubbish.txt
diff --git a/quests/checkpoint-03/zh-TW/home/trash_can/secret/letter.txt b/quests/checkpoint-03/zh-TW/home/trash_can/secret/letter.txt
new file mode 100644
index 0000000..b95980b
--- /dev/null
+++ b/quests/checkpoint-03/zh-TW/home/trash_can/secret/letter.txt
@@ -0,0 +1 @@
+aHR0cHM6Ly9yZXVybC5jYy9YUjZZMDA=
diff --git a/quests/explore/home/folder2/file4.txt b/quests/checkpoint-03/zh-TW/home/trash_can/trash.txt
similarity index 100%
rename from quests/explore/home/folder2/file4.txt
rename to quests/checkpoint-03/zh-TW/home/trash_can/trash.txt
diff --git a/quests/copy/answer.sh b/quests/copy/en/answer.sh
similarity index 100%
rename from quests/copy/answer.sh
rename to quests/copy/en/answer.sh
diff --git a/quests/copy/en/game.yml b/quests/copy/en/game.yml
new file mode 100644
index 0000000..e8ecf01
--- /dev/null
+++ b/quests/copy/en/game.yml
@@ -0,0 +1,348 @@
+id: copy
+title: Copying Files
+image: base
+instruction: |
+ In this quest, you will learn:
+ 1. How to copy files with `cp`
+
+requirements:
+ - create
+
+stages:
+ - id: start
+ name: Click `Start` to begin
+ condition:
+ command: echo start
+ response:
+ content: |
+ Today we will learn how to copy files.
+ First, use `ls` to see which files are in your current folder.
+ hint: Use `ls` to list the files in your current folder
+
+ - id: ls
+ name: List the files in the current folder
+ requirements:
+ - start
+ condition:
+ pwd: /home/commander
+ command: ls
+ exceptions:
+ - id: wrong_directory
+ condition:
+ not:
+ pwd: /home/commander
+ response:
+ content: |
+ You are outside the `/home/commander` folder.
+ Use `cd /home/commander` to return to `/home/commander`.
+ response:
+ content: |
+ You can see `hello.txt` and `world.txt` in this folder.
+ Use `cat` to display their contents.
+ When you have finished, enter `echo next` to continue!
+ hint: |
+ Use `cat hello.txt` and `cat world.txt` to display the file contents
+ If a command is keeping the terminal busy, press `Ctrl + C` to cancel it
+
+ - id: cat-hello
+ name: Display the contents of `hello.txt`
+ requirements:
+ - ls
+ condition:
+ pwd: /home/commander
+ command: cat hello.txt
+ output: Hello
+ exceptions:
+ - id: wrong_directory
+ condition:
+ not:
+ pwd: /home/commander
+ response:
+ content: |
+ You are outside the `/home/commander` folder.
+ Use `cd /home/commander` to return to `/home/commander`
+ - id: missing_command
+ condition:
+ command: cat
+ response:
+ content: |
+ To display this file, add its filename after `cat`.
+ If `cat` is waiting for input, press `Ctrl + C` to cancel it.
+ Enter `cat hello.txt` to display the contents of `hello.txt`
+ - id: missing_extension
+ condition:
+ command: cat hello
+ response:
+ content: |
+ Did you forget the `.txt` extension?
+ Enter `cat hello.txt` to display the contents of `hello.txt`
+ response:
+ content: |
+ You have displayed the contents of `hello.txt`!
+ Here is what it contains:
+ ```
+ Hello
+ ```
+
+ - id: cat-world
+ name: Display the contents of `world.txt`
+ requirements:
+ - ls
+ condition:
+ pwd: /home/commander
+ command: cat world.txt
+ output: World
+ exceptions:
+ - id: wrong_directory
+ condition:
+ not:
+ pwd: /home/commander
+ response:
+ content: |
+ You are outside the `/home/commander` folder.
+ Use `cd /home/commander` to return to `/home/commander`
+ - id: missing_command
+ condition:
+ command: cat
+ response:
+ content: |
+ To display this file, add its filename after `cat`.
+ If `cat` is waiting for input, press `Ctrl + C` to cancel it.
+ Enter `cat world.txt` to display the contents of `world.txt`
+ - id: missing_extension
+ condition:
+ command: cat world
+ response:
+ content: |
+ Did you forget the `.txt` extension?
+ Enter `cat world.txt` to display the contents of `world.txt`
+ response:
+ content: |
+ You have displayed the contents of `world.txt`!
+ Here is what it contains:
+ ```
+ World
+ ```
+
+ - id: cat-next
+ name: Enter `echo next` to continue
+ requirements:
+ - cat-hello
+ - cat-world
+ condition:
+ command: echo next
+ response:
+ content: |
+ Next, let's learn how to copy files.
+ `cp` is short for copy.
+ To copy a file, use `cp [source file] [destination file]`.
+ Use `cp` to copy `hello.txt` to `hello2.txt`.
+ hint: Use `cp hello.txt hello2.txt` to copy `hello.txt` to `hello2.txt`
+
+ - id: cp-hello
+ name: Copy `hello.txt` to `hello2.txt`
+ requirements:
+ - cat-next
+ condition:
+ pwd: /home/commander
+ command: cp hello.txt hello2.txt
+ files:
+ - path: /home/commander/hello2.txt
+ type: file
+ exists: true
+ exceptions:
+ - id: wrong_directory
+ condition:
+ not:
+ pwd: /home/commander
+ response:
+ content: |
+ You are outside the `/home/commander` folder.
+ Use `cd /home/commander` to return to `/home/commander`.
+ response:
+ content: |
+ You have copied `hello.txt` to `hello2.txt`!
+ Let's check the contents of `hello2.txt`.
+ hint: Use `cat hello2.txt` to display the contents of `hello2.txt`
+
+ - id: cat-hello2
+ name: Display the contents of `hello2.txt`
+ requirements:
+ - cp-hello
+ condition:
+ pwd: /home/commander
+ command: cat hello2.txt
+ output: Hello
+ exceptions:
+ - id: wrong_directory
+ condition:
+ not:
+ pwd: /home/commander
+ response:
+ content: |
+ You are outside the `/home/commander` folder.
+ Use `cd /home/commander` to return to `/home/commander`.
+ response:
+ content: |
+ You have displayed the contents of `hello2.txt`!
+ As you can see, `hello2.txt` contains exactly the same text as `hello.txt`.
+ Now try copying `world.txt` to `world2.txt`!
+ hint: Use `cp world.txt world2.txt` to copy `world.txt` to `world2.txt`
+
+ - id: cp-world
+ name: Copy `world.txt` to `world2.txt`
+ requirements:
+ - cat-hello2
+ condition:
+ pwd: /home/commander
+ command: cp world.txt world2.txt
+ files:
+ - path: /home/commander/world2.txt
+ type: file
+ exists: true
+ exceptions:
+ - id: wrong_directory
+ condition:
+ not:
+ pwd: /home/commander
+ response:
+ content: |
+ You are outside the `/home/commander` folder.
+ Use `cd /home/commander` to return to `/home/commander`.
+ response:
+ content: |
+ You have copied `world.txt` to `world2.txt`!
+ Let's check the contents of `world2.txt`.
+ hint: Use `cat world2.txt` to display the contents of `world2.txt`
+
+ - id: cat-world2
+ name: Display the contents of `world2.txt`
+ requirements:
+ - cp-world
+ condition:
+ pwd: /home/commander
+ command: cat world2.txt
+ output: World
+ exceptions:
+ - id: wrong_directory
+ condition:
+ not:
+ pwd: /home/commander
+ response:
+ content: |
+ You are outside the `/home/commander` folder.
+ Use `cd /home/commander` to return to `/home/commander`.
+ response:
+ content: |
+ You have displayed the contents of `world2.txt`!
+ `world2.txt` contains exactly the same text as `world.txt`.
+ Next, let's learn how to copy folders.
+ Your current folder contains a `notes` folder with a file named `note.txt` inside.
+ We want to copy the entire folder, including its contents, to `notes2`.
+ To copy a folder, add the `-r` option (recursive). This copies the folder and everything inside it.
+ Options let you change how a command behaves.
+ An option often starts with `-` followed by a letter, such as `-r`.
+ Options usually go after the command name and before the filenames.
+ Use `cp -r notes notes2` to copy `notes` to `notes2`.
+ hint: |
+ Use `cp -r notes notes2` to copy `notes` to `notes2`
+ Remember to include `-r` to copy all the contents
+
+ - id: cp-notes
+ name: Copy the `notes` folder to `notes2`
+ requirements:
+ - cat-world2
+ condition:
+ pwd: /home/commander
+ command: cp -r notes notes2
+ files:
+ - path: /home/commander/notes2
+ type: directory
+ exists: true
+ - path: /home/commander/notes2/note.txt
+ type: file
+ exists: true
+ exceptions:
+ - id: wrong_directory
+ condition:
+ not:
+ pwd: /home/commander
+ response:
+ content: |
+ You are outside the `/home/commander` folder.
+ Use `cd /home/commander` to return to `/home/commander`.
+ - id: missing_recursive
+ condition:
+ command: cp notes notes2
+ response:
+ content: |
+ Did you forget the `-r` option?
+ Enter `cp -r notes notes2` to copy `notes` to `notes2`
+ response:
+ content: |
+ You have copied the `notes` folder to `notes2`!
+ The entire `notes` folder, including its files, has been copied to `notes2`.
+ `cp -r` copies everything in a folder, including files inside any nested folders.
+
+ Finally, let's see what happens when you copy a file to a destination that already exists.
+ Copy `hello.txt` to `world.txt` and see what happens.
+ hint: Use `cp hello.txt world.txt` to copy `hello.txt` to `world.txt`
+
+ - id: cp-overwrite
+ name: Copy `hello.txt` to `world.txt`
+ requirements:
+ - cp-notes
+ condition:
+ pwd: /home/commander
+ command: cp hello.txt world.txt
+ exceptions:
+ - id: wrong_directory
+ condition:
+ not:
+ pwd: /home/commander
+ response:
+ content: |
+ You are outside the `/home/commander` folder.
+ Use `cd /home/commander` to return to `/home/commander`.
+ response:
+ content: |
+ You have copied `hello.txt` to `world.txt`!
+ Let's check the contents of `world.txt`.
+ hint: Use `cat world.txt`
+
+ - id: check-world
+ name: Check the contents of `world.txt`
+ requirements:
+ - cp-overwrite
+ condition:
+ pwd: /home/commander
+ command: cat world.txt
+ output: Hello
+ exceptions:
+ - id: wrong_directory
+ condition:
+ not:
+ pwd: /home/commander
+ response:
+ content: |
+ You are outside the `/home/commander` folder.
+ Use `cd /home/commander` to return to `/home/commander`.
+ response:
+ content: |
+ `world.txt` now contains `Hello`!
+ When the destination file already exists, copying another file to it overwrites its contents.
+ Check whether the destination already exists before copying, so you do not accidentally lose data.
+ That covers the basics of copying files!
+ Enter `echo finish` to complete this quest.
+
+ - id: finish
+ name: Congratulations! All tasks completed!
+ requirements:
+ - check-world
+ condition:
+ command: echo finish
+ response:
+ content: |
+ Congratulations! You have completed all the tasks!
+ You now know how to copy files and folders.
+ In the next quest, you will learn how to move files and folders.
diff --git a/quests/copy/home/hello.txt b/quests/copy/en/home/hello.txt
similarity index 100%
rename from quests/copy/home/hello.txt
rename to quests/copy/en/home/hello.txt
diff --git a/quests/copy/home/notes/note.txt b/quests/copy/en/home/notes/note.txt
similarity index 100%
rename from quests/copy/home/notes/note.txt
rename to quests/copy/en/home/notes/note.txt
diff --git a/quests/copy/home/world.txt b/quests/copy/en/home/world.txt
similarity index 100%
rename from quests/copy/home/world.txt
rename to quests/copy/en/home/world.txt
diff --git a/quests/copy/zh-TW/answer.sh b/quests/copy/zh-TW/answer.sh
new file mode 100644
index 0000000..bef48aa
--- /dev/null
+++ b/quests/copy/zh-TW/answer.sh
@@ -0,0 +1,13 @@
+echo start
+ls
+cat hello.txt
+cat world.txt
+echo next
+cp hello.txt hello2.txt
+cat hello2.txt
+cp world.txt world2.txt
+cat world2.txt
+cp -r notes notes2
+ls notes2
+cp hello.txt world.txt
+cat world.txt
\ No newline at end of file
diff --git a/quests/copy/game.yml b/quests/copy/zh-TW/game.yml
similarity index 100%
rename from quests/copy/game.yml
rename to quests/copy/zh-TW/game.yml
diff --git a/quests/move/home/hello.txt b/quests/copy/zh-TW/home/hello.txt
similarity index 100%
rename from quests/move/home/hello.txt
rename to quests/copy/zh-TW/home/hello.txt
diff --git a/quests/move/home/notes/note.txt b/quests/copy/zh-TW/home/notes/note.txt
similarity index 100%
rename from quests/move/home/notes/note.txt
rename to quests/copy/zh-TW/home/notes/note.txt
diff --git a/quests/move/home/world.txt b/quests/copy/zh-TW/home/world.txt
similarity index 100%
rename from quests/move/home/world.txt
rename to quests/copy/zh-TW/home/world.txt
diff --git a/quests/create/answer.sh b/quests/create/en/answer.sh
similarity index 100%
rename from quests/create/answer.sh
rename to quests/create/en/answer.sh
diff --git a/quests/create/en/game.yml b/quests/create/en/game.yml
new file mode 100644
index 0000000..921ce01
--- /dev/null
+++ b/quests/create/en/game.yml
@@ -0,0 +1,165 @@
+id: create
+title: Creating Files and Folders
+image: base
+instruction: |
+ In this quest, you will learn:
+ 1. How to create files with `touch`
+ 2. How to create folders with `mkdir`
+
+requirements:
+ - tab
+
+stages:
+ - id: start
+ name: Click `Start` to begin
+ condition:
+ command: echo start
+ response:
+ content: |
+ This time, we will learn about the `touch` command.
+ First, use `ls` to list the files in your current folder.
+ You should find that the folder has no files.
+ hint: Enter `ls` to list the files in your current folder
+
+ - id: ls
+ name: List the files in the folder
+ requirements:
+ - start
+ condition:
+ command: ls
+ response:
+ content: |
+ The terminal shows no output, which means there are no visible files in this folder.
+ You can create a file with `touch [filename]`.
+ Try creating a file named `hello.txt` in the terminal.
+ hint: Enter `touch hello.txt` to create a file
+
+ - id: touch
+ name: Create a file with `touch`
+ requirements:
+ - ls
+ condition:
+ files:
+ - path: /home/commander/hello.txt
+ type: file
+ exists: true
+ exceptions:
+ - id: missing_extension
+ condition:
+ files:
+ - path: /home/commander/hello
+ type: file
+ exists: true
+ response:
+ content: |
+ It looks like you forgot the `.txt` extension.
+ Try again and make sure the filename is `hello.txt`.
+ response:
+ content: |
+ The terminal should show no output.
+ The file tree on the right now shows the new `hello.txt` file.
+ Let's look inside `hello.txt`!
+ Use the `cat` command you learned earlier to display its contents.
+ hint: Enter `cat hello.txt` to display the contents of `hello.txt`
+
+ - id: cat
+ name: Display the contents of `hello.txt` with `cat`
+ requirements:
+ - touch
+ condition:
+ command: cat hello.txt
+ response:
+ content: |
+ Huh? There is nothing there!
+ That's right. A new file created with `touch` is empty.
+ We will learn how to edit file contents in a future lesson.
+ Let's create another file!
+ Try creating another file named `world.txt` in the terminal.
+ hint: Enter `touch world.txt` to create a file
+
+ - id: touch-2
+ name: Create another file named `world.txt` with `touch`
+ requirements:
+ - cat
+ condition:
+ files:
+ - path: /home/commander/world.txt
+ type: file
+ exists: true
+ exceptions:
+ - id: missing_extension
+ condition:
+ files:
+ - path: /home/commander/world
+ type: file
+ exists: true
+ response:
+ content: |
+ It looks like you forgot the `.txt` extension.
+ Try again and make sure the filename is `world.txt`.
+ response:
+ content: |
+ Great! You have created your second file.
+ The file tree now shows both `hello.txt` and `world.txt`.
+ Next, let's create a folder!
+ The `mkdir` command creates an empty folder.
+ Use `mkdir [folder name]`.
+ Try creating a folder named `notes` in the terminal.
+ hint: Enter `mkdir notes` to create a folder
+
+ - id: mkdir
+ name: Create the `notes` folder with `mkdir`
+ requirements:
+ - touch-2
+ condition:
+ files:
+ - path: /home/commander/notes
+ type: directory
+ exists: true
+ response:
+ content: |
+ Great! You have created the `notes` folder.
+ Next, we will create a file named `note.txt` inside `notes`.
+ First, enter the `notes` folder.
+ Remember the `cd` command? Try using it to enter `notes`.
+ hint: |
+ Enter `cd notes` to enter the `notes` folder
+ After typing `cd n`, press `Tab` to complete the folder name
+
+ - id: cd-notes
+ name: Enter the `notes` folder
+ requirements:
+ - mkdir
+ condition:
+ pwd: /home/commander/notes
+ response:
+ content: |
+ You have entered the `notes` folder.
+ Now try creating a file named `note.txt` inside `notes`.
+ hint: Enter `touch note.txt` to create a file
+
+ - id: touch-note
+ name: Create `note.txt` inside `notes`
+ requirements:
+ - cd-notes
+ condition:
+ files:
+ - path: /home/commander/notes/note.txt
+ type: file
+ exists: true
+ response:
+ content: |
+ Great! You have created `note.txt`.
+ Congratulations! You have completed this quest!
+ In the next quest, you will learn how to copy files and folders.
+ Enter `echo finish` to complete this quest.
+
+ - id: END
+ name: Complete the quest
+ requirements:
+ - touch-note
+ condition:
+ command: echo finish
+ response:
+ content: |
+ Congratulations! You have completed this quest!
diff --git a/quests/create/zh-TW/answer.sh b/quests/create/zh-TW/answer.sh
new file mode 100644
index 0000000..b0367e1
--- /dev/null
+++ b/quests/create/zh-TW/answer.sh
@@ -0,0 +1,12 @@
+echo start
+ls
+touch hello.txt
+ls
+cat hello.txt
+touch world.txt
+ls
+mkdir notes
+ls
+cd notes
+touch note.txt
+ls
\ No newline at end of file
diff --git a/quests/create/game.yml b/quests/create/zh-TW/game.yml
similarity index 100%
rename from quests/create/game.yml
rename to quests/create/zh-TW/game.yml
diff --git a/quests/execute/en/execute.md b/quests/execute/en/execute.md
new file mode 100644
index 0000000..2b7877e
--- /dev/null
+++ b/quests/execute/en/execute.md
@@ -0,0 +1,79 @@
+# Running Executable Files
+
+## Outline
+
+1. What an executable file is
+2. How to find an executable with `which`
+3. How to run an executable file
+
+---
+
+In earlier lessons, you used commands such as `ls`, `cat`, `cp`, `mv`, and `rm`.
+These commands are **executable files** that the operating system runs when you enter them.
+For most commands, you can find the corresponding file in the file system.
+Use `which` to find the file associated with a command.
+Enter `which [command name]` to display its path.
+
+> Use `which ls` to find the path to `ls`
+
+---
+
+The terminal displays `/usr/bin/ls`, the location of the `ls` executable.
+`/usr/bin` is a system folder containing many standard programs, including commands such as `cat` and `cp`.
+
+> Use `which cat` to find the path to `cat`
+
+---
+
+To run an executable file, enter its path directly.
+Let's try running a command using the path to its executable.
+
+> Enter `/usr/bin/ls` to list the contents of your current folder
+
+---
+
+It lists the files in your folder, just as entering `ls` does. You can compare the results yourself.
+You can still pass arguments. Let's try this with `cat`.
+
+> Enter `/usr/bin/cat hello.txt` to display the contents of `hello.txt`
+
+---
+
+You can see the contents of `hello.txt`, just as you would with `cat`.
+This makes sense: `cat` is located at `/usr/bin/cat`, so both commands run the same program.
+A command like this consists of **an executable** plus **arguments**.
+You can enter `ls` instead of `/usr/bin/ls` because the `PATH` environment variable tells the terminal which *folders* to search for programs.
+`PATH` and environment variables are beyond this lesson's scope. We will cover them in a future lesson.
+
+You can also run custom programs, such as the `dog` file in your current folder.
+
+> Use `cat dog` to display the contents of `dog`
+
+---
+
+This is a simple program built from `echo` and `sleep` commands.
+You do not need to understand every detail yet. It prints a few lines of text, with short pauses between them.
+
+To run a file, specify its path.
+Since `dog` is in your home folder, its absolute path is `/home/commander/dog`. You can also use the shorter form `~/dog`.
+
+> Enter `~/dog` to run the `dog` file
+
+---
+
+The terminal displays a few words from this dog with an attitude.
+When you are in the same folder as a program, a relative path is often more convenient than an absolute path.
+To run a program in your current folder, put `./` before its filename instead of entering just its name.
+In a path, `.` means the current folder.
+Now try running `dog` using a relative path.
+
+> Enter `./dog` to run the `dog` file
+
+---
+
+The dog produces the same output as before.
+This is an example of a simple custom command.
+You can put a sequence of commands in a file and run them together to handle complex or repetitive tasks.
+This technique is also useful when running programs written by other people.
+
+Here is a final challenge: enter the `commands` folder, then run the `lucky` file inside it using a relative path.
diff --git a/quests/execute/en/game.yml b/quests/execute/en/game.yml
new file mode 100644
index 0000000..7670a8c
--- /dev/null
+++ b/quests/execute/en/game.yml
@@ -0,0 +1,236 @@
+id: execute
+title: Running Executable Files
+image: base
+instruction: |
+ In this quest, you will learn:
+ 1. What an executable file is
+ 2. How to find an executable with `which`
+ 3. How to run an executable file
+
+requirements:
+ - remove
+
+stages:
+ - id: start
+ name: Click Start
+ condition:
+ command: echo start
+ response:
+ content: |
+ In earlier lessons, you used commands such as `ls`, `cat`, `cp`, `mv`, and `rm`.
+ These commands are **executable files** that the operating system runs when you enter them.
+ For most commands, you can find the corresponding file in the file system.
+ Use `which` to find the file associated with a command.
+ Enter `which [command name]` to display its path.
+ hint: Use `which ls` to find the path to `ls`
+
+ - id: which-ls
+ name: Find the path to `ls` with `which`
+ requirements:
+ - start
+ condition:
+ command: which ls
+ response:
+ content: |
+ The terminal displays `/usr/bin/ls`, the location of the `ls` executable.
+ `/usr/bin` is a system folder containing many standard programs.
+
+ Next, let's find the path to `cat`.
+ hint: Use `which cat` to find the path to `cat`
+
+ - id: which-cat
+ name: Find the path to `cat` with `which`
+ requirements:
+ - which-ls
+ condition:
+ command: which cat
+ response:
+ content: |
+ You can see `/usr/bin/cat`, which is also in the `/usr/bin` folder.
+ To run an executable file, **enter its path directly**.
+ Let's try running a command using the path to its executable.
+
+ First, let's see what happens when you run `ls` normally.
+ hint: Enter `ls` to list the contents of your current folder
+
+ - id: ls
+ name: List the current folder with `ls`
+ requirements:
+ - which-cat
+ condition:
+ command: ls
+ response:
+ content: |
+ Your current folder contains the `commands` folder and two files, `dog` and `hello.txt`.
+ Now try running `ls` using its absolute path, `/usr/bin/ls`.
+ hint: Enter `/usr/bin/ls` to list the contents of your current folder
+
+ - id: bin-ls
+ name: Run `ls` using its absolute path
+ requirements:
+ - ls
+ condition:
+ command: /usr/bin/ls
+ exceptions:
+ - id: exception-ls
+ condition:
+ command: ls
+ response:
+ content: |
+ Here, we are practicing running `ls` using its absolute path.
+ Enter `/usr/bin/ls` to list the contents of your current folder.
+ response:
+ content: |
+ It lists the same files as `ls`. Both commands run the program at `/usr/bin/ls`, so the results are the same.
+
+ You can still pass arguments. Try using `/usr/bin/cat` to display the contents of `hello.txt`.
+ hint: Enter `/usr/bin/cat hello.txt`
+
+ - id: bin-cat
+ name: Run `cat` using its absolute path
+ requirements:
+ - bin-ls
+ condition:
+ command: /usr/bin/cat hello.txt
+ response:
+ content: |
+ You can see the contents of `hello.txt`, just as you would with `cat`.
+ Both commands run the program at `/usr/bin/cat`, so the results are the same.
+
+ A command usually has the form `command argument1 argument2 ...`.
+ The `command` is the name or path of the program to run, such as `ls`, `cat`, or `which`.
+ The `arguments` provide information to the program, such as `hello.txt` after `cat`.
+
+ You can enter `ls` instead of `/usr/bin/ls` because an environment variable called `PATH` tells the shell which folders to search for programs.
+ We will learn more about `PATH` in a future lesson.
+
+ Now let's try running a custom command.
+ The file named `dog` in this folder is a custom command provided for this lesson.
+ First, let's see what is written inside it.
+ hint: Use `cat dog` to display the contents of `dog`
+
+ - id: cat-dog
+ name: Display the contents of `dog` with `cat`
+ requirements:
+ - bin-cat
+ condition:
+ command: cat dog
+ response:
+ content: |
+ This is a simple program built from `echo` and `sleep` commands.
+ You do not need to understand every detail yet. It just prints a few lines of text.
+
+ To run a file, specify its path.
+ Here, the first part of the command is the path to `dog`. This example does not use any arguments.
+ Since `dog` is in your home folder, its absolute path is `/home/commander/dog`.
+ Using the format `executable arguments`, the command is:
+ ```
+ /home/commander/dog
+ ```
+ hint: Enter `/home/commander/dog` to run the `dog` file
+
+ - id: dog-abs
+ name: Run `dog` using an absolute path
+ requirements:
+ - cat-dog
+ condition:
+ or:
+ - command: /home/commander/dog
+ - command: ~/dog
+ exceptions:
+ - id: exception-dog
+ condition:
+ command: dog
+ error: 'command not found: dog'
+ response:
+ content: |
+ `dog` cannot be run by name here because it is not in the command search path. Specify the path to its executable file.
+ Here, we are practicing running `dog` using an absolute path.
+ Enter `/home/commander/dog` to run the `dog` file.
+ response:
+ content: |
+ The terminal displays a few words from this dog with an attitude.
+ When you are in the same folder as a program, a relative path is often more convenient than an absolute path.
+ To run a program in your current folder, put `./` before its filename instead of entering just its name.
+ In a path, `.` means the current folder.
+ Now try running `dog` using a relative path.
+ hint: Enter `./dog` to run the `dog` file
+
+ - id: dog-rel
+ name: Run `dog` using a relative path
+ requirements:
+ - dog-abs
+ condition:
+ pwd: /home/commander
+ command: ./dog
+ exceptions:
+ - id: exception-dog-2
+ condition:
+ command: dog
+ error: 'command not found: dog'
+ response:
+ content: |
+ `dog` cannot be run by name here because it is not in the command search path. Specify the path to its executable file.
+ Here, we are practicing running `dog` using a relative path.
+ Enter `./dog` to run the `dog` file.
+ response:
+ content: |
+ The dog produces the same output as before.
+ This is an example of a simple custom command.
+ You can put a sequence of commands in a file and run them together to handle complex or repetitive tasks.
+ This technique is also useful when running programs written by other people.
+
+ Here is a final challenge: enter the `commands` folder, then run the `lucky` file inside it using a relative path.
+ hint: |
+ Use `cd` to enter a folder
+ Use `ls` to list the files
+ Enter `./filename` to run a file
+
+ - id: execute-lucky
+ name: Run `lucky` using a relative path
+ requirements:
+ - dog-rel
+ condition:
+ pwd: /home/commander/commands
+ command: ./lucky
+ response:
+ content: |
+ The terminal displays `I am SO LUCKY!!!`, the message printed by `lucky`.
+ Congratulations! You have completed this lesson!
+ Enter `echo finish` to complete this quest.
+ exceptions:
+ - id: exception-lucky
+ condition:
+ command: lucky
+ error: 'command not found: lucky'
+ response:
+ content: |
+ `lucky` cannot be run by name here because it is not in the command search path. Specify the path to its executable file.
+
+ - id: exception-lucky-2
+ condition:
+ pwd: /home/commander
+ command: ./lucky
+ error: 'no such file or directory'
+ response:
+ content: |
+ You are outside the `commands` folder, so `./lucky` does not point to the file yet.
+ First, enter `cd commands` to move into `commands`. Then enter `./lucky` to run `lucky` using a relative path.
+ - id: help
+ condition:
+ command: echo help
+ response:
+ content: |
+ Enter the `commands` folder before running `lucky` using a relative path.
+ First, enter `cd commands` to move into `commands`. Then enter `./lucky` to run `lucky` using a relative path.
+
+ - id: END
+ name: Congratulations! All tasks completed!
+ requirements:
+ - execute-lucky
+ condition:
+ command: echo finish
+ response:
+ content: |
+ Congratulations! You have completed this lesson!
+ You now know how to run executable files.
diff --git a/quests/execute/home/commands/lucky b/quests/execute/en/home/commands/lucky
old mode 100755
new mode 100644
similarity index 100%
rename from quests/execute/home/commands/lucky
rename to quests/execute/en/home/commands/lucky
diff --git a/quests/execute/en/home/dog b/quests/execute/en/home/dog
new file mode 100644
index 0000000..f4204ce
--- /dev/null
+++ b/quests/execute/en/home/dog
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+echo "You ran the dog file"
+echo "Woof woof woof"
+sleep 1
+echo "You know what?"
+sleep 1
+echo "I am better than a cat"
diff --git a/quests/execute/home/hello.txt b/quests/execute/en/home/hello.txt
similarity index 100%
rename from quests/execute/home/hello.txt
rename to quests/execute/en/home/hello.txt
diff --git a/quests/execute/execute.md b/quests/execute/zh-TW/execute.md
similarity index 100%
rename from quests/execute/execute.md
rename to quests/execute/zh-TW/execute.md
diff --git a/quests/execute/game.yml b/quests/execute/zh-TW/game.yml
similarity index 100%
rename from quests/execute/game.yml
rename to quests/execute/zh-TW/game.yml
diff --git a/quests/execute/zh-TW/home/commands/lucky b/quests/execute/zh-TW/home/commands/lucky
new file mode 100755
index 0000000..78771c2
--- /dev/null
+++ b/quests/execute/zh-TW/home/commands/lucky
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+echo "I am SO LUCKY!!!"
diff --git a/quests/execute/home/dog b/quests/execute/zh-TW/home/dog
similarity index 100%
rename from quests/execute/home/dog
rename to quests/execute/zh-TW/home/dog
diff --git a/quests/execute/zh-TW/home/hello.txt b/quests/execute/zh-TW/home/hello.txt
new file mode 100644
index 0000000..2d9bd63
--- /dev/null
+++ b/quests/execute/zh-TW/home/hello.txt
@@ -0,0 +1 @@
+Hello! This is a file hello.txt
diff --git a/quests/explore/answer.sh b/quests/explore/en/answer.sh
similarity index 100%
rename from quests/explore/answer.sh
rename to quests/explore/en/answer.sh
diff --git a/quests/explore/en/game.yml b/quests/explore/en/game.yml
new file mode 100644
index 0000000..5732275
--- /dev/null
+++ b/quests/explore/en/game.yml
@@ -0,0 +1,161 @@
+id: explore
+image: base
+title: Exploring Files
+instruction: |
+ In this quest, you will learn:
+ 1. How to list folder contents with `ls`
+ 2. How to move between folders with `cd`
+
+requirements:
+ - get-started
+
+stages:
+ - id: start
+ name: Click `Start` to begin the lesson
+ condition:
+ command: ^echo start$
+ response:
+ content: |
+ The terminal has no graphical interface for browsing folder contents.
+ Instead, you can use `ls` to list what is inside a folder.
+ `ls` is short for list. Its first character is a lowercase L.
+ When you enter `ls`, the terminal lists the files and folders in your current folder.
+ Now enter `ls` to see what is in this folder.
+ hint: Enter `ls` to list the contents of the folder
+
+ - id: ls
+ name: List the files in the folder
+ requirements:
+ - start
+ condition:
+ command: ^ls$
+ exceptions:
+ - id: wrong_directory
+ condition:
+ not:
+ pwd: /home/commander
+ response:
+ type: narrative
+ content: |
+ First, return to `/home/commander/`
+ Enter `cd ~` to return to `/home/commander/`
+ response:
+ content: |
+ You should now see the contents of the folder.
+ The file tree on the right has changed too.
+ This folder contains two folders named `folder1` and `folder2`.
+
+ Next, let's learn how to enter a folder.
+ `cd` is short for change directory, which means changing your current folder.
+ Enter `cd [folder name]` to move into a folder.
+ For example, enter `cd folder1` to move into `folder1`.
+ Now enter the `folder1` folder.
+ hint: Enter `cd folder1` to move into `folder1`
+
+ - id: cd-folder1
+ name: Enter `folder1`
+ requirements:
+ - ls
+ condition:
+ pwd: /home/commander/folder1
+ exceptions:
+ - id: wrong_spelling
+ condition:
+ command: ^folderl$
+ response:
+ type: narrative
+ content: |
+ `folder1` ends with the number `1`, not the letter `l`.
+ - id: wrong_directory
+ condition:
+ pwd: /home/commander
+ response:
+ type: narrative
+ content: Enter `cd folder1` to move into `folder1`
+ - id: not_in_home
+ condition:
+ not:
+ pwd: /home/commander
+ response:
+ type: narrative
+ content: |
+ First, return to `/home/commander/`
+ Enter `cd ~` to return to `/home/commander/`
+ Then enter `cd folder1` to move into `folder1`
+ response:
+ content: |
+ You have entered the `folder1` folder.
+ Notice that the command prompt has changed from
+ `commander:~ $` to
+ `commander:~/folder1 $`.
+ This tells you that you are now in `folder1`.
+ Now list the contents of `folder1`.
+ hint: Enter `ls` to list the contents of the folder
+
+ - id: ls-folder1
+ name: List the contents of `folder1`
+ requirements:
+ - cd-folder1
+ condition:
+ pwd: /home/commander/folder1
+ command: ^ls$
+ response:
+ content: |
+ You can see two files in `folder1`, named `file1.txt` and `file2.txt`.
+ Now let's leave `folder1` and return to the original folder.
+ Use `cd ..` to move up to the parent folder.
+ Here, `..` means the parent folder.
+ hint: Enter `cd ..` to move up to the parent folder
+
+ - id: cd_up
+ name: Return to the parent folder
+ requirements:
+ - ls-folder1
+ condition:
+ pwd: /home/commander
+ response:
+ content: |
+ You have returned to the parent folder.
+ Use the commands you just learned to enter `folder2`, list its contents, and return to this folder.
+ hint: |
+ Use `cd` to move between folders
+ Use `ls` to list the contents of a folder
+
+ - id: cd-folder2
+ name: Enter `folder2`
+ requirements:
+ - cd_up
+ condition:
+ pwd: /home/commander/folder2
+ response:
+ content: |
+ You have entered the `folder2` folder.
+ Now list the contents of `folder2`.
+ hint: Use `ls` to list the contents of the folder
+
+ - id: ls-folder2
+ name: List the contents of `folder2`
+ requirements:
+ - cd-folder2
+ condition:
+ pwd: /home/commander/folder2
+ command: ^ls$
+ response:
+ content: |
+ You can see two files in `folder2`, named `file3.txt` and `file4.txt`.
+ Now leave `folder2` and return to the original folder.
+ hint: Use `cd ..` to move up to the parent folder
+
+ - id: END
+ name: Return to the original folder
+ requirements:
+ - ls-folder2
+ condition:
+ pwd: /home/commander
+ response:
+ content: |
+ You have returned to the original folder.
+ Congratulations! You now know how to use `ls` and `cd`.
+ hint: |
+ Use `cd` to move between folders
+ Use `ls` to list the contents of a folder
diff --git a/quests/path/home/folder1/file1.txt b/quests/explore/en/home/folder1/file1.txt
similarity index 100%
rename from quests/path/home/folder1/file1.txt
rename to quests/explore/en/home/folder1/file1.txt
diff --git a/quests/path/home/folder1/file2.txt b/quests/explore/en/home/folder1/file2.txt
similarity index 100%
rename from quests/path/home/folder1/file2.txt
rename to quests/explore/en/home/folder1/file2.txt
diff --git a/quests/path/home/folder2/file3.txt b/quests/explore/en/home/folder2/file3.txt
similarity index 100%
rename from quests/path/home/folder2/file3.txt
rename to quests/explore/en/home/folder2/file3.txt
diff --git a/quests/path/home/folder2/file4.txt b/quests/explore/en/home/folder2/file4.txt
similarity index 100%
rename from quests/path/home/folder2/file4.txt
rename to quests/explore/en/home/folder2/file4.txt
diff --git a/quests/explore/zh-TW/answer.sh b/quests/explore/zh-TW/answer.sh
new file mode 100644
index 0000000..9a36ff0
--- /dev/null
+++ b/quests/explore/zh-TW/answer.sh
@@ -0,0 +1,8 @@
+echo start
+ls
+cd folder1
+ls
+cd ..
+cd folder2
+ls
+cd ..
\ No newline at end of file
diff --git a/quests/explore/game.yml b/quests/explore/zh-TW/game.yml
similarity index 100%
rename from quests/explore/game.yml
rename to quests/explore/zh-TW/game.yml
diff --git a/quests/remove/home/empty/.gitkeep b/quests/explore/zh-TW/home/folder1/file1.txt
similarity index 100%
rename from quests/remove/home/empty/.gitkeep
rename to quests/explore/zh-TW/home/folder1/file1.txt
diff --git a/quests/remove/home/trash/secrets/envelope.txt b/quests/explore/zh-TW/home/folder1/file2.txt
similarity index 100%
rename from quests/remove/home/trash/secrets/envelope.txt
rename to quests/explore/zh-TW/home/folder1/file2.txt
diff --git a/quests/explore/zh-TW/home/folder2/file3.txt b/quests/explore/zh-TW/home/folder2/file3.txt
new file mode 100644
index 0000000..e69de29
diff --git a/quests/explore/zh-TW/home/folder2/file4.txt b/quests/explore/zh-TW/home/folder2/file4.txt
new file mode 100644
index 0000000..e69de29
diff --git a/quests/get-started/answer.sh b/quests/get-started/en/answer.sh
similarity index 100%
rename from quests/get-started/answer.sh
rename to quests/get-started/en/answer.sh
diff --git a/quests/get-started/en/game.yml b/quests/get-started/en/game.yml
new file mode 100644
index 0000000..8845a97
--- /dev/null
+++ b/quests/get-started/en/game.yml
@@ -0,0 +1,76 @@
+id: get-started
+image: base
+title: Terminal Basics
+instruction: |
+ Welcome to Linux Odyssey!
+ This quest introduces the basics of using the terminal.
+ In this quest, you will learn:
+ 1. How to use this website
+ 2. How to use the terminal
+ 3. How to use the `echo` command
+
+stages:
+ - id: start
+ name: Click `Start` to begin
+ condition:
+ command: ^echo start$
+ response:
+ content: |
+ Hello, brave adventurer!
+ Welcome to Linux Odyssey, an adventure filled with terminal magic!
+ Think of the terminal as your magic wand. You control the computer with **text spells**, a useful skill for any aspiring programmer!
+ The area below is your stage for casting spells: the terminal. Ready to cast your first one?
+ Enter `echo hello` below, then press Enter to see what happens!
+
+ - id: echo-hello
+ name: Enter `echo hello` in the terminal
+ requirements:
+ - start
+ condition:
+ command: ^echo hello$
+ response:
+ content: |
+ Congratulations on running your first command!
+ You should see this in the terminal:
+ ```
+ commander:~ $ echo hello
+ hello
+ ```
+ The prompt `commander:~ $` means the computer is waiting for a command.
+ After you enter `echo hello`, the computer prints "hello" on the next line.
+
+ A command is like a sentence telling the computer what to do.
+ Take `echo hello`, for example:
+ - `echo` tells the computer to print something
+ - `hello` is the text to print
+ So when you enter `echo hello`, the computer prints "hello".
+
+ Now try making the terminal display the text `next`.
+ If you get stuck, click the `Hint` button for help.
+ hint: |
+ Enter `echo next` to continue to the next step.
+
+ - id: echo-next
+ name: Enter `echo next` in the terminal
+ requirements:
+ - echo-hello
+ condition:
+ command: ^echo next$
+ response:
+ content: |
+ Congratulations! You now know how to use `echo`.
+ You might wonder why we need a command like this.
+ Although `echo` is simple, printing text is a basic building block for many tasks.
+ In this adventure, we will also use `echo` to trigger events.
+ That is all for this quest. Enter `echo finish` to complete it.
+
+ - id: END
+ name: Finish the quest
+ requirements:
+ - echo-next
+ condition:
+ command: ^echo finish$
+ response:
+ content: |
+ Congratulations! You have completed this quest!
+ You have learned how to use `echo` and what it does.
diff --git a/quests/get-started/zh-TW/answer.sh b/quests/get-started/zh-TW/answer.sh
new file mode 100644
index 0000000..cbe1543
--- /dev/null
+++ b/quests/get-started/zh-TW/answer.sh
@@ -0,0 +1,4 @@
+echo start
+echo hello
+echo next
+echo finish
\ No newline at end of file
diff --git a/quests/get-started/game.yml b/quests/get-started/zh-TW/game.yml
similarity index 100%
rename from quests/get-started/game.yml
rename to quests/get-started/zh-TW/game.yml
diff --git a/quests/move/answer.sh b/quests/move/en/answer.sh
similarity index 100%
rename from quests/move/answer.sh
rename to quests/move/en/answer.sh
diff --git a/quests/move/en/game.yml b/quests/move/en/game.yml
new file mode 100644
index 0000000..76a266d
--- /dev/null
+++ b/quests/move/en/game.yml
@@ -0,0 +1,136 @@
+id: move
+title: Moving Files
+image: base
+instruction: |
+ In this quest, you will learn:
+ 1. How to move files and folders with `mv`
+
+requirements:
+ - create
+
+stages:
+ - id: start
+ name: Click `Start` to begin
+ condition:
+ command: echo start
+ response:
+ content: |
+ Today we will learn how to move files and folders.
+ Your current folder contains `hello.txt`, `world.txt`, and a folder named `notes`.
+ First, use `ls` and `cat` to explore their contents.
+ When you have finished, enter `echo next` to continue!
+ hint: |
+ Use `ls` and `ls notes` to list the files in the folders
+ Use `cat hello.txt` and `cat world.txt` to display the file contents
+ When you have finished, enter `echo next` to continue
+
+ - id: next
+ requirements:
+ - start
+ name: Explore the files, then enter `echo next` to continue
+ condition:
+ command: echo next
+ response:
+ content: |
+ Now let's learn how to move files and folders.
+ `mv` is short for move. It moves files and folders.
+ Use `mv [source] [destination]`.
+ Let's start by moving `hello.txt` into the `notes` folder!
+ hint: Use `mv hello.txt notes` to move `hello.txt` into `notes`
+
+ - id: mv-hello
+ requirements:
+ - next
+ name: Move `hello.txt` into `notes`
+ condition:
+ command: mv hello.txt notes
+ files:
+ - path: /home/commander/notes/hello.txt
+ type: file
+ exists: true
+ - path: /home/commander/hello.txt
+ type: file
+ exists: false
+ response:
+ content: |
+ Well done!
+ `hello.txt` has been moved into `notes`!
+
+ `mv` can also rename files!
+ Just use the new name as the destination.
+ Now rename `world.txt` to `wow.txt`.
+ hint: Use `mv world.txt wow.txt` to rename `world.txt` to `wow.txt`
+
+ - id: mv-world
+ requirements:
+ - mv-hello
+ name: Rename `world.txt` to `wow.txt`
+ condition:
+ command: mv world.txt wow.txt
+ files:
+ - path: /home/commander/wow.txt
+ type: file
+ exists: true
+ - path: /home/commander/world.txt
+ type: file
+ exists: false
+ response:
+ content: |
+ `world.txt` is now named `wow.txt`.
+
+ Next, let's learn how to move folders!
+ Moving a folder works the same way as moving a file.
+ `mv` automatically moves everything inside the folder too.
+ First, rename the `notes` folder to `docs`!
+ hint: Use `mv notes docs` to rename `notes` to `docs`
+
+ - id: mv-notes
+ requirements:
+ - mv-world
+ name: Rename `notes` to `docs`
+ condition:
+ command: mv notes docs
+ files:
+ - path: /home/commander/docs
+ type: directory
+ exists: true
+ - path: /home/commander/notes
+ type: directory
+ exists: false
+ response:
+ content: |
+ The `notes` folder is now named `docs`.
+ Here is a final challenge:
+ Move `docs/hello.txt` to your home folder and rename it to `hi.txt`.
+ hint: Use `mv docs/hello.txt hi.txt` to move the file to your home folder as `hi.txt`
+
+ - id: mv-docs-hello
+ requirements:
+ - mv-notes
+ name: Move `docs/hello.txt` to your home folder as `hi.txt`
+ condition:
+ command: mv docs/hello.txt hi.txt
+ files:
+ - path: /home/commander/hi.txt
+ type: file
+ exists: true
+ - path: /home/commander/docs/hello.txt
+ type: file
+ exists: false
+ response:
+ content: |
+ Well done!
+ You now know how to move files and folders!
+
+ Enter `echo finish` to complete this quest.
+
+ - id: END
+ requirements:
+ - mv-docs-hello
+ name: Complete the quest
+ condition:
+ command: echo finish
+ response:
+ content: |
+ You now know how to move files and folders!
+ Congratulations on completing this quest!
diff --git a/quests/remove/home/hello.txt b/quests/move/en/home/hello.txt
similarity index 100%
rename from quests/remove/home/hello.txt
rename to quests/move/en/home/hello.txt
diff --git a/quests/remove/home/notes/note.txt b/quests/move/en/home/notes/note.txt
similarity index 100%
rename from quests/remove/home/notes/note.txt
rename to quests/move/en/home/notes/note.txt
diff --git a/quests/remove/home/world.txt b/quests/move/en/home/world.txt
similarity index 100%
rename from quests/remove/home/world.txt
rename to quests/move/en/home/world.txt
diff --git a/quests/move/zh-TW/answer.sh b/quests/move/zh-TW/answer.sh
new file mode 100644
index 0000000..a24458b
--- /dev/null
+++ b/quests/move/zh-TW/answer.sh
@@ -0,0 +1,11 @@
+echo start
+echo next
+mv hello.txt notes
+ls
+ls notes
+echo next
+mv world.txt wow.txt
+ls
+mv notes docs
+ls
+mv docs/hello.txt hi.txt
\ No newline at end of file
diff --git a/quests/move/game.yml b/quests/move/zh-TW/game.yml
similarity index 100%
rename from quests/move/game.yml
rename to quests/move/zh-TW/game.yml
diff --git a/quests/move/zh-TW/home/hello.txt b/quests/move/zh-TW/home/hello.txt
new file mode 100644
index 0000000..e965047
--- /dev/null
+++ b/quests/move/zh-TW/home/hello.txt
@@ -0,0 +1 @@
+Hello
diff --git a/quests/move/zh-TW/home/notes/note.txt b/quests/move/zh-TW/home/notes/note.txt
new file mode 100644
index 0000000..77c6f9e
--- /dev/null
+++ b/quests/move/zh-TW/home/notes/note.txt
@@ -0,0 +1 @@
+Note
diff --git a/quests/move/zh-TW/home/world.txt b/quests/move/zh-TW/home/world.txt
new file mode 100644
index 0000000..216e97c
--- /dev/null
+++ b/quests/move/zh-TW/home/world.txt
@@ -0,0 +1 @@
+World
diff --git a/quests/path/answer.sh b/quests/path/en/answer.sh
similarity index 100%
rename from quests/path/answer.sh
rename to quests/path/en/answer.sh
diff --git a/quests/path/en/game.yml b/quests/path/en/game.yml
new file mode 100644
index 0000000..95c6c8f
--- /dev/null
+++ b/quests/path/en/game.yml
@@ -0,0 +1,146 @@
+id: path
+title: File Paths
+image: base
+instruction: |
+ In this quest, you will learn:
+ 1. Absolute and relative paths
+ 2. How to show your current path with `pwd`
+
+requirements:
+ - explore
+
+stages:
+ - id: start
+ name: Click `Start` to begin the lesson
+ condition:
+ command: echo start
+ response:
+ content: |
+ In Linux, a path describes the location of a file or folder.
+ Paths can be relative or absolute.
+ A relative path starts from your current folder. An absolute path starts from the root folder.
+ First, use `ls` to list the files in this folder.
+
+ - id: ls
+ name: List the files in the folder
+ requirements:
+ - start
+ condition:
+ pwd: /home/commander
+ command: ls
+ response:
+ content: |
+ Now use `cd` to enter the `folder1` folder.
+
+ - id: cd-folder1
+ name: Enter `folder1`
+ requirements:
+ - ls
+ condition:
+ pwd: /home/commander/folder1
+ response:
+ content: |
+ You have entered `folder1`.
+ In `cd folder1`, the `folder1` part is a relative path.
+ Likewise, `..` in `cd ..` is a relative path.
+ A relative path can span more than one level of folders.
+ For example, to enter `folder2` from here, you could use `cd ..` to go up one level, then `cd folder2` to enter it.
+ You can also join `..` and `folder2` with `/` and enter `cd ../folder2` to get there in one command.
+ Try entering `folder2` this way.
+ hint: Enter `cd ../folder2` to move into `folder2`.
+
+ - id: cd-folder2
+ name: Enter `folder2`
+ requirements:
+ - cd-folder1
+ condition:
+ pwd: /home/commander/folder2
+ exceptions:
+ - id: not_in_folder2
+ condition:
+ pwd: /home/commander
+ response:
+ content: Enter `cd folder2` to move into `folder2`
+ response:
+ content: |
+ You have entered `folder2` using a relative path.
+ You can also add a relative path after `ls` to list the contents of another folder.
+ For example, use `ls ../folder1` to see which files are in `folder1`.
+ Try listing the files in `folder1` this way.
+ hint: Enter `ls ../folder1` to list the files in `folder1`.
+
+ - id: ls-folder1
+ name: List the files in `folder1`
+ requirements:
+ - cd-folder2
+ condition:
+ output: file1.txt
+ exceptions:
+ - id: not_in_folder2
+ condition:
+ not:
+ pwd: /home/commander/folder2
+ response:
+ content: Return to `folder2` with `cd ~/folder2`, then use `ls ../folder1` to list the contents of `folder1` without leaving `folder2`.
+ response:
+ content: |
+ Great! You now know how to use relative paths.
+ Next, let's look at absolute paths.
+ An absolute path starts from the root folder, which is at the top of the file tree.
+ Use `pwd` to display the absolute path to your current folder.
+ `pwd` stands for `print working directory`: it displays the path to the folder you are in.
+ Now use `pwd` to display your current absolute path.
+ hint: Enter `pwd` to display your current absolute path
+
+ - id: pwd
+ name: Display your current absolute path
+ requirements:
+ - ls-folder1
+ condition:
+ pwd: /home/commander/folder2
+ command: pwd
+ exceptions:
+ - id: not_in_folder2
+ condition:
+ not:
+ pwd: /home/commander/folder2
+ response:
+ content: Return to `folder2`, then use `pwd` to display your current absolute path
+ response:
+ content: |
+ Your current absolute path is `/home/commander/folder2`.
+ The command prompt also shows your current location.
+ In `commander:~/folder2 $`, `~/folder2` is a shortened form of the path to your current folder.
+ `~` stands for `/home/commander`, your home folder. Each game starts here.
+ You can use absolute paths with `ls` and `cd` too.
+ For example, use `ls ~` to list your home folder or `cd ~` to return to it.
+ Now use `ls ~` to list the contents of your home folder.
+ hint: Enter `ls ~` to list the contents of your home folder
+
+ - id: ls_home
+ name: List the contents of your home folder
+ requirements:
+ - pwd
+ condition:
+ command: ls ~
+ response:
+ content: |
+ Now use `cd ~` to return to your home folder.
+ hint: Enter `cd ~` to return to your home folder
+
+ - id: END
+ name: Return to your home folder
+ requirements:
+ - ls_home
+ condition:
+ command: cd ~
+ exceptions:
+ - id: already_home
+ condition:
+ pwd: /home/commander
+ response:
+ content: Use `cd ~` to return to your home folder
+ response:
+ content: |
+ You have returned to your home folder.
+ Congratulations! You now know how to use absolute and relative paths and display your current absolute path.
diff --git a/quests/path/en/home/folder1/file1.txt b/quests/path/en/home/folder1/file1.txt
new file mode 100644
index 0000000..e69de29
diff --git a/quests/path/en/home/folder1/file2.txt b/quests/path/en/home/folder1/file2.txt
new file mode 100644
index 0000000..e69de29
diff --git a/quests/path/en/home/folder2/file3.txt b/quests/path/en/home/folder2/file3.txt
new file mode 100644
index 0000000..e69de29
diff --git a/quests/path/en/home/folder2/file4.txt b/quests/path/en/home/folder2/file4.txt
new file mode 100644
index 0000000..e69de29
diff --git a/quests/path/zh-TW/answer.sh b/quests/path/zh-TW/answer.sh
new file mode 100644
index 0000000..bf3a210
--- /dev/null
+++ b/quests/path/zh-TW/answer.sh
@@ -0,0 +1,8 @@
+echo start
+ls
+cd folder1
+cd ../folder2
+ls ../folder1
+pwd
+ls ~
+cd ~
\ No newline at end of file
diff --git a/quests/path/game.yml b/quests/path/zh-TW/game.yml
similarity index 100%
rename from quests/path/game.yml
rename to quests/path/zh-TW/game.yml
diff --git a/quests/path/zh-TW/home/folder1/file1.txt b/quests/path/zh-TW/home/folder1/file1.txt
new file mode 100644
index 0000000..e69de29
diff --git a/quests/path/zh-TW/home/folder1/file2.txt b/quests/path/zh-TW/home/folder1/file2.txt
new file mode 100644
index 0000000..e69de29
diff --git a/quests/path/zh-TW/home/folder2/file3.txt b/quests/path/zh-TW/home/folder2/file3.txt
new file mode 100644
index 0000000..e69de29
diff --git a/quests/path/zh-TW/home/folder2/file4.txt b/quests/path/zh-TW/home/folder2/file4.txt
new file mode 100644
index 0000000..e69de29
diff --git a/quests/read/answer.sh b/quests/read/en/answer.sh
similarity index 100%
rename from quests/read/answer.sh
rename to quests/read/en/answer.sh
diff --git a/quests/read/en/game.yml b/quests/read/en/game.yml
new file mode 100644
index 0000000..e27aaba
--- /dev/null
+++ b/quests/read/en/game.yml
@@ -0,0 +1,93 @@
+id: read
+title: Reading File Contents
+image: base
+instruction: |
+ In this quest, you will learn:
+ 1. How to display file contents with `cat`
+
+requirements:
+ - path
+
+stages:
+ - id: start
+ name: Click `Start` to begin the lesson
+ condition:
+ command: echo start
+ response:
+ content: |
+ The terminal has no graphical interface for viewing file contents.
+ Instead, you can use `cat` to display them.
+ `cat` is short for concatenate, which means joining things together.
+ Enter `cat [filename]` to print a file's contents in the terminal.
+ First, see which files are in this folder.
+ hint: Enter `ls` to list the files in this folder.
+
+ - id: ls_home
+ name: List the files in the folder
+ requirements:
+ - start
+ condition:
+ pwd: /home/commander
+ command: ls
+ response:
+ content: |
+ This folder contains one file and one folder.
+ Now use `cat` to display the contents of `file1.txt`.
+ hint: Enter `cat file1.txt` to display the file contents.
+
+ - id: cat-file1
+ name: Display the contents of `file1.txt`
+ requirements:
+ - ls_home
+ condition:
+ pwd: /home/commander
+ command: cat file1.txt
+ response:
+ content: |
+ You should now see the file's contents.
+ You can also give `cat` a path to a file in another folder.
+ First, use `ls folder` to list the files inside `folder`.
+ hint: Enter `ls folder` to list the files inside the folder
+
+ - id: ls-folder
+ name: List the contents of `folder`
+ requirements:
+ - cat-file1
+ condition:
+ pwd: /home/commander
+ command: ls folder
+ exceptions:
+ - id: not_in_home
+ condition:
+ not:
+ pwd: /home/commander
+ response:
+ content: |
+ Stay in the `/home/commander` folder.
+ Enter `cd ~` to return to `/home/commander`.
+ response:
+ content: |
+ You now know what is inside `folder`.
+ Use `cat` to display the contents of `file.txt` inside `folder`.
+ hint: Enter `cat folder/file.txt` to display the contents of `file.txt`.
+
+ - id: END
+ name: Display the contents of `file.txt`
+ requirements:
+ - ls-folder
+ condition:
+ pwd: /home/commander
+ command: cat folder/file.txt
+ exceptions:
+ - id: not_in_home
+ condition:
+ not:
+ pwd: /home/commander
+ response:
+ content: |
+ Stay in the `/home/commander` folder.
+ Enter `cd ~` to return to `/home/commander`.
+ response:
+ content: |
+ You have displayed the contents of `file.txt`.
+ You now know how to display file contents with `cat`.
diff --git a/quests/read/home/file1.txt b/quests/read/en/home/file1.txt
similarity index 100%
rename from quests/read/home/file1.txt
rename to quests/read/en/home/file1.txt
diff --git a/quests/read/home/folder/file.txt b/quests/read/en/home/folder/file.txt
similarity index 100%
rename from quests/read/home/folder/file.txt
rename to quests/read/en/home/folder/file.txt
diff --git a/quests/read/zh-TW/answer.sh b/quests/read/zh-TW/answer.sh
new file mode 100644
index 0000000..c70c380
--- /dev/null
+++ b/quests/read/zh-TW/answer.sh
@@ -0,0 +1,5 @@
+echo start
+ls
+cat file1.txt
+ls folder
+cat folder/file.txt
\ No newline at end of file
diff --git a/quests/read/game.yml b/quests/read/zh-TW/game.yml
similarity index 100%
rename from quests/read/game.yml
rename to quests/read/zh-TW/game.yml
diff --git a/quests/read/zh-TW/home/file1.txt b/quests/read/zh-TW/home/file1.txt
new file mode 100644
index 0000000..b3cfc35
--- /dev/null
+++ b/quests/read/zh-TW/home/file1.txt
@@ -0,0 +1 @@
+This is file1.txt
diff --git a/quests/read/zh-TW/home/folder/file.txt b/quests/read/zh-TW/home/folder/file.txt
new file mode 100644
index 0000000..597d692
--- /dev/null
+++ b/quests/read/zh-TW/home/folder/file.txt
@@ -0,0 +1 @@
+This is file.txt
\ No newline at end of file
diff --git a/quests/remove/answer.sh b/quests/remove/en/answer.sh
similarity index 100%
rename from quests/remove/answer.sh
rename to quests/remove/en/answer.sh
diff --git a/quests/remove/en/game.yml b/quests/remove/en/game.yml
new file mode 100644
index 0000000..e327ec5
--- /dev/null
+++ b/quests/remove/en/game.yml
@@ -0,0 +1,202 @@
+id: remove
+title: Deleting Files and Folders
+image: base
+instruction: |
+ In this quest, you will learn:
+ 1. How to delete files with `rm`
+ 2. How to delete empty folders with `rmdir`
+ 3. How to delete folders and their contents with `rm -r`
+
+requirements:
+ - copy
+ - move
+
+stages:
+ - id: start
+ name: Click `Start` to begin
+ condition:
+ command: echo start
+ response:
+ content: |
+ Today we will learn how to delete files and folders.
+ Your current folder contains `hello.txt`, `world.txt`, and a folder named `notes`.
+ First, use `ls` to list them.
+ When you have finished, enter `echo next` to continue!
+ hint: |
+ Use `ls` to list the files in the folder
+ When you have finished, enter `echo next` to continue
+
+ - id: next
+ requirements:
+ - start
+ name: Explore the files, then enter `echo next` to continue
+ condition:
+ command: echo next
+ response:
+ content: |
+ Now let's learn how to delete files.
+ `rm` is short for remove. It deletes files.
+ Use `rm [filename]`.
+ Let's start by deleting `hello.txt`!
+ hint: Use `rm hello.txt` to delete `hello.txt`
+
+ - id: rm-hello
+ requirements:
+ - next
+ name: Delete `hello.txt`
+ condition:
+ command: rm hello.txt
+ files:
+ - path: /home/commander/hello.txt
+ type: file
+ exists: false
+ response:
+ content: |
+ `hello.txt` is no longer in the folder!
+
+ `rm` can delete several files at once. Separate their names with spaces.
+ Use `rm [file1] [file2] ...`.
+ Now use `rm` to delete `world.txt` and `wow.txt`.
+ hint: |
+ Use `rm world.txt wow.txt` to delete `world.txt` and `wow.txt`
+ Separate the filenames with a space
+ The order of the names does not matter
+
+ - id: rm-world-wow
+ requirements:
+ - rm-hello
+ name: Delete `world.txt` and `wow.txt`
+ condition:
+ files:
+ - path: /home/commander/world.txt
+ type: file
+ exists: false
+ - path: /home/commander/wow.txt
+ type: file
+ exists: false
+ response:
+ content: |
+ `world.txt` and `wow.txt` are now gone.
+
+ Next, let's learn how to delete folders.
+ `rmdir` deletes empty folders.
+ Use `rmdir [folder name]`.
+ Let's start by deleting the `empty` folder!
+ hint: Use `rmdir empty` to delete the `empty` folder
+
+ - id: rm-empty
+ requirements:
+ - rm-world-wow
+ name: Delete the `empty` folder
+ condition:
+ command: rmdir empty
+ files:
+ - path: /home/commander/empty
+ type: directory
+ exists: false
+ response:
+ content: |
+ The `empty` folder is now gone.
+ Now try using `rmdir` to delete the `notes` folder.
+ hint: Use `rmdir notes` to delete the `notes` folder
+
+ - id: rm-notes-failed
+ requirements:
+ - rm-empty
+ name: Try deleting the `notes` folder
+ condition:
+ command: rmdir notes
+ error: "rmdir: failed to remove 'notes': Directory not empty"
+ response:
+ content: |
+ The command failed!
+ The `notes` folder is still there.
+ This is because `rmdir` only deletes empty folders.
+ Let's delete the files inside `notes` and try again.
+ Now use `rm` to delete the files inside `notes`.
+ hint: |
+ Use `rm notes/[filename]` to delete a file inside `notes`
+ You may need to use `ls notes` to see which files are inside
+ Or use Tab to complete the filename
+
+ - id: rm-notes-content
+ requirements:
+ - rm-notes-failed
+ name: Delete the files inside `notes`
+ condition:
+ command: rm notes/note.txt
+ files:
+ - path: /home/commander/notes/note.txt
+ type: file
+ exists: false
+ exceptions:
+ - id: hint
+ condition:
+ command: echo hint
+ response:
+ content: |
+ Use `rm notes/note.txt` to delete `note.txt` inside `notes`.
+ hint: Use `rm notes/note.txt` to delete `note.txt` inside `notes`
+ response:
+ content: |
+ Great! Now use `rmdir` again to delete the `notes` folder.
+ hint: Use `rmdir notes` to delete the `notes` folder
+
+ - id: rm-notes
+ requirements:
+ - rm-notes-content
+ name: Delete the `notes` folder
+ condition:
+ command: rmdir notes
+ files:
+ - path: /home/commander/notes
+ type: directory
+ exists: false
+ response:
+ content: |
+ Great! The `notes` folder has been deleted.
+ Next, let's delete the `trash` folder.
+ First, use `ls` to see what is inside `trash`.
+ hint: Use `ls trash` to list the files inside `trash`
+
+ - id: ls-trash
+ requirements:
+ - rm-notes
+ name: List the contents of `trash`
+ condition:
+ command: ls trash
+ response:
+ content: |
+ Wow! `trash` contains lots of files, and even more folders!
+ What if a folder has many files and subfolders and we want to delete them all at once?
+ Just as with `cp`, add the `-r` option to delete the folder and everything inside it.
+ `-r` means recursive: the command also deletes the contents of any nested folders.
+ Let's try it!
+ hint: Use `rm -r trash` to delete the `trash` folder
+
+ - id: rm-trash
+ requirements:
+ - ls-trash
+ name: Delete `trash` and its contents
+ condition:
+ command: rm -r trash
+ files:
+ - path: /home/commander/trash
+ type: directory
+ exists: false
+ response:
+ content: |
+ The `trash` folder and everything inside it are now gone.
+ Congratulations! You now know how to delete files and folders!
+ Enter `echo finish` to complete this quest.
+
+ - id: END
+ requirements:
+ - rm-trash
+ name: Complete the quest
+ condition:
+ command: echo finish
+ response:
+ content: |
+ You now know how to delete files and folders!
+ Congratulations on completing this quest!
diff --git a/quests/remove/en/home/empty/.gitkeep b/quests/remove/en/home/empty/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/quests/remove/en/home/hello.txt b/quests/remove/en/home/hello.txt
new file mode 100644
index 0000000..e965047
--- /dev/null
+++ b/quests/remove/en/home/hello.txt
@@ -0,0 +1 @@
+Hello
diff --git a/quests/remove/en/home/notes/note.txt b/quests/remove/en/home/notes/note.txt
new file mode 100644
index 0000000..77c6f9e
--- /dev/null
+++ b/quests/remove/en/home/notes/note.txt
@@ -0,0 +1 @@
+Note
diff --git a/quests/remove/en/home/trash/garbage.txt b/quests/remove/en/home/trash/garbage.txt
new file mode 100644
index 0000000..e69de29
diff --git a/quests/remove/en/home/trash/rubbish.txt b/quests/remove/en/home/trash/rubbish.txt
new file mode 100644
index 0000000..e69de29
diff --git a/quests/remove/en/home/trash/secrets/envelope.txt b/quests/remove/en/home/trash/secrets/envelope.txt
new file mode 100644
index 0000000..e69de29
diff --git a/quests/remove/en/home/world.txt b/quests/remove/en/home/world.txt
new file mode 100644
index 0000000..216e97c
--- /dev/null
+++ b/quests/remove/en/home/world.txt
@@ -0,0 +1 @@
+World
diff --git a/quests/remove/home/wow.txt b/quests/remove/en/home/wow.txt
similarity index 100%
rename from quests/remove/home/wow.txt
rename to quests/remove/en/home/wow.txt
diff --git a/quests/remove/zh-TW/answer.sh b/quests/remove/zh-TW/answer.sh
new file mode 100644
index 0000000..9d69429
--- /dev/null
+++ b/quests/remove/zh-TW/answer.sh
@@ -0,0 +1,14 @@
+echo start
+ls
+rm hello.txt
+ls
+rm world.txt wow.txt
+ls
+rmdir empty
+ls
+rmdir notes
+rm notes/note.txt
+rmdir notes
+ls trash
+rm -r trash
+ls
\ No newline at end of file
diff --git a/quests/remove/game.yml b/quests/remove/zh-TW/game.yml
similarity index 100%
rename from quests/remove/game.yml
rename to quests/remove/zh-TW/game.yml
diff --git a/quests/remove/zh-TW/home/empty/.gitkeep b/quests/remove/zh-TW/home/empty/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/quests/remove/zh-TW/home/hello.txt b/quests/remove/zh-TW/home/hello.txt
new file mode 100644
index 0000000..e965047
--- /dev/null
+++ b/quests/remove/zh-TW/home/hello.txt
@@ -0,0 +1 @@
+Hello
diff --git a/quests/remove/zh-TW/home/notes/note.txt b/quests/remove/zh-TW/home/notes/note.txt
new file mode 100644
index 0000000..77c6f9e
--- /dev/null
+++ b/quests/remove/zh-TW/home/notes/note.txt
@@ -0,0 +1 @@
+Note
diff --git a/quests/remove/zh-TW/home/trash/garbage.txt b/quests/remove/zh-TW/home/trash/garbage.txt
new file mode 100644
index 0000000..e69de29
diff --git a/quests/remove/zh-TW/home/trash/rubbish.txt b/quests/remove/zh-TW/home/trash/rubbish.txt
new file mode 100644
index 0000000..e69de29
diff --git a/quests/remove/zh-TW/home/trash/secrets/envelope.txt b/quests/remove/zh-TW/home/trash/secrets/envelope.txt
new file mode 100644
index 0000000..e69de29
diff --git a/quests/remove/zh-TW/home/world.txt b/quests/remove/zh-TW/home/world.txt
new file mode 100644
index 0000000..216e97c
--- /dev/null
+++ b/quests/remove/zh-TW/home/world.txt
@@ -0,0 +1 @@
+World
diff --git a/quests/remove/zh-TW/home/wow.txt b/quests/remove/zh-TW/home/wow.txt
new file mode 100644
index 0000000..18bd653
--- /dev/null
+++ b/quests/remove/zh-TW/home/wow.txt
@@ -0,0 +1 @@
+Wow
\ No newline at end of file
diff --git a/quests/tab/en/game.yml b/quests/tab/en/game.yml
new file mode 100644
index 0000000..ec3aea2
--- /dev/null
+++ b/quests/tab/en/game.yml
@@ -0,0 +1,174 @@
+id: tab
+title: Tab Completion
+image: base
+instruction: |
+ In this quest, you will learn:
+
+ 1. How to complete paths with Tab
+ 2. How to handle multiple matching names
+
+ Click the button in the middle to start this quest.
+
+requirements:
+ - read
+
+stages:
+ - id: start
+ name: Click Start to begin the quest
+ condition:
+ command: echo start
+ response:
+ content: |
+ In the terminal, you work by entering text.
+ Typing takes time, and a single typo can make a command fail.
+ Autocompletion helps you fill in file paths, command names, and supported options, saving time and avoiding typos.
+
+ Before we start practicing, let's see which files are in your home folder.
+ hint: Use `ls` to list the files
+
+ - id: ls
+ name: List the files with `ls`
+ requirements:
+ - start
+ condition:
+ command: ls
+ response:
+ content: |
+ This folder is full of very long and misspelled filenames. Typing them character by character would be painful.
+ Luckily, autocompletion makes even long filenames easy to enter.
+
+ To use autocompletion, **type part of a filename, then press Tab**.
+ (Tab is the key to the left of Q.)
+ For example, type `Su` and press Tab to complete `SuperSecretPlansForWorldDomination.txt`. The terminal fills in the rest for you.
+
+ Let's display the contents of `SuperSecretPlansForWorldDomination.txt`.
+ hint: |
+ Use `cat SuperSecretPlansForWorldDomination.txt` to display the file contents
+ Type `cat Su`, then press Tab to complete the filename. Press Enter to run the command
+
+ - id: cat_super_secret
+ name: Display SuperSecretPlansForWorldDomination.txt with `cat`
+ requirements:
+ - ls
+ condition:
+ pwd: /home/commander
+ command: cat SuperSecretPlansForWorldDomination.txt
+ response:
+ content: |
+ The terminal completed `SuperSecretPlansForWorldDomination.txt` for you, saving you from typing a long name and making mistakes.
+ You can start with `S`, `Su`, `Sup`, `Super`, or even no characters at all.
+ However, the fewer characters you enter, the more likely they are to match several files.
+ We will practice handling multiple matches shortly.
+
+ Autocompletion also helps with awkward spellings. Take `ThsisAFlieNmaeWthAloOfTpyosThatisRleayHdToRaed.txt`: anyone could make a mistake typing that!
+ Use `cat` again to display the contents of `ThsisAFlieNmaeWthAloOfTpyosThatisRleayHdToRaed.txt`.
+ hint: |
+ Enter `cat ThsisAFlieNmaeWthAloOfTpyosThatisRleayHdToRaed.txt` to display the file contents
+ Type `cat Thsi`, then press Tab to complete the filename
+ Try pressing Tab after entering fewer characters and see what happens
+
+ - id: cat_typo
+ name: Display ThsisAFlieNmaeWthAloOfTpyosThatisRleayHdToRaed.txt with `cat`
+ requirements:
+ - cat_super_secret
+ condition:
+ pwd: /home/commander
+ command: cat ThsisAFlieNmaeWthAloOfTpyosThatisRleayHdToRaed.txt
+ response:
+ content: |
+ You have opened the file with the misspelled name.
+ With autocompletion, typos, mixed capitalization, and random-looking names are much easier to handle.
+ Let's practice again. This time, list the files inside `ThingsIWillOrganizeLaterWhenIHaveTime`.
+ As before, type the first few characters, then press Tab.
+ hint: |
+ Enter `ls ThingsIWillOrganizeLaterWhenIHaveTime` to list the files inside the folder
+ Type `ls Thing`, then press Tab to complete the folder name
+
+ - id: ls_things
+ name: List ThingsIWillOrganizeLaterWhenIHaveTime with `ls`
+ requirements:
+ - cat_typo
+ condition:
+ pwd: /home/commander
+ command: ls ThingsIWillOrganizeLaterWhenIHaveTime
+ response:
+ content: |
+ This folder contains three files: a guide to making sandwiches and two lists of things downloaded in the middle of the night.
+ The sandwich guide, `HowToProperlyMakeASandwich.txt`, is inside another folder, so one press of Tab will not complete the whole path.
+ This time, let's practice **completing a path in steps**.
+
+ Type the start of the folder name and press Tab. Then type the start of the filename and press Tab again to complete `folder/filename`.
+ When you complete a folder name, the terminal adds `/` at the end for you.
+ Let's open `HowToProperlyMakeASandwich.txt`.
+ hint: |
+ Enter `cat ThingsIWillOrganizeLaterWhenIHaveTime/HowToProperlyMakeASandwich.txt` to display the file contents
+ Type `cat Thin`, then press Tab to complete it to `cat ThingsIWillOrganizeLaterWhenIHaveTime/`
+ Next, type `How` after the folder path and press Tab to complete the full path
+
+ - id: cat_sandwich
+ name: Display ThingsIWillOrganizeLaterWhenIHaveTime/HowToProperlyMakeASandwich.txt with `cat`
+ requirements:
+ - ls_things
+ condition:
+ command: cat ThingsIWillOrganizeLaterWhenIHaveTime/HowToProperlyMakeASandwich.txt
+ response:
+ content: |
+ You have read this very strict guide to making a sandwich.
+ Two files remain: `RandomStuffThatIDownloadedAt3AM.txt` and `RandomStuffThatIDownloadedAt4AM.txt`.
+ Their names are almost identical. They differ only at `3AM` and `4AM` before the `.txt` extension.
+ If you only type the first few characters, the terminal cannot tell which file you want.
+ This is a case of **multiple matches**.
+
+ You can handle multiple matches in steps, just as you did with the folder path.
+ Type `Ran` and press Tab. The terminal completes the shared part of both names: `RandomStuffThatIDownloadedAt`.
+ Then type `3` or `4`, depending on which file you want, and press Tab again to complete the rest.
+
+ _Completing a full path also takes steps: after completing the folder name, you still need to choose a file inside it._
+ hint: |
+ Display the contents of `ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt3AM.txt`
+ Type `cat Thin`, then press Tab to complete it to `cat ThingsIWillOrganizeLaterWhenIHaveTime/`
+ Next, type `Ran` and press Tab to complete it to `cat ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt`
+ Then type `3` and press Tab to complete the full path.
+
+ - id: cat_3am
+ name: Display ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt3AM.txt with `cat`
+ requirements:
+ - cat_sandwich
+ condition:
+ pwd: /home/commander
+ command: cat ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt3AM.txt
+ response:
+ content: |
+ You have read a list of things downloaded at 3 AM. Hmm... that is enough internet for today.
+ Here is a final challenge: enter `ThingsIWillOrganizeLaterWhenIHaveTime`, then display the contents of `RandomStuffThatIDownloadedAt4AM.txt`.
+ Use the autocompletion skills you learned today to complete the task.
+ hint: |
+ First, use `cd` to enter ThingsIWillOrganizeLaterWhenIHaveTime
+ Then use `cat` to display RandomStuffThatIDownloadedAt4AM.txt
+ Remember to use Tab completion to save time and avoid mistakes
+
+ - id: cd_things
+ name: Enter ThingsIWillOrganizeLaterWhenIHaveTime with `cd`
+ requirements:
+ - cat_3am
+ condition:
+ pwd: /home/commander/ThingsIWillOrganizeLaterWhenIHaveTime
+ response:
+ content: |
+ Great! You have entered ThingsIWillOrganizeLaterWhenIHaveTime.
+ Now use `cat` to display the contents of `RandomStuffThatIDownloadedAt4AM.txt`.
+ Remember to use Tab completion!
+
+ - id: END
+ name: Display RandomStuffThatIDownloadedAt4AM.txt with `cat`
+ requirements:
+ - cd_things
+ condition:
+ pwd: /home/commander/ThingsIWillOrganizeLaterWhenIHaveTime
+ command: cat RandomStuffThatIDownloadedAt4AM.txt
+ response:
+ content: |
+ Congratulations! You have completed all the tasks and practiced using Tab completion.
+ This skill will make working in the terminal much easier.
+ Whether you face long filenames, complex paths, or tricky spellings, Tab completion is there to help!
+ hint: All tasks completed! You can keep exploring or finish this quest.
diff --git a/quests/tab/en/home/SuperSecretPlansForWorldDomination.txt b/quests/tab/en/home/SuperSecretPlansForWorldDomination.txt
new file mode 100644
index 0000000..4e6f35b
--- /dev/null
+++ b/quests/tab/en/home/SuperSecretPlansForWorldDomination.txt
@@ -0,0 +1,10 @@
+1. Collect all the socks in the world so nobody can find a matching pair.
+2. Train an army of penguins to conquer the world with cuteness.
+3. Invent a device that turns everyone's speech into chicken noises.
+4. Replace all coffee with decaf and watch the world descend into chaos.
+5. Erase every national border with a giant eraser and declare Earth one huge playground.
+6. Make all politicians wear clown costumes when giving speeches.
+7. Replace every math textbook with a cookbook and see what happens.
+8. Invent a perfume that makes people tell the truth, then spray it at the UN General Assembly.
+9. Use a giant magnet to collect all the metal on Earth and build a Disneyland in space.
+10. If all else fails, flood the world's social media with cute cat videos until everyone surrenders.
diff --git a/quests/tab/en/home/ThingsIWillOrganizeLaterWhenIHaveTime/HowToProperlyMakeASandwich.txt b/quests/tab/en/home/ThingsIWillOrganizeLaterWhenIHaveTime/HowToProperlyMakeASandwich.txt
new file mode 100644
index 0000000..adb8eea
--- /dev/null
+++ b/quests/tab/en/home/ThingsIWillOrganizeLaterWhenIHaveTime/HowToProperlyMakeASandwich.txt
@@ -0,0 +1,9 @@
+How to Make a Sandwich "Properly":
+
+1. Slap your bread awake. It has been loafing around all day!
+2. Choose your ingredients carefully. Each one must have its own personality. For example, pick a slice of ham that loves telling jokes.
+3. Measure the thickness of each ingredient with a ruler. The margin of error must be no more than 1 mm.
+4. Wear white gloves and laboratory goggles while assembling the sandwich to ensure the highest standards of hygiene.
+5. Once finished, inspect every inch of the sandwich with a magnifying glass to make sure it is flawless.
+
+Remember: making a sandwich is an exact science. No mistakes or creativity allowed!
diff --git a/quests/tab/en/home/ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt3AM.txt b/quests/tab/en/home/ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt3AM.txt
new file mode 100644
index 0000000..63fed87
--- /dev/null
+++ b/quests/tab/en/home/ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt3AM.txt
@@ -0,0 +1,5 @@
+Things I started downloading at 3 AM but never had time to organize.
+
+1. Blueprints for a magic wand that turns any food into pizza
+2. A secret guide to learning ten alien languages in five minutes
+3. A tutorial video on making fashionable hats from old socks (link no longer works)
diff --git a/quests/tab/en/home/ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt4AM.txt b/quests/tab/en/home/ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt4AM.txt
new file mode 100644
index 0000000..72f14a5
--- /dev/null
+++ b/quests/tab/en/home/ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt4AM.txt
@@ -0,0 +1,5 @@
+Things I started downloading at 4 AM but never had time to organize.
+
+1. A paper explaining in detail why cats are the true rulers of Earth
+2. A wikiHow article on repairing the International Space Station with chewing gum and paper clips
+3. A complete course syllabus for training jellyfish as underwater detectives
diff --git a/quests/tab/home/ThsisAFlieNmaeWthAloOfTpyosThatisRleayHdToRaed.txt b/quests/tab/en/home/ThsisAFlieNmaeWthAloOfTpyosThatisRleayHdToRaed.txt
similarity index 100%
rename from quests/tab/home/ThsisAFlieNmaeWthAloOfTpyosThatisRleayHdToRaed.txt
rename to quests/tab/en/home/ThsisAFlieNmaeWthAloOfTpyosThatisRleayHdToRaed.txt
diff --git a/quests/tab/en/tab.md b/quests/tab/en/tab.md
new file mode 100644
index 0000000..2e18e14
--- /dev/null
+++ b/quests/tab/en/tab.md
@@ -0,0 +1,95 @@
+# Using Tab Completion
+
+In this quest, you will learn:
+
+1. How to complete paths with Tab
+2. How to handle multiple matching names
+
+Click the button in the middle to start this quest.
+
+---
+
+In the terminal, you work by entering text.
+Typing takes time, and a single typo can make a command fail.
+Autocompletion helps you fill in file paths, command names, and supported options, saving time and avoiding typos.
+
+Before we start practicing, let's see which files are in your home folder.
+
+> Use `ls` to list the files
+
+---
+
+This folder is full of very long and misspelled filenames. Typing them character by character would be painful.
+Luckily, autocompletion makes even long filenames easy to enter.
+
+To use autocompletion, **type part of a filename, then press Tab**.
+(Tab is the key to the left of Q.)
+For example, type `Su` and press Tab to complete `SuperSecretPlansForWorldDomination.txt`. The terminal fills in the rest for you.
+
+Let's display the contents of `SuperSecretPlansForWorldDomination.txt`.
+
+> Use `cat SuperSecretPlansForWorldDomination.txt` to display the file contents
+> Type `cat Su`, then press Tab to complete the filename. Press Enter to run the command
+
+---
+
+The terminal completed `SuperSecretPlansForWorldDomination.txt` for you, saving you from typing a long name and making mistakes.
+You can start with `S`, `Su`, `Sup`, `Super`, or even no characters at all.
+However, the fewer characters you enter, the more likely they are to match several files.
+We will practice handling multiple matches shortly.
+
+Autocompletion also helps with awkward spellings. Take `ThsisAFlieNmaeWthAloOfTpyosThatisRleayHdToRaed.txt`: anyone could make a mistake typing that!
+Use `cat` again to display the contents of `ThsisAFlieNmaeWthAloOfTpyosThatisRleayHdToRaed.txt`.
+
+> Enter `cat ThsisAFlieNmaeWthAloOfTpyosThatisRleayHdToRaed.txt` to display the file contents
+> Type `cat Thsi`, then press Tab to complete the filename
+> Try pressing Tab after entering fewer characters and see what happens
+
+---
+
+You have opened the file with the misspelled name.
+With autocompletion, typos, mixed capitalization, and random-looking names are much easier to handle.
+Let's practice again. This time, list the files inside `ThingsIWillOrganizeLaterWhenIHaveTime`.
+As before, type the first few characters, then press Tab.
+
+> Enter `ls ThingsIWillOrganizeLaterWhenIHaveTime` to list the files inside the folder
+> Type `ls Thing`, then press Tab to complete the folder name
+
+---
+
+This folder contains three files: a guide to making sandwiches and two lists of things downloaded in the middle of the night.
+The sandwich guide, `HowToProperlyMakeASandwich.txt`, is inside another folder, so one press of Tab will not complete the whole path.
+This time, let's practice **completing a path in steps**.
+
+Type the start of the folder name and press Tab. Then type the start of the filename and press Tab again to complete `folder/filename`.
+When you complete a folder name, the terminal adds `/` at the end for you.
+Let's open `HowToProperlyMakeASandwich.txt`.
+
+> Enter `cat ThingsIWillOrganizeLaterWhenIHaveTime/HowToProperlyMakeASandwich.txt` to display the file contents
+> Type `cat Thin`, then press Tab to complete it to `cat ThingsIWillOrganizeLaterWhenIHaveTime/`
+> Next, type `How` after the folder path and press Tab to complete the full path
+
+---
+
+You have read this very strict guide to making a sandwich.
+Two files remain: `RandomStuffThatIDownloadedAt3AM.txt` and `RandomStuffThatIDownloadedAt4AM.txt`.
+Their names are almost identical. They differ only at `3AM` and `4AM` before the `.txt` extension.
+If you only type the first few characters, the terminal cannot tell which file you want.
+This is a case of **multiple matches**.
+
+You can handle multiple matches in steps, just as you did with the folder path.
+Type `Ran` and press Tab. The terminal completes the shared part of both names: `RandomStuffThatIDownloadedAt`.
+Then type `3` or `4`, depending on which file you want, and press Tab again to complete the rest.
+
+_Completing a full path also takes steps: after completing the folder name, you still need to choose a file inside it._
+
+> Enter `cat ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt3AM.txt` to display the file contents
+> Type `cat Thin`, then press Tab to complete it to `cat ThingsIWillOrganizeLaterWhenIHaveTime/`
+> Next, type `Ran` and press Tab to complete it to `cat ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt`
+> Then type `3` and press Tab to complete the full path.
+
+---
+
+You have read a list of things downloaded at 3 AM. Hmm... that is enough internet for today.
+Here is a final challenge: enter `ThingsIWillOrganizeLaterWhenIHaveTime`, then display the contents of `RandomStuffThatIDownloadedAt4AM.txt`.
+Use the autocompletion skills you learned today to complete the task.
diff --git a/quests/tab/game.yml b/quests/tab/zh-TW/game.yml
similarity index 100%
rename from quests/tab/game.yml
rename to quests/tab/zh-TW/game.yml
diff --git a/quests/tab/home/SuperSecretPlansForWorldDomination.txt b/quests/tab/zh-TW/home/SuperSecretPlansForWorldDomination.txt
similarity index 100%
rename from quests/tab/home/SuperSecretPlansForWorldDomination.txt
rename to quests/tab/zh-TW/home/SuperSecretPlansForWorldDomination.txt
diff --git a/quests/tab/home/ThingsIWillOrganizeLaterWhenIHaveTime/HowToProperlyMakeASandwich.txt b/quests/tab/zh-TW/home/ThingsIWillOrganizeLaterWhenIHaveTime/HowToProperlyMakeASandwich.txt
similarity index 100%
rename from quests/tab/home/ThingsIWillOrganizeLaterWhenIHaveTime/HowToProperlyMakeASandwich.txt
rename to quests/tab/zh-TW/home/ThingsIWillOrganizeLaterWhenIHaveTime/HowToProperlyMakeASandwich.txt
diff --git a/quests/tab/home/ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt3AM.txt b/quests/tab/zh-TW/home/ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt3AM.txt
similarity index 100%
rename from quests/tab/home/ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt3AM.txt
rename to quests/tab/zh-TW/home/ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt3AM.txt
diff --git a/quests/tab/home/ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt4AM.txt b/quests/tab/zh-TW/home/ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt4AM.txt
similarity index 100%
rename from quests/tab/home/ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt4AM.txt
rename to quests/tab/zh-TW/home/ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt4AM.txt
diff --git a/quests/tab/zh-TW/home/ThsisAFlieNmaeWthAloOfTpyosThatisRleayHdToRaed.txt b/quests/tab/zh-TW/home/ThsisAFlieNmaeWthAloOfTpyosThatisRleayHdToRaed.txt
new file mode 100644
index 0000000..f9aa76c
--- /dev/null
+++ b/quests/tab/zh-TW/home/ThsisAFlieNmaeWthAloOfTpyosThatisRleayHdToRaed.txt
@@ -0,0 +1,3 @@
+Congrats! You fond a flie namd by somone who cleerly had a strok. If you can raed this, you mght be a geenius or jsut realy gud at Wheel of Fortnue.
+
+Remeber: freinds don't let freinds name files aftr drinkng too much. But hey, at lest you used tab compltion to get here!
diff --git a/quests/tab/tab.md b/quests/tab/zh-TW/tab.md
similarity index 100%
rename from quests/tab/tab.md
rename to quests/tab/zh-TW/tab.md
diff --git a/server/src/api/controllers/commandController.ts b/server/src/api/controllers/commandController.ts
index c49e80a..aeab0a3 100644
--- a/server/src/api/controllers/commandController.ts
+++ b/server/src/api/controllers/commandController.ts
@@ -54,7 +54,7 @@ export const newCommand = asyncHandler(async (req: Request, res: Response) => {
session.lastActivityAt = new Date()
- const quest = await questManager.get(session.quest)
+ const quest = await questManager.get(session.quest, session.locale)
if (!quest) {
res.status(404).json({ message: 'quest not found' })
return
diff --git a/server/src/containers/docker.ts b/server/src/containers/docker.ts
index ef4b84b..a3b03a4 100644
--- a/server/src/containers/docker.ts
+++ b/server/src/containers/docker.ts
@@ -1,5 +1,6 @@
import fs from 'fs/promises'
import Docker from 'dockerode'
+import type { QuestLocale } from '../../../packages/constants'
import config, { getQuestImage } from '../config.js'
import logger from '../utils/logger.js'
@@ -36,19 +37,20 @@ const newContainerOptions = (
export async function createContainer(
name: string,
questId: string,
+ locale: QuestLocale,
imageId: string
): Promise {
const binds: string[] = []
- if (await questHomeExists(questId)) {
+ if (await questHomeExists(questId, locale)) {
logger.info('Mounting quest home', questId)
binds.push(
- `${config.docker.hostProjectRoot}/quests/${questId}/home:/etc/skel:ro`
+ `${config.docker.hostProjectRoot}/quests/${questId}/${locale}/home:/etc/skel:ro`
)
}
if (config.docker.mountQuest && imageId !== 'base') {
logger.info('Mounting quest directory', questId)
binds.push(
- `${config.docker.hostProjectRoot}/quests/${questId}/home:/home/commander`
+ `${config.docker.hostProjectRoot}/quests/${questId}/${locale}/home:/home/commander`
)
}
if (config.docker.mountCLI) {
@@ -75,9 +77,11 @@ async function createNetworkIfNotExists(network: string) {
}
}
-async function questHomeExists(imageId: string) {
+async function questHomeExists(imageId: string, locale: QuestLocale) {
try {
- const stat = await fs.stat(`${config.projectRoot}/quests/${imageId}/home`)
+ const stat = await fs.stat(
+ `${config.projectRoot}/quests/${imageId}/${locale}/home`
+ )
return stat.isDirectory()
} catch {
return false
diff --git a/server/src/models/quest.ts b/server/src/models/quest.ts
index fa5f71d..cab531a 100644
--- a/server/src/models/quest.ts
+++ b/server/src/models/quest.ts
@@ -3,6 +3,8 @@ import fs from 'fs/promises'
import path from 'path'
import yaml from 'yaml'
+import type { QuestLocale } from '../../../packages/constants'
+import { SUPPORTED_LOCALES } from '../../../packages/constants'
import {
questSchema,
globalExceptionSchema,
@@ -36,16 +38,22 @@ class QuestValidationError extends Error {
}
}
+function questImageId(id: string, locale: QuestLocale) {
+ return `${id}-${locale.toLowerCase()}`
+}
+
class QuestManager {
- private quests = new Map()
+ private quests = new Map>()
private questDirectory = path.join(config.projectRoot, 'quests')
- get(id: string) {
- return this.quests.get(id)
+ get(id: string, locale: QuestLocale) {
+ return this.quests.get(id)?.get(locale)
}
- getAll() {
+ getAll(locale: QuestLocale) {
return Array.from(this.quests.values())
+ .map((versions) => versions.get(locale))
+ .filter((quest): quest is IQuest => quest !== undefined)
}
private async mapQuests(callback: QuestCallback) {
@@ -83,42 +91,119 @@ class QuestManager {
async loadAndUpdateQuests() {
const exceptions = await this.loadGlobalExceptions()
- return this.mapQuests(async (id, questPath) => {
- const files = await fs.readdir(questPath)
- if (!files.includes('game.yml')) {
- throw new QuestValidationError(`Missing game.yml`, id, null)
+ const loaded = new Map>()
+
+ await this.mapQuests(async (id, questPath) => {
+ const entries = await fs.readdir(questPath, { withFileTypes: true })
+ const versions = new Map()
+
+ for (const locale of SUPPORTED_LOCALES) {
+ const localeEntry = entries.find((entry) => entry.name === locale)
+
+ if (!localeEntry) {
+ logger.warn(`Missing locale "${locale}" for quest "${id}"`)
+ // eslint-disable-next-line no-continue
+ continue
+ }
+
+ if (!localeEntry.isDirectory()) {
+ throw new QuestValidationError(
+ `${locale} exists but is not a directory`,
+ id,
+ null
+ )
+ }
+
+ const localePath = path.join(questPath, locale)
+
+ try {
+ const files = await fs.readdir(localePath)
+
+ if (!files.includes('game.yml')) {
+ throw new QuestValidationError(
+ `Missing ${locale}/game.yml`,
+ id,
+ null
+ )
+ }
+
+ const body = await fs.readFile(
+ path.join(localePath, 'game.yml'),
+ 'utf-8'
+ )
+
+ const questData = yaml.parse(body, {
+ merge: true,
+ })
+
+ const image = files.includes('Dockerfile')
+ ? questImageId(id, locale)
+ : 'base'
+ const quest = questSchema.parse({
+ id,
+ image,
+ ...questData,
+ })
+ quest.exceptions = [...exceptions]
+ versions.set(locale, quest)
+ } catch (error) {
+ logger.error(`Error parsing quest ${id} for locale ${locale}`, {
+ error,
+ })
+ throw new QuestValidationError(
+ `Error parsing quest ${id} for locale ${locale}`,
+ id,
+ error
+ )
+ }
}
- try {
- const body = await fs.readFile(
- path.join(questPath, 'game.yml'),
- 'utf-8'
- )
-
- const questData = yaml.parse(body, {
- merge: true,
- })
-
- const image = files.includes('Dockerfile') ? id : 'base'
- const quest = questSchema.parse({
- id,
- image,
- ...questData,
- })
- quest.exceptions = [...exceptions]
- this.quests.set(id, quest)
- } catch (error) {
- logger.error(`Error parsing quest ${id}`, { error })
- throw new QuestValidationError(`Error parsing quest ${id}`, id, error)
+ if (versions.size > 0) {
+ loaded.set(id, versions)
+ } else {
+ logger.warn(`No valid locales found for quest "${id}"`)
}
})
+
+ this.quests = loaded
}
async getQuestDockerfiles() {
const questlist = await this.mapQuests(async (id, questPath) => {
- const files = await fs.readdir(questPath)
- return files.includes('Dockerfile') ? { id, questPath } : null
+ const entries = await fs.readdir(questPath, { withFileTypes: true })
+ const dockerfiles: { id: string; questPath: string }[] = []
+
+ for (const locale of SUPPORTED_LOCALES) {
+ const localeEntry = entries.find((entry) => entry.name === locale)
+
+ if (!localeEntry) {
+ logger.warn(`Missing locale "${locale}" for quest "${id}"`)
+ // eslint-disable-next-line no-continue
+ continue
+ }
+
+ if (!localeEntry.isDirectory()) {
+ throw new QuestValidationError(
+ `${locale} exists but is not a directory`,
+ id,
+ null
+ )
+ }
+
+ const localePath = path.join(questPath, locale)
+ const files = await fs.readdir(localePath)
+
+ if (files.includes('Dockerfile')) {
+ dockerfiles.push({
+ id: questImageId(id, locale),
+ questPath: localePath,
+ })
+ }
+ }
+
+ return dockerfiles.length > 0 ? dockerfiles : null
})
+
return questlist.filter((quest) => quest !== null)
}
}
diff --git a/server/src/models/sessionManager.ts b/server/src/models/sessionManager.ts
index 56ca590..c58266e 100644
--- a/server/src/models/sessionManager.ts
+++ b/server/src/models/sessionManager.ts
@@ -1,4 +1,6 @@
import { HydratedDocument } from 'mongoose'
+import type { QuestLocale } from '../../../packages/constants'
+import { LOCALE_NAME_MAP } from '../../../packages/constants'
import { Session, UserProfile } from '../../../packages/models'
import type { ISession, IUser } from '../../../packages/models'
import {
@@ -34,11 +36,12 @@ async function deactivateSessions(userId: string, quest: string) {
export async function createNewSession(
userId: string,
- questId: string
+ questId: string,
+ locale: QuestLocale
): Promise {
- const quest = await questManager.get(questId)
+ const quest = await questManager.get(questId, locale)
if (!quest) {
- throw new Error(`Quest ${questId} not found`)
+ throw new Error(`Quest ${questId} not found for ${LOCALE_NAME_MAP[locale]}`)
}
// deactivate all active sessions
@@ -55,12 +58,18 @@ export async function createNewSession(
const containerName = `quest-${quest.id}-${userProfile.user.username}-${Date.now()}`
- const container = await createContainer(containerName, quest.id, quest.image)
+ const container = await createContainer(
+ containerName,
+ quest.id,
+ locale,
+ quest.image
+ )
await getAndStartContainer(container.id)
const newSession = new Session({
user: userId,
quest: quest.id,
+ locale,
containerId: container.id,
containerName,
})
@@ -117,15 +126,16 @@ export async function finishSession(
export async function isQuestUnlocked(
userId: string,
- questId: string
+ questId: string,
+ locale: QuestLocale
): Promise {
const userProfile = await UserProfile.findOne({ user: userId })
if (!userProfile) {
throw new Error(`UserProfile ${userId} not found`)
}
- const quest = await questManager.get(questId)
+ const quest = await questManager.get(questId, locale)
if (!quest) {
- throw new Error(`Quest ${questId} not found`)
+ throw new Error(`Quest ${questId} not found for ${LOCALE_NAME_MAP[locale]}`)
}
if (!quest.requirements) {
return true
diff --git a/server/src/routers/questRouter.ts b/server/src/routers/questRouter.ts
index 7d00fdf..fc84084 100644
--- a/server/src/routers/questRouter.ts
+++ b/server/src/routers/questRouter.ts
@@ -2,26 +2,34 @@ import { TRPCError } from '@trpc/server'
import { z } from 'zod'
import { router, publicProcedure } from '../trpc.js'
import { questManager } from '../models/quest.js'
+import { SUPPORTED_LOCALES } from '../../../packages/constants'
export const questRouter = router({
- getQuests: publicProcedure.query(async () => {
- const quests = questManager.getAll()
- return quests.map(({ title, id, requirements }) => ({
- id,
- title,
- requirements,
- }))
- }),
- getQuestDetail: publicProcedure.input(z.string()).query(async (opts) => {
- const questId = opts.input
- const quest = questManager.get(questId)
- if (!quest) {
- throw new TRPCError({
- code: 'NOT_FOUND',
- message: 'Quest not found.',
- })
- }
- const { id, title, instruction, requirements } = quest
- return { id, title, instruction, requirements }
- }),
+ getQuests: publicProcedure
+ .input(z.object({ locale: z.enum(SUPPORTED_LOCALES) }))
+ .query(async ({ input }) => {
+ const quests = questManager.getAll(input.locale)
+
+ return quests.map(({ title, id, requirements }) => ({
+ id,
+ title,
+ requirements,
+ }))
+ }),
+
+ getQuestDetail: publicProcedure
+ .input(z.object({ questId: z.string(), locale: z.enum(SUPPORTED_LOCALES) }))
+ .query(async ({ input }) => {
+ const { questId, locale } = input
+ const quest = questManager.get(questId, locale)
+
+ if (!quest) {
+ throw new TRPCError({
+ code: 'NOT_FOUND',
+ message: 'Quest not found.',
+ })
+ }
+ const { id, title, instruction, requirements } = quest
+ return { id, title, instruction, requirements }
+ }),
})
diff --git a/server/src/routers/sessionRouter.ts b/server/src/routers/sessionRouter.ts
index 3c5ecbf..0226597 100644
--- a/server/src/routers/sessionRouter.ts
+++ b/server/src/routers/sessionRouter.ts
@@ -1,5 +1,6 @@
import { TRPCError } from '@trpc/server'
import { z } from 'zod'
+import { SUPPORTED_LOCALES } from '../../../packages/constants'
import { ISession, Session } from '../../../packages/models'
import { GameSession, VoidFileExistenceChecker } from '../../../packages/game'
import { createNewSession, isQuestUnlocked } from '../models/sessionManager.js'
@@ -11,7 +12,7 @@ import { genJWT } from '../utils/auth'
export type SessionDetail = Awaited>
async function sessionDetail(session: ISession) {
- const quest = questManager.get(session.quest)
+ const quest = questManager.get(session.quest, session.locale)
if (!quest) {
throw new TRPCError({
code: 'NOT_FOUND',
@@ -31,6 +32,7 @@ async function sessionDetail(session: ISession) {
_id: session._id.toString(),
user: session.user.toString(),
quest: session.quest,
+ locale: session.locale,
status: session.status,
createdAt: session.createdAt.toISOString(),
lastActivityAt: session.lastActivityAt.toISOString(),
@@ -45,12 +47,20 @@ async function sessionDetail(session: ISession) {
export const sessionRouter = router({
createSession: protectedProcedure
- .input(z.object({ questId: z.string() }))
+ .input(
+ z.object({
+ questId: z.string(),
+ locale: z.enum(SUPPORTED_LOCALES),
+ })
+ )
.mutation(async (opts) => {
- const { questId } = opts.input
+ const { questId, locale } = opts.input
const { user } = opts.ctx
- if ((await isQuestUnlocked(user.id, questId)) || config.testing.enabled) {
- const session = await createNewSession(user.id, questId)
+ if (
+ (await isQuestUnlocked(user.id, questId, locale)) ||
+ config.testing.enabled
+ ) {
+ const session = await createNewSession(user.id, questId, locale)
return sessionDetail(session)
}
throw new TRPCError({
diff --git a/services/analytics/package.json b/services/analytics/package.json
index 4542de4..6cf20dd 100644
--- a/services/analytics/package.json
+++ b/services/analytics/package.json
@@ -15,7 +15,8 @@
"express": "^4.20.0",
"express-basic-auth": "^1.2.1",
"express-handlebars": "^7.1.2",
- "mongoose": "^8.24.1"
+ "mongoose": "^8.24.1",
+ "zod": "^3.24.2"
},
"devDependencies": {
"@swc/core": "^1.4.2",