์ด ํ์ด์ง์๋ ์ ํ๋ฆฌ์ผ์ด์ ํ ํ๋ฆฟ์ด ์ ๊ณต๋ฉ๋๋ค.
์ด ํ์ด์ง๋ ํ ํ๋ฆฟ์ ๋ผ์ด๋ธ ๋ฐ๋ชจ๋ก ์ฐ๊ฒฐ๋ฉ๋๋ค.
์ด ํ์ด์ง์ ์ํ๋ AI ์ด์์คํดํธ๋ฅผ ์ฌ์ฉํ์ฌ ๋ฌธ์๋ฅผ ์์ฝํฉ๋๋ค
๋ฒ์ ๊ธฐ๋ก
- "Solid useIntlayer API ์ฌ์ฉ๋ฒ์ ์ง์ ์์ฑ ์ก์ธ์ค๋ก ์
๋ฐ์ดํธ"v8.9.02026. 5. 4.
- "init ๋ช
๋ น์ด ์ถ๊ฐ"v7.5.92025. 12. 30.
- "์ด๊ธฐ ์ด๋ ฅ"v5.5.102025. 6. 29.
์ด ํ์ด์ง์ ์ฝํ ์ธ ๋ AI๋ฅผ ์ฌ์ฉํ์ฌ ๋ฒ์ญ๋์์ต๋๋ค.
์์ด ์๋ณธ ๋ด์ฉ์ ์ต์ ๋ฒ์ ์ ๋ณด๊ธฐ์ด ๋ฌธ์๋ฅผ ๊ฐ์ ํ ์์ด๋์ด๊ฐ ์์ผ์๋ฉด GitHub์ ํ ๋ฆฌํ์คํธ๋ฅผ ์ ์ถํ์ฌ ์์ ๋กญ๊ฒ ๊ธฐ์ฌํด ์ฃผ์ธ์.
๋ฌธ์์ ๋ํ GitHub ๋งํฌ๋ฌธ์์ Markdown์ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
Intlayer๋ก Vite ๋ฐ React ๋ฒ์ญํ๊ธฐ | ๊ตญ์ ํ(i18n)
๋ชฉ์ฐจ
์ ๋ค๋ฅธ ๋์๋ค ๋์ Intlayer๋ฅผ ์ ํํด์ผ ํ๋์?
react-i18next๋ i18next์ ๊ฐ์ ์ฃผ์ ์๋ฃจ์ ๊ณผ ๋น๊ตํ์ ๋, Intlayer๋ ๋ค์๊ณผ ๊ฐ์ ํตํฉ๋ ์ต์ ํ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ์๋ฃจ์ ์ ๋๋ค:
Intlayer๋ ์ปดํฌ๋ํธ ์์ค์ ์ฝํ ์ธ ์ค์ฝํ, ์ง์ฐ ๋ก๋(lazy-loaded) ๋ฒ์ญ, ๊ทธ๋ฆฌ๊ณ ๊ตญ์ ํ(i18n) ํ์ฅ์ ํ์ํ ๋ชจ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ์ฌ Vite ๋ฐ React์ ์๋ฒฝํ๊ฒ ์๋ํ๋๋ก ์ต์ ํ๋์ด ์์ต๋๋ค.
ํ์ด์ง์ ๋ฐฉ๋ํ JSON ํ์ผ์ ๋ก๋ํ๋ ๋์ , ํ์ํ ์ฝํ ์ธ ๋ง ๋ก๋ํ์ธ์. Intlayer๋ ๋ฒ๋ค ๋ฐ ํ์ด์ง ํฌ๊ธฐ๋ฅผ ์ต๋ 50%๊น์ง ์ค์ด๋ ๋ฐ ๋์์ ์ค๋๋ค.
์ ํ๋ฆฌ์ผ์ด์ ์ ์ฝํ ์ธ ๋ฅผ ์ค์ฝํํ๋ฉด ๋๊ท๋ชจ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ์ง๋ณด์๊ฐ ์ฉ์ดํด์ง๋๋ค. ์ ์ฒด ์ฝํ ์ธ codebase๋ฅผ ๊ฒํ ํด์ผ ํ๋ค๋ ์ฌ๋ฆฌ์ ๋ถ๋ด ์์ด ๋จ์ผ ๊ธฐ๋ฅ ํด๋๋ฅผ ๋ณต์ ํ๊ฑฐ๋ ์ญ์ ํ ์ ์์ต๋๋ค. ๋ํ, Intlayer๋ ์ฝํ ์ธ ์ ์ ํ์ฑ์ ๋ณด์ฅํ๊ธฐ ์ํด ์๋ฒฝํ ํ์ ์ง์ (fully typed)์ ์ง์ํฉ๋๋ค.
์ฝํ ์ธ ๋ฅผ ๊ฐ์ ์์น์ ๋ฐฐ์น(Co-locating)ํ๋ฉด ๋๊ท๋ชจ ์ธ์ด ๋ชจ๋ธ(LLM)์ ํ์ํ ๋ฌธ๋งฅ์ด ์ค์ด๋ญ๋๋ค. Intlayer๋ ๋ํ ๋๋ฝ๋ ๋ฒ์ญ์ ํ ์คํธํ๊ธฐ ์ํ CLI, LSP, MCP, ๊ทธ๋ฆฌ๊ณ agent skills๊ณผ ๊ฐ์ ๋๊ตฌ ๋ชจ์์ ์ ๊ณตํ์ฌ AI ์์ด์ ํธ์ ๊ฐ๋ฐ์ ๊ฒฝํ(DX)์ ๋์ฑ ์ํํ๊ฒ ๋ง๋ญ๋๋ค.
AI ์ ๊ณต์ ์ฒด์ ๋น์ฉ์ผ๋ก ์ํ๋ LLM์ ์ฌ์ฉํ์ฌ CI/CD ํ์ดํ๋ผ์ธ์์ ๋ฒ์ญ์ ์๋ํํ์ธ์. Intlayer๋ ๋ํ ์ฝํ ์ธ ์ถ์ถ์ ์๋ํํ๋ ์ปดํ์ผ๋ฌ๋ฟ๋ง ์๋๋ผ, ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๋ฒ์ญ์ ๋์์ฃผ๋ ์น ํ๋ซํผ์ ์ ๊ณตํฉ๋๋ค.
๋ฐฉ๋ํ JSON ํ์ผ์ ์ปดํฌ๋ํธ์ ์ฐ๊ฒฐํ๋ฉด ์ฑ๋ฅ ๋ฐ ๋ฐ์์ฑ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. Intlayer๋ ๋น๋ ํ์์ ์ฝํ ์ธ ๋ก๋ฉ์ ์ต์ ํํฉ๋๋ค.
๋จ์ํ i18n ์๋ฃจ์ ๊ทธ ์ด์์ผ๋ก, Intlayer๋ ์์ฒด ํธ์คํ ๊ฐ๋ฅํ ์๊ฐ์ ํธ์ง๊ธฐ(visual editor)์ ์ ์ฒด CMS๋ฅผ ์ ๊ณตํ์ฌ ๋ค๊ตญ์ด ์ฝํ ์ธ ๋ฅผ ์ค์๊ฐ์ผ๋ก ๊ด๋ฆฌํ ์ ์๊ฒ ๋์์ฃผ๋ฉฐ, ๋ฒ์ญ๊ฐ, ์นดํผ๋ผ์ดํฐ ๋ฐ ๊ธฐํ ํ์๊ณผ์ ํ์ ์ ์ํํ๊ฒ ๋ง๋ญ๋๋ค. ์ฝํ ์ธ ๋ ๋ก์ปฌ ๋ฐ/๋๋ ์๊ฒฉ์ผ๋ก ์ ์ฅํ ์ ์์ต๋๋ค.
๋์๋ณด๋ค Intlayer๋ฅผ ์ ํํด์ผ ํ๋ ์ด์ ๋ ๋ฌด์์ ๋๊น?
GitHub์์ ์ ํ๋ฆฌ์ผ์ด์ ํ ํ๋ฆฟ์ ํ์ธํ์ธ์.
์ข ์์ฑ ์ค์น
npm์ ์ฌ์ฉํ์ฌ ํ์ํ ํจํค์ง๋ฅผ ์ค์นํ์ธ์:
bash์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
--interactiveํ๋๊ทธ๋ ์ ํ ์ฌํญ์ ๋๋ค. AI ์์ด์ ํธ์ธ ๊ฒฝ์ฐintlayer-cli init์ ์ฌ์ฉํ์ธ์.Intlayer๋ ๊ตฌ์ฑ ์์ ์์ค ์ฝํ ์ธ ๋ฒ์ ์ง์ , ์ง์ฐ ๋ก๋ ๋ฒ์ญ ๋ฐ ๊ตญ์ ํ ํ์ฅ(i18n)์ ํ์ํ ๋ชจ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ์ฌ Vite ๋ฐ React์ ์๋ฒฝํ๊ฒ ์๋ํ๋๋ก ์ต์ ํ๋์์ต๋๋ค.
bash์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
intlayer ๊ตญ์ ํ ๋๊ตฌ๋ฅผ ์ ๊ณตํ๋ ํต์ฌ ํจํค์ง๋ก, ์ค์ ๊ด๋ฆฌ, ๋ฒ์ญ, ์ฝํ ์ธ ์ ์ธ, ํธ๋์คํ์ผ๋ ์ด์ , ๋ฐ CLI ๋ช ๋ น์ด๋ฅผ ํฌํจํฉ๋๋ค.
react-intlayer Intlayer๋ฅผ React ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ ํตํฉํ๋ ํจํค์ง์ ๋๋ค. React ๊ตญ์ ํ๋ฅผ ์ํ context providers์ hooks์ ์ ๊ณตํฉ๋๋ค.
vite-intlayer Intlayer๋ฅผ Vite bundler์ ํตํฉํ๊ธฐ ์ํ Vite ํ๋ฌ๊ทธ์ธ, ๊ทธ๋ฆฌ๊ณ ์ฌ์ฉ์์ ์ ํธ ๋ก์ผ์ผ ๊ฐ์ง, ์ฟ ํค ๊ด๋ฆฌ, URL ๋ฆฌ๋ค์ด๋ ์ ์ฒ๋ฆฌ๋ฅผ ์ํ ๋ฏธ๋ค์จ์ด๋ฅผ ํฌํจํฉ๋๋ค.
ํ๋ก์ ํธ ๊ตฌ์ฑ
์ํคํ ์ฒ
์ด ์ํคํ ์ฒ์์๋
react-intlayer๊ฐmain.tsx์ ๋ง์ดํธ๋IntlayerProvider๋ฅผ ์ ๊ณตํ์ฌ React ํธ๋ฆฌ๋ฅผ ๊ฐ์๋๋ค. ์ฝํ ์ธ ์ ์ธ์src/์ ์ปดํฌ๋ํธ์ ํจ๊ป ๋ฐฐ์น๋๋ฉฐ, Vite๋vite-intlayer๋ฅผ ํตํด ์ฝํ ์ธ ์ฌ๋น๋ ๋ฐ HMR์ ์ฒ๋ฆฌํฉ๋๋ค.bash์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
์ค์
์ ํ๋ฆฌ์ผ์ด์ ์ ์ธ์ด๋ฅผ ๊ตฌ์ฑํ๊ธฐ ์ํ config ํ์ผ์ ์์ฑํฉ๋๋ค:
intlayer.config.ts์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import { Locales, type IntlayerConfig } from "intlayer"; const config: IntlayerConfig = { internationalization: { locales: [ Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH, // ๋ค๋ฅธ ๋ก์ผ์ผ๋ค ], defaultLocale: Locales.ENGLISH, }, }; export default config;์ด ๊ตฌ์ฑ ํ์ผ์ ํตํด ๋ก์ปฌ๋ผ์ด์ฆ๋ URL, ๋ฏธ๋ค์จ์ด ๋ฆฌ๋๋ ์ , ์ฟ ํค ์ด๋ฆ, ์ฝํ ์ธ ์ ์ธ์ ์์น ๋ฐ ํ์ฅ์, Intlayer ๋ก๊ทธ ๋นํ์ฑํ ๋ฑ์ ์ค์ ํ ์ ์์ต๋๋ค. ์ฌ์ฉ ๊ฐ๋ฅํ ๋ชจ๋ ๋งค๊ฐ๋ณ์์ ์ ์ฒด ๋ชฉ๋ก์ ๊ตฌ์ฑ ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ญ์์ค.
Intlayer๋ฅผ Vite ๊ตฌ์ฑ์ ํตํฉ
intlayer ํ๋ฌ๊ทธ์ธ์ ๊ตฌ์ฑ์ ์ถ๊ฐํ์ธ์:
vite.config.ts์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import { defineConfig } from "vite"; import react from "@vitejs/plugin-react-swc"; import { intlayer } from "vite-intlayer"; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react(), intlayer()], });intlayer()Vite ํ๋ฌ๊ทธ์ธ์ Intlayer๋ฅผ Vite์ ํตํฉํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. ์ฝํ ์ธ ์ ์ธ ํ์ผ์ ๋น๋๋ฅผ ๋ณด์ฅํ๊ณ ๊ฐ๋ฐ ๋ชจ๋์์ ์ด๋ฅผ ๊ฐ์ํฉ๋๋ค. Vite ์ ํ๋ฆฌ์ผ์ด์ ๋ด์ Intlayer ํ๊ฒฝ ๋ณ์๋ฅผ ์ ์ํฉ๋๋ค. ๋ํ ์ฑ๋ฅ์ ์ต์ ํํ๊ธฐ ์ํ ๋ณ์นญ์ ์ ๊ณตํฉ๋๋ค.์ฝํ ์ธ ์ ์ธ
๋ฒ์ญ์ ์ ์ฅํ๊ธฐ ์ํด ์ฝํ ์ธ ์ ์ธ์ ์์ฑํ๊ณ ๊ด๋ฆฌํฉ๋๋ค:
src/app.content.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import { t, type Dictionary } from "intlayer"; import type { ReactNode } from "react"; const appContent = { key: "app", content: { viteLogo: t({ ko: "Vite ๋ก๊ณ ", en: "Vite logo", fr: "Logo Vite", es: "Logo Vite", }), reactLogo: t({ ko: "React ๋ก๊ณ ", en: "React logo", fr: "Logo React", es: "Logo React", }), title: "Vite + React", count: t({ ko: "์นด์ดํธ๋ ", en: "count is ", fr: "le compte est ", es: "el recuento es ", }), edit: t<ReactNode>({ ko: ( <> <code>src/App.tsx</code>๋ฅผ ์์ ํ๊ณ ์ ์ฅํ์ฌ HMR์ ํ ์คํธํ์ธ์ </> ), en: ( <> Edit <code>src/App.tsx</code> and save to test HMR </> ), fr: ( <> รditez <code>src/App.tsx</code> et enregistrez pour tester HMR </> ), es: ( <> Edita <code>src/App.tsx</code> y guarda para probar HMR </> ), }), readTheDocs: t({ ko: "Vite ๋ฐ React ๋ก๊ณ ๋ฅผ ํด๋ฆญํ์ฌ ์์ธํ ์์๋ณด์ธ์", en: "Click on the Vite and React logos to learn more", fr: "Cliquez sur les logos Vite et React pour en savoir plus", es: "Haga clic en los logotipos de Vite y React para obtener mรกs informaciรณn", }), }, } satisfies Dictionary; export default appContent;์ฝํ ์ธ ์ ์ธ์
contentDir๋๋ ํ ๋ฆฌ(๊ธฐ๋ณธ๊ฐ:./src)์ ํฌํจ๋๋ ํ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ด๋ ๊ณณ์๋ ์ ์ํ ์ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ฝํ ์ธ ์ ์ธ ํ์ผ ํ์ฅ์(๊ธฐ๋ณธ๊ฐ:.content.{json,ts,tsx,js,jsx,mjs,cjs,md,mdx,yaml,yml})์ ์ผ์นํด์ผ ํฉ๋๋ค.์์ธํ ๋ด์ฉ์ ์ฝํ ์ธ ์ ์ธ ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ธ์.
์ฝํ ์ธ ํ์ผ์ TSX ์ฝ๋๊ฐ ํฌํจ๋์ด ์๋ค๋ฉด, ์ฝํ ์ธ ํ์ผ์
import React from "react";๋ฅผ importํ๋ ๊ฒ์ ๊ณ ๋ คํด์ผ ํฉ๋๋ค.์ฝ๋์์ ์ฝํ ์ธ ํ์ฉ
์ ํ๋ฆฌ์ผ์ด์ ์ ์ฒด์์ ์ฝํ ์ธ ์ฌ์ ์ ์ ๊ทผํ์ธ์:
src/App.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import { useState, type FC } from "react"; import reactLogo from "./assets/react.svg"; import viteLogo from "/vite.svg"; import "./App.css"; import { IntlayerProvider, useIntlayer } from "react-intlayer"; const AppContent: FC = () => { const [count, setCount] = useState(0); const content = useIntlayer("app"); return ( <> <div> <a href="https://vitejs.dev" target="_blank"> <img src={viteLogo} className="logo" alt={content.viteLogo.value} /> </a> <a href="https://react.dev" target="_blank"> <img src={reactLogo} className="logo react" alt={content.reactLogo.value} /> </a> </div> <h1>{content.title}</h1> <div className="card"> <button onClick={() => setCount((count) => count + 1)}> {content.count} {count} </button> <p>{content.edit}</p> </div> <p className="read-the-docs">{content.readTheDocs}</p> </> ); }; const App: FC = () => ( <IntlayerProvider> <AppContent /> </IntlayerProvider> ); export default App;alt,title,href,aria-label๋ฑ๊ณผ ๊ฐ์string์์ฑ์์ ์ฝํ ์ธ ๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ํจ์์ ๊ฐ์ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํ ์ ์์ต๋๋ค:tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
useIntlayerํ ์ ๋ํด ๋ ์์๋ณด๋ ค๋ฉด ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ธ์.๊ธฐ์กด ์ ํ๋ฆฌ์ผ์ด์ ์ด ์๋ ๊ฒฝ์ฐ, Intlayer Compiler ๋ฐ extract ๋ช ๋ น์ ์ฌ์ฉํ์ฌ ์์ฒ ๊ฐ์ ์ปดํฌ๋ํธ๋ฅผ ๋ช ์ด ๋ง์ ๋ณํํ ์ ์์ต๋๋ค.
์ฝํ ์ธ ์ธ์ด ๋ณ๊ฒฝ
์ ํ์ฌํญ์ฝํ ์ธ ์ธ์ด๋ฅผ ๋ณ๊ฒฝํ๋ ค๋ฉด
useLocaleํ ์์ ์ ๊ณตํ๋setLocaleํจ์๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด ํจ์๋ฅผ ์ฌ์ฉํ๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ก์ผ์ผ์ ์ค์ ํ๊ณ ๊ทธ์ ๋ฐ๋ผ ์ฝํ ์ธ ๋ฅผ ์ ๋ฐ์ดํธํ ์ ์์ต๋๋ค.src/components/LocaleSwitcher.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import type { FC } from "react"; import { Locales } from "intlayer"; import { useLocale } from "react-intlayer"; const LocaleSwitcher: FC = () => { const { setLocale } = useLocale(); return ( <button onClick={() => setLocale(Locales.English)}> ์ธ์ด๋ฅผ ์์ด๋ก ๋ณ๊ฒฝ </button> ); };useLocalehook์ ๋ํด ์์ธํ ์์๋ณด๋ ค๋ฉด documentation์ ์ฐธ์กฐํ์ธ์.์ ํ๋ฆฌ์ผ์ด์ ์ ํ์งํ๋ ๋ผ์ฐํ ์ถ๊ฐ
์ ํ์ฌํญ์ด ๋จ๊ณ์ ๋ชฉ์ ์ ๊ฐ ์ธ์ด์ ๋ํ ๊ณ ์ ํ ๊ฒฝ๋ก๋ฅผ ๋ง๋๋ ๊ฒ์ ๋๋ค. ์ด๋ SEO ๋ฐ SEO ์นํ์ ์ธ URL์ ์ ์ฉํฉ๋๋ค. ์์:
-์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
Intlayer๋ ๋จ์ํ i18n ์๋ฃจ์ ๊ทธ ์ด์์ผ๋ก ๊ด๋ฆฌ์ ๋์์ด ๋๋ ์์ฒด ํธ์คํ ๋น์ฃผ์ผ ํธ์ง๊ธฐ ๋ฐ ์ ์ฒด CMS๋ฅผ ์ ๊ณตํฉ๋๋ค. ๋ค๊ตญ์ด ์ฝํ ์ธ ๋ฅผ ์ค์๊ฐ์ผ๋ก ์ ๊ณตํ์ฌ ๋ฒ์ญ๊ฐ, ์นดํผ๋ผ์ดํฐ, ๊ธฐํ ํ์๊ณผ ์ํํ๊ฒ ํ์ ํ ์ ์์ต๋๋ค. ์ฝํ ์ธ ๋ ๋ก์ปฌ ๋ฐ/๋๋ ์๊ฒฉ์ผ๋ก ์ ์ฅ๋ ์ ์์ต๋๋ค.
์ ํ๋ฆฌ์ผ์ด์ ์ ๋ก์ปฌ๋ผ์ด์ ์ด์ ๋ ๋ผ์ฐํ ์ ์ถ๊ฐํ๋ ค๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ผ์ฐํธ๋ฅผ ๋ํํ๊ณ ๋ก์บ ๊ธฐ๋ฐ ๋ผ์ฐํ ์ ์ฒ๋ฆฌํ๋
LocaleRouter์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค. ๋ค์์ React Router๋ฅผ ์ฌ์ฉํ ์์์ ๋๋ค:src/components/LocaleRouter.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import { localeMap } from "intlayer"; // 'intlayer'์ ์ ํธ๋ฆฌํฐ ํจ์ ๋ฐ ํ์ import type { FC, PropsWithChildren } from "react"; // ํจ์ํ ์ปดํฌ๋ํธ ๋ฐ props์ ๋ํ React ํ์ import { IntlayerProvider } from "react-intlayer"; // ๊ตญ์ ํ ์ปจํ ์คํธ๋ฅผ ์ํ Provider import { BrowserRouter, Route, Routes } from "react-router-dom"; // ๋ค๋น๊ฒ์ด์ ์ ๊ด๋ฆฌํ๊ธฐ ์ํ Router ์ปดํฌ๋ํธ /** * ๋ก์ผ์ผ๋ณ ๋ผ์ฐํธ๋ฅผ ์ค์ ํ๋ ๋ผ์ฐํฐ ์ปดํฌ๋ํธ์ ๋๋ค. * React Router๋ฅผ ์ฌ์ฉํ์ฌ ๋ค๋น๊ฒ์ด์ ์ ๊ด๋ฆฌํ๊ณ ๋ก์ปฌ๋ผ์ด์ฆ๋ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํฉ๋๋ค. */ export const LocaleRouter: FC<PropsWithChildren> = ({ children }) => ( <BrowserRouter> <Routes> {localeMap(({ locale, urlPrefix }) => ( <Route // ๋ก์ผ์ผ(/en/, /fr/ ๋ฑ)์ ์บก์ฒํ๊ณ ์ดํ์ ๋ชจ๋ ๊ฒฝ๋ก์ ์ผ์นํ๋ ๋ผ์ฐํธ ํจํด path={`${urlPrefix}/*`} key={locale} element={ <IntlayerProvider locale={locale}>{children}</IntlayerProvider> } // ๋ก์ผ์ผ ๊ด๋ฆฌ๋ฅผ ์ํด children์ ๋ํํฉ๋๋ค /> ))} </Routes> </BrowserRouter> );์ฐธ๊ณ :
routing.mode: 'no-prefix' | 'search-params'๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ,localeMapํจ์๋ฅผ ์ฌ์ฉํ ํ์๊ฐ ์์ ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค.๊ทธ๋ฌ๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์์
LocaleRouter์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค:src/App.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import { LocaleRouter } from "./components/LocaleRouter"; import type { FC } from "react"; // ... Your AppContent component const App: FC = () => ( <LocaleRouter> <AppContent /> </LocaleRouter> );๋ํ
intlayerProxy๋ฅผ ์ฌ์ฉํ์ฌ ์ ํ๋ฆฌ์ผ์ด์ ์ ์๋ฒ ์ธก ๋ผ์ฐํ ์ ์ถ๊ฐํ ์ ์์ต๋๋ค. ์ด ํ๋ฌ๊ทธ์ธ์ URL์ ๊ธฐ๋ฐ์ผ๋ก ํ์ฌ ๋ก์ผ์ผ์ ์๋์ผ๋ก ๊ฐ์งํ๊ณ ์ ์ ํ ๋ก์ผ์ผ ์ฟ ํค๋ฅผ ์ค์ ํฉ๋๋ค. ๋ก์ผ์ผ์ด ์ง์ ๋์ง ์์ ๊ฒฝ์ฐ, ํ๋ฌ๊ทธ์ธ์ ์ฌ์ฉ์์ ๋ธ๋ผ์ฐ์ ์ธ์ด ์ค์ ์ ๊ธฐ๋ฐ์ผ๋ก ๊ฐ์ฅ ์ ์ ํ ๋ก์ผ์ผ์ ๊ฒฐ์ ํฉ๋๋ค. ๋ก์ผ์ผ์ด ๊ฐ์ง๋์ง ์์ผ๋ฉด ๊ธฐ๋ณธ ๋ก์ผ์ผ๋ก ๋ฆฌ๋ค์ด๋ ํธํฉ๋๋ค.ํ๋ก๋์ ํ๊ฒฝ์์
intlayerProxy๋ฅผ ์ฌ์ฉํ๋ ค๋ฉดvite-intlayerํจํค์ง๋ฅผdevDependencies์์dependencies๋ก ์ด๋ํด์ผ ํฉ๋๋ค.Intlayer v9๋ถํฐ
intlayerProxy()๋intlayer()ํ๋ฌ๊ทธ์ธ์ ์ง์ ๋ฒ๋ค๋์ด ์์ผ๋ฉฐrouting.enableProxy์ต์ (true๊ฐ ๊ธฐ๋ณธ๊ฐ)์ ํตํด ๊ธฐ๋ณธ์ ์ผ๋ก ํ์ฑํ๋ฉ๋๋ค. ์๋์ ํ์๋ ๋๋ก ๋ณ๋๋ก ๋ฑ๋กํ๋ ๊ฒ์ ์ด์ ์ ํ์ฌํญ์ ๋๋ค โ ์ญํธํ์ฑ ๋ฐ ํ๋ฌ๊ทธ์ธ ์์๋ฅผ ์ ์ดํด์ผ ํ๋ ์ค์ ์ ์ํด ์ ์ง๋ฉ๋๋ค.routing.enableProxy: false๋ก ์ค์ ํ์ฌ ์ฌ์ฉํ์ง ์์ ์ ์์ต๋๋ค. v9 ๋ฆด๋ฆฌ์ค ๋ ธํธ๋ฅผ ์ฐธ์กฐํ์ธ์.vite.config.ts์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import { defineConfig } from "vite"; import react from "@vitejs/plugin-react-swc"; import { intlayer } from "vite-intlayer"; // https://vitejs.dev/config/ export default defineConfig({ plugins: [ react(), intlayer({ proxy: { ignore: (req) => req.url?.startsWith("/api"), }, }), ], });๋ก์ผ์ผ์ด ๋ณ๊ฒฝ๋ ๋ URL ๋ณ๊ฒฝํ๊ธฐ
์ ํ์ฌํญ๋ก์ผ์ผ์ด ๋ณ๊ฒฝ๋ ๋ URL์ ๋ณ๊ฒฝํ๋ ค๋ฉด,
useLocaleํ ์์ ์ ๊ณตํ๋onLocaleChange์์ฑ์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๋์์,react-router-dom์useLocation๊ณผuseNavigateํ ์ ์ฌ์ฉํ์ฌ URL ๊ฒฝ๋ก๋ฅผ ์ ๋ฐ์ดํธํ ์ ์์ต๋๋ค.src/components/LocaleSwitcher.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import { useLocation, useNavigate } from "react-router-dom"; import { Locales, getHTMLTextDir, getLocaleName, getLocalizedUrl, } from "intlayer"; import { useLocale } from "react-intlayer"; import { type FC } from "react"; const LocaleSwitcher: FC = () => { const { pathname, search } = useLocation(); // ํ์ฌ URL ๊ฒฝ๋ก๋ฅผ ๊ฐ์ ธ์ต๋๋ค. ์: /fr/about?foo=bar const navigate = useNavigate(); const { locale, availableLocales, setLocale } = useLocale({ onLocaleChange: (locale) => { // ์ ๋ฐ์ดํธ๋ ๋ก์ผ์ผ๋ก URL์ ๊ตฌ์ฑํฉ๋๋ค // ์: /es/about?foo=bar const pathWithLocale = getLocalizedUrl(`${pathname}${search}`, locale); // URL ๊ฒฝ๋ก๋ฅผ ์ ๋ฐ์ดํธํฉ๋๋ค navigate(pathWithLocale); }, }); return ( <div> <button popoverTarget="localePopover">{getLocaleName(locale)}</button> <div id="localePopover" popover="auto"> {availableLocales.map((localeItem) => ( <a href={getLocalizedUrl(location.pathname, localeItem)} hrefLang={localeItem} aria-current={locale === localeItem ? "page" : undefined} onClick={(e) => { e.preventDefault(); setLocale(localeItem); }} key={localeItem} > <span> {/* ๋ก์ผ์ผ - ์: FR */} {localeItem} </span> <span> {/* ํด๋น ๋ก์ผ์ผ์์์ ์ธ์ด๋ช - ์: Franรงais */} {getLocaleName(localeItem, locale)} </span> <span dir={getHTMLTextDir(localeItem)} lang={localeItem}> {/* ํ์ฌ ๋ก์ผ์ผ์์์ ์ธ์ด๋ช - ์: Francรฉs (ํ์ฌ ๋ก์ผ์ผ์ด Locales.SPANISH์ผ ๋) */} {getLocaleName(localeItem)} </span> <span dir="ltr" lang={Locales.ENGLISH}> {/* ์์ด๋ก ๋ ์ธ์ด๋ช - ์: French */} {getLocaleName(localeItem, Locales.ENGLISH)} </span> </a> ))} </div> </div> ); };๋ฌธ์ ์ฐธ๊ณ :
์๋๋ ์ถ๊ฐ ์ค๋ช ๊ณผ ๊ฐ์ ๋ ์ฝ๋ ์์ ๊ฐ ํฌํจ๋ ์ ๋ฐ์ดํธ๋ 9๋จ๊ณ์ ๋๋ค:
HTML ์ธ์ด ๋ฐ ๋ฐฉํฅ ์์ฑ ์ ํ
์ ํ์ฌํญ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ฌ๋ฌ ์ธ์ด๋ฅผ ์ง์ํ ๋,
<html>ํ๊ทธ์lang๋ฐdir์์ฑ์ ํ์ฌ ๋ก์ผ์ผ๊ณผ ์ผ์นํ๋๋ก ์ ๋ฐ์ดํธํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ๋ค์์ ๋ณด์ฅํฉ๋๋ค:- ์ ๊ทผ์ฑ: ์คํฌ๋ฆฐ ๋ฆฌ๋์ ๋ณด์กฐ ๊ธฐ์ ์ ์ฌ๋ฐ๋ฅธ
lang์์ฑ์ ์์กดํ์ฌ ์ฝํ ์ธ ๋ฅผ ์ ํํ๊ฒ ๋ฐ์ํ๊ณ ํด์ํฉ๋๋ค. - ํ
์คํธ ๋ ๋๋ง:
dir(๋ฐฉํฅ) ์์ฑ์ ํ ์คํธ๊ฐ ์ฌ๋ฐ๋ฅธ ์์๋ก ๋ ๋๋ง๋๋๋ก ๋ณด์ฅํฉ๋๋ค(์: ์์ด์ ๊ฒฝ์ฐ ์ข์์ ์ฐ๋ก, ์๋์ด ๋๋ ํ๋ธ๋ฆฌ์ด์ ๊ฒฝ์ฐ ์ฐ์์ ์ข๋ก). ์ด๋ ๊ฐ๋ ์ฑ์ ํ์์ ์ ๋๋ค. - SEO: ๊ฒ์ ์์ง์
lang์์ฑ์ ์ฌ์ฉํ์ฌ ํ์ด์ง์ ์ธ์ด๋ฅผ ๊ฒฐ์ ํ๊ณ ๊ฒ์ ๊ฒฐ๊ณผ์์ ์ฌ๋ฐ๋ฅธ ์ง์ญํ๋ ์ฝํ ์ธ ๋ฅผ ์ ๊ณตํ๋ ๋ฐ ๋์์ ์ค๋๋ค.
๋ก์ผ์ผ์ด ๋ณ๊ฒฝ๋ ๋ ์ด๋ฌํ ์์ฑ๋ค์ ๋์ ์ผ๋ก ์ ๋ฐ์ดํธํจ์ผ๋ก์จ, ์ง์ํ๋ ๋ชจ๋ ์ธ์ด์์ ์ฌ์ฉ์์๊ฒ ์ผ๊ด๋๊ณ ์ ๊ทผ ๊ฐ๋ฅํ ๊ฒฝํ์ ๋ณด์ฅํฉ๋๋ค.
Hook ๊ตฌํํ๊ธฐ
HTML ์์ฑ์ ๊ด๋ฆฌํ๊ธฐ ์ํ ์ปค์คํ hook์ ์์ฑํฉ๋๋ค. ์ด hook์ ๋ก์ผ์ผ ๋ณ๊ฒฝ์ ๊ฐ์งํ๊ณ ๊ทธ์ ๋ฐ๋ผ ์์ฑ์ ์ ๋ฐ์ดํธํฉ๋๋ค:
src/hooks/useI18nHTMLAttributes.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import { useEffect } from "react"; import { useLocale } from "react-intlayer"; import { getHTMLTextDir } from "intlayer"; /** * ํ์ฌ ๋ก์ผ์ผ์ ๊ธฐ๋ฐ์ผ๋ก HTML <html> ์์์ `lang` ๋ฐ `dir` ์์ฑ์ ์ ๋ฐ์ดํธํฉ๋๋ค. * - `lang`: ๋ธ๋ผ์ฐ์ ์ ๊ฒ์ ์์ง์ ํ์ด์ง์ ์ธ์ด๋ฅผ ์๋ฆฝ๋๋ค. * - `dir`: ์ฌ๋ฐ๋ฅธ ์ฝ๊ธฐ ์์๋ฅผ ๋ณด์ฅํฉ๋๋ค (์: ์์ด์ ๊ฒฝ์ฐ 'ltr', ์๋์ด์ ๊ฒฝ์ฐ 'rtl'). * * ์ด ๋์ ์ ๋ฐ์ดํธ๋ ์ฌ๋ฐ๋ฅธ ํ ์คํธ ๋ ๋๋ง, ์ ๊ทผ์ฑ ๋ฐ SEO์ ํ์์ ์ ๋๋ค. */ export const useI18nHTMLAttributes = () => { const { locale } = useLocale(); useEffect(() => { // ์ธ์ด ์์ฑ์ ํ์ฌ ๋ก์ผ์ผ๋ก ์ ๋ฐ์ดํธํฉ๋๋ค. document.documentElement.lang = locale; // ํ์ฌ ๋ก์ผ์ผ์ ๊ธฐ๋ฐ์ผ๋ก ํ ์คํธ ๋ฐฉํฅ์ ์ค์ ํฉ๋๋ค. document.documentElement.dir = getHTMLTextDir(locale); }, [locale]); };ํ ๊ตฌํํ๊ธฐ
HTML ์์ฑ์ ๊ด๋ฆฌํ๋ ์ปค์คํ ํ ์ ๋ง๋ญ๋๋ค. ์ด ํ ์ ๋ก์ผ์ผ ๋ณ๊ฒฝ์ ๊ฐ์งํ์ฌ ์์ฑ์ ์ ์ ํ ์ ๋ฐ์ดํธํฉ๋๋ค:
src/hooks/useI18nHTMLAttributes.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import { useEffect } from "react"; import { useLocale } from "react-intlayer"; import { getHTMLTextDir } from "intlayer"; /** * ํ์ฌ ๋ก์ผ์ผ์ ๋ฐ๋ผ HTML <html> ์์์ `lang` ๋ฐ `dir` ์์ฑ์ ์ ๋ฐ์ดํธํฉ๋๋ค. * - `lang`: ๋ธ๋ผ์ฐ์ ์ ๊ฒ์ ์์ง์ ํ์ด์ง์ ์ธ์ด๋ฅผ ์๋ฆฝ๋๋ค. * - `dir`: ์ฌ๋ฐ๋ฅธ ์ฝ๊ธฐ ์์(์: ์์ด๋ 'ltr', ์๋์ด๋ 'rtl')๋ฅผ ๋ณด์ฅํฉ๋๋ค. * * ์ด ๋์ ์ ๋ฐ์ดํธ๋ ์ฌ๋ฐ๋ฅธ ํ ์คํธ ๋ ๋๋ง, ์ ๊ทผ์ฑ ๋ฐ SEO์ ํ์์ ์ ๋๋ค. */ export const useI18nHTMLAttributes = () => { const { locale } = useLocale(); useEffect(() => { // ํ์ฌ ๋ก์ผ์ผ๋ก ์ธ์ด ์์ฑ์ ์ ๋ฐ์ดํธํฉ๋๋ค. document.documentElement.lang = locale; // ํ์ฌ ๋ก์ผ์ผ์ ๋ฐ๋ผ ํ ์คํธ ๋ฐฉํฅ์ ์ค์ ํฉ๋๋ค. document.documentElement.dir = getHTMLTextDir(locale); }, [locale]); };์ด๋ฌํ ๋ณ๊ฒฝ ์ฌํญ์ ์ ์ฉํ๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ค์์ ์ํํฉ๋๋ค:
- ์ธ์ด (
lang) ์์ฑ์ด ํ์ฌ ๋ก์บ์ ์ฌ๋ฐ๋ฅด๊ฒ ๋ฐ์ํ๋์ง ํ์ธํ์ธ์. ์ด๋ SEO์ ๋ธ๋ผ์ฐ์ ๋์์ ์ค์ํฉ๋๋ค. - ๋ก์บ์ ๋ฐ๋ผ ํ
์คํธ ๋ฐฉํฅ (
dir)์ ์กฐ์ ํ์ฌ ๋ค์ํ ์ฝ๊ธฐ ์์๋ฅผ ๊ฐ์ง ์ธ์ด์ ๊ฐ๋ ์ฑ๊ณผ ์ฌ์ฉ์ฑ์ ํฅ์์ํค์ธ์. - ์ ๊ทผ์ฑ ์๋ ๊ฒฝํ์ ์ ๊ณตํ์ธ์. ๋ณด์กฐ ๊ธฐ์ ์ ์ต์ ์ ๊ธฐ๋ฅ์ ์ํด ์ด๋ฌํ ์์ฑ์ ์์กดํฉ๋๋ค.
- ์ ๊ทผ์ฑ: ์คํฌ๋ฆฐ ๋ฆฌ๋์ ๋ณด์กฐ ๊ธฐ์ ์ ์ฌ๋ฐ๋ฅธ
์ง์ญํ๋ ๋งํฌ ์ปดํฌ๋ํธ ๋ง๋ค๊ธฐ
์ ํ์ฌํญ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ด๋น๊ฒ์ด์ ์ด ํ์ฌ ๋ก์ผ์ผ์ ์ค์ํ๋๋ก ํ๋ ค๋ฉด, ์ปค์คํ
Link์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค. ์ด ์ปดํฌ๋ํธ๋ ๋ด๋ถ URL์ ์๋์ผ๋ก ํ์ฌ ์ธ์ด ์ ๋์ฌ๋ฅผ ๋ถ์ฌ์ค๋๋ค. ์๋ฅผ ๋ค์ด, ํ๋์ค์ด ์ฌ์ฉ์๊ฐ "About" ํ์ด์ง๋ก ๊ฐ๋ ๋งํฌ๋ฅผ ํด๋ฆญํ๋ฉด/about๋์/fr/about๋ก ๋ฆฌ๋๋ ์ ๋ฉ๋๋ค.์ด ๋์์ ์ฌ๋ฌ ๊ฐ์ง ์ด์ ๋ก ์ ์ฉํฉ๋๋ค:
- SEO ๋ฐ ์ฌ์ฉ์ ๊ฒฝํ: ์ง์ญํ๋ URL์ ๊ฒ์ ์์ง์ด ์ธ์ด๋ณ ํ์ด์ง๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ์์ธํํ๋๋ก ๋๊ณ , ์ฌ์ฉ์๊ฐ ์ ํธํ๋ ์ธ์ด๋ก ๋ ์ฝํ ์ธ ๋ฅผ ์ ๊ณตํฉ๋๋ค.
- ์ผ๊ด์ฑ: ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐ์ ๊ฑธ์ณ ์ง์ญํ๋ ๋งํฌ๋ฅผ ์ฌ์ฉํจ์ผ๋ก์จ ๋ด๋น๊ฒ์ด์ ์ด ํ์ฌ ๋ก์ผ์ผ ๋ด์์ ์ ์ง๋์ด ์๊ธฐ์น ์์ ์ธ์ด ์ ํ์ ๋ฐฉ์งํฉ๋๋ค.
- ์ ์ง๋ณด์์ฑ: ๋ก์ปฌ๋ผ์ด์ ์ด์ ๋ก์ง์ ๋จ์ผ ์ปดํฌ๋ํธ์ ์ค์ ์ง์คํํ๋ฉด URL ๊ด๋ฆฌ๋ฅผ ๋จ์ํํ์ฌ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ฑ์ฅํจ์ ๋ฐ๋ผ ์ฝ๋๋ฒ ์ด์ค๋ฅผ ๋ ์ฝ๊ฒ ์ ์ง๋ณด์ํ๊ณ ํ์ฅํ ์ ์์ต๋๋ค.
์๋๋ TypeScript๋ก ๊ตฌํํ ๋ก์ปฌ๋ผ์ด์ฆ๋
Link์ปดํฌ๋ํธ์ ๋๋ค:src/components/Link.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import { getLocalizedUrl } from "intlayer"; import { forwardRef, type DetailedHTMLProps, type AnchorHTMLAttributes, } from "react"; import { useLocale } from "react-intlayer"; export interface LinkProps extends DetailedHTMLProps< AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement > {} /** * ์ฃผ์ด์ง URL์ด ์ธ๋ถ ๋งํฌ์ธ์ง ํ์ธํ๋ ์ ํธ๋ฆฌํฐ ํจ์์ ๋๋ค. * URL์ด http:// ๋๋ https://๋ก ์์ํ๋ฉด ์ธ๋ถ ๋งํฌ๋ก ๊ฐ์ฃผํฉ๋๋ค. */ export const checkIsExternalLink = (href?: string): boolean => /^https?:\/\//.test(href ?? ""); /** * ํ์ฌ ๋ก์ผ์ผ์ ๋ฐ๋ผ href ์์ฑ์ ์กฐ์ ํ๋ ์ปค์คํ Link ์ปดํฌ๋ํธ์ ๋๋ค. * ๋ด๋ถ ๋งํฌ์ ๊ฒฝ์ฐ `getLocalizedUrl`์ ์ฌ์ฉํ์ฌ URL ์์ ๋ก์ผ์ผ์ ๋ถ์ ๋๋ค (์: /fr/about). * ์ด๋ฅผ ํตํด ๋ด๋น๊ฒ์ด์ ์ด ๋์ผํ ๋ก์ผ์ผ ์ปจํ ์คํธ ๋ด์์ ์ด๋ฃจ์ด์ง๋๋ก ๋ณด์ฅํฉ๋๋ค. */ export const Link = forwardRef<HTMLAnchorElement, LinkProps>( ({ href, children, ...props }, ref) => { const { locale } = useLocale(); const isExternalLink = checkIsExternalLink(href); // ๋งํฌ๊ฐ ๋ด๋ถ ๋งํฌ์ด๊ณ ์ ํจํ href๊ฐ ์ ๊ณต๋ ๊ฒฝ์ฐ, ๋ก์ผ์ผ์ด ์ ์ฉ๋ URL์ ๊ฐ์ ธ์ต๋๋ค. const hrefI18n = href && !isExternalLink ? getLocalizedUrl(href, locale) : href; return ( <a href={hrefI18n} ref={ref} {...props}> {children} </a> ); } ); Link.displayName = "Link";์๋ ๋ฐฉ์
์ธ๋ถ ๋งํฌ ๊ฐ์ง:
ํฌํผ ํจ์checkIsExternalLink๋ ์ง์ ๋ URL์ด ์ธ๋ถ ๋งํฌ์ธ์ง ์ฌ๋ถ๋ฅผ ํ๋จํฉ๋๋ค. ์ธ๋ถ ๋งํฌ๋ ํ์งํํ ํ์๊ฐ ์์ผ๋ฏ๋ก ๋ณ๊ฒฝ๋์ง ์์ ์ํ๋ก ์ ์ง๋ฉ๋๋ค.ํ์ฌ ๋ก์ผ์ผ ๊ฐ์ ธ์ค๊ธฐ:
useLocaleํ ์ ํ์ฌ ๋ก์ผ์ผ(์: ํ๋์ค์ด์ ๊ฒฝ์ฐfr)์ ์ ๊ณตํฉ๋๋ค.URL ํ์งํ:
๋ด๋ถ ๋งํฌ(์ฆ, ์ธ๋ถ ๋งํฌ๊ฐ ์๋ ๋งํฌ)์ ๊ฒฝ์ฐ,getLocalizedUrl์ ์ฌ์ฉํ์ฌ ํ์ฌ ๋ก์ผ์ผ๋ก URL ์ ๋์ฌ๋ฅผ ์๋์ผ๋ก ๋ถ์ ๋๋ค. ์ฆ, ์ฌ์ฉ์๊ฐ ํ๋์ค์ด ํ๊ฒฝ์ธ ๊ฒฝ์ฐ/about์href๋ก ์ ๋ฌํ๋ฉด/fr/about์ผ๋ก ๋ณํ๋ฉ๋๋ค.๋งํฌ ๋ฐํ:
์ด ์ปดํฌ๋ํธ๋ ๋ก์ปฌ๋ผ์ด์ฆ๋ URL์ด ์ ์ฉ๋<a>์์๋ฅผ ๋ฐํํ์ฌ ๋ด๋น๊ฒ์ด์ ์ด ๋ก์ผ์ผ๊ณผ ์ผ์นํ๋๋ก ๋ณด์ฅํฉ๋๋ค.
์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐ์ ์ด
Link์ปดํฌ๋ํธ๋ฅผ ํตํฉํจ์ผ๋ก์จ, ์ผ๊ด๋๊ณ ์ธ์ด๋ฅผ ์ธ์ํ๋ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ์งํ๋ ๋์์ ๊ฐ์ ๋ SEO ๋ฐ ์ฌ์ฉ์ฑ ์ด์ ์ ๋๋ฆด ์ ์์ต๋๋ค.์ปดํฌ๋ํธ ์ฝํ ์ธ ์ถ์ถ
์ ํ์ฌํญ๊ธฐ์กด ์ฝ๋๋ฒ ์ด์ค๊ฐ ์๋ ๊ฒฝ์ฐ ์์ฒ ๊ฐ์ ํ์ผ์ ๋ณํํ๋ ๋ฐ ์๊ฐ์ด ๋ง์ด ๊ฑธ๋ฆด ์ ์์ต๋๋ค.
์ด ํ๋ก์ธ์ค๋ฅผ ์ฉ์ดํ๊ฒ ํ๊ธฐ ์ํด 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์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
Since v9, the
intlayerCompileris included in theintlayerplugin. So you don't need to add it manually.vite.config.ts๋ฅผ ์ ๋ฐ์ดํธํ์ฌintlayerCompilerํ๋ฌ๊ทธ์ธ์ ํฌํจํฉ๋๋ค.vite.config.ts์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
์ ํ๋ฆฌ์ผ์ด์ ์ ๋น๋ํ์ฌ ์ปดํฌ๋ํธ๋ฅผ ๋ณํํ๊ณ ์ฝํ ์ธ ๋ฅผ ์ถ์ถํฉ๋๋ค.
bash์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
(์ ํ) ์ฌ์ดํธ๋งต๊ณผ robots.txt(๋น๋ ์ ์์ฑ)
Intlayer๋ generateSitemap๊ณผ getMultilingualUrls๋ก ํฌ๋กค๋ฌ์ฉ ๋ค๊ตญ์ด sitemap.xml๊ณผ robots.txt๋ฅผ ๋ง๋ค์ด public/์ ์๋์ผ๋ก ์ธ ์ ์์ต๋๋ค. ๋ณดํต Vite ์คํ ์ ์ ์์ Node ์คํฌ๋ฆฝํธ๋ฅผ ๋๋ฆฝ๋๋ค(์: npm predev / prebuild).
์ฌ์ดํธ๋งต
Intlayer ์ฌ์ดํธ๋งต ์์ฑ๊ธฐ๋ ๋ก์ผ์ผ ์ค์ ์ ๋ฐ์ํ๊ณ ํฌ๋กค๋ฌ์ฉ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ํฌํจํฉ๋๋ค.
์์ฑ๋ ์ฌ์ดํธ๋งต์xhtml:link(hreflang)๋ฅผ ์ง์ํฉ๋๋ค. ๋จ์ URL ๋์ด์ด ์๋๋ผ ๊ฐ ํ์ด์ง์ ๋ชจ๋ ์ธ์ด ๋ฒ์ ์ ์๋ฐฉํฅ์ผ๋ก ์ฐ๊ฒฐํฉ๋๋ค(์:/about,/fr/about,/about?lang=fr- ๋ผ์ฐํ ๋ชจ๋์ ๋ฐ๋ฆ).
Robots.txt
getMultilingualUrls๋ก Disallow๊ฐ ๋ฏผ๊ฐ ๊ฒฝ๋ก์ ๋ชจ๋ ํ์งํ ๋ณํ์ ์ ์ฉ๋๋๋ก ํ์ธ์.
1. ํ๋ก์ ํธ ๋ฃจํธ์ generate-seo.mjs ์ถ๊ฐ
์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
์คํฌ๋ฆฝํธ๊ฐ intlayer๋ฅผ importํ๋ ค๋ฉด ํจํค์ง๊ฐ ์ค์น๋์ด ์์ด์ผ ํฉ๋๋ค. ํ๋ก๋์
์์๋ ํ๊ฒฝ ๋ณ์ SITE_URL์ ์ค์ ํ์ธ์(CI ๋ฑ).
Node ESM์๋generate-seo.mjs์ฌ์ฉ์ ๊ถ์ฅํฉ๋๋ค.generate-seo.js๋ฅผ ์ฐ๋ฉดpackage.json์"type": "module"๋ฑ์ผ๋ก ESM์ ์ผ์ธ์.
2. Vite ์ ์ ์คํฌ๋ฆฝํธ ์คํ
์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
pnpm์ด๋ yarn์ ์ฐ๋ฉด ๋ช ๋ น์ ๋ง๊ฒ ๋ฐ๊พธ์ธ์. CI์์ ํธ์ถํด๋ ๋ฉ๋๋ค.
TypeScript ์ค์
Intlayer๋ TypeScript์ ์ด์ ์ ํ์ฉํ๊ณ codebase๋ฅผ ๋์ฑ ๊ฐ๋ ฅํ๊ฒ ๋ง๋ค๊ธฐ ์ํด module augmentation์ ์ฌ์ฉํฉ๋๋ค.


TypeScript ์ค์ ์ ์๋ ์์ฑ๋ ํ์ ์ด ํฌํจ๋์ด ์๋์ง ํ์ธํ์ธ์.
์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
Git ์ค์
Intlayer์ ์ํด ์์ฑ๋ ํ์ผ๋ค์ ๋ฌด์ํ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค. ์ด๋ฅผ ํตํด ํด๋น ํ์ผ๋ค์ด Git ๋ฆฌํฌ์งํ ๋ฆฌ์ ์ปค๋ฐ๋๋ ๊ฒ์ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
์ด๋ฅผ ์ํด, .gitignore ํ์ผ์ ๋ค์ ๋ด์ฉ์ ์ถ๊ฐํ ์ ์์ต๋๋ค:
์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
VS Code ํ์ฅ ํ๋ก๊ทธ๋จ
Intlayer์ ํจ๊ปํ๋ ๊ฐ๋ฐ ํ๊ฒฝ์ ๊ฐ์ ํ๊ธฐ ์ํด ๊ณต์ Intlayer VS Code ํ์ฅ ํ๋ก๊ทธ๋จ์ ์ค์นํ ์ ์์ต๋๋ค.
VS Code ๋ง์ผํ๋ ์ด์ค์์ ์ค์นํ๊ธฐ
์ด ํ์ฅ ํ๋ก๊ทธ๋จ์ ๋ค์์ ์ ๊ณตํฉ๋๋ค:
- ๋ฒ์ญ ํค์ ๋ํ ์๋ ์์ฑ(Autocompletion).
- ๋๋ฝ๋ ๋ฒ์ญ์ ๋ํ ์ค์๊ฐ ์ค๋ฅ ๊ฐ์ง.
- ๋ฒ์ญ๋ ์ฝํ ์ธ ์ ์ธ๋ผ์ธ ๋ฏธ๋ฆฌ๋ณด๊ธฐ.
- ๋ฒ์ญ์ ์ฝ๊ฒ ์์ฑํ๊ณ ์ ๋ฐ์ดํธํ ์ ์๋ ๋น ๋ฅธ ์์ (Quick actions).
ํ์ฅ ํ๋ก๊ทธ๋จ ์ฌ์ฉ๋ฒ์ ๋ํ ์์ธํ ๋ด์ฉ์ Intlayer VS Code ํ์ฅ ํ๋ก๊ทธ๋จ ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ธ์.
๋ ๋์๊ฐ๊ธฐ
๋ ๋์๊ฐ๋ ค๋ฉด ๋น์ฃผ์ผ ์๋ํฐ๋ฅผ ๊ตฌํํ๊ฑฐ๋ CMS๋ฅผ ์ฌ์ฉํ์ฌ ์ฝํ ์ธ ๋ฅผ ์ธ๋ถํํ ์ ์์ต๋๋ค.
์์ฃผ ๋ฌป๋ ์ง๋ฌธ
Vite ์์ฒด๋ i18n์ ๋ํด ํน์ ๋ฐฉ์์ ๊ฐ์ ํ์ง ์์ผ๋ฏ๋ก React ์ํ๊ณ์ ์ผ๋ฐ์ ์ธ ์ต์ ๋ค์ ์ฌ์ฉํ ์ ์์ต๋๋ค:
react-i18next/i18next: ๊ฐ์ฅ ๋๋ฆฌ ์ฌ์ฉ๋๋ฉฐ, ๋ฐํ์์ JSON ๋ค์์คํ์ด์ค๋ฅผ ๋ก๋ํฉ๋๋ค.react-intl๋ฐLingui: ICU ๋ฉ์์ง ํฌ๋งท ๊ธฐ๋ฐ์ด๋ฉฐ, ์ถ์ถ(extraction) ๋ฐฉ์์ ์ฌ์ฉํฉ๋๋ค.Intlayer: ๊ฐ์ฅ ์ง๋ณด๋ ์๋ฃจ์ ์ ๋๋ค. ์ฝ๋๋ฒ ์ด์ค ์ด๋์๋ ์ฝํ ์ธ ๋ฅผ ์ ์ธํ ์ ์์ผ๋ฉฐ(๊ฐ ์ปดํฌ๋ํธ ์ ๋๋ ์ค์ ์ง์ค์), ๋น๋ ํ์์ Vite ํ๋ฌ๊ทธ์ธ์ ํตํด ์ปดํ์ผ๋๊ณ , ์์ ํ ํ์ ์์ ์ฑ์ ์ ๊ณตํ๋ฉฐ AI ๋ฒ์ญ, ๋น์ฃผ์ผ ์๋ํฐ ๋ฐ CMS๋ฅผ ์ง์ํฉ๋๋ค.
ํนํ Vite์์ Intlayer๋ ๋น๋ ํ๋ก์ธ์ค์ ์ง์ ์ฐ๊ฒฐ๋๋ฏ๋ก ๋ฒ์ญ์ด ๋ฐํ์์ JSON์ผ๋ก ๊ฐ์ ธ์ค๋ ๋์ ์ปดํ์ผ ํ์์ ํด์๋๊ณ ํธ๋ฆฌ ์์ดํน(tree-shaking)๋ฉ๋๋ค. ์ Intlayer์ธ๊ฐ์ ๋ฒค์น๋งํฌ๋ฅผ ์ฐธ์กฐํ์ธ์.
๋ค์์คํ์ด์ค ๊ธฐ๋ฐ ์ค์ ๋ณด๋ค ํจ์ฌ ์ ์ต๋๋ค. ํ์ด์ง๋ ๋ ๋๋งํ์ง ์๋ ์ธ์ด์ ์นดํ๋ก๊ทธ๋ฅผ ์ ๋ ๋ค์ด๋ก๋ํ์ง ์๊ธฐ ๋๋ฌธ์
๋๋ค. ๋น๋ ํ์ ์ปดํ์ผ๋ฌ๋ useIntlayer ํธ์ถ์ ์ปดํฌ๋ํธ๊ฐ ์ฌ์ฉํ๋ ์ ํํ ์ฌ์ ํญ๋ชฉ์ผ๋ก ๋์ฒดํ๋ฏ๋ก ์ฌ์ฉ๋์ง ์๋ ํค์ ์ธ์ด๋ ์ ๊ฑฐ๋๋ฉฐ, ๋์ ์ฌ์ ์ ํตํด ๋ก์ผ์ผ๋ณ๋ก ๋ถํ ๋ฉ๋๋ค. ์ผ๋ฐ์ ์ธ ๋์๋ค๊ณผ ๋น๊ตํ์ ๋ Intlayer๋ ๋ฒ๋ค ๋ฐ ํ์ด์ง ํฌ๊ธฐ๋ฅผ ์ต๋ 50%๊น์ง ์ค์ฌ์ค๋๋ค. ๋ฒ๋ค ์ต์ ํ์ ๋ฒค์น๋งํฌ๋ฅผ ์ฐธ์กฐํ์ธ์.
๋ค, ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ด ์์ต๋๋ค. react-i18next ๋ง์ด๊ทธ๋ ์ด์
๊ฐ์ด๋ ๋๋ i18next ๋ง์ด๊ทธ๋ ์ด์
๊ฐ์ด๋๋ฅผ ํตํด ์ฝํ
์ธ ๋ฅผ ์ ์ง์ ์ผ๋ก ๋ง์ด๊ทธ๋ ์ด์
ํ ์ ์์ต๋๋ค. ๋๋ ํ์ฌ API๋ฅผ ์์ ํ ์ ์งํ ์๋ ์์ต๋๋ค. ํธํ ์ด๋ํฐ(compat adapters)๋ react-i18next, react-intl, i18next์ ์์ ํ ๋์ผํ API๋ฅผ ๋
ธ์ถํ๋ฉด์ Intlayer ์ฌ์ ์์ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ๋ฏ๋ก, import ๊ตฌ๋ฌธ๋ง ๋ณ๊ฒฝํ๊ณ ์ปดํฌ๋ํธ ์ฝ๋๋ ๊ทธ๋๋ก ์ ์งํ ์ ์์ต๋๋ค.
๋ค. sync JSON ํ๋ฌ๊ทธ์ธ์ /messages/{locale}/{namespace}.json ํ์ผ์ ๋จ์ผ ์ง์ค ๊ณต๊ธ์(source of truth)์ผ๋ก ์ ์งํ๋ฉด์ ์๋ฐฉํฅ์ผ๋ก Intlayer ์ฌ์ ์ ์์ฑํฉ๋๋ค. sync PO ํ๋ฌ๊ทธ์ธ์ gettext ์นดํ๋ก๊ทธ์ ๋ํด ๋์ผํ ์์
์ ์ํํ๋ฉฐ, ๋ก์ผ์ผ๋ณ ํ์ผ์ ํตํด ๋ก์ผ์ผ์ ํ ํ์ผ์ ๋ชจ์ผ๋ ๋์ ์ธ์ด๋ณ๋ก ์ฝํ
์ธ ๋ฅผ ๋ถํ ํ ์๋ ์์ต๋๋ค.
์๋๋๋ค. npx intlayer extract๋ฅผ ์คํํ๋ฉด Intlayer๊ฐ ์ปดํฌ๋ํธ๋ฅผ ์ฝ๊ณ ์ฌ์ฉ์ ๋๋ฉด ๋ฌธ์์ด์ ์ถ์ถํ์ฌ ๊ฐ ์ปดํฌ๋ํธ ์์ .content ํ์ผ์ ์์ฑํ๋ฏ๋ก ์นดํ๋ก๊ทธ์ ์ผ์ผ์ด ๋ณต์ฌํ ํ์ ์์ด diff๋ง ๊ฒํ ํ๋ฉด ๋ฉ๋๋ค. ์ด ๊ฐ์ด๋์ 11๋จ๊ณ๋ฅผ ํ์ธํ์ธ์.
์์ ์๋ํ๋ ํ์ดํ๋ผ์ธ์ ์ํด 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๊ท์น์ผ๋ก ํ๋์ฝ๋ฉ๋ ๋ฌธ์์ด์ ํ์ํ๊ณ , ์ ์ ์ฌ์ ํค ๋ฐ ์ฌ์ฉ๋์ง ์๋ ์ฝํ ์ธ ์ ๋ํ ์ถ๊ฐ ๊ท์น์ ์ ๊ณตํฉ๋๋ค.
