diff --git a/package-lock.json b/package-lock.json index ab0ac8f..0d6e73b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "icons-karuta", - "version": "0.3.0", + "version": "0.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "icons-karuta", - "version": "0.3.0", + "version": "0.4.0", "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", @@ -15,15 +15,23 @@ "@mui/material": "^5.15.16", "@next/third-parties": "^14.2.0", "@vercel/analytics": "^1.2.2", + "i18next": "^23.11.3", + "i18next-resources-to-backend": "^1.2.1", + "negotiator": "^0.6.3", "next": "14.1.4", + "next-i18next": "^15.3.0", "react": "^18", "react-dom": "^18", - "simple-icons": "^11.14.0" + "react-i18next": "^14.1.1", + "simple-icons": "^11.14.0", + "string-format": "^2.0.0" }, "devDependencies": { + "@types/negotiator": "^0.6.3", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "@types/string-format": "^2.0.3", "eslint": "^8", "eslint-config-next": "14.1.4", "typescript": "^5" @@ -961,12 +969,27 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", + "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@types/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-JkXTOdKs5MF086b/pt8C3+yVp3iDUwG635L7oCH6HvJvvr6lSUU5oe/gLXnPEfYRROHjJIPgCV6cuAg8gGkntQ==", + "dev": true + }, "node_modules/@types/node": { "version": "20.12.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.5.tgz", @@ -1012,6 +1035,12 @@ "@types/react": "*" } }, + "node_modules/@types/string-format": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/string-format/-/string-format-2.0.3.tgz", + "integrity": "sha512-2BuDXYaQvSZR5XHlLJQ3KQ455NW+C1Mok0uOO6q8bnp97V/gtZbDEYy44kmu5Ng1U8wRtCSF/leREYVmH2vYmQ==", + "dev": true + }, "node_modules/@typescript-eslint/parser": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", @@ -1605,6 +1634,16 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, + "node_modules/core-js": { + "version": "3.37.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.0.tgz", + "integrity": "sha512-fu5vHevQ8ZG4og+LXug8ulUtVxjOcEYvifJr7L5Bfq9GOztVqsKd9/59hUk2ZSbCrS3BqUr3EpaYGIYzq7g3Ug==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -2867,6 +2906,49 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/i18next": { + "version": "23.11.3", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.3.tgz", + "integrity": "sha512-Pq/aSKowir7JM0rj+Wa23Kb6KKDUGno/HjG+wRQu0PxoTbpQ4N89MAT0rFGvXmLkRLNMb1BbBOKGozl01dabzg==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/i18next-fs-backend": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/i18next-fs-backend/-/i18next-fs-backend-2.3.1.tgz", + "integrity": "sha512-tvfXskmG/9o+TJ5Fxu54sSO5OkY6d+uMn+K6JiUGLJrwxAVfer+8V3nU8jq3ts9Pe5lXJv4b1N7foIjJ8Iy2Gg==" + }, + "node_modules/i18next-resources-to-backend": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/i18next-resources-to-backend/-/i18next-resources-to-backend-1.2.1.tgz", + "integrity": "sha512-okHbVA+HZ7n1/76MsfhPqDou0fptl2dAlhRDu2ideXloRRduzHsqDOznJBef+R3DFZnbvWoBW+KxJ7fnFjd6Yw==", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -3566,6 +3648,14 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/next": { "version": "14.1.4", "resolved": "https://registry.npmjs.org/next/-/next-14.1.4.tgz", @@ -3611,6 +3701,41 @@ } } }, + "node_modules/next-i18next": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/next-i18next/-/next-i18next-15.3.0.tgz", + "integrity": "sha512-bq7Cc9XJFcmGOCLnyEtHaeJ3+JJNsI/8Pkj9BaHAnhm4sZ9vNNC4ZsaqYnlRZ7VH5ypSo73fEqLK935jLsmCvQ==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + }, + { + "type": "individual", + "url": "https://locize.com" + } + ], + "dependencies": { + "@babel/runtime": "^7.23.2", + "@types/hoist-non-react-statics": "^3.3.4", + "core-js": "^3", + "hoist-non-react-statics": "^3.3.2", + "i18next-fs-backend": "^2.3.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "i18next": ">= 23.7.13", + "next": ">= 12.0.0", + "react": ">= 17.0.2", + "react-i18next": ">= 13.5.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4004,6 +4129,27 @@ "react": "^18.2.0" } }, + "node_modules/react-i18next": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-14.1.1.tgz", + "integrity": "sha512-QSiKw+ihzJ/CIeIYWrarCmXJUySHDwQr5y8uaNIkbxoGRm/5DukkxZs+RPla79IKyyDPzC/DRlgQCABHtrQuQQ==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -4372,6 +4518,11 @@ "node": ">=10.0.0" } }, + "node_modules/string-format": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string-format/-/string-format-2.0.0.tgz", + "integrity": "sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA==" + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -4817,6 +4968,14 @@ "punycode": "^2.1.0" } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 2cd8934..1854931 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "icons-karuta", - "version": "0.3.0", + "version": "0.4.0", "private": true, "scripts": { "dev": "next dev", @@ -16,15 +16,23 @@ "@mui/material": "^5.15.16", "@next/third-parties": "^14.2.0", "@vercel/analytics": "^1.2.2", + "i18next": "^23.11.3", + "i18next-resources-to-backend": "^1.2.1", + "negotiator": "^0.6.3", "next": "14.1.4", + "next-i18next": "^15.3.0", "react": "^18", "react-dom": "^18", - "simple-icons": "^11.14.0" + "react-i18next": "^14.1.1", + "simple-icons": "^11.14.0", + "string-format": "^2.0.0" }, "devDependencies": { + "@types/negotiator": "^0.6.3", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "@types/string-format": "^2.0.3", "eslint": "^8", "eslint-config-next": "14.1.4", "typescript": "^5" diff --git a/src/app/games/Infinite/page.tsx b/src/app/[lang]/games/infinite/page.tsx similarity index 83% rename from src/app/games/Infinite/page.tsx rename to src/app/[lang]/games/infinite/page.tsx index d18cba3..2f6136b 100644 --- a/src/app/games/Infinite/page.tsx +++ b/src/app/[lang]/games/infinite/page.tsx @@ -6,9 +6,13 @@ import { randomInt } from "@/utils/commonUtil"; import GameUI from "@/components/GameUI"; import { Button, Stack, Typography } from "@mui/material"; import { fetchSlugs, randomIcons } from "@/utils/iconUtil"; +import { useTranslation } from '@/i18n/client'; let iconSlugList: IconData[]; -const Infinite = () => { +const Infinite = ({ params }: { params: { lang: string } }) => { + const lang = params.lang; + const { t } = useTranslation(lang); + const [correctIcon, setCorrectIcon] = useState(); const [iconList, setIconList] = useState([]); const [totalAttention, setTotalAttention] = useState(0); @@ -34,13 +38,13 @@ const Infinite = () => { Infinite Game - Total number of touches: {totalAttention} + {t("game:total-touches")} : {totalAttention}
- ); diff --git a/src/app/games/NCard/page.tsx b/src/app/[lang]/games/ncard/page.tsx similarity index 79% rename from src/app/games/NCard/page.tsx rename to src/app/[lang]/games/ncard/page.tsx index 05bc5b7..f7fd335 100644 --- a/src/app/games/NCard/page.tsx +++ b/src/app/[lang]/games/ncard/page.tsx @@ -8,16 +8,21 @@ import { fetchSlugs, randomIcons } from "@/utils/iconUtil"; import { Box, Button, Stack, Typography } from "@mui/material"; import Image from "next/image"; import Link from "next/link"; +import { useTranslation } from "@/i18n/client"; +import Format from "string-format"; const numList = [12, 24, 36]; let iconSlugList: IconData[]; -const Random = ({ params, searchParams }: { params: { num: string }, searchParams: { [key: string]: string } }) => { +const Random = ({ params, searchParams }: { params: { num: string, lang: string }, searchParams: { [key: string]: string } }) => { const [correctIcon, setCorrectIcon] = useState(); const [iconList, setIconList] = useState([]); const [totalAttention, setTotalAttention] = useState(0); const [gameEnd, setGameEnd] = useState(false); const [score, setScore] = useState(0); + + const lang = params.lang; + const { t } = useTranslation(lang); let num: number = parseInt(params.num) || parseInt(searchParams.num); if (num === undefined || (!numList.includes(num))) { num = 12; @@ -43,7 +48,7 @@ const Random = ({ params, searchParams }: { params: { num: string }, searchParam setTotalAttention(_totalAttention); if (_iconList.length === 0) { setGameEnd(true); - alert(`Total number of touches: ${_totalAttention}\nScore: ${_score}`); + alert(Format(t("game:finish-message"), _totalAttention.toString(), _score.toString())); } } @@ -54,10 +59,10 @@ const Random = ({ params, searchParams }: { params: { num: string }, searchParam {num} Cards Game - Total number of touches: {totalAttention} + {t("game:total-touches")} : {totalAttention} - Score: {score} + {t("game:score")}: {score} @@ -66,15 +71,15 @@ const Random = ({ params, searchParams }: { params: { num: string }, searchParam {/** SNS share */} - Share your score : + {t("game:share")} - + x - - + + } diff --git a/src/app/layout.tsx b/src/app/[lang]/layout.tsx similarity index 73% rename from src/app/layout.tsx rename to src/app/[lang]/layout.tsx index 747c6f7..0f4bee8 100644 --- a/src/app/layout.tsx +++ b/src/app/[lang]/layout.tsx @@ -1,9 +1,11 @@ +import { dir } from 'i18next'; import Footer from "@/components/Footer"; import Header from "@/components/Header"; import { Analytics } from '@vercel/analytics/react'; import type { Metadata } from "next"; import { Inter } from "next/font/google"; import { GoogleAnalytics } from '@next/third-parties/google'; +import { LanguageProvider } from '@/i18n/client'; const inter = Inter({ subsets: ["latin"] }); @@ -21,16 +23,20 @@ export const metadata: Metadata = { export default function RootLayout({ children, + params: { lang } }: Readonly<{ children: React.ReactNode; + params: { lang: string }; }>) { return ( - + -
- {children} -