์ด ํ์ด์ง์๋ ์ ํ๋ฆฌ์ผ์ด์ ํ ํ๋ฆฟ์ด ์ ๊ณต๋ฉ๋๋ค.
์ด ํ์ด์ง์ ์ํ๋ AI ์ด์์คํดํธ๋ฅผ ์ฌ์ฉํ์ฌ ๋ฌธ์๋ฅผ ์์ฝํฉ๋๋ค
๋ฒ์ ๊ธฐ๋ก
- "Initial history"v9.1.32025. 8. 6.
์ด ํ์ด์ง์ ์ฝํ ์ธ ๋ AI๋ฅผ ์ฌ์ฉํ์ฌ ๋ฒ์ญ๋์์ต๋๋ค.
์์ด ์๋ณธ ๋ด์ฉ์ ์ต์ ๋ฒ์ ์ ๋ณด๊ธฐ์ด ๋ฌธ์๋ฅผ ๊ฐ์ ํ ์์ด๋์ด๊ฐ ์์ผ์๋ฉด GitHub์ ํ ๋ฆฌํ์คํธ๋ฅผ ์ ์ถํ์ฌ ์์ ๋กญ๊ฒ ๊ธฐ์ฌํด ์ฃผ์ธ์.
๋ฌธ์์ ๋ํ GitHub ๋งํฌ๋ฌธ์์ Markdown์ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
Intlayer๋ฅผ ์ฌ์ฉํ์ฌ SolidStart ์น์ฌ์ดํธ ๋ฒ์ญํ๊ธฐ | ๊ตญ์ ํ (i18n)
๋ชฉ์ฐจ
์ด ๊ฐ์ด๋๋ ์๋ฒ ๋ ๋๋ง 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๋ฅผ ์ค์ ํ๊ธฐ ์ํ ๋จ๊ณ๋ณ ๊ฐ์ด๋
์์กด์ฑ ์ค์น
npm์ ์ฌ์ฉํ์ฌ ํ์ํ ํจํค์ง๋ฅผ ์ค์นํฉ๋๋ค:
bash์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
--interactiveํ๋๊ทธ๋ ์ ํ ์ฌํญ์ ๋๋ค. AI ์์ด์ ํธ์ธ ๊ฒฝ์ฐintlayer-cli init์ ์ฌ์ฉํ์ธ์.์ด ๋ช ๋ น์ ํ๊ฒฝ์ ๊ฐ์งํ๊ณ ํ์ํ ํจํค์ง๋ฅผ ์ค์นํฉ๋๋ค. ์:
bash์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
intlayer
์ค์ ๊ด๋ฆฌ, ๋ฒ์ญ, ์ฝํ ์ธ ์ ์ธ, ํธ๋์คํ์ผ๋ง ๋ฐ CLI ๋ช ๋ น์ ์ํ ๊ตญ์ ํ ๋๊ตฌ๋ฅผ ์ ๊ณตํ๋ ํต์ฌ ํจํค์ง์ ๋๋ค.
solid-intlayer
Intlayer๋ฅผ Solid ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ ํตํฉํ๋ ํจํค์ง์ ๋๋ค. Solid ๊ตญ์ ํ๋ฅผ ์ํ ์ปจํ ์คํธ ์ ๊ณต์์ ํ ์ ์ ๊ณตํฉ๋๋ค.
vite-intlayer
Intlayer๋ฅผ Vite ๋ฒ๋ค๋ฌ์ ํตํฉํ๊ธฐ ์ํ Vite ํ๋ฌ๊ทธ์ธ๊ณผ ์ฌ์ฉ์์ ์ ํธ ๋ก์ผ์ผ์ ๊ฐ์งํ๊ณ ์ฟ ํค๋ฅผ ๊ด๋ฆฌํ๋ฉฐ URL ๋ฆฌ๋๋ ์ ์ ์ฒ๋ฆฌํ๋ ๋ก์ผ์ผ ๋ผ์ฐํ ํธ๋ค๋ฌ๋ฅผ ํฌํจํฉ๋๋ค.
vite-intlayer๋ ๋น๋ ์์ ๋ฟ๋ง ์๋๋ผ ์๋ฒ ์ธก์ ๊ด์ฌ์ฌ์ ๋๋ค: SolidStart์ Nitro ์๋ฒ๊ฐ ์คํํ๋ ์์ฒญ ํธ๋ค๋ฌ๋ฅผ ์ ๊ณตํฉ๋๋ค.dependencies์ ์ ์งํ๋ ๊ฒ์ด ์์ ํ ๊ธฐ๋ณธ๊ฐ์ ๋๋ค โ Nitro๊ฐ ํธ๋ค๋ฌ๋ฅผ ์ธ๋ผ์ธํํ๋ ๋น๋๋.output๋๋ ํฐ๋ฆฌ๋ฅผ ๋ฐฐํฌํ๋ ๊ฒฝ์ฐ์๋งdevDependencies๋ก ์ด๋ํ ์ ์์ต๋๋ค.ํ๋ก์ ํธ ์ค์
์ ํ๋ฆฌ์ผ์ด์ ์ ์ธ์ด๋ฅผ ์ค์ ํ๊ธฐ ์ํด ์ค์ ํ์ผ์ ์์ฑํฉ๋๋ค:
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์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
์ด ์ค์ ํ์ผ์ ํตํด ์ง์ญํ๋ URL, ๋ฏธ๋ค์จ์ด ๋ฆฌ๋๋ ์ , ์ฟ ํค ์ด๋ฆ, ์ฝํ ์ธ ์ ์ธ์ ์์น ๋ฐ ํ์ฅ์๋ฅผ ์ค์ ํ๊ณ ์ฝ์์์ Intlayer ๋ก๊ทธ๋ฅผ ๋นํ์ฑํํ๋ ๋ฑ์ ์์ ์ ์ํํ ์ ์์ต๋๋ค. ์ฌ์ฉ ๊ฐ๋ฅํ ๋งค๊ฐ๋ณ์์ ์ ์ฒด ๋ชฉ๋ก์ ์ค์ ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ธ์.
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); - ๋ก์ผ์ผ ์ฟ ํค๊ฐ ์๋ต์ ๋ค์ ๊ธฐ๋ก๋ฉ๋๋ค.
- ๋ก์ผ์ผ์ URL ์ ๋์ฌ,
์ฝํ ์ธ ์ ์ธํ๊ธฐ
๋ฒ์ญ์ ์ ์ฅํ๊ธฐ ์ํ ์ฝํ ์ธ ์ ์ธ์ ์์ฑํ๊ณ ๊ด๋ฆฌํฉ๋๋ค:
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})์ ์ผ์นํ๋ ํ ์ ํ๋ฆฌ์ผ์ด์ ์ด๋์์๋ ์ ์ํ ์ ์์ต๋๋ค.์์ธํ ๋ด์ฉ์ ์ฝํ ์ธ ์ ์ธ ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ธ์.
์ง์ญํ๋ ๋ผ์ฐํ ์ถ๊ฐํ๊ธฐ
์ด ๋จ๊ณ์ ๋ชฉํ๋ ๊ฐ ์ธ์ด์ ๊ณ ์ ํ URL์ ๋ถ์ฌํ๋ ๊ฒ์ ๋๋ค. ์ด๊ฒ์ด ๊ฒ์ ์์ง์ด ์ธ๋ฑ์ฑํ๋ ๋์์ ๋๋ค.
ํ์ด์ง๋ฅผ ์ ํ์ ๋์ ์ธ๊ทธ๋จผํธ ์๋๋ก ์ด๋ํฉ๋๋ค. SolidStart์ ํ์ผ ์์คํ ๋ผ์ฐํฐ์์
[[locale]]์:locale?๊ฒฝ๋ก ํจํด์ผ๋ก ์ปดํ์ผ๋ฉ๋๋ค:plaintext์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
๋ ์ด์์ ํ์ผ์ ์ ์ผํ ์ญํ ์ ์ธ๊ทธ๋จผํธ๋ฅผ ์ค์ ๋ ๋ก์ผ์ผ๋ก ์ ํํ๋ ๊ฒ์ ๋๋ค:
src/routes/[[locale]].tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
@solidjs/router๋:locale?์ ์ธ๊ทธ๋จผํธ๊ฐ ์๋ ํจํด๊ณผ ์๋ ํจํด ๋ ๊ฐ์ง๋ก ํ์ฅํ๊ณ ๋ช ํ์ฑ์ด ๋์ ์์๋๋ก ์๋ํฉ๋๋ค.matchFilters๋ ์๋ํ๋ ์ค์ ๊ณผ ํผ๋์ค๋ฌ์ด ์ค์ ์ ์ฐจ์ด๋ฅผ ๋ง๋๋ ์์์ ๋๋ค:ํ ์ด๋ธ์ ๋ชจ๋ ๋ด์ฉ ํ์ํ ์ด๋ธ์ ๋ชจ๋ฌ๋ก ์ด์ด ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ๋ช ํํ๊ฒ ํ์ธ
URL matchFilters์์matchFilters์์/fr/aboutํ๋์ค์ด about ํ์ด์ง ํ๋์ค์ด about ํ์ด์ง /aboutAbout ํ์ด์ง (์ ์ ์ธ๊ทธ๋จผํธ๊ฐ ์ฐ์ ) About ํ์ด์ง /unknownํ ํ์ด์ง๊ฐ locale=unknown์ผ๋ก ์กฐ์ฉํ ํ์๋จ์ผ์น ์์ โ 404 ์บ์น์ฌ๋ก ๋์ด๊ฐ 'prefix-all'๋ผ์ฐํ ๋ชจ๋๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ[[locale]]๋์[locale](ํ์)์ ๊ถ์ฅํ๋ฉฐ,'no-prefix'๋๋'search-params'์ ๊ฒฝ์ฐ ์ธ๊ทธ๋จผํธ๋ฅผ ์์ ํ ์ ๊ฑฐํ์ธ์.์ ํ๋ฆฌ์ผ์ด์ ์ ๋ก์ผ์ผ ์ ๊ณตํ๊ธฐ
URL์ ๋ก์ผ์ผ์ ๋จ์ผ ์ง์ค ์ถ์ฒ(single source of truth)์ ๋๋ค: ๋ฏธ๋ค์จ์ด๊ฐ ์ด๋ฏธ ์์ฒญ์ ์ง์ญํ๋ ๊ฒฝ๋ก๋ก ๋ฆฌ๋๋ ์ ํ์ผ๋ฏ๋ก ๋ฃจํธ ๋ ์ด์์์์ ๊ฒฝ๋ก๋ฅผ ์ฝ์ผ๋ฉด ์๋ฒ ๋ ๋๋ง๊ณผ ํด๋ผ์ด์ธํธ ํ์ด๋๋ ์ด์ ์ด ์ผ์นํ๊ฒ ๋๊ณ ํด๋ผ์ด์ธํธ ์ธก ํ์ ์ ๋ก์ผ์ผ์ด ๋ฌด๋ฃ๋ก ์ ๋ฐ์ดํธ๋ฉ๋๋ค.
src/app.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
IntlayerProvider๋localeprop์ ๋ฐ์ํ๋ฏ๋ก JSX ๋ด๋ถ์์ ์ ๊ทผ์ ํธ์ถlocale()์ ์ ๋ฌํ๋ ๊ฒ์ผ๋ก ์ถฉ๋ถํฉ๋๋ค โ Solid๋ ์ด๋ฅผ ๊ฒํฐ๋ก ์ปดํ์ผํ๋ฉฐ URL์ด ๋ณ๊ฒฝ๋ ๋ ์ ์ฒด ํธ๋ฆฌ๊ฐ ์๋ก์ด ์ธ์ด๋ก ๋ค์ ๋ ๋๋ง๋ฉ๋๋ค.์๋ฒ์์ HTML lang ๋ฐ dir ์์ฑ ์ค์ ํ๊ธฐ
<html>์๋ฆฌ๋จผํธ๋Router์ธ๋ถ์entry-server.tsx์ ์ํด ๋ ๋๋ง๋ฉ๋๋ค. ๋์ ์์ฒญ URL์์ ๋ก์ผ์ผ์ ์ฝ์ผ์ธ์:src/entry-server.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
ํฌ๋กค๋ฌ๋ ์ด์ ์ฒซ ๋ฒ์งธ ๋ฐ์ดํธ์์ ์ฌ๋ฐ๋ฅธ ์ธ์ด๋ฅผ ๋ฐ์ต๋๋ค:
html์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
ํ์ด์ง์์ Intlayer ํ์ฉํ๊ธฐ
์ ํ๋ฆฌ์ผ์ด์ ์ ์ฒด์์ ์ฝํ ์ธ ์ฌ์ ์ ์ ๊ทผํฉ๋๋ค:
src/routes/[[locale]]/index.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
Solid์์
useIntlayer๋ ๋ฐ์ํ ์ฝํ ์ธ (์:content)๋ฅผ ๋ฐํํฉ๋๋ค. ํด๋น ์์ฑ์ ์ง์ ์ ๊ทผํ ์ ์์ต๋๋ค.alt,title,href,aria-label๋ฑ๊ณผ ๊ฐ์string์์ฑ์์ ์ฝํ ์ธ ๋ฅผ ์ฌ์ฉํ๋ ค๋ ๊ฒฝ์ฐ ๋ค์๊ณผ ๊ฐ์ด ํจ์์ ๊ฐ์ ์ฌ์ฉํ ์ ์์ต๋๋ค:tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
useIntlayerํ ์ ๋ํด ์์ธํ ์์๋ณด๋ ค๋ฉด ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ธ์.์ฝํ ์ธ ๋ ธ๋๋ ์ผ๋ฐ ๋ฒ์ญ์๋ง ๊ตญํ๋์ง ์์ต๋๋ค. ์๋ฅผ ๋ค์ด ๋ณต์ํ ์นด์ดํฐ:
src/components/Counter.content.ts์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
src/components/Counter.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
plural()์ ํ์ฑ ๋ก์ผ์ผ์ ๋ํดIntl.PluralRules๋ฅผ ํตํด ์นดํ ๊ณ ๋ฆฌ๋ฅผ ์ ํํ๋ฏ๋ก ๋ ๊ฐ ์ด์์ ๋ณต์ํ ํํ๋ฅผ ๊ฐ์ง ์ธ์ด๋ ์ถ๊ฐ ์ฝ๋ ์์ด ์๋ํฉ๋๋ค.์ง์ญํ๋ Link ์ปดํฌ๋ํธ ์์ฑํ๊ธฐ
๋ด๋ถ URL์ ํ์ฌ ์ธ์ด ์ ๋์ฌ๋ฅผ ์๋์ผ๋ก ๋ถ์ด๋ ์ปค์คํ
Link์ปดํฌ๋ํธ๋ฅผ ์์ฑํฉ๋๋ค:src/components/LocalizedLink.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
src/components/Nav.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
์ด์
href="/about"์ ํ ๋ฒ ์์ฑํ๋ฉด ํ์ฑ ๋ก์ผ์ผ์ ๋ฐ๋ผ/about,/fr/about๋๋/es/about์ด ์์ฑ๋ฉ๋๋ค โ ํ์ด์ง ๋ด ์ด๋์์๋ ์๋์ผ๋ก ์ ๋์ฌ๋ฅผ ๋ถ์ผ ํ์๊ฐ ์์ต๋๋ค.๋ก์ผ์ผ ์ค์์ฒ ์ปดํฌ๋ํธ ์์ฑํ๊ธฐ
์ค์์ฒ๋ฅผ
<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ํ ์ ๋ํด ์์ธํ ์์๋ณด๋ ค๋ฉด ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ธ์.canonical ๋ฐ hreflang ๋งํฌ ๋ด๋ณด๋ด๊ธฐ
์ ํ์ฌํญhreflang์ฃผ์์/about,/fr/about๋ฐ/es/about์ด ์๋ก ๋ค๋ฅธ ์ธ์ด๋ก ๋ ๋์ผํ ํ์ด์ง์์ ๊ฒ์ ์์ง์ ์๋ฆฝ๋๋ค.getMultilingualUrls๋ ๋ผ์ฐํ ๋ชจ๋์ ๋ฐ๋ผ ํ์ค(๋ก์ผ์ผ ์๋) ๊ฒฝ๋ก์์ ์ด๋ฅผ ๋์ถํ๋ฏ๋ก ์๋ฌด๊ฒ๋ ํ๋์ฝ๋ฉ๋์ง ์์ต๋๋ค:src/components/AlternateLinks.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
์ด๋ฅผ ์์ฒญ URL์ ์ฌ์ฉํ ์ ์๋ ๋ฌธ์ head์ ๋ ๋๋งํฉ๋๋ค:
src/entry-server.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
GET /fr/about์ ๋ค์์ ๋ด๋ณด๋ ๋๋ค:html์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
@solidjs/meta์ ๋ํ ์ฐธ๊ณ ์ฌํญ: ์์ฑ ์์ ๊ธฐ์ค์ผ๋ก@solidjs/meta์<Title>๋ฐ<Meta>๋ ํ์ด๋๋ ์ด์ ํ ํด๋ผ์ด์ธํธ์ ์ ์ฉ๋์ง๋ง SolidStart v2์ ์๋ฒ ๋ ๋๋ง๋<head>์๋ ๋ด๋ณด๋ด์ง์ง ์์ต๋๋ค. ์๋ฅ์์ ์์ ๋ ๋๊น์ง ํฌ๋กค๋ฌ๊ฐ JavaScript ์์ด ๋ด์ผ ํ๋ ํ๊ทธ(canonical,hreflang, ํ์ํ ๊ฒฝ์ฐtitle/description)๋ฅผ ์์ ๊ฐ์ดentry-server.tsx์ ์ง์ ๋ ๋๋งํ์ธ์.404 ํ์ด์ง ๊ด๋ฆฌํ๊ธฐ
์ ํ์ฌํญsrc/routes๋ฃจํธ์ ์คํ๋ซ ๋ผ์ฐํธ๋matchFilters์ ์ํด ๊ฑฐ๋ถ๋ ์๋ชป๋ ๋ก์ผ์ผ ์ ๋์ฌ๋ฅผ ํฌํจํ์ฌ ๋ก์ผ์ผ ์ธ๊ทธ๋จผํธ์ ์ผ์นํ์ง ์์ ๋ชจ๋ ๊ฒฝ๋ก๋ฅผ ์บก์ฒํฉ๋๋ค. ๋ก์ผ์ผ์ ์ฌ์ ํ ๋ฃจํธ ๋ ์ด์์์ ํตํด URL์์ ์ค๋ฏ๋ก 404 ํ์ด์ง๋ ๋ฐฉ๋ฌธ์์ ์ธ์ด๋ก ํ์๋ฉ๋๋ค:src/routes/[...404].tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
ํ ์ด๋ธ์ ๋ชจ๋ ๋ด์ฉ ํ์ํ ์ด๋ธ์ ๋ชจ๋ฌ๋ก ์ด์ด ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ๋ช ํํ๊ฒ ํ์ธ
์์ฒญ ๊ฒฐ๊ณผ /xx404โxx๋ ์ค์ ๋ ๋ก์ผ์ผ์ด ์๋๋๋ค/nonexistent๊ธฐ๋ณธ ๋ก์ผ์ผ์ 404/fr/nonexistentํ๋์ค์ด์ 404(Page introuvable)๋ค๊ตญ์ด ์ฌ์ดํธ๋งต ์์ฑํ๊ธฐ
์ ํ์ฌํญ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์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
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" } } );์๋ฒ ํจ์์์ ๋ก์ผ์ผ ๊ฐ์ ธ์ค๊ธฐ
์ ํ์ฌํญ์๋ฒ ํจ์ ๋๋ API ๋ผ์ฐํธ ๋ด๋ถ์์ ํ์ฌ ๋ก์ผ์ผ์ ์ ๊ทผํ๊ณ ์ถ์ ์ ์์ต๋๋ค.
์ด์ ๊ฐ์ ์ ๋์ฌ ๊ธฐ๋ฐ ์ค์ ์์๋ URL์ด ๊ถํ์ ๊ฐ์ง๋๋ค:
getLocaleFromPath๋ ์์ฒญ URL์์ ์ ๋์ฌ๋ฅผ ์ฝ์ต๋๋ค.getLocale์ ๋ก์ผ์ผ ์ ๋์ฌ๊ฐ ์๋ ์์ฒญ์ ๋ํ ํด๋ฐฑ์ ๋๋ค โINTLAYER_LOCALE์ฟ ํค๋ฅผ ๊ฒ์ฌํ๊ณ ๊ทธ ๋ค์x-intlayer-localeํค๋๋ฅผ ๊ฒ์ฌํ ํAccept-Languageํ์์ ์งํํฉ๋๋ค.src/routes/[[locale]]/index.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
์ฌ๊ธฐ์
getLocale์๋ง ์์กดํ์ง ๋ง์ธ์: ๋ก์ผ์ผ ์ฟ ํค๋ ๋ฐฉ๋ฌธ์๊ฐ ์ธ์ด๋ฅผ ์ ๊ทน์ ์ผ๋ก ์ ํํ ๋๋ง ์์ฑ๋๋ฏ๋ก/fr/...์ ๋ํ ์ฒซ ๋ฐฉ๋ฌธ์ ๊ธฐ๋ณธ ๋ก์ผ์ผ๋ก ํด๊ฒฐ๋ ์ ์์ต๋๋ค.์ปดํฌ๋ํธ ์ฝํ ์ธ ์ถ์ถํ๊ธฐ
์ ํ์ฌํญ๊ธฐ์กด ์ฝ๋๋ฒ ์ด์ค๊ฐ ์๋ ๊ฒฝ์ฐ ์์ฒ ๊ฐ์ ํ์ผ์ ๋ณํํ๋ ๋ฐ ์๊ฐ์ด ๊ฑธ๋ฆด ์ ์์ต๋๋ค.
์ด ํ๋ก์ธ์ค๋ฅผ ์ฉ์ดํ๊ฒ ํ๊ธฐ ์ํด 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์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
5๋จ๊ณ์์ ์ค๋ช ํ ์ด์ ๋ก ๋์ค์ ์์ฑ๋ ํ์ด์ง์ ์ฝํ ์ธ ํ์ผ์
src/routes์ธ๋ถ๋ก ์ด๋ํ์ธ์.v9๋ถํฐ
intlayerCompiler๊ฐintlayerํ๋ฌ๊ทธ์ธ์ ํฌํจ๋์ด ์์ผ๋ฏ๋ก ์๋์ผ๋ก ์ถ๊ฐํ ํ์๊ฐ ์์ต๋๋ค.vite.config.ts๋ฅผ ์ ๋ฐ์ดํธํ์ฌintlayerCompilerํ๋ฌ๊ทธ์ธ์ ํฌํจํ์ธ์:vite.config.ts์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
bash์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
TypeScript ์ค์ ํ๊ธฐ
Intlayer๋ ๋ชจ๋ ํ์ฅ์ ์ฌ์ฉํ์ฌ TypeScript์ ์ด์ ์ ์ป๊ณ ์ฝ๋๋ฒ ์ด์ค๋ฅผ ๋์ฑ ๊ฒฌ๊ณ ํ๊ฒ ๋ง๋ญ๋๋ค.
TypeScript ์ค์ ์ ์๋ ์์ฑ๋ ํ์ ์ด ํฌํจ๋์ด ์๋์ง ํ์ธํ์ธ์:
tsconfig.json์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
์ฌ์ ํค ๋ฐ ์ฝํ ์ธ ๊ฒฝ๋ก๊ฐ ์ด์ ์ปดํ์ผ ์์ ์ ๊ฒ์ฌ๋ฉ๋๋ค:
tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
์ค์ ํ์ธํ๊ธฐ
์๋ฒ๋ฅผ ๋น๋ํ๊ณ ์์ํ ํ ๋ค์ ์์ฒญ์ด ์์๋๋ก ๋์ํ๋์ง ํ์ธํ์ธ์:
์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
ํ ์ด๋ธ์ ๋ชจ๋ฌ๋ก ์ด์ด ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ๋ช ํํ๊ฒ ํ์ธ
| ์์ฒญ | ์์ ์๋ต |
|---|---|
GET / | 200 โ ์์ด |
GET / (Accept-Language: fr ํฌํจ) | 302 โ /fr |
GET / (INTLAYER_LOCALE=es ์ฟ ํค ํฌํจ) | 302 โ /es |
GET /fr | 200 โ ํ๋์ค์ด, <html lang="fr"> |
GET /fr/about | 200 โ ํ๋์ค์ด about ํ์ด์ง |
GET /en/about | 302 โ /about (ํ์ค ๋ฆฌ๋๋ ์
) |
GET /xx | 404 |
GET /fr/nonexistent | 404 (ํ๋์ค์ด) |
GET /sitemap.xml | 200 โ ๋ค๊ตญ์ด 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 ํ์ผ์ ๋ค์ ์ง์นจ์ ์ถ๊ฐํ ์ ์์ต๋๋ค:
์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
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๊ท์น์ผ๋ก ํ๋์ฝ๋ฉ๋ ๋ฌธ์์ด์ ํ์ํ๊ณ , ์ ์ ์ฌ์ ํค ๋ฐ ์ฌ์ฉ๋์ง ์๋ ์ฝํ ์ธ ์ ๋ํ ์ถ๊ฐ ๊ท์น์ ์ ๊ณตํฉ๋๋ค.
