์ž‘๊ฐ€:
    ์ƒ์„ฑ:2026-09-02๋งˆ์ง€๋ง‰ ์—…๋ฐ์ดํŠธ:2026-10-03

    ICU MessageFormat: ๊ตฌ๋ฌธ๊ณผ ์ž์ฃผ ๊ฒช๋Š” ํ•จ์ •

    ICU MessageFormat์€ ๋ฒˆ์—ญ ๋ฌธ์ž์—ด ์ž์ฒด์— ์กฐ๊ฑด ๋ถ„๊ธฐ ๋กœ์ง(๋ณต์ˆ˜ํ˜•, ์„ฑ๋ณ„์— ๋”ฐ๋ฅธ ํ˜•ํƒœ, ์ˆซ์ž ๋ฐ ๋‚ ์งœ ์„œ์‹)์„ ์ง์ ‘ ํฌํ•จํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ฃผ๋Š” ๋ฌธ์ž์—ด ๊ตฌ๋ฌธ ๊ทœ๊ฒฉ์ž…๋‹ˆ๋‹ค. ๋ฌธ๋ฒ• ์ฒ˜๋ฆฌ๋Š” if (count === 1)์„ ์ž‘์„ฑํ•˜๋Š” ๊ฐœ๋ฐœ์ž๊ฐ€ ์•„๋‹ˆ๋ผ ๋ฒˆ์—ญ๊ฐ€์˜ ์˜์—ญ์ด์–ด์•ผ ํ•œ๋‹ค๋Š” ์›์น™์— ๊ธฐ๋ฐ˜ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ธ€์—์„œ๋Š” ๊ธฐ๋ณธ ๊ตฌ๋ฌธ, ๋‹จ์ˆœํ•œ ๊ตฌํ˜„์„ ๋ง๊ฐ€๋œจ๋ฆฌ๋Š” ์–ธ์–ด๋ณ„ ํŠน์ง•, ๊ทธ๋ฆฌ๊ณ  JavaScript ์ƒํƒœ๊ณ„๊ฐ€ ์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์„ ์‚ดํŽด๋ด…๋‹ˆ๋‹ค.

    ๋ชฉ์ฐจ

    ๋ฌธ์ œ ์ƒํ™ฉ ์‚ดํŽด๋ณด๊ธฐ

    ๋Œ€๋ถ€๋ถ„์˜ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ฒ˜์Œ ์ž‘์„ฑํ•˜๋Š” ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

    ts
    const label = count + " " + (count === 1 ? t("item") : t("items"));
    

    ์ด ์ฝ”๋“œ๋Š” ์˜์–ด์—์„œ๋Š” ์ž˜ ๋™์ž‘ํ•˜์ง€๋งŒ ๋‹ค๋ฅธ ๊ฑฐ์˜ ๋ชจ๋“  ์–ธ์–ด์—์„œ๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

    • ๋Ÿฌ์‹œ์•„์–ด์™€ ํด๋ž€๋“œ์–ด์—์„œ๋Š” 2๊ฐœ๊ฐ€ ์•„๋‹Œ 3~4๊ฐœ์˜ ํ˜•ํƒœ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
    • ํ•œ๊ตญ์–ด์™€ ์ผ๋ณธ์–ด์—์„œ๋Š” ๋ณต์ˆ˜ ๊ตฌ๋ถ„์ด ํ•„์š” ์—†์œผ๋ฉฐ, ๋ฌธ์ž์—ด ๊ฒฐํ•ฉ์œผ๋กœ ๋ถ™์ธ ๊ณต๋ฐฑ์ด ๋ถ€์ž์—ฐ์Šค๋Ÿฌ์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์•„๋ž์–ด์—์„œ๋Š” 6๊ฐœ์˜ ํ˜•ํƒœ๊ฐ€ ํ•„์š”ํ•˜๋ฉฐ, ์ˆซ์ž ์ž์ฒด๋„ ํ•ด๋‹น ๋กœ์ผ€์ผ์˜ ์ˆซ์ž ํ‘œ๊ธฐ ์ฒด๊ณ„๋กœ ๋ Œ๋”๋ง๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    • ํ”„๋ž‘์Šค์–ด์—์„œ๋Š” ํŠน์ • ๋ฌธ์žฅ ๋ถ€ํ˜ธ ์•ž์— ์ค„๋ฐ”๊ฟˆ ์—†๋Š” ๊ณต๋ฐฑ(non-breaking space)์„ ๋„ฃ์–ด์•ผ ํ•˜๋Š”๋ฐ, + " " ๊ฒฐํ•ฉ ๋ฐฉ์‹์ด ์ด๋ฅผ ๊นจ๋œจ๋ฆฝ๋‹ˆ๋‹ค.

    ๋” ๊ทผ๋ณธ์ ์ธ ๋ฌธ์ œ๋Š” ๋ฌธ์žฅ์ด ์—ฌ๋Ÿฌ ์กฐ๊ฐ์œผ๋กœ ํŒŒํŽธํ™”๋œ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. ๋ฒˆ์—ญ๊ฐ€๋Š” ๋ฌธ๋งฅ์ด ์—†๋Š” item๊ณผ items๋งŒ์„ ๋ณด๊ฒŒ ๋˜๋ฉฐ, ๋ฌธ์žฅ์˜ ์–ด์ˆœ์„ ์œ ์—ฐํ•˜๊ฒŒ ์กฐ์ •ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ICU MessageFormat์€ ์ „์ฒด ๋ฌธ์žฅ์„ ๋‹จ์ผ ๋ฒˆ์—ญ ๋ฌธ์ž์—ด๋กœ ์œ ์ง€ํ•˜๋ฉด์„œ ๋ฒˆ์—ญ๊ฐ€์—๊ฒŒ ๋ถ„๊ธฐ ์—ฐ์‚ฐ์ž๋ฅผ ์ œ๊ณตํ•˜์—ฌ ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค.

    ๋‹จ์ˆœ ์ธ์ž ๋ณด๊ฐ„

    ๊ฐ€์žฅ ์ž‘์€ ๋‹จ์œ„๋Š” ๋‹จ์ผ ์ค‘๊ด„ํ˜ธ๋กœ ๋ฌถ์ธ ํ”Œ๋ ˆ์ด์Šคํ™€๋”์ž…๋‹ˆ๋‹ค.

    text
    Hello, {name}!
    

    ํฌ๋งท ์‹œ์ ์— { name: "Alice" }๋ฅผ ๋„˜๊ฒจ์ฃผ๋ฉด Hello, Alice!๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ์ค‘๊ด„ํ˜ธ๋Š” ICU์—์„œ ์œ ์ผํ•œ ํŠน์ˆ˜ ๋ฌธ์ž์ž…๋‹ˆ๋‹ค. ๋ฌธ์ž ๊ทธ๋Œ€๋กœ์˜ ์ค‘๊ด„ํ˜ธ๋ฅผ ์ถœ๋ ฅํ•˜๋ ค๋ฉด ์ž‘์€๋”ฐ์˜ดํ‘œ๋กœ ๊ฐ์‹ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค(์˜ˆ: '{').

    ์ด๊ฒƒ์ด ๋ณด๊ฐ„(interpolation)์˜ ๊ธฐ๋ณธ ์›๋ฆฌ์ด๋ฉฐ, ICU์˜ ๋‹ค๋ฅธ ๋ชจ๋“  ๊ธฐ๋Šฅ์€ ์ด ์œ„์— ๊ตฌ์ถ•๋ฉ๋‹ˆ๋‹ค.

    ๋ณต์ˆ˜ํ˜• (plural)

    plural์€ ์ˆซ์ž๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์•Œ๋งž์€ ๋ถ„๊ธฐ๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

    text
    {count, plural,
      one {You have one unread message}
      other {You have # unread messages}
    }
    

    ๋ฐ˜๋“œ์‹œ ์•Œ์•„๋‘์–ด์•ผ ํ•  ์„ธ ๊ฐ€์ง€ ๊ทœ์น™:

    • # ๊ธฐํ˜ธ๋Š” ๋กœ์ผ€์ผ ์„œ์‹์ด ์ ์šฉ๋œ count ๊ฐ’์œผ๋กœ ์น˜ํ™˜๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด 1234๋Š” en-US์—์„œ๋Š” 1,234๊ฐ€ ๋˜๊ณ  ko-KR์—์„œ๋„ 1,234๋กœ ์„œ์‹ํ™”๋ฉ๋‹ˆ๋‹ค.
    • other๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค. ๋ชจ๋“  ICU ๊ตฌํ˜„์ฒด๋Š” other๊ฐ€ ๋ˆ„๋ฝ๋˜๋ฉด ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ฑฐ๋‚˜ ๊ฒ€์ฆ์— ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค. ์–ด๋–ค ์นดํ…Œ๊ณ ๋ฆฌ์—๋„ ์ผ์น˜ํ•˜์ง€ ์•Š์„ ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ํด๋ฐฑ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
    • =0, =1 ๋“ฑ์€ ์ •ํ™•ํ•œ ๊ฐ’๊ณผ ์ผ์น˜ํ•˜๋ฉฐ, CLDR ์นดํ…Œ๊ณ ๋ฆฌ๋ณด๋‹ค ๋จผ์ € ํ‰๊ฐ€๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” one์„ ๋Œ€์ฒดํ•˜๋Š” ์šฉ๋„๊ฐ€ ์•„๋‹ˆ๋ผ "๋ฉ”์‹œ์ง€๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค"์™€ ๊ฐ™์€ ํŠน์ˆ˜ํ•œ ๋ฌธ๊ตฌ๋ฅผ ํ‘œํ˜„ํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
    text
    {count, plural,
      =0 {No unread messages}
      one {One unread message}
      other {# unread messages}
    }
    

    offset

    offset:n์€ ์นดํ…Œ๊ณ ๋ฆฌ ํŒ๋ณ„ ๋ฐ # ์น˜ํ™˜์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์ „์— ์ˆซ์ž์—์„œ n์„ ์ฐจ๊ฐํ•ฉ๋‹ˆ๋‹ค. "์•จ๋ฆฌ์Šค ์™ธ 3๋ช…์ด ์ข‹์•„ํ•ฉ๋‹ˆ๋‹ค"์™€ ๊ฐ™์€ ํŒจํ„ด์— ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

    text
    {count, plural, offset:1
      =0 {No one liked this}
      =1 {{name} liked this}
      one {{name} and one other liked this}
      other {{name} and # others liked this}
    }
    

    count: 4๊ฐ€ ์ „๋‹ฌ๋˜๋ฉด # ์ž๋ฆฌ์— 3์ด ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค. offset์€ ์œ ์šฉํ•˜์ง€๋งŒ ๋Ÿฐํƒ€์ž„์— ๋”ฐ๋ผ ์ง€์› ํŽธ์ฐจ๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ ์‚ฌ์šฉ ์ „ ํ™•์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

    ์–ธ์–ด๋งˆ๋‹ค ๋‹ค๋ฅธ ๋ณต์ˆ˜ํ˜• ์นดํ…Œ๊ณ ๋ฆฌ

    ๊ฐ€์žฅ ๋งŽ์€ ์˜คํ•ด๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค. zero, one, two, few, many, other ์นดํ…Œ๊ณ ๋ฆฌ ์ด๋ฆ„์€ ๋ชจ๋“  ์–ธ์–ด์— ๋™์ผํ•˜๊ฒŒ ์ ์šฉ๋˜๋Š” ๊ณตํ†ต ๊ทœ๊ฒฉ์ด ์•„๋‹™๋‹ˆ๋‹ค. ๊ฐ ๋กœ์ผ€์ผ์€ CLDR ๋ณต์ˆ˜ํ˜• ๊ทœ์น™์— ์ •์˜๋œ ์ผ๋ถ€๋งŒ์„ ์‚ฌ์šฉํ•˜๋ฉฐ, ์ด๋Š” ์ˆ˜ํ•™์  ์ง๊ด€์ด ์•„๋‹Œ ๋ฌธ๋ฒ• ๊ทœ์น™์— ๊ทผ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.

    ์–ธ์–ดํƒœ๊ทธ์‚ฌ์šฉ ์นดํ…Œ๊ณ ๋ฆฌ์ด ๊ฐœ์ˆ˜
    ํ•œ๊ตญ์–ดkoother1
    ์ผ๋ณธ์–ดjaother1
    ์ค‘๊ตญ์–ดzhother1
    ์˜์–ดenone, other2
    ๋…์ผ์–ดdeone, other2
    ํ”„๋ž‘์Šค์–ดfrone, many, other3
    ์ฒด์ฝ”์–ดcsone, few, many, other4
    ํด๋ž€๋“œ์–ดplone, few, many, other4
    ๋Ÿฌ์‹œ์•„์–ดruone, few, many, other4
    ์•„๋ž์–ดarzero, one, two, few, many, other6
    ์›จ์ผ์Šค์–ดcyzero, one, two, few, many, other6

    ์ฃผ์˜ํ•ด์•ผ ํ•  ๋‘ ๊ฐ€์ง€ ๊ฒฐ๊ณผ:

    • one์ด ์ˆซ์ž "1"๋งŒ์„ ์˜๋ฏธํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋Ÿฌ์‹œ์•„์–ด์—์„œ one์€ 11๋กœ ๋๋‚˜๋Š” ๊ฒฝ์šฐ๋ฅผ ์ œ์™ธํ•˜๊ณ  1, 21, 31, 101 ๋“ฑ 1๋กœ ๋๋‚˜๋Š” ๋ชจ๋“  ์ˆ˜๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ํ”„๋ž‘์Šค์–ด์—์„œ๋Š” 0๋„ one์— ์†ํ•ฉ๋‹ˆ๋‹ค.
    • ์˜์–ด ์›๋ฌธ์— ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•ด๋„ ๋‹ค๋ฅธ ์–ธ์–ด์— ์ž๋™์œผ๋กœ ์ ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์˜์–ด ๋ฉ”์‹œ์ง€๋Š” one๊ณผ other๋งŒ ์žˆ์œผ๋ฉด ์ถฉ๋ถ„ํ•˜์ง€๋งŒ, ํด๋ž€๋“œ์–ด ๋ฒˆ์—ญ์€ 4๊ฐœ์˜ ๋ถ„๊ธฐ๊ฐ€ ํ•„์š”ํ•˜๋ฉฐ ์ด ๊ตฌ์กฐ๋Š” ํด๋ž€๋“œ์–ด ๋ฌธ์ž์—ด ์•ˆ์— ์ง์ ‘ ์ •์˜๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ๋กœ์ผ€์ผ์ด ๋™์ผํ•œ ํ‚ค ๊ตฌ์กฐ๋ฅผ ๊ฐ–๋„๋ก ๊ฐ•์ œํ•˜๋Š” ๋„๊ตฌ๋Š” ์ด๋Ÿฌํ•œ ๋‹ค๊ตญ์–ด ๊ตฌ์กฐ์—์„œ ์ถฉ๋Œ์„ ์ผ์œผํ‚ต๋‹ˆ๋‹ค.

    ๋ณ„๋„์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜ ์—†์ด ํ˜„์žฌ ๋Ÿฐํƒ€์ž„์˜ ๋™์ž‘ ๋ฐฉ์‹์„ ์ง์ ‘ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    ts
    new Intl.PluralRules("pl").select(2); // "few"
    new Intl.PluralRules("pl").select(5); // "many"
    new Intl.PluralRules("ru").select(21); // "one"
    new Intl.PluralRules("ar").select(0); // "zero"
    

    ๋ชจ๋“  ๋ชจ๋˜ ๋ธŒ๋ผ์šฐ์ €์™€ Node.js ํ™˜๊ฒฝ์˜ Intl.PluralRules์—๋Š” CLDR ๋ฐ์ดํ„ฐ๊ฐ€ ๊ธฐ๋ณธ ํƒ‘์žฌ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. CLDR ๋ณต์ˆ˜ํ˜•์„ ์ง€์›ํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์€ ๋Œ€๋ถ€๋ถ„ ๋‚ด๋ถ€์ ์œผ๋กœ ์ด ํ‘œ์ค€ API๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

    select ์™€ selectordinal

    select๋Š” ์„ฑ๋ณ„, ์—ญํ• , ์ƒํƒœ, ๊ตฌ๋… ๋“ฑ ์ž„์˜์˜ ๋ฌธ์ž์—ด์„ ๊ธฐ์ค€์œผ๋กœ ๋ถ„๊ธฐํ•ฉ๋‹ˆ๋‹ค.

    text
    {gender, select,
      female {She updated her profile}
      male {He updated his profile}
      other {They updated their profile}
    }
    

    ํ‚ค ๊ฐ’์€ ์ •ํ™•ํžˆ ์ผ์น˜ํ•ด์•ผ ํ•˜๋ฉฐ ์—ฌ๊ธฐ์„œ๋„ other๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค. ์–ธ์–ด๋งˆ๋‹ค ๋ฌธ์žฅ ๊ตฌ์กฐ์— ์˜ํ–ฅ์„ ๋ฏธ์น˜๋Š” ์—ด๊ฑฐํ˜• ๊ฐ’์ด ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์—, ๋ฌธ๋ฒ•์  ๋ณ€ํ™”๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ select๊ฐ€ ์ตœ์ ์˜ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

    selectordinal์€ plural๊ณผ ํ˜•ํƒœ๊ฐ€ ๊ฐ™์ง€๋งŒ, ๊ธฐ์ˆ˜๊ฐ€ ์•„๋‹Œ ์„œ์ˆ˜(1st, 2nd ๋“ฑ) ๊ทœ์น™์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.

    text
    {rank, selectordinal,
      one {#st place}
      two {#nd place}
      few {#rd place}
      other {#th place}
    }
    

    ์˜์–ด๋Š” ๊ธฐ์ˆ˜์—์„œ๋Š” 2๊ฐœ์˜ ์นดํ…Œ๊ณ ๋ฆฌ๋งŒ ์‚ฌ์šฉํ•˜์ง€๋งŒ, ์„œ์ˆ˜์—์„œ๋Š” 4๊ฐœ(1st, 2nd, 3rd, 4th)๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋น„๋Œ€์นญ์„ฑ ๋•Œ๋ฌธ์— ๋‘ ์—ฐ์‚ฐ์ž๊ฐ€ ๋ถ„๋ฆฌ๋˜์–ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

    ์ˆซ์ž, ๋‚ ์งœ ๋ฐ ์‹œ๊ฐ„ ์„œ์‹

    ICU๋Š” ๋ณด๊ฐ„๋˜๋Š” ๊ฐ’์„ ์ง์ ‘ ์„œ์‹ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    text
    Total: {price, number, currency}
    Published {publishedAt, date, long} at {publishedAt, time, short}
    Conversion: {rate, number, percent}
    

    ํ˜„๋Œ€์ ์ธ ๋ฐฉ์‹์€ ICU 60์—์„œ ๋„์ž…๋œ :: ์ ‘๋‘์‚ฌ ๊ธฐ๋ฐ˜์˜ ์Šค์ผˆ๋ ˆํ†ค(skeleton)์ž…๋‹ˆ๋‹ค. ์Šค์ผˆ๋ ˆํ†ค์€ ๊ธฐ์กด ๋ ˆ๊ฑฐ์‹œ ์Šคํƒ€์ผ์— ๋น„ํ•ด ํ›จ์”ฌ ํ’๋ถ€ํ•œ ํ‘œํ˜„๋ ฅ์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

    text
    {price, number, ::currency/EUR}
    {value, number, ::percent scale/100}
    {amount, number, ::compact-short}
    {distance, number, ::unit/kilometer unit-width-narrow}
    

    ์ƒํƒœ๊ณ„ ๋‚ด ์Šค์ผˆ๋ ˆํ†ค ์ง€์› ํ˜„ํ™ฉ์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋งˆ๋‹ค ์ƒ์ดํ•ฉ๋‹ˆ๋‹ค. FormatJS๋Š” ์ด๋ฅผ ์˜จ์ „ํžˆ ์ง€์›ํ•˜์ง€๋งŒ ๋‹ค๋ฅธ ๋Ÿฐํƒ€์ž„์€ ๋ ˆ๊ฑฐ์‹œ ํ˜•ํƒœ(number, currency๋‚˜ date, long)๋งŒ ์ง€์›ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์— ๋ฐฐํฌํ•˜๊ธฐ ์ „ ์‹ค์ œ ๋Ÿฐํƒ€์ž„์˜ ์ง€์› ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜์„ธ์š”.

    ์ค‘์ฒฉ ๊ตฌ์กฐ์™€ ๊ฐ€๋…์„ฑ

    ICU๋Š” ์กฐํ•ฉ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ๋ณต์ˆ˜ํ˜• ๋ถ„๊ธฐ ์•ˆ์— select๋ฅผ ํฌํ•จํ•˜๊ณ , ๊ทธ ์•ˆ์— ๋‹ค์‹œ ๋‹ค๋ฅธ ๋ณต์ˆ˜ํ˜•์„ ๋„ฃ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    text
    {hostGender, select,
      female {{guestCount, plural, offset:1
        =0 {{host} does not give a party}
        =1 {{host} invites {guest} to her party}
        other {{host} invites {guest} and # other people to her party}
      }}
      other {{guestCount, plural, offset:1
        =0 {{host} does not give a party}
        other {{host} invites {guest} and # other people to their party}
      }}
    }
    

    ์ด ์ฝ”๋“œ๋Š” ์ „ํ˜•์ ์ธ ICU ์˜ˆ์ œ์ด๋ฉด์„œ ๊ณผ๋„ํ•œ ์ค‘์ฒฉ์„ ํ”ผํ•ด์•ผ ํ•˜๋Š” ์ด์œ ๋ฅผ ์ž˜ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. 2๋‹จ๊ณ„๋ฅผ ๋„˜์–ด๊ฐ€๋ฉด ๋ฒˆ์—ญ๊ฐ€๊ฐ€ ๊ด„ํ˜ธ ์‹ค์ˆ˜๋ฅผ ์ €์ง€๋ฅด๊ธฐ ์‰ฌ์šฐ๋ฉฐ TMS ์—๋””ํ„ฐ์˜ ๋ณด์กฐ ๊ธฐ๋Šฅ๋„ ํ•œ๊ณ„์— ๋ถ€๋”ชํž™๋‹ˆ๋‹ค. ์ค‘์ฒฉ์€ ์ตœ๋Œ€ 2๋‹จ๊ณ„๊นŒ์ง€๋งŒ ์œ ์ง€ํ•˜๊ณ , 3๋‹จ๊ณ„๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด ๋ฌธ์žฅ์„ ๋‘ ๊ฐœ์˜ ๊ฐœ๋ณ„ ๋ฉ”์‹œ์ง€๋กœ ๋‚˜๋ˆ„๋Š” ๊ฒƒ์ด ํ˜„๋ช…ํ•ฉ๋‹ˆ๋‹ค.

    JavaScript ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ณ„ ICU ์ง€์› ํ˜„ํ™ฉ

    ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌICU ์ง€์› ์ˆ˜์ค€์‹ค์ œ ์ž‘์„ฑํ•˜๋Š” ์ฝ”๋“œ ํ˜•ํƒœ
    react-intl (FormatJS)๋„ค์ดํ‹ฐ๋ธŒ, ์™„์ „ ์ง€์›์Šค์ผˆ๋ ˆํ†ค ๋ฐ ๋ฆฌ์น˜ ํ…์ŠคํŠธ ํƒœ๊ทธ๋ฅผ ํฌํ•จํ•œ ICU ๋ฌธ์ž์—ด
    next-intl๋„ค์ดํ‹ฐ๋ธŒFormatJS์˜ intl-messageformat ๊ธฐ๋ฐ˜ ICU ๋ฌธ์ž์—ด
    i18nextํ”Œ๋Ÿฌ๊ทธ์ธ ํ•„์š”key_one / key_other ์ ‘๋ฏธ์‚ฌ ๋ฐ {{name}}; ICU๋Š” i18next-icu๋กœ ์ง€์›
    vue-i18n๋ถ€๋ถ„์  / ๋…์ž ๊ทœ๊ฒฉ{name} ๋ณด๊ฐ„ ๋ฐ ํŒŒ์ดํ”„๋กœ ๊ตฌ๋ถ„๋œ ๋ณต์ˆ˜ํ˜• ๋ถ„๊ธฐ
    Angular ($localize)ํ•˜์œ„ ์ง‘ํ•ฉํ…œํ”Œ๋ฆฟ ๋‚ด๋ถ€์˜ ICU plural / select, XLIFF๋กœ ์ถ”์ถœ

    ํ‘œ๋ฅผ ์ฝ์„ ๋•Œ ์ฐธ๊ณ ํ•ด์•ผ ํ•  ์‚ฌํ•ญ:

    • i18next์˜ ๊ธฐ๋ณธ ๊ตฌ๋ฌธ์€ ICU๊ฐ€ ์•„๋‹ˆ๋ฉฐ, ์ด๊ฒƒ์ด ๋‹จ์ ์„ ์˜๋ฏธํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. ์ ‘๋ฏธ์‚ฌ ํ‚ค(item_one, item_few)๋Š” Intl.PluralRules ์นดํ…Œ๊ณ ๋ฆฌ์— ๋งคํ•‘๋˜๋ฉฐ ํ”Œ๋žซ JSON์—์„œ ํŽธ์ง‘ํ•˜๊ธฐ ๋” ์ˆ˜์›”ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋งŒ select๋‚˜ ์ค‘์ฒฉ ๋ถ„๊ธฐ๋Š” ์ง€์›ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ i18next-icu๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์ฝ”๋“œ ๋ ˆ๋ฒจ์—์„œ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    • vue-i18n์˜ ํŒŒ์ดํ”„ ๋ณต์ˆ˜ํ˜•์€ ๊ธฐ๋ณธ์ ์œผ๋กœ CLDR ๊ทœ์น™ ๋Œ€์‹  ๋กœ์ผ€์ผ๋ณ„ ๊ทœ์น™ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋™์ž‘์—๋Š” ๋ฌธ์ œ๊ฐ€ ์—†์ง€๋งŒ ๊ทœ์น™์ด ๋ฐ์ดํ„ฐ๊ฐ€ ์•„๋‹Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ค์ •์— ์ข…์†๋ฉ๋‹ˆ๋‹ค.
    • FormatJS๋Š” JS ์ƒํƒœ๊ณ„์˜ ๋ ˆํผ๋Ÿฐ์Šค์ž…๋‹ˆ๋‹ค. JavaScript ์ง„์˜์—์„œ "ICU MessageFormat"์„ ์–ธ๊ธ‰ํ•  ๋•Œ๋Š” ํ†ต์ƒ FormatJS๊ฐ€ ์ˆ˜์šฉํ•˜๋Š” ํ‘œ์ค€์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
    • ์™„์ „ํ•œ ICU ์ง€์›์€ ๋ฒˆ๋“ค ํฌ๊ธฐ ๋ถ€๋‹ด์„ ์ˆ˜๋ฐ˜ํ•ฉ๋‹ˆ๋‹ค. ํŒŒ์„œ์™€ ์Šค์ผˆ๋ ˆํ†ค ์ฒ˜๋ฆฌ ๊ธฐ๋Šฅ์€ ์•ฝ 10KB์˜ ์••์ถ•๋œ JavaScript ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ICU๊ฐ€ JavaScript์— ๋งž์ง€ ์•Š๋Š” ์ด์œ ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.

    Intlayer์˜ ํ•ด๊ฒฐ ๋ฐฉ์‹

    Intlayer๋Š” ๋ฌธ์ž์—ด DSL์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์กฐ๊ฑด ๋ถ„๊ธฐ ์—ฐ์‚ฐ์ž๋“ค์ด ํƒ€์ž… ์•ˆ์ „ํ•œ ์ฝ˜ํ…์ธ  ์„ ์–ธ ํŒŒ์ผ ๋‚ด์˜ ํ•จ์ˆ˜๋กœ ์ œ๊ณต๋˜๋ฏ€๋กœ, ๊ตฌ์กฐ๊ฐ€ ๊ฒฌ๊ณ ํ•˜๊ฒŒ ์œ ์ง€๋˜๊ณ  ๊ฐ ๋กœ์ผ€์ผ์€ ์ž์‹ ์˜ ๋ฌธ๋ฒ•์— ํ•„์š”ํ•œ ์นดํ…Œ๊ณ ๋ฆฌ๋งŒ์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    **/*.content.ts
    import { plural, t, type Dictionary } from "intlayer";
    
    const openingsContent = {
      key: "total_openings",
      content: {
        totalOpenings: t({
          en: plural({
            one: "{{count}} opening",
            other: "{{count}} openings",
          }),
          ko: plural({
            other: "{{count}}๊ฐœ์˜ ์ฑ„์šฉ ๊ณต๊ณ ",
          }),
          pl: plural({
            one: "{{count}} oferta",
            few: "{{count}} oferty",
            many: "{{count}} ofert",
            other: "{{count}} ofert",
          }),
        }),
      },
    } satisfies Dictionary;
    
    export default openingsContent;
    
    **/*.tsx
    const { totalOpenings } = useIntlayer("total_openings");
    
    totalOpenings(5); // ํ•œ๊ตญ์–ด ๋กœ์ผ€์ผ โ†’ "5๊ฐœ์˜ ์ฑ„์šฉ ๊ณต๊ณ "
    

    ICU ๊ฐœ๋…๊ณผ์˜ ๋งคํ•‘ ๊ด€๊ณ„๋Š” ์ง๊ด€์ ์ž…๋‹ˆ๋‹ค.

    ICU ๊ตฌ์กฐIntlayer ๋Œ€์‘ ๋ฐฉ์‹
    {name}insert("Hello {{name}}") ๋˜๋Š” ์ž๋™ ๊ฐ์ง€
    {count, plural, โ€ฆ}plural({ one, few, many, other })
    {value, select, โ€ฆ}select({ draft, published, fallback })
    select์˜ ์„ฑ๋ณ„ ๋ถ„๊ธฐgender({ male, female, fallback })
    select์˜ ๋ถˆ๋ฆฌ์–ธ ๋ถ„๊ธฐcond({ true, false })
    ์ˆซ์ž ๋ฒ”์œ„(๋น„CLDR)enu({ "0": โ€ฆ, ">5": โ€ฆ, fallback: โ€ฆ })
    {n, number, ::currency/EUR}useCurrency()(1234.5, { currency: "EUR" })

    plural์€ ์นดํ…Œ๊ณ ๋ฆฌ ํŒ๋ณ„์„ Intl.PluralRules์— ์œ„์ž„ํ•˜๋ฏ€๋กœ ์•ž์„œ ์‚ดํŽด๋ณธ CLDR ํ…Œ์ด๋ธ”์ด ๊ทธ๋Œ€๋กœ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ˆซ์ž, ๋‚ ์งœ, ํ†ตํ™”, ๋ชฉ๋ก ์„œ์‹ํ™”๋Š” ๋ฉ”์‹œ์ง€ ๋ฌธ์ž์—ด์— ์„ž์ง€ ์•Š๊ณ  ํฌ๋งทํ„ฐ ํ›…์„ ํ†ตํ•ด ๊ด€์‹ฌ์‚ฌ๋ฅผ ๋ถ„๋ฆฌํ•˜์—ฌ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

    ๊ณ ๋ คํ•ด์•ผ ํ•  ํ•œ๊ณ„์ :

    • Intlayer๋Š” ๋นŒ๋“œ ๋‹จ๊ณ„๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ๋นŒ๋“œ ํƒ€์ž„์— ์ฝ˜ํ…์ธ  ์„ ์–ธ์„ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค. ๋Ÿฐํƒ€์ž„์— ๋‹จ์ˆœ JSON์„ ๋™์ ์œผ๋กœ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ชจ๋ธ๊ณผ๋Š” ๋ฐฉํ–ฅ์„ฑ์ด ๋‹ค๋ฆ…๋‹ˆ๋‹ค.
    • ํ˜„์žฌ plural ๋ถ„๊ธฐ ๋‚ด๋ถ€์— t()๋ฅผ ์ง์ ‘ ์ค‘์ฒฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. t() ๋‚ด๋ถ€์—์„œ plural์„ ํ˜ธ์ถœํ•˜๋Š” ๊ตฌ์กฐ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    • i18next์— ๋น„ํ•ด ์ƒ๋Œ€์ ์œผ๋กœ ์‹ ์ƒ ์ƒํƒœ๊ณ„์ด๋ฏ€๋กœ TMS ๋„๊ตฌ ์—ฐ๋™์ด๋‚˜ ์ปค๋ฎค๋‹ˆํ‹ฐ ๋ ˆํผ๋Ÿฐ์Šค๊ฐ€ ์ถ•์ ๋˜๋Š” ๋‹จ๊ณ„์— ์žˆ์Šต๋‹ˆ๋‹ค.

    ๊ธฐ์กด์— ์ž‘์„ฑ๋œ ICU ๋ฌธ์ž์—ด์ด ์žˆ๋Š” ํ”„๋กœ์ ํŠธ๋ฅผ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•  ๊ฒฝ์šฐ, react-intl ํ˜ธํ™˜ ์–ด๋Œ‘ํ„ฐ๊ฐ€ plural, select, selectordinal, #, ๋ ˆ๊ฑฐ์‹œ number / date / time ๊ตฌ๋ฌธ์„ ์ง์ ‘ ํ•ด์„ํ•ฉ๋‹ˆ๋‹ค. ์Šค์ผˆ๋ ˆํ†ค ๋ฐ offset:์€ ํ˜„์žฌ ํ•ด๋‹น ๋ฆฌ์กธ๋ฒ„์—์„œ ์ง€์›๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹œ ๊ฒ€ํ† ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. i18next ์–ด๋Œ‘ํ„ฐ๋Š” ์ ‘๋ฏธ์‚ฌ ํ˜•์‹(key_one, key_male)์„ Intl.PluralRules๋ฅผ ํ†ตํ•ด ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

    ํ”ํžˆ ๋ฒ”ํ•˜๋Š” ์‹ค์ˆ˜

    • ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์ฝ”๋“œ ๋‚ด์— ๋ณต์ˆ˜ํ˜• ์‚ผํ•ญ ์—ฐ์‚ฐ์ž๋ฅผ ํ•˜๋“œ์ฝ”๋”ฉํ•˜๋Š” ๊ฒƒ. count === 1 ? a : b ๊ตฌ๋ฌธ์€ ์œ„ ํ‘œ์— ์žˆ๋Š” 10๊ฐœ ์–ธ์–ด ์ค‘ 8๊ฐœ ์–ธ์–ด์—์„œ ์ž˜๋ชป๋œ ๋ฌธ๋ฒ•์„ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค. ์‚ผํ•ญ ์—ฐ์‚ฐ์ž๊ฐ€ ์ฝ”๋“œ์— ๊ณ ์ •๋˜๋ฉด ๋ฒˆ์—ญ๊ฐ€๊ฐ€ ์ด๋ฅผ ์ˆ˜์ •ํ•  ๋ฐฉ๋ฒ•์ด ์—†์Šต๋‹ˆ๋‹ค.
    • ๋ฒˆ์—ญ๋œ ํ…์ŠคํŠธ ์กฐ๊ฐ๋“ค์„ ๋ฌธ์ž์—ด๋กœ ์ด์–ด ๋ถ™์ด๋Š” ๊ฒƒ. ์–ด์ˆœ, ์„ฑ์ˆ˜ ์ผ์น˜, ๋ฌธ์žฅ ๋ถ€ํ˜ธ ์•ž๋’ค ๊ณต๋ฐฑ ๊ทœ์น™์€ ๋กœ์ผ€์ผ๋งˆ๋‹ค ๋‹ค๋ฆ…๋‹ˆ๋‹ค. ํ•ญ์ƒ ๋ฌธ์žฅ ๋‹จ์œ„๋กœ ์˜จ์ „ํ•˜๊ฒŒ ๋ฒˆ์—ญํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    • other๋ฅผ ๋น ๋œจ๋ฆฌ๋Š” ๊ฒƒ. ์ด๋Š” ๊ถŒ์žฅ ์‚ฌํ•ญ์ด ์•„๋‹ˆ๋ผ ํ‘œ์ค€ ๊ทœ๊ฒฉ์˜ ํ•„์ˆ˜ ์š”๊ตฌ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ํŒŒ์„œ๋Š” ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋ฉฐ, ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋„ ๋นˆ ๋ฌธ์ž์—ด๋งŒ ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค.
    • ๋ชจ๋“  ์–ธ์–ด์˜ ์นดํ…Œ๊ณ ๋ฆฌ ๊ตฌ์„ฑ์ด ๊ฐ™์„ ๊ฒƒ์ด๋ผ ๊ฐ€์ •ํ•˜๋Š” ๊ฒƒ. ์˜์–ด ์›๋ฌธ์ด one๊ณผ other 2๊ฐœ๋ผ๊ณ  ํ•ด์„œ ํด๋ž€๋“œ์–ด ๋ฒˆ์—ญ๋„ 2๊ฐœ์ผ ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค. ๊ฐ ์–ธ์–ด๊ฐ€ ๊ณ ์œ ํ•œ ๋ถ„๊ธฐ๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋กœ์ผ€์ผ๋ณ„ ์ฝ˜ํ…์ธ  ์„ ์–ธ ๊ฐ€์ด๋“œ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.
    • one ๋Œ€์‹  =1์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ. =1์€ ์ •ํ™•ํžˆ ์ˆซ์ž 1์—๋งŒ ๋งค์นญ๋ฉ๋‹ˆ๋‹ค. ๋Ÿฌ์‹œ์•„์–ด์—์„œ 21์€ one ์นดํ…Œ๊ณ ๋ฆฌ์— ์†ํ•˜์ง€๋งŒ =1์—๋Š” ๊ฒฐ์ฝ” ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
    • #์„ ๋ณต์ˆ˜ํ˜• ๋ถ„๊ธฐ ์™ธ๋ถ€์— ๋ฐฐ์น˜ํ•˜๋Š” ๊ฒƒ. #์€ plural์ด๋‚˜ selectordinal ๋‚ด๋ถ€์—์„œ๋งŒ ํŠน์ˆ˜ํ•œ ์น˜ํ™˜์ž๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ ์™ธ ์œ„์น˜์—์„œ๋Š” ๋‹จ์ˆœํ•œ ํ•ด์‹œ ๊ธฐํ˜ธ๋กœ ์ทจ๊ธ‰๋ฉ๋‹ˆ๋‹ค.
    • #์ด ์ด๋ฏธ ๋กœ์ผ€์ผ ์„œ์‹ํ™”๋˜์—ˆ์Œ์„ ๊ฐ„๊ณผํ•˜๋Š” ๊ฒƒ. ์ฒœ ๋‹จ์œ„ ๊ตฌ๋ถ„ ๊ธฐํ˜ธ๊ฐ€ ์—†๋Š” ์ˆœ์ˆ˜ ์ˆซ์ž๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์ธ์ž ์ด๋ฆ„์„ ์ง์ ‘ ์ง€์ •ํ•˜์—ฌ ๋ณด๊ฐ„ํ•˜์„ธ์š”.

    ๋” ์•Œ์•„๋ณด๊ธฐ

    ๋Œ“๊ธ€

    ์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ๋กœ ์˜๊ฒฌ์„ ๋‚˜๋ˆ ๋ณด์„ธ์š”.

    ๊ด€๋ จ ๊ฒŒ์‹œ๋ฌผ

    ์ตœ๊ทผ ๊ฒŒ์‹œ๋ฌผ