+
+
diff --git a/src/composables/use-i18n.ts b/src/composables/use-i18n.ts
new file mode 100644
index 0000000..99ff72f
--- /dev/null
+++ b/src/composables/use-i18n.ts
@@ -0,0 +1,60 @@
+import type { ShallowRef } from 'vue'
+import { type Language, getLanguage, setLanguage } from './use-local-cache'
+import zhCN from '@/locale/zh-CN.json'
+
+interface I18N {
+ lang: ShallowRef
+ setLanguage: (language: Language) => void
+ $t: (key: string, fallback?: string) => string
+}
+
+export const i18NInjectionKey = Symbol('i18n') as InjectionKey
+
+function loadLanguage(lang: Language) {
+ return import.meta.glob(`@/locale/*.json`)[`/src/locale/${lang}.json`]()
+}
+
+export function useI18N() {
+ const lang = shallowRef('zh-CN')
+
+ const messages = ref>>({
+ 'zh-CN': zhCN,
+ 'en-US': null,
+ 'ja-JP': null,
+ })
+
+ const msg = computed(() => messages.value[lang.value] ?? messages.value['zh-CN'])
+
+ const _setLang = (language: Language) => {
+ lang.value = language
+ setLanguage(language)
+
+ if (!messages.value[language]) {
+ loadLanguage(language).then((res: any) => {
+ messages.value[language] = res.default as Record
+ })
+ }
+ }
+
+ getLanguage().then((val) => {
+ if (val) {
+ _setLang(val)
+ }
+ })
+
+ const $t = (key: string, fallback: string = ''): string => {
+ return msg.value?.[key] ?? fallback
+ }
+
+ provide(i18NInjectionKey, {
+ lang,
+ setLanguage: _setLang,
+ $t,
+ })
+
+ return {
+ lang,
+ setLanguage: _setLang,
+ $t,
+ }
+}
diff --git a/src/composables/use-local-cache.ts b/src/composables/use-local-cache.ts
index dfc48a8..3169294 100644
--- a/src/composables/use-local-cache.ts
+++ b/src/composables/use-local-cache.ts
@@ -15,10 +15,14 @@ export interface RecordItem {
endTime: string
}
+export type Language = 'zh-CN' | 'en-US' | 'ja-JP'
+
export const RECORD_KEY = 'record'
const HIGHEST_SCORE_KEY = 'highestScore.'
+const LANGUAGE_KEY = 'memoryBlockLanguage'
+
// 获取最高分
export function getHighestScoreInHistory(level: GameLevel) {
return localforage.getItem(HIGHEST_SCORE_KEY + level, v => v ?? 0)
@@ -46,3 +50,11 @@ export async function getAllRecordsFromStore() {
return _records
}
+
+// 获取语言
+export const getLanguage = () => localforage.getItem(LANGUAGE_KEY, v => v ?? 'zh-CN')
+
+// 设置语言
+export function setLanguage(lang: Language = 'zh-CN') {
+ localforage.setItem(LANGUAGE_KEY, lang)
+}
diff --git a/src/config/game.ts b/src/config/game.ts
index 436a279..b5656b9 100644
--- a/src/config/game.ts
+++ b/src/config/game.ts
@@ -1,9 +1,9 @@
export const GAME_LEVELS = {
- easy: { code: 'easy', type: 'default', en: 'Easy Level', zh: '简单难度', path: '/game/easy' },
- normal: { code: 'normal', type: 'primary', en: 'Normal Level', zh: '中等难度', path: '/game/normal' },
- master: { code: 'master', type: 'warning', en: 'Master Level', zh: '困难难度', path: '/game/master' },
- expert: { code: 'expert', type: 'danger', en: 'Expert Level', zh: '专家难度', path: '/game/expert' },
- custom: { code: 'custom', type: 'custom', en: 'Practice Mode', zh: '练习模式', path: '/settings/custom' },
+ easy: { code: 'easy', type: 'default', en: 'Easy Level', zh: '简单难度', ja: '簡単なレベル', path: '/game/easy' },
+ normal: { code: 'normal', type: 'primary', en: 'Normal Level', zh: '中等难度', ja: '中レベル', path: '/game/normal' },
+ master: { code: 'master', type: 'warning', en: 'Master Level', zh: '困难难度', ja: 'マスターレベル', path: '/game/master' },
+ expert: { code: 'expert', type: 'danger', en: 'Expert Level', zh: '专家难度', ja: 'エキスパートレベル', path: '/game/expert' },
+ custom: { code: 'custom', type: 'custom', en: 'Practice Mode', zh: '练习模式', ja: '練習モード', path: '/settings/custom' },
} as const
export const LEVEL_GRIDS = {
@@ -61,3 +61,9 @@ export const LEVEL_GRIDS = {
export type GameLevel = {
[K in keyof typeof GAME_LEVELS]: typeof GAME_LEVELS[K]['code'];
}[keyof typeof GAME_LEVELS]
+
+export const languages = [
+ { label: '中文', value: 'zh-CN' },
+ { label: 'English', value: 'en-US' },
+ { label: '日本語', value: 'ja-JP' },
+]
diff --git a/src/locale/en-US.json b/src/locale/en-US.json
new file mode 100644
index 0000000..223bd5f
--- /dev/null
+++ b/src/locale/en-US.json
@@ -0,0 +1,28 @@
+
+{
+ "memory-block": "Memory block",
+ "custom-levels": "Custom levels",
+ "number-of-grids": "Number of grids",
+ "minimum-blocks": "Minimum number of generated blocks",
+ "maximum-blocks": "Maximum number of generated blocks",
+ "hp": "HP",
+ "second": "second",
+ "setup-completed": "Setup completed, start the game",
+
+ "score": "Score",
+ "start-time": "Start time",
+ "end-time": "Ends time",
+ "using-time": "Using time",
+
+ "game-over": "Game over",
+ "start": "Start",
+ "again": "Again",
+ "clear": "Clear",
+ "selected": "Selected",
+ "continue": "Continue",
+
+ "memory-time": "Memory time before the start of each round",
+ "configuration-integer-gt": "Configuration can only be an integer greater than 1",
+ "select-one-first": "Please select at least one block first",
+ "remember-block-locations": "Please remember the following block locations"
+}
diff --git a/src/locale/ja-JP.json b/src/locale/ja-JP.json
new file mode 100644
index 0000000..a838942
--- /dev/null
+++ b/src/locale/ja-JP.json
@@ -0,0 +1,27 @@
+{
+ "memory-block": "メモリ ブロック",
+ "custom-levels": "カスタム レベル",
+ "number-of-grids": "グリッドの数",
+ "minimum-blocks": "生成されるブロックの最小数",
+ "maximum-blocks": "生成されるブロックの最大数",
+ "hp": "HP",
+ "second": "秒",
+ "setup-completed": "セットアップが完了しました。ゲームを開始します",
+
+ "score": "スコア",
+ "start-time": "開始時刻",
+ "end-time": "終了時刻",
+ "using-time": "使用時間",
+
+ "game-over": "ゲームオーバー",
+ "start": "ゲーム開始",
+ "again": "また",
+ "clear": "選択をクリアします",
+ "selected": "選択済み",
+ "continue": "継続",
+
+ "memory-time": "各ラウンド開始前の記憶時間",
+ "configuration-integer-gt": "構成には 1 より大きい整数のみを指定できます",
+ "select-one-first": "最初に少なくとも 1 つのブロックを選択してください",
+ "remember-block-locations": "次のブロックの場所を覚えておいてください"
+}
diff --git a/src/locale/zh-CN.json b/src/locale/zh-CN.json
new file mode 100644
index 0000000..d33b205
--- /dev/null
+++ b/src/locale/zh-CN.json
@@ -0,0 +1,27 @@
+{
+ "memory-block": "记忆方块",
+ "custom-levels": "自定义关卡",
+ "number-of-grids": "网格数量",
+ "minimum-blocks": "最小生成方块数",
+ "maximum-blocks": "最大生成方块数",
+ "hp": "生命值",
+ "second": "秒",
+ "setup-completed": "设置完成,开始游戏",
+
+ "score": "得分",
+ "start-time": "开始于",
+ "end-time": "结束于",
+ "using-time": "用时",
+
+ "game-over": "游戏结束",
+ "start": "游戏开始",
+ "again": "再来一次",
+ "clear": "清空选中",
+ "selected": "选好了",
+ "continue": "继续",
+
+ "memory-time": "每回合开始前的记忆时间",
+ "configuration-integer-gt": "配置只能为大于 1 的整数",
+ "select-one-first": "请先选择至少一个方块",
+ "remember-block-locations": "请记住以下方块位置"
+}
diff --git a/src/views/game/[level].vue b/src/views/game/[level].vue
index c57b605..940d62e 100644
--- a/src/views/game/[level].vue
+++ b/src/views/game/[level].vue
@@ -7,9 +7,12 @@ import { useGameStatus } from '@/composables/use-game-status'
import { useGameScore } from '@/composables/use-game-score'
import { useCheckedBlocks } from '@/composables/use-checked-blocks'
import { setHighestScoreInHistory } from '@/composables/use-local-cache'
+import { i18NInjectionKey } from '@/composables/use-i18n'
type GameStatus = 'over' | 'pause' | 'playing' | 'previewing'
+const { $t } = inject(i18NInjectionKey)!
+
const _gameState = shallowRef('previewing')
const gameStatus = computed(() => ({
@@ -111,7 +114,7 @@ function onCheckResult() {
const { matched, blocks } = getAllCheckedResult()
if (!matched && !blocks.length) {
- useToast('请先选择至少一个方块')
+ useToast($t('select-one-first', '请先选择至少一个方块'))
return
}
@@ -158,7 +161,7 @@ function gameOver() {
markAllWrongBlocks()
setGameStatus('over')
- useToastError('游戏结束')
+ useToastError($t('game-over', '游戏结束'))
// 如果分数比历史最高分高, 更新历史最高分, 并播放纸屑
if (gameScore.value > highestScore.value) {
@@ -289,7 +292,7 @@ onBeforeUnmount(() => {
- {{ gameStatus.previewing ? '请记住以下方块位置' : gameStatus.over ? '游戏结束' : '游戏开始' }}
+ {{ gameStatus.previewing ? $t('remember-block-locations', '请记住以下方块位置') : gameStatus.over ? $t('game-over', '游戏结束') : $t('start', '游戏开始') }}
({{ countdown }})
@@ -357,17 +360,16 @@ onBeforeUnmount(() => {
:disabled="gameStatus.previewing || gameStatus.pause"
@click="onResetBlocks"
>
- {{ gameStatus.over ? '再来一次' : '清空选中' }}
+ {{ gameStatus.over ? $t('again', '再来一次') : $t('clear', '清空选中') }}
diff --git a/src/views/index.vue b/src/views/index.vue
index b61e14e..caf6542 100644
--- a/src/views/index.vue
+++ b/src/views/index.vue
@@ -1,18 +1,21 @@