diff --git a/package.json b/package.json index d045e93..7d1e31e 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,12 @@ "antd": "^4.20.1", "axios": "^0.27.2", "eslint-plugin-react-hooks": "^4.5.0-next-bd4784c8f-20220425", + "i18next": "^21.8.8", "lodash": "^4.17.21", "moment": "^2.29.3", "react": "^18.0.0", "react-dom": "^18.0.0", + "react-i18next": "^11.17.0", "react-json-formatter": "^0.2.1", "react-redux": "^8.0.1", "react-router-dom": "6", diff --git a/src/i18n/index.js b/src/i18n/index.js new file mode 100644 index 0000000..4dd8804 --- /dev/null +++ b/src/i18n/index.js @@ -0,0 +1,29 @@ +import i18n from 'i18next' +import { initReactI18next } from 'react-i18next' +import { button, form, validate, message } from './ko' + +// the translations +// (tip move them in a JSON file and import them, +// or even better, manage them separated from your code: https://react.i18next.com/guides/multiple-translation-files) +const resources = { + ko: { + translation: { + button, + form, + validate, + message + } + } +} + +i18n + .use(initReactI18next) // passes i18n down to react-i18next + .init({ + resources, + lng: 'ko', + interpolation: { + escapeValue: false // react already safes from xss + } + }) + +export default i18n diff --git a/src/i18n/ko/button.json b/src/i18n/ko/button.json new file mode 100644 index 0000000..a5ef887 --- /dev/null +++ b/src/i18n/ko/button.json @@ -0,0 +1,5 @@ +{ + "login": { + "submit": "로그인" + } +} diff --git a/src/i18n/ko/form.json b/src/i18n/ko/form.json new file mode 100644 index 0000000..336b090 --- /dev/null +++ b/src/i18n/ko/form.json @@ -0,0 +1,11 @@ +{ + "label": { + "login": { + "save_id": "아이디 저장", + "find_id": "아이디 찾기", + "find_pw": "비밀번호 찾기", + "id": "아이디", + "pw": "비밀번호" + } + } +} diff --git a/src/i18n/ko/index.js b/src/i18n/ko/index.js new file mode 100644 index 0000000..6ae57a2 --- /dev/null +++ b/src/i18n/ko/index.js @@ -0,0 +1,6 @@ +import button from './button.json' +import form from './form.json' +import validate from './validate.json' +import message from './message.json' + +export { button, form, validate, message } diff --git a/src/i18n/ko/message.json b/src/i18n/ko/message.json new file mode 100644 index 0000000..85e3ed1 --- /dev/null +++ b/src/i18n/ko/message.json @@ -0,0 +1,5 @@ +{ + "login": { + "success": "정상적으로 로그인 되었습니다." + } +} diff --git a/src/i18n/ko/validate.json b/src/i18n/ko/validate.json new file mode 100644 index 0000000..7f4a37f --- /dev/null +++ b/src/i18n/ko/validate.json @@ -0,0 +1,6 @@ +{ + "login": { + "validate_login_id": "아이디를 입력해주세요.", + "validate_login_pw": "비밀번호를 입력해주세요." + } +} diff --git a/src/index.js b/src/index.js index 2a24034..0edb24a 100644 --- a/src/index.js +++ b/src/index.js @@ -3,6 +3,7 @@ import ReactDOM from 'react-dom/client' import { BrowserRouter } from 'react-router-dom' import 'antd/dist/antd.min.css' import './styles/index.scss' +import './i18n' import App from './views/App' import { Provider } from 'react-redux' diff --git a/src/styles/scss/_login.scss b/src/styles/scss/_login.scss index c4074b7..1f56f57 100644 --- a/src/styles/scss/_login.scss +++ b/src/styles/scss/_login.scss @@ -3,7 +3,7 @@ } .login-wrapper { - margin-top: 200px; + margin-top: 190px; max-height: 560px; .ant-input-prefix { diff --git a/src/views/login/Login.js b/src/views/login/Login.js index 14cbd65..51c0e9e 100644 --- a/src/views/login/Login.js +++ b/src/views/login/Login.js @@ -17,6 +17,7 @@ import { authUser, userAccount } from '../../store/actions/user_action' import { SET_LOADING } from '../../store/modules/app' import { useNavigate } from 'react-router-dom' import { notificationSuccess } from '../../components/notification/Notification' +import { useTranslation } from 'react-i18next' import _ from 'lodash' import banner1 from '../../assets/images/login/user_banner_01.png' @@ -24,6 +25,7 @@ import banner2 from '../../assets/images/login/user_banner_02.png' import banner3 from '../../assets/images/login/user_banner_03.png' const Login = () => { + const { t } = useTranslation() const dispatch = useDispatch() const navigate = useNavigate() const { isAuthentication } = useSelector((state) => ({ @@ -42,13 +44,13 @@ const Login = () => { username: [ { required: true, - message: '아이디를 입력해주세요.' + message: t('validate.login.validate_login_id') } ], password: [ { required: true, - message: '비밀번호를 입력해주세요.' + message: t('validate.login.validate_login_pw') } ] } @@ -72,7 +74,7 @@ const Login = () => { navigate('/dashboard', { replace: true }) dispatch(SET_LOADING(false)) notificationSuccess({ - description: '정상적으로 로그인 되었습니다.' + description: t('message.login.success') }) }, 600) } @@ -126,7 +128,7 @@ const Login = () => { }} onFinish={onFinish} > - + { @@ -160,17 +162,21 @@ const Login = () => { className="login-form-button" size="large" > - 로그인 + {t('button.login.submit')} - 아이디 저장 + {t('form.label.login.save_id')} }> - 아이디 찾기 - 비밀번호 찾기 + + {t('form.label.login.find_id')} + + + {t('form.label.login.find_pw')} + diff --git a/yarn.lock b/yarn.lock index e1eb4f6..b265ff7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1058,6 +1058,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.14.5", "@babel/runtime@^7.17.2": + version "7.18.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.3.tgz#c7b654b57f6f63cf7f8b418ac9ca04408c4579f4" + integrity sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.16.7", "@babel/template@^7.3.3": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" @@ -4855,7 +4862,7 @@ html-entities@^2.1.0, html-entities@^2.3.2: resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46" integrity sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA== -html-escaper@^2.0.0: +html-escaper@^2.0.0, html-escaper@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== @@ -4873,6 +4880,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" @@ -4967,6 +4981,13 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +i18next@^21.8.8: + version "21.8.8" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.8.8.tgz#725a05f2529689d059bce17221cf86fcdccf7665" + integrity sha512-iN/5JuWStyivyBgmUgy5BRiFs0lZrgCRaeV9q4yVH/eR9NID7pZSMt3rpF8C16GplchoEjDP0JalKwZMJ1CJAA== + dependencies: + "@babel/runtime" "^7.17.2" + iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -7866,6 +7887,15 @@ react-error-overlay@^6.0.10: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6" integrity sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA== +react-i18next@^11.17.0: + version "11.17.0" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.17.0.tgz#44a0689dac7903352733e40303b743fa465eb797" + integrity sha512-ewq2S4bVUTRqOMAdM/XvzCn9xUPIryzeBQRghmJ8lC6VI/8Kp7z1GwoLyt8j7GB2ywhN2SjPk7LU4sHzVeu7aw== + dependencies: + "@babel/runtime" "^7.14.5" + html-escaper "^2.0.2" + html-parse-stringify "^3.0.1" + react-is@^16.12.0, 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" @@ -9250,6 +9280,11 @@ vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= +void-elements@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" + integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk= + 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"