์ด ํ์ด์ง์๋ ์ ํ๋ฆฌ์ผ์ด์ ํ ํ๋ฆฟ์ด ์ ๊ณต๋ฉ๋๋ค.
์ด ํ์ด์ง์ ์ํ๋ 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๋ก Create React App ๋ฒ์ญํ๊ธฐ | ๊ตญ์ ํ(i18n)
Application Template ์ฐธ์กฐ.
Intlayer๋ ๋ฌด์์ธ๊ฐ์?
react-i18next ๋๋ i18next์ ๊ฐ์ ์ฃผ์ ์๋ฃจ์
๊ณผ ๋น๊ตํ์ฌ Intlayer๋ ๋ค์๊ณผ ๊ฐ์ ํตํฉ ์ต์ ํ๋ฅผ ์ ๊ณตํ๋ ์๋ฃจ์
์
๋๋ค:
Intlayer๋ ํ๋ ์น ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ค๊ตญ์ด ์ง์์ ๊ฐ์ํํ๊ธฐ ์ํด ์ค๊ณ๋ ํ์ ์ ์ด๊ณ ์คํ ์์ค์ธ ๊ตญ์ ํ(i18n) ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.
๋๊ท๋ชจ JSON ํ์ผ์ ํ์ด์ง์ ๋ก๋ํ๋ ๋์ ํ์ํ ์ฝํ ์ธ ๋ง ๋ก๋ํ์ธ์. Intlayer๋ ๋ฒ๋ค ๋ฐ ํ์ด์ง ํฌ๊ธฐ๋ฅผ ์ต๋ 50%๊น์ง ์ค์ด๋ ๋ฐ ๋์์ ์ค๋๋ค.
Intlayer๋ฅผ ์ฌ์ฉํ๋ฉด:
- ์ปดํฌ๋ํธ ์์ค์์ ์ ์ธ์ ์ฌ์ ์ ์ฌ์ฉํ์ฌ ๋ฒ์ญ์ ์ฝ๊ฒ ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
- ๋ฉํ๋ฐ์ดํฐ, ๋ผ์ฐํธ ๋ฐ ์ฝํ ์ธ ๋ฅผ ๋์ ์ผ๋ก ๋ก์ปฌ๋ผ์ด์ฆํ ์ ์์ต๋๋ค.
- ์๋ ์์ฑ๋ ํ์ ์ผ๋ก TypeScript ์ง์์ ๋ณด์ฅํ์ฌ ์๋ ์์ฑ ๋ฐ ์ค๋ฅ ๊ฐ์ง๋ฅผ ๊ฐ์ ํ ์ ์์ต๋๋ค.
- ๋์ ๋ก์ผ์ผ ๊ฐ์ง ๋ฐ ์ ํ๊ณผ ๊ฐ์ ๊ณ ๊ธ ๊ธฐ๋ฅ์ ํ์ฉํ ์ ์์ต๋๋ค.
์ฝํ ์ธ ๋ฅผ ํจ๊ป ๋ฐฐ์นํ๋ฉด Large Language Models (LLMs)์ ํ์ํ ์ปจํ ์คํธ๋ฅผ ์ค์ผ ์ ์์ต๋๋ค. Intlayer๋ ๋ํ ๋๋ฝ๋ ๋ฒ์ญ์ ํ ์คํธํ๊ธฐ ์ํ CLI, LSP, MCP, ๋ฐ agent skills ๋ฑ์ ๋๊ตฌ ๋ชจ์์ ์ ๊ณตํ์ฌ AI agents๋ฅผ ์ํ ๊ฐ๋ฐ์ ๊ฒฝํ(DX)์ ๋์ฑ ๋งค๋๋ฝ๊ฒ ํฉ๋๋ค.
CI/CD ํ์ดํ๋ผ์ธ์์ ์ ํํ LLM์ ์ฌ์ฉํ์ฌ ์๋ํ๋ก ๋ฒ์ญํ๋ฉด AI ๊ณต๊ธ์์ ๋น์ฉ์ผ๋ก ์ฒ๋ฆฌํ ์ ์์ต๋๋ค. Intlayer๋ ๋ํ ์ฝํ ์ธ ์ถ์ถ์ ์๋ํํ๋ ์ปดํ์ผ๋ฌ์ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๋ฒ์ญ์ ๋๋ ์น ํ๋ซํผ์ ์ ๊ณตํฉ๋๋ค.
๋๊ท๋ชจ JSON ํ์ผ์ ์ปดํฌ๋ํธ์ ์ฐ๊ฒฐํ๋ฉด ์ฑ๋ฅ ๋ฐ ๋ฐ์์ฑ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. Intlayer๋ ๋น๋ ์๊ฐ์ ์ฝํ ์ธ ๋ก๋ฉ์ ์ต์ ํํฉ๋๋ค.
๋จ์ํ i18n ์๋ฃจ์ ์ด์์ผ๋ก, Intlayer๋ ์์ฒด ํธ์คํ ๋น์ฃผ์ผ ์๋ํฐ์ ์ ์ฒด CMS๋ฅผ ์ ๊ณตํ์ฌ ๋ค๊ตญ์ด ์ฝํ ์ธ ๋ฅผ ์ค์๊ฐ์ผ๋ก ๊ด๋ฆฌํ๊ณ , ๋ฒ์ญ๊ฐ, ์นดํผ๋ผ์ดํฐ ๋ฐ ๊ธฐํ ํ ๋ฉค๋ฒ์์ ํ์ ์ ์ํํ๊ฒ ๋ง๋ญ๋๋ค. ์ฝํ ์ธ ๋ ๋ก์ปฌ ๋ฐ/๋๋ ์๊ฒฉ์ผ๋ก ์ ์ฅ๋ ์ ์์ต๋๋ค.
React ์ ํ๋ฆฌ์ผ์ด์ ์์ Intlayer ์ค์ ๋จ๊ณ๋ณ ๊ฐ์ด๋
์ข ์์ฑ ์ค์น
npm์ ์ฌ์ฉํ์ฌ ํ์ํ ํจํค์ง๋ฅผ ์ค์นํฉ๋๋ค:
bash์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
--interactiveํ๋๊ทธ๋ ์ ํ ์ฌํญ์ ๋๋ค. AI ์์ด์ ํธ์ธ ๊ฒฝ์ฐintlayer-cli init๋ฅผ ์ฌ์ฉํ์ธ์.์ด ๋ช ๋ น์ ํ๊ฒฝ์ ๊ฐ์งํ๊ณ ํ์ํ ํจํค์ง๋ฅผ ์ค์นํฉ๋๋ค. ์๋ฅผ ๋ค์ด:
bash์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
intlayer
๊ตฌ์ฑ ๊ด๋ฆฌ, ๋ฒ์ญ, ์ฝํ ์ธ ์ ์ธ, ํธ๋์คํ์ผ๋ง ๋ฐ CLI ๋ช ๋ น์ ์ํ ๊ตญ์ ํ ๋๊ตฌ๋ฅผ ์ ๊ณตํ๋ ํต์ฌ ํจํค์ง์ ๋๋ค.
react-intlayer
Intlayer๋ฅผ React ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ ํตํฉํ๋ ํจํค์ง์ ๋๋ค. React ๊ตญ์ ํ๋ฅผ ์ํ ์ปจํ ์คํธ ์ ๊ณต์์ ํ ์ ์ ๊ณตํฉ๋๋ค.
react-scripts-intlayer
Create React App ๊ธฐ๋ฐ ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ Intlayer๋ฅผ ํตํฉํ๊ธฐ ์ํ
react-scripts-intlayer๋ช ๋ น ๋ฐ ํ๋ฌ๊ทธ์ธ์ ํฌํจํฉ๋๋ค. ์ด ํ๋ฌ๊ทธ์ธ๋ค์ craco๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ๋ฉฐ, Webpack ๋ฒ๋ค๋ฌ์ ๋ํ ์ถ๊ฐ ๊ตฌ์ฑ์ ํฌํจํฉ๋๋ค.ํ๋ก์ ํธ ๊ตฌ์ฑ
์ ํ๋ฆฌ์ผ์ด์ ์ ์ธ์ด๋ฅผ ๊ตฌ์ฑํ๊ธฐ ์ํ ์ค์ ํ์ผ์ ์์ฑํฉ๋๋ค:
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 ๋ก๊ทธ ๋นํ์ฑํ ๋ฑ์ ์ค์ ํ ์ ์์ต๋๋ค. ์ฌ์ฉ ๊ฐ๋ฅํ ๋งค๊ฐ๋ณ์์ ์ ์ฒด ๋ชฉ๋ก์ ๊ตฌ์ฑ ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ธ์.
CRA ๊ตฌ์ฑ์ Intlayer ํตํฉ
์คํฌ๋ฆฝํธ๋ฅผ react-intlayer๋ก ๋ณ๊ฒฝํฉ๋๋ค:
package.json์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
react-scripts-intlayer์คํฌ๋ฆฝํธ๋ CRACO๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํฉ๋๋ค. Intlayer craco ํ๋ฌ๊ทธ์ธ์ ๊ธฐ๋ฐ์ผ๋ก ์์ฒด ์ค์ ์ ๊ตฌํํ ์๋ ์์ต๋๋ค. ์์ ๋ณด๊ธฐ.์ฝํ ์ธ ์ ์ธ
๋ฒ์ญ์ ์ ์ฅํ๊ธฐ ์ํด ์ฝํ ์ธ ์ ์ธ์ ์์ฑํ๊ณ ๊ด๋ฆฌํฉ๋๋ค:
src/app.content.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import { t, type Dictionary } from "intlayer"; import React, { type ReactNode } from "react"; const appContent = { key: "app", content: { getStarted: t<ReactNode>({ en: ( <> Edit <code>src/App.tsx</code> and save to reload </> ), fr: ( <> รditez <code>src/App.tsx</code> et enregistrez pour recharger </> ), es: ( <> Edita <code>src/App.tsx</code> y guarda para recargar </> ), }), reactLink: { href: "https://reactjs.org", content: t({ ko: "React ๋ฐฐ์ฐ๊ธฐ", en: "Learn React", fr: "Apprendre React", es: "Aprender React", }), }, }, } satisfies Dictionary; export default appContent;์ฝํ ์ธ ์ ์ธ์ ์ ํ๋ฆฌ์ผ์ด์ ์ด๋์์๋ ์ ์ํ ์ ์์ผ๋ฉฐ,
contentDir๋๋ ํ ๋ฆฌ(๊ธฐ๋ณธ๊ฐ:./src)์ ํฌํจ๋๊ณ ์ฝํ ์ธ ์ ์ธ ํ์ผ ํ์ฅ์(๊ธฐ๋ณธ๊ฐ:.content.{json,ts,tsx,js,jsx,mjs,cjs,md,mdx,yaml,yml})์ ์ผ์นํด์ผ ํฉ๋๋ค.์์ธํ ๋ด์ฉ์ ์ฝํ ์ธ ์ ์ธ ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ธ์.
์ฝํ ์ธ ํ์ผ์ TSX ์ฝ๋๊ฐ ํฌํจ๋ ๊ฒฝ์ฐ, ์ฝํ ์ธ ํ์ผ์
import React from "react";๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฒ์ ๊ณ ๋ คํด์ผ ํฉ๋๋ค.์ฝ๋์์ Intlayer ์ฌ์ฉ
์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐ์์ ์ฝํ ์ธ ์ฌ์ ์ ์ก์ธ์คํฉ๋๋ค:
src/App.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import logo from "./logo.svg"; import "./App.css"; import type { FC } from "react"; import { IntlayerProvider, useIntlayer } from "react-intlayer"; const AppContent: FC = () => { const content = useIntlayer("app"); return ( <div className="App"> <img src={logo} className="App-logo" alt="logo" /> {content.getStarted} <a className="App-link" href={content.reactLink.href.value} target="_blank" rel="noopener noreferrer" > {content.reactLink.content} </a> </div> ); }; const App: FC = () => ( <IntlayerProvider> <AppContent /> </IntlayerProvider> ); export default App;์ฐธ๊ณ :
alt,title,href,aria-label๋ฑ๊ณผ ๊ฐ์string์์ฑ์์ ์ฝํ ์ธ ๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด, ํจ์์ ๊ฐ์ ํธ์ถํด์ผ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด:html์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
useIntlayerํ ์ ๋ํด ๋ ์์๋ณด๋ ค๋ฉด ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ธ์.
์ฝํ ์ธ ์ธ์ด ๋ณ๊ฒฝ
์ ํ์ฌํญ์ฝํ
์ธ ์ ์ธ์ด๋ฅผ ๋ณ๊ฒฝํ๋ ค๋ฉด useLocale ํ
์์ ์ ๊ณตํ๋ setLocale ํจ์๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด ํจ์๋ ์ ํ๋ฆฌ์ผ์ด์
์ ๋ก์ผ์ผ์ ์ค์ ํ๊ณ ์ฝํ
์ธ ๋ฅผ ์
๋ฐ์ดํธํฉ๋๋ค.
์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import { Locales } from "intlayer";
import { useLocale } from "react-intlayer";
const LocaleSwitcher = () => {
const { setLocale } = useLocale();
return (
<button onClick={() => setLocale(Locales.English)}>
์ธ์ด๋ฅผ ์์ด๋ก ๋ณ๊ฒฝ
</button>
);
};
useLocale ํ
์ ๋ํด ๋ ์์๋ณด๋ ค๋ฉด ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ธ์.
์ ํ๋ฆฌ์ผ์ด์ ์ ๋ก์ปฌํ๋ ๋ผ์ฐํ ์ถ๊ฐ
์ ํ์ฌํญ์ด ๋จ๊ณ์ ๋ชฉ์ ์ ๊ฐ ์ธ์ด์ ๋ํด ๊ณ ์ ํ ๊ฒฝ๋ก๋ฅผ ๋ง๋๋ ๊ฒ์ ๋๋ค. ์ด๋ SEO ๋ฐ SEO ์นํ์ ์ธ URL์ ์ ์ฉํฉ๋๋ค. ์์:
์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
๊ธฐ๋ณธ์ ์ผ๋ก, ๊ธฐ๋ณธ ๋ก์ผ์ผ์ ๋ํด์๋ ๊ฒฝ๋ก๊ฐ ์ ๋์ด๋ก ์ค์ ๋์ง ์์ต๋๋ค. ๊ธฐ๋ณธ ๋ก์ผ์ผ์ ์ ๋์ด๋ฅผ ์ค์ ํ๋ ค๋ฉด, ๊ตฌ์ฑ์์middleware.prefixDefault์ต์ ์true๋ก ์ค์ ํ ์ ์์ต๋๋ค. ์์ธํ ๋ด์ฉ์ ๊ตฌ์ฑ ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ธ์.
์ ํ๋ฆฌ์ผ์ด์
์ ๋ก์ปฌํ๋ ๋ผ์ฐํ
์ ์ถ๊ฐํ๋ ค๋ฉด, ์ ํ๋ฆฌ์ผ์ด์
์ ๊ฒฝ๋ก๋ฅผ ๊ฐ์ธ๊ณ ๋ก์ผ์ผ ๊ธฐ๋ฐ ๋ผ์ฐํ
์ ์ฒ๋ฆฌํ๋ LocaleRouter ์ปดํฌ๋ํธ๋ฅผ ์์ฑํ ์ ์์ต๋๋ค. ๋ค์์ React Router๋ฅผ ์ฌ์ฉํ๋ ์์ ์
๋๋ค:
์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
// ํ์ํ ์ข
์์ฑ๊ณผ ํจ์ ๊ฐ์ ธ์ค๊ธฐ
import { type Locales, configuration, getPathWithoutLocale } from "intlayer"; // 'intlayer'์์ ์ ํธ๋ฆฌํฐ ํจ์ ๋ฐ ํ์
๊ฐ์ ธ์ค๊ธฐ
import type { FC, PropsWithChildren } from "react"; // React์ ํจ์ํ ์ปดํฌ๋ํธ ๋ฐ props ํ์
import { IntlayerProvider } from "react-intlayer"; // ๊ตญ์ ํ ์ปจํ
์คํธ๋ฅผ ์ํ ์ ๊ณต์
import {
BrowserRouter,
Routes,
Route,
Navigate,
useLocation,
} from "react-router-dom"; // ํ์ ๊ด๋ฆฌ๋ฅผ ์ํ ๋ผ์ฐํฐ ์ปดํฌ๋ํธ
// Intlayer์์ ๊ตฌ์ฑ ํด์ฒด
const { internationalization, middleware } = configuration;
const { locales, defaultLocale } = internationalization;
/**
* ๋ก์ปฌํ๋ฅผ ์ฒ๋ฆฌํ๊ณ ์ ์ ํ ๋ก์ผ์ผ ์ปจํ
์คํธ๋ก ์์์ ๊ฐ์ธ๋ ์ปดํฌ๋ํธ.
* URL ๊ธฐ๋ฐ ๋ก์ผ์ผ ๊ฐ์ง ๋ฐ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ๊ด๋ฆฌํฉ๋๋ค.
*/
const AppLocalized: FC<PropsWithChildren<{ locale: Locales }>> = ({
children,
locale,
}) => {
const { pathname, search } = useLocation(); // ํ์ฌ URL ๊ฒฝ๋ก ๊ฐ์ ธ์ค๊ธฐ
// ํ์ฌ ๋ก์ผ์ผ ๊ฒฐ์ , ์ ๊ณต๋์ง ์์ ๊ฒฝ์ฐ ๊ธฐ๋ณธ ๋ก์ผ์ผ๋ก ๋์ฒด
const currentLocale = locale ?? defaultLocale;
// ๊ฒฝ๋ก์์ ๋ก์ผ์ผ ์ ๋์ฌ๋ฅผ ์ ๊ฑฐํ์ฌ ๊ธฐ๋ณธ ๊ฒฝ๋ก ์์ฑ
const pathWithoutLocale = getPathWithoutLocale(
pathname // ํ์ฌ URL ๊ฒฝ๋ก
);
/**
* middleware.prefixDefault๊ฐ true์ธ ๊ฒฝ์ฐ, ๊ธฐ๋ณธ ๋ก์ผ์ผ์ ํญ์ ์ ๋์ด๋ก ์ค์ ๋์ด์ผ ํฉ๋๋ค.
*/
if (middleware.prefixDefault) {
// ๋ก์ผ์ผ ์ ํจ์ฑ ๊ฒ์ฌ
if (!locale || !locales.includes(locale)) {
// ๊ธฐ๋ณธ ๋ก์ผ์ผ๊ณผ ์
๋ฐ์ดํธ๋ ๊ฒฝ๋ก๋ก ๋ฆฌ๋๋ ์
return (
<Navigate
to={`/${defaultLocale}/${pathWithoutLocale}${search}`}
replace // ํ์ฌ ํ์คํ ๋ฆฌ ํญ๋ชฉ์ ์ ํญ๋ชฉ์ผ๋ก ๋์ฒด
/>
);
}
// IntlayerProvider๋ก ์์์ ๊ฐ์ธ๊ณ ํ์ฌ ๋ก์ผ์ผ ์ค์
return (
<IntlayerProvider locale={currentLocale}>{children}</IntlayerProvider>
);
} else {
/**
* middleware.prefixDefault๊ฐ false์ธ ๊ฒฝ์ฐ, ๊ธฐ๋ณธ ๋ก์ผ์ผ์ ์ ๋์ด๋ก ์ค์ ๋์ง ์์ต๋๋ค.
* ํ์ฌ ๋ก์ผ์ผ์ด ์ ํจํ๊ณ ๊ธฐ๋ณธ ๋ก์ผ์ผ์ด ์๋์ง ํ์ธํฉ๋๋ค.
*/
if (
currentLocale.toString() !== defaultLocale.toString() &&
!locales
.filter(
(locale) => locale.toString() !== defaultLocale.toString() // ๊ธฐ๋ณธ ๋ก์ผ์ผ ์ ์ธ
)
.includes(currentLocale) // ํ์ฌ ๋ก์ผ์ผ์ด ์ ํจํ ๋ก์ผ์ผ ๋ชฉ๋ก์ ์๋์ง ํ์ธ
) {
// ๋ก์ผ์ผ ์ ๋์ฌ๊ฐ ์๋ ๊ฒฝ๋ก๋ก ๋ฆฌ๋๋ ์
return <Navigate to={`${pathWithoutLocale}${search}`} replace />;
}
// IntlayerProvider๋ก ์์์ ๊ฐ์ธ๊ณ ํ์ฌ ๋ก์ผ์ผ ์ค์
return (
<IntlayerProvider locale={currentLocale}>{children}</IntlayerProvider>
);
}
};
/**
* ๋ก์ผ์ผ๋ณ ๊ฒฝ๋ก๋ฅผ ์ค์ ํ๋ ๋ผ์ฐํฐ ์ปดํฌ๋ํธ์
๋๋ค.
* React Router๋ฅผ ์ฌ์ฉํ์ฌ ํ์์ ๊ด๋ฆฌํ๊ณ ๋ก์ปฌํ๋ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํฉ๋๋ค.
*/
export const LocaleRouter: FC<PropsWithChildren> = ({ children }) => (
<BrowserRouter>
<Routes>
{locales
.filter(
(locale) => middleware.prefixDefault || locale !== defaultLocale
)
.map((locale) => (
<Route
// ๋ก์ผ์ผ์ ์บก์ฒํ๋ ๊ฒฝ๋ก ํจํด(e.g., /en/, /fr/) ๋ฐ ๋ชจ๋ ํ์ ๊ฒฝ๋ก์ ์ผ์น
path={`/${locale}/*`}
key={locale}
element={<AppLocalized locale={locale}>{children}</AppLocalized>} // ๋ก์ผ์ผ ๊ด๋ฆฌ๋ก ์์์ ๊ฐ์
/>
))}
{
// ๊ธฐ๋ณธ ๋ก์ผ์ผ ์ ๋์ด๊ฐ ๋นํ์ฑํ๋ ๊ฒฝ์ฐ, ๋ฃจํธ ๊ฒฝ๋ก์์ ์์์ ์ง์ ๋ ๋๋ง
!middleware.prefixDefault && (
<Route
path="*"
element={
<AppLocalized locale={defaultLocale}>{children}</AppLocalized>
} // ๋ก์ผ์ผ ๊ด๋ฆฌ๋ก ์์์ ๊ฐ์
/>
)
}
</Routes>
</BrowserRouter>
);
๊ทธ๋ฐ ๋ค์, ์ ํ๋ฆฌ์ผ์ด์
์์ LocaleRouter ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค:
์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import { LocaleRouter } from "./components/LocaleRouter";
import type { FC } from "react";
// ... AppContent ์ปดํฌ๋ํธ
const App: FC = () => (
<LocaleRouter>
<AppContent />
</LocaleRouter>
);
๋ก์ผ์ผ ๋ณ๊ฒฝ ์ URL ๋ณ๊ฒฝ
์ ํ์ฌํญ๋ก์ผ์ผ์ด ๋ณ๊ฒฝ๋ ๋ URL์ ๋ณ๊ฒฝํ๋ ค๋ฉด useLocale ํ
์์ ์ ๊ณตํ๋ onLocaleChange ์์ฑ์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๋์์, react-router-dom์ useLocation ๋ฐ useNavigate ํ
์ ์ฌ์ฉํ์ฌ URL ๊ฒฝ๋ก๋ฅผ ์
๋ฐ์ดํธํ ์ ์์ต๋๋ค.
์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
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>
);
};
๋ฌธ์ ์ฐธ์กฐ:
HTML ์ธ์ด ๋ฐ ๋ฐฉํฅ ์์ฑ ์ ํ
์ ํ์ฌํญ์ ํ๋ฆฌ์ผ์ด์
์ด ์ฌ๋ฌ ์ธ์ด๋ฅผ ์ง์ํ ๋, <html> ํ๊ทธ์ lang ๋ฐ dir ์์ฑ์ ํ์ฌ ๋ก์ผ์ผ์ ๋ง๊ฒ ์
๋ฐ์ดํธํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. ์ด๋ฅผ ํตํด ๋ค์์ ๋ณด์ฅํ ์ ์์ต๋๋ค:
- ์ ๊ทผ์ฑ: ์คํฌ๋ฆฐ ๋ฆฌ๋ ๋ฐ ๋ณด์กฐ ๊ธฐ์ ์ ์ฌ๋ฐ๋ฅธ
lang์์ฑ์ ์ฌ์ฉํ์ฌ ์ฝํ ์ธ ๋ฅผ ์ ํํ ๋ฐ์ํ๊ณ ํด์ํฉ๋๋ค. - ํ
์คํธ ๋ ๋๋ง:
dir(๋ฐฉํฅ) ์์ฑ์ ํ ์คํธ๊ฐ ์ฌ๋ฐ๋ฅธ ์์๋ก ๋ ๋๋ง๋๋๋ก ๋ณด์ฅํฉ๋๋ค(์: ์์ด๋ ์ผ์ชฝ์์ ์ค๋ฅธ์ชฝ, ์๋์ด๋ ํ๋ธ๋ฆฌ์ด๋ ์ค๋ฅธ์ชฝ์์ ์ผ์ชฝ). ์ด๋ ๊ฐ๋ ์ฑ์ ํ์์ ์ ๋๋ค. - SEO: ๊ฒ์ ์์ง์
lang์์ฑ์ ์ฌ์ฉํ์ฌ ํ์ด์ง์ ์ธ์ด๋ฅผ ํ๋จํ๊ณ , ๊ฒ์ ๊ฒฐ๊ณผ์์ ์ ์ ํ ํ์งํ๋ ์ฝํ ์ธ ๋ฅผ ์ ๊ณตํ๋ ๋ฐ ๋์์ ์ค๋๋ค.
๋ก์ผ์ผ์ด ๋ณ๊ฒฝ๋ ๋ ์ด๋ฌํ ์์ฑ์ ๋์ ์ผ๋ก ์ ๋ฐ์ดํธํ๋ฉด ์ง์๋๋ ๋ชจ๋ ์ธ์ด์์ ์ฌ์ฉ์์๊ฒ ์ผ๊ด๋๊ณ ์ ๊ทผ ๊ฐ๋ฅํ ๊ฒฝํ์ ๋ณด์ฅํ ์ ์์ต๋๋ค.
Hook ๊ตฌํ
HTML ์์ฑ์ ๊ด๋ฆฌํ๊ธฐ ์ํ ์ปค์คํ ํ ์ ์์ฑํฉ๋๋ค. ์ด ํ ์ ๋ก์ผ์ผ ๋ณ๊ฒฝ์ ๊ฐ์งํ๊ณ ์์ฑ์ ์ ์ ํ ์ ๋ฐ์ดํธํฉ๋๋ค:
์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
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]);
};
์ ํ๋ฆฌ์ผ์ด์ ์์ Hook ์ฌ์ฉํ๊ธฐ
๋ก์ผ์ผ์ด ๋ณ๊ฒฝ๋ ๋๋ง๋ค HTML ์์ฑ์ด ์ ๋ฐ์ดํธ๋๋๋ก ํ ์ ๋ฉ์ธ ์ปดํฌ๋ํธ์ ํตํฉํ์ธ์:
์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import type { FC } from "react";
import { IntlayerProvider, useIntlayer } from "react-intlayer";
import { useI18nHTMLAttributes } from "./hooks/useI18nHTMLAttributes";
import "./App.css";
const AppContent: FC = () => {
// ๋ก์ผ์ผ์ ๋ฐ๋ผ <html> ํ๊ทธ์ lang ๋ฐ dir ์์ฑ์ ์
๋ฐ์ดํธํ๋ ํ
์ ์ ์ฉํฉ๋๋ค.
useI18nHTMLAttributes();
// ... ๋๋จธ์ง ์ปดํฌ๋ํธ
};
const App: FC = () => (
<IntlayerProvider>
<AppContent />
</IntlayerProvider>
);
export default App;
์ด๋ฌํ ๋ณ๊ฒฝ ์ฌํญ์ ์ ์ฉํ๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ค์์ ๋ณด์ฅํฉ๋๋ค:
- ์ธ์ด(
lang) ์์ฑ์ด ํ์ฌ ๋ก์ผ์ผ์ ์ ํํ ๋ฐ์ํ์ฌ SEO ๋ฐ ๋ธ๋ผ์ฐ์ ๋์์ ์ค์ํฉ๋๋ค. - ๋ก์ผ์ผ์ ๋ฐ๋ผ ํ
์คํธ ๋ฐฉํฅ(
dir)์ ์กฐ์ ํ์ฌ ๋ค๋ฅธ ์ฝ๊ธฐ ์์๋ฅผ ๊ฐ์ง ์ธ์ด์ ๊ฐ๋ ์ฑ๊ณผ ์ฌ์ฉ์ฑ์ ํฅ์์ํต๋๋ค. - ์ ๊ทผ์ฑ์ ๊ฐ์ ํ์ฌ ๋ณด์กฐ ๊ธฐ์ ์ด ์ด๋ฌํ ์์ฑ์ ์์กดํด ์ต์ ์ ๊ธฐ๋ฅ์ ์ํํ ์ ์๋๋ก ํฉ๋๋ค.
TypeScript ๊ตฌ์ฑ
Intlayer๋ TypeScript์ ๋ชจ๋ ํ์ฅ์ ์ฌ์ฉํ์ฌ ์ฝ๋๋ฒ ์ด์ค๋ฅผ ๋ ๊ฐ๋ ฅํ๊ฒ ๋ง๋ญ๋๋ค.


TypeScript ๊ตฌ์ฑ์ ์๋ ์์ฑ๋ ํ์ ์ ํฌํจํด์ผ ํฉ๋๋ค.
์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
Git ๊ตฌ์ฑ
Intlayer์์ ์์ฑ๋ ํ์ผ์ ๋ฌด์ํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ์ด๋ฅผ ํตํด ํด๋น ํ์ผ์ Git ์ ์ฅ์์ ์ปค๋ฐํ๋ ๊ฒ์ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
์ด๋ฅผ ์ํด .gitignore ํ์ผ์ ๋ค์ ์ง์นจ์ ์ถ๊ฐํ ์ ์์ต๋๋ค:
์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
VS Code ํ์ฅ
Intlayer๋ฅผ ์ฌ์ฉํ๋ฉด์ ๊ฐ๋ฐ ๊ฒฝํ์ ๊ฐ์ ํ๊ธฐ ์ํด ๊ณต์ Intlayer VS Code Extension์ ์ค์นํ ์ ์์ต๋๋ค.
Intlayer์ ํจ๊ป ๊ฐ๋ฐ ๊ฒฝํ์ ํฅ์์ํค๋ ค๋ฉด ๊ณต์ Intlayer VS Code ํ์ฅ์ ์ค์นํ ์ ์์ต๋๋ค. VS Code ๋ง์ผํ๋ ์ด์ค์์ ์ค์นํ๊ธฐ
์ด ํ์ฅ ๊ธฐ๋ฅ์ ๋ค์์ ์ ๊ณตํฉ๋๋ค:
- ๋ฒ์ญ ํค์ ๋ํ ์๋ ์์ฑ.
- ๋๋ฝ๋ ๋ฒ์ญ์ ๋ํ ์ค์๊ฐ ์ค๋ฅ ๊ฐ์ง.
- ๋ฒ์ญ๋ ์ฝํ ์ธ ์ ์ธ๋ผ์ธ ๋ฏธ๋ฆฌ๋ณด๊ธฐ.
- ๋ฒ์ญ์ ์ฝ๊ฒ ์์ฑํ๊ณ ์ ๋ฐ์ดํธํ ์ ์๋ ๋น ๋ฅธ ์์ .
ํ์ฅ ๊ธฐ๋ฅ ์ฌ์ฉ ๋ฐฉ๋ฒ์ ๋ํ ์์ธํ ๋ด์ฉ์ Intlayer VS Code ํ์ฅ ๊ธฐ๋ฅ ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ธ์.
๋ ๋์๊ฐ๊ธฐ
๋ ๋์๊ฐ๋ ค๋ฉด ๋น์ฃผ์ผ ์๋ํฐ๋ฅผ ๊ตฌํํ๊ฑฐ๋ CMS๋ฅผ ์ฌ์ฉํ์ฌ ์ฝํ ์ธ ๋ฅผ ์ธ๋ถํํ ์ ์์ต๋๋ค.
์์ฃผ ๋ฌป๋ ์ง๋ฌธ
react-i18next/i18next: ๊ฐ์ฅ ๋๋ฆฌ ์ฌ์ฉ๋๋ฉฐ, ๋ฐํ์์ JSON ๋ค์์คํ์ด์ค๋ฅผ ๋ก๋ํฉ๋๋ค.react-intl๋ฐLingui: ICU ๋ฉ์์ง ํ์ ๊ธฐ๋ฐ์ผ๋ก, ์ถ์ถ ๋ฐฉ์์ ์ฌ์ฉํฉ๋๋ค.Intlayer: ๊ฐ์ฅ ์ง๋ณด๋ ์๋ฃจ์ ์ ๋๋ค. ์ฝ๋๋ฒ ์ด์ค ์ด๋์๋ ์ฝํ ์ธ ๋ฅผ ์ ์ธํ ์ ์์ผ๋ฉฐ(๊ฐ ์ปดํฌ๋ํธ ์ ๋๋ ์ค์ ์ง์ค์),react-scripts-intlayer๋ฅผ ํตํด ๋น๋ ํ์์ ์ปดํ์ผ๋๊ณ , ์์ ํ ํ์ ์์ ์ฑ์ ์ ๊ณตํ๋ฉฐ AI ๋ฒ์ญ, ๋น์ฃผ์ผ ์๋ํฐ ๋ฐ CMS๋ฅผ ์ง์ํฉ๋๋ค.
Create React App์ ์์ฒด webpack ๊ตฌ์ฑ์ ๋ํํ๋ฏ๋ก, ์ง์ ํ๋ฌ๊ทธ์ธ์ ๋ฑ๋กํ๋ ๋์ react-scripts์ ๋๋กญ์ธ ๋์ฒดํ์ธ react-scripts-intlayer๋ฅผ ํตํด ํตํฉ๋ฉ๋๋ค. ์ 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๋ง ๊ฒํ ํ๋ฉด ๋ฉ๋๋ค.
์์ ์๋ํ๋ ํ์ดํ๋ผ์ธ์ ์ํด 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๊ท์น์ผ๋ก ํ๋์ฝ๋ฉ๋ ๋ฌธ์์ด์ ํ์ํ๊ณ , ์ ์ ์ฌ์ ํค ๋ฐ ์ฌ์ฉ๋์ง ์๋ ์ฝํ ์ธ ์ ๋ํ ์ถ๊ฐ ๊ท์น์ ์ ๊ณตํฉ๋๋ค.
