Skip to content

Commit

Permalink
Merge pull request #20 from VecHK/language
Browse files Browse the repository at this point in the history
Language
  • Loading branch information
VecHK authored Jun 22, 2021
2 parents fd66112 + 6451342 commit b189e89
Show file tree
Hide file tree
Showing 10 changed files with 328 additions and 51 deletions.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
{
"name": "vmsde",
"homepage": "http://vec.moe/vms/",
"version": "1.0.1",
"version": "1.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"dayjs": "^1.10.5",
"i18next": "^20.3.2",
"i18next-browser-languagedetector": "^6.1.2",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-hook-form": "^7.9.0",
"react-i18next": "^11.11.0",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.1",
"web-vitals": "^0.2.4"
Expand Down
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ReactNode, useState } from 'react'
import { HashRouter as Router, Redirect, useLocation } from 'react-router-dom'

import './i18n'

import './App.css'

import LayoutToggle from './components/LayoutToggle'
Expand Down
138 changes: 138 additions & 0 deletions src/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import LanguageDetector from 'i18next-browser-languagedetector'

const resources = {
en: {
translation: {
PlayingTips: 'Please click "<1></1>"',
'胜败乃兵家常事,大侠请重新来过': 'YOU LOSE',
无序连败早该管管了: '😥',
'✌️你赢了': '✌️YOU WIN',
'✌️赢麻了': 'WIN WIN WIN WIN WIN',
'✌️四赢': '✌️WIN 4 times',
'✌️三赢': '✌️WIN 3 times',
'✌️双赢': '✌️WIN twice',
再来一把: 'Replay',
关于: 'About',
宽度: 'Width',
高度: 'Height',
雷数: 'Bomb number',

难度设定: 'Diffculty',
简单: 'EASY',
谁都能玩的程度: 'anyone',

困难: 'HARD',
需要点功夫: 'need patience',

大佬: 'PROFESSIONAL',
能玩完这关才好意思说自己会玩扫雷: 'Minesweeper expert',

自定义: 'CUSTOM',
骨灰级玩家最爱的: 'to Fanatic',

其它的一些设定: 'Other setting',
边缘不放置地雷: 'no mines on edge',
可以避免这种情况: 'can skip this case',
保存并返回: 'save & back',
},
},

'zh-Hant': {
translation: {
PlayingTips: '點擊<1></1>就完事了',
'胜败乃兵家常事,大侠请重新来过': '勝敗乃兵家常事,大俠請重新來過',
无序连败早该管管了: '無序連敗早該管管了',
'✌️你赢了': '✌️你贏了',
'✌️赢麻了': '✌️贏麻了',
'✌️四赢': '✌️四贏',
'✌️三赢': '✌️三贏',
'✌️双赢': '✌️雙贏',
再来一把: '再來一局',
关于: '關於',
宽度: '寬度',
高度: '高度',
雷数: '雷數',

难度设定: '難度設定',
简单: '簡單',
谁都能玩的程度: '誰都能玩的程度',

困难: '困難',
需要点功夫: '需要點功夫',

大佬: '高手',
能玩完这关才好意思说自己会玩扫雷: '能玩完這關才好意思說自己會玩掃雷',

自定义: '自定義',
骨灰级玩家最爱的: '骨灰級玩家最愛的',

其它的一些设定: '其它的一些設定',
边缘不放置地雷: '邊緣不放置地雷',
可以避免这种情况: '可以避免這種情況',
保存并返回: '保存並返回',
},
},

ja: {
translation: {
PlayingTips: '<1></1>をクリックしてください',
'胜败乃兵家常事,大侠请重新来过': '失敗した',
无序连败早该管管了: '満身創痍',
'✌️你赢了': '勝ちだ!',
'✌️赢麻了': '凄い!連続勝利5回以上',
'✌️四赢': '四回勝ち',
'✌️三赢': '三回勝ち',
'✌️双赢': '二回勝ち',
再来一把: 'もう一回',
关于: 'このゲームについて',
宽度: '宽度',
高度: '高度',
雷数: '地雷数',

难度设定: '難易度調整',
简单: '簡単',
谁都能玩的程度: '誰でも遊べるのレベル',

困难: '困難',
需要点功夫: 'ある努力が必要、ちょっと難しいのレベル',

大佬: '達者',
能玩完这关才好意思说自己会玩扫雷:
'このレベルをクリアしたら、”マインスイーパ分かる”と言うのはできるでしょ',

自定义: 'カスタム',
骨灰级玩家最爱的: 'マインスイーパ マニアのあなたへ',

其它的一些设定: '他の設定',
边缘不放置地雷: '边缘のマスが地雷放置禁止',
可以避免这种情况: 'こういう状況が回避できる',
保存并返回: 'セーブして戻る',
},
},
}

export default i18n
.use(initReactI18next)
.use(LanguageDetector)
.init({
resources,

detection: {
order: [
// 'querystring',
// 'cookie',
'localStorage',
// 'sessionStorage',
'navigator',
// 'htmlTag',
// 'path',
// 'subdomain',
],
},

interpolation: {
escapeValue: false,
},
})
25 changes: 25 additions & 0 deletions src/layouts/home/components/GameLanguage/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.language-select {
cursor: pointer;
user-select: none;
}

.lang-icon:last-child {
border-right: 0;
}

.lang-icon {
display: inline-flex;
justify-content: center;
align-content: center;
align-items: center;
padding: 0 4px;
border-right: solid 1px grey;
width: 28px;
font-size: 16px;
height: 28px;
}

.lang-icon.current {
color: hsl(185, 6%, 25%);
font-weight: bolder;
}
55 changes: 55 additions & 0 deletions src/layouts/home/components/GameLanguage/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import './index.css'

function initLangCode() {
let currentLangCode: string = localStorage.i18nextLng || 'zh-CN'
if (currentLangCode === 'dev') {
currentLangCode = 'zh-cn'
}
return currentLangCode
}

export default () => {
const [currentLangCode, setCurrentLangCode] = useState(initLangCode())

const langMap: Array<[string, RegExp, string]> = [
['汉', /(zh-hans)|(zh-cn)|(zh-sg)|(zh-my)/i, 'zh-hans'],
['漢', /(zh-hant)|(zh-tw)|(zh-hk)|(zh-mo)/i, 'zh-hant'],
['あ', /ja/i, 'ja'],
['EN', /en/i, 'en'],
]

const { i18n } = useTranslation()

const selectedIdx = langMap.findIndex(([langIcon, exp]) => {
return exp.test(currentLangCode)
})

const toggleLanguage = (currentIdx: number = selectedIdx) => {
const nextLang = langMap[currentIdx + 1]
if (nextLang) {
setCurrentLangCode(nextLang[2])
i18n.changeLanguage(nextLang[2])
} else {
toggleLanguage(-1)
}
}

return (
<div className="language-select" onClick={() => toggleLanguage()}>
{langMap.map(([langIcon], idx) => {
const isCurrent = selectedIdx === idx

return (
<span
key={langIcon}
className={`lang-icon ${isCurrent ? 'current' : ''}`}
>
{langIcon}
</span>
)
})}
</div>
)
}
60 changes: 34 additions & 26 deletions src/layouts/home/components/GameStatus/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,36 @@ import { VMS, VMSStatus } from 'src/vms-logic'
import GameCell from '../GameCell'
import './index.css'

import { Trans, useTranslation } from 'react-i18next'

function PlayingTips() {
const cellNode = useMemo(
() => (
<GameCell
cell={{
id: 9,
isBomb: false,
mark: 'NONE',
neighborNumber: 0,
isOpen: false,
}}
/>
),
[]
)

return <Trans i18nKey="PlayingTips">点击{cellNode}就完事了</Trans>
}

export type GameStatusProps = {
vms: VMS
status: VMSStatus

onClickReplay: () => void
}
export function GameStatus({ status, onClickReplay }: GameStatusProps) {
const { t } = useTranslation()

const [winCount, setWinCount] = useState(0)
const [loseCount, setLoseCount] = useState(0)

Expand All @@ -29,50 +52,35 @@ export function GameStatus({ status, onClickReplay }: GameStatusProps) {
onClickReplay()
}}
>
再来一把
{t('再来一把')}
</Button>
)
}, [onClickReplay, status])
}, [onClickReplay, status, t])

return (
<div className="game-status">
{useMemo<ReactNode>(() => {
if (status === 'PLAYING') {
// setLock(true)
return (
<>
点击
<GameCell
cell={{
id: 9,
isBomb: false,
mark: 'NONE',
neighborNumber: 0,
isOpen: false,
}}
/>
就完事了
</>
)
return <PlayingTips />
}

let labelText = '✌️你赢了'
let labelText = t('✌️你赢了')

if (status === 'WIN') {
if (winCount > 3) {
labelText = '✌️赢麻了'
labelText = t('✌️赢麻了')
} else if (winCount > 2) {
labelText = '✌️四赢'
labelText = t('✌️四赢')
} else if (winCount > 1) {
labelText = '✌️三赢'
labelText = t('✌️三赢')
} else if (winCount > 0) {
labelText = '✌️双赢'
labelText = t('✌️双赢')
}
} else if (status === 'LOSE') {
labelText = `胜败乃兵家常事,大侠请重新来过`
labelText = t(`胜败乃兵家常事,大侠请重新来过`)

if (loseCount > 1) {
labelText = '无序连败早该管管了!'
labelText = t(`无序连败早该管管了`)
}
}

Expand All @@ -82,7 +90,7 @@ export function GameStatus({ status, onClickReplay }: GameStatusProps) {
{replayButton}
</div>
)
}, [loseCount, replayButton, status, winCount])}
}, [loseCount, replayButton, status, t, winCount])}
</div>
)
}
9 changes: 7 additions & 2 deletions src/layouts/home/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,13 @@
.game-frame {}
.setting-enter {
position: absolute;
left: 0;
top: 0;
left: 1em;
top: 1em;
}
.language-wrapper {
position: absolute;
right: 1em;
top: 1em;
}

.game-map {
Expand Down
4 changes: 4 additions & 0 deletions src/layouts/home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import GameMap from './components/GameMap'
import { GameStatus } from './components/GameStatus'
import GameTimer from './components/GameTimer'
import Cell from 'src/components/Cell'
import GameLanguage from './components/GameLanguage'
import { diff2WHB, loadConfig } from 'src/config'

// const WIDTH = 10
Expand Down Expand Up @@ -82,6 +83,9 @@ export default () => {
}}
/>
</div>
<div className="language-wrapper">
<GameLanguage />
</div>
</div>
)
}
Loading

0 comments on commit b189e89

Please sign in to comment.