์ž‘๊ฐ€:
    ์ƒ์„ฑ:2025-08-06๋งˆ์ง€๋ง‰ ์—…๋ฐ์ดํŠธ:2026-08-30

    Intlayer๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ SolidStart ์›น์‚ฌ์ดํŠธ ๋ฒˆ์—ญํ•˜๊ธฐ | ๊ตญ์ œํ™” (i18n)

    youtube.com
    ide.intlayer.org
    intlayer-solid-start-template.vercel.app

    ๋ชฉ์ฐจ

    ์ด ๊ฐ€์ด๋“œ๋Š” ์„œ๋ฒ„ ๋ Œ๋”๋ง SolidStart ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค: ๋กœ์ผ€์ผ ๊ฐ์ง€๋Š” ์š”์ฒญ ์‹œ ์ˆ˜ํ–‰๋˜๊ณ , ํŽ˜์ด์ง€๋Š” ์˜ฌ๋ฐ”๋ฅธ ์–ธ์–ด๋กœ ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋ง๋˜๋ฉฐ, ๊ฒ€์ƒ‰ ์—”์ง„์— ํ•„์š”ํ•œ <html lang>, hreflang ๋ฐ ์‚ฌ์ดํŠธ๋งต ์‹ ํ˜ธ๊ฐ€ ์„œ๋ฒ„ ์ธก์—์„œ ๋‚ด๋ณด๋‚ด์ง‘๋‹ˆ๋‹ค.

    ๋‹ค๋ฅธ ๋Œ€์•ˆ ๋Œ€์‹  Intlayer๋ฅผ ์„ ํƒํ•ด์•ผ ํ•˜๋Š” ์ด์œ 

    @solid-primitives/i18n ๋˜๋Š” i18next์™€ ๊ฐ™์€ ์ฃผ์š” ์†”๋ฃจ์…˜๊ณผ ๋น„๊ตํ•˜์—ฌ Intlayer๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ†ตํ•ฉ ์ตœ์ ํ™”๋ฅผ ์ œ๊ณตํ•˜๋Š” ์†”๋ฃจ์…˜์ž…๋‹ˆ๋‹ค:

    Intlayer๋Š” ์ปดํฌ๋„ŒํŠธ ์ˆ˜์ค€์˜ ์ฝ˜ํ…์ธ  ์Šค์ฝ”ํ•‘, ๋ฐ˜์‘ํ˜• ๋ฒˆ์—ญ ๋ฐ ๊ตญ์ œํ™”(i18n) ํ™•์žฅ์— ํ•„์š”ํ•œ ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜์—ฌ Solid์™€ ์™„๋ฒฝํ•˜๊ฒŒ ์ž‘๋™ํ•˜๋„๋ก ์ตœ์ ํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

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

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

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

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

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

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

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

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

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

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

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

      • solid-intlayer

        Intlayer๋ฅผ Solid ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ํ†ตํ•ฉํ•˜๋Š” ํŒจํ‚ค์ง€์ž…๋‹ˆ๋‹ค. Solid ๊ตญ์ œํ™”๋ฅผ ์œ„ํ•œ ์ปจํ…์ŠคํŠธ ์ œ๊ณต์ž์™€ ํ›…์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

      • vite-intlayer

        Intlayer๋ฅผ Vite ๋ฒˆ๋“ค๋Ÿฌ์™€ ํ†ตํ•ฉํ•˜๊ธฐ ์œ„ํ•œ Vite ํ”Œ๋Ÿฌ๊ทธ์ธ๊ณผ ์‚ฌ์šฉ์ž์˜ ์„ ํ˜ธ ๋กœ์ผ€์ผ์„ ๊ฐ์ง€ํ•˜๊ณ  ์ฟ ํ‚ค๋ฅผ ๊ด€๋ฆฌํ•˜๋ฉฐ URL ๋ฆฌ๋””๋ ‰์…˜์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋กœ์ผ€์ผ ๋ผ์šฐํŒ… ํ•ธ๋“ค๋Ÿฌ๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

      vite-intlayer๋Š” ๋นŒ๋“œ ์‹œ์ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์„œ๋ฒ„ ์ธก์˜ ๊ด€์‹ฌ์‚ฌ์ž…๋‹ˆ๋‹ค: SolidStart์˜ Nitro ์„œ๋ฒ„๊ฐ€ ์‹คํ–‰ํ•˜๋Š” ์š”์ฒญ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. dependencies์— ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์ด ์•ˆ์ „ํ•œ ๊ธฐ๋ณธ๊ฐ’์ž…๋‹ˆ๋‹ค โ€” Nitro๊ฐ€ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ธ๋ผ์ธํ™”ํ•˜๋Š” ๋นŒ๋“œ๋œ .output ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ๋ฐฐํฌํ•˜๋Š” ๊ฒฝ์šฐ์—๋งŒ devDependencies๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    2. ํ”„๋กœ์ ํŠธ ์„ค์ •

      ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์–ธ์–ด๋ฅผ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด ์„ค์ • ํŒŒ์ผ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค:

      intlayer.config.ts
      import { type IntlayerConfig, Locales } from "intlayer";
      
      const config: IntlayerConfig = {
        internationalization: {
          locales: [
            Locales.ENGLISH,
            Locales.FRENCH,
            Locales.SPANISH,
            // ๊ธฐํƒ€ ๋กœ์ผ€์ผ
          ],
          defaultLocale: Locales.ENGLISH,
        },
        routing: {
          mode: "prefix-no-default",
        },
      };
      
      export default config;
      

      prefix-no-default๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ธฐ๋ณธ ๋กœ์ผ€์ผ์€ ์ ‘๋‘์‚ฌ๊ฐ€ ์—†๋Š” URL์—์„œ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค:

      plaintext
      /            /about          โ†’ ์˜์–ด   (๊ธฐ๋ณธ ๋กœ์ผ€์ผ)
      /fr          /fr/about       โ†’ ํ”„๋ž‘์Šค์–ด
      /es          /es/about       โ†’ ์ŠคํŽ˜์ธ์–ด
      
      ์ด ์„ค์ • ํŒŒ์ผ์„ ํ†ตํ•ด ์ง€์—ญํ™”๋œ URL, ๋ฏธ๋“ค์›จ์–ด ๋ฆฌ๋””๋ ‰์…˜, ์ฟ ํ‚ค ์ด๋ฆ„, ์ฝ˜ํ…์ธ  ์„ ์–ธ์˜ ์œ„์น˜ ๋ฐ ํ™•์žฅ์ž๋ฅผ ์„ค์ •ํ•˜๊ณ  ์ฝ˜์†”์—์„œ Intlayer ๋กœ๊ทธ๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•˜๋Š” ๋“ฑ์˜ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ์ „์ฒด ๋ชฉ๋ก์€ ์„ค์ • ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.
    3. Vite ์„ค์ •์— Intlayer ํ†ตํ•ฉํ•˜๊ธฐ

      ์„ค์ •์— Intlayer ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค:

      vite.config.ts
      import { solidStart } from "@solidjs/start/config";
      import { nitro } from "nitro/vite";
      import { defineConfig } from "vite";
      import { intlayer } from "vite-intlayer";
      
      export default defineConfig({
        plugins: [solidStart(), nitro(), intlayer()],
      });
      
      intlayer() Vite ํ”Œ๋Ÿฌ๊ทธ์ธ์€ ์ฝ˜ํ…์ธ  ์„ ์–ธ ํŒŒ์ผ์„ ๋นŒ๋“œํ•˜๊ณ  ๊ฐœ๋ฐœ ๋ชจ๋“œ์—์„œ ์ด๋ฅผ ๊ฐ์‹œํ•˜๋ฉฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด์— Intlayer ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•˜๋Š” ๋ณ„์นญ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

      ๋กœ์ผ€์ผ ๋ผ์šฐํŒ…์€ ํ”Œ๋Ÿฌ๊ทธ์ธ๊ณผ ํ•จ๊ป˜ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค

      SolidStart๋Š” Nitro์—์„œ ์‹คํ–‰๋˜๋ฉฐ, intlayer()๋Š” ๋กœ์ผ€์ผ ๋ผ์šฐํŒ… ํ•ธ๋“ค๋Ÿฌ๋ฅผ Nitro์˜ ์„œ๋ฒ„ ํŒŒ์ดํ”„๋ผ์ธ์— ์ง์ ‘ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค (๊ธฐ๋ณธ์ ์œผ๋กœ true์ธ routing.enableProxy ์˜ต์…˜์„ ํ†ตํ•ด). ์ถ”๊ฐ€๋กœ ์—ฐ๊ฒฐํ•  ์ž‘์—…์ด ์—†์Šต๋‹ˆ๋‹ค: ๋นŒ๋“œ๋œ ์„œ๋ฒ„์—์„œ ๋ชจ๋“  ์š”์ฒญ์€ ๋ฃจํ„ฐ์— ๋„๋‹ฌํ•˜๊ธฐ ์ „์— ๊ฒ€์‚ฌ๋˜๋ฉฐ,

      • ๋กœ์ผ€์ผ์€ URL ์ ‘๋‘์‚ฌ, INTLAYER_LOCALE ์ฟ ํ‚ค, ๊ทธ๋ฆฌ๊ณ  Accept-Language ํ—ค๋” ์ˆœ์œผ๋กœ ์ฝํž™๋‹ˆ๋‹ค;
      • ์ ‘๋‘์‚ฌ๊ฐ€ ์—†๋Š” URL์€ ํ™•์ธ๋œ ๋กœ์ผ€์ผ์ด ๊ธฐ๋ณธ ๋กœ์ผ€์ผ์ด ์•„๋‹ ๋•Œ ํ•ด๋‹น ์ง€์—ญํ™”๋œ URL๋กœ ๋ฆฌ๋””๋ ‰์…˜๋ฉ๋‹ˆ๋‹ค (/ โ†’ /fr);
      • ์ค‘๋ณต ์ ‘๋‘์‚ฌ๊ฐ€ ์žˆ๋Š” URL์€ ํ‘œ์ค€ ํ˜•์‹์œผ๋กœ ๋‹ค์‹œ ๋ฆฌ๋””๋ ‰์…˜๋ฉ๋‹ˆ๋‹ค (/en/about โ†’ /about);
      • ๋กœ์ผ€์ผ ์ฟ ํ‚ค๊ฐ€ ์‘๋‹ต์— ๋‹ค์‹œ ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค.
    4. ์ฝ˜ํ…์ธ  ์„ ์–ธํ•˜๊ธฐ

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

      src/contents/home.content.ts
      import { type Dictionary, t } from "intlayer";
      
      const homeContent = {
        key: "home-page",
        content: {
          title: t({
            en: "Hello world!",
            fr: "Bonjour le monde !",
            es: "ยกHola mundo!",
          }),
          metaTitle: "SolidStart + Intlayer",
          metaDescription: t({
            en: "A SolidStart application internationalized with Intlayer.",
            fr: "Une application SolidStart internationalisรฉe avec Intlayer.",
            es: "Una aplicaciรณn SolidStart internacionalizada con Intlayer.",
          }),
          documentation: t({
            en: "Visit start.solidjs.com to learn how to build SolidStart apps.",
            fr: "Visitez start.solidjs.com pour apprendre ร  crรฉer des applications SolidStart.",
            es: "Visita start.solidjs.com para aprender a crear aplicaciones SolidStart.",
          }),
        },
      } satisfies Dictionary;
      
      export default homeContent;
      
      โš ๏ธ SolidStart ์ „์šฉ ์ฃผ์˜์‚ฌํ•ญ: src/routes ์•„๋ž˜์˜ ๋ชจ๋“  .ts / .tsx ํŒŒ์ผ์€ ๋ผ์šฐํŠธ๊ฐ€ ๋˜๊ณ , .content.ts ํŒŒ์ผ์—๋Š” ๊ธฐ๋ณธ ๋‚ด๋ณด๋‚ด๊ธฐ๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ ํŽ˜์ด์ง€๋กœ ๊ฐ์ง€๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŽ˜์ด์ง€์˜ ์ฝ˜ํ…์ธ  ์„ ์–ธ์€ ๋ผ์šฐํŠธ ๋””๋ ‰ํ„ฐ๋ฆฌ ์™ธ๋ถ€์— ๋ณด๊ด€ํ•˜์„ธ์š” (src/contents/๊ฐ€ ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค). ์ปดํฌ๋„ŒํŠธ์˜ ์ฝ˜ํ…์ธ ๋Š” src/components๊ฐ€ ํŒŒ์ผ ์‹œ์Šคํ…œ ๋ผ์šฐํ„ฐ์— ์˜ํ•ด ์Šค์บ”๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๊ฐ™์€ ์œ„์น˜์— ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

      ์ฝ˜ํ…์ธ  ์„ ์–ธ์€ contentDir ๋””๋ ‰ํ„ฐ๋ฆฌ(๊ธฐ๋ณธ๊ฐ’ ./src)์— ํฌํ•จ๋˜์–ด ์žˆ๊ณ  ์ฝ˜ํ…์ธ  ์„ ์–ธ ํŒŒ์ผ ํ™•์žฅ์ž(๊ธฐ๋ณธ๊ฐ’ .content.{json,ts,tsx,js,jsx,mjs,cjs,md,mdx,yaml,yml})์™€ ์ผ์น˜ํ•˜๋Š” ํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์–ด๋””์—์„œ๋‚˜ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

      ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์ฝ˜ํ…์ธ  ์„ ์–ธ ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

    5. ์ง€์—ญํ™”๋œ ๋ผ์šฐํŒ… ์ถ”๊ฐ€ํ•˜๊ธฐ

      ์ด ๋‹จ๊ณ„์˜ ๋ชฉํ‘œ๋Š” ๊ฐ ์–ธ์–ด์— ๊ณ ์œ ํ•œ URL์„ ๋ถ€์—ฌํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ๊ฒ€์ƒ‰ ์—”์ง„์ด ์ธ๋ฑ์‹ฑํ•˜๋Š” ๋Œ€์ƒ์ž…๋‹ˆ๋‹ค.

      ํŽ˜์ด์ง€๋ฅผ ์„ ํƒ์  ๋™์  ์„ธ๊ทธ๋จผํŠธ ์•„๋ž˜๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค. SolidStart์˜ ํŒŒ์ผ ์‹œ์Šคํ…œ ๋ผ์šฐํ„ฐ์—์„œ [[locale]]์€ :locale? ๊ฒฝ๋กœ ํŒจํ„ด์œผ๋กœ ์ปดํŒŒ์ผ๋ฉ๋‹ˆ๋‹ค:

      plaintext
      src/routes/
        [[locale]].tsx          โ† ์„ธ๊ทธ๋จผํŠธ๋ฅผ ๊ฒ€์ฆํ•˜๋Š” ๋ ˆ์ด์•„์›ƒ
        [[locale]]/
          index.tsx             โ†’ /        ๋ฐ /fr        ๋ฐ /es
          about.tsx             โ†’ /about   ๋ฐ /fr/about  ๋ฐ /es/about
        [...404].tsx            โ†’ ๊ธฐํƒ€ ๋ชจ๋“  ์š”์ฒญ์„ ์œ„ํ•œ ์บ์น˜์˜ฌ
      

      ๋ ˆ์ด์•„์›ƒ ํŒŒ์ผ์˜ ์œ ์ผํ•œ ์—ญํ• ์€ ์„ธ๊ทธ๋จผํŠธ๋ฅผ ์„ค์ •๋œ ๋กœ์ผ€์ผ๋กœ ์ œํ•œํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค:

      src/routes/[[locale]].tsx
      import type { RouteSectionProps } from "@solidjs/router";
      import { locales } from "intlayer";
      
      export const route = {
        matchFilters: {
          locale: locales,
        },
      };
      
      export default function LocaleLayout(props: RouteSectionProps) {
        return <>{props.children}</>;
      }
      

      @solidjs/router๋Š” :locale?์„ ์„ธ๊ทธ๋จผํŠธ๊ฐ€ ์žˆ๋Š” ํŒจํ„ด๊ณผ ์—†๋Š” ํŒจํ„ด ๋‘ ๊ฐ€์ง€๋กœ ํ™•์žฅํ•˜๊ณ  ๋ช…ํ™•์„ฑ์ด ๋†’์€ ์ˆœ์„œ๋Œ€๋กœ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค. matchFilters๋Š” ์ž‘๋™ํ•˜๋Š” ์„ค์ •๊ณผ ํ˜ผ๋ž€์Šค๋Ÿฌ์šด ์„ค์ •์˜ ์ฐจ์ด๋ฅผ ๋งŒ๋“œ๋Š” ์š”์†Œ์ž…๋‹ˆ๋‹ค:

      URLmatchFilters ์—†์ŒmatchFilters ์žˆ์Œ
      /fr/aboutํ”„๋ž‘์Šค์–ด about ํŽ˜์ด์ง€ํ”„๋ž‘์Šค์–ด about ํŽ˜์ด์ง€
      /aboutAbout ํŽ˜์ด์ง€ (์ •์  ์„ธ๊ทธ๋จผํŠธ๊ฐ€ ์šฐ์„ )About ํŽ˜์ด์ง€
      /unknownํ™ˆ ํŽ˜์ด์ง€๊ฐ€ locale=unknown์œผ๋กœ ์กฐ์šฉํžˆ ํ‘œ์‹œ๋จ์ผ์น˜ ์—†์Œ โ†’ 404 ์บ์น˜์˜ฌ๋กœ ๋„˜์–ด๊ฐ
      'prefix-all' ๋ผ์šฐํŒ… ๋ชจ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ [[locale]] ๋Œ€์‹  [locale] (ํ•„์ˆ˜)์„ ๊ถŒ์žฅํ•˜๋ฉฐ, 'no-prefix' ๋˜๋Š” 'search-params'์˜ ๊ฒฝ์šฐ ์„ธ๊ทธ๋จผํŠธ๋ฅผ ์™„์ „ํžˆ ์ œ๊ฑฐํ•˜์„ธ์š”.
    6. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋กœ์ผ€์ผ ์ œ๊ณตํ•˜๊ธฐ

      URL์€ ๋กœ์ผ€์ผ์˜ ๋‹จ์ผ ์ง„์‹ค ์ถœ์ฒ˜(single source of truth)์ž…๋‹ˆ๋‹ค: ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ์ด๋ฏธ ์š”์ฒญ์„ ์ง€์—ญํ™”๋œ ๊ฒฝ๋กœ๋กœ ๋ฆฌ๋””๋ ‰์…˜ํ–ˆ์œผ๋ฏ€๋กœ ๋ฃจํŠธ ๋ ˆ์ด์•„์›ƒ์—์„œ ๊ฒฝ๋กœ๋ฅผ ์ฝ์œผ๋ฉด ์„œ๋ฒ„ ๋ Œ๋”๋ง๊ณผ ํด๋ผ์ด์–ธํŠธ ํ•˜์ด๋“œ๋ ˆ์ด์…˜์ด ์ผ์น˜ํ•˜๊ฒŒ ๋˜๊ณ  ํด๋ผ์ด์–ธํŠธ ์ธก ํƒ์ƒ‰ ์‹œ ๋กœ์ผ€์ผ์ด ๋ฌด๋ฃŒ๋กœ ์—…๋ฐ์ดํŠธ๋ฉ๋‹ˆ๋‹ค.

      src/app.tsx
      import { MetaProvider } from "@solidjs/meta";
      import { Router, useLocation } from "@solidjs/router";
      import { FileRoutes } from "@solidjs/start/router";
      import { defaultLocale, getHTMLTextDir, getLocaleFromPath } from "intlayer";
      import { IntlayerProvider } from "solid-intlayer";
      import { createEffect, type ParentProps, Suspense } from "solid-js";
      import { isServer } from "solid-js/web";
      import { Nav } from "~/components/Nav";
      import "./app.css";
      
      const RootLayout = (props: ParentProps) => {
        const location = useLocation();
        const locale = () => getLocaleFromPath(location.pathname) ?? defaultLocale;
      
        // ์„œ๋ฒ„๋Š” entry-server.tsx์—์„œ <html>์„ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค; ๋กœ์ผ€์ผ ๊ฐ„ ํด๋ผ์ด์–ธํŠธ ์ธก
        // ํƒ์ƒ‰์€ ์†์„ฑ์„ ์ง์ ‘ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
        createEffect(() => {
          if (isServer) return;
      
          document.documentElement.lang = locale();
          document.documentElement.dir = getHTMLTextDir(locale());
        });
      
        return (
          <MetaProvider>
            <IntlayerProvider locale={locale()}>
              <Nav />
              <Suspense>{props.children}</Suspense>
            </IntlayerProvider>
          </MetaProvider>
        );
      };
      
      export default function App() {
        return (
          <Router root={RootLayout}>
            <FileRoutes />
          </Router>
        );
      }
      
      IntlayerProvider๋Š” locale prop์— ๋ฐ˜์‘ํ•˜๋ฏ€๋กœ JSX ๋‚ด๋ถ€์—์„œ ์ ‘๊ทผ์ž ํ˜ธ์ถœ locale()์„ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์œผ๋กœ ์ถฉ๋ถ„ํ•ฉ๋‹ˆ๋‹ค โ€” Solid๋Š” ์ด๋ฅผ ๊ฒŒํ„ฐ๋กœ ์ปดํŒŒ์ผํ•˜๋ฉฐ URL์ด ๋ณ€๊ฒฝ๋  ๋•Œ ์ „์ฒด ํŠธ๋ฆฌ๊ฐ€ ์ƒˆ๋กœ์šด ์–ธ์–ด๋กœ ๋‹ค์‹œ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค.
    7. ์„œ๋ฒ„์—์„œ HTML lang ๋ฐ dir ์†์„ฑ ์„ค์ •ํ•˜๊ธฐ

      <html> ์—˜๋ฆฌ๋จผํŠธ๋Š” Router ์™ธ๋ถ€์˜ entry-server.tsx์— ์˜ํ•ด ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค. ๋Œ€์‹  ์š”์ฒญ URL์—์„œ ๋กœ์ผ€์ผ์„ ์ฝ์œผ์„ธ์š”:

      src/entry-server.tsx
      // @refresh reload
      import { createHandler, StartServer } from "@solidjs/start/server";
      import { defaultLocale, getHTMLTextDir, getLocaleFromPath } from "intlayer";
      import { getRequestEvent } from "solid-js/web";
      
      export default createHandler(() => (
        <StartServer
          document={({ assets, children, scripts }) => {
            const url = getRequestEvent()?.request.url ?? "/";
            const locale = getLocaleFromPath(url) ?? defaultLocale;
      
            return (
              <html dir={getHTMLTextDir(locale)} lang={locale}>
                <head>
                  <meta charset="utf-8" />
                  <meta
                    name="viewport"
                    content="width=device-width, initial-scale=1"
                  />
                  <link rel="icon" href="/favicon.ico" />
                  {assets}
                </head>
                <body>
                  <div id="app">{children}</div>
                  {scripts}
                </body>
              </html>
            );
          }}
        />
      ));
      

      ํฌ๋กค๋Ÿฌ๋Š” ์ด์ œ ์ฒซ ๋ฒˆ์งธ ๋ฐ”์ดํŠธ์—์„œ ์˜ฌ๋ฐ”๋ฅธ ์–ธ์–ด๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค:

      html
      <html dir="ltr" lang="fr"></html>
      
    8. ํŽ˜์ด์ง€์—์„œ Intlayer ํ™œ์šฉํ•˜๊ธฐ

      ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด์—์„œ ์ฝ˜ํ…์ธ  ์‚ฌ์ „์— ์ ‘๊ทผํ•ฉ๋‹ˆ๋‹ค:

      src/routes/[[locale]]/index.tsx
      import { Meta, Title } from "@solidjs/meta";
      import { useIntlayer } from "solid-intlayer";
      import Counter from "~/components/Counter";
      
      export default function Home() {
        const content = useIntlayer("home-page");
      
        return (
          <main>
            <Title>{content.metaTitle.value}</Title>
            <Meta content={content.metaDescription.value} name="description" />
            <h1>{content.title}</h1>
            <Counter />
            <p>{content.documentation}</p>
          </main>
        );
      }
      
      Solid์—์„œ useIntlayer๋Š” ๋ฐ˜์‘ํ˜• ์ฝ˜ํ…์ธ (์˜ˆ: content)๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ์†์„ฑ์— ์ง์ ‘ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

      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 ํ›…์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณด๋ ค๋ฉด ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

      ์ฝ˜ํ…์ธ  ๋…ธ๋“œ๋Š” ์ผ๋ฐ˜ ๋ฒˆ์—ญ์—๋งŒ ๊ตญํ•œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋ณต์ˆ˜ํ˜• ์นด์šดํ„ฐ:

      src/components/Counter.content.ts
      import { type Dictionary, plural, t } from "intlayer";
      
      const counterContent = {
        key: "counter",
        content: {
          clicks: plural({
            one: t({
              en: "{{count}} click",
              fr: "{{count}} clic",
              es: "{{count}} clic",
            }),
            other: t({
              en: "{{count}} clicks",
              fr: "{{count}} clics",
              es: "{{count}} clics",
            }),
          }),
        },
      } satisfies Dictionary;
      
      export default counterContent;
      
      src/components/Counter.tsx
      import { useIntlayer } from "solid-intlayer";
      import { createSignal } from "solid-js";
      
      export default function Counter() {
        const [count, setCount] = createSignal(0);
        const content = useIntlayer("counter");
      
        return (
          <button onClick={() => setCount(count() + 1)} type="button">
            {content.clicks(count())}
          </button>
        );
      }
      

      plural()์€ ํ™œ์„ฑ ๋กœ์ผ€์ผ์— ๋Œ€ํ•ด Intl.PluralRules๋ฅผ ํ†ตํ•ด ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์„ ํƒํ•˜๋ฏ€๋กœ ๋‘ ๊ฐœ ์ด์ƒ์˜ ๋ณต์ˆ˜ํ˜• ํ˜•ํƒœ๋ฅผ ๊ฐ€์ง„ ์–ธ์–ด๋„ ์ถ”๊ฐ€ ์ฝ”๋“œ ์—†์ด ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

    9. ๋‚ด๋ถ€ URL์— ํ˜„์žฌ ์–ธ์–ด ์ ‘๋‘์‚ฌ๋ฅผ ์ž๋™์œผ๋กœ ๋ถ™์ด๋Š” ์ปค์Šคํ…€ Link ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค:

      src/components/LocalizedLink.tsx
      import { A, type AnchorProps } from "@solidjs/router";
      import { getLocalizedUrl } from "intlayer";
      import { useLocale } from "solid-intlayer";
      import type { ParentComponent } from "solid-js";
      
      export const LocalizedLink: ParentComponent<AnchorProps> = (props) => {
        const { locale } = useLocale();
      
        const isExternal = () => /^[a-z][a-z0-9+.-]*:/i.test(props.href);
      
        const localizedHref = () =>
          isExternal() ? props.href : getLocalizedUrl(props.href, locale());
      
        return <A {...props} href={localizedHref()} />;
      };
      
      src/components/Nav.tsx
      import { useIntlayer } from "solid-intlayer";
      import type { Component } from "solid-js";
      import { LocaleSwitcher } from "./LocaleSwitcher";
      import { LocalizedLink } from "./LocalizedLink";
      
      export const Nav: Component = () => {
        const content = useIntlayer("nav");
      
        return (
          <nav>
            <LocalizedLink href="/">{content.home}</LocalizedLink>
            <LocalizedLink href="/about">{content.about}</LocalizedLink>
            <LocaleSwitcher />
          </nav>
        );
      };
      

      ์ด์ œ href="/about"์„ ํ•œ ๋ฒˆ ์ž‘์„ฑํ•˜๋ฉด ํ™œ์„ฑ ๋กœ์ผ€์ผ์— ๋”ฐ๋ผ /about, /fr/about ๋˜๋Š” /es/about์ด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค โ€” ํŽ˜์ด์ง€ ๋‚ด ์–ด๋””์—์„œ๋„ ์ˆ˜๋™์œผ๋กœ ์ ‘๋‘์‚ฌ๋ฅผ ๋ถ™์ผ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

    10. ๋กœ์ผ€์ผ ์Šค์œ„์ฒ˜ ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑํ•˜๊ธฐ

      ์Šค์œ„์ฒ˜๋ฅผ <select> ๋Œ€์‹  ์‹ค์ œ ์•ต์ปค๋กœ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค: ํ˜„์žฌ ํŽ˜์ด์ง€์˜ ๊ฐ ์–ธ์–ด๋Š” ์ƒˆ ํƒญ์—์„œ ์—ด ์ˆ˜ ์žˆ๋Š” ํฌ๋กค๋ง ๊ฐ€๋Šฅํ•œ ๋งํฌ๊ฐ€ ๋˜๋ฉฐ, ์ด๋Š” JavaScript ์ „์šฉ ์ปจํŠธ๋กค์ด ์ œ๊ณตํ•  ์ˆ˜ ์—†๋Š” ์žฅ์ ์ž…๋‹ˆ๋‹ค.

      getPathWithoutLocale์€ ํ˜„์žฌ ๊ฒฝ๋กœ์—์„œ ๋กœ์ผ€์ผ ์„ธ๊ทธ๋จผํŠธ๋ฅผ ์ œ๊ฑฐํ•˜๊ณ  getLocalizedUrl์€ ๋Œ€์ƒ ๋กœ์ผ€์ผ์— ๋งž๊ฒŒ ์ด๋ฅผ ์žฌ๊ตฌ์„ฑํ•˜๋ฏ€๋กœ ํ•˜๋“œ์ฝ”๋”ฉ ์—†์ด ๋งํฌ๊ฐ€ ๋ผ์šฐํŒ… ๋ชจ๋“œ๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค. ๋ Œ๋”๋ง๋œ ๋กœ์ผ€์ผ์„ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์€ ํƒ์ƒ‰์ž…๋‹ˆ๋‹ค โ€” [[locale]] ๋ผ์šฐํŠธ๋Š” URL์—์„œ ์ด๋ฅผ ๋„์ถœํ•ฉ๋‹ˆ๋‹ค โ€” ํ•œํŽธ setLocale์€ ์„ ํƒ์„ INTLAYER_LOCALE ์ฟ ํ‚ค์— ์œ ์ง€ํ•˜๋ฏ€๋กœ ๋‚˜์ค‘์— ๋กœ์ผ€์ผ์ด ์—†๋Š” URL์„ ๋ฐฉ๋ฌธํ•  ๋•Œ ๋™์ผํ•œ ์–ธ์–ด๋กœ ํ•ด๊ฒฐ๋ฉ๋‹ˆ๋‹ค.

      src/components/LocaleSwitcher.tsx
      import { A, useLocation } from "@solidjs/router";
      import {
        getHTMLTextDir,
        getLocaleName,
        getLocalizedUrl,
        getPathWithoutLocale,
      } from "intlayer";
      import { useIntlayer, useLocale } from "solid-intlayer";
      import { type Component, For } from "solid-js";
      
      export const LocaleSwitcher: Component = () => {
        const content = useIntlayer("locale-switcher");
        const location = useLocation();
        const { locale, setLocale, availableLocales } = useLocale();
      
        // ํ˜„์žฌ ํ‘œ์‹œ๋œ ํŽ˜์ด์ง€์˜ ํ‘œ์ค€(๋กœ์ผ€์ผ ์—†๋Š”) ๊ฒฝ๋กœ
        const pathWithoutLocale = () => getPathWithoutLocale(location.pathname);
      
        return (
          <div>
            <button
              aria-label={content.label.value}
              popoverTarget="localePopover"
              type="button"
            >
              {getLocaleName(locale())}
            </button>
            <div id="localePopover" popover="auto">
              <For each={availableLocales}>
                {(localeItem) => (
                  <A
                    dir={getHTMLTextDir(localeItem)}
                    // ์ •ํ™•ํ•œ ์ผ์น˜๋งŒ ํ—ˆ์šฉํ•˜์—ฌ ๊ธฐ๋ณธ ๋กœ์ผ€์ผ ๋งํฌ๊ฐ€ ๋ชจ๋“  ํŽ˜์ด์ง€์—์„œ
                    // ํ™œ์„ฑํ™”๋œ ๊ฒƒ์œผ๋กœ ํ”Œ๋ž˜๊ทธ ์ง€์ •๋˜์ง€ ์•Š๋„๋ก ํ•จ
                    end
                    href={getLocalizedUrl(pathWithoutLocale(), localeItem)}
                    hreflang={localeItem}
                    lang={localeItem}
                    onClick={() => setLocale(localeItem)}
                    // ๋ธŒ๋ผ์šฐ์ €์˜ "๋’ค๋กœ ๊ฐ€๊ธฐ" ๋ฒ„ํŠผ์ด ์ด์ „ ํŽ˜์ด์ง€๋กœ ๋Œ์•„๊ฐ€๋„๋ก ๋ณด์žฅํ•จ
                    replace
                  >
                    {/* ์ž์ฒด ๋กœ์ผ€์ผ์—์„œ์˜ ์–ธ์–ด - ์˜ˆ: Franรงais */}
                    {getLocaleName(localeItem)}
                  </A>
                )}
              </For>
            </div>
          </div>
        );
      };
      

      Solid์—์„œ useLocale์˜ locale์€ ์‹œ๊ทธ๋„ ์ ‘๊ทผ์ž(signal accessor)์ž…๋‹ˆ๋‹ค. ํ˜„์žฌ ๊ฐ’์„ ๋ฐ˜์‘ํ˜•์œผ๋กœ ์ฝ์œผ๋ ค๋ฉด locale() (๊ด„ํ˜ธ ํฌํ•จ)์„ ์‚ฌ์šฉํ•˜์„ธ์š”.

      getLocaleName(localeItem)์€ ๊ฐ ์–ธ์–ด๋ฅผ ํ•ด๋‹น ์–ธ์–ด ์ž์ฒด๋กœ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค โ€” English / Franรงais / Espaรฑol. ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๋ฅผ ์ „๋‹ฌํ•˜๋ฉด ๋Œ€์‹  ํ˜„์žฌ ํ‘œ์‹œ๋œ ์–ธ์–ด๋กœ ์ด๋ฆ„์„ ๋ฒˆ์—ญํ•ฉ๋‹ˆ๋‹ค: getLocaleName(localeItem, locale())์€ ์˜์–ด๋กœ English / French / Spanish, ํ•œ๊ตญ์–ด๋กœ ์˜์–ด / ํ”„๋ž‘์Šค์–ด / ์ŠคํŽ˜์ธ์–ด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

      <A>๋Š” ํ˜„์žฌ URL๊ณผ ์ผ์น˜ํ•˜๋Š” ๋งํฌ์— ์ด๋ฏธ aria-current="page"๋ฅผ ์„ค์ •ํ•˜๋ฏ€๋กœ ์ถ”๊ฐ€ํ•  ๊ฒƒ์ด ์—†์Šต๋‹ˆ๋‹ค. replace๋Š” ๋ผ์šฐํ„ฐ์— ์˜ํ•ด ๋ Œ๋”๋ง๋œ ์†์„ฑ์—์„œ ๋‹ค์‹œ ์ฝํž™๋‹ˆ๋‹ค: ํžˆ์Šคํ† ๋ฆฌ ํ•ญ๋ชฉ์„ ํ‘ธ์‹œํ•˜๋Š” ๋Œ€์‹  ๊ต์ฒดํ•˜๋ฏ€๋กœ ๋ธŒ๋ผ์šฐ์ € "๋’ค๋กœ ๊ฐ€๊ธฐ" ๋ฒ„ํŠผ์€ ์ด์ „ ์–ธ์–ด์˜ ๋™์ผํ•œ ํŽ˜์ด์ง€๊ฐ€ ์•„๋‹ˆ๋ผ ์ „ํ™˜ ์ „์— ๋ฐฉ๋ฌธํ•œ ํŽ˜์ด์ง€๋กœ ๋Œ์•„๊ฐ‘๋‹ˆ๋‹ค.

      ๊ฐ ๋งํฌ์˜ dir ๋ฐ hreflang์€ ์˜ค๋ฅธ์ชฝ์—์„œ ์™ผ์ชฝ์œผ๋กœ ์“ฐ๋Š” ์–ธ์–ด ์ด๋ฆ„์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ง€ํ–ฅํ•˜๋„๋ก ์œ ์ง€ํ•˜๊ณ  ๋ณด์กฐ ๊ธฐ์ˆ  ๋ฐ ํฌ๋กค๋Ÿฌ์— ๊ฐ ๋งํฌ๊ฐ€ ๊ฐ€๋ฆฌํ‚ค๋Š” ์–ธ์–ด๋ฅผ ์•Œ๋ ค์ค๋‹ˆ๋‹ค.

      useLocale ํ›…์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณด๋ ค๋ฉด ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

    11. canonical ๋ฐ hreflang ๋งํฌ ๋‚ด๋ณด๋‚ด๊ธฐ

      ์„ ํƒ์‚ฌํ•ญ

      hreflang ์ฃผ์„์€ /about, /fr/about ๋ฐ /es/about์ด ์„œ๋กœ ๋‹ค๋ฅธ ์–ธ์–ด๋กœ ๋œ ๋™์ผํ•œ ํŽ˜์ด์ง€์ž„์„ ๊ฒ€์ƒ‰ ์—”์ง„์— ์•Œ๋ฆฝ๋‹ˆ๋‹ค. getMultilingualUrls๋Š” ๋ผ์šฐํŒ… ๋ชจ๋“œ์— ๋”ฐ๋ผ ํ‘œ์ค€(๋กœ์ผ€์ผ ์—†๋Š”) ๊ฒฝ๋กœ์—์„œ ์ด๋ฅผ ๋„์ถœํ•˜๋ฏ€๋กœ ์•„๋ฌด๊ฒƒ๋„ ํ•˜๋“œ์ฝ”๋”ฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค:

      src/components/AlternateLinks.tsx
      import {
        defaultLocale,
        getMultilingualUrls,
        getPathWithoutLocale,
      } from "intlayer";
      import { type Component, For } from "solid-js";
      
      export type AlternateLinksProps = {
        /** ๋ Œ๋”๋ง๋˜๋Š” ํŽ˜์ด์ง€์˜ ์ ˆ๋Œ€ URL. */
        url: string;
      };
      
      export const AlternateLinks: Component<AlternateLinksProps> = (props) => {
        const multilingualUrls = () => {
          const { origin, pathname } = new URL(props.url);
      
          return Object.entries(
            getMultilingualUrls(`${origin}${getPathWithoutLocale(pathname)}`)
          );
        };
      
        const canonicalUrl = () =>
          new URL(props.url).origin + new URL(props.url).pathname;
      
        return (
          <>
            <link href={canonicalUrl()} rel="canonical" />
            <For each={multilingualUrls()}>
              {([locale, localizedUrl]) => (
                <link href={localizedUrl} hreflang={locale} rel="alternate" />
              )}
            </For>
            <link
              href={
                multilingualUrls().find(([locale]) => locale === defaultLocale)?.[1]
              }
              hreflang="x-default"
              rel="alternate"
            />
          </>
        );
      };
      

      ์ด๋ฅผ ์š”์ฒญ URL์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์„œ head์— ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค:

      src/entry-server.tsx
      import { AlternateLinks } from "~/components/AlternateLinks";
      
      // โ€ฆ <head> ๋‚ด๋ถ€, ๋‹ค๋ฅธ meta ํƒœ๊ทธ ์˜†:
      <AlternateLinks url={url} />;
      

      GET /fr/about์€ ๋‹ค์Œ์„ ๋‚ด๋ณด๋ƒ…๋‹ˆ๋‹ค:

      html
      <link href="https://example.com/fr/about" rel="canonical" />
      <link href="https://example.com/about" hreflang="en" rel="alternate" />
      <link href="https://example.com/fr/about" hreflang="fr" rel="alternate" />
      <link href="https://example.com/es/about" hreflang="es" rel="alternate" />
      <link href="https://example.com/about" hreflang="x-default" rel="alternate" />
      
      @solidjs/meta์— ๋Œ€ํ•œ ์ฐธ๊ณ  ์‚ฌํ•ญ: ์ž‘์„ฑ ์‹œ์  ๊ธฐ์ค€์œผ๋กœ @solidjs/meta์˜ <Title> ๋ฐ <Meta>๋Š” ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ํ›„ ํด๋ผ์ด์–ธํŠธ์— ์ ์šฉ๋˜์ง€๋งŒ SolidStart v2์˜ ์„œ๋ฒ„ ๋ Œ๋”๋ง๋œ <head>์—๋Š” ๋‚ด๋ณด๋‚ด์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ƒ๋ฅ˜์—์„œ ์ˆ˜์ •๋  ๋•Œ๊นŒ์ง€ ํฌ๋กค๋Ÿฌ๊ฐ€ JavaScript ์—†์ด ๋ด์•ผ ํ•˜๋Š” ํƒœ๊ทธ(canonical, hreflang, ํ•„์š”ํ•œ ๊ฒฝ์šฐ title / description)๋ฅผ ์œ„์™€ ๊ฐ™์ด entry-server.tsx์— ์ง์ ‘ ๋ Œ๋”๋งํ•˜์„ธ์š”.
    12. 404 ํŽ˜์ด์ง€ ๊ด€๋ฆฌํ•˜๊ธฐ

      ์„ ํƒ์‚ฌํ•ญ

      src/routes ๋ฃจํŠธ์˜ ์Šคํ”Œ๋žซ ๋ผ์šฐํŠธ๋Š” matchFilters์— ์˜ํ•ด ๊ฑฐ๋ถ€๋œ ์ž˜๋ชป๋œ ๋กœ์ผ€์ผ ์ ‘๋‘์‚ฌ๋ฅผ ํฌํ•จํ•˜์—ฌ ๋กœ์ผ€์ผ ์„ธ๊ทธ๋จผํŠธ์™€ ์ผ์น˜ํ•˜์ง€ ์•Š์€ ๋ชจ๋“  ๊ฒฝ๋กœ๋ฅผ ์บก์ฒ˜ํ•ฉ๋‹ˆ๋‹ค. ๋กœ์ผ€์ผ์€ ์—ฌ์ „ํžˆ ๋ฃจํŠธ ๋ ˆ์ด์•„์›ƒ์„ ํ†ตํ•ด URL์—์„œ ์˜ค๋ฏ€๋กœ 404 ํŽ˜์ด์ง€๋Š” ๋ฐฉ๋ฌธ์ž์˜ ์–ธ์–ด๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค:

      src/routes/[...404].tsx
      import { Title } from "@solidjs/meta";
      import { HttpStatusCode } from "@solidjs/start";
      import { useIntlayer } from "solid-intlayer";
      import { LocalizedLink } from "~/components/LocalizedLink";
      
      export default function NotFound() {
        const content = useIntlayer("not-found-page");
      
        return (
          <main>
            <Title>{content.metaTitle.value}</Title>
            <HttpStatusCode code={404} />
            <h1>{content.title}</h1>
            <LocalizedLink href="/">{content.backHome}</LocalizedLink>
          </main>
        );
      }
      
      ์š”์ฒญ๊ฒฐ๊ณผ
      /xx404 โ€” xx๋Š” ์„ค์ •๋œ ๋กœ์ผ€์ผ์ด ์•„๋‹™๋‹ˆ๋‹ค
      /nonexistent๊ธฐ๋ณธ ๋กœ์ผ€์ผ์˜ 404
      /fr/nonexistentํ”„๋ž‘์Šค์–ด์˜ 404 (Page introuvable)
    13. ๋‹ค๊ตญ์–ด ์‚ฌ์ดํŠธ๋งต ์ƒ์„ฑํ•˜๊ธฐ

      ์„ ํƒ์‚ฌํ•ญ

      Intlayer์˜ ์‚ฌ์ดํŠธ๋งต ์ƒ์„ฑ๊ธฐ๋Š” ๋ชจ๋“  ๊ฒฝ๋กœ๋ฅผ ๋กœ์ผ€์ผ๋‹น ํ•˜๋‚˜์˜ ํ•ญ๋ชฉ์œผ๋กœ ํ™•์žฅํ•˜๊ณ  ์ด๋“ค ์‚ฌ์ด์˜ xhtml:link ๋Œ€์ฒด ํ•ญ๋ชฉ์„ ์—ฐ๊ฒฐํ•˜๋ฏ€๋กœ ๋ผ์šฐํŠธ๋Š” ํ‘œ์ค€ ๋ฐ ๋กœ์ผ€์ผ ์—†๋Š” ๊ฒฝ๋กœ๋งŒ ๋‚˜์—ดํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

      ํ‰๋ฉด URL๋งŒ ๋‚ด๋ณด๋‚ด๋Š” ๊ธฐ๋ณธ ์ƒ์„ฑ๊ธฐ์™€ ๋‹ฌ๋ฆฌ Intlayer๋Š” ๊ฐ ํŽ˜์ด์ง€์˜ ๋ชจ๋“  ์ง€์—ญํ™”๋œ ๋ณ€ํ˜• ๊ฐ„์— ์–‘๋ฐฉํ–ฅ ๋งํฌ๋ฅผ ์—ฐ๊ฒฐํ•˜์—ฌ ๊ฒ€์ƒ‰ ์—”์ง„์ด ์ง€์—ญํ™”๋œ URL์„ ๊ด€๋ จ์‹œํ‚ค๊ณ  ์˜ฌ๋ฐ”๋ฅธ ๋Œ€์ƒ์—๊ฒŒ ์˜ฌ๋ฐ”๋ฅธ URL์„ ์ œ๊ณตํ•˜๋„๋ก ๋•์Šต๋‹ˆ๋‹ค.

      SolidStart๋Š” HTTP ๋ฉ”์„œ๋“œ๋ฅผ ๋‚ด๋ณด๋‚ด๋Š” ํŒŒ์ผ์„ API ๋ผ์šฐํŠธ๋กœ ๋ณ€ํ™˜ํ•˜๊ณ  ๊ฒฝ๋กœ์—์„œ .ts ํ™•์žฅ์ž๋ฅผ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค โ€” ๋”ฐ๋ผ์„œ src/routes/sitemap.xml.ts๋Š” /sitemap.xml์—์„œ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค:

      src/routes/sitemap.xml.ts
      import type { APIEvent } from "@solidjs/start/server";
      import { generateSitemap } from "intlayer";
      
      const SITE_URL = process.env.SITE_URL ?? "http://localhost:3000";
      
      export const GET = (_event: APIEvent) => {
        const sitemap = generateSitemap(
          [
            { path: "/", changefreq: "daily", priority: 1.0 },
            { path: "/about", changefreq: "monthly", priority: 0.8 },
          ],
          { siteUrl: SITE_URL }
        );
      
        return new Response(sitemap, {
          headers: { "Content-Type": "application/xml" },
        });
      };
      
      output of GET /sitemap.xml
      <?xml version="1.0" encoding="UTF-8"?>
      <urlset
        xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        xmlns:xhtml="http://www.w3.org/1999/xhtml"
      >
        <url>
          <loc>https://example.com/about</loc>
          <changefreq>monthly</changefreq>
          <priority>0.8</priority>
          <xhtml:link rel="alternate" hreflang="en" href="https://example.com/about"/>
          <xhtml:link rel="alternate" hreflang="fr" href="https://example.com/fr/about"/>
          <xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/about"/>
          <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/about"/>
        </url>
      </urlset>
      
      API ๋ผ์šฐํŠธ๋Š” ์„ ํƒ์  ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ด ํŒŒ์ผ์„ [[locale]] ์„ธ๊ทธ๋จผํŠธ ์™ธ๋ถ€์ธ src/routes ๋ฃจํŠธ์— ์œ ์ง€ํ•˜์„ธ์š”. ์‚ฌ์ดํŠธ๋งต์—๋Š” ์ด๋ฏธ ๋ชจ๋“  ๋กœ์ผ€์ผ์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

      getMultilingualUrls๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋™์ผํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ robots.txt๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ Disallow ํ•ญ๋ชฉ์ด ๋ฏผ๊ฐํ•œ ๊ฒฝ๋กœ์˜ ๋ชจ๋“  ์ง€์—ญํ™”๋œ ์ฒ ์ž๋ฅผ ํฌํ•จํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

      src/routes/robots.txt.ts
      import { getMultilingualUrls } from "intlayer";
      
      const SITE_URL = process.env.SITE_URL ?? "http://localhost:3000";
      
      const disallowedPaths = ["/admin", "/private"].flatMap((path) =>
        Object.values(getMultilingualUrls(path))
      );
      
      export const GET = () =>
        new Response(
          [
            "User-agent: *",
            "Allow: /",
            ...disallowedPaths.map((path) => `Disallow: ${path}`),
            "",
            `Sitemap: ${SITE_URL}/sitemap.xml`,
          ].join("\n"),
          { headers: { "Content-Type": "text/plain" } }
        );
      
    14. ์„œ๋ฒ„ ํ•จ์ˆ˜์—์„œ ๋กœ์ผ€์ผ ๊ฐ€์ ธ์˜ค๊ธฐ

      ์„ ํƒ์‚ฌํ•ญ

      ์„œ๋ฒ„ ํ•จ์ˆ˜ ๋˜๋Š” API ๋ผ์šฐํŠธ ๋‚ด๋ถ€์—์„œ ํ˜„์žฌ ๋กœ์ผ€์ผ์— ์ ‘๊ทผํ•˜๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

      ์ด์™€ ๊ฐ™์€ ์ ‘๋‘์‚ฌ ๊ธฐ๋ฐ˜ ์„ค์ •์—์„œ๋Š” URL์ด ๊ถŒํ•œ์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค: getLocaleFromPath๋Š” ์š”์ฒญ URL์—์„œ ์ ‘๋‘์‚ฌ๋ฅผ ์ฝ์Šต๋‹ˆ๋‹ค. getLocale์€ ๋กœ์ผ€์ผ ์ ‘๋‘์‚ฌ๊ฐ€ ์—†๋Š” ์š”์ฒญ์— ๋Œ€ํ•œ ํด๋ฐฑ์ž…๋‹ˆ๋‹ค โ€” INTLAYER_LOCALE ์ฟ ํ‚ค๋ฅผ ๊ฒ€์‚ฌํ•˜๊ณ  ๊ทธ ๋‹ค์Œ x-intlayer-locale ํ—ค๋”๋ฅผ ๊ฒ€์‚ฌํ•œ ํ›„ Accept-Language ํ˜‘์ƒ์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

      src/routes/[[locale]]/index.tsx
      import { createAsync } from "@solidjs/router";
      import { getCookie, getIntlayer, getLocale, getLocaleFromPath } from "intlayer";
      import { getRequestEvent } from "solid-js/web";
      
      const loadLocalizedData = async () => {
        "use server";
      
        const request = getRequestEvent()?.request;
      
        const locale =
          getLocaleFromPath(request?.url) ??
          (await getLocale({
            // ์š”์ฒญ์—์„œ ์ฟ ํ‚ค ๊ฐ€์ ธ์˜ค๊ธฐ (๊ธฐ๋ณธ๊ฐ’: 'INTLAYER_LOCALE')
            getCookie: (name) =>
              getCookie(name, request?.headers.get("cookie") ?? ""),
            // ์š”์ฒญ์—์„œ ํ—ค๋” ๊ฐ€์ ธ์˜ค๊ธฐ (๊ธฐ๋ณธ๊ฐ’: 'x-intlayer-locale'),
            // Accept-Language ํ˜‘์ƒ์œผ๋กœ ํด๋ฐฑ
            getHeader: (name) => request?.headers.get(name) ?? undefined,
          }));
      
        // getIntlayer()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ ์™ธ๋ถ€์—์„œ ์ฝ˜ํ…์ธ  ๊ฐ€์ ธ์˜ค๊ธฐ
        const content = getIntlayer("home-page", locale);
      
        return { locale, title: String(content.title) };
      };
      
      export default function Page() {
        const data = createAsync(() => loadLocalizedData());
      
        return <p>{data()?.title}</p>;
      }
      
      ์—ฌ๊ธฐ์„œ getLocale์—๋งŒ ์˜์กดํ•˜์ง€ ๋งˆ์„ธ์š”: ๋กœ์ผ€์ผ ์ฟ ํ‚ค๋Š” ๋ฐฉ๋ฌธ์ž๊ฐ€ ์–ธ์–ด๋ฅผ ์ ๊ทน์ ์œผ๋กœ ์ „ํ™˜ํ•  ๋•Œ๋งŒ ์ƒ์„ฑ๋˜๋ฏ€๋กœ /fr/...์— ๋Œ€ํ•œ ์ฒซ ๋ฐฉ๋ฌธ์€ ๊ธฐ๋ณธ ๋กœ์ผ€์ผ๋กœ ํ•ด๊ฒฐ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    15. ์ปดํฌ๋„ŒํŠธ ์ฝ˜ํ…์ธ  ์ถ”์ถœํ•˜๊ธฐ

      ์„ ํƒ์‚ฌํ•ญ

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

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

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

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

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

      bash
      npx intlayer extract
      
      5๋‹จ๊ณ„์—์„œ ์„ค๋ช…ํ•œ ์ด์œ ๋กœ ๋‚˜์ค‘์— ์ƒ์„ฑ๋œ ํŽ˜์ด์ง€์˜ ์ฝ˜ํ…์ธ  ํŒŒ์ผ์„ src/routes ์™ธ๋ถ€๋กœ ์ด๋™ํ•˜์„ธ์š”.
      v9๋ถ€ํ„ฐ intlayerCompiler๊ฐ€ intlayer ํ”Œ๋Ÿฌ๊ทธ์ธ์— ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ์ˆ˜๋™์œผ๋กœ ์ถ”๊ฐ€ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

      vite.config.ts๋ฅผ ์—…๋ฐ์ดํŠธํ•˜์—ฌ intlayerCompiler ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ํฌํ•จํ•˜์„ธ์š”:

      vite.config.ts
      import { solidStart } from "@solidjs/start/config";
      import { nitro } from "nitro/vite";
      import { defineConfig } from "vite";
      import { intlayer, intlayerCompiler } from "vite-intlayer";
      
      export default defineConfig({
        plugins: [
          solidStart({ middleware: "src/middleware.ts" }),
          nitro(),
          intlayer(),
          intlayerCompiler(), // ์ปดํŒŒ์ผ๋Ÿฌ ํ”Œ๋Ÿฌ๊ทธ์ธ ์ถ”๊ฐ€
        ],
      });
      
      bash
      npm run build # ๋˜๋Š” npm run dev
      
    16. TypeScript ์„ค์ •ํ•˜๊ธฐ

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

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

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

      ์‚ฌ์ „ ํ‚ค ๋ฐ ์ฝ˜ํ…์ธ  ๊ฒฝ๋กœ๊ฐ€ ์ด์ œ ์ปดํŒŒ์ผ ์‹œ์ ์— ๊ฒ€์‚ฌ๋ฉ๋‹ˆ๋‹ค:

      tsx
      useIntlayer("home-page"); // โœ…
      useIntlayer("hom-page"); // โŒ Argument of type '"hom-page"' is not assignable to parameter of type 'keyof __DictionaryRegistry'
      

    ์„ค์ • ํ™•์ธํ•˜๊ธฐ

    ์„œ๋ฒ„๋ฅผ ๋นŒ๋“œํ•˜๊ณ  ์‹œ์ž‘ํ•œ ํ›„ ๋‹ค์Œ ์š”์ฒญ์ด ์˜ˆ์ƒ๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”:

    bash
    npm run build
    node .output/server/index.mjs
    
    ์š”์ฒญ์˜ˆ์ƒ ์‘๋‹ต
    GET /200 โ€” ์˜์–ด
    GET / (Accept-Language: fr ํฌํ•จ)302 โ†’ /fr
    GET / (INTLAYER_LOCALE=es ์ฟ ํ‚ค ํฌํ•จ)302 โ†’ /es
    GET /fr200 โ€” ํ”„๋ž‘์Šค์–ด, <html lang="fr">
    GET /fr/about200 โ€” ํ”„๋ž‘์Šค์–ด about ํŽ˜์ด์ง€
    GET /en/about302 โ†’ /about (ํ‘œ์ค€ ๋ฆฌ๋””๋ ‰์…˜)
    GET /xx404
    GET /fr/nonexistent404 (ํ”„๋ž‘์Šค์–ด)
    GET /sitemap.xml200 โ€” ๋‹ค๊ตญ์–ด XML ์‚ฌ์ดํŠธ๋งต

    ํŽ˜์ด์ง€๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ํ–‰์€ vite dev์—์„œ๋„ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. 3๊ฐœ์˜ ๋ฆฌ๋””๋ ‰์…˜ ํ–‰์€ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ง์ ‘ ๋ฏธ๋“ค์›จ์–ด๋กœ ๋“ฑ๋กํ•˜์ง€ ์•Š๋Š” ํ•œ ๋นŒ๋“œ๋œ ์„œ๋ฒ„์—๋งŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค (3๋‹จ๊ณ„ ์ฐธ์กฐ).

    ๊ฐœ๋ฐœ ์„œ๋ฒ„๋ฅผ Bun (bun --bun vite dev) ๋Œ€์‹  Node (vite dev)์—์„œ ์‹คํ–‰ํ•˜์„ธ์š”: SolidStart์˜ SSR์€ ํ˜„์žฌ Bun ๋Ÿฐํƒ€์ž„์—์„œ Expected a Response object, but received 'NodeResponse' ์˜ค๋ฅ˜๋กœ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” Intlayer์™€ ๋ฌด๊ด€ํ•˜๋ฉฐ (์ผ๋ฐ˜ ํ…œํ”Œ๋ฆฟ์—์„œ๋„ ์žฌํ˜„๋จ) vite build๊ฐ€ ์•„๋‹Œ ๊ฐœ๋ฐœ ์„œ๋ฒ„์—๋งŒ ์˜ํ–ฅ์„ ๋ฏธ์นฉ๋‹ˆ๋‹ค.

    Git ์„ค์ •

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

    ์ด๋ ‡๊ฒŒ ํ•˜๋ ค๋ฉด .gitignore ํŒŒ์ผ์— ๋‹ค์Œ ์ง€์นจ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

    .gitignore
    # Intlayer์—์„œ ์ƒ์„ฑ๋œ ํŒŒ์ผ ๋ฌด์‹œ
    .intlayer
    

    VS Code ํ™•์žฅ ํ”„๋กœ๊ทธ๋žจ

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

    VS Code Marketplace์—์„œ ์„ค์น˜

    ์ด ํ™•์žฅ ํ”„๋กœ๊ทธ๋žจ์€ ๋‹ค์Œ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:

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

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

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

    ๋ฌธ์„œ ์ฐธ์กฐ

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

    • @solid-primitives/i18n: ์ง์ ‘ ์กฐํ•ฉํ•˜๊ณ  ๋กœ๋“œํ•˜๋ฉฐ ํƒ€์ž…์„ ์ง€์ •ํ•ด์•ผ ํ•˜๋Š” ์ปค๋ฎค๋‹ˆํ‹ฐ ํ”„๋ฆฌ๋ฏธํ‹ฐ๋ธŒ ํ‰๋ฉด ์‚ฌ์ „์ž…๋‹ˆ๋‹ค.
    • i18next(Solid ๋ž˜ํผ ํฌํ•จ): ์„ฑ์ˆ™ํ•œ ์นดํƒˆ๋กœ๊ทธ์ด์ง€๋งŒ Solid Start์—์„œ์˜ ๋กœ์ผ€์ผ ์ธ์‹ ๋ผ์šฐํŒ…์ด๋‚˜ ์„œ๋ฒ„ ๋ Œ๋”๋ง์„ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
    • Intlayer: ๊ฐ€์žฅ ์ง„๋ณด๋œ ์†”๋ฃจ์…˜์ž…๋‹ˆ๋‹ค. ์ฝ”๋“œ๋ฒ ์ด์Šค ์–ด๋””์—๋‚˜ ์ฝ˜ํ…์ธ ๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ(๊ฐ ์ปดํฌ๋„ŒํŠธ ์˜† ๋˜๋Š” ์ค‘์•™ ์ง‘์ค‘์‹), ๋นŒ๋“œ ํƒ€์ž„์— ์ปดํŒŒ์ผ๋˜๊ณ , ์ง€์—ญํ™”๋œ ๋ผ์šฐํŠธ, ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋กœ์ผ€์ผ ํ™•์ธ, ํ‘œ์ค€(canonical) ๋ฐ hreflang ๋งํฌ, ๋‹ค๊ตญ์–ด ์‚ฌ์ดํŠธ๋งต, AI ๋ฒˆ์—ญ, ๋น„์ฃผ์–ผ ์—๋””ํ„ฐ ๋ฐ CMS๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

    Solid Start์—์„œ๋Š” ์ด ๊ฐ€์ด๋“œ์—์„œ ๋ณ„๋„์˜ ๋‹จ๊ณ„๋กœ ์ƒ์„ธํžˆ ๋‹ค๋ฃจ๋Š” ์„œ๋ฒ„ ์ธก ๊ตฌ์„ฑ ์š”์†Œ์—์„œ ํฐ ์ฐจ์ด๊ฐ€ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. ์™œ Intlayer์ธ๊ฐ€์™€ Solid i18n ๋ฒค์น˜๋งˆํฌ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

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

    ๋Œ€๋ถ€๋ถ„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ์ฝ˜ํ…์ธ ๋ฅผ ์ด์ „ํ•˜๋ ค๋ฉด i18next ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฐ€์ด๋“œ๋ฅผ ๋”ฐ๋ฅด์„ธ์š”. ์ ์ง„์ ์œผ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค: sync JSON ํ”Œ๋Ÿฌ๊ทธ์ธ์€ ๊ธฐ์กด JSON ์นดํƒˆ๋กœ๊ทธ๋ฅผ ๋‹จ์ผ ์ง„์‹ค ๊ณต๊ธ‰์›(source of truth)์œผ๋กœ ์œ ์ง€ํ•˜๋ฉด์„œ Intlayer ์‚ฌ์ „์„ ์ƒ์„ฑํ•˜๋ฏ€๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ•˜๋‚˜์”ฉ ์ด์ „ํ•˜๋Š” ๋™์•ˆ ๋‘ ๊ณ„์ธต์˜ ๋™๊ธฐํ™”๊ฐ€ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.

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

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

    ์™„์ „ ์ž๋™ํ™”๋œ ํŒŒ์ดํ”„๋ผ์ธ์„ ์œ„ํ•ด 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 ๊ทœ์น™์œผ๋กœ ํ•˜๋“œ์ฝ”๋”ฉ๋œ ๋ฌธ์ž์—ด์„ ํ‘œ์‹œํ•˜๊ณ , ์ •์  ์‚ฌ์ „ ํ‚ค ๋ฐ ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ์ฝ˜ํ…์ธ ์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ๊ทœ์น™์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.