diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ad446d6..2d4feda 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -87,5 +87,5 @@ yarn add @mui/icons-material ## react-i18next ```shell -npm install react-i18next i18next --save +yarn add react-i18next i18next ``` diff --git a/craco.config.js b/craco.config.js index 22194c6..8ff8ac0 100644 --- a/craco.config.js +++ b/craco.config.js @@ -9,6 +9,7 @@ module.exports = { '@images': path.resolve(__dirname, 'src/assets/images'), '@components': path.resolve(__dirname, 'src/components'), '@contexts': path.resolve(__dirname, 'src/contexts'), + '@locales': path.resolve(__dirname, 'src/locales'), '@pages': path.resolve(__dirname, 'src/pages'), '@store': path.resolve(__dirname, 'src/store'), '@templates': path.resolve(__dirname, 'src/templates') diff --git a/package.json b/package.json index 456a894..c90f26e 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,10 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.3.0", "@testing-library/user-event": "^14.4.2", + "i18next": "^21.9.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-i18next": "^11.18.3", "react-scripts": "5.0.1", "recoil": "^0.7.4", "web-vitals": "^2.1.4" diff --git a/src/App.js b/src/App.js index 7628b3a..56b2d4f 100644 --- a/src/App.js +++ b/src/App.js @@ -4,7 +4,7 @@ import { ThemeProvider, createTheme } from "@mui/material/styles"; import CssBaseline from "@mui/material/CssBaseline"; import { modeState } from "@store/theme"; import { ColorModeContext } from "@contexts/theme"; -import Page from "@pages"; +import Page from "@pages/home"; function App() { const [mode, setMode] = useRecoilState(modeState); diff --git a/src/components/header/ChangeLanguage.jsx b/src/components/header/ChangeLanguage.jsx new file mode 100644 index 0000000..c9c4050 --- /dev/null +++ b/src/components/header/ChangeLanguage.jsx @@ -0,0 +1,36 @@ +import * as React from 'react'; +import { useRecoilState } from "recoil"; +import { useTranslation } from 'react-i18next'; +import MuiToggleButton from '@mui/material/ToggleButton'; +import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; +import { styled } from "@mui/material/styles"; +import { languageState } from "@store/theme"; + +const ToggleButton = styled(MuiToggleButton)({ + "&.Mui-selected, &.Mui-selected:hover": { + color: "white" + } +}); + +export default function ChangeLanguage() { + const { i18n } = useTranslation(); + const [language, setLanguage] = useRecoilState(languageState); + const handleChangeLanguage = (event, newLanguage) => { + if (newLanguage) { + i18n.changeLanguage(newLanguage); + setLanguage(newLanguage); + } + }; + + return ( + + EN + KO + + ); +} diff --git a/src/components/header/MoreButton.jsx b/src/components/header/MoreActions.jsx similarity index 85% rename from src/components/header/MoreButton.jsx rename to src/components/header/MoreActions.jsx index b993aba..d6850bc 100644 --- a/src/components/header/MoreButton.jsx +++ b/src/components/header/MoreActions.jsx @@ -1,7 +1,7 @@ import IconButton from "@mui/material/IconButton"; import MoreIcon from "@mui/icons-material/MoreVert"; -export default function MoreButton() { +export default function MoreActions() { return ( diff --git a/src/components/main/CharactersLength.jsx b/src/components/main/CharactersLength.jsx index 868cc46..b7a20a5 100644 --- a/src/components/main/CharactersLength.jsx +++ b/src/components/main/CharactersLength.jsx @@ -1,20 +1,22 @@ import * as React from "react"; +import { useTranslation } from "react-i18next"; import Typography from "@mui/material/Typography"; import Slider from "@mui/material/Slider"; export default function CharactersLength() { + const { t } = useTranslation(); + return ( - Characters Length: 0 - 9 + {t("characters length")}: 10 "Temperature range"} - // value={value} - // onChange={handleChange} + defaultValue={10} + aria-label="Small" valueLabelDisplay="auto" - // getAriaValueText={valuetext} - disableSwap + min={0} + max={20} /> ); diff --git a/src/components/main/CharactersUsed.jsx b/src/components/main/CharactersUsed.jsx index ae5ebf3..4a076a6 100644 --- a/src/components/main/CharactersUsed.jsx +++ b/src/components/main/CharactersUsed.jsx @@ -1,14 +1,17 @@ import * as React from "react"; +import { useTranslation } from "react-i18next"; import Typography from "@mui/material/Typography"; import FormGroup from "@mui/material/FormGroup"; import FormControlLabel from "@mui/material/FormControlLabel"; import Checkbox from "@mui/material/Checkbox"; export default function CharactersUsed() { + const { t } = useTranslation(); + return ( - Characters Used: + {t("characters used")}: - Input + {t("input")} - Output + {t("output")} diff --git a/src/components/main/RandomWords.jsx b/src/components/main/RandomWord.jsx similarity index 75% rename from src/components/main/RandomWords.jsx rename to src/components/main/RandomWord.jsx index 3d7ac0f..ed25243 100644 --- a/src/components/main/RandomWords.jsx +++ b/src/components/main/RandomWord.jsx @@ -1,15 +1,18 @@ import * as React from "react"; +import { useTranslation } from "react-i18next"; import Switch from "@mui/material/Switch"; import Divider from "@mui/material/Divider"; import CharactersUsed from "@components/main/CharactersUsed"; import CharactersLength from "@components/main/CharactersLength"; import StopWords from "@components/main/StopWords"; -export default function RandomWords() { +export default function RandomWord() { + const { t } = useTranslation(); + return ( - Random Words + {t("random word")} diff --git a/src/components/main/Settings.jsx b/src/components/main/Settings.jsx index e60d2ea..819b87a 100644 --- a/src/components/main/Settings.jsx +++ b/src/components/main/Settings.jsx @@ -1,42 +1,45 @@ import * as React from "react"; +import { useTranslation } from "react-i18next"; import Typography from "@mui/material/Typography"; import Grid from "@mui/material/Grid"; import TextField from "@mui/material/TextField"; import Switch from "@mui/material/Switch"; -import RandomWords from "@components/main/RandomWords"; +import RandomWord from "@components/main/RandomWord"; export default function Settings() { + const { t } = useTranslation(); + return ( - Customize Prefix and Suffix + {t("set additional string")} - Prefix + {t("prepend to string")} - + - Suffix + {t("append after string")} - + diff --git a/src/components/main/StopWords.jsx b/src/components/main/StopWords.jsx index 275be14..31c1bfc 100644 --- a/src/components/main/StopWords.jsx +++ b/src/components/main/StopWords.jsx @@ -1,20 +1,23 @@ import * as React from "react"; +import { useTranslation } from "react-i18next"; import Typography from "@mui/material/Typography"; import TextField from "@mui/material/TextField"; export default function StopWords() { + const { t } = useTranslation(); + return ( - Stop Words: + {t("stop words")}: ); diff --git a/src/index.js b/src/index.js index 2822138..35c3f3a 100644 --- a/src/index.js +++ b/src/index.js @@ -1,9 +1,10 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import '@css/App.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; import { RecoilRoot } from "recoil"; +import '@locales/i18n'; +import '@css/App.css'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json new file mode 100644 index 0000000..6d90bfc --- /dev/null +++ b/src/locales/en/translation.json @@ -0,0 +1,15 @@ +{ + "title": "Smart Shuffle Words", + "input": "Input", + "output": "Output", + "shuffle & copy": "Shuffle & Copy", + "set additional string": "Set additional string", + "prepend to string": "Prepend to string", + "append after string": "Append after string", + "random word": "Random Word", + "characters used": "Characters Used", + "characters length": "Characters Length", + "stop words": "Stop Words", + "please enter text": "Please enter text", + "seperated by comma": "Seperated by comma" +} diff --git a/src/locales/i18n.js b/src/locales/i18n.js new file mode 100644 index 0000000..0bdfc11 --- /dev/null +++ b/src/locales/i18n.js @@ -0,0 +1,36 @@ +import i18n from "i18next"; +import { initReactI18next } from "react-i18next"; +import translationEn from "@locales/en/translation"; +import translationKo from "@locales/ko/translation"; + +i18n + .use(initReactI18next) // passes i18n down to react-i18next + .init({ + // the translations + // (tip move them in a JSON file and import them, + // or even better, manage them via a UI: https://react.i18next.com/guides/multiple-translation-files#manage-your-translations-with-a-management-gui) + resources: { + en: { + translations: translationEn + }, + ko: { + translations: translationKo + } + }, + + lng: "ko", // if you're using a language detector, do not define the lng option + fallbackLng: "ko", + + ns: "translations", // string or array of namespaces to load + defaultNS: "translations", // default namespace used if not passed to the translation function + + // char to separate keys. If working with a flat JSON, + // it's recommended to set this to false. + keySeparator: false, // (default) '.' + + interpolation: { + escapeValue: false // react already safes from xss => https://www.i18next.com/translation-function/interpolation#unescape + } + }); + + export default i18n; diff --git a/src/locales/ko/translation.json b/src/locales/ko/translation.json new file mode 100644 index 0000000..c108990 --- /dev/null +++ b/src/locales/ko/translation.json @@ -0,0 +1,15 @@ +{ + "title": "스마트 셔플 워드", + "input": "상품명", + "output": "상품명 수정", + "shuffle & copy": "상품명 변경 후 자동 복사", + "set additional string": "추가 문자열 설정", + "prepend to string": "문자열 앞에 추가", + "append after string": "문자열 뒤에 추가", + "random word": "무작위 단어", + "characters used": "사용된 문자", + "characters length": "문자 길이", + "stop words": "금칙어", + "please enter text": "텍스트를 입력 하십시오", + "seperated by comma": "쉼표로 구분 합니다" +} diff --git a/src/pages/index.jsx b/src/pages/home.jsx similarity index 100% rename from src/pages/index.jsx rename to src/pages/home.jsx diff --git a/src/store/theme.js b/src/store/theme.js index b6ae863..464028a 100644 --- a/src/store/theme.js +++ b/src/store/theme.js @@ -2,5 +2,10 @@ import { atom } from "recoil"; export const modeState = atom({ key: "modeState", - default: 'light' + default: "light", +}); + +export const languageState = atom({ + key: "languageState", + default: "ko", }); diff --git a/src/templates/Header.jsx b/src/templates/Header.jsx index c50930e..07f3e92 100644 --- a/src/templates/Header.jsx +++ b/src/templates/Header.jsx @@ -1,18 +1,23 @@ +import { useTranslation } from "react-i18next"; import AppBar from "@mui/material/AppBar"; import Toolbar from "@mui/material/Toolbar"; import Typography from "@mui/material/Typography"; import ToggleColorMode from "@components/header/ToggleColorMode"; -import MoreButton from '@components/header/MoreButton'; +import ChangeLanguage from "@components/header/ChangeLanguage"; +// import MoreActions from "@components/header/MoreActions"; export default function Header() { + const { t } = useTranslation(); + return ( - Smart Shuffle Words + {t("title")} - + + {/* */} ); diff --git a/yarn.lock b/yarn.lock index 1251dd3..ee024e5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1035,7 +1035,7 @@ core-js-pure "^3.20.2" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a" integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw== @@ -5495,6 +5495,13 @@ html-minifier-terser@^6.0.2: relateurl "^0.2.7" terser "^5.10.0" +html-parse-stringify@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2" + integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg== + dependencies: + void-elements "3.1.0" + html-webpack-plugin@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz#c3911936f57681c1f9f4d8b68c158cd9dfe52f50" @@ -5618,6 +5625,13 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" +i18next@^21.9.0: + version "21.9.0" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.9.0.tgz#b63ebb0d4e1b23709951ca4774dc19d2ffac9553" + integrity sha512-B+6/yd7rCpJidyPuBaEApUECx7G8Ai6+tqYhrChsY4MmQqJhG7qJ4eT6Lm1OnRhieVelEtfxh4aAQktdNVZtDA== + dependencies: + "@babel/runtime" "^7.17.2" + iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -8585,6 +8599,14 @@ react-error-overlay@^6.0.11: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== +react-i18next@^11.18.3: + version "11.18.3" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.18.3.tgz#50211810bcc9fdea2d70c8aefdfff5f1eb39a923" + integrity sha512-EttTX31HbqzZymUM3SIrMPuvamfSXFZVsDHm/ZAqoDfTLjhzlwyxqfbDNxcKNAGOi2mjZaXfR7hSNMlvLNpB/g== + dependencies: + "@babel/runtime" "^7.14.5" + html-parse-stringify "^3.0.1" + react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -10101,6 +10123,11 @@ vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== +void-elements@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" + integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== + w3c-hr-time@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"