diff --git a/src/App.scss b/src/App.scss index a4c9552..4d8b7f7 100644 --- a/src/App.scss +++ b/src/App.scss @@ -20,7 +20,7 @@ & > .helpButton { display: grid; - grid-template-columns: 20px 1fr; + grid-template-columns: 1fr; align-items: center; gap: 5px; padding: 10px 15px; diff --git a/src/App.tsx b/src/App.tsx index 3943e4b..1495c83 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,12 +3,13 @@ // --------------------------------------------------------------------------------------------------------------------- // ------------------------------------------------------ React -------------------------------------------------------- -import { useState } from 'react'; +import { useEffect, useState } from 'react'; // --------------------------------------------------------------------------------------------------------------------- // ----------------------------------------------------- Context ------------------------------------------------------- import { useRecoilState } from 'recoil'; import { gameStateAtom } from './contexts/gameState'; +import { langAtom } from './contexts/langState'; // --------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------- Components ------------------------------------------------------ @@ -20,47 +21,83 @@ import Help from './components/Help/Help'; import LeaderBoard from './components/Leaderboard/Leaderboard'; // --------------------------------------------------------------------------------------------------------------------- +// ------------------------------------------------------ Langs -------------------------------------------------------- +import frConf from './langs/fr.json'; +import enConf from './langs/en.json'; +// --------------------------------------------------------------------------------------------------------------------- + // ----------------------------------------------------- Styles -------------------------------------------------------- import Logo from './assets/logo'; -import Question from './assets/question'; import './App.scss'; +import LangToggler from './components/LangToggler/LangToggler'; // --------------------------------------------------------------------------------------------------------------------- const App = () => { const [helpDisplayed, displayHelp] = useState(false); const [gameState] = useRecoilState(gameStateAtom); + const [lang, setLang] = useRecoilState(langAtom); - return ( -
- {gameState.status === 'settings' && } - {(gameState.status === 'idle' || gameState.status === 'playing') && ( - <> -
- - - -
- - - {helpDisplayed && ( - { - displayHelp(false); - }} - /> - )} - - )} - {gameState.status === 'board' && } -
- ); + useEffect(() => { + const storedLang = localStorage.getItem('lang'); + + if (storedLang === null) { + localStorage.setItem('lang', 'fr'); + setLang({ + key: 'fr', + config: frConf, + }); + return; + } + + if (storedLang === 'fr') { + setLang({ + key: 'fr', + config: frConf, + }); + return; + } + + setLang({ + key: 'en', + config: enConf, + }); + }, [lang.key]); + + if (lang.config) { + return ( +
+ + {gameState.status === 'settings' && } + {(gameState.status === 'idle' || gameState.status === 'playing') && ( + <> +
+ + + +
+ + + {helpDisplayed && ( + { + displayHelp(false); + }} + /> + )} + + )} + {gameState.status === 'board' && } +
+ ); + } + + return <>; }; export default App; diff --git a/src/assets/en.png b/src/assets/en.png new file mode 100644 index 0000000..9670356 Binary files /dev/null and b/src/assets/en.png differ diff --git a/src/assets/fr.png b/src/assets/fr.png new file mode 100644 index 0000000..2ed2cc7 Binary files /dev/null and b/src/assets/fr.png differ diff --git a/src/assets/question.tsx b/src/assets/question.tsx deleted file mode 100644 index b558b4a..0000000 --- a/src/assets/question.tsx +++ /dev/null @@ -1,26 +0,0 @@ -const Question = (props: any) => { - return ( - - - - - ); -}; - -export default Question; diff --git a/src/components/Grid/Grid.tsx b/src/components/Grid/Grid.tsx index 6195b38..123fa75 100644 --- a/src/components/Grid/Grid.tsx +++ b/src/components/Grid/Grid.tsx @@ -9,6 +9,7 @@ import { useState } from 'react'; // ----------------------------------------------------- Context ------------------------------------------------------- import { useRecoilState } from 'recoil'; import { gameStateAtom } from '../../contexts/gameState'; +import { langAtom } from '../../contexts/langState'; // --------------------------------------------------------------------------------------------------------------------- // ------------------------------------------------------ Hooks -------------------------------------------------------- @@ -29,6 +30,8 @@ const Grid = () => { const [gameState, setGameState] = useRecoilState(gameStateAtom); const [grid, setGrid] = useState(genGrid(gameState.gridSize)); + const [lang] = useRecoilState(langAtom); + const { handleLeftClick, handleRightClick } = useActions(grid, setGrid, gameState, setGameState); return ( @@ -69,7 +72,7 @@ const Grid = () => { } as React.CSSProperties } > -

{gameState.endType === 'win' ? 'Gagné !' : 'Perdu !'}

+

{gameState.endType === 'win' ? lang.config.win : lang.config.loose}

diff --git a/src/components/LangToggler/LangToggler.tsx b/src/components/LangToggler/LangToggler.tsx new file mode 100644 index 0000000..a8a9245 --- /dev/null +++ b/src/components/LangToggler/LangToggler.tsx @@ -0,0 +1,45 @@ +import { useRecoilState } from 'recoil'; +import { langAtom } from '../../contexts/langState'; + +import fr from '../../assets/fr.png'; +import en from '../../assets/en.png'; +import './styles.scss'; + +const LangToggler = () => { + const [lang, setLang] = useRecoilState(langAtom); + + return ( + + ); +}; + +export default LangToggler; diff --git a/src/components/LangToggler/styles.scss b/src/components/LangToggler/styles.scss new file mode 100644 index 0000000..7fe7c57 --- /dev/null +++ b/src/components/LangToggler/styles.scss @@ -0,0 +1,23 @@ +.langToggler { + position: fixed; + top: 10px; + right: 10px; + display: flex; + justify-self: center; + align-items: center; + border: none; + border-radius: 5px; + cursor: pointer; + transition: scale 0.3s ease; + overflow: hidden; + + &:hover { + scale: 1.1; + } + + & > img { + width: 45px; + height: 25px; + object-fit: cover; + } +} diff --git a/src/components/Leaderboard/Leaderboard.tsx b/src/components/Leaderboard/Leaderboard.tsx index 4212dd4..1216684 100644 --- a/src/components/Leaderboard/Leaderboard.tsx +++ b/src/components/Leaderboard/Leaderboard.tsx @@ -10,6 +10,7 @@ import { useEffect, useState } from 'react'; import localforage from 'localforage'; import { useRecoilState } from 'recoil'; import { gameStateAtom } from '../../contexts/gameState'; +import { langAtom } from '../../contexts/langState'; // --------------------------------------------------------------------------------------------------------------------- // -------------------------------------------------- Utils & Types ---------------------------------------------------- @@ -40,6 +41,8 @@ const LeaderBoard = () => { const [gameState, setGameState] = useRecoilState(gameStateAtom); const [data, setData] = useState([]); + const [lang] = useRecoilState(langAtom); + const getData = async () => { const storeData = await store.getItem(`${gameState.difficulty}:${gameState.gridSize}`); @@ -63,26 +66,26 @@ const LeaderBoard = () => { return ( <> -

Time Leaderboard

+

{lang.config.leaderboardTitle}

#

- Date + {lang.config.date}

- Difficulté + {lang.config.settings.difficulty.label}

- Taille + {lang.config.settings.size.label}

- Time + {lang.config.time}

@@ -95,7 +98,7 @@ const LeaderBoard = () => { > {index + 1} {item.date} - {getDifficultyLabel(gameState.difficulty as Difficulty)} + {lang.config.settings.difficulty.values[gameState.difficulty]} {gameState.gridSize} {item.time}
@@ -110,7 +113,8 @@ const LeaderBoard = () => { onClick={() => setGameState((prev) => ({ ...prev, status: 'settings', endType: '', placedFlags: 0, bombs: 0, gameTime: '' }))} > - Paramètres + + {lang.config.settingsBtn} diff --git a/src/components/Leaderboard/styles.scss b/src/components/Leaderboard/styles.scss index 6475ae2..85c2b16 100644 --- a/src/components/Leaderboard/styles.scss +++ b/src/components/Leaderboard/styles.scss @@ -16,8 +16,8 @@ & > .header { display: grid; - grid-template-columns: 1em 1fr 1fr 0.7fr 1fr; - gap: 30px; + grid-template-columns: 1em 1fr 1fr 0.8fr 1fr; + gap: 20px; padding: 15px 20px; background-color: #333; @@ -26,12 +26,12 @@ flex-wrap: nowrap; align-items: center; gap: 10px; - font-size: 1.3em; + font-size: 1.1em; font-weight: 400; color: #fff; & > svg { - height: 1.3em; + height: 1.2em; } &:last-child { @@ -47,10 +47,10 @@ & > .score { display: grid; - grid-template-columns: 1em 1fr 1fr 0.7fr 1fr; + grid-template-columns: 1em 1fr 1fr 0.8fr 1fr; align-items: center; justify-content: center; - gap: 30px; + gap: 20px; padding: 10px 20px; background-color: #e1e1e1; color: #333; diff --git a/src/contexts/langState.ts b/src/contexts/langState.ts new file mode 100644 index 0000000..a768a6e --- /dev/null +++ b/src/contexts/langState.ts @@ -0,0 +1,9 @@ +import { atom } from 'recoil'; + +export const langAtom = atom({ + key: 'lang', + default: { + key: null as any, + config: null as any, + }, +}); diff --git a/src/langs/en.json b/src/langs/en.json new file mode 100644 index 0000000..68a66d1 --- /dev/null +++ b/src/langs/en.json @@ -0,0 +1,32 @@ +{ + "appTitle": "Minesweeper", + "settings": { + "size": { + "label": "Grid size", + "values": { + "small": "Small", + "medium": "Medium", + "large": "Large" + } + }, + "difficulty": { + "label": "Difficulty", + "values": { + "beginner": "Beginner", + "intermediate": "Intermediate", + "expert": "Expert" + } + } + }, + "start": "Start", + "how": "How to play?", + "win": "Win!", + "loose": "Loose!", + "leaderboardTitle": "Leaderboard", + "date": "Date", + "time": "Time", + "settingsBtn": "Settings", + "replay": "Replay", + "delete": "Delete", + "deleteAll": "Delete all" +} diff --git a/src/langs/fr.json b/src/langs/fr.json new file mode 100644 index 0000000..bc61c56 --- /dev/null +++ b/src/langs/fr.json @@ -0,0 +1,32 @@ +{ + "appTitle": "Démineur", + "settings": { + "size": { + "label": "Taille de grille", + "values": { + "small": "Petit", + "medium": "Moyen", + "large": "Grand" + } + }, + "difficulty": { + "label": "Difficulté", + "values": { + "beginner": "Facile", + "intermediate": "Intermédiaire", + "expert": "Difficile" + } + } + }, + "start": "Commencer", + "how": "Comment jouer ?", + "win": "Gagné !", + "loose": "Perdu !", + "leaderboardTitle": "Tableau des scores", + "date": "Date", + "time": "Temps", + "settingsBtn": "Paramètres", + "replay": "Rejouer", + "delete": "Supprimer", + "deleteAll": "Tout supprimer" +}