์ด ๊ธ์ ์ ๊ฐ ์ฌ์ง์ค์ธ Dable์ techblog์ ์์ฑํ i18n(๊ตญ์ ํ) system์ ๋ํ ๊ธ์ ๋๋ค. ๊ฐ๋ฐ ๋ธ๋ก๊ทธ์ ์ฒซ ๊ธ๋ก ์ฌ๋ฆฌ๊ธฐ์ ์ข์ ๊ธ์ธ์ง๋ ์ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค. ์ฌ๋ด ์์คํ ์ ์ฝ๋ ๋ ๋ฒจ๊น์ง ๋ด๋ ค๊ฐ์ ๊ณต๊ฐํ๋ ๊ฒ์ ์ข์ง ์์ ๊ฒ ๊ฐ์์ ์์คํ ์ ๋ง๋ก ํํํ๋ค๋ณด๋ ์ ๋งคํด์ง ๋ถ๋ถ๋ ์๋ ๊ฒ ๊ฐ์ต๋๋ค. ์ธ์ ๊ฐ ์์คํ ๊ฐ์ ์์ ์ ์งํํ๊ฒ ๋๋ฉด ๋ฐ๋ ๋ถ๋ถ์ ๋ค์ ์ฌ๋ ค๋ณด๋ ค๊ณ ํฉ๋๋ค.
ย Dable์ ํ์ฌ ํ๊ตญ ์ด์ธ์๋ ๋๋ง, ๋ง๋ ์ด์์, ์ธ๋๋ค์์, ๋ฒ ํธ๋จ ๋ฑ ๋ค์ํ ๊ตญ๊ฐ์ ์๋น์ค๋ฅผ ์ ๊ณตํ๊ณ ์์ต๋๋ค. ๋ฐ๋ผ์ ์ํํ ์๋น์ค๋ฅผ ์ํด์๋ ์ธ์ด, ํตํ ๋ฑ์ ํ์งํํ์ฌ ์ ๊ณตํด์ผ ํฉ๋๋ค. ์ด๋ฒ ๊ธ์์๋ ํ์งํ๋ฅผ ์ํ ์ฌ๋ฌ ์์ ์ค ๋ค์ํ ์ธ์ด๋ฅผ ์ ๊ณตํ๊ธฐ ์ํ ๋ค๊ตญ์ด ์์คํ (i18n system)์ ์ด๋ป๊ฒ ๊ตฌ์ถํ์ฌ ์ฌ์ฉํ๊ณ ์๋์ง ์๊ฐํ๋ ค๊ณ ํฉ๋๋ค.
์ดํ๋ก๋ ๊ณ์ํด์ ๋ฑ์ฅํ i18n ์ด๋ผ๋ ์ฉ์ด์ ๋ํด์ ๋จผ์ ์ดํด๋ณด๊ณ ๋์ด๊ฐ๋๋ก ํ๊ฒ ์ต๋๋ค.
i18n === Internationalization(๊ตญ์ ํ) i์ n ์ฌ์ด์ 18๊ฐ์ ์ํ๋ฒณ์ด ์๋ค๋ ์๋ฏธ์ ๋๋ค.
i18n์ ์ํํธ์จ์ด๊ฐ(์ํํธ์จ์ด์ ํ์ ๋ ๊ฐ๋ ์ ์๋์ง๋ง ์ฌ๊ธฐ์๋ ์ํํธ์จ์ด์ i18n์ผ๋ก ํ์ ํ์ฌ ์ด์ผ๊ธฐํ๊ฒ ์ต๋๋ค) ํน์ ์ง์ญ์ด๋ ์ธ์ด์ ์ข ์๋์ง ์๊ณ ๋ค์ํ ์ง์ญ๊ณผ ์ธ์ด ํ๊ฒฝ์์ ์ ์ ๋์ํ๋๋ก ๊ตญ์ ์ ์ผ๋ก ํต์ฉ๋๋ ์ํํธ์จ์ด๋ฅผ ์ค๊ณํ๊ณ ๊ฐ๋ฐํ๋ ๊ณผ์ ์ ๋งํฉ๋๋ค. ๋ฐ๋ผ์ i18n์ ๋จ์ํ ๋ฒ์ญ์ ์ ๊ณตํ๋ ๊ฒ์ ๋์ด ๋ค์๊ณผ ๊ฐ์ ์ฌ๋ฌ ๊ฐ์ง ์ฌํญ์ ๊ณ ๋ คํด์ผ ํฉ๋๋ค.
๊ตญ์ ํ๋ฅผ ์ํคํผ๋์์์ ๊ฒ์ํ๋ฉด ๊ตญ์ ํ์ ์ง์ญํ๋ผ๊ณ ํ์ฌ ์ง์ญํ๋ผ๋ ์ฉ์ด๋ฅผ ๊ฐ์ด ๋ค๋ฃจ๊ณ ์๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค. ์ํคํผ๋์์ ์ค๋ช ์ ์ด๋ ์ต๋๋ค.
๊ตญ์ ํ์ ์ง์ญํ๋ ์ถํ๋ฌผ์ด๋ ํ๋์จ์ด ๋๋ ์ํํธ์จ์ด ๋ฑ์ ์ ํ์ ์ธ์ด ๋ฐ ๋ฌธํ๊ถ ๋ฑ์ด ๋ค๋ฅธ ์ฌ๋ฌ ํ๊ฒฝ์ ๋ํด ์ฌ์ฉํ ์ ์๋๋ก ์ง์ํ๋ ๊ฒ์ ์๋ฏธํ๋ค. ์ด๋ ๊ตญ์ ํ๋ ์ ํ ์์ฒด๊ฐ ์ฌ๋ฌ ํ๊ฒฝ์ ์ง์ํ ์ ์๋๋ก ์ ํ์ ์ค๊ณํ๋ ๊ฒ์ ์๋ฏธํ๋ฉฐ, ์ง์ญํ๋ ์ ํ์ ๊ฐ ํ๊ฒฝ์ ๋ํด ์ง์ํ๋ ๊ฒ์ ์๋ฏธํ๋ค.
์ํคํผ๋์์์๋ ๋ฒ์ญ์ L10n์ ์์๋ก ์ด์ผ๊ธฐํฉ๋๋ค. ์ ๋ ๋์ฒด๋ก ์ํคํผ๋์์ ์ค๋ช ์ ๊ณต๊ฐํฉ๋๋ค. ์ ํ์ด ์ฌ๋ฌ ํ๊ฒฝ์ ์ง์ํ ์ ์๋๋ก ์ ํ์ ์ค๊ณํ๊ณ ์ ์ํ๋ ๊ฒ ์์ฒด๋ i18n์ ์์ญ์ด๊ณ , L10n์ ์กฐ๊ธ ๋ ๊ตฌ์ฒด์ ์ผ๋ก ์ง์ญ์ ํ์ ํ์ฌ ํด๋น ์ง์ญ์ ์ธ์ด, ๋ฌธํ์ ํน์ฑ์ ๊ณ ๋ คํ๋ ์์ ์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค. ๋ฐ๋ผ์ ๋ฒ์ญ์ ์ ๊ณตํ๋ ๊ฒ์ด ๊ฐ๋ฅํ๋๋ก ์์คํ ์ ๊ตฌ์ถํ๋ ์์ ์ i18n์ผ๋ก ๋ถ๋ฅํ๊ณ , ํน์ ์ง์ญ์ ์ธ์ด๋ก ๋ฒ์ญ์ ๋ง๋๋ ์ผ์ L10n์ผ๋ก ๋ถ๋ฅํ๋ ๊ฒ์ด ์ข๋ค๊ณ ์๊ฐํฉ๋๋ค. ๋ค๋ง Dable์์๋ ์ฉ์ด๋ฅผ ๊ตณ์ด ๊ตฌ๋ถํ์ง ์๊ณ i18n์ ๋ฒ์ญ์์คํ + ๋ฒ์ญ์ ํจ๊ป ์ผ์ปซ๋ ์ฉ๋๋ก ์ฌ์ฉํ๊ณ ์์ผ๋ฉฐ, ์ดํ์๋ ๊ธ์์ i18n์ด๋ผ๋ ๋จ์ด๊ฐ ๋์จ๋ค๋ฉด ๊ฐ์ ์๋ฏธ๋ก ์๊ฐํ์๋ฉด ๋๊ฒ ์ต๋๋ค.
i18next๋ JS๋ก ์์ฑ๋ i18n ํ๋ ์์ํฌ์ ๋๋ค(๊ณต์ ํํ์ด์ง). ์์์ i18n์์ ๊ณ ๋ คํด์ผ ํ ์ฌํญ์ ๋ค์ฏ ๊ฐ์ง ์ ๋๋ก ๋ง์๋๋ ธ๋๋ฐ, ๊ทธ ์ค ๋ค์ฏ ๋ฒ์งธ์ธ ๋ฌธ์์ด ์นํ ๋ฐฉ๋ฒ์ ํด๋นํฉ๋๋ค. Dable์์๋ i18next๋ฅผ ์ฌ์ฉํ์ฌ ๋ฒ์ญ์ ์ ๊ณตํ๊ณ ์์ต๋๋ค. i18next๋ ๋ค์ํ ํ๋ ์์ํฌ์ ์ธ์ด(React, Vue, Angular, Express... ์ฌ์ง์ด NodeJS ๋ฐ PHP ๋ฑ)๋ฅผ ์ง์ํ๊ณ ์๊ธฐ ๋๋ฌธ์ ์๋น์ค๋ณ, Repository ๋ณ๋ก ์ฌ์ฉํ๋ ํ๋ ์์ํฌ๊ฐ ๋ค๋ฅธ Dable์์ ์ฌ์ฉํ๊ธฐ ์๋ง์ ๋๊ตฌ์ ๋๋ค.
์ค๋ ํฌ์คํ ์ ์ฃผ๋ ์ฃผ์ ๋ Dable์ i18n system์ ๋ํ ์ค๋ช ์ด๊ธฐ ๋๋ฌธ์ ์ฌ์ฉ๋ฒ์ ๊ฐ๋จํ๊ฒ๋ง ์ง๊ณ ๋์ด๊ฐ๊ฒ ์ต๋๋ค. ๋ชจ๋ ์์๋ i18next ํํ์ด์ง์์ ๊ฐ์ ธ์์ต๋๋ค. i18next๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ๋น์ฐํ ๋ฒ์ญ์ด ํ์ํฉ๋๋ค(!). Resource๋ผ๊ณ ํํํ ์๋ ์์ต๋๋ค. Resource๋ ์๋์ ๊ฐ์ JSON ํํ๋ก ์ ๊ณตํฉ๋๋ค.
1{2 "key": "value of key",3 "look": {4 "deep": "value of look deep"5 }6}
Resource๋ ์ง์ static ํ์ผ๋ก ์ ๊ณตํ๋ ๊ฒ๋ ๊ฐ๋ฅํ๊ณ , ํ๋ฌ๊ทธ์ธ์ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ฐ๊ฒฐํ๋ ๊ฒ๋ ๊ฐ๋ฅํฉ๋๋ค. ์ดํ ๋ ์ค๋ช ํด ๋๋ฆฌ๊ฒ ์ง๋ง, Dable์ ๋ฐฐํฌ ์์ ํ๋ก์ ํธ๋ฅผ build ํ๋ฉด์ ์ผ์ฐจ์ ์ผ๋ก AWS S3์ ์ ์ฅ๋ JSON ํ์ผ์ ํตํด resource๋ฅผ ๊ฐ์ ธ์ค๊ณ (์ ํฌ๋ snapshot์ด๋ผ๊ณ ๋ถ๋ฆ ๋๋ค), ๋ฐฐํฌ ์ดํ์ ๋ณ๊ฒฝ ์ฌํญ์ ๋ฐ์ํ๊ธฐ ์ํด์ Redis์ ์ฐ๊ฒฐํ์ฌ ๋ฒ์ญ Resource๋ฅผ ๊ฐ์ ธ์ต๋๋ค. i18next๋ mongodb, nodejs filesystem ๋ฑ์ ์ฐ๊ฒฐํ๋ ํ๋ฌ๊ทธ์ธ์ ์ ๊ณตํ์ง๋ง, Redis์ ์ฐ๊ฒฐํ๋ ํ๋ฌ๊ทธ์ธ์ ๋ฐ๋ก ์์ด์ ์ ํฌ๊ฐ ๋ง๋ ๊ฒ์ ์ฌ์ฉํ๊ณ ์์ต๋๋ค.
์ด๋ ๊ฒ Resource๊ฐ ์ค๋น๋๋ฉด i18next๋ฅผ initํ์ฌ ์ฌ์ฉํ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค.
1import i18next from 'i18next';23i18next.init({4 lng: 'en',5 debug: true,6 resources: {7 en: {8 translation: {9 "key": "value of key",10 "look": {11 "deep": "value of look deep"12 }13 }14 }15 }16}, function(err, t) {17 // initialized and ready to go!18 document.getElementById('output').innerHTML = i18next.t('key');19});2021i18next.t('key');22// -> "value of key"23i18next.t('look.deep');24// -> "value of look deep"
init()
์ ๋ค์ํ ์ต์
์ ๋ํด์๋ ์ฌ๊ธฐ๋ฅผ ์ฐธ์กฐํ์ธ์.
์๋์ ๊ฐ์ด ๋ค์ํ ํ๋ฌ๊ทธ์ธ์ ์ถ๊ฐํ๋ ๊ฒ๋ ๊ฐ๋ฅํฉ๋๋ค. languageDetector๊ฐ์ ํ๋ฌ๊ทธ์ธ์ ์์ฃผ ์ ์ฉํฉ๋๋ค. ์ธ์ด๋ฅผ queryString, cookie, path ๋ฑ์ ์ด์ฉํ์ฌ ์ฐพ์์ฃผ๊ธฐ ๋๋ฌธ์ Dable์์๋ ์ด์ฉํ๊ณ ์์ต๋๋ค. browser๋ฟ ์๋๋ผ ๋ค์ํ ํ๊ฒฝ์์ ๋์ํ๋ ํ๋ฌ๊ทธ์ธ์ด ์์ผ๋ ํ์ธํด๋ณด์ธ์! (๋งํฌ)
1import i18next from 'i18next';2import Backend from 'i18next-http-backend';3import Cache from 'i18next-localstorage-cache';4import postProcessor from 'i18next-sprintf-postprocessor';5import LanguageDetector from 'i18next-browser-languagedetector';67i18next8 .use(Backend)9 .use(Cache)10 .use(LanguageDetector)11 .use(postProcessor)12 .init(options, callback);
์์ ์์ ๋ฅผ ํฉํ์ฌ Dable์ด ์ง๊ธ ์ฌ์ฉํ๋ ํํ์ ์ ์ฌํ๊ฒ ๋ง๋ค์ด ๋ณธ๋ค๋ฉด ์๋์ ๊ฐ์ต๋๋ค.
1import i18next from 'i18next';2import LanguageDetector from 'i18next-browser-languagedetector';34i18next5 .use(LanguageDetector)6 .init({7 debug: true,8 detection: { // languagedetector option9 order: ['querystring', 'htmlTag', 'cookie'], // detect ์ฐ์ ์์10 lookupQueryString: 'lang', // ?lang=11 lookupCookie: 'i18n_lang' // cookie name12 },13 resources: {14 en: {15 translation: {16 "key": "value of key",17 "look": {18 "deep": "value of look deep"19 }20 }21 },22 ko: {23 translation: {24 "key": "์ด๊ฑด key์ ๋ํ ๋ฒ์ญ value์
๋๋ค.",25 "look": {26 "deep": "ํ ๋จ๊ณ ๋ ๋ค์ด๊ฐ deep value์
๋๋ค."27 }28 }29 }30 }31}, function(err) {32 if(err) console.error(err);33});3435i18next.t('key');36// -> ๋ง์ฝ lang์ด 'ko'๋ผ๋ฉด "์ด๊ฑด key์ ๋ํ ๋ฒ์ญ value์
๋๋ค."
๊ฐ์ ํค์ ๋ํ์ฌ ๊ฐ ์ธ์ด๋ก ๋ฒ์ญํ resource๋ฅผ ์ ๊ณตํ๊ณ , ๋ธ๋ผ์ฐ์ ์์ detector๊ฐ ๊ฐ์งํ ์ธ์ด์ ๋ง์ถ์ด ๋ฒ์ญ์ด ์ ๊ณต๋๋ ํํ์ ๋๋ค. Dable์์๋ ์ผ๋ถ ์ต์ ๊ฐ์ ๋ค๋ฅด์ง๋ง ๋น์ทํ ํํ๋ก ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ์ง๊ธ๊น์ง i18next์ ๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ๋ฒ์ ์ดํด๋ณด์์ต๋๋ค. ๋ณต์ํ ์ง์, ๋ณ์ ์ฌ์ฉ ๋ฑ i18next์ ๋ํด ๋ ์์ธํ ๋ด์ฉ์ด ํ์ํ์ ๋ถ์ i18next ๊ณต์ ํํ์ด์ง์ ๊ณต์ ๋ฌธ์๋ฅผ ํ์ธํ์๋ฉด ๋ฉ๋๋ค.
์ด์ ๋๋์ด Dable์ i18n system์ ๋ํด์ ์ค๋ช ํ๊ฒ ์ต๋๋ค.
Dable์ ๋ค์ํ ์๋น์ค์์ ์ฌ์ฉ๋๋ i18n์ ๋ฒ์ญํค ๊ด๋ฆฌ, ๋ฐฐํฌ๋ฅผ ์ฝ๊ฒ ํ๊ธฐ ์ํด ๋ณ๋์ system์ ๊ตฌ์ถํ์ฌ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ๋ค์์ ๋์ค๋ ์ด๋ฏธ์ง๋ ์ ์ฒด ๊ตฌ์กฐ๋ฅผ ๊ฐ๋ตํ๊ฒ ํํํ ์ด๋ฏธ์ง์ ๋๋ค.
์์๊ฐ ๋ง์์ ๊ทธ๋ฆผ์ด ์ ๋ณด์ด์ง ์์ ๊ฒ ๊ฐ์์ ๊ฐ๋จํ ํ์ด์ ์ค๋ช ํด ๋ณด๊ฒ ์ต๋๋ค. Dable i18n system์ ํต์ฌ์ i18n-dashboard์ ๋๋ค. ์ด๋ฏธ์ง์ ํ ๊ฐ์ด๋ฐ์ ์๋ฆฌํ๊ณ ์์ต๋๋ค. ์ด ๋์๋ณด๋๋
๋ฑ ๋ค์ํ ์ญํ ์ ์ํํ๊ณ ์์ต๋๋ค. ๊ฐ ์ญํ ์ ์์ธํ ๋ด์ฉ์ ์ดํ ๋ ์ค๋ช ํ๊ฒ ์ต๋๋ค.
i18n-sync๋ ๋ฐฐํฌ ๊ณผ์ ์ค์ i18n-dashboard์ ์ํธ ์์ฉํ์ฌ ๋ฒ์ญํค์ ๋ณ๊ฒฝ์ ๋๊ธฐํํ๋(์ค์ ๋ก๋ i18n-dashboard์ ์์ฒญ์ ๋ณด๋ด๋) ๋ชจ๋์
๋๋ค. Dable ๋ด๋ถ private repository ์ค ํ๋์ด๋ฉฐ, ๊ฐ ์๋น์ค์๋ package.json
์ ์ถ๊ฐ๋์ด ๋ชจ๋ ํํ๋ก ์ฌ์ฉํ๊ณ ์์ต๋๋ค.
์ด์ด์ Dable์ด ๋ฒ์ญํค(Resource)๋ฅผ ์ด๋ป๊ฒ ๊ด๋ฆฌํ๋์ง ์ค๋ช ํ๊ณ ๊ทธ ํ์ ์๋น์ค ๋ฐฐํฌ ๊ณผ์ ์์์ i18n์ ์ค๋ช ํ๋๋ก ํ๊ฒ ์ต๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก ํ๊ตญ์ด ๋ฒ์ญ์ ๊ธฐ์ค์ผ๋ก ๋ฒ์ญํค ๊ด๋ฆฌ๋ฅผ ํฉ๋๋ค. ๊ฐ ์๋น์ค์ repository์์๋ ํ๊ตญ์ด ๋ฒ์ญํค ํ์ผ๋ง ๊ด๋ฆฌํ๊ณ , ๊ทธ ์ธ์ ์ธ๊ตญ์ด ํค๋ i18n-system์ ํตํด ๊ด๋ฆฌํ๋ค๊ณ ๋ณด์๋ฉด ๋ฉ๋๋ค. ์ธ๊ตญ์ด ํค๋ ๋ฐฐํฌ ๊ณผ์ ์์ ์ถ๊ฐ๋๋ฉฐ, live server์ local์ file๋ก ์ ์ฅ๋์ด ์๋น์ค์ ๋ฒ์ญํค๋ฅผ ์ ๊ณตํ๊ฒ ๋ฉ๋๋ค. ๊ทธ๋ผ ํ๊ตญ์ด ๋ฒ์ญํค ํ์ผ์ ํํ๋ฅผ ๋ณด๊ฒ ์ต๋๋ค.
1// /locales/ko/translation.js2export default {3 "nav": {4 "ํ": "ํ",5 "์ฝํ
์ธ ๊ด๋ฆฌ": "์ฝํ
์ธ ๊ด๋ฆฌ",6 "์บ ํ์ธ ๊ด๋ฆฌ": "์บ ํ์ธ ๊ด๋ฆฌ",7 "์ฑ๊ณผ ๋ถ์": "์ฑ๊ณผ ๋ถ์",8 ...9 }10}
๋๋ถ๋ถ์ ์๋น์ค๊ฐ locales ํด๋๋ฅผ ๋ง๋ค์ด ๊ทธ ์์ ๋ฒ์ญํค ํ์ผ์ ์ ์ฅํ๋๋ก ๋ง๋ค์ด์ ธ ์์ต๋๋ค.
1// /locales/index.js2import translation_ko from './ko/translation';34const locales = {5 'ko': {6 translation: translation_ko7 }8};910export default locales;
๋ฐฐํฌ ๊ณผ์ ์์ i18n-sync ๋ชจ๋์ด ์๋ํ์ฌ Database์์ ์ธ๊ตญ์ด ๋ฒ์ญํค๋ฅผ ๋ชจ๋ ๊ฐ์ ธ์ค๊ฒ ๋๊ณ , ์ธ์ด๋ณ๋ก locales
ํด๋ ์๋์ ๊ฐ ์ธ์ด์ ์ฝ๋๋ฅผ ํด๋๋ช
์ผ๋ก ํ์ฌ ์ ์ฅ๋ฉ๋๋ค.
๋์์ i18n-sync ๋ชจ๋์ด /locales/index.js
ํ์ผ์ ์๋ก ์ถ๊ฐ๋ ๋ฒ์ญํค๋ค์ import ํ๋๋ก ๋ค์ ์๋๋ค.
๊ทธ๋ฆฌ๊ณ ์ต์ข
live ๋ฐฐํฌ๊ฐ ์ด๋ฃจ์ด์ ธ์ ์๋น์ค์ ์น ์๋ฒ๊ฐ ์ผ์ง ๋, ๋ชจ๋ ๋ฒ์ญ ํ์ผ์ ํ๋์ ํ์ผ๋ก ๋ฌถ์ public/dist/locale/translation.js
ํ์ผ์ ๋ง๋ค๊ณ , ํด๋น ํ์ผ์ ์ ๊ณตํ๊ฒ ๋ฉ๋๋ค. (์ด๊ฑด ๋ชจ๋ ์๋น์ค๊ฐ ๊ทธ๋ ์ง๋ ์๊ณ , ๊ฐ ์๋น์ค์ ํน์ฑ์ ๋ง์ถฐ ์กฐ๊ธ ๋ค๋ฅด๊ฒ ์ ์ฉ๋ ๊ณณ๋ ์์ต๋๋ค)
๊ทธ๋ผ ์ด์ ๋ถํฐ ๋ฐฐํฌ ๊ณผ์ ์ ์ฐจ๋ก๋ก ์ง์ด๋ณด๋ฉฐ i18n-system์ด ์ด๋ป๊ฒ ํ๋ฌ๊ฐ๋์ง ์กฐ๊ธ ๋ ์์ธํ ์ดํด๋ณด๊ฒ ์ต๋๋ค. Dable์ ๋ฒ์ญํค ๊ด๋ฆฌ ๋ฐฉ์์ ๋ํ ์ค๋ช ์ด ์กฐ๊ธ ๋ฏธํกํ๊ฒ ๋๊ปด์ง์ จ๋๋ผ๋, ๋ฐฐํฌ ๊ณผ์ ์์ ๋ณด์ถฉ์ด ๋๋ฆฌ๋ผ ์๊ฐํฉ๋๋ค.
Dable์์๋ Github๊ณผ Jenkins๋ฅผ ์ด์ฉํ์ฌ ์๋น์ค๋ฅผ ๋ฐฐํฌํฉ๋๋ค. ์ด ๋ฐฐํฌ ๊ณผ์ ์ Pull Request, Merge & Build, Build์ ์ธ ๋จ๊ณ๋ก ๋๋์ด ๊ฐ ๋จ๊ณ์์ i18n์ด ์ด๋ป๊ฒ ๋์ํ๋์ง ์ค๋ช ์ ํด๋ณด๊ฒ ์ต๋๋ค.
์ฒซ ๋ฒ์งธ๋ก Pull Request ๋จ๊ณ๋ฅผ ์ค๋ช ํ๊ฒ ์ต๋๋ค. ๊ฐ๋ฐ์๊ฐ ์์ ํ feature ๋ธ๋์น๋ฅผ merge ํ๊ธฐ ์ํด PR์ ์ฌ๋ฆฌ๋ฉด Github webhook์ ํตํด Jenkins Job์ด ์คํ๋ฉ๋๋ค. ์ค๋น๋ Test๋ฅผ ๋๋ฆฌ๊ณ Test๊ฐ ํต๊ณผํ๋ฉด i18n-sync ๊ฐ์ฒด๊ฐ ๋์ํฉ๋๋ค. i18n-sync ๊ฐ์ฒด๋ ์๋ก ์ถ๊ฐ๋ ๋ฒ์ญํค๊ฐ ์์ผ๋ฉด ํด๋น key ์ ๋ณด๋ฅผ i18n-dashboard์ post ํ์ฌ Database์ ๋ฑ๋กํ ์ ์๋๋ก ํฉ๋๋ค. merge๊น์ง ์ค์ ๋ก ์ด์ด์ง ๊ฒ์ธ์ง ํ์ ํ ์ ์๊ธฐ ๋๋ฌธ์ ์ถ๊ฐ๋ ๋ฒ์ญํค์ ๋ํด์๋ง ์์ ์ ํ๊ฒ ๋ฉ๋๋ค. ์ดํ ์ถ๊ฐ๋ ๋ฒ์ญํค ๋ฆฌ์คํธ๋ฅผ ๋ฒ์ญ ๋ด๋น์๊ฐ ํ์ธํ์ฌ ๋ฒ์ญํ ์ ์๋๋ก ์ง์ ๋ slack ์ฑ๋์ ์ ์กํฉ๋๋ค. ๋ฒ์ญ ๋ด๋น์๋ i18n-dashboard์ UI๋ฅผ ์ด์ฉํ์ฌ ๋ฒ์ญ์ ๋ฑ๋กํฉ๋๋ค.
์ต์ด๋ก PR๋ฅผ ๋ฑ๋กํ ๋ ํ ๋ฒ(๋ฒ์ญํค ์์ ์ด ์๋ ๊ฒฝ์ฐ Test๋ง ํต๊ณผํ๋ฉด PR approve๋ฅผ ํด์ฃผ๊ธฐ ์ํจ), ๊ทธ๋ฆฌ๊ณ ๊ทธ ์ดํ ์์ด ๋ฒ์ญ ํค๊ฐ i18n-dashboard์ ๋ฑ๋ก๋ ๋ ์์ด ๋ฒ์ญ์ด ๋ชจ๋ ์๋ฃ๋์๋์ง ํ์ธํฉ๋๋ค. ๋ค์ํ ์ธ์ด๋ก ์๋น์ค๊ฐ ์ ๊ณต๋์ง๋ง, ์์ด ๋ฒ์ญ์ด ์๋ฃ๋๋ฉด ์๋น์ค๋ฅผ ๋ด๋ณด๋ผ ์ ์๋ค๊ณ ํ๋จํ๊ณ dable-bot์ด PR์ approve ํฉ๋๋ค. ์ฌ๊ธฐ๊น์ง ์งํ์ด ๋์๋ค๋ฉด merge๋ฅผ ์ํ ์ค๋น๊ฐ ๋๋๊ฒ ๋ฉ๋๋ค. QAํ์ ๊ฒ์๋ฅผ ์ํด ํน์ branch๋ฅผ ์ง์ ํ์ฌ ์๋ฒ๋ฅผ ๋์ฐ๋ ๊ณผ์ ์ด ์๊ธด ํ์ง๋ง, i18n system๊ณผ๋ ํฌ๊ฒ ์ฐ๊ด์ด ์์ด ์๋ตํ๋๋ก ํ๊ฒ ์ต๋๋ค.
์ด์ Pull Request๊ฐ Merge๋ ํ Build๊ฐ ์ผ์ด๋๊ธฐ ์ ๊น์ง์ ๊ณผ์ ์ ์ค๋ช ํ๊ฒ ์ต๋๋ค. ๋ฒ์ญ์ด ํ์ํ Dashboard๋ฅ์ ์๋น์ค๋ค์ ๋๋ถ๋ถ Docker Image Build๊น์ง ํ ํธํก์ผ๋ก ์ผ์ด๋์ง๋ง, ํธ์์ build ์ ๊น์ง์ ์์ ๋ง ๋จผ์ ์ค๋ช ํ๊ฒ ์ต๋๋ค. ํ๋ฆ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
Pull Request๊ฐ merge๋๊ณ , ๋ธ๋์น๊ฐ ์ญ์ ๋๋ฉด(์ ํฌ๋ merge๋ branch๋ฅผ ์๋์ผ๋ก ์ญ์ ํ๋๋ก ์ต์ ์ ์ค์ ํด ๋์์ต๋๋ค) webhook์ ํตํด Jenkins Build Job์ด ์คํ๋ฉ๋๋ค. ๊ทธ๋ผ i18n-sync ๊ฐ์ฒด์ merge ๋ฉ์๋๊ฐ ์คํ๋ฉ๋๋ค. ๋ด์ฉ์ ๋จ์ํฉ๋๋ค. DB์ ๋ฑ๋ก๋ ๋ฒ์ญํค๋ branch ๊ฐ์ด ๊ฐ์ด ์ ์ฅ๋์ด ์๋๋ฐ์, PR์ ์ฌ๋ฆด ๋ ์๋ก ์ถ๊ฐ๋ ํค๋ feature ๋ธ๋์น๋ช ์ด ๋ค์ด๊ฐ ์์ต๋๋ค. ๊ทธ๋์ merge๋ ๋ธ๋์น ๋ช ์ผ๋ก ํค๋ฅผ ๊ฒ์ํ์ฌ ํด๋น row์ branch ๊ฐ์ master๋ก ๋ณ๊ฒฝํด์ค๋๋ค. ๊ทธ๋ฆฌ๊ณ PR์์ ์ญ์ ๋ ๋ฒ์ญํค๊ฐ ์๋ค๋ฉด ์ด ๋จ๊ณ์์ ์ค์ ํค๋ฅผ ์ญ์ ํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ key ์ญ์ ์๋์ ์ฌ๋์ผ๋ก ์ ์กํฉ๋๋ค.
๊ทธ ์ดํ ํ์ฌ DB์ ์ ์ฅ๋์ด ์๋ (ํด๋น ์๋น์ค์) ๋ฒ์ญํค๋ฅผ ๋ชจ๋ JSON ํํ๋ก ๋ง๋ค์ด์ S3์ ์ ์ฅํฉ๋๋ค. ์ด๋ฅผ ์ ํฌ๋ snapshot์ด๋ผ๊ณ ๋ถ๋ฆ ๋๋ค. ํ์ผ ์ด๋ฆ์ ์๋น์ค ์ด๋ฆ๊ณผ ์ค๋ ์ท์ ๋ฒ์ ์ ์กฐํฉํ์ฌ ๋ง๋ค์ด์ง๋๋ค. i18n DB์๋ ์๋น์ค๋ณ๋ก ์ค๋ ์ท์ ๋ช ๊ฐ ๊ฐ์ง๊ณ ์์์ง, ๊ฐ์ง๊ณ ์๋ ์ค๋ ์ท์ด ์ด๋ค ๋ฒ์ ์ธ์ง๋ฅผ ๊ธฐ๋กํด ๋์์ต๋๋ค. ๋ฐ๋ผ์ ์๋ก ์ค๋ ์ท์ ์ถ๊ฐํ์ฌ ์ค๋ ์ท ํ๋๋ฅผ ์ด๊ณผํ์๋ค๋ฉด ๊ฐ์ฅ ์ค๋๋ ์ค๋ ์ท ํ๋๋ฅผ ์ง์๋๋ค. ์ฌ๊ธฐ๊น์ง ์๋ฃ๋์๋ค๋ฉด ์ดํ์๋ Image build๋ฅผ ์์ํฉ๋๋ค.
๋ฐฐํฌ ๊ณผ์ ์ ๋ง์ง๋ง ๋จ๊ณ๋ก Build ๊ณผ์ ์ ์ค๋ช ํ๊ฒ ์ต๋๋ค. Build ๊ณผ์ ์ ๊ฐ๋จํ์ฌ ๋ณ๋๋ก ๊ทธ๋ฆผ์ ์ฒจ๋ถํ์ง ์๊ณ ๊ธ๋ก ์ค๋ช ํ๋๋ก ํ๊ฒ ์ต๋๋ค. merge๋ฅผ ์ค๋ช ํ ๋ merge์ build๋ ํธ์์ ๋๋ ์ ์ค๋ช ํด ๋๋ ธ์ง๋ง ์ค์ Jenkins์์๋ ํ๋์ Job ๋ด๋ถ์์ ๊ฐ์ด ์คํ๋ฉ๋๋ค. Build ์ง์ ์ ๋ง๋ ์ค๋ ์ท์ ๋ฒ์ ์ ํ๊ฒฝ ๋ณ์๋ก ๋ฃ์ด์ Docker Image Build๊ฐ ์ผ์ด๋ฉ๋๋ค.
1# DABLE I18N VERSION2ARG I18N_VERSION3ENV I18N_VERSION ${I18N_VERSION}45## BUILD6RUN npm run lang:refresh -- --i18n_version=$I18N_VERSION
npm run lang:refresh
์ปค๋งจ๋๋ i18n-sync์ localSync ๋ฉ์๋๋ฅผ ์คํํฉ๋๋ค. ์ด๋ ์ ๋ฌ๋ฐ์ snapshot์ version์ parameter๋ก ๊ฐ์ด ๋๊ธฐ๊ฒ ๋ฉ๋๋ค.
localSync ํจ์๋ snapshot version์ ์ ๋ฌ๋ฐ์ผ๋ฉด S3 bucket์์ ํด๋น ์ค๋
์ท์ ์กฐํํ์ฌ ๋ฒ์ญํค๋ฅผ ๋ถ๋ฌ์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ๋ถ๋ฌ์จ ๋ฒ์ญํค๋ฅผ /locales/
ํด๋ ์์ ๊ตญ๊ฐ๋ณ๋ก ๋๋์ด์ ์ ์ฅํด๋ก๋๋ค.
๊ทธ๋ฆฌ๊ณ Buildํ ์ด๋ฏธ์ง๋ AWS ์ด๋ฏธ์ง ์ ์ฅ์์ ์ ์ฅํฉ๋๋ค.
์ง๊ธ๊น์ง ๋ฐฐํฌ ๊ณผ์ ์์ ๋ฒ์ญํค๊ฐ ๊ด๋ฆฌ๋๋ ํ๋ฆ์ ์ ๋ฆฌํด๋ดค์ต๋๋ค. ๋ค์์ ๋ง์ง๋ง ์์๋ก live server์์๋ i18n system์ด ์ด๋ป๊ฒ ๋์ํ๋์ง ๋ณด๊ฒ ์ต๋๋ค.
๋น๋๋ Docker Image๋ฅผ ๊ฐ์ ธ๋ค๊ฐ live server๋ฅผ ์ผค ๋, /locales/
ํด๋ ๋ด๋ถ์ ์ธ์ด๋ณ๋ก ์ฐข์ด์ ธ ์๋ ๋ฒ์ญ ํ์ผ์ ๋ชจ๋ ๋ชจ์์ ํ๋๋ก ๋ง๋ค์ด public/dist/locale
ํด๋์ ๋ฃ์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ ๊ทธ ์ดํ ๋ค๋ฅธ ์ธ์ด์ ๋ฒ์ญํค๊ฐ ์ถ๊ฐ๋๊ฑฐ๋, ๊ธฐ์กด์ ๋ฒ์ญํค๊ฐ ์์ ๋ ๊ฒฝ์ฐ๋ฅผ ์ํด ์ฃผ๊ธฐ์ ์ผ๋ก ๋๊ธฐํ ์์
์ ์คํํฉ๋๋ค. ๋๊ธฐํ ์์
์ ์ด ์ธ ๋ถ๋ถ์ผ๋ก ๋๋์ด ๋ณผ ์ ์์ต๋๋ค.
key_sync i18n-dashboard๋ 10๋ถ๋ง๋ค ๋ชจ๋ ์๋น์ค์ ๋ฒ์ญํค๋ฅผ DB์์ ๊ฐ์ ธ์์ Dable ๋ด๋ถ์์ ๊ณต์ฉ์ผ๋ก ์ฌ์ฉํ๋ Elasticache(redis)์ ์ง์ด๋ฃ์ต๋๋ค. Redis์ ๋ฃ์ ๋์ Key๋ ์๋น์ค๋ช ๊ณผ ๋ฒ์ญํค๋ช ๋ฑ์ ์กฐํฉํ์ฌ ๊ท์น์ ์ผ๋ก ๋ง๋ค์ด์ง๋๋ค.
reload
๊ฐ ์๋น์ค๋ 1๋ถ๋ง๋ค i18n-dashboard์ ์๋น์ค ๊ฐ๋ฅํ ์ธ์ด ๋ชฉ๋ก์ ์์ฒญํฉ๋๋ค. ์๋น์ค ๊ฐ๋ฅํ ์ธ์ด ๋ชฉ๋ก์ ํ์ฌ ์๋น์ค์ ๋ฑ๋ก๋ ๋ฒ์ญํค์ ์ธ์ด ๊ฐ์ DISTINCT
ํ์ฌ ๋ฐ์์ค๋ ๊ฐ์
๋๋ค.
์์์ ์ธ๊ธํ Redis์์ ๋ฒ์ญํค๋ฅผ ์ ๋ถ ๋ฐ์์์ ์๋น์ค ๊ฐ๋ฅํ ์ธ์ด ๋ณ๋ก localํ์ผ๊ณผ ๋น๊ตํ์ฌ ๋ฌ๋ผ์ง ์ ์ ์ฒดํฌํฉ๋๋ค. ์ด๋ ๋ฌ๋ผ์ง ์ ์ด ์กด์ฌํ๋ค๋ฉด ๋ฌ๋ผ์ง ๋ถ๋ถ์ ๊ฐฑ์ ํ์ฌ translation.js ํ์ผ์ ์๋ก ๋ง๋ญ๋๋ค.
slack_reminder i18n-dashboard์์ ๋งค์ฃผ ์์์ผ ์คํ 3์์ ์คํ๋ฉ๋๋ค. ์๋น์ค(๊ทธ๋ฆผ์ ๋์จ RECO, MKT๋ ์ ํฌ ๋์๋ณด๋ ์ค ๊ฐ์ฅ ๋ง์ด ์ฐ์ด๋ ์๋น์ค์ ๋๋ค)์ ๋ฒ์ญ ํํฉ์ ๊ณ์ฐํ์ฌ ํผ์ผํธ๋ก ๋ํ๋ธ ํ ์ฌ๋์ ์ ์กํ์ฌ ์์ง ๋ฒ์ญ์ ๋ชป ํ ๋ด๋น์๋ค์ด ์๊ณ ๋์ด๊ฐ์ง ์๋๋ก ๋์ต๋๋ค.
Live server์์์ i18n system์ ์ดํด๋ณด์์ต๋๋ค. ๋ฐฐํฌ ๊ณผ์ ๊ณผ Live ๊ณผ์ ์ ๋์์ ์ค๋ช ํจ์ผ๋ก Dable์ i18n ์์คํ ์ ๋ํ ์ ๋ฐ์ ์ธ ์ค๋ช ์ ๋ชจ๋ ๋ง์ณค์ต๋๋ค.
์ง๊ธ๊น์ง i18n์ด ๋ฌด์์ธ์ง, i18next๋ ๋ ๋ฌด์์ธ์ง, Dable์์๋ i18n ์์คํ ์ ์ด๋ป๊ฒ ๋ง๋ค์ด ์ฌ์ฉํ๊ณ ์๋์ง๋ฅผ ๋ง์๋๋ ธ์ต๋๋ค. ํน์ ์๋น์ค์ ๋ฒ์ญ ์์คํ ๋์ ์ ๊ณ ๋ฏผํ๊ณ ๊ณ์ จ๋ ๋ถ์ด ์ด ๊ธ์ ๋ณด์๊ณ ์กฐ๊ธ์ด๋๋ง ๋์์ ์ป์ผ์ จ์ผ๋ฉด ์ข๊ฒ ์ต๋๋ค. ์์ง Dable์ i18n ์์คํ ๋ ์๋ฒฝํ ์์ฑ๋ ๊ฒ์ด ์๋๋๋ค. ๋ฒ์ญํค sync ๊ณผ์ ๊ณ ๋ํ, ๋ฒ์ญ ํ์ผ์ ๋์ฑ ์ปดํฉํธํ ์๋น ๋ฑ์ ๋ชฉํ๋ฅผ ์ก๊ณ ์๋น์ค๋ฅผ ๊ฐ์ ํ ์์ ์ ๋๋ค. ์์ผ๋ก ๋ฐ์ด๋ธ์ด ์ง์ถํ ๊ตญ๊ฐ๊ฐ ๋ง์์ง๋ฉด i18n๋ ๋ณ๋ชจํ ์ผ์ด ์๊ธธ ํ ๋ฐ ๊ทธ๋ ๋ ์๋ก์ด ๋ณ๊ฒฝ ์ฌํญ์ ๊ฐ์ง๊ณ ํฌ์คํ ์ผ๋ก ๋์์ค๊ฒ ์ต๋๋ค.