
๋ธ๋ก๊ทธ์ ๊ธฐ๋ณธ ์ปจํ ์ธ ๋ ์์ด๋ก ์์ฑํ๋ค. ์ด๋ ฅ์ ๊ฐ์ด ์๊ธฐ PR๋ฅผ ํด์ผ ํ๋ ๊ธ์ ์ธ ๋๋ ์์ด๊ฐ ๋์์ ๊ทธ๋๋ก ๋์์ง๋ง, ์์ธ์์ ๊ตฌ์งํ๋์ ํ ๊ฒ์ธ๋ฐ ์์ด๋ก๋ง ๋๋ ๊ฒ๋ ์๋๋ค ์ถ์ด ์๊ฐ๋ ๊น์ ์ด์ ์ธ์ด ์ค์ ๊ธฐ๋ฅ์ ๋ฃ์ด๋์๋ค.
๊ตญ์ ํ(i18n) ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋
Internationalization์ ๋งจ ์์, ๋งจ ๋์๋ฅผ ๋ด ์ฉ์ด์ด๋ค. i18next๋ ์๋ฐ์คํฌ๋ฆฝํธ์์ ์ฌ์ฉํ ์ ์๋ ๊ตญ์ ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค. ์ด๋ฒ์๋ react-i18next ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ค.
์ธ์ด ์ ํ UI ๋ง๋ค๊ธฐ


๋ฉ์ธ ๋ฉ๋ด์ ์ฌ์ด๋ ๋ฉ๋ด ๋ ๊ตฐ๋ฐ์ ์ธ์ด ์ค์ ์ ์ํด Grommet์์ ์ ๊ณตํ๋ ์ ๋ ํธ ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ๋ค. Nav ์ปดํฌ๋ํธ ์์ ์์น์ํค๊ธฐ์ ๋๋ฌด ๋ณต์กํ ๊ฒ ๊ฐ์์ ์ผ๋จ์ ์ด๋ ๊ฒ ํด๋์๋๋ฐ, Collapsableํ side menu์์์ ์ ๋ ํธ ์์น๋ ์ข์๋ฐ ๋ฉ์ธ ํฌ์คํธ ์๋จ์ ์์นํ๋๋ก ํ ๊ฑด ์ ๋ง์์ ์ ๋ค์ด์ dark mode๋ผ๋ ์ง ๋ค๋ฅธ ๊ธฐ๋ฅ์ด ๋ค์ด๊ฐ๊ฒ ๋๋ฉด ๊ธฐ๋ฅ๋ณ๋ก ๋ณ๋์ ์ฌ์ด๋ ๋ฉ๋ด๋ฅผ ๋ ๊พธ๋ฆฌ๋ ๊ฒ ๋์๋ณด์ธ๋ค.
์ด์จ๋ ์ธ์ด ์ค์ ์ ์ํ UI๋ฅผ ๋ง๋ ๋ค. Github ํ ์์ค ์ฝ๋๋ ์๋์์ ๋ณผ ์ ์๋ค.
hannah26hannah/hannah26hannah.github.io
๐ฏ Dev Blog + Portfolio . Contribute to hannah26hannah/hannah26hannah.github.io development by creating an account on GitHub.
github.com
import { Select } from 'grommet'; | |
import React, { useState } from 'react'; | |
import i18next from 'i18next'; | |
function changeLang(lang) { | |
i18next.changeLanguage(lang); | |
} | |
const LangSelect = () => { | |
const options = [ | |
{ label: 'ํ๊ตญ์ด', value: 'ko' }, | |
{ label: 'English', value: 'en'} | |
]; | |
return ( | |
<Select | |
options={options} | |
placeholder='Language' | |
labelKey='label' | |
valueKey='value' | |
onChange={({ option }) => { | |
changeLang(option.value); | |
}} | |
alignSelf='end' | |
size='small' | |
style={{ | |
width: '150px', | |
}} | |
></Select> | |
) | |
} | |
export default LangSelect; |
์ ๋ ํธ ์ปดํฌ๋ํธ๋ options ์์ฑ์ ๊ฐ์ง๋ค. options ๋ฐฐ์ด์ ์ ์ธํด label๊ณผ value ํค๋ฅผ ๊ฐ์ง๋๋ก ํ๋ค. ๊ฐ๊ฐ์ labelKey์ valueKey ์์ฑ์ผ๋ก ๊ตฌ๋ถ์ง์ด์ค๋ค. ์ ๊ณต๋๋ onChange ์์ฑ์ options์ option์ forEach์ฒ๋ผ ๊ฐ ์ด๋ฒคํธ๋ฅผ ๊ฑธ ์ ์๋๋ก ๋์ด ์๋ค. changeLang ํจ์๋ฅผ ์จ์ ์ ํ๋ option์ผ๋ก ์ธ์ด๋ฅผ ์ค์ ํ๋ค. i18next๊ฐ ์ ๊ณตํ๋ changeLanguage ๋ฉ์๋๋ฅผ ์ฐ๋ฉด ๋๋ค. ko, en ๊ฐ์ ๋ฌธ์์ด์ ์ธ์๋ก ๋ฐ๋๋ค.
ํ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ค์ด๋ก๋ ๋ฐ ์ค์
๋ฆฌ์กํธ ํ๋ก์ ํธ์์ ์ฌ์ฉํ ์ ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ๋ฌ ๊ฐ ๊ฒํ ํด๋ณด๋ค๊ฐ, ์ด ๋ธ๋ก๊ทธ ๊ธ๊ณผ ๊ณต์ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํด react-i18next ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ ํํ๋ค.
yarn add react-i18next
// or
npm install react-i18next --save
yarn์ ํตํด ๋ค์ด ๋ฐ์์ค ํ, src ์๋์ config๋ผ๋ ํด๋๋ฅผ ์๋ก ์์ฑํ๋ค.

์ค์ ์ ์ํ i18n.js ํ์ผ, ๋ฒ์ญ ์ธ์ด ๊ฐ์ ๋ชจ์๋ json ํ์ผ์ ๊ฐ๊ฐ ๋ง๋ค์ด์ค๋ค.
import i18n from 'i18next'; | |
import LanguageDetector from 'i18next-browser-languagedetector'; // ์ฌ์ฉ์์ ์ธ์ด ์ค์ ์ ๊ธฐ์ตํ๊ธฐ ์ํด ํ์ํ ํ๋ฌ๊ทธ์ธ | |
import { initReactI18next } from 'react-i18next'; | |
import translationEn from './translation.en' | |
import translationKo from './translation.ko' | |
const resource = { | |
en : { | |
translation: translationEn | |
}, | |
ko : { | |
translation: translationKo | |
} | |
} | |
i18n | |
.use(LanguageDetector) | |
.use(initReactI18next) // passes i18n down to react-i18next | |
.init({ | |
resources: resource, | |
// lang: 'en', detector๋ฅผ ์ฐ๋ ค๋ฉด ์ด ๋ถ๋ถ์ ์ฐ์ง ์๋๋ค. | |
fallbackLng: 'en', | |
debug: true, | |
keySeparator: false, | |
interpolation: { | |
escapeValue: false // react already safes from xss | |
} | |
}); | |
export default i18n; | |
i18n์ import ํด์ฃผ๊ณ , initReactI18next๋ก import ํด์ค๋ค. ๋ง๋ค์ด๋ json ํ์ผ๋ค์ ๊ฐ๊ฐ ๊ฐ์ง๊ณ ์ค๊ณ , resource ๋ณ์๋ฅผ ์ ์ธํด ์ ์ฝ๋์ฒ๋ผ ์์ฑํด์ค๋ค. debug๋ฅผ true ๊ฐ์ผ๋ก ์ค์ ํด๋๋ฉด ๊ฐ๋ฐ ์์ ๋ฒ์ญ ๊ฐ์ด ์๊ฑฐ๋ ๋ฒ์ญ์ด๋ฅผ ๋ฐ๊ฟ ๋ update๋๋ ๋ด์ฉ์ ์ฝ์์ ์ฐ์ด์ค๋ค. ๊ธฐ๋ณธ ์ธ์ด๋ 'en'๋ก ํด์ฃผ์๋ค. (
i18next-browser-languagedetector์ ๋ํ ๋ด์ฉ์ ๋งจ ์๋ ๋ชฉ์ฐจ๋ฅผ ์ฐธ๊ณ )
์ด์ index.js๋ก ์์, ์ค์ ํ์ผ์ import './config/lang/i18n' ๋ก ๊ฐ์ ธ์์ค๋ค.
i18next ์ฌ์ฉํ๊ธฐ
๊ณต์ ํํ์ด์ง์ ์ค๋ช ์ด ์ ๋์ด ์๋๋ฐ, ๊ฒฝ์ฐ์ ๋ฐ๋ผ ํ์ํ ๋ฐฉ๋ฒ์ ์ทจํ๋ฉด ๋๋ค. useTranslation Hook์ ์ฐ๊ฑฐ๋, withTranslation HOC์ ์ฐ๊ฑฐ๋, Trans ์ปดํฌ๋ํธ๋ฅผ ์ฐ๋ ๋ฐฉ๋ฒ๋ ์๋ค.
withTranslation Hoc
์ฐ์ header์ธ App Bar์ ๋ฉ๋ด ์ด๋ฆ๋ค์ ๋ฒ์ญํด๋ณด์๋ค.
import { withTranslation } from 'react-i18next'; | |
import LangSelect from './MultiLang'; | |
// ... | |
const MenuNav = (props) => { | |
const t = prop.multi // prop์ผ๋ก ๋ฐ์ ๊ฐ์ t๋ก ๋ค์ ์ ์ธํด์ฃผ์๋ค. | |
return ( | |
// .. | |
<Anchor label={t('Contact')} /> | |
) | |
} | |
class Header extends Component { | |
constructor(props) { | |
// ... | |
} | |
render() { | |
const {t} = this.props; | |
return ( | |
// .. | |
<MenuNav multi={t} /> | |
) | |
} | |
} | |
export default withTranslation()(Header); // ์์์ import ํด์ค withTranslation์ ์ด๊ณณ์์ ์จ์ค๋ค. |
t๋ ๋ฒ์ญํ๊ณ ์ ํ๋ ์ธ์ด๋ฅผ ์ธ์๋ก ๋ฐ๋๋ค. string ํ์ ์ผ๋ก ํด์ฃผ์ด์ผ ํ๋ค. ์ฌ๊ธฐ์ Contact๋ json ํ์ผ์ ์ผ๋ Key๊ฐ ๋๋ค.


๋จ์ํ 1:1๋ก ๋งค์นญ๋๋ ํ ์คํธ๋ผ๋ฉด ์ด ๋ฐฉ๋ฒ์ผ๋ก ์ถฉ๋ถํ๋ฐ, ๊ธด ๊ธ์ ๋ํด์๋ ์ด๋ป๊ฒ ํ ๊น ๊ณ ๋ฏผํ๋ค๊ฐ key๋ฅผ ๋ณ์์ฒ๋ผ ํ์ฉํ๊ณ Trans ์ปดํฌ๋ํธ๋ฅผ ์ฐ๊ธฐ๋ก ํ๋ค.
Trans Component
ํํ์ด์ง์ ๋๋ฉ ํ์ด์ง์ ํฌํธํด๋ฆฌ์ค์ ์ฌ์ด๋ ํ๋ก์ ํธ์ ์ ๋ณด๋ฅผ ๋ด์, ๋ฆฌ์กํธ ์ปดํฌ๋ํธ๊ฐ ์๋ js ํ์ผ์ด ์๋ค.
import { Heading, Text } from 'grommet'; | |
import profile from '../assets/images/memoji.jpg'; | |
import profile2 from '../assets/images/memoji_2.jpg'; | |
// import profile3 from '../assets/images/rizard.png'; | |
import { Trans } from 'react-i18next'; | |
const TitleContent = (prop) => ( | |
<Heading textAlign='center'>{ prop.title ? prop.title : <Trans>Portfolio</Trans> }<br /><Text size='xxlarge' color='orange'>{prop.desc}</Text> | |
</Heading> | |
) | |
const contents = [ | |
// ... | |
{ | |
order: 6, | |
titleComponent: <TitleContent title={<Trans>Contact</Trans>} desc={<Trans>contact_sub</Trans>} />, | |
link: 'Contact', | |
contents: <Trans>Contact_desc</Trans> | |
} | |
] | |
export default contents; |
contents๋ ๊ฐ์ฒด๋ฅผ ์์๋ก ํ ๋ฐฐ์ด์ด๊ณ , titleComponent๋ ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ผ๋ก ๊ฐ์ง๊ณ , contents๋ ์๋ ๊ธด ๊ธ์ string์ ๊ฐ๊ณ ์์๋ค. contents_ko ๋ผ๋ ํค๋ฅผ ์๋ก ์ถ๊ฐํด ํ๊ตญ์ด ๋ฒ์ญ ๊ฐ์ ๊ฐ์ง๋ ค๊ณ ํ์ง๋ง, ๋์ ์ผ๋ก ๋ ๋๋ง์ด ์ ๋๊ธธ๋ ์ด ๊ฒฝ์ฐ์๋ ๋ชจ๋ Trans ์ปดํฌ๋ํธ๋ฅผ ์ด์ฉํด ์ฒ๋ฆฌํ๋ค. json ํ์ผ์ ์ผ๋ ํค ๊ฐ์ Trans ์ปดํฌ๋ํธ ์ฌ์ด์ ์์น์ํค๋ฉด ๋๋ค.

์ฐธ, ์ด๋ฐ ๊ฒฝ์ฐ์๋ HTML ํ๊ทธ๋ ์ฌ์ฉ ๊ฐ๋ฅํ๋ค.
์ค์ ์ธ์ด๋ฅผ ๊ธฐ์ตํ๊ธฐ w/i18next-browser-languagedetector
๋ง์ง๋ง์ผ๋ก ๋ก์ปฌ ์คํ ๋ฆฌ์ง์ ์ ์ฅํด ์ฌ์ฉ์์ ์ธ์ด ์ค์ ์ ๊ธฐ์ตํ๊ฒ ํด์ฃผ๊ธฐ ์ํด ์ ํ๋ฌ๊ทธ์ธ์ ์ค์นํ๋ค.
yarn add i18next-browser-languagedetector
๊ณต์ ๋ฌธ์์ instance ๋ถ๋ถ์ ์ฐธ๊ณ ํด์, i18n.js ์ค์ ํ์ผ์ ์์ฑํด์ฃผ๋ฉด ๋๋ค. i18n์ init ํ๊ธฐ ์ language Detector๋ฅผ useํ๊ณ , ๊ธฐ๋ณธ ์ธ์ด ์ค์ ์์ฑ์ธ lang: 'en'์ ์ฃผ์์ฒ๋ฆฌํ๋ค.

์ด์ ์ธ์ด๋ฅผ ์ค์ ํ๋ฉด i19nextLng์ด๋ผ๋ ํค๋ก ์ธ์ด๊ฐ ์ ์ฅ๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.