Skip to content
Dev-blog

Dable์˜ i18n ์‹œ์Šคํ…œ

โ€” i18n, js โ€” 7 min read

์ด ๊ธ€์€ ์ œ๊ฐ€ ์žฌ์ง์ค‘์ธ Dable์˜ techblog์— ์ž‘์„ฑํ•œ i18n(๊ตญ์ œํ™”) system์— ๋Œ€ํ•œ ๊ธ€์ž…๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ ๋ธ”๋กœ๊ทธ์˜ ์ฒซ ๊ธ€๋กœ ์˜ฌ๋ฆฌ๊ธฐ์— ์ข‹์€ ๊ธ€์ธ์ง€๋Š” ์ž˜ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค. ์‚ฌ๋‚ด ์‹œ์Šคํ…œ์„ ์ฝ”๋“œ ๋ ˆ๋ฒจ๊นŒ์ง€ ๋‚ด๋ ค๊ฐ€์„œ ๊ณต๊ฐœํ•˜๋Š” ๊ฒƒ์€ ์ข‹์ง€ ์•Š์€ ๊ฒƒ ๊ฐ™์•„์„œ ์‹œ์Šคํ…œ์„ ๋ง๋กœ ํ‘œํ˜„ํ•˜๋‹ค๋ณด๋‹ˆ ์• ๋งคํ•ด์ง„ ๋ถ€๋ถ„๋„ ์žˆ๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์–ธ์  ๊ฐ€ ์‹œ์Šคํ…œ ๊ฐœ์„  ์ž‘์—…์„ ์ง„ํ–‰ํ•˜๊ฒŒ ๋˜๋ฉด ๋ฐ”๋€ ๋ถ€๋ถ„์„ ๋‹ค์‹œ ์˜ฌ๋ ค๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.


ย  Dable์€ ํ˜„์žฌ ํ•œ๊ตญ ์ด์™ธ์—๋„ ๋Œ€๋งŒ, ๋ง๋ ˆ์ด์‹œ์•„, ์ธ๋„๋„ค์‹œ์•„, ๋ฒ ํŠธ๋‚จ ๋“ฑ ๋‹ค์–‘ํ•œ ๊ตญ๊ฐ€์— ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์›ํ™œํ•œ ์„œ๋น„์Šค๋ฅผ ์œ„ํ•ด์„œ๋Š” ์–ธ์–ด, ํ†ตํ™” ๋“ฑ์„ ํ˜„์ง€ํ™”ํ•˜์—ฌ ์ œ๊ณตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฒˆ ๊ธ€์—์„œ๋Š” ํ˜„์ง€ํ™”๋ฅผ ์œ„ํ•œ ์—ฌ๋Ÿฌ ์š”์†Œ ์ค‘ ๋‹ค์–‘ํ•œ ์–ธ์–ด๋ฅผ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•œ ๋‹ค๊ตญ์–ด ์‹œ์Šคํ…œ(i18n system)์„ ์–ด๋–ป๊ฒŒ ๊ตฌ์ถ•ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š”์ง€ ์†Œ๊ฐœํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

i18n

์ดํ›„๋กœ๋„ ๊ณ„์†ํ•ด์„œ ๋“ฑ์žฅํ•  i18n ์ด๋ผ๋Š” ์šฉ์–ด์— ๋Œ€ํ•ด์„œ ๋จผ์ € ์‚ดํŽด๋ณด๊ณ  ๋„˜์–ด๊ฐ€๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

i18n === Internationalization(๊ตญ์ œํ™”) i์™€ n ์‚ฌ์ด์— 18๊ฐœ์˜ ์•ŒํŒŒ๋ฒณ์ด ์žˆ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค.

i18n์€ ์†Œํ”„ํŠธ์›จ์–ด๊ฐ€(์†Œํ”„ํŠธ์›จ์–ด์— ํ•œ์ •๋œ ๊ฐœ๋…์€ ์•„๋‹ˆ์ง€๋งŒ ์—ฌ๊ธฐ์„œ๋Š” ์†Œํ”„ํŠธ์›จ์–ด์˜ i18n์œผ๋กœ ํ•œ์ •ํ•˜์—ฌ ์ด์•ผ๊ธฐํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค) ํŠน์ • ์ง€์—ญ์ด๋‚˜ ์–ธ์–ด์— ์ข…์†๋˜์ง€ ์•Š๊ณ  ๋‹ค์–‘ํ•œ ์ง€์—ญ๊ณผ ์–ธ์–ด ํ™˜๊ฒฝ์—์„œ ์ •์ƒ ๋™์ž‘ํ•˜๋„๋ก ๊ตญ์ œ์ ์œผ๋กœ ํ†ต์šฉ๋˜๋Š” ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ์„ค๊ณ„ํ•˜๊ณ  ๊ฐœ๋ฐœํ•˜๋Š” ๊ณผ์ •์„ ๋งํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ i18n์€ ๋‹จ์ˆœํžˆ ๋ฒˆ์—ญ์„ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์„ ๋„˜์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ์‚ฌํ•ญ์„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  1. ๊ฐ ์ง€์—ญ ์–ธ์–ด์˜ ์ธ์ฝ”๋”ฉ ๋ฐฉ์‹ - ์œ ๋‹ˆ์ฝ”๋“œ, ์“ฐ๊ธฐ ๋ฐฉํ–ฅ, ๋ฌธ์ž์—ด ์ •๋ ฌ ์ˆœ์„œ ๋“ฑ
  2. ๋‚ ์งœ/์‹œ๊ฐ„ ํ˜•์‹, ๋‹ฌ๋ ฅ, ํ†ตํ™”
  3. ๋ฒˆ์—ญ ๋ฆฌ์†Œ์Šค ์™ธ๋ถ€ํ™” - ํ”„๋กœ๊ทธ๋žจ์„ ์ง์ ‘์ ์œผ๋กœ ์ˆ˜์ •ํ•˜์ง€ ์•Š๊ณ  ๋‹ค๊ตญ์–ด ์ง€์›์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด
  4. UI ๋Œ€์‘(๋ฌธ์ž์—ด ํฌ๊ธฐ ๋ณ€ํ™”, ํฐํŠธ ๋“ฑ)
  5. ๋ฌธ์ž์—ด ์น˜ํ™˜ ๋ฐฉ๋ฒ•

i18n vs L10n(Localization)

๊ตญ์ œํ™”๋ฅผ ์œ„ํ‚คํ”ผ๋””์•„์—์„œ ๊ฒ€์ƒ‰ํ•˜๋ฉด ๊ตญ์ œํ™”์™€ ์ง€์—ญํ™”๋ผ๊ณ  ํ•˜์—ฌ ์ง€์—ญํ™”๋ผ๋Š” ์šฉ์–ด๋ฅผ ๊ฐ™์ด ๋‹ค๋ฃจ๊ณ  ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์œ„ํ‚คํ”ผ๋””์•„์˜ ์„ค๋ช…์€ ์ด๋ ‡์Šต๋‹ˆ๋‹ค.

๊ตญ์ œํ™”์™€ ์ง€์—ญํ™”๋Š” ์ถœํŒ๋ฌผ์ด๋‚˜ ํ•˜๋“œ์›จ์–ด ๋˜๋Š” ์†Œํ”„ํŠธ์›จ์–ด ๋“ฑ์˜ ์ œํ’ˆ์„ ์–ธ์–ด ๋ฐ ๋ฌธํ™”๊ถŒ ๋“ฑ์ด ๋‹ค๋ฅธ ์—ฌ๋Ÿฌ ํ™˜๊ฒฝ์— ๋Œ€ํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค. ์ด๋•Œ ๊ตญ์ œํ™”๋Š” ์ œํ’ˆ ์ž์ฒด๊ฐ€ ์—ฌ๋Ÿฌ ํ™˜๊ฒฝ์„ ์ง€์›ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ œํ’ˆ์„ ์„ค๊ณ„ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•˜๋ฉฐ, ์ง€์—ญํ™”๋Š” ์ œํ’ˆ์„ ๊ฐ ํ™˜๊ฒฝ์— ๋Œ€ํ•ด ์ง€์›ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.

์œ„ํ‚คํ”ผ๋””์•„์—์„œ๋Š” ๋ฒˆ์—ญ์„ L10n์˜ ์š”์†Œ๋กœ ์ด์•ผ๊ธฐํ•ฉ๋‹ˆ๋‹ค. ์ €๋Š” ๋Œ€์ฒด๋กœ ์œ„ํ‚คํ”ผ๋””์•„์˜ ์„ค๋ช…์— ๊ณต๊ฐํ•ฉ๋‹ˆ๋‹ค. ์ œํ’ˆ์ด ์—ฌ๋Ÿฌ ํ™˜๊ฒฝ์„ ์ง€์›ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ œํ’ˆ์„ ์„ค๊ณ„ํ•˜๊ณ  ์ œ์ž‘ํ•˜๋Š” ๊ฒƒ ์ž์ฒด๋Š” i18n์˜ ์˜์—ญ์ด๊ณ , L10n์€ ์กฐ๊ธˆ ๋” ๊ตฌ์ฒด์ ์œผ๋กœ ์ง€์—ญ์„ ํ•œ์ •ํ•˜์—ฌ ํ•ด๋‹น ์ง€์—ญ์˜ ์–ธ์–ด, ๋ฌธํ™”์  ํŠน์„ฑ์„ ๊ณ ๋ คํ•˜๋Š” ์ž‘์—…์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋ฒˆ์—ญ์„ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•˜๋Š” ์ž‘์—…์€ i18n์œผ๋กœ ๋ถ„๋ฅ˜ํ•˜๊ณ , ํŠน์ • ์ง€์—ญ์˜ ์–ธ์–ด๋กœ ๋ฒˆ์—ญ์„ ๋งŒ๋“œ๋Š” ์ผ์€ L10n์œผ๋กœ ๋ถ„๋ฅ˜ํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋งŒ Dable์—์„œ๋Š” ์šฉ์–ด๋ฅผ ๊ตณ์ด ๊ตฌ๋ถ„ํ•˜์ง€ ์•Š๊ณ  i18n์„ ๋ฒˆ์—ญ์‹œ์Šคํ…œ + ๋ฒˆ์—ญ์„ ํ•จ๊ป˜ ์ผ์ปซ๋Š” ์šฉ๋„๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ์ดํ›„์—๋„ ๊ธ€์—์„œ i18n์ด๋ผ๋Š” ๋‹จ์–ด๊ฐ€ ๋‚˜์˜จ๋‹ค๋ฉด ๊ฐ™์€ ์˜๋ฏธ๋กœ ์ƒ๊ฐํ•˜์‹œ๋ฉด ๋˜๊ฒ ์Šต๋‹ˆ๋‹ค.

i18next

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';
2
3i18next.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});
20
21i18next.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';
6
7i18next
8 .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';
3
4i18next
5 .use(LanguageDetector)
6 .init({
7 debug: true,
8 detection: { // languagedetector option
9 order: ['querystring', 'htmlTag', 'cookie'], // detect ์šฐ์„ ์ˆœ์œ„
10 lookupQueryString: 'lang', // ?lang=
11 lookupCookie: 'i18n_lang' // cookie name
12 },
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});
34
35i18next.t('key');
36// -> ๋งŒ์•ฝ lang์ด 'ko'๋ผ๋ฉด "์ด๊ฑด key์— ๋Œ€ํ•œ ๋ฒˆ์—ญ value์ž…๋‹ˆ๋‹ค."

๊ฐ™์€ ํ‚ค์— ๋Œ€ํ•˜์—ฌ ๊ฐ ์–ธ์–ด๋กœ ๋ฒˆ์—ญํ•œ resource๋ฅผ ์ œ๊ณตํ•˜๊ณ , ๋ธŒ๋ผ์šฐ์ €์—์„œ detector๊ฐ€ ๊ฐ์ง€ํ•œ ์–ธ์–ด์— ๋งž์ถ”์–ด ๋ฒˆ์—ญ์ด ์ œ๊ณต๋˜๋Š” ํ˜•ํƒœ์ž…๋‹ˆ๋‹ค. Dable์—์„œ๋„ ์ผ๋ถ€ ์˜ต์…˜๊ฐ’์€ ๋‹ค๋ฅด์ง€๋งŒ ๋น„์Šทํ•œ ํ˜•ํƒœ๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ๊นŒ์ง€ i18next์˜ ๊ธฐ๋ณธ์ ์ธ ์‚ฌ์šฉ๋ฒ•์„ ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค. ๋ณต์ˆ˜ํ˜• ์ง€์›, ๋ณ€์ˆ˜ ์‚ฌ์šฉ ๋“ฑ i18next์— ๋Œ€ํ•ด ๋” ์ž์„ธํ•œ ๋‚ด์šฉ์ด ํ•„์š”ํ•˜์‹  ๋ถ„์€ i18next ๊ณต์‹ ํ™ˆํŽ˜์ด์ง€์˜ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ํ™•์ธํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

์ด์ œ ๋“œ๋””์–ด Dable์˜ i18n system์— ๋Œ€ํ•ด์„œ ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

Dable i18n System

Dable์€ ๋‹ค์–‘ํ•œ ์„œ๋น„์Šค์—์„œ ์‚ฌ์šฉ๋˜๋Š” i18n์˜ ๋ฒˆ์—ญํ‚ค ๊ด€๋ฆฌ, ๋ฐฐํฌ๋ฅผ ์‰ฝ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ๋ณ„๋„์˜ system์„ ๊ตฌ์ถ•ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์— ๋‚˜์˜ค๋Š” ์ด๋ฏธ์ง€๋Š” ์ „์ฒด ๊ตฌ์กฐ๋ฅผ ๊ฐ„๋žตํ•˜๊ฒŒ ํ‘œํ˜„ํ•œ ์ด๋ฏธ์ง€์ž…๋‹ˆ๋‹ค.

๋ฐ์ด๋ธ”์˜ i18n ์‹œ์Šคํ…œ ๊ตฌ์กฐ๋ฅผ ๊ทธ๋ฆฐ ๋„ํ‘œ. i18n dashboard๋ผ๋Š” ์„œ๋น„์Šค์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, AWS S3 ์ €์žฅ์†Œ, Redis Elasticache ๋“ฑ์˜ ์„œ๋น„์Šค๊ฐ€ ์—ฐ๊ฒฐ๋˜์–ด์„œ ๋ฒˆ์—ญํ‚ค์˜ ๋ฐฐํฌ๋ฅผ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๊ณ , ๊ฐ ์„œ๋น„์Šค repository๋Š” ๋ฐฐํฌ ๊ณผ์ •์—์„œ i18n-dashboard์™€ ์ƒํ˜ธ ์ž‘์šฉํ•˜๋ฉฐ ๋ฒˆ์—ญํ‚ค์˜ ์ˆ˜์ •, ์ถ”๊ฐ€, ์‚ญ์ œ๋ฅผ ์ ์šฉํ•˜๊ณ  ๋™๊ธฐํ™”ํ•œ๋‹ค.

์š”์†Œ๊ฐ€ ๋งŽ์•„์„œ ๊ทธ๋ฆผ์ด ์ž˜ ๋ณด์ด์ง€ ์•Š์„ ๊ฒƒ ๊ฐ™์•„์„œ ๊ฐ„๋‹จํžˆ ํ’€์–ด์„œ ์„ค๋ช…ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. Dable i18n system์˜ ํ•ต์‹ฌ์€ i18n-dashboard์ž…๋‹ˆ๋‹ค. ์ด๋ฏธ์ง€์˜ ํ•œ ๊ฐ€์šด๋ฐ์— ์ž๋ฆฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋Œ€์‹œ๋ณด๋“œ๋Š”

  1. ๋ฒˆ์—ญํ‚ค์˜ ์ถ”๊ฐ€, ์‚ญ์ œ ๋ฐ ์ˆ˜์ •(AWS RDS์— ์ €์žฅ)
  2. ๋ฒˆ์—ญํ‚ค๋ฅผ JSON ํŒŒ์ผ๋กœ ๋งŒ๋“ค์–ด(snapshot) S3์— ์ €์žฅํ•˜๊ณ , ํ•„์š”ํ•  ๋•Œ๋Š” snapshot ํŒŒ์ผ์„ ์กฐํšŒ
  3. RDS์— ๋“ค์–ด ์žˆ๋Š” ๋ฒˆ์—ญํ‚ค๋ฅผ ์ „๋ถ€ Elasticache(Redis)์— ์ €์žฅ (ํ˜„์žฌ๋Š” ๋งค 10๋ถ„ ์ฃผ๊ธฐ๋กœ ์ง„ํ–‰)
  4. ์ฃผ๊ธฐ์ ์œผ๋กœ ๋ฒˆ์—ญ ์ƒํ™ฉ์„ slack message๋กœ ์•Œ๋ฆผ
  5. ๋ฒˆ์—ญ๊ฐ€ ๋ฐ ํ•ด์™ธ Dabler๋“ค์ด ์ง์ ‘ ๋ฒˆ์—ญ์„ ์˜ฌ๋ฆด ์ˆ˜ ์žˆ๋„๋ก UI ์ œ๊ณต(React + Redux)
  6. ๋ฒˆ์—ญ์ด ์ˆ˜์ •๋œ PR์ด ์˜ฌ๋ผ์˜จ ๊ฒฝ์šฐ ์˜์–ด ๋ฒˆ์—ญ์ด ๋ชจ๋‘ ์™„๋ฃŒ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜์—ฌ PR approve

๋“ฑ ๋‹ค์–‘ํ•œ ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ์—ญํ• ์˜ ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์ดํ›„ ๋” ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

i18n-sync๋Š” ๋ฐฐํฌ ๊ณผ์ • ์ค‘์— i18n-dashboard์™€ ์ƒํ˜ธ ์ž‘์šฉํ•˜์—ฌ ๋ฒˆ์—ญํ‚ค์˜ ๋ณ€๊ฒฝ์„ ๋™๊ธฐํ™”ํ•˜๋Š”(์‹ค์ œ๋กœ๋Š” i18n-dashboard์— ์š”์ฒญ์„ ๋ณด๋‚ด๋Š”) ๋ชจ๋“ˆ์ž…๋‹ˆ๋‹ค. Dable ๋‚ด๋ถ€ private repository ์ค‘ ํ•˜๋‚˜์ด๋ฉฐ, ๊ฐ ์„œ๋น„์Šค์—๋Š” package.json์— ์ถ”๊ฐ€๋˜์–ด ๋ชจ๋“ˆ ํ˜•ํƒœ๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์ด์–ด์„œ Dable์ด ๋ฒˆ์—ญํ‚ค(Resource)๋ฅผ ์–ด๋–ป๊ฒŒ ๊ด€๋ฆฌํ•˜๋Š”์ง€ ์„ค๋ช…ํ•˜๊ณ  ๊ทธ ํ›„์— ์„œ๋น„์Šค ๋ฐฐํฌ ๊ณผ์ •์—์„œ์˜ i18n์„ ์„ค๋ช…ํ•˜๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

Dable์˜ ๋ฒˆ์—ญํ‚ค ๊ด€๋ฆฌ

๊ธฐ๋ณธ์ ์œผ๋กœ ํ•œ๊ตญ์–ด ๋ฒˆ์—ญ์„ ๊ธฐ์ค€์œผ๋กœ ๋ฒˆ์—ญํ‚ค ๊ด€๋ฆฌ๋ฅผ ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ์„œ๋น„์Šค์˜ repository์—์„œ๋Š” ํ•œ๊ตญ์–ด ๋ฒˆ์—ญํ‚ค ํŒŒ์ผ๋งŒ ๊ด€๋ฆฌํ•˜๊ณ , ๊ทธ ์™ธ์˜ ์™ธ๊ตญ์–ด ํ‚ค๋Š” i18n-system์„ ํ†ตํ•ด ๊ด€๋ฆฌํ•œ๋‹ค๊ณ  ๋ณด์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์™ธ๊ตญ์–ด ํ‚ค๋Š” ๋ฐฐํฌ ๊ณผ์ •์—์„œ ์ถ”๊ฐ€๋˜๋ฉฐ, live server์˜ local์— file๋กœ ์ €์žฅ๋˜์–ด ์„œ๋น„์Šค์— ๋ฒˆ์—ญํ‚ค๋ฅผ ์ œ๊ณตํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿผ ํ•œ๊ตญ์–ด ๋ฒˆ์—ญํ‚ค ํŒŒ์ผ์˜ ํ˜•ํƒœ๋ฅผ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

1// /locales/ko/translation.js
2export default {
3 "nav": {
4 "ํ™ˆ": "ํ™ˆ",
5 "์ฝ˜ํ…์ธ  ๊ด€๋ฆฌ": "์ฝ˜ํ…์ธ  ๊ด€๋ฆฌ",
6 "์บ ํŽ˜์ธ ๊ด€๋ฆฌ": "์บ ํŽ˜์ธ ๊ด€๋ฆฌ",
7 "์„ฑ๊ณผ ๋ถ„์„": "์„ฑ๊ณผ ๋ถ„์„",
8 ...
9 }
10}

๋Œ€๋ถ€๋ถ„์˜ ์„œ๋น„์Šค๊ฐ€ locales ํด๋”๋ฅผ ๋งŒ๋“ค์–ด ๊ทธ ์•ˆ์— ๋ฒˆ์—ญํ‚ค ํŒŒ์ผ์„ ์ €์žฅํ•˜๋„๋ก ๋งŒ๋“ค์–ด์ ธ ์žˆ์Šต๋‹ˆ๋‹ค.

1// /locales/index.js
2import translation_ko from './ko/translation';
3
4const locales = {
5 'ko': {
6 translation: translation_ko
7 }
8};
9
10export 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

๋ฐ์ด๋ธ”์˜ i18n ์‹œ์Šคํ…œ์ด ๋™์ž‘ํ•˜๋Š” ํ”„๋กœ์„ธ์Šค๋ฅผ ๋‚˜ํƒ€๋‚ธ ๊ทธ๋ฆผ. github์— Pull Request๋ฅผ ์˜ฌ๋ ธ์„ ๋•Œ ์–ด๋–ค ์ผ์ด ์ผ์–ด๋‚˜๋Š”์ง€ ๊ทธ๋ฆฌ๊ณ  ์žˆ๋‹ค. PR์ด ์˜ฌ๋ผ์˜จ ๋ธŒ๋žœ์น˜์— ๋ฒˆ์—ญํ‚ค๊ฐ€ ์ถ”๊ฐ€๋œ ๋‚ด์šฉ์ด ์žˆ์œผ๋ฉด DB์— ์ถ”๊ฐ€ํ•˜๊ณ  ์˜์–ด ๋ฒˆ์—ญ ์ƒํƒœ๋ฅผ ์ฒดํฌํ•œ๋‹ค. ์˜์–ด ๋ฒˆ์—ญ์ด ์™„๋ฃŒ๋œ ๊ฒฝ์šฐ PR์„ ์Šน์ธํ•œ๋‹ค. ์ฒซ ๋ฒˆ์งธ๋กœ 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๊ณผ๋Š” ํฌ๊ฒŒ ์—ฐ๊ด€์ด ์—†์–ด ์ƒ๋žตํ•˜๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

Merge

์ด์ œ Pull Request๊ฐ€ Merge๋œ ํ›„ Build๊ฐ€ ์ผ์–ด๋‚˜๊ธฐ ์ „๊นŒ์ง€์˜ ๊ณผ์ •์„ ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๋ฒˆ์—ญ์ด ํ•„์š”ํ•œ Dashboard๋ฅ˜์˜ ์„œ๋น„์Šค๋“ค์€ ๋Œ€๋ถ€๋ถ„ Docker Image Build๊นŒ์ง€ ํ•œ ํ˜ธํก์œผ๋กœ ์ผ์–ด๋‚˜์ง€๋งŒ, ํŽธ์˜์ƒ build ์ „๊นŒ์ง€์˜ ์ž‘์—…๋งŒ ๋จผ์ € ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ํ๋ฆ„์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. i18n ์‹œ์Šคํ…œ์˜ merge ๊ณผ์ •์„ ๋‚˜ํƒ€๋‚ธ ๊ทธ๋ฆผ. ํ’€ ๋ฆฌํ€˜์ŠคํŠธ๊ฐ€ merge๋˜๋ฉด ๋ฒˆ์—ญํ‚ค์˜ ๋ธŒ๋žœ์น˜ ์ •๋ณด๋„ ๋ชจ๋‘ master๋กœ ๋ณ€๊ฒฝํ•˜๊ณ  ์‚ญ์ œ๋œ ํ‚ค๋ฅผ ์ง€์šด ํ›„์—, ํ˜„์žฌ์˜ ๋ฒˆ์—ญํ‚ค ์ „๋ถ€๋ฅผ json ํ˜•ํƒœ๋กœ s3์— ์ €์žฅํ•œ๋‹ค.

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 ๊ณผ์ •์„ ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. Build ๊ณผ์ •์€ ๊ฐ„๋‹จํ•˜์—ฌ ๋ณ„๋„๋กœ ๊ทธ๋ฆผ์„ ์ฒจ๋ถ€ํ•˜์ง€ ์•Š๊ณ  ๊ธ€๋กœ ์„ค๋ช…ํ•˜๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. merge๋ฅผ ์„ค๋ช…ํ•  ๋•Œ merge์™€ build๋Š” ํŽธ์˜์ƒ ๋‚˜๋ˆ ์„œ ์„ค๋ช…ํ•ด ๋“œ๋ ธ์ง€๋งŒ ์‹ค์ œ Jenkins์—์„œ๋Š” ํ•˜๋‚˜์˜ Job ๋‚ด๋ถ€์—์„œ ๊ฐ™์ด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. Build ์ง์ „์— ๋งŒ๋“  ์Šค๋ƒ…์ƒท์˜ ๋ฒ„์ „์„ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ๋„ฃ์–ด์„œ Docker Image Build๊ฐ€ ์ผ์–ด๋‚ฉ๋‹ˆ๋‹ค.

1# DABLE I18N VERSION
2ARG I18N_VERSION
3ENV I18N_VERSION ${I18N_VERSION}
4
5## BUILD
6RUN 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์ด ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

Live

live server์—์„œ i18n system์ด ๋™์ž‘ํ•˜๋Š” ๊ตฌ์กฐ๋ฅผ ๋‚˜ํƒ€๋‚ธ ๊ทธ๋ฆผ. i18n-dashboard๋Š” 10๋ถ„๋งˆ๋‹ค Redis์— ๋ฒˆ์—ญ ํ‚ค ์ „์ฒด๋ฅผ ๋ฐ€์–ด๋„ฃ๊ณ , ๊ฐ ์„œ๋ฒ„๋Š” 1๋ถ„๋งˆ๋‹ค ๋ ˆ๋””์Šค๋ฅผ ํ™•์ธํ•˜์—ฌ ๋ฒˆ์—ญํ‚ค์— ์ˆ˜์ •์ด ์ƒ๊ฒผ์œผ๋ฉด ํ‚ค๋ฅผ ๋ฐ›์•„์™€์„œ ์ˆ˜์ •ํ•จ. ๊ทธ๋ฆฌ๊ณ  i18n-dashboard๋Š” ๋งค ์›”์š”์ผ๋งˆ๋‹ค ๋ฒˆ์—ญ์ด ์™„๋ฃŒ๋˜์ง€ ์•Š์€ ์„œ๋น„์Šค์˜ ๋ฒˆ์—ญ ํ˜„ํ™ฉ์„ ํผ์„ผํŠธ๋กœ ๊ณ„์‚ฐํ•˜์—ฌ ์Šฌ๋ž™์— ์ „์†กํ•œ๋‹ค. ๋นŒ๋“œ๋œ Docker Image๋ฅผ ๊ฐ€์ ธ๋‹ค๊ฐ€ live server๋ฅผ ์ผค ๋•Œ, /locales/ ํด๋” ๋‚ด๋ถ€์— ์–ธ์–ด๋ณ„๋กœ ์ฐข์–ด์ ธ ์žˆ๋Š” ๋ฒˆ์—ญ ํŒŒ์ผ์„ ๋ชจ๋‘ ๋ชจ์•„์„œ ํ•˜๋‚˜๋กœ ๋งŒ๋“ค์–ด public/dist/locale ํด๋”์— ๋„ฃ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ ์ดํ›„ ๋‹ค๋ฅธ ์–ธ์–ด์˜ ๋ฒˆ์—ญํ‚ค๊ฐ€ ์ถ”๊ฐ€๋˜๊ฑฐ๋‚˜, ๊ธฐ์กด์˜ ๋ฒˆ์—ญํ‚ค๊ฐ€ ์ˆ˜์ •๋œ ๊ฒฝ์šฐ๋ฅผ ์œ„ํ•ด ์ฃผ๊ธฐ์ ์œผ๋กœ ๋™๊ธฐํ™” ์ž‘์—…์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋™๊ธฐํ™” ์ž‘์—…์€ ์ด ์„ธ ๋ถ€๋ถ„์œผ๋กœ ๋‚˜๋ˆ„์–ด ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  1. key_sync i18n-dashboard๋Š” 10๋ถ„๋งˆ๋‹ค ๋ชจ๋“  ์„œ๋น„์Šค์˜ ๋ฒˆ์—ญํ‚ค๋ฅผ DB์—์„œ ๊ฐ€์ ธ์™€์„œ Dable ๋‚ด๋ถ€์—์„œ ๊ณต์šฉ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” Elasticache(redis)์— ์ง‘์–ด๋„ฃ์Šต๋‹ˆ๋‹ค. Redis์— ๋„ฃ์„ ๋•Œ์˜ Key๋Š” ์„œ๋น„์Šค๋ช…๊ณผ ๋ฒˆ์—ญํ‚ค๋ช… ๋“ฑ์„ ์กฐํ•ฉํ•˜์—ฌ ๊ทœ์น™์ ์œผ๋กœ ๋งŒ๋“ค์–ด์ง‘๋‹ˆ๋‹ค.

  2. reload ๊ฐ ์„œ๋น„์Šค๋Š” 1๋ถ„๋งˆ๋‹ค i18n-dashboard์— ์„œ๋น„์Šค ๊ฐ€๋Šฅํ•œ ์–ธ์–ด ๋ชฉ๋ก์„ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค. ์„œ๋น„์Šค ๊ฐ€๋Šฅํ•œ ์–ธ์–ด ๋ชฉ๋ก์€ ํ˜„์žฌ ์„œ๋น„์Šค์— ๋“ฑ๋ก๋œ ๋ฒˆ์—ญํ‚ค์˜ ์–ธ์–ด ๊ฐ’์„ DISTINCT ํ•˜์—ฌ ๋ฐ›์•„์˜ค๋Š” ๊ฐ’์ž…๋‹ˆ๋‹ค. ์œ„์—์„œ ์–ธ๊ธ‰ํ•œ Redis์—์„œ ๋ฒˆ์—ญํ‚ค๋ฅผ ์ „๋ถ€ ๋ฐ›์•„์™€์„œ ์„œ๋น„์Šค ๊ฐ€๋Šฅํ•œ ์–ธ์–ด ๋ณ„๋กœ localํŒŒ์ผ๊ณผ ๋น„๊ตํ•˜์—ฌ ๋‹ฌ๋ผ์ง„ ์ ์„ ์ฒดํฌํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ ๋‹ฌ๋ผ์ง„ ์ ์ด ์กด์žฌํ•œ๋‹ค๋ฉด ๋‹ฌ๋ผ์ง„ ๋ถ€๋ถ„์„ ๊ฐฑ์‹ ํ•˜์—ฌ translation.js ํŒŒ์ผ์„ ์ƒˆ๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

  3. slack_reminder i18n-dashboard์—์„œ ๋งค์ฃผ ์›”์š”์ผ ์˜คํ›„ 3์‹œ์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์„œ๋น„์Šค(๊ทธ๋ฆผ์— ๋‚˜์˜จ RECO, MKT๋Š” ์ €ํฌ ๋Œ€์‹œ๋ณด๋“œ ์ค‘ ๊ฐ€์žฅ ๋งŽ์ด ์“ฐ์ด๋Š” ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค)์˜ ๋ฒˆ์—ญ ํ˜„ํ™ฉ์„ ๊ณ„์‚ฐํ•˜์—ฌ ํผ์„ผํŠธ๋กœ ๋‚˜ํƒ€๋‚ธ ํ›„ ์Šฌ๋ž™์— ์ „์†กํ•˜์—ฌ ์•„์ง ๋ฒˆ์—ญ์„ ๋ชป ํ•œ ๋‹ด๋‹น์ž๋“ค์ด ์žŠ๊ณ  ๋„˜์–ด๊ฐ€์ง€ ์•Š๋„๋ก ๋•์Šต๋‹ˆ๋‹ค.

Live server์—์„œ์˜ i18n system์„ ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค. ๋ฐฐํฌ ๊ณผ์ •๊ณผ Live ๊ณผ์ •์˜ ๋™์ž‘์„ ์„ค๋ช…ํ•จ์œผ๋กœ Dable์˜ i18n ์‹œ์Šคํ…œ์— ๋Œ€ํ•œ ์ „๋ฐ˜์ ์ธ ์„ค๋ช…์„ ๋ชจ๋‘ ๋งˆ์ณค์Šต๋‹ˆ๋‹ค.

๋งˆ์น˜๋ฉฐ

์ง€๊ธˆ๊นŒ์ง€ i18n์ด ๋ฌด์—‡์ธ์ง€, i18next๋Š” ๋˜ ๋ฌด์—‡์ธ์ง€, Dable์—์„œ๋Š” i18n ์‹œ์Šคํ…œ์„ ์–ด๋–ป๊ฒŒ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š”์ง€๋ฅผ ๋ง์”€๋“œ๋ ธ์Šต๋‹ˆ๋‹ค. ํ˜น์‹œ ์„œ๋น„์Šค์˜ ๋ฒˆ์—ญ ์‹œ์Šคํ…œ ๋„์ž…์„ ๊ณ ๋ฏผํ•˜๊ณ  ๊ณ„์…จ๋˜ ๋ถ„์ด ์ด ๊ธ€์„ ๋ณด์‹œ๊ณ  ์กฐ๊ธˆ์ด๋‚˜๋งˆ ๋„์›€์„ ์–ป์œผ์…จ์œผ๋ฉด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค. ์•„์ง Dable์˜ i18n ์‹œ์Šคํ…œ๋„ ์™„๋ฒฝํžˆ ์™„์„ฑ๋œ ๊ฒƒ์ด ์•„๋‹™๋‹ˆ๋‹ค. ๋ฒˆ์—ญํ‚ค sync ๊ณผ์ • ๊ณ ๋„ํ™”, ๋ฒˆ์—ญ ํŒŒ์ผ์˜ ๋”์šฑ ์ปดํŒฉํŠธํ•œ ์„œ๋น™ ๋“ฑ์˜ ๋ชฉํ‘œ๋ฅผ ์žก๊ณ  ์„œ๋น„์Šค๋ฅผ ๊ฐœ์„ ํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค. ์•ž์œผ๋กœ ๋ฐ์ด๋ธ”์ด ์ง„์ถœํ•œ ๊ตญ๊ฐ€๊ฐ€ ๋งŽ์•„์ง€๋ฉด i18n๋„ ๋ณ€๋ชจํ•  ์ผ์ด ์ƒ๊ธธ ํ…๋ฐ ๊ทธ๋•Œ ๋˜ ์ƒˆ๋กœ์šด ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๊ฐ€์ง€๊ณ  ํฌ์ŠคํŒ…์œผ๋กœ ๋Œ์•„์˜ค๊ฒ ์Šต๋‹ˆ๋‹ค.

ยฉ 2023 by Raf. All rights reserved.