์ž‘๊ฐ€:
    ์ƒ์„ฑ:2024-12-06๋งˆ์ง€๋ง‰ ์—…๋ฐ์ดํŠธ:2026-09-27

    Intlayer๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Next.js 16 ์›น์‚ฌ์ดํŠธ ๋ฒˆ์—ญํ•˜๊ธฐ

    youtube.com
    ide.intlayer.org
    ide.intlayer.org
    intlayer-next-16-template.vercel.app

    GitHub์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ…œํ”Œ๋ฆฟ ๋ฐ ๋กœ์ผ€์ผ ๊ฒฝ๋กœ ์—†๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ…œํ”Œ๋ฆฟ์„ ํ™•์ธํ•˜์„ธ์š”.

    ๋ชฉ์ฐจ

    ๋Œ€์•ˆ๋ณด๋‹ค Intlayer๋ฅผ ์„ ํƒํ•ด์•ผ ํ•˜๋Š” ์ด์œ ๋Š” ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

    'next-intl' ๋˜๋Š” 'i18next'์™€ ๊ฐ™์€ ์ฃผ์š” ์†”๋ฃจ์…˜๊ณผ ๋น„๊ตํ•  ๋•Œ Intlayer๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ†ตํ•ฉ ์ตœ์ ํ™” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” ์†”๋ฃจ์…˜์ž…๋‹ˆ๋‹ค.

    Intlayer๋Š” ํšจ์œจ์ ์ธ ๋ Œ๋”๋ง์„ ์œ„ํ•ด ์„œ๋ฒ„ ๊ตฌ์„ฑ์š”์†Œ์™€ ์ž‘๋™ํ•˜๋„๋ก ์ตœ์ ํ™”๋˜์–ด ์žˆ์œผ๋ฉฐ Turbopack๊ณผ ์™„๋ฒฝํ•˜๊ฒŒ ํ˜ธํ™˜๋ฉ๋‹ˆ๋‹ค. ์ •์  ๋ Œ๋”๋ง์„ ์ฐจ๋‹จํ•˜์ง€ ์•Š์œผ๋ฉฐ ๋ฏธ๋“ค์›จ์–ด๋Š” ๋ฌผ๋ก  ๊ตญ์ œํ™” ํ™•์žฅ(i18n)์— ํ•„์š”ํ•œ ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

    Intlayer๋Š” Next.js 12, 13, 14, 15, 16๊ณผ ํ˜ธํ™˜๋ฉ๋‹ˆ๋‹ค. Next.js Pages Router๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ์ด ๊ฐ€์ด๋“œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”. ๋กœ์ผ€์ผ ๋ผ์šฐํŒ…์€ SEO, ๋ฒˆ๋“ค ํฌ๊ธฐ ๋ฐ ์„ฑ๋Šฅ์— ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋กœ์ผ€์ผ ๊ฒฝ๋กœ๊ฐ€ ์žˆ๋Š” ์„ค์ •๊ณผ ์—†๋Š” ์„ค์ • ๋ชจ๋‘ ์ง€์›๋˜๋ฉฐ ์ด ๊ฐ€์ด๋“œ์—์„œ ๋‹ค๋ฃน๋‹ˆ๋‹ค. ์•ฑ ๋ผ์šฐํ„ฐ๊ฐ€ ํฌํ•จ๋œ Next.js 12, 13, 14, 15์˜ ๊ฒฝ์šฐ ์ด ๊ฐ€์ด๋“œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

    ๋Œ€์šฉ๋Ÿ‰ JSON ํŒŒ์ผ์„ ํŽ˜์ด์ง€์— ๋กœ๋“œํ•˜๋Š” ๋Œ€์‹  ํ•„์š”ํ•œ ์ฝ˜ํ…์ธ ๋งŒ ๋กœ๋“œํ•˜์„ธ์š”. Intlayer๋Š” ๋ฒˆ๋“ค ๋ฐ ํŽ˜์ด์ง€ ํฌ๊ธฐ๋ฅผ ์ตœ๋Œ€ 50% ์ค„์ด๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

    ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ˜ํ…์ธ ์˜ ๋ฒ”์œ„๋ฅผ ์ง€์ •ํ•˜๋ฉด ๋Œ€๊ทœ๋ชจ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์œ ์ง€ ๊ด€๋ฆฌ๊ฐ€ ์šฉ์ดํ•ด์ง‘๋‹ˆ๋‹ค. ์ „์ฒด ์ฝ˜ํ…์ธ  ์ฝ”๋“œ๋ฒ ์ด์Šค๋ฅผ ๊ฒ€ํ† ํ•ด์•ผ ํ•˜๋Š” ์ •์‹ ์  ๋ถ€๋‹ด ์—†์ด ๋‹จ์ผ ๊ธฐ๋Šฅ ํด๋”๋ฅผ ๋ณต์ œํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ Intlayer๋Š” ์™„์ „ํžˆ ์œ ํ˜•๋˜์–ด ์ฝ˜ํ…์ธ ์˜ ์ •ํ™•์„ฑ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

    ์ฝ˜ํ…์ธ ๋ฅผ ๊ฐ™์€ ์œ„์น˜์— ๋ฐฐ์น˜ํ•˜๋ฉด LLM(๋Œ€ํ˜• ์–ธ์–ด ๋ชจ๋ธ)์— ํ•„์š”ํ•œ ์ปจํ…์ŠคํŠธ๊ฐ€ ์ค„์–ด๋“ญ๋‹ˆ๋‹ค. Intlayer์—๋Š” ๋ˆ„๋ฝ๋œ ๋ฒˆ์—ญ์„ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•œ CLI, LSP, MCP ๋ฐ agent skills, AI ์—์ด์ „ํŠธ๋ฅผ ์œ„ํ•œ ๊ฐœ๋ฐœ์ž ๊ฒฝํ—˜(DX)์„ ๋”์šฑ ์›ํ™œํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

    AI ๊ณต๊ธ‰์ž์˜ ๋น„์šฉ์œผ๋กœ ์„ ํƒํ•œ LLM์„ ์‚ฌ์šฉํ•˜์—ฌ CI/CD ํŒŒ์ดํ”„๋ผ์ธ์„ ๋ฒˆ์—ญํ•˜๋ ค๋ฉด ์ž๋™ํ™”๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. Intlayer๋Š” ๋˜ํ•œ ์ฝ˜ํ…์ธ  ์ถ”์ถœ์„ ์ž๋™ํ™”ํ•˜๋Š” ์ปดํŒŒ์ผ๋Ÿฌ์™€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋ฒˆ์—ญ์„ ๋•๋Š” ์›น ํ”Œ๋žซํผ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

    ๋Œ€๊ทœ๋ชจ JSON ํŒŒ์ผ์„ ๊ตฌ์„ฑ ์š”์†Œ์— ์—ฐ๊ฒฐํ•˜๋ฉด ์„ฑ๋Šฅ ๋ฐ ๋ฐ˜์‘์„ฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Intlayer๋Š” ๋นŒ๋“œ ์‹œ ์ฝ˜ํ…์ธ  ๋กœ๋”ฉ์„ ์ตœ์ ํ™”ํ•ฉ๋‹ˆ๋‹ค.

    Intlayer๋Š” ๋‹จ์ˆœํ•œ i18n ์†”๋ฃจ์…˜ ๊ทธ ์ด์ƒ์œผ๋กœ ๊ด€๋ฆฌ์— ๋„์›€์ด ๋˜๋Š” ์ž์ฒด ํ˜ธ์ŠคํŒ… ๋น„์ฃผ์–ผ ํŽธ์ง‘๊ธฐ ๋ฐ ์ „์ฒด CMS๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๊ตญ์–ด ์ฝ˜ํ…์ธ ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ œ๊ณตํ•˜์—ฌ ๋ฒˆ์—ญ๊ฐ€, ์นดํ”ผ๋ผ์ดํ„ฐ, ๊ธฐํƒ€ ํŒ€์›๊ณผ ์›ํ™œํ•˜๊ฒŒ ํ˜‘์—…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฝ˜ํ…์ธ ๋Š” ๋กœ์ปฌ ๋ฐ/๋˜๋Š” ์›๊ฒฉ์œผ๋กœ ์ €์žฅ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    Next.js ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ Intlayer ์„ค์ • ๋‹จ๊ณ„๋ณ„ ๊ฐ€์ด๋“œ

    1. ์˜์กด์„ฑ ์„ค์น˜

      npm์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•„์š”ํ•œ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜์„ธ์š”:

      bash
      npx intlayer init --interactive
      
      --interactive ํ”Œ๋ž˜๊ทธ๋Š” ์„ ํƒ ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค. AI ์—์ด์ „ํŠธ์ธ ๊ฒฝ์šฐ intlayer-cli init๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
      ์ด ๋ช…๋ น์€ ํ™˜๊ฒฝ์„ ๊ฐ์ง€ํ•˜๊ณ  ํ•„์š”ํ•œ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด:
      bash
      npm install intlayer next-intlayer
      
      • intlayer

        ๊ตฌ์„ฑ ๊ด€๋ฆฌ, ๋ฒˆ์—ญ, ์ฝ˜ํ…์ธ  ์„ ์–ธ, ํŠธ๋žœ์ŠคํŒŒ์ผ๋ง ๋ฐ CLI ๋ช…๋ น์–ด๋ฅผ ์œ„ํ•œ ๊ตญ์ œํ™” ๋„๊ตฌ๋ฅผ ์ œ๊ณตํ•˜๋Š” ํ•ต์‹ฌ ํŒจํ‚ค์ง€์ž…๋‹ˆ๋‹ค.

      • next-intlayer

        Intlayer๋ฅผ Next.js์™€ ํ†ตํ•ฉํ•˜๋Š” ํŒจํ‚ค์ง€์ž…๋‹ˆ๋‹ค. Next.js ๊ตญ์ œํ™”๋ฅผ ์œ„ํ•œ ์ปจํ…์ŠคํŠธ ํ”„๋กœ๋ฐ”์ด๋”์™€ ํ›…์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, Intlayer๋ฅผ Webpack ๋˜๋Š” Turbopack๊ณผ ํ†ตํ•ฉํ•˜๊ธฐ ์œ„ํ•œ Next.js ํ”Œ๋Ÿฌ๊ทธ์ธ๊ณผ ์‚ฌ์šฉ์ž์˜ ์„ ํ˜ธ ๋กœ์ผ€์ผ ๊ฐ์ง€, ์ฟ ํ‚ค ๊ด€๋ฆฌ, URL ๋ฆฌ๋””๋ ‰์…˜ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ํ”„๋ก์‹œ๋„ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

    2. ํ”„๋กœ์ ํŠธ ๊ตฌ์„ฑ

      ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ง€์—ญํ™”๋œ URL ๊ฒฝ๋กœ(์˜ˆ: /ko/about, /en/about)๋ฅผ ์‚ฌ์šฉํ• ์ง€, ์•„๋‹ˆ๋ฉด ๊ฒฝ๋กœ์— ๋กœ์ผ€์ผ ์„ธ๊ทธ๋จผํŠธ ์—†์ด ์ฝ˜ํ…์ธ ๋ฅผ ์ œ๊ณตํ• ์ง€(์˜ˆ: /about, ์ฟ ํ‚ค, ํ—ค๋” ๋˜๋Š” ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ํ†ตํ•ด ๊ฐ์ง€) ์„ ํƒํ•˜์„ธ์š”.

      ์•„ํ‚คํ…์ฒ˜

      ์ด ์•„ํ‚คํ…์ฒ˜์—์„œ๋Š” ์ง€์—ญํ™”๋œ ๋ชจ๋“  ํŽ˜์ด์ง€๊ฐ€ [locale] ๋™์  ๋ผ์šฐํŠธ ์„ธ๊ทธ๋จผํŠธ ์•„๋ž˜์— ์ค‘์ฒฉ๋ฉ๋‹ˆ๋‹ค. ๊ฐ ์–ธ์–ด๋งˆ๋‹ค ๊ณ ์œ ํ•œ ์ „์šฉ URL์ด ์ œ๊ณต๋˜๋ฏ€๋กœ SEO ๋ฐ ์ •์  ๋ Œ๋”๋ง์— ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค:

      bash
      .
      โ”œโ”€โ”€ src
      โ”‚   โ”œโ”€โ”€ app
      โ”‚   โ”‚   โ”œโ”€โ”€ [locale]
      โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ layout.tsx            # Intlayer ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ์œ„ํ•œ ๋กœ์ผ€์ผ ๋ ˆ์ด์•„์›ƒ
      โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ page.content.ts
      โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ page.tsx
      โ”‚   โ”‚   โ””โ”€โ”€ layout.tsx                # ์Šคํƒ€์ผ ๋ฐ ์ „์—ญ ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ์œ„ํ•œ ๋ฃจํŠธ ๋ ˆ์ด์•„์›ƒ
      โ”‚   โ”œโ”€โ”€ components
      โ”‚   โ”‚   โ”œโ”€โ”€ client-component-example.content.ts
      โ”‚   โ”‚   โ”œโ”€โ”€ ClientComponentExample.tsx
      โ”‚   โ”‚   โ”œโ”€โ”€ LocaleSwitcher
      โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ localeSwitcher.content.ts
      โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ LocaleSwitcher.tsx
      โ”‚   โ”‚   โ”œโ”€โ”€ server-component-example.content.ts
      โ”‚   โ”‚   โ””โ”€โ”€ ServerComponentExample.tsx
      โ”‚   โ””โ”€โ”€ proxy.ts
      โ”œโ”€โ”€ intlayer.config.ts
      โ”œโ”€โ”€ next.config.ts
      โ”œโ”€โ”€ package.json
      โ””โ”€โ”€ tsconfig.json
      

      ์„ค์ •

      ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ง€์›ํ•˜๋Š” ์–ธ์–ด๋ฅผ ์„ ์–ธํ•˜๊ธฐ ์œ„ํ•ด intlayer.config.ts ์„ค์ • ํŒŒ์ผ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค:

      intlayer.config.ts
      import { Locales, type IntlayerConfig } from "intlayer";
      
      const config: IntlayerConfig = {
        internationalization: {
          locales: [
            Locales.ENGLISH,
            Locales.FRENCH,
            Locales.SPANISH,
            // ๋‹ค๋ฅธ ๋กœ์ผ€์ผ๋“ค
          ],
          defaultLocale: Locales.ENGLISH,
        },
        routing: {
          mode: "prefix-no-default", // ๋˜๋Š” `prefix-all`
        },
      };
      
      export default config;
      

      ์•„ํ‚คํ…์ฒ˜

      ์ด ์•„ํ‚คํ…์ฒ˜์—์„œ๋Š” ๋ผ์šฐํŠธ์˜ URL ๊ฒฝ๋กœ์— [locale] ๋™์  ์„ธ๊ทธ๋จผํŠธ๊ฐ€ ํฌํ•จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ํŽ˜์ด์ง€๋Š” src/app/ ๋ฐ”๋กœ ์•„๋ž˜์— ์œ„์น˜ํ•˜๋ฉฐ ์ฟ ํ‚ค, ํ—ค๋” ๋˜๋Š” ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ํ†ตํ•ด ๋กœ์ผ€์ผ์ด ๊ฒฐ์ •๋ฉ๋‹ˆ๋‹ค. ๋Œ€์‹œ๋ณด๋“œ, ์‚ฌ๋‚ด ๋„๊ตฌ ๋˜๋Š” ์ธ์ฆ๋œ ์•ฑ์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค:

      bash
      .
      โ”œโ”€โ”€ src
      โ”‚   โ”œโ”€โ”€ app
      โ”‚   โ”‚   โ”œโ”€โ”€ layout.tsx
      โ”‚   โ”‚   โ”œโ”€โ”€ page.content.ts
      โ”‚   โ”‚   โ””โ”€โ”€ page.tsx
      โ”‚   โ”œโ”€โ”€ components
      โ”‚   โ”‚   โ”œโ”€โ”€ clientComponentExample
      โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ client-component-example.content.ts
      โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ ClientComponentExample.tsx
      โ”‚   โ”‚   โ”œโ”€โ”€ localeSwitcher
      โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ localeSwitcher.content.ts
      โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ LocaleSwitcher.tsx
      โ”‚   โ”‚   โ””โ”€โ”€ serverComponentExample
      โ”‚   โ”‚       โ”œโ”€โ”€ server-component-example.content.ts
      โ”‚   โ”‚       โ””โ”€โ”€ ServerComponentExample.tsx
      โ”‚   โ””โ”€โ”€ proxy.ts
      โ”œโ”€โ”€ intlayer.config.ts
      โ”œโ”€โ”€ next.config.ts
      โ”œโ”€โ”€ package.json
      โ””โ”€โ”€ tsconfig.json
      

      ์„ค์ •

      ๊ฒฝ๋กœ ์ ‘๋‘์‚ฌ ์—†์ด ์ฝ˜ํ…์ธ ๋ฅผ ์ œ๊ณตํ•˜๋ ค๋ฉด routing.mode ์†์„ฑ์„ "search-params"(์˜ˆ: /about?locale=ko) ๋˜๋Š” "no-prefix"๋กœ ์„ค์ •ํ•˜์„ธ์š”:

      intlayer.config.ts
      import { Locales, type IntlayerConfig } from "intlayer";
      
      const config: IntlayerConfig = {
        internationalization: {
          locales: [
            Locales.ENGLISH,
            Locales.FRENCH,
            Locales.SPANISH,
            // ๋‹ค๋ฅธ ๋กœ์ผ€์ผ๋“ค
          ],
          defaultLocale: Locales.ENGLISH,
        },
        routing: {
          mode: "search-params", // ๋˜๋Š” `no-prefix` - ๋ฏธ๋“ค์›จ์–ด ๊ฐ์ง€์— ์œ ์šฉํ•จ
        },
      };
      
      export default config;
      
      ์ด ๊ตฌ์„ฑ ํŒŒ์ผ์„ ํ†ตํ•ด ์ง€์—ญํ™”๋œ URL, ํ”„๋ก์‹œ ๋ฆฌ๋””๋ ‰์…˜, ์ฟ ํ‚ค ์ด๋ฆ„, ์ฝ˜ํ…์ธ  ์„ ์–ธ์˜ ์œ„์น˜ ๋ฐ ํ™•์žฅ์ž ์„ค์ •, ์ฝ˜์†”์—์„œ Intlayer ๋กœ๊ทธ ๋น„ํ™œ์„ฑํ™” ๋“ฑ ๋‹ค์–‘ํ•œ ์„ค์ •์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ชฉ๋ก์€ ๊ตฌ์„ฑ ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.
    3. Next.js ๊ตฌ์„ฑ์— Intlayer ํ†ตํ•ฉํ•˜๊ธฐ

      Next.js ์„ค์ •์„ Intlayer์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋„๋ก ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค:

      next.config.ts
      import type { NextConfig } from "next";
      import { withIntlayer } from "next-intlayer/server";
      
      const nextConfig: NextConfig = {/* ์—ฌ๊ธฐ์— ๊ตฌ์„ฑ ์˜ต์…˜์„ ์ž…๋ ฅํ•˜์„ธ์š” */};
      
      export default withIntlayer(nextConfig);
      
      withIntlayer() Next.js ํ”Œ๋Ÿฌ๊ทธ์ธ์€ Intlayer๋ฅผ Next.js์™€ ํ†ตํ•ฉํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ด ํ”Œ๋Ÿฌ๊ทธ์ธ์€ ์ฝ˜ํ…์ธ  ์„ ์–ธ ํŒŒ์ผ์˜ ๋นŒ๋“œ๋ฅผ ๋ณด์žฅํ•˜๊ณ  ๊ฐœ๋ฐœ ๋ชจ๋“œ์—์„œ ์ด๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ Webpack ๋˜๋Š” Turbopack ํ™˜๊ฒฝ ๋‚ด์—์„œ Intlayer ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ์ถ”๊ฐ€๋กœ, ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•œ ๋ณ„์นญ(alias)์„ ์ œ๊ณตํ•˜๋ฉฐ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์™€์˜ ํ˜ธํ™˜์„ฑ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

      withIntlayer() ํ•จ์ˆ˜๋Š” ํ”„๋กœ๋ฏธ์Šค ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. ๋นŒ๋“œ๊ฐ€ ์‹œ์ž‘๋˜๊ธฐ ์ „์— Intlayer ์‚ฌ์ „์„ ์ค€๋น„ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ํ”Œ๋Ÿฌ๊ทธ์ธ๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ ค๋ฉด awaitํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ์‹œ:

      ts
      const nextConfig = await withIntlayer(nextConfig);
      const nextConfigWithOtherPlugins = withOtherPlugins(nextConfig);
      
      export default nextConfigWithOtherPlugins;
      

      ๋™๊ธฐ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด withIntlayerSync() ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ์‹œ:

      ts
      const nextConfig = withIntlayerSync(nextConfig);
      const nextConfigWithOtherPlugins = withOtherPlugins(nextConfig);
      
      export default nextConfigWithOtherPlugins;
      

      Intlayer๋Š” ๋ช…๋ น์ค„ ํ”Œ๋ž˜๊ทธ --webpack, --turbo ๋˜๋Š” --turbopack๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ํ˜„์žฌ Next.js ๋ฒ„์ „์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ”„๋กœ์ ํŠธ๊ฐ€ webpack ๋˜๋Š” Turbopack์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š”์ง€ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•ฉ๋‹ˆ๋‹ค.

      next>=16๋ถ€ํ„ฐ Rspack์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, Turbopack์„ ๋น„ํ™œ์„ฑํ™”ํ•˜์—ฌ Intlayer๊ฐ€ webpack ๊ตฌ์„ฑ์„ ์‚ฌ์šฉํ•˜๋„๋ก ๋ช…์‹œ์ ์œผ๋กœ ๊ฐ•์ œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:

      ts
      withRspack(withIntlayer(nextConfig, { enableTurbopack: false }));
      
    4. ๋™์  ๋กœ์ผ€์ผ ๊ฒฝ๋กœ ์ •์˜

      RootLayout์˜ ๋ชจ๋“  ๋‚ด์šฉ์„ ์ œ๊ฑฐํ•˜๊ณ  ๋‹ค์Œ ์ฝ”๋“œ๋กœ ๊ต์ฒดํ•˜์„ธ์š”:

      src/app/layout.tsx
      import type { PropsWithChildren, FC } from "react";
      import "./globals.css";
      
      const RootLayout: FC<PropsWithChildren> = ({ children }) => (
        // ์—ฌ์ „ํžˆ `next-themes`, `react-query`, `framer-motion` ๋“ฑ๊ณผ ๊ฐ™์€ ๋‹ค๋ฅธ ํ”„๋กœ๋ฐ”์ด๋”๋กœ children์„ ๊ฐ์Œ€ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
        <>{children}</>
      );
      
      export default RootLayout;
      
      RootLayout ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋น„์›Œ๋‘๋ฉด <html> ํƒœ๊ทธ์— lang ๋ฐ dir ์†์„ฑ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

      ๋™์  ๋ผ์šฐํŒ…์„ ๊ตฌํ˜„ํ•˜๋ ค๋ฉด [locale] ๋””๋ ‰ํ† ๋ฆฌ์— ์ƒˆ ๋ ˆ์ด์•„์›ƒ์„ ์ถ”๊ฐ€ํ•˜์—ฌ ๋กœ์ผ€์ผ ๊ฒฝ๋กœ๋ฅผ ์ œ๊ณตํ•˜์„ธ์š”:

      src/app/[locale]/layout.tsx
      import { type NextLayoutIntlayer } from "next-intlayer";
      import { IntlayerProvider } from "next-intlayer/server";
      import { Inter } from "next/font/google";
      import { getHTMLTextDir } from "intlayer";
      
      const inter = Inter({ subsets: ["latin"] });
      
      const LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => {
        const { locale } = await params;
        return (
          <html lang={locale} dir={getHTMLTextDir(locale)}>
            <body className={inter.className}>
              <IntlayerProvider locale={locale}>{children}</IntlayerProvider>
            </body>
          </html>
        );
      };
      
      export default LocaleLayout;
      
      ๋‹จ์ผ IntlayerProvider๋Š” ํŠธ๋ฆฌ์˜ ์–‘์ชฝ ์ ˆ๋ฐ˜์„ ๋ชจ๋‘ ๋‹ค๋ฃน๋‹ˆ๋‹ค: ์„œ๋ฒ„ ํ›…์—์„œ ์ฝ๋Š” ์š”์ฒญ ๋ฒ”์œ„ ์„œ๋ฒ„ ์ปจํ…์ŠคํŠธ๋ฅผ ์‹œ๋“œํ•˜๊ณ , ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋™์ผํ•œ ๋กœ์ผ€์ผ์„ ๋ฐ›๋„๋ก ํด๋ผ์ด์–ธํŠธ ๊ณต๊ธ‰์ž๋ฅผ ๋งˆ์šดํŠธํ•ฉ๋‹ˆ๋‹ค.
      src/app/[locale]/layout.tsx
      import { type NextLayoutIntlayer, IntlayerClientProvider } from "next-intlayer";
      import { Inter } from "next/font/google";
      import { getHTMLTextDir } from "intlayer";
      
      const inter = Inter({ subsets: ["latin"] });
      
      const LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => {
        const { locale } = await params;
        return (
          <html lang={locale} dir={getHTMLTextDir(locale)}>
            <body className={inter.className}>
              <IntlayerClientProvider locale={locale}>
                {children}
              </IntlayerClientProvider>
            </body>
          </html>
        );
      };
      
      export default LocaleLayout;
      
      [locale] ๊ฒฝ๋กœ ์„ธ๊ทธ๋จผํŠธ๋Š” ๋กœ์ผ€์ผ์„ ์ •์˜ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ: /en-US/about๋Š” en-US๋ฅผ ๊ฐ€๋ฆฌํ‚ค๊ณ  /fr/about๋Š” fr๋ฅผ ๊ฐ€๋ฆฌํ‚ต๋‹ˆ๋‹ค.
      ์ด ๋‹จ๊ณ„์—์„œ Error: Missing <html> and <body> tags in the root layout. ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” /app/page.tsx ํŒŒ์ผ์ด ๋” ์ด์ƒ ์‚ฌ์šฉ๋˜์ง€ ์•Š์œผ๋ฉฐ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์˜ˆ์ƒ๋˜๋Š” ํ˜„์ƒ์ž…๋‹ˆ๋‹ค. ๋Œ€์‹ , [locale] ๊ฒฝ๋กœ ์„ธ๊ทธ๋จผํŠธ๊ฐ€ /app/[locale]/page.tsx ํŽ˜์ด์ง€๋ฅผ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋ธŒ๋ผ์šฐ์ €์—์„œ /en, /fr, /es์™€ ๊ฐ™์€ ๊ฒฝ๋กœ๋ฅผ ํ†ตํ•ด ํŽ˜์ด์ง€์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ๋กœ์ผ€์ผ์„ ๋ฃจํŠธ ํŽ˜์ด์ง€๋กœ ์„ค์ •ํ•˜๋ ค๋ฉด 7๋‹จ๊ณ„์˜ proxy ์„ค์ •์„ ์ฐธ์กฐํ•˜์„ธ์š”.

      ๊ทธ๋Ÿฐ ๋‹ค์Œ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ Layout์—์„œ generateStaticParams ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•˜์„ธ์š”.

      src/app/[locale]/layout.tsx
      export { generateStaticParams } from "next-intlayer"; // ์‚ฝ์ž…ํ•  ๋ผ์ธ
      
      const LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => {
        /*... ๋‚˜๋จธ์ง€ ์ฝ”๋“œ */
      };
      
      export default LocaleLayout;
      
      generateStaticParams๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋ชจ๋“  ๋กœ์ผ€์ผ์— ๋Œ€ํ•ด ํ•„์š”ํ•œ ํŽ˜์ด์ง€๋ฅผ ์‚ฌ์ „ ๋นŒ๋“œํ•˜๋„๋ก ๋ณด์žฅํ•˜์—ฌ ๋Ÿฐํƒ€์ž„ ๊ณ„์‚ฐ์„ ์ค„์ด๊ณ  ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ Next.js์˜ generateStaticParams ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.
      Intlayer๋Š” export const dynamic = 'force-static';์™€ ํ•จ๊ป˜ ์ž‘๋™ํ•˜์—ฌ ๋ชจ๋“  ๋กœ์ผ€์ผ์— ๋Œ€ํ•ด ํŽ˜์ด์ง€๊ฐ€ ์‚ฌ์ „ ๋นŒ๋“œ๋˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

      ๋กœ์ผ€์ผ ๊ฒฝ๋กœ๊ฐ€ ์—†๋Š” ์•„ํ‚คํ…์ฒ˜์—์„œ๋Š” [locale] ๋””๋ ‰ํ„ฐ๋ฆฌ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์š”์ฒญ ์ปจํ…์ŠคํŠธ์—์„œ ๋กœ์ผ€์ผ์„ ์ฝ๊ณ  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ IntlayerProvider๋กœ ๋ž˜ํ•‘ํ•˜๋„๋ก src/app/layout.tsx๋ฅผ ์ง์ ‘ ๊ตฌ์„ฑํ•˜์„ธ์š”:

      src/app/layout.tsx
      import type { Metadata } from "next";
      import type { ReactNode } from "react";
      import "./globals.css";
      import { getHTMLTextDir, getIntlayer } from "intlayer";
      import { getLocale, IntlayerProvider } from "next-intlayer/server";
      export { generateStaticParams } from "next-intlayer";
      
      export const generateMetadata = async (): Promise<Metadata> => {
        const locale = await getLocale();
        const { title, description, keywords } = getIntlayer("metadata", locale);
      
        return {
          title,
          description,
          keywords,
        };
      };
      
      const RootLayout = async ({
        children,
      }: Readonly<{
        children: ReactNode;
      }>) => {
        const locale = await getLocale();
      
        return (
          <html lang={locale} dir={getHTMLTextDir(locale)}>
            <body>
              <IntlayerProvider locale={locale}>{children}</IntlayerProvider>
            </body>
          </html>
        );
      };
      
      export default RootLayout;
      
      ๋‹จ์ผ IntlayerProvider๋Š” ํŠธ๋ฆฌ์˜ ๋‘ ๋ถ€๋ถ„์„ ๋ชจ๋‘ ์ปค๋ฒ„ํ•ฉ๋‹ˆ๋‹ค: ์„œ๋ฒ„ ํ›…์—์„œ ์ฝ๋Š” request-scoped ์„œ๋ฒ„ ์ปจํ…์ŠคํŠธ๋ฅผ ์ œ๊ณตํ•˜๊ณ , ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋™์ผํ•œ ๋กœ์ผ€์ผ์„ ๋ฐ›๋„๋ก ํด๋ผ์ด์–ธํŠธ provider๋ฅผ ๋งˆ์šดํŠธํ•ฉ๋‹ˆ๋‹ค.

      RootLayout์—์„œ ๋ชจ๋“  ๋‚ด์šฉ์„ ์ œ๊ฑฐํ•˜๊ณ  ๋‹ค์Œ ์ฝ”๋“œ๋กœ ๊ต์ฒดํ•˜์„ธ์š”:

      src/app/layout.tsx
      import type { Metadata } from "next";
      import type { ReactNode } from "react";
      import "./globals.css";
      import { IntlayerProvider, LocalPromiseParams } from "next-intlayer";
      import { getHTMLTextDir, getIntlayer } from "intlayer";
      import { getLocale } from "next-intlayer/server";
      export { generateStaticParams } from "next-intlayer";
      
      export const generateMetadata = async (): Promise<Metadata> => {
        const locale = await getLocale();
        const { title, description, keywords } = getIntlayer("metadata", locale);
      
        return {
          title,
          description,
          keywords,
        };
      };
      
      const RootLayout = async ({
        children,
      }: Readonly<{
        children: ReactNode;
      }>) => {
        const locale = await getLocale();
      
        return (
          <html lang={locale} dir={getHTMLTextDir(locale)}>
            <body>
              <IntlayerProvider locale={locale}>{children}</IntlayerProvider>
            </body>
          </html>
        );
      };
      
      export default RootLayout;
      
    5. ์ฝ˜ํ…์ธ  ์„ ์–ธํ•˜๊ธฐ

      ๋ฒˆ์—ญ์„ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด ์ฝ˜ํ…์ธ  ์„ ์–ธ์„ ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•˜์„ธ์š”:

      src/app/[locale]/page.content.ts
      import { t, type Dictionary } from "intlayer";
      
      const pageContent = {
        key: "page",
        content: {
          getStarted: {
            main: t({
              en: "Get started by editing",
              fr: "Commencez par รฉditer",
              es: "Comience por editar",
            }),
            pageLink: "src/app/page.tsx",
          },
        },
      } satisfies Dictionary;
      
      export default pageContent;
      
      ์ฝ˜ํ…์ธ  ์„ ์–ธ์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด ์–ด๋””์—์„œ๋‚˜ ์ •์˜ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, contentDir ๋””๋ ‰ํ† ๋ฆฌ(๊ธฐ๋ณธ๊ฐ’: ./src)์— ํฌํ•จ๋˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ฝ˜ํ…์ธ  ์„ ์–ธ ํŒŒ์ผ ํ™•์žฅ์ž(๊ธฐ๋ณธ๊ฐ’: .content.{json,ts,tsx,js,jsx,mjs,cjs,md,mdx,yaml,yml})์™€ ์ผ์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
      ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์ฝ˜ํ…์ธ  ์„ ์–ธ ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.
    6. ์ฝ”๋“œ์—์„œ ์ฝ˜ํ…์ธ  ํ™œ์šฉํ•˜๊ธฐ

      ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „๋ฐ˜์—์„œ ์ฝ˜ํ…์ธ  ์‚ฌ์ „์„ ์ ‘๊ทผํ•˜์„ธ์š”:

      src/app/[locale]/page.tsx
      import type { FC } from "react";
      import { ClientComponentExample } from "@components/ClientComponentExample";
      import { ServerComponentExample } from "@components/ServerComponentExample";
      import { type NextPageIntlayer, useIntlayer } from "next-intlayer";
      
      const PageContent: FC = () => {
        const content = useIntlayer("page");
      
        return (
          <>
            <p>{content.getStarted.main}</p>
            <code>{content.getStarted.pageLink}</code>
          </>
        );
      };
      
      const Page: NextPageIntlayer = () => (
        <>
          <PageContent />
          <ServerComponentExample />
      
          <ClientComponentExample />
        </>
      );
      
      export default Page;
      
      • IntlayerProvider๋Š” locale ๋ ˆ์ด์•„์›ƒ์—์„œ ํ•œ ๋ฒˆ ๋งˆ์šดํŠธ๋ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„ ๋ฐ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ ๋ชจ๋‘์— locale์„ ์ œ๊ณตํ•˜๋ฏ€๋กœ ํŽ˜์ด์ง€๊ฐ€ ๋” ์ด์ƒ ์ž์‹ ์„ ๋ž˜ํ•‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
      • ์„œ๋ฒ„ hooks๋Š” ๋‹ค์Œ ์ˆœ์„œ๋กœ locale์„ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค: ํ˜ธ์ถœ ์‚ฌ์ดํŠธ์—์„œ ์ „๋‹ฌ๋œ locale, ์ œ๊ณต์ž์— ์˜ํ•ด ์‹œ๋“œ๋œ ์„œ๋ฒ„ ์ปจํ…์ŠคํŠธ, ์š”์ฒญ์— ์˜ํ•ด ์ „๋‹ฌ๋œ locale (Intlayer ํ”„๋ก์‹œ๊ฐ€ ์„ค์ •ํ•œ x-intlayer-locale ํ—ค๋”, ๊ทธ ๋‹ค์Œ locale ์ฟ ํ‚ค). ๋งˆ์ง€๋ง‰ ๋‹จ๊ณ„๋Š” ๋ ˆ์ด์•„์›ƒ์ด ๋‹ค์‹œ ์‹คํ–‰๋˜์ง€ ์•Š๋Š” ํŽ˜์ด์ง€ ์„ธ๊ทธ๋จผํŠธ๋งŒ ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ ์ธก ๋„ค๋น„๊ฒŒ์ด์…˜์—์„œ ์ฝ˜ํ…์ธ ๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
      src/app/[locale]/page.tsx
      import type { FC } from "react";
      import { ClientComponentExample } from "@components/ClientComponentExample";
      import { ServerComponentExample } from "@components/ServerComponentExample";
      import { type NextPageIntlayer } from "next-intlayer";
      import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";
      
      const PageContent: FC = () => {
        const content = useIntlayer("page");
      
        return (
          <>
            <p>{content.getStarted.main}</p>
            <code>{content.getStarted.pageLink}</code>
          </>
        );
      };
      
      const Page: NextPageIntlayer = async ({ params }) => {
        const { locale } = await params;
      
        return (
          <IntlayerServerProvider locale={locale}>
            <PageContent />
            <ServerComponentExample />
      
            <ClientComponentExample />
          </IntlayerServerProvider>
        );
      };
      
      export default Page;
      
      • IntlayerClientProvider๋Š” ํด๋ผ์ด์–ธํŠธ ์ธก ์ปดํฌ๋„ŒํŠธ์— ๋กœ์ผ€์ผ์„ ์ œ๊ณตํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋ ˆ์ด์•„์›ƒ์„ ํฌํ•จํ•œ ๋ชจ๋“  ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ์— ๋ฐฐ์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ Next.js๊ฐ€ ํŽ˜์ด์ง€ ๊ฐ„์— ๋ ˆ์ด์•„์›ƒ ์ฝ”๋“œ๋ฅผ ๊ณต์œ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ ˆ์ด์•„์›ƒ์— ๋ฐฐ์น˜ํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค. ๋ ˆ์ด์•„์›ƒ์—์„œ IntlayerClientProvider๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ ํŽ˜์ด์ง€๋งˆ๋‹ค ๋‹ค์‹œ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜์—ฌ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ค๊ณ  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด์—์„œ ์ผ๊ด€๋œ ๋กœ์ปฌ๋ผ์ด์ œ์ด์…˜ ์ปจํ…์ŠคํŠธ๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
      • IntlayerServerProvider๋Š” ์„œ๋ฒ„ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์— ๋กœ์ผ€์ผ์„ ์ œ๊ณตํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋ ˆ์ด์•„์›ƒ์—์„œ๋Š” ์„ค์ •ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

        ๋ ˆ์ด์•„์›ƒ๊ณผ ํŽ˜์ด์ง€๋Š” ๊ณตํ†ต ์„œ๋ฒ„ ์ปจํ…์ŠคํŠธ๋ฅผ ๊ณต์œ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์„œ๋ฒ„ ์ปจํ…์ŠคํŠธ ์‹œ์Šคํ…œ์€ ์š”์ฒญ๋ณ„ ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ(React์˜ cache ๋ฉ”์ปค๋‹ˆ์ฆ˜)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์„œ๋กœ ๋‹ค๋ฅธ ์„ธ๊ทธ๋จผํŠธ๋งˆ๋‹ค ๊ฐ "์ปจํ…์ŠคํŠธ"๊ฐ€ ๋‹ค์‹œ ์ƒ์„ฑ๋˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๊ณต๊ธ‰์ž๋ฅผ ๊ณต์œ  ๋ ˆ์ด์•„์›ƒ์— ๋ฐฐ์น˜ํ•˜๋ฉด ์ด ๊ฒฉ๋ฆฌ๊ฐ€ ๊นจ์ ธ ์„œ๋ฒ„ ์ปจํ…์ŠคํŠธ ๊ฐ’์ด ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์— ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ „๋‹ฌ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
      src/components/ClientComponentExample.tsx
      "use client";
      
      import type { FC } from "react";
      import { useIntlayer } from "next-intlayer";
      
      export const ClientComponentExample: FC = () => {
        const content = useIntlayer("client-component-example"); // ๊ด€๋ จ ์ฝ˜ํ…์ธ  ์„ ์–ธ ์ƒ์„ฑ
      
        return (
          <div>
            <h2>{content.title}</h2>
            <p>{content.content}</p>
          </div>
        );
      };
      
      src/components/ServerComponentExample.tsx
      import type { FC } from "react";
      import { useIntlayer } from "next-intlayer";
      
      export const ServerComponentExample: FC = () => {
        const content = useIntlayer("server-component-example"); // ๊ด€๋ จ ์ฝ˜ํ…์ธ  ์„ ์–ธ ์ƒ์„ฑ
      
        return (
          <div>
            <h2>{content.title}</h2>
            <p>{content.content}</p>
          </div>
        );
      };
      
      next-intlayer๋Š” ๋™ํ˜• import ๊ฒฝ๋กœ์ž…๋‹ˆ๋‹ค: react-server export ์กฐ๊ฑด์€ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์— ambient-locale ๊ตฌํ˜„์„ ์ œ๊ณตํ•˜๊ณ , ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ๋Š” context ๊ธฐ๋ฐ˜ ๊ตฌํ˜„์„ ๋ฐ›์Šต๋‹ˆ๋‹ค. ๋™์ผํ•œ ํ˜ธ์ถœ์ด ์–‘์ชฝ์—์„œ ๋ชจ๋‘ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.
      src/components/ServerComponentExample.tsx
      import type { FC } from "react";
      import { useIntlayer } from "next-intlayer/server";
      
      export const ServerComponentExample: FC = () => {
        const content = useIntlayer("server-component-example"); // ๊ด€๋ จ ์ฝ˜ํ…์ธ  ์„ ์–ธ ์ƒ์„ฑ
      
        return (
          <div>
            <h2>{content.title}</h2>
            <p>{content.content}</p>
          </div>
        );
      };
      
      ์ฝ˜ํ…์ธ ๋ฅผ alt, title, href, aria-label ๋“ฑ๊ณผ ๊ฐ™์€ string ์†์„ฑ์—์„œ ์‚ฌ์šฉํ•˜๋ ค๋ฉด, ํ•จ์ˆ˜์˜ ๊ฐ’์„ ํ˜ธ์ถœํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด:
      tsx
      <img src={content.image.src.value} alt={content.image.value} />
      <img src={content.image.src.toString()} alt={content.image.toString()} />
      <img src={String(content.image.src)} alt={String(content.image)} />
      
      useIntlayer ํ›…์— ๋Œ€ํ•ด ๋” ์•Œ์•„๋ณด๋ ค๋ฉด ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.
      ๊ธฐ์กด ์•ฑ์ด ์žˆ๋‹ค๋ฉด, Intlayer Compiler์™€ extract ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ˆ˜์ฒœ ๊ฐœ์˜ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ช‡ ์ดˆ ๋งŒ์— ๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    7. ๋กœ์ผ€์ผ ๊ฐ์ง€๋ฅผ ์œ„ํ•œ ํ”„๋ก์‹œ ๊ตฌ์„ฑ

      ์„ ํƒ์‚ฌํ•ญ

      ์‚ฌ์šฉ์ž์˜ ์„ ํ˜ธ ๋กœ์ผ€์ผ์„ ๊ฐ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ํ”„๋ก์‹œ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค:

      src/proxy.ts
      export { intlayerProxy as proxy } from "next-intlayer/proxy";
      
      export const config = {
        matcher:
          "/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\..*|_next).*)",
      };
      
      intlayerProxy๋Š” ์‚ฌ์šฉ์ž์˜ ์„ ํ˜ธ ๋กœ์ผ€์ผ์„ ๊ฐ์ง€ํ•˜์—ฌ ๊ตฌ์„ฑ์— ๋ช…์‹œ๋œ ์ ์ ˆํ•œ URL๋กœ ๋ฆฌ๋””๋ ‰์…˜ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์‚ฌ์šฉ์ž์˜ ์„ ํ˜ธ ๋กœ์ผ€์ผ์„ ์ฟ ํ‚ค์— ์ €์žฅํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
      Intlayer v9 ์ดํ›„, ์ด ๋ฏธ๋“ค์›จ์–ด๋Š” routing.enableProxy ์˜ต์…˜(true๊ฐ€ ๊ธฐ๋ณธ๊ฐ’)์„ ์ค€์ˆ˜ํ•ฉ๋‹ˆ๋‹ค. ์ด ํŒŒ์ผ์„ ์ œ๊ฑฐํ•˜์ง€ ์•Š๊ณ  pass-through๋กœ ์ „ํ™˜ํ•˜๋ ค๋ฉด ์„ค์ •์—์„œ routing.enableProxy: false๋ฅผ ์„ค์ •ํ•˜์„ธ์š”. v9 release notes๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.
      ์—ฌ๋Ÿฌ ํ”„๋ก์‹œ๋ฅผ ํ•จ๊ป˜ ์—ฐ๊ฒฐํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ(์˜ˆ: ์ธ์ฆ ๋˜๋Š” ์ปค์Šคํ…€ ํ”„๋ก์‹œ์™€ ํ•จ๊ป˜ intlayerProxy๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ), Intlayer๋Š” ์ด์ œ multipleProxies๋ผ๋Š” ํ—ฌํผ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
      ts
      import { multipleProxies, intlayerProxy } from "next-intlayer/proxy";
      import { customProxy } from "@utils/customProxy";
      
      export const proxy = multipleProxies([intlayerProxy, customProxy]);
      
    8. ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์˜ ๊ตญ์ œํ™”

      ์„ ํƒ์‚ฌํ•ญ

      ํŽ˜์ด์ง€ ์ œ๋ชฉ๊ณผ ๊ฐ™์€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๊ตญ์ œํ™”ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ, Next.js์—์„œ ์ œ๊ณตํ•˜๋Š” generateMetadata ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‚ด๋ถ€์—์„œ getIntlayer ํ•จ์ˆ˜๋กœ๋ถ€ํ„ฐ ์ฝ˜ํ…์ธ ๋ฅผ ๊ฐ€์ ธ์™€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๋ฒˆ์—ญํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

      src/app/[locale]/metadata.content.ts
      import { type Dictionary, t } from "intlayer";
      import { Metadata } from "next";
      
      const metadataContent = {
        key: "page-metadata",
        content: {
          title: t({
            en: "Create Next App",
            fr: "Crรฉer une application Next.js",
            es: "Crear una aplicaciรณn Next.js",
          }),
          description: t({
            en: "Generated by create next app",
            fr: "Gรฉnรฉrรฉ par create next app",
            es: "Generado por create next app",
          }),
        },
      } satisfies Dictionary<Metadata>;
      
      export default metadataContent;
      
      src/app/[locale]/layout.tsx or src/app/[locale]/page.tsx
      import { getIntlayer, getMultilingualUrls } from "intlayer";
      import type { Metadata } from "next";
      import type { LocalPromiseParams } from "next-intlayer";
      
      export const generateMetadata = async ({
        params,
      }: LocalPromiseParams): Promise<Metadata> => {
        const { locale } = await params;
      
        const metadata = getIntlayer("page-metadata", locale);
      
        /**
         * ๊ฐ ๋กœ์ผ€์ผ์— ๋Œ€ํ•œ ๋ชจ๋“  URL์„ ํฌํ•จํ•˜๋Š” ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
         *
         * ์˜ˆ์‹œ:
         * ```ts
         *  getMultilingualUrls('/about');
         *
         *  // ๋ฐ˜ํ™˜๊ฐ’
         *  // {
         *  //   en: '/about',
         *  //   fr: '/fr/about',
         *  //   es: '/es/about',
         *  // }
         * ```
         */
        const multilingualUrls = getMultilingualUrls("/");
        const localizedUrl =
          multilingualUrls[locale as keyof typeof multilingualUrls];
      
        return {
          ...metadata,
          alternates: {
            canonical: localizedUrl,
            languages: { ...multilingualUrls, "x-default": "/" },
          },
          openGraph: {
            url: localizedUrl,
          },
        };
      };
      
      // ... ๋‚˜๋จธ์ง€ ์ฝ”๋“œ
      
      next-intlayer์—์„œ ๊ฐ€์ ธ์˜จ getIntlayer ํ•จ์ˆ˜๋Š” ์ฝ˜ํ…์ธ ๋ฅผ IntlayerNode๋กœ ๋ž˜ํ•‘ํ•˜์—ฌ ์‹œ๊ฐ์  ํŽธ์ง‘๊ธฐ์™€์˜ ํ†ตํ•ฉ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด, intlayer์—์„œ ๊ฐ€์ ธ์˜จ getIntlayer ํ•จ์ˆ˜๋Š” ์ถ”๊ฐ€ ์†์„ฑ ์—†์ด ์ฝ˜ํ…์ธ ๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
      ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ตœ์ ํ™”์— ๋Œ€ํ•ด ๋” ์•Œ์•„๋ณด๊ธฐ ๊ณต์‹ Next.js ๋ฌธ์„œ์—์„œ.
    9. sitemap.xml ๋ฐ robots.txt์˜ ๋‹ค๊ตญ์–ดํ™”

      ์„ ํƒ์‚ฌํ•ญ

      sitemap.xml ๋ฐ robots.txt๋ฅผ ๋‹ค๊ตญ์–ดํ™”ํ•˜๋ ค๋ฉด Intlayer์—์„œ ์ œ๊ณตํ•˜๋Š” getMultilingualUrls ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ํ•จ์ˆ˜๋Š” ์‚ฌ์ดํŠธ๋งต์— ๋‹ค๊ตญ์–ด URL์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.

      src/app/sitemap.ts
      import { getMultilingualUrls } from "intlayer";
      import type { MetadataRoute } from "next";
      
      const sitemap = (): MetadataRoute.Sitemap => [
        {
          url: "https://example.com",
          alternates: {
            languages: {
              ...getMultilingualUrls("https://example.com"),
              "x-default": "https://example.com",
            },
          },
        },
        {
          url: "https://example.com/login",
          alternates: {
            languages: {
              ...getMultilingualUrls("https://example.com/login"),
              "x-default": "https://example.com/login",
            },
          },
        },
        {
          url: "https://example.com/register",
          alternates: {
            languages: {
              ...getMultilingualUrls("https://example.com/register"),
              "x-default": "https://example.com/register",
            },
          },
        },
      ];
      
      export default sitemap;
      
      src/app/robots.ts
      import type { MetadataRoute } from "next";
      import { getMultilingualUrls } from "intlayer";
      
      const getAllMultilingualUrls = (urls: string[]) =>
        urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);
      
      const robots = (): MetadataRoute.Robots => ({
        rules: {
          userAgent: "*",
          allow: ["/"],
          disallow: getAllMultilingualUrls(["/login", "/register"]),
        },
        host: "https://example.com",
        sitemap: `https://example.com/sitemap.xml`,
      });
      
      export default robots;
      
      ์‚ฌ์ดํŠธ๋งต ์ตœ์ ํ™”์— ๋Œ€ํ•ด ๋” ์•Œ์•„๋ณด๊ธฐ ๊ณต์‹ Next.js ๋ฌธ์„œ์—์„œ. robots.txt ์ตœ์ ํ™”์— ๋Œ€ํ•ด ๋” ์•Œ์•„๋ณด๊ธฐ ๊ณต์‹ Next.js ๋ฌธ์„œ์—์„œ.
    10. ์ฝ˜ํ…์ธ ์˜ ์–ธ์–ด ๋ณ€๊ฒฝํ•˜๊ธฐ

      ์„ ํƒ์‚ฌํ•ญ

      Next.js์—์„œ ์ฝ˜ํ…์ธ ์˜ ์–ธ์–ด๋ฅผ ๋ณ€๊ฒฝํ•˜๋ ค๋ฉด, ๊ถŒ์žฅ๋˜๋Š” ๋ฐฉ๋ฒ•์€ Link ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž๋ฅผ ์ ์ ˆํ•œ ํ˜„์ง€ํ™”๋œ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋””๋ ‰์…˜ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. Link ์ปดํฌ๋„ŒํŠธ๋Š” ํŽ˜์ด์ง€์˜ ์‚ฌ์ „ ๋กœ๋”ฉ(prefetching)์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜์—ฌ ์ „์ฒด ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ์„ ๋ฐฉ์ง€ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

      src/components/LocaleSwitcher.tsx
      "use client";
      
      import type { FC } from "react";
      import {
        Locales,
        getHTMLTextDir,
        getLocaleName,
        getLocalizedUrl,
      } from "intlayer";
      import { useLocale } from "next-intlayer";
      import Link from "next/link";
      
      export const LocaleSwitcher: FC = () => {
        const { locale, pathWithoutLocale, availableLocales, setLocale } =
          useLocale();
      
        return (
          <div>
            <button popoverTarget="localePopover">{getLocaleName(locale)}</button>
            <div id="localePopover" popover="auto">
              {availableLocales.map((localeItem) => (
                <Link
                  href={getLocalizedUrl(pathWithoutLocale, localeItem)}
                  key={localeItem}
                  aria-current={locale === localeItem ? "page" : undefined}
                  onClick={() => setLocale(localeItem)}
                  replace // "๋’ค๋กœ ๊ฐ€๊ธฐ" ๋ธŒ๋ผ์šฐ์ € ๋ฒ„ํŠผ์ด ์ด์ „ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋””๋ ‰์…˜๋˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.
                >
                  <span>
                    {/* ๋กœ์ผ€์ผ - ์˜ˆ: FR */}
                    {localeItem}
                  </span>
                  <span>
                    {/* ํ•ด๋‹น ๋กœ์ผ€์ผ์˜ ์–ธ์–ด - ์˜ˆ: Franรงais */}
                    {getLocaleName(localeItem, locale)}
                  </span>
                  <span dir={getHTMLTextDir(localeItem)} lang={localeItem}>
                    {/* ํ˜„์žฌ ๋กœ์ผ€์ผ์—์„œ์˜ ์–ธ์–ด - ์˜ˆ: ํ˜„์žฌ ๋กœ์ผ€์ผ์ด Locales.SPANISH์ผ ๋•Œ Francรฉs */}
                    {getLocaleName(localeItem)}
                  </span>
                  <span dir="ltr" lang={Locales.ENGLISH}>
                    {/* ์˜์–ด๋กœ ๋œ ์–ธ์–ด๋ช… - ์˜ˆ: French */}
                    {getLocaleName(localeItem, Locales.ENGLISH)}
                  </span>
                </Link>
              ))}
            </div>
          </div>
        );
      };
      
      ๋Œ€์•ˆ์œผ๋กœ useLocale ํ›…์—์„œ ์ œ๊ณตํ•˜๋Š” setLocale ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ํ•จ์ˆ˜๋Š” ํŽ˜์ด์ง€ ์‚ฌ์ „ ๋กœ๋”ฉ(prefetching)์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ useLocale ํ›… ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.
      ๋˜ํ•œ onLocaleChange ์˜ต์…˜์— ํ•จ์ˆ˜๋ฅผ ์„ค์ •ํ•˜์—ฌ ๋กœ์ผ€์ผ์ด ๋ณ€๊ฒฝ๋  ๋•Œ ์‚ฌ์šฉ์ž ์ •์˜ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
      src/components/LocaleSwitcher.tsx
      "use client";
      
      import { useLocale } from "next-intlayer";
      import { getLocalizedUrl } from "intlayer";
      
      // ... ๋‚˜๋จธ์ง€ ์ฝ”๋“œ
      
      const { setLocale } = useLocale();
      
      return (
        <button onClick={() => setLocale(Locales.FRENCH)}>ํ”„๋ž‘์Šค์–ด๋กœ ๋ณ€๊ฒฝ</button>
      );
      

      ๋ฌธ์„œ ์ฐธ๊ณ :

    11. ์ง€์—ญํ™”๋œ ๋งํฌ ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑํ•˜๊ธฐ

      ์„ ํƒ์‚ฌํ•ญ

      ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋‚ด๋น„๊ฒŒ์ด์…˜์ด ํ˜„์žฌ ๋กœ์ผ€์ผ์„ ์ค€์ˆ˜ํ•˜๋„๋ก ํ•˜๋ ค๋ฉด, ์ปค์Šคํ…€ Link ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์ปดํฌ๋„ŒํŠธ๋Š” ๋‚ด๋ถ€ URL์— ํ˜„์žฌ ์–ธ์–ด๋ฅผ ์ž๋™์œผ๋กœ ์ ‘๋‘์‚ฌ๋กœ ๋ถ™์—ฌ์ค๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํ”„๋ž‘์Šค์–ด ์‚ฌ์šฉ์ž๊ฐ€ "About" ํŽ˜์ด์ง€๋กœ ๊ฐ€๋Š” ๋งํฌ๋ฅผ ํด๋ฆญํ•˜๋ฉด /about ๋Œ€์‹  /fr/about๋กœ ๋ฆฌ๋””๋ ‰์…˜๋ฉ๋‹ˆ๋‹ค.

      ์ด ๋™์ž‘์€ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ์ด์œ ๋กœ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค:

      • SEO ๋ฐ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜: ๋กœ์ปฌ๋ผ์ด์ฆˆ๋œ URL์€ ๊ฒ€์ƒ‰ ์—”์ง„์ด ์–ธ์–ด๋ณ„ ํŽ˜์ด์ง€๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ƒ‰์ธํ•˜๋„๋ก ๋„์™€์ฃผ๋ฉฐ, ์‚ฌ์šฉ์ž์—๊ฒŒ ์„ ํ˜ธํ•˜๋Š” ์–ธ์–ด๋กœ ๋œ ์ฝ˜ํ…์ธ ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
      • ์ผ๊ด€์„ฑ: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „๋ฐ˜์— ๊ฑธ์ณ ๋กœ์ปฌ๋ผ์ด์ฆˆ๋œ ๋งํฌ๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ๋‚ด๋น„๊ฒŒ์ด์…˜์ด ํ˜„์žฌ ๋กœ์ผ€์ผ ๋‚ด์—์„œ ์œ ์ง€๋˜์–ด ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์–ธ์–ด ์ „ํ™˜์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.
      • ์œ ์ง€๋ณด์ˆ˜์„ฑ: ๋กœ์ปฌ๋ผ์ด์ œ์ด์…˜ ๋กœ์ง์„ ๋‹จ์ผ ์ปดํฌ๋„ŒํŠธ์— ์ค‘์•™ ์ง‘์ค‘ํ™”ํ•˜๋ฉด URL ๊ด€๋ฆฌ๋ฅผ ๋‹จ์ˆœํ™”ํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์„ฑ์žฅํ•จ์— ๋”ฐ๋ผ ์ฝ”๋“œ๋ฒ ์ด์Šค๋ฅผ ๋” ์‰ฝ๊ฒŒ ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ณ  ํ™•์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

      ์•„๋ž˜๋Š” TypeScript๋กœ ๊ตฌํ˜„ํ•œ ๋กœ์ปฌ๋ผ์ด์ฆˆ๋œ Link ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค:

      src/components/Link.tsx
      "use client";
      
      import { getLocalizedUrl } from "intlayer";
      import NextLink, { type LinkProps as NextLinkProps } from "next/link";
      import { useLocale } from "next-intlayer";
      import type { PropsWithChildren, FC } from "react";
      
      /**
       * ์ฃผ์–ด์ง„ URL์ด ์™ธ๋ถ€ ๋งํฌ์ธ์ง€ ํ™•์ธํ•˜๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
       * URL์ด http:// ๋˜๋Š” https://๋กœ ์‹œ์ž‘ํ•˜๋ฉด ์™ธ๋ถ€ ๋งํฌ๋กœ ๊ฐ„์ฃผํ•ฉ๋‹ˆ๋‹ค.
       */
      export const checkIsExternalLink = (href?: string): boolean =>
        /^https?:\/\//.test(href ?? "");
      
      /**
       * ํ˜„์žฌ ๋กœ์ผ€์ผ์— ๋”ฐ๋ผ href ์†์„ฑ์„ ์กฐ์ •ํ•˜๋Š” ์ปค์Šคํ…€ Link ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค.
       * ๋‚ด๋ถ€ ๋งํฌ์˜ ๊ฒฝ์šฐ, `getLocalizedUrl`์„ ์‚ฌ์šฉํ•˜์—ฌ URL ์•ž์— ๋กœ์ผ€์ผ์„ ๋ถ™์ž…๋‹ˆ๋‹ค (์˜ˆ: /fr/about).
       * ์ด๋ฅผ ํ†ตํ•ด ๋‚ด๋น„๊ฒŒ์ด์…˜์ด ๋™์ผํ•œ ๋กœ์ผ€์ผ ์ปจํ…์ŠคํŠธ ๋‚ด์—์„œ ์ด๋ฃจ์–ด์ง€๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.
       */
      export const Link: FC<PropsWithChildren<NextLinkProps>> = ({
        href,
        children,
        ...props
      }) => {
        const { locale } = useLocale();
        const isExternalLink = checkIsExternalLink(href.toString());
      
        // ๋งŒ์•ฝ ๋งํฌ๊ฐ€ ๋‚ด๋ถ€ ๋งํฌ์ด๊ณ  ์œ ํšจํ•œ href๊ฐ€ ์ œ๊ณต๋˜์—ˆ๋‹ค๋ฉด, ์ง€์—ญํ™”๋œ URL์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
        const hrefI18n: NextLinkProps["href"] =
          href && !isExternalLink ? getLocalizedUrl(href.toString(), locale) : href;
      
        return (
          <NextLink href={hrefI18n} {...props}>
            {children}
          </NextLink>
        );
      };
      

      ์ž‘๋™ ์›๋ฆฌ

      • ์™ธ๋ถ€ ๋งํฌ ๊ฐ์ง€:
        ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜ checkIsExternalLink๋Š” URL์ด ์™ธ๋ถ€ ๋งํฌ์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์™ธ๋ถ€ ๋งํฌ๋Š” ํ˜„์ง€ํ™”๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์€ ์ƒํƒœ๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.

      • ํ˜„์žฌ ๋กœ์ผ€์ผ ๊ฐ€์ ธ์˜ค๊ธฐ:
        useLocale ํ›…์€ ํ˜„์žฌ ๋กœ์ผ€์ผ(์˜ˆ: ํ”„๋ž‘์Šค์–ด์˜ ๊ฒฝ์šฐ fr)์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

      • URL ํ˜„์ง€ํ™”:
        ๋‚ด๋ถ€ ๋งํฌ(์ฆ‰, ์™ธ๋ถ€ ๋งํฌ๊ฐ€ ์•„๋‹Œ ๋งํฌ)์˜ ๊ฒฝ์šฐ, getLocalizedUrl์„ ์‚ฌ์šฉํ•˜์—ฌ URL ์•ž์— ํ˜„์žฌ ๋กœ์ผ€์ผ์„ ์ž๋™์œผ๋กœ ๋ถ™์ž…๋‹ˆ๋‹ค. ์ฆ‰, ์‚ฌ์šฉ์ž๊ฐ€ ํ”„๋ž‘์Šค์–ด ํ™˜๊ฒฝ์— ์žˆ๋Š” ๊ฒฝ์šฐ, href๋กœ /about์„ ์ „๋‹ฌํ•˜๋ฉด /fr/about์œผ๋กœ ๋ณ€ํ™˜๋ฉ๋‹ˆ๋‹ค.

      • ๋งํฌ ๋ฐ˜ํ™˜:
        ์ปดํฌ๋„ŒํŠธ๋Š” ํ˜„์ง€ํ™”๋œ URL์„ ๊ฐ€์ง„ <a> ์š”์†Œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜์—ฌ ๋‚ด๋น„๊ฒŒ์ด์…˜์ด ๋กœ์ผ€์ผ๊ณผ ์ผ์น˜ํ•˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

      ์ด Link ์ปดํฌ๋„ŒํŠธ๋ฅผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „๋ฐ˜์— ํ†ตํ•ฉํ•จ์œผ๋กœ์จ, ์ผ๊ด€์„ฑ ์žˆ๊ณ  ์–ธ์–ด๋ฅผ ์ธ์‹ํ•˜๋Š” ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์œ ์ง€ํ•˜๋Š” ๋™์‹œ์— ํ–ฅ์ƒ๋œ SEO ๋ฐ ์‚ฌ์šฉ์„ฑ ์ด์ ์„ ๋ˆ„๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    12. Server Actions์—์„œ ํ˜„์žฌ ๋กœ์ผ€์ผ ๊ฐ€์ ธ์˜ค๊ธฐ

      ์„ ํƒ์‚ฌํ•ญ

      Server Action ๋‚ด๋ถ€์—์„œ ํ™œ์„ฑ ๋กœ์ผ€์ผ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ(์˜ˆ: ์ด๋ฉ”์ผ ํ˜„์ง€ํ™” ๋˜๋Š” ๋กœ์ผ€์ผ ์ธ์‹ ๋กœ์ง ์‹คํ–‰), next-intlayer/server์—์„œ getLocale์„ ํ˜ธ์ถœํ•˜์‹ญ์‹œ์˜ค:

      src/app/actions/getLocale.ts
      "use server";
      
      import { getLocale } from "next-intlayer/server";
      
      export const myServerAction = async () => {
        const locale = await getLocale();
      
        // ๋กœ์ผ€์ผ์„ ์‚ฌ์šฉํ•œ ์ž‘์—… ์ˆ˜ํ–‰
      };
      

      getLocale ํ•จ์ˆ˜๋Š” ์‚ฌ์šฉ์ž์˜ ๋กœ์ผ€์ผ์„ ๊ฒฐ์ •ํ•˜๊ธฐ ์œ„ํ•ด ๊ณ„๋‹จ์‹ ์ „๋žต์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค:

      1. ๋จผ์ €, ํ”„๋ก์‹œ์— ์˜ํ•ด ์„ค์ •๋˜์—ˆ์„ ์ˆ˜ ์žˆ๋Š” ๋กœ์ผ€์ผ ๊ฐ’์„ ์š”์ฒญ ํ—ค๋”์—์„œ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค
      2. ํ—ค๋”์—์„œ ๋กœ์ผ€์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์œผ๋ฉด ์ฟ ํ‚ค์— ์ €์žฅ๋œ ๋กœ์ผ€์ผ์„ ์ฐพ์Šต๋‹ˆ๋‹ค
      3. ์ฟ ํ‚ค๊ฐ€ ๋ฐœ๊ฒฌ๋˜์ง€ ์•Š์œผ๋ฉด ๋ธŒ๋ผ์šฐ์ € ์„ค์ •์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์„ ํ˜ธํ•˜๋Š” ์–ธ์–ด ๊ฐ์ง€๋ฅผ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค
      4. ๋งˆ์ง€๋ง‰ ์ˆ˜๋‹จ์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ตฌ์„ฑ๋œ ๊ธฐ๋ณธ ๋กœ์ผ€์ผ๋กœ ๋Œ€์ฒด๋ฉ๋‹ˆ๋‹ค

      ์ด๋Š” ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ปจํ…์ŠคํŠธ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ฐ€์žฅ ์ ์ ˆํ•œ ๋กœ์ผ€์ผ์ด ์„ ํƒ๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

    13. ๋ฒˆ๋“ค ํฌ๊ธฐ ์ตœ์ ํ™”

      ์„ ํƒ์‚ฌํ•ญ

      next-intlayer๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ, ์‚ฌ์ „(dictionary)์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ชจ๋“  ํŽ˜์ด์ง€์˜ ๋ฒˆ๋“ค์— ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. ๋ฒˆ๋“ค ํฌ๊ธฐ๋ฅผ ์ตœ์ ํ™”ํ•˜๊ธฐ ์œ„ํ•ด Intlayer๋Š” ๋งคํฌ๋กœ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ useIntlayer ํ˜ธ์ถœ์„ ์ง€๋Šฅ์ ์œผ๋กœ ๋Œ€์ฒดํ•˜๋Š” ์„ ํƒ์  SWC ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์‚ฌ์ „์€ ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•˜๋Š” ํŽ˜์ด์ง€์˜ ๋ฒˆ๋“ค์—๋งŒ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

      ์ด ์ตœ์ ํ™”๋ฅผ ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด @intlayer/swc ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜์„ธ์š”. ์„ค์น˜๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด next-intlayer๊ฐ€ ์ž๋™์œผ๋กœ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๊ฐ์ง€ํ•˜๊ณ  ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค:

      bash
      npm install @intlayer/swc --save-dev
      
      ์ฐธ๊ณ : ์ด ์ตœ์ ํ™”๋Š” Next.js 13 ์ด์ƒ์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
      ์ฐธ๊ณ : ์ด ํŒจํ‚ค์ง€๋Š” SWC ํ”Œ๋Ÿฌ๊ทธ์ธ์ด Next.js์—์„œ ์•„์ง ์‹คํ—˜์ ์ด๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ๋ณธ์ ์œผ๋กœ ์„ค์น˜๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ–ฅํ›„ ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
      ์ฐธ๊ณ : dictionary ์„ค์ •์—์„œ ์˜ต์…˜์„ importMode: 'dynamic' ๋˜๋Š” importMode: 'fetch'๋กœ ์„ค์ •ํ•˜๋ฉด Suspense์— ์˜์กดํ•˜๊ฒŒ ๋˜๋ฏ€๋กœ, useIntlayer ํ˜ธ์ถœ์„ Suspense ๊ฒฝ๊ณ„๋กœ ๊ฐ์‹ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, Page / Layout ์ปดํฌ๋„ŒํŠธ์˜ ์ตœ์ƒ์œ„ ๋ ˆ๋ฒจ์—์„œ useIntlayer๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
    14. ์ปดํฌ๋„ŒํŠธ ์ฝ˜ํ…์ธ  ์ถ”์ถœ

      ์„ ํƒ์‚ฌํ•ญ

      ๊ธฐ์กด ์ฝ”๋“œ๋ฒ ์ด์Šค๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ์ˆ˜์ฒœ ๊ฐœ์˜ ํŒŒ์ผ์„ ๋ณ€ํ™˜ํ•˜๋Š” ๋ฐ ์‹œ๊ฐ„์ด ๋งŽ์ด ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

      ์ด ํ”„๋กœ์„ธ์Šค๋ฅผ ์šฉ์ดํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด Intlayer๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ณ€ํ™˜ํ•˜๊ณ  ์ฝ˜ํ…์ธ ๋ฅผ ์ถ”์ถœํ•˜๊ธฐ ์œ„ํ•œ ์ปดํŒŒ์ผ๋Ÿฌ / ์ถ”์ถœ๊ธฐ๋ฅผ ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค.

      ์„ค์ •ํ•˜๋ ค๋ฉด intlayer.config.ts ํŒŒ์ผ์— compiler ์„น์…˜์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

      intlayer.config.ts
      import { type IntlayerConfig } from "intlayer";
      
      const config: IntlayerConfig = {
        // ... ๋‚˜๋จธ์ง€ ๊ตฌ์„ฑ
        compiler: {
          /**
           * ์ปดํŒŒ์ผ๋Ÿฌ ํ™œ์„ฑํ™” ์—ฌ๋ถ€๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
           */
          enabled: true,
      
          /**
           * ์ถœ๋ ฅ ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
           */
          output: ({ fileName, extension }) => `./${fileName}${extension}`,
      
          /**
           * ๋ณ€ํ™˜ ํ›„ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ €์žฅํ• ์ง€ ์—ฌ๋ถ€๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ฒŒ ํ•˜๋ฉด ์ปดํŒŒ์ผ๋Ÿฌ๋ฅผ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰ํ•˜์—ฌ ์•ฑ์„ ๋ณ€ํ™˜ํ•œ ๋‹ค์Œ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
           */
          saveComponents: false,
      
          /**
           * ์‚ฌ์ „ ํ‚ค ์ ‘๋‘์‚ฌ
           */
          dictionaryKeyPrefix: "",
        },
      };
      
      export default config;
      

      ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ณ€ํ™˜ํ•˜๊ณ  ์ฝ˜ํ…์ธ ๋ฅผ ์ถ”์ถœํ•˜๊ธฐ ์œ„ํ•ด ์ถ”์ถœ๊ธฐ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค

      bash
      npx intlayer extract
      
      bash
      npm install @intlayer/babel --save-dev
      
      babel.config.js
      const {
        intlayerExtractBabelPlugin,
        getExtractPluginOptions,
      } = require("@intlayer/babel");
      
      module.exports = {
        presets: ["next/babel"],
        plugins: [
          // ์ปดํฌ๋„ŒํŠธ์—์„œ ์ฝ˜ํ…์ธ ๋ฅผ ์‚ฌ์ „์œผ๋กœ ์ถ”์ถœ
          [intlayerExtractBabelPlugin, getExtractPluginOptions()],
        ],
      };
      
      bash
      npm run build # ๋˜๋Š” npm run dev
      

    Turbopack์—์„œ ์‚ฌ์ „ ๋ณ€๊ฒฝ ์‚ฌํ•ญ ๊ฐ์‹œ

    Intlayer v9.5๋ถ€ํ„ฐ withIntlayer๊ฐ€ content watcher๋ฅผ ์ž์ฒด์ ์œผ๋กœ ์‹œ์ž‘ํ•˜๋ฏ€๋กœ ์ถ”๊ฐ€ ์„ค์ •์ด ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

    next dev --turbopack ๋ช…๋ น์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐœ๋ฐœ ์„œ๋ฒ„๋กœ Turbopack์„ ์‚ฌ์šฉํ•  ๋•Œ, ๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ฌ์ „ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ์ž๋™์œผ๋กœ ๊ฐ์ง€๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

    ์ด ์ œํ•œ์€ Turbopack์ด ์ฝ˜ํ…์ธ  ํŒŒ์ผ์˜ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ธฐ ์œ„ํ•ด webpack ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰ํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด intlayer watch ๋ช…๋ น์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐœ๋ฐœ ์„œ๋ฒ„์™€ Intlayer ๋นŒ๋“œ ๊ฐ์‹œ์ž๋ฅผ ๋™์‹œ์— ์‹คํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

    package.json
    {
      // ... ๊ธฐ์กด package.json ๊ตฌ์„ฑ
      "scripts": {
        // ... ๊ธฐ์กด ์Šคํฌ๋ฆฝํŠธ ๊ตฌ์„ฑ
        "dev": "intlayer watch --with 'next dev'",
      },
    }
    
    next-intlayer@<=6.x.x๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, Next.js 16 ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด Turbopack์—์„œ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ž‘๋™ํ•˜๋„๋ก --turbopack ํ”Œ๋ž˜๊ทธ๋ฅผ ์œ ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ์ œํ•œ์„ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด next-intlayer@>=7.x.x๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

    TypeScript ๊ตฌ์„ฑ

    Intlayer๋Š” ๋ชจ๋“ˆ ์ฆ๊ฐ•(module augmentation)์„ ์‚ฌ์šฉํ•˜์—ฌ TypeScript์˜ ์ด์ ์„ ํ™œ์šฉํ•˜๊ณ  ์ฝ”๋“œ๋ฒ ์ด์Šค๋ฅผ ๋”์šฑ ๊ฒฌ๊ณ ํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

    Autocompletion

    Translation error

    TypeScript ๊ตฌ์„ฑ์— ์ž๋™ ์ƒ์„ฑ๋œ ํƒ€์ž…์ด ํฌํ•จ๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

    tsconfig.json
    {
      // ... ๊ธฐ์กด TypeScript ๊ตฌ์„ฑ
      "include": [
        // ... ๊ธฐ์กด TypeScript ์„ค์ •
        ".intlayer/**/*.ts", // ์ž๋™ ์ƒ์„ฑ๋œ ํƒ€์ž… ํฌํ•จ
      ],
    }
    

    Git ์„ค์ •

    Intlayer๊ฐ€ ์ƒ์„ฑํ•œ ํŒŒ์ผ๋“ค์€ Git ์ €์žฅ์†Œ์— ์ปค๋ฐ‹ํ•˜์ง€ ์•Š๋„๋ก ๋ฌด์‹œํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค.

    ์ด๋ฅผ ์œ„ํ•ด .gitignore ํŒŒ์ผ์— ๋‹ค์Œ ์ง€์‹œ๋ฌธ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

    .gitignore
    # Intlayer๊ฐ€ ์ƒ์„ฑํ•œ ํŒŒ์ผ ๋ฌด์‹œ
    .intlayer
    

    VS Code ํ™•์žฅ

    Intlayer์™€ ํ•จ๊ป˜ ๊ฐœ๋ฐœ ๊ฒฝํ—˜์„ ํ–ฅ์ƒ์‹œํ‚ค๊ธฐ ์œ„ํ•ด ๊ณต์‹ Intlayer VS Code ํ™•์žฅ์„ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    ์ด ํ™•์žฅ์€ ๋‹ค์Œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:

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

    ํ™•์žฅ ๊ธฐ๋Šฅ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ Intlayer VS Code ํ™•์žฅ ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

    ๋” ๋‚˜์•„๊ฐ€๊ธฐ

    ๋” ๋‚˜์•„๊ฐ€๋ ค๋ฉด ๋น„์ฃผ์–ผ ์—๋””ํ„ฐ๋ฅผ ๊ตฌํ˜„ํ•˜๊ฑฐ๋‚˜ CMS๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฝ˜ํ…์ธ ๋ฅผ ์™ธ๋ถ€ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    ์ž์ฃผ ๋ฌป๋Š” ์งˆ๋ฌธ

    Next.js๋Š” next.config.js์˜ i18n ํ•„๋“œ๊ฐ€ App Router์— ๋” ์ด์ƒ ์ ์šฉ๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๋‚ด์žฅ ๋ฉ”์‹œ์ง€ ๊ณ„์ธต์ด ์—†์œผ๋ฉฐ, ๋”ฐ๋ผ์„œ ์ง€์—ญํ™” ๊ณ„์ธต์€ ํ•ญ์ƒ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ ํƒ์˜ ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค:

    • next-intl, i18next / next-i18next ๋ฐ react-intl: ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ณ„๋กœ ๋กœ๋“œ๋˜๋Š” JSON ๋˜๋Š” ICU ๋ฉ”์‹œ์ง€ ์นดํƒˆ๋กœ๊ทธ ๊ธฐ๋ฐ˜์˜ ์ „ํ†ต์ ์ธ ์˜ต์…˜์ž…๋‹ˆ๋‹ค.
    • Lingui: ๋นŒ๋“œ ํƒ€์ž„์— ์ปดํŒŒ์ผ๋˜๋Š” ICU ๋ฉ”์‹œ์ง€๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ถ”์ถœ ๊ธฐ๋ฐ˜ ์†”๋ฃจ์…˜์ž…๋‹ˆ๋‹ค.
    • Intlayer: ๊ฐ€์žฅ ์ง„๋ณด๋œ ์†”๋ฃจ์…˜์ž…๋‹ˆ๋‹ค. ์ฝ”๋“œ๋ฒ ์ด์Šค ์–ด๋””์—๋‚˜ ์ฝ˜ํ…์ธ ๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ(๊ฐ ์ปดํฌ๋„ŒํŠธ ์˜† ๋˜๋Š” ์ค‘์•™ ์ง‘์ค‘์‹), ๋นŒ๋“œ ํƒ€์ž„์— ์ปดํฌ๋„ŒํŠธ๋ณ„ ์‚ฌ์ „์œผ๋กœ ์ปดํŒŒ์ผ๋˜๊ณ , ์™„์ „ํ•œ ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ์ œ๊ณตํ•˜๋ฉฐ AI ๋ฒˆ์—ญ, ๋น„์ฃผ์–ผ ์—๋””ํ„ฐ ๋ฐ CMS๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

    ์‹ค์ œ์ ์ธ ์ฐจ์ด๋Š” ๋ธŒ๋ผ์šฐ์ €์— ๋„๋‹ฌํ•˜๋Š” ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค. ๋„ค์ž„์ŠคํŽ˜์ด์Šค ๊ธฐ๋ฐ˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ํŽ˜์ด์ง€์— ์ „์ฒด JSON ์นดํƒˆ๋กœ๊ทธ๋ฅผ ์ „์†กํ•˜์ง€๋งŒ, Intlayer๋Š” ๋ Œ๋”๋ง๋œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ์ฝ˜ํ…์ธ ๋งŒ ์ „์†กํ•˜๋ฏ€๋กœ ๋ฒˆ๋“ค ๋ฐ ํŽ˜์ด์ง€ ํฌ๊ธฐ๋ฅผ ์ตœ๋Œ€ 50%๊นŒ์ง€ ์ค„์—ฌ์ค๋‹ˆ๋‹ค. ์™œ Intlayer์ธ๊ฐ€์™€ Next.js i18n ๋ฒค์น˜๋งˆํฌ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

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

    ๋„ค, ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. next-intl ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฐ€์ด๋“œ ๋˜๋Š” i18next ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฐ€์ด๋“œ๋ฅผ ํ†ตํ•ด ์ฝ˜ํ…์ธ ๋ฅผ ์ ์ง„์ ์œผ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜๋Š” ํ˜„์žฌ API๋ฅผ ์™„์ „ํžˆ ์œ ์ง€ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ํ˜ธํ™˜ ์–ด๋Œ‘ํ„ฐ(compat adapters)๋Š” next-intl, react-i18next, react-intl๊ณผ ์™„์ „ํžˆ ๋™์ผํ•œ API๋ฅผ ๋…ธ์ถœํ•˜๋ฉด์„œ Intlayer ์‚ฌ์ „์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•˜๋ฏ€๋กœ, import ๊ตฌ๋ฌธ๋งŒ ๋ณ€๊ฒฝํ•˜๊ณ  ์ปดํฌ๋„ŒํŠธ ์ฝ”๋“œ๋Š” ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    ๋„ค. sync JSON ํ”Œ๋Ÿฌ๊ทธ์ธ์€ /messages/{locale}/{namespace}.json ํŒŒ์ผ์„ ๋‹จ์ผ ์ง„์‹ค ๊ณต๊ธ‰์›(source of truth)์œผ๋กœ ์œ ์ง€ํ•˜๋ฉด์„œ ์–‘๋ฐฉํ–ฅ์œผ๋กœ Intlayer ์‚ฌ์ „์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. sync PO ํ”Œ๋Ÿฌ๊ทธ์ธ์€ gettext ์นดํƒˆ๋กœ๊ทธ์— ๋Œ€ํ•ด ๋™์ผํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋ฉฐ, ๋กœ์ผ€์ผ๋ณ„ ํŒŒ์ผ์„ ํ†ตํ•ด ๋กœ์ผ€์ผ์„ ํ•œ ํŒŒ์ผ์— ๋ชจ์œผ๋Š” ๋Œ€์‹  ์–ธ์–ด๋ณ„๋กœ ์ฝ˜ํ…์ธ ๋ฅผ ๋ถ„ํ• ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

    ์•„๋‹™๋‹ˆ๋‹ค. npx intlayer extract๋ฅผ ์‹คํ–‰ํ•˜๋ฉด Intlayer๊ฐ€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ฝ๊ณ  ์‚ฌ์šฉ์ž ๋Œ€๋ฉด ๋ฌธ์ž์—ด์„ ์ถ”์ถœํ•˜์—ฌ ๊ฐ ์ปดํฌ๋„ŒํŠธ ์˜†์— .content ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๋ฏ€๋กœ ์นดํƒˆ๋กœ๊ทธ์— ์ผ์ผ์ด ๋ณต์‚ฌํ•  ํ•„์š” ์—†์ด diff๋งŒ ๊ฒ€ํ† ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์ด ๊ฐ€์ด๋“œ์˜ 14๋‹จ๊ณ„๋ฅผ ํ™•์ธํ•˜์„ธ์š”.

    ์™„์ „ ์ž๋™ํ™”๋œ ํŒŒ์ดํ”„๋ผ์ธ์„ ์œ„ํ•ด Intlayer ์ปดํŒŒ์ผ๋Ÿฌ๋Š” ๋นŒ๋“œ ํƒ€์ž„์— JSX, TSX, Vue ๋ฐ Svelte ์†Œ์Šค์—์„œ ๋™์ผํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜์—ฌ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ์‚ฌ์ „์„ ์ƒ์„ฑํ•˜๊ณ  HMR์„ ํ†ตํ•ด ๋™๊ธฐํ™”ํ•˜๋ฏ€๋กœ ์ˆ˜๋™์œผ๋กœ ํ‚ค๋ฅผ ๊ด€๋ฆฌํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

    ์ปดํŒŒ์ผ๋Ÿฌ๋ฅผ ์ผœ๊ธฐ ์ „์— ์•Œ์•„๋‘์–ด์•ผ ํ•  ๋‘ ๊ฐ€์ง€ ์ œํ•œ ์‚ฌํ•ญ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ •์  ๋ถ„์„์œผ๋กœ ์ž‘๋™ํ•˜๋ฏ€๋กœ API ์˜ค๋ฅ˜ ์ฝ”๋“œ๋‚˜ CMS ํ•„๋“œ์™€ ๊ฐ™์ด ๋Ÿฐํƒ€์ž„์—๋งŒ ์กด์žฌํ•˜๋Š” ๋ฌธ์ž์—ด์€ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ํฐ ์ฝ”๋“œ๋ฒ ์ด์Šค์—์„œ ๋ช‡ ๊ฐ€์ง€ ์–ด๋…ธํ…Œ์ด์…˜์ด ํ•„์š”ํ•œ className="active" ๋˜๋Š” ์ƒํƒœ ์ฝ”๋“œ์™€ ๊ฐ™์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ์ง๊ณผ ์‚ฌ์šฉ์ž ๋Œ€๋ฉด ํ…์ŠคํŠธ๋ฅผ ๊ตฌ๋ถ„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. extract ๋ช…๋ น์€ ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜์—ฌ ๋‘ ๊ฐ€์ง€ ๋ฌธ์ œ๋ฅผ ๋ชจ๋‘ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.

    5๊ฐ€์ง€ ๋„๊ตฌ๊ฐ€ ๋ชจ๋‘ ์„ ํƒ ์‚ฌํ•ญ์œผ๋กœ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค:

    • VS Code ํ™•์žฅ ํ”„๋กœ๊ทธ๋žจ: useIntlayer ํ‚ค์—์„œ ์ด๋ฅผ ์„ ์–ธํ•œ ์ฝ˜ํ…์ธ  ํŒŒ์ผ๋กœ ๋ฐ”๋กœ ์ด๋™ํ•˜๊ณ , ์ปดํฌ๋„ŒํŠธ์—์„œ ์ฝ˜ํ…์ธ ๋ฅผ ์ถ”์ถœํ•˜๋ฉฐ, ๋ช…๋ น ํŒ”๋ ˆํŠธ๋‚˜ ์ „์šฉ Intlayer ํƒญ์—์„œ build, fill, test, push, pull์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • LSP ์„œ๋ฒ„: LSP๋ฅผ ์ง€์›ํ•˜๋Š” ๋ชจ๋“  ์—๋””ํ„ฐ์—์„œ ์ •์˜๋กœ ์ด๋™, ๋ชจ๋“  ์ฐธ์กฐ ์ฐพ๊ธฐ, ๋ฒˆ์—ญ ๊ฐ’ ๋งˆ์šฐ์Šค ์˜ค๋ฒ„ ๋ฏธ๋ฆฌ๋ณด๊ธฐ, ํ‚ค ๋ฐ ํ•„๋“œ ์ž๋™ ์™„์„ฑ, ์„ ์–ธ๋˜์ง€ ์•Š์€ ํ‚ค์— ๋Œ€ํ•œ ๊ฒฝ๊ณ  ๋“ฑ ๋™์ผํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ i18next, react-i18next, next-intl, use-intl ํ˜ธ์ถœ๋„ ํ•ด์„ํ•˜๋ฏ€๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
    • MCP ์„œ๋ฒ„: Cursor, VS Code, Claude Desktop, Claude Code, ChatGPT์— Intlayer ๋ฌธ์„œ์™€ CLI๋ฅผ ๋…ธ์ถœํ•˜์—ฌ AI ์–ด์‹œ์Šคํ„ดํŠธ๊ฐ€ ์ตœ์‹  ๋ฌธ์„œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ •ํ™•ํžˆ ๋‹ต๋ณ€ํ•˜๊ณ  intlayer fill ๋“ฑ์˜ ๋ช…๋ น์„ ์ง์ ‘ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
    • Agent Skills: intlayer-config, intlayer-cli, intlayer-content ๋ฐ ๊ฐ ํ”„๋ ˆ์ž„์›Œํฌ ์ „์šฉ ์Šคํ‚ฌ์„ ํ†ตํ•ด AI ์—์ด์ „ํŠธ์—๊ฒŒ ๋ผ์šฐํŒ… ์„ค์ •๊ณผ ์ฝ˜ํ…์ธ  ๋…ธ๋“œ ํƒ€์ž…์„ ํ•™์Šต์‹œํ‚ต๋‹ˆ๋‹ค.
    • ESLint ํ”Œ๋Ÿฌ๊ทธ์ธ: no-raw-text ๊ทœ์น™์œผ๋กœ ํ•˜๋“œ์ฝ”๋”ฉ๋œ ๋ฌธ์ž์—ด์„ ํ‘œ์‹œํ•˜๊ณ , ์ •์  ์‚ฌ์ „ ํ‚ค ๋ฐ ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ์ฝ˜ํ…์ธ ์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ๊ทœ์น™์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.