์ด ํ์ด์ง์๋ ์ ํ๋ฆฌ์ผ์ด์ ํ ํ๋ฆฟ์ด ์ ๊ณต๋ฉ๋๋ค.
์ด ํ์ด์ง๋ ํ ํ๋ฆฟ์ ๋ผ์ด๋ธ ๋ฐ๋ชจ๋ก ์ฐ๊ฒฐ๋ฉ๋๋ค.
์ด ํ์ด์ง์ ์ํ๋ AI ์ด์์คํดํธ๋ฅผ ์ฌ์ฉํ์ฌ ๋ฌธ์๋ฅผ ์์ฝํฉ๋๋ค
๋ฒ์ ๊ธฐ๋ก
- "Solid useIntlayer API ์ฌ์ฉ๋ฒ์ ์ง์ ์์ฑ ์ก์ธ์ค๋ก ์
๋ฐ์ดํธ"v8.9.02026. 5. 4.
- "init ๋ช
๋ น์ด ์ถ๊ฐ"v7.5.92025. 12. 30.
- "`alternates` ๊ฐ์ฒด์ `x-default` ์ธ๊ธ ์ถ๊ฐ"v7.0.62025. 11. 1.
- "์ด๊ธฐ ๊ธฐ๋ก"v7.0.02025. 6. 29.
์ด ํ์ด์ง์ ์ฝํ ์ธ ๋ AI๋ฅผ ์ฌ์ฉํ์ฌ ๋ฒ์ญ๋์์ต๋๋ค.
์์ด ์๋ณธ ๋ด์ฉ์ ์ต์ ๋ฒ์ ์ ๋ณด๊ธฐ์ด ๋ฌธ์๋ฅผ ๊ฐ์ ํ ์์ด๋์ด๊ฐ ์์ผ์๋ฉด GitHub์ ํ ๋ฆฌํ์คํธ๋ฅผ ์ ์ถํ์ฌ ์์ ๋กญ๊ฒ ๊ธฐ์ฌํด ์ฃผ์ธ์.
๋ฌธ์์ ๋ํ GitHub ๋งํฌ๋ฌธ์์ Markdown์ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
Intlayer๋ฅผ ์ฌ์ฉํ์ฌ Next.js 16 ์น์ฌ์ดํธ ๋ฒ์ญํ๊ธฐ
GitHub์์ ์ ํ๋ฆฌ์ผ์ด์ ํ ํ๋ฆฟ ๋ฐ ๋ก์ผ์ผ ๊ฒฝ๋ก ์๋ ์ ํ๋ฆฌ์ผ์ด์ ํ ํ๋ฆฟ์ ํ์ธํ์ธ์.
๋ชฉ์ฐจ
๋์๋ณด๋ค Intlayer๋ฅผ ์ ํํด์ผ ํ๋ ์ด์ ๋ ๋ฌด์์ ๋๊น?
'next-intl' ๋๋ 'i18next'์ ๊ฐ์ ์ฃผ์ ์๋ฃจ์ ๊ณผ ๋น๊ตํ ๋ Intlayer๋ ๋ค์๊ณผ ๊ฐ์ ํตํฉ ์ต์ ํ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ์๋ฃจ์ ์ ๋๋ค.
Intlayer๋ ํจ์จ์ ์ธ ๋ ๋๋ง์ ์ํด ์๋ฒ ๊ตฌ์ฑ์์์ ์๋ํ๋๋ก ์ต์ ํ๋์ด ์์ผ๋ฉฐ Turbopack๊ณผ ์๋ฒฝํ๊ฒ ํธํ๋ฉ๋๋ค. ์ ์ ๋ ๋๋ง์ ์ฐจ๋จํ์ง ์์ผ๋ฉฐ ๋ฏธ๋ค์จ์ด๋ ๋ฌผ๋ก ๊ตญ์ ํ ํ์ฅ(i18n)์ ํ์ํ ๋ชจ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.
Intlayer๋ Next.js 12, 13, 14, 15, 16๊ณผ ํธํ๋ฉ๋๋ค. Next.js Pages Router๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ์ด ๊ฐ์ด๋๋ฅผ ์ฐธ์กฐํ์ธ์. ๋ก์ผ์ผ ๋ผ์ฐํ ์ SEO, ๋ฒ๋ค ํฌ๊ธฐ ๋ฐ ์ฑ๋ฅ์ ์ ์ฉํฉ๋๋ค. ๋ก์ผ์ผ ๊ฒฝ๋ก๊ฐ ์๋ ์ค์ ๊ณผ ์๋ ์ค์ ๋ชจ๋ ์ง์๋๋ฉฐ ์ด ๊ฐ์ด๋์์ ๋ค๋ฃน๋๋ค. ์ฑ ๋ผ์ฐํฐ๊ฐ ํฌํจ๋ Next.js 12, 13, 14, 15์ ๊ฒฝ์ฐ ์ด ๊ฐ์ด๋๋ฅผ ์ฐธ์กฐํ์ธ์.
๋์ฉ๋ JSON ํ์ผ์ ํ์ด์ง์ ๋ก๋ํ๋ ๋์ ํ์ํ ์ฝํ ์ธ ๋ง ๋ก๋ํ์ธ์. Intlayer๋ ๋ฒ๋ค ๋ฐ ํ์ด์ง ํฌ๊ธฐ๋ฅผ ์ต๋ 50% ์ค์ด๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค.
์ ํ๋ฆฌ์ผ์ด์ ์ฝํ ์ธ ์ ๋ฒ์๋ฅผ ์ง์ ํ๋ฉด ๋๊ท๋ชจ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ์ง ๊ด๋ฆฌ๊ฐ ์ฉ์ดํด์ง๋๋ค. ์ ์ฒด ์ฝํ ์ธ ์ฝ๋๋ฒ ์ด์ค๋ฅผ ๊ฒํ ํด์ผ ํ๋ ์ ์ ์ ๋ถ๋ด ์์ด ๋จ์ผ ๊ธฐ๋ฅ ํด๋๋ฅผ ๋ณต์ ํ๊ฑฐ๋ ์ญ์ ํ ์ ์์ต๋๋ค. ๋ํ Intlayer๋ ์์ ํ ์ ํ๋์ด ์ฝํ ์ธ ์ ์ ํ์ฑ์ ๋ณด์ฅํฉ๋๋ค.
์ฝํ ์ธ ๋ฅผ ๊ฐ์ ์์น์ ๋ฐฐ์นํ๋ฉด LLM(๋ํ ์ธ์ด ๋ชจ๋ธ)์ ํ์ํ ์ปจํ ์คํธ๊ฐ ์ค์ด๋ญ๋๋ค. Intlayer์๋ ๋๋ฝ๋ ๋ฒ์ญ์ ํ ์คํธํ๊ธฐ ์ํ CLI, LSP, MCP ๋ฐ agent skills, AI ์์ด์ ํธ๋ฅผ ์ํ ๊ฐ๋ฐ์ ๊ฒฝํ(DX)์ ๋์ฑ ์ํํ๊ฒ ๋ง๋ญ๋๋ค.
AI ๊ณต๊ธ์์ ๋น์ฉ์ผ๋ก ์ ํํ LLM์ ์ฌ์ฉํ์ฌ CI/CD ํ์ดํ๋ผ์ธ์ ๋ฒ์ญํ๋ ค๋ฉด ์๋ํ๋ฅผ ์ฌ์ฉํ์ธ์. Intlayer๋ ๋ํ ์ฝํ ์ธ ์ถ์ถ์ ์๋ํํ๋ ์ปดํ์ผ๋ฌ์ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๋ฒ์ญ์ ๋๋ ์น ํ๋ซํผ์ ์ ๊ณตํฉ๋๋ค.
๋๊ท๋ชจ JSON ํ์ผ์ ๊ตฌ์ฑ ์์์ ์ฐ๊ฒฐํ๋ฉด ์ฑ๋ฅ ๋ฐ ๋ฐ์์ฑ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. Intlayer๋ ๋น๋ ์ ์ฝํ ์ธ ๋ก๋ฉ์ ์ต์ ํํฉ๋๋ค.
Intlayer๋ ๋จ์ํ i18n ์๋ฃจ์ ๊ทธ ์ด์์ผ๋ก ๊ด๋ฆฌ์ ๋์์ด ๋๋ ์์ฒด ํธ์คํ ๋น์ฃผ์ผ ํธ์ง๊ธฐ ๋ฐ ์ ์ฒด CMS๋ฅผ ์ ๊ณตํฉ๋๋ค. ๋ค๊ตญ์ด ์ฝํ ์ธ ๋ฅผ ์ค์๊ฐ์ผ๋ก ์ ๊ณตํ์ฌ ๋ฒ์ญ๊ฐ, ์นดํผ๋ผ์ดํฐ, ๊ธฐํ ํ์๊ณผ ์ํํ๊ฒ ํ์ ํ ์ ์์ต๋๋ค. ์ฝํ ์ธ ๋ ๋ก์ปฌ ๋ฐ/๋๋ ์๊ฒฉ์ผ๋ก ์ ์ฅ๋ ์ ์์ต๋๋ค.
Next.js ์ ํ๋ฆฌ์ผ์ด์ ์์ Intlayer ์ค์ ๋จ๊ณ๋ณ ๊ฐ์ด๋
์์กด์ฑ ์ค์น
npm์ ์ฌ์ฉํ์ฌ ํ์ํ ํจํค์ง๋ฅผ ์ค์นํ์ธ์:
bash์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
--interactiveํ๋๊ทธ๋ ์ ํ ์ฌํญ์ ๋๋ค. AI ์์ด์ ํธ์ธ ๊ฒฝ์ฐintlayer-cli init๋ฅผ ์ฌ์ฉํ์ธ์.์ด ๋ช ๋ น์ ํ๊ฒฝ์ ๊ฐ์งํ๊ณ ํ์ํ ํจํค์ง๋ฅผ ์ค์นํฉ๋๋ค. ์๋ฅผ ๋ค์ด:
bash์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
intlayer
๊ตฌ์ฑ ๊ด๋ฆฌ, ๋ฒ์ญ, ์ฝํ ์ธ ์ ์ธ, ํธ๋์คํ์ผ๋ง ๋ฐ CLI ๋ช ๋ น์ด๋ฅผ ์ํ ๊ตญ์ ํ ๋๊ตฌ๋ฅผ ์ ๊ณตํ๋ ํต์ฌ ํจํค์ง์ ๋๋ค.
next-intlayer
Intlayer๋ฅผ Next.js์ ํตํฉํ๋ ํจํค์ง์ ๋๋ค. Next.js ๊ตญ์ ํ๋ฅผ ์ํ ์ปจํ ์คํธ ํ๋ก๋ฐ์ด๋์ ํ ์ ์ ๊ณตํฉ๋๋ค. ๋ํ, Intlayer๋ฅผ Webpack ๋๋ Turbopack๊ณผ ํตํฉํ๊ธฐ ์ํ Next.js ํ๋ฌ๊ทธ์ธ๊ณผ ์ฌ์ฉ์์ ์ ํธ ๋ก์ผ์ผ ๊ฐ์ง, ์ฟ ํค ๊ด๋ฆฌ, URL ๋ฆฌ๋๋ ์ ์ฒ๋ฆฌ๋ฅผ ์ํ ํ๋ก์๋ ํฌํจ๋์ด ์์ต๋๋ค.
ํ๋ก์ ํธ ๊ตฌ์ฑ
์ ํ๋ฆฌ์ผ์ด์ ์ด ์ง์ญํ๋ URL ๊ฒฝ๋ก(์:
/ko/about,/en/about)๋ฅผ ์ฌ์ฉํ ์ง, ์๋๋ฉด ๊ฒฝ๋ก์ ๋ก์ผ์ผ ์ธ๊ทธ๋จผํธ ์์ด ์ฝํ ์ธ ๋ฅผ ์ ๊ณตํ ์ง(์:/about, ์ฟ ํค, ํค๋ ๋๋ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ฅผ ํตํด ๊ฐ์ง) ์ ํํ์ธ์.์ํคํ ์ฒ
์ด ์ํคํ ์ฒ์์๋ ์ง์ญํ๋ ๋ชจ๋ ํ์ด์ง๊ฐ
[locale]๋์ ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ ์๋์ ์ค์ฒฉ๋ฉ๋๋ค. ๊ฐ ์ธ์ด๋ง๋ค ๊ณ ์ ํ ์ ์ฉ URL์ด ์ ๊ณต๋๋ฏ๋ก SEO ๋ฐ ์ ์ ๋ ๋๋ง์ ๊ถ์ฅ๋ฉ๋๋ค:bash์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
์ค์
์ ํ๋ฆฌ์ผ์ด์ ์์ ์ง์ํ๋ ์ธ์ด๋ฅผ ์ ์ธํ๊ธฐ ์ํด
intlayer.config.ts์ค์ ํ์ผ์ ์์ฑํฉ๋๋ค:intlayer.config.ts์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import { Locales, type IntlayerConfig } from "intlayer"; const config: IntlayerConfig = { internationalization: { locales: [ Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH, // ๋ค๋ฅธ ๋ก์ผ์ผ๋ค ], defaultLocale: Locales.ENGLISH, }, routing: { mode: "prefix-no-default", // ๋๋ `prefix-all` }, }; export default config;์ํคํ ์ฒ
์ด ์ํคํ ์ฒ์์๋ ๋ผ์ฐํธ์ URL ๊ฒฝ๋ก์
[locale]๋์ ์ธ๊ทธ๋จผํธ๊ฐ ํฌํจ๋์ง ์์ต๋๋ค. ๋ชจ๋ ํ์ด์ง๋src/app/๋ฐ๋ก ์๋์ ์์นํ๋ฉฐ ์ฟ ํค, ํค๋ ๋๋ ์ฟผ๋ฆฌ ๋งค๊ฐ๋ณ์๋ฅผ ํตํด ๋ก์ผ์ผ์ด ๊ฒฐ์ ๋ฉ๋๋ค. ๋์๋ณด๋, ์ฌ๋ด ๋๊ตฌ ๋๋ ์ธ์ฆ๋ ์ฑ์ ์ ํฉํฉ๋๋ค:bash์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
์ค์
๊ฒฝ๋ก ์ ๋์ฌ ์์ด ์ฝํ ์ธ ๋ฅผ ์ ๊ณตํ๋ ค๋ฉด
routing.mode์์ฑ์"search-params"(์:/about?locale=ko) ๋๋"no-prefix"๋ก ์ค์ ํ์ธ์:intlayer.config.ts์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import { Locales, type IntlayerConfig } from "intlayer"; const config: IntlayerConfig = { internationalization: { locales: [ Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH, // ๋ค๋ฅธ ๋ก์ผ์ผ๋ค ], defaultLocale: Locales.ENGLISH, }, routing: { mode: "search-params", // ๋๋ `no-prefix` - ๋ฏธ๋ค์จ์ด ๊ฐ์ง์ ์ ์ฉํจ }, }; export default config;์ด ๊ตฌ์ฑ ํ์ผ์ ํตํด ์ง์ญํ๋ URL, ํ๋ก์ ๋ฆฌ๋๋ ์ , ์ฟ ํค ์ด๋ฆ, ์ฝํ ์ธ ์ ์ธ์ ์์น ๋ฐ ํ์ฅ์ ์ค์ , ์ฝ์์์ Intlayer ๋ก๊ทธ ๋นํ์ฑํ ๋ฑ ๋ค์ํ ์ค์ ์ ํ ์ ์์ต๋๋ค. ์ฌ์ฉ ๊ฐ๋ฅํ ๋ชจ๋ ๋งค๊ฐ๋ณ์ ๋ชฉ๋ก์ ๊ตฌ์ฑ ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ธ์.
Next.js ๊ตฌ์ฑ์ Intlayer ํตํฉํ๊ธฐ
Next.js ์ค์ ์ Intlayer์ ํจ๊ป ์ฌ์ฉํ๋๋ก ๊ตฌ์ฑํฉ๋๋ค:
next.config.ts์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import type { NextConfig } from "next"; import { withIntlayer } from "next-intlayer/server"; const nextConfig: NextConfig = {/* ์ฌ๊ธฐ์ ๊ตฌ์ฑ ์ต์ ์ ์ ๋ ฅํ์ธ์ */}; export default withIntlayer(nextConfig);withIntlayer()Next.js ํ๋ฌ๊ทธ์ธ์ Intlayer๋ฅผ Next.js์ ํตํฉํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. ์ด ํ๋ฌ๊ทธ์ธ์ ์ฝํ ์ธ ์ ์ธ ํ์ผ์ ๋น๋๋ฅผ ๋ณด์ฅํ๊ณ ๊ฐ๋ฐ ๋ชจ๋์์ ์ด๋ฅผ ๋ชจ๋ํฐ๋งํฉ๋๋ค. ๋ํ Webpack ๋๋ Turbopack ํ๊ฒฝ ๋ด์์ Intlayer ํ๊ฒฝ ๋ณ์๋ฅผ ์ ์ํฉ๋๋ค. ์ถ๊ฐ๋ก, ์ฑ๋ฅ ์ต์ ํ๋ฅผ ์ํ ๋ณ์นญ(alias)์ ์ ๊ณตํ๋ฉฐ ์๋ฒ ์ปดํฌ๋ํธ์์ ํธํ์ฑ์ ๋ณด์ฅํฉ๋๋ค.withIntlayer()ํจ์๋ ํ๋ก๋ฏธ์ค ํจ์์ ๋๋ค. ๋น๋๊ฐ ์์๋๊ธฐ ์ ์ Intlayer ์ฌ์ ์ ์ค๋นํ ์ ์๊ฒ ํด์ค๋๋ค. ๋ค๋ฅธ ํ๋ฌ๊ทธ์ธ๊ณผ ํจ๊ป ์ฌ์ฉํ๋ ค๋ฉดawaitํ ์ ์์ต๋๋ค. ์์:ts์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
๋๊ธฐ์ ์ผ๋ก ์ฌ์ฉํ๊ณ ์ถ๋ค๋ฉด
withIntlayerSync()ํจ์๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์์:ts์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
Intlayer๋ ๋ช ๋ น์ค ํ๋๊ทธ
--webpack,--turbo๋๋--turbopack๋ฟ๋ง ์๋๋ผ ํ์ฌ Next.js ๋ฒ์ ์ ๊ธฐ๋ฐ์ผ๋ก ํ๋ก์ ํธ๊ฐ webpack ๋๋ Turbopack์ ์ฌ์ฉํ๊ณ ์๋์ง ์๋์ผ๋ก ๊ฐ์งํฉ๋๋ค.next>=16๋ถํฐ Rspack์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ, Turbopack์ ๋นํ์ฑํํ์ฌ Intlayer๊ฐ webpack ๊ตฌ์ฑ์ ์ฌ์ฉํ๋๋ก ๋ช ์์ ์ผ๋ก ๊ฐ์ ํด์ผ ํฉ๋๋ค:ts์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
๋์ ๋ก์ผ์ผ ๊ฒฝ๋ก ์ ์
RootLayout์ ๋ชจ๋ ๋ด์ฉ์ ์ ๊ฑฐํ๊ณ ๋ค์ ์ฝ๋๋ก ๊ต์ฒดํ์ธ์:src/app/layout.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import type { PropsWithChildren, FC } from "react"; import "./globals.css"; const RootLayout: FC<PropsWithChildren> = ({ children }) => ( // ์ฌ์ ํ `next-themes`, `react-query`, `framer-motion` ๋ฑ๊ณผ ๊ฐ์ ๋ค๋ฅธ ํ๋ก๋ฐ์ด๋๋ก children์ ๊ฐ์ ์ ์์ต๋๋ค. <>{children}</> ); export default RootLayout;RootLayout์ปดํฌ๋ํธ๋ฅผ ๋น์๋๋ฉด<html>ํ๊ทธ์lang๋ฐdir์์ฑ์ ์ค์ ํ ์ ์์ต๋๋ค.๋์ ๋ผ์ฐํ ์ ๊ตฌํํ๋ ค๋ฉด
[locale]๋๋ ํ ๋ฆฌ์ ์ ๋ ์ด์์์ ์ถ๊ฐํ์ฌ ๋ก์ผ์ผ ๊ฒฝ๋ก๋ฅผ ์ ๊ณตํ์ธ์:src/app/[locale]/layout.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import { type NextLayoutIntlayer } from "next-intlayer"; import { IntlayerProvider } from "next-intlayer/server"; import { Inter } from "next/font/google"; import { getHTMLTextDir } from "intlayer"; const inter = Inter({ subsets: ["latin"] }); const LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => { const { locale } = await params; return ( <html lang={locale} dir={getHTMLTextDir(locale)}> <body className={inter.className}> <IntlayerProvider locale={locale}>{children}</IntlayerProvider> </body> </html> ); }; export default LocaleLayout;๋จ์ผ
IntlayerProvider๋ ํธ๋ฆฌ์ ์์ชฝ ์ ๋ฐ์ ๋ชจ๋ ๋ค๋ฃน๋๋ค: ์๋ฒ ํ ์์ ์ฝ๋ ์์ฒญ ๋ฒ์ ์๋ฒ ์ปจํ ์คํธ๋ฅผ ์๋ํ๊ณ , ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ๊ฐ ๋์ผํ ๋ก์ผ์ผ์ ๋ฐ๋๋ก ํด๋ผ์ด์ธํธ ๊ณต๊ธ์๋ฅผ ๋ง์ดํธํฉ๋๋ค.src/app/[locale]/layout.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import { type NextLayoutIntlayer, IntlayerClientProvider } from "next-intlayer"; import { Inter } from "next/font/google"; import { getHTMLTextDir } from "intlayer"; const inter = Inter({ subsets: ["latin"] }); const LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => { const { locale } = await params; return ( <html lang={locale} dir={getHTMLTextDir(locale)}> <body className={inter.className}> <IntlayerClientProvider locale={locale}> {children} </IntlayerClientProvider> </body> </html> ); }; export default LocaleLayout;[locale]๊ฒฝ๋ก ์ธ๊ทธ๋จผํธ๋ ๋ก์ผ์ผ์ ์ ์ํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. ์:/en-US/about๋en-US๋ฅผ ๊ฐ๋ฆฌํค๊ณ/fr/about๋fr๋ฅผ ๊ฐ๋ฆฌํต๋๋ค.์ด ๋จ๊ณ์์
Error: Missing <html> and <body> tags in the root layout.์ค๋ฅ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. ์ด๋/app/page.tsxํ์ผ์ด ๋ ์ด์ ์ฌ์ฉ๋์ง ์์ผ๋ฉฐ ์ญ์ ํ ์ ์๊ธฐ ๋๋ฌธ์ ์์๋๋ ํ์์ ๋๋ค. ๋์ ,[locale]๊ฒฝ๋ก ์ธ๊ทธ๋จผํธ๊ฐ/app/[locale]/page.tsxํ์ด์ง๋ฅผ ํ์ฑํํฉ๋๋ค. ๋ฐ๋ผ์ ๋ธ๋ผ์ฐ์ ์์/en,/fr,/es์ ๊ฐ์ ๊ฒฝ๋ก๋ฅผ ํตํด ํ์ด์ง์ ์ ๊ทผํ ์ ์์ต๋๋ค. ๊ธฐ๋ณธ ๋ก์ผ์ผ์ ๋ฃจํธ ํ์ด์ง๋ก ์ค์ ํ๋ ค๋ฉด 7๋จ๊ณ์proxy์ค์ ์ ์ฐธ์กฐํ์ธ์.๊ทธ๋ฐ ๋ค์, ์ ํ๋ฆฌ์ผ์ด์ Layout์์
generateStaticParamsํจ์๋ฅผ ๊ตฌํํ์ธ์.src/app/[locale]/layout.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
export { generateStaticParams } from "next-intlayer"; // ์ฝ์ ํ ๋ผ์ธ const LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => { /*... ๋๋จธ์ง ์ฝ๋ */ }; export default LocaleLayout;generateStaticParams๋ ์ ํ๋ฆฌ์ผ์ด์ ์ด ๋ชจ๋ ๋ก์ผ์ผ์ ๋ํด ํ์ํ ํ์ด์ง๋ฅผ ์ฌ์ ๋น๋ํ๋๋ก ๋ณด์ฅํ์ฌ ๋ฐํ์ ๊ณ์ฐ์ ์ค์ด๊ณ ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํต๋๋ค. ์์ธํ ๋ด์ฉ์ Next.js์ generateStaticParams ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ธ์.Intlayer๋
export const dynamic = 'force-static';์ ํจ๊ป ์๋ํ์ฌ ๋ชจ๋ ๋ก์ผ์ผ์ ๋ํด ํ์ด์ง๊ฐ ์ฌ์ ๋น๋๋๋๋ก ๋ณด์ฅํฉ๋๋ค.๋ก์ผ์ผ ๊ฒฝ๋ก๊ฐ ์๋ ์ํคํ ์ฒ์์๋
[locale]๋๋ ํฐ๋ฆฌ๊ฐ ์์ต๋๋ค. ์์ฒญ ์ปจํ ์คํธ์์ ๋ก์ผ์ผ์ ์ฝ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์IntlayerProvider๋ก ๋ํํ๋๋กsrc/app/layout.tsx๋ฅผ ์ง์ ๊ตฌ์ฑํ์ธ์:src/app/layout.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import type { Metadata } from "next"; import type { ReactNode } from "react"; import "./globals.css"; import { getHTMLTextDir, getIntlayer } from "intlayer"; import { getLocale, IntlayerProvider } from "next-intlayer/server"; export { generateStaticParams } from "next-intlayer"; export const generateMetadata = async (): Promise<Metadata> => { const locale = await getLocale(); const { title, description, keywords } = getIntlayer("metadata", locale); return { title, description, keywords, }; }; const RootLayout = async ({ children, }: Readonly<{ children: ReactNode; }>) => { const locale = await getLocale(); return ( <html lang={locale} dir={getHTMLTextDir(locale)}> <body> <IntlayerProvider locale={locale}>{children}</IntlayerProvider> </body> </html> ); }; export default RootLayout;๋จ์ผ
IntlayerProvider๋ ํธ๋ฆฌ์ ๋ ๋ถ๋ถ์ ๋ชจ๋ ์ปค๋ฒํฉ๋๋ค: ์๋ฒ ํ ์์ ์ฝ๋ request-scoped ์๋ฒ ์ปจํ ์คํธ๋ฅผ ์ ๊ณตํ๊ณ , ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ๊ฐ ๋์ผํ ๋ก์ผ์ผ์ ๋ฐ๋๋ก ํด๋ผ์ด์ธํธ provider๋ฅผ ๋ง์ดํธํฉ๋๋ค.RootLayout์์ ๋ชจ๋ ๋ด์ฉ์ ์ ๊ฑฐํ๊ณ ๋ค์ ์ฝ๋๋ก ๊ต์ฒดํ์ธ์:
src/app/layout.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import type { Metadata } from "next"; import type { ReactNode } from "react"; import "./globals.css"; import { IntlayerProvider, LocalPromiseParams } from "next-intlayer"; import { getHTMLTextDir, getIntlayer } from "intlayer"; import { getLocale } from "next-intlayer/server"; export { generateStaticParams } from "next-intlayer"; export const generateMetadata = async (): Promise<Metadata> => { const locale = await getLocale(); const { title, description, keywords } = getIntlayer("metadata", locale); return { title, description, keywords, }; }; const RootLayout = async ({ children, }: Readonly<{ children: ReactNode; }>) => { const locale = await getLocale(); return ( <html lang={locale} dir={getHTMLTextDir(locale)}> <body> <IntlayerProvider locale={locale}>{children}</IntlayerProvider> </body> </html> ); }; export default RootLayout;์ฝํ ์ธ ์ ์ธํ๊ธฐ
๋ฒ์ญ์ ์ ์ฅํ๊ธฐ ์ํด ์ฝํ ์ธ ์ ์ธ์ ์์ฑํ๊ณ ๊ด๋ฆฌํ์ธ์:
src/app/[locale]/page.content.ts์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import { t, type Dictionary } from "intlayer"; const pageContent = { key: "page", content: { getStarted: { main: t({ en: "Get started by editing", fr: "Commencez par รฉditer", es: "Comience por editar", }), pageLink: "src/app/page.tsx", }, }, } satisfies Dictionary; export default pageContent;์ฝํ ์ธ ์ ์ธ์ ์ ํ๋ฆฌ์ผ์ด์ ๋ด ์ด๋์์๋ ์ ์ํ ์ ์์ผ๋ฉฐ,
contentDir๋๋ ํ ๋ฆฌ(๊ธฐ๋ณธ๊ฐ:./src)์ ํฌํจ๋๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ฝํ ์ธ ์ ์ธ ํ์ผ ํ์ฅ์(๊ธฐ๋ณธ๊ฐ:.content.{json,ts,tsx,js,jsx,mjs,cjs,md,mdx,yaml,yml})์ ์ผ์นํด์ผ ํฉ๋๋ค.์์ธํ ๋ด์ฉ์ ์ฝํ ์ธ ์ ์ธ ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ธ์.
์ฝ๋์์ ์ฝํ ์ธ ํ์ฉํ๊ธฐ
์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐ์์ ์ฝํ ์ธ ์ฌ์ ์ ์ ๊ทผํ์ธ์:
src/app/[locale]/page.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import type { FC } from "react"; import { ClientComponentExample } from "@components/ClientComponentExample"; import { ServerComponentExample } from "@components/ServerComponentExample"; import { type NextPageIntlayer, useIntlayer } from "next-intlayer"; const PageContent: FC = () => { const content = useIntlayer("page"); return ( <> <p>{content.getStarted.main}</p> <code>{content.getStarted.pageLink}</code> </> ); }; const Page: NextPageIntlayer = () => ( <> <PageContent /> <ServerComponentExample /> <ClientComponentExample /> </> ); export default Page;IntlayerProvider๋ locale ๋ ์ด์์์์ ํ ๋ฒ ๋ง์ดํธ๋ฉ๋๋ค. ์๋ฒ ๋ฐ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ ๋ชจ๋์ locale์ ์ ๊ณตํ๋ฏ๋ก ํ์ด์ง๊ฐ ๋ ์ด์ ์์ ์ ๋ํํ์ง ์์ต๋๋ค.- ์๋ฒ hooks๋ ๋ค์ ์์๋ก locale์ ํด๊ฒฐํฉ๋๋ค: ํธ์ถ ์ฌ์ดํธ์์ ์ ๋ฌ๋ locale, ์ ๊ณต์์ ์ํด ์๋๋ ์๋ฒ ์ปจํ
์คํธ, ์์ฒญ์ ์ํด ์ ๋ฌ๋ locale (Intlayer ํ๋ก์๊ฐ ์ค์ ํ
x-intlayer-localeํค๋, ๊ทธ ๋ค์ locale ์ฟ ํค). ๋ง์ง๋ง ๋จ๊ณ๋ ๋ ์ด์์์ด ๋ค์ ์คํ๋์ง ์๋ ํ์ด์ง ์ธ๊ทธ๋จผํธ๋ง ๋ค์ ๋ ๋๋งํ๋ ํด๋ผ์ด์ธํธ ์ธก ๋ค๋น๊ฒ์ด์ ์์ ์ฝํ ์ธ ๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ์ ์งํ๋ ๊ฒ์ ๋๋ค.
src/app/[locale]/page.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import type { FC } from "react"; import { ClientComponentExample } from "@components/ClientComponentExample"; import { ServerComponentExample } from "@components/ServerComponentExample"; import { type NextPageIntlayer } from "next-intlayer"; import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server"; const PageContent: FC = () => { const content = useIntlayer("page"); return ( <> <p>{content.getStarted.main}</p> <code>{content.getStarted.pageLink}</code> </> ); }; const Page: NextPageIntlayer = async ({ params }) => { const { locale } = await params; return ( <IntlayerServerProvider locale={locale}> <PageContent /> <ServerComponentExample /> <ClientComponentExample /> </IntlayerServerProvider> ); }; export default Page;IntlayerClientProvider๋ ํด๋ผ์ด์ธํธ ์ธก ์ปดํฌ๋ํธ์ ๋ก์ผ์ผ์ ์ ๊ณตํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. ๋ ์ด์์์ ํฌํจํ ๋ชจ๋ ์์ ์ปดํฌ๋ํธ์ ๋ฐฐ์นํ ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ Next.js๊ฐ ํ์ด์ง ๊ฐ์ ๋ ์ด์์ ์ฝ๋๋ฅผ ๊ณต์ ํ๊ธฐ ๋๋ฌธ์ ๋ ์ด์์์ ๋ฐฐ์นํ๋ ๊ฒ์ด ๊ถ์ฅ๋ฉ๋๋ค. ๋ ์ด์์์์IntlayerClientProvider๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฐ ํ์ด์ง๋ง๋ค ๋ค์ ์ด๊ธฐํํ๋ ๊ฒ์ ๋ฐฉ์งํ์ฌ ์ฑ๋ฅ์ ํฅ์์ํค๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฒด์์ ์ผ๊ด๋ ๋ก์ปฌ๋ผ์ด์ ์ด์ ์ปจํ ์คํธ๋ฅผ ์ ์งํ ์ ์์ต๋๋ค.IntlayerServerProvider๋ ์๋ฒ ์์ ์ปดํฌ๋ํธ์ ๋ก์ผ์ผ์ ์ ๊ณตํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. ๋ ์ด์์์์๋ ์ค์ ํ ์ ์์ต๋๋ค.๋ ์ด์์๊ณผ ํ์ด์ง๋ ๊ณตํต ์๋ฒ ์ปจํ ์คํธ๋ฅผ ๊ณต์ ํ ์ ์์ต๋๋ค. ์๋ฒ ์ปจํ ์คํธ ์์คํ ์ ์์ฒญ๋ณ ๋ฐ์ดํฐ ์ ์ฅ์(React์ cache ๋ฉ์ปค๋์ฆ)๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ์ฌ ์ ํ๋ฆฌ์ผ์ด์ ์ ์๋ก ๋ค๋ฅธ ์ธ๊ทธ๋จผํธ๋ง๋ค ๊ฐ "์ปจํ ์คํธ"๊ฐ ๋ค์ ์์ฑ๋๊ธฐ ๋๋ฌธ์ ๋๋ค. ๊ณต๊ธ์๋ฅผ ๊ณต์ ๋ ์ด์์์ ๋ฐฐ์นํ๋ฉด ์ด ๊ฒฉ๋ฆฌ๊ฐ ๊นจ์ ธ ์๋ฒ ์ปจํ ์คํธ ๊ฐ์ด ์๋ฒ ์ปดํฌ๋ํธ์ ์ฌ๋ฐ๋ฅด๊ฒ ์ ๋ฌ๋์ง ์์ต๋๋ค.
src/components/ClientComponentExample.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
"use client"; import type { FC } from "react"; import { useIntlayer } from "next-intlayer"; export const ClientComponentExample: FC = () => { const content = useIntlayer("client-component-example"); // ๊ด๋ จ ์ฝํ ์ธ ์ ์ธ ์์ฑ return ( <div> <h2>{content.title}</h2> <p>{content.content}</p> </div> ); };src/components/ServerComponentExample.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import type { FC } from "react"; import { useIntlayer } from "next-intlayer"; export const ServerComponentExample: FC = () => { const content = useIntlayer("server-component-example"); // ๊ด๋ จ ์ฝํ ์ธ ์ ์ธ ์์ฑ return ( <div> <h2>{content.title}</h2> <p>{content.content}</p> </div> ); };next-intlayer๋ ๋ํ import ๊ฒฝ๋ก์ ๋๋ค:react-serverexport ์กฐ๊ฑด์ ์๋ฒ ์ปดํฌ๋ํธ์ ambient-locale ๊ตฌํ์ ์ ๊ณตํ๊ณ , ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ๋ context ๊ธฐ๋ฐ ๊ตฌํ์ ๋ฐ์ต๋๋ค. ๋์ผํ ํธ์ถ์ด ์์ชฝ์์ ๋ชจ๋ ์๋ํฉ๋๋ค.src/components/ServerComponentExample.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import type { FC } from "react"; import { useIntlayer } from "next-intlayer/server"; export const ServerComponentExample: FC = () => { const content = useIntlayer("server-component-example"); // ๊ด๋ จ ์ฝํ ์ธ ์ ์ธ ์์ฑ return ( <div> <h2>{content.title}</h2> <p>{content.content}</p> </div> ); };์ฝํ ์ธ ๋ฅผ
alt,title,href,aria-label๋ฑ๊ณผ ๊ฐ์string์์ฑ์์ ์ฌ์ฉํ๋ ค๋ฉด, ํจ์์ ๊ฐ์ ํธ์ถํด์ผ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด:tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
useIntlayerํ ์ ๋ํด ๋ ์์๋ณด๋ ค๋ฉด ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ธ์.๊ธฐ์กด ์ฑ์ด ์๋ค๋ฉด, Intlayer Compiler์ extract ๋ช ๋ น์ด๋ฅผ ์ฌ์ฉํ์ฌ ์์ฒ ๊ฐ์ ์ปดํฌ๋ํธ๋ฅผ ๋ช ์ด ๋ง์ ๋ณํํ ์ ์์ต๋๋ค.
๋ก์ผ์ผ ๊ฐ์ง๋ฅผ ์ํ ํ๋ก์ ๊ตฌ์ฑ
์ ํ์ฌํญ์ฌ์ฉ์์ ์ ํธ ๋ก์ผ์ผ์ ๊ฐ์งํ๊ธฐ ์ํด ํ๋ก์๋ฅผ ์ค์ ํฉ๋๋ค:
src/proxy.ts์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
export { intlayerProxy as proxy } from "next-intlayer/proxy"; export const config = { matcher: "/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\..*|_next).*)", };intlayerProxy๋ ์ฌ์ฉ์์ ์ ํธ ๋ก์ผ์ผ์ ๊ฐ์งํ์ฌ ๊ตฌ์ฑ์ ๋ช ์๋ ์ ์ ํ URL๋ก ๋ฆฌ๋๋ ์ ํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. ๋ํ ์ฌ์ฉ์์ ์ ํธ ๋ก์ผ์ผ์ ์ฟ ํค์ ์ ์ฅํ ์ ์๋๋ก ํฉ๋๋ค.Intlayer v9 ์ดํ, ์ด ๋ฏธ๋ค์จ์ด๋
routing.enableProxy์ต์ (true๊ฐ ๊ธฐ๋ณธ๊ฐ)์ ์ค์ํฉ๋๋ค. ์ด ํ์ผ์ ์ ๊ฑฐํ์ง ์๊ณ pass-through๋ก ์ ํํ๋ ค๋ฉด ์ค์ ์์routing.enableProxy: false๋ฅผ ์ค์ ํ์ธ์. v9 release notes๋ฅผ ์ฐธ์กฐํ์ธ์.์ฌ๋ฌ ํ๋ก์๋ฅผ ํจ๊ป ์ฐ๊ฒฐํด์ผ ํ๋ ๊ฒฝ์ฐ(์: ์ธ์ฆ ๋๋ ์ปค์คํ ํ๋ก์์ ํจ๊ป
intlayerProxy๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ), Intlayer๋ ์ด์ multipleProxies๋ผ๋ ํฌํผ๋ฅผ ์ ๊ณตํฉ๋๋ค.ts์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
๋ฉํ๋ฐ์ดํฐ์ ๊ตญ์ ํ
์ ํ์ฌํญํ์ด์ง ์ ๋ชฉ๊ณผ ๊ฐ์ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ๊ตญ์ ํํ๋ ค๋ ๊ฒฝ์ฐ, Next.js์์ ์ ๊ณตํ๋
generateMetadataํจ์๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๋ด๋ถ์์getIntlayerํจ์๋ก๋ถํฐ ์ฝํ ์ธ ๋ฅผ ๊ฐ์ ธ์ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ๋ฒ์ญํ ์ ์์ต๋๋ค.src/app/[locale]/metadata.content.ts์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import { type Dictionary, t } from "intlayer"; import { Metadata } from "next"; const metadataContent = { key: "page-metadata", content: { title: t({ en: "Create Next App", fr: "Crรฉer une application Next.js", es: "Crear una aplicaciรณn Next.js", }), description: t({ en: "Generated by create next app", fr: "Gรฉnรฉrรฉ par create next app", es: "Generado por create next app", }), }, } satisfies Dictionary<Metadata>; export default metadataContent;src/app/[locale]/layout.tsx or src/app/[locale]/page.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import { getIntlayer, getMultilingualUrls } from "intlayer"; import type { Metadata } from "next"; import type { LocalPromiseParams } from "next-intlayer"; export const generateMetadata = async ({ params, }: LocalPromiseParams): Promise<Metadata> => { const { locale } = await params; const metadata = getIntlayer("page-metadata", locale); /** * ๊ฐ ๋ก์ผ์ผ์ ๋ํ ๋ชจ๋ URL์ ํฌํจํ๋ ๊ฐ์ฒด๋ฅผ ์์ฑํฉ๋๋ค. * * ์์: * ```ts * getMultilingualUrls('/about'); * * // ๋ฐํ๊ฐ * // { * // en: '/about', * // fr: '/fr/about', * // es: '/es/about', * // } * ``` */ const multilingualUrls = getMultilingualUrls("/"); const localizedUrl = multilingualUrls[locale as keyof typeof multilingualUrls]; return { ...metadata, alternates: { canonical: localizedUrl, languages: { ...multilingualUrls, "x-default": "/" }, }, openGraph: { url: localizedUrl, }, }; }; // ... ๋๋จธ์ง ์ฝ๋next-intlayer์์ ๊ฐ์ ธ์จgetIntlayerํจ์๋ ์ฝํ ์ธ ๋ฅผIntlayerNode๋ก ๋ํํ์ฌ ์๊ฐ์ ํธ์ง๊ธฐ์์ ํตํฉ์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค. ๋ฐ๋ฉด,intlayer์์ ๊ฐ์ ธ์จgetIntlayerํจ์๋ ์ถ๊ฐ ์์ฑ ์์ด ์ฝํ ์ธ ๋ฅผ ์ง์ ๋ฐํํฉ๋๋ค.๋ฉํ๋ฐ์ดํฐ ์ต์ ํ์ ๋ํด ๋ ์์๋ณด๊ธฐ ๊ณต์ Next.js ๋ฌธ์์์.
sitemap.xml ๋ฐ robots.txt์ ๋ค๊ตญ์ดํ
์ ํ์ฌํญsitemap.xml๋ฐrobots.txt๋ฅผ ๋ค๊ตญ์ดํํ๋ ค๋ฉด Intlayer์์ ์ ๊ณตํ๋getMultilingualUrlsํจ์๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด ํจ์๋ ์ฌ์ดํธ๋งต์ ๋ค๊ตญ์ด URL์ ์์ฑํ ์ ์๊ฒ ํด์ค๋๋ค.src/app/sitemap.ts์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import { getMultilingualUrls } from "intlayer"; import type { MetadataRoute } from "next"; const sitemap = (): MetadataRoute.Sitemap => [ { url: "https://example.com", alternates: { languages: { ...getMultilingualUrls("https://example.com"), "x-default": "https://example.com", }, }, }, { url: "https://example.com/login", alternates: { languages: { ...getMultilingualUrls("https://example.com/login"), "x-default": "https://example.com/login", }, }, }, { url: "https://example.com/register", alternates: { languages: { ...getMultilingualUrls("https://example.com/register"), "x-default": "https://example.com/register", }, }, }, ]; export default sitemap;src/app/robots.ts์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
import type { MetadataRoute } from "next"; import { getMultilingualUrls } from "intlayer"; const getAllMultilingualUrls = (urls: string[]) => urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]); const robots = (): MetadataRoute.Robots => ({ rules: { userAgent: "*", allow: ["/"], disallow: getAllMultilingualUrls(["/login", "/register"]), }, host: "https://example.com", sitemap: `https://example.com/sitemap.xml`, }); export default robots;์ฌ์ดํธ๋งต ์ต์ ํ์ ๋ํด ๋ ์์๋ณด๊ธฐ ๊ณต์ Next.js ๋ฌธ์์์. robots.txt ์ต์ ํ์ ๋ํด ๋ ์์๋ณด๊ธฐ ๊ณต์ Next.js ๋ฌธ์์์.
์ฝํ ์ธ ์ ์ธ์ด ๋ณ๊ฒฝํ๊ธฐ
์ ํ์ฌํญNext.js์์ ์ฝํ ์ธ ์ ์ธ์ด๋ฅผ ๋ณ๊ฒฝํ๋ ค๋ฉด, ๊ถ์ฅ๋๋ ๋ฐฉ๋ฒ์
Link์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์๋ฅผ ์ ์ ํ ํ์งํ๋ ํ์ด์ง๋ก ๋ฆฌ๋๋ ์ ํ๋ ๊ฒ์ ๋๋ค.Link์ปดํฌ๋ํธ๋ ํ์ด์ง์ ์ฌ์ ๋ก๋ฉ(prefetching)์ ๊ฐ๋ฅํ๊ฒ ํ์ฌ ์ ์ฒด ํ์ด์ง ์๋ก๊ณ ์นจ์ ๋ฐฉ์งํ๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค.src/components/LocaleSwitcher.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
"use client"; import type { FC } from "react"; import { Locales, getHTMLTextDir, getLocaleName, getLocalizedUrl, } from "intlayer"; import { useLocale } from "next-intlayer"; import Link from "next/link"; export const LocaleSwitcher: FC = () => { const { locale, pathWithoutLocale, availableLocales, setLocale } = useLocale(); return ( <div> <button popoverTarget="localePopover">{getLocaleName(locale)}</button> <div id="localePopover" popover="auto"> {availableLocales.map((localeItem) => ( <Link href={getLocalizedUrl(pathWithoutLocale, localeItem)} key={localeItem} aria-current={locale === localeItem ? "page" : undefined} onClick={() => setLocale(localeItem)} replace // "๋ค๋ก ๊ฐ๊ธฐ" ๋ธ๋ผ์ฐ์ ๋ฒํผ์ด ์ด์ ํ์ด์ง๋ก ๋ฆฌ๋๋ ์ ๋๋๋ก ๋ณด์ฅํฉ๋๋ค. > <span> {/* ๋ก์ผ์ผ - ์: FR */} {localeItem} </span> <span> {/* ํด๋น ๋ก์ผ์ผ์ ์ธ์ด - ์: Franรงais */} {getLocaleName(localeItem, locale)} </span> <span dir={getHTMLTextDir(localeItem)} lang={localeItem}> {/* ํ์ฌ ๋ก์ผ์ผ์์์ ์ธ์ด - ์: ํ์ฌ ๋ก์ผ์ผ์ด Locales.SPANISH์ผ ๋ Francรฉs */} {getLocaleName(localeItem)} </span> <span dir="ltr" lang={Locales.ENGLISH}> {/* ์์ด๋ก ๋ ์ธ์ด๋ช - ์: French */} {getLocaleName(localeItem, Locales.ENGLISH)} </span> </Link> ))} </div> </div> ); };๋์์ผ๋ก
useLocaleํ ์์ ์ ๊ณตํ๋setLocaleํจ์๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด ํจ์๋ ํ์ด์ง ์ฌ์ ๋ก๋ฉ(prefetching)์ ํ์ฉํ์ง ์์ต๋๋ค. ์์ธํ ๋ด์ฉ์useLocaleํ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์ธ์.๋ํ
onLocaleChange์ต์ ์ ํจ์๋ฅผ ์ค์ ํ์ฌ ๋ก์ผ์ผ์ด ๋ณ๊ฒฝ๋ ๋ ์ฌ์ฉ์ ์ ์ ํจ์๋ฅผ ์คํํ ์ ์์ต๋๋ค.src/components/LocaleSwitcher.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
๋ฌธ์ ์ฐธ๊ณ :
์ง์ญํ๋ ๋งํฌ ์ปดํฌ๋ํธ ์์ฑํ๊ธฐ
์ ํ์ฌํญ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ด๋น๊ฒ์ด์ ์ด ํ์ฌ ๋ก์ผ์ผ์ ์ค์ํ๋๋ก ํ๋ ค๋ฉด, ์ปค์คํ
Link์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค. ์ด ์ปดํฌ๋ํธ๋ ๋ด๋ถ URL์ ํ์ฌ ์ธ์ด๋ฅผ ์๋์ผ๋ก ์ ๋์ฌ๋ก ๋ถ์ฌ์ค๋๋ค. ์๋ฅผ ๋ค์ด, ํ๋์ค์ด ์ฌ์ฉ์๊ฐ "About" ํ์ด์ง๋ก ๊ฐ๋ ๋งํฌ๋ฅผ ํด๋ฆญํ๋ฉด/about๋์/fr/about๋ก ๋ฆฌ๋๋ ์ ๋ฉ๋๋ค.์ด ๋์์ ์ฌ๋ฌ ๊ฐ์ง ์ด์ ๋ก ์ ์ฉํฉ๋๋ค:
- SEO ๋ฐ ์ฌ์ฉ์ ๊ฒฝํ: ๋ก์ปฌ๋ผ์ด์ฆ๋ URL์ ๊ฒ์ ์์ง์ด ์ธ์ด๋ณ ํ์ด์ง๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ์์ธํ๋๋ก ๋์์ฃผ๋ฉฐ, ์ฌ์ฉ์์๊ฒ ์ ํธํ๋ ์ธ์ด๋ก ๋ ์ฝํ ์ธ ๋ฅผ ์ ๊ณตํฉ๋๋ค.
- ์ผ๊ด์ฑ: ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐ์ ๊ฑธ์ณ ๋ก์ปฌ๋ผ์ด์ฆ๋ ๋งํฌ๋ฅผ ์ฌ์ฉํจ์ผ๋ก์จ ๋ด๋น๊ฒ์ด์ ์ด ํ์ฌ ๋ก์ผ์ผ ๋ด์์ ์ ์ง๋์ด ์์์น ๋ชปํ ์ธ์ด ์ ํ์ ๋ฐฉ์งํฉ๋๋ค.
- ์ ์ง๋ณด์์ฑ: ๋ก์ปฌ๋ผ์ด์ ์ด์ ๋ก์ง์ ๋จ์ผ ์ปดํฌ๋ํธ์ ์ค์ ์ง์คํํ๋ฉด URL ๊ด๋ฆฌ๋ฅผ ๋จ์ํํ์ฌ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ฑ์ฅํจ์ ๋ฐ๋ผ ์ฝ๋๋ฒ ์ด์ค๋ฅผ ๋ ์ฝ๊ฒ ์ ์ง๋ณด์ํ๊ณ ํ์ฅํ ์ ์์ต๋๋ค.
์๋๋ TypeScript๋ก ๊ตฌํํ ๋ก์ปฌ๋ผ์ด์ฆ๋
Link์ปดํฌ๋ํธ์ ๋๋ค:src/components/Link.tsx์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
"use client"; import { getLocalizedUrl } from "intlayer"; import NextLink, { type LinkProps as NextLinkProps } from "next/link"; import { useLocale } from "next-intlayer"; import type { PropsWithChildren, FC } from "react"; /** * ์ฃผ์ด์ง URL์ด ์ธ๋ถ ๋งํฌ์ธ์ง ํ์ธํ๋ ์ ํธ๋ฆฌํฐ ํจ์์ ๋๋ค. * URL์ด http:// ๋๋ https://๋ก ์์ํ๋ฉด ์ธ๋ถ ๋งํฌ๋ก ๊ฐ์ฃผํฉ๋๋ค. */ export const checkIsExternalLink = (href?: string): boolean => /^https?:\/\//.test(href ?? ""); /** * ํ์ฌ ๋ก์ผ์ผ์ ๋ฐ๋ผ href ์์ฑ์ ์กฐ์ ํ๋ ์ปค์คํ Link ์ปดํฌ๋ํธ์ ๋๋ค. * ๋ด๋ถ ๋งํฌ์ ๊ฒฝ์ฐ, `getLocalizedUrl`์ ์ฌ์ฉํ์ฌ URL ์์ ๋ก์ผ์ผ์ ๋ถ์ ๋๋ค (์: /fr/about). * ์ด๋ฅผ ํตํด ๋ด๋น๊ฒ์ด์ ์ด ๋์ผํ ๋ก์ผ์ผ ์ปจํ ์คํธ ๋ด์์ ์ด๋ฃจ์ด์ง๋๋ก ๋ณด์ฅํฉ๋๋ค. */ export const Link: FC<PropsWithChildren<NextLinkProps>> = ({ href, children, ...props }) => { const { locale } = useLocale(); const isExternalLink = checkIsExternalLink(href.toString()); // ๋ง์ฝ ๋งํฌ๊ฐ ๋ด๋ถ ๋งํฌ์ด๊ณ ์ ํจํ href๊ฐ ์ ๊ณต๋์๋ค๋ฉด, ์ง์ญํ๋ URL์ ๊ฐ์ ธ์ต๋๋ค. const hrefI18n: NextLinkProps["href"] = href && !isExternalLink ? getLocalizedUrl(href.toString(), locale) : href; return ( <NextLink href={hrefI18n} {...props}> {children} </NextLink> ); };์๋ ์๋ฆฌ
์ธ๋ถ ๋งํฌ ๊ฐ์ง:
์ ํธ๋ฆฌํฐ ํจ์checkIsExternalLink๋ URL์ด ์ธ๋ถ ๋งํฌ์ธ์ง ํ์ธํฉ๋๋ค. ์ธ๋ถ ๋งํฌ๋ ํ์งํ๊ฐ ํ์ํ์ง ์์ผ๋ฏ๋ก ๋ณ๊ฒฝ๋์ง ์์ ์ํ๋ก ์ ์ง๋ฉ๋๋ค.ํ์ฌ ๋ก์ผ์ผ ๊ฐ์ ธ์ค๊ธฐ:
useLocaleํ ์ ํ์ฌ ๋ก์ผ์ผ(์: ํ๋์ค์ด์ ๊ฒฝ์ฐfr)์ ์ ๊ณตํฉ๋๋ค.URL ํ์งํ:
๋ด๋ถ ๋งํฌ(์ฆ, ์ธ๋ถ ๋งํฌ๊ฐ ์๋ ๋งํฌ)์ ๊ฒฝ์ฐ,getLocalizedUrl์ ์ฌ์ฉํ์ฌ URL ์์ ํ์ฌ ๋ก์ผ์ผ์ ์๋์ผ๋ก ๋ถ์ ๋๋ค. ์ฆ, ์ฌ์ฉ์๊ฐ ํ๋์ค์ด ํ๊ฒฝ์ ์๋ ๊ฒฝ์ฐ,href๋ก/about์ ์ ๋ฌํ๋ฉด/fr/about์ผ๋ก ๋ณํ๋ฉ๋๋ค.๋งํฌ ๋ฐํ:
์ปดํฌ๋ํธ๋ ํ์งํ๋ URL์ ๊ฐ์ง<a>์์๋ฅผ ๋ฐํํ์ฌ ๋ด๋น๊ฒ์ด์ ์ด ๋ก์ผ์ผ๊ณผ ์ผ์นํ๋๋ก ๋ณด์ฅํฉ๋๋ค.
์ด
Link์ปดํฌ๋ํธ๋ฅผ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐ์ ํตํฉํจ์ผ๋ก์จ, ์ผ๊ด์ฑ ์๊ณ ์ธ์ด๋ฅผ ์ธ์ํ๋ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ์งํ๋ ๋์์ ํฅ์๋ SEO ๋ฐ ์ฌ์ฉ์ฑ ์ด์ ์ ๋๋ฆด ์ ์์ต๋๋ค.Server Actions์์ ํ์ฌ ๋ก์ผ์ผ ๊ฐ์ ธ์ค๊ธฐ
์ ํ์ฌํญServer Action ๋ด๋ถ์์ ํ์ฑ ๋ก์ผ์ผ์ด ํ์ํ ๊ฒฝ์ฐ(์: ์ด๋ฉ์ผ ํ์งํ ๋๋ ๋ก์ผ์ผ ์ธ์ ๋ก์ง ์คํ),
next-intlayer/server์์getLocale์ ํธ์ถํ์ญ์์ค:src/app/actions/getLocale.ts์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
getLocaleํจ์๋ ์ฌ์ฉ์์ ๋ก์ผ์ผ์ ๊ฒฐ์ ํ๊ธฐ ์ํด ๊ณ๋จ์ ์ ๋ต์ ๋ฐ๋ฆ ๋๋ค:- ๋จผ์ , ํ๋ก์์ ์ํด ์ค์ ๋์์ ์ ์๋ ๋ก์ผ์ผ ๊ฐ์ ์์ฒญ ํค๋์์ ํ์ธํฉ๋๋ค
- ํค๋์์ ๋ก์ผ์ผ์ ์ฐพ์ ์ ์์ผ๋ฉด ์ฟ ํค์ ์ ์ฅ๋ ๋ก์ผ์ผ์ ์ฐพ์ต๋๋ค
- ์ฟ ํค๊ฐ ๋ฐ๊ฒฌ๋์ง ์์ผ๋ฉด ๋ธ๋ผ์ฐ์ ์ค์ ์์ ์ฌ์ฉ์๊ฐ ์ ํธํ๋ ์ธ์ด ๊ฐ์ง๋ฅผ ์๋ํฉ๋๋ค
- ๋ง์ง๋ง ์๋จ์ผ๋ก ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ฑ๋ ๊ธฐ๋ณธ ๋ก์ผ์ผ๋ก ๋์ฒด๋ฉ๋๋ค
์ด๋ ์ฌ์ฉ ๊ฐ๋ฅํ ์ปจํ ์คํธ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๊ฐ์ฅ ์ ์ ํ ๋ก์ผ์ผ์ด ์ ํ๋๋๋ก ํฉ๋๋ค.
๋ฒ๋ค ํฌ๊ธฐ ์ต์ ํ
์ ํ์ฌํญnext-intlayer๋ฅผ ์ฌ์ฉํ ๋, ์ฌ์ (dictionary)์ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ชจ๋ ํ์ด์ง์ ๋ฒ๋ค์ ํฌํจ๋ฉ๋๋ค. ๋ฒ๋ค ํฌ๊ธฐ๋ฅผ ์ต์ ํํ๊ธฐ ์ํด Intlayer๋ ๋งคํฌ๋ก๋ฅผ ์ฌ์ฉํ์ฌuseIntlayerํธ์ถ์ ์ง๋ฅ์ ์ผ๋ก ๋์ฒดํ๋ ์ ํ์ SWC ํ๋ฌ๊ทธ์ธ์ ์ ๊ณตํฉ๋๋ค. ์ด๋ฅผ ํตํด ์ฌ์ ์ ์ค์ ๋ก ์ฌ์ฉํ๋ ํ์ด์ง์ ๋ฒ๋ค์๋ง ํฌํจ๋ฉ๋๋ค.์ด ์ต์ ํ๋ฅผ ํ์ฑํํ๋ ค๋ฉด
@intlayer/swcํจํค์ง๋ฅผ ์ค์นํ์ธ์. ์ค์น๊ฐ ์๋ฃ๋๋ฉดnext-intlayer๊ฐ ์๋์ผ๋ก ํ๋ฌ๊ทธ์ธ์ ๊ฐ์งํ๊ณ ์ฌ์ฉํฉ๋๋ค:bash์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
์ฐธ๊ณ : ์ด ์ต์ ํ๋ Next.js 13 ์ด์์์๋ง ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ฐธ๊ณ : ์ด ํจํค์ง๋ SWC ํ๋ฌ๊ทธ์ธ์ด Next.js์์ ์์ง ์คํ์ ์ด๊ธฐ ๋๋ฌธ์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ค์น๋์ง ์์ต๋๋ค. ํฅํ ๋ณ๊ฒฝ๋ ์ ์์ต๋๋ค.
์ฐธ๊ณ :
dictionary์ค์ ์์ ์ต์ ์importMode: 'dynamic'๋๋importMode: 'fetch'๋ก ์ค์ ํ๋ฉด Suspense์ ์์กดํ๊ฒ ๋๋ฏ๋ก,useIntlayerํธ์ถ์Suspense๊ฒฝ๊ณ๋ก ๊ฐ์ธ์ผ ํฉ๋๋ค. ์ฆ, Page / Layout ์ปดํฌ๋ํธ์ ์ต์์ ๋ ๋ฒจ์์useIntlayer๋ฅผ ์ง์ ์ฌ์ฉํ ์ ์์ต๋๋ค.์ปดํฌ๋ํธ ์ฝํ ์ธ ์ถ์ถ
์ ํ์ฌํญ๊ธฐ์กด ์ฝ๋๋ฒ ์ด์ค๊ฐ ์๋ ๊ฒฝ์ฐ ์์ฒ ๊ฐ์ ํ์ผ์ ๋ณํํ๋ ๋ฐ ์๊ฐ์ด ๋ง์ด ๊ฑธ๋ฆด ์ ์์ต๋๋ค.
์ด ํ๋ก์ธ์ค๋ฅผ ์ฉ์ดํ๊ฒ ํ๊ธฐ ์ํด 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์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
bash์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
babel.config.js์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
bash์ฝ๋ ๋ณต์ฌ์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
Turbopack์์ ์ฌ์ ๋ณ๊ฒฝ ์ฌํญ ๊ฐ์
Intlayer v9.5๋ถํฐ withIntlayer๊ฐ content watcher๋ฅผ ์์ฒด์ ์ผ๋ก ์์ํ๋ฏ๋ก ์ถ๊ฐ ์ค์ ์ด ํ์ํ์ง ์์ต๋๋ค.
next dev --turbopack ๋ช
๋ น์ ์ฌ์ฉํ์ฌ ๊ฐ๋ฐ ์๋ฒ๋ก Turbopack์ ์ฌ์ฉํ ๋, ๊ธฐ๋ณธ์ ์ผ๋ก ์ฌ์ ๋ณ๊ฒฝ ์ฌํญ์ด ์๋์ผ๋ก ๊ฐ์ง๋์ง ์์ต๋๋ค.
์ด ์ ํ์ Turbopack์ด ์ฝํ
์ธ ํ์ผ์ ๋ณ๊ฒฝ ์ฌํญ์ ๋ชจ๋ํฐ๋งํ๊ธฐ ์ํด webpack ํ๋ฌ๊ทธ์ธ์ ๋ณ๋ ฌ๋ก ์คํํ ์ ์๊ธฐ ๋๋ฌธ์ ๋ฐ์ํฉ๋๋ค. ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ค๋ฉด intlayer watch ๋ช
๋ น์ ์ฌ์ฉํ์ฌ ๊ฐ๋ฐ ์๋ฒ์ Intlayer ๋น๋ ๊ฐ์์๋ฅผ ๋์์ ์คํํด์ผ ํฉ๋๋ค.
์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
next-intlayer@<=6.x.x๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ, Next.js 16 ์ ํ๋ฆฌ์ผ์ด์
์ด Turbopack์์ ์ฌ๋ฐ๋ฅด๊ฒ ์๋ํ๋๋ก --turbopack ํ๋๊ทธ๋ฅผ ์ ์งํด์ผ ํฉ๋๋ค. ์ด ์ ํ์ ํผํ๊ธฐ ์ํด next-intlayer@>=7.x.x๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค.
TypeScript ๊ตฌ์ฑ
Intlayer๋ ๋ชจ๋ ์ฆ๊ฐ(module augmentation)์ ์ฌ์ฉํ์ฌ TypeScript์ ์ด์ ์ ํ์ฉํ๊ณ ์ฝ๋๋ฒ ์ด์ค๋ฅผ ๋์ฑ ๊ฒฌ๊ณ ํ๊ฒ ๋ง๋ญ๋๋ค.


TypeScript ๊ตฌ์ฑ์ ์๋ ์์ฑ๋ ํ์ ์ด ํฌํจ๋์ด ์๋์ง ํ์ธํ์ธ์.
์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
Git ์ค์
Intlayer๊ฐ ์์ฑํ ํ์ผ๋ค์ Git ์ ์ฅ์์ ์ปค๋ฐํ์ง ์๋๋ก ๋ฌด์ํ๋ ๊ฒ์ด ๊ถ์ฅ๋ฉ๋๋ค.
์ด๋ฅผ ์ํด .gitignore ํ์ผ์ ๋ค์ ์ง์๋ฌธ์ ์ถ๊ฐํ ์ ์์ต๋๋ค:
์ฝ๋๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
VS Code ํ์ฅ
Intlayer์ ํจ๊ป ๊ฐ๋ฐ ๊ฒฝํ์ ํฅ์์ํค๊ธฐ ์ํด ๊ณต์ Intlayer VS Code ํ์ฅ์ ์ค์นํ ์ ์์ต๋๋ค.
์ด ํ์ฅ์ ๋ค์ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค:
- ๋ฒ์ญ ํค์ ๋ํ ์๋ ์์ฑ.
- ์ค์๊ฐ ์ค๋ฅ ๊ฐ์ง๋ก ๋๋ฝ๋ ๋ฒ์ญ์ ํ์ธํ ์ ์์ต๋๋ค.
- ์ธ๋ผ์ธ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ก ๋ฒ์ญ๋ ๋ด์ฉ์ ๋ฐ๋ก ํ์ธํ ์ ์์ต๋๋ค.
- ๋น ๋ฅธ ์์ ์ผ๋ก ๋ฒ์ญ์ ์ฝ๊ฒ ์์ฑํ๊ณ ์ ๋ฐ์ดํธํ ์ ์์ต๋๋ค.
ํ์ฅ ๊ธฐ๋ฅ ์ฌ์ฉ ๋ฐฉ๋ฒ์ ๋ํ ์์ธํ ๋ด์ฉ์ Intlayer VS Code ํ์ฅ ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ธ์.
๋ ๋์๊ฐ๊ธฐ
๋ ๋์๊ฐ๋ ค๋ฉด ๋น์ฃผ์ผ ์๋ํฐ๋ฅผ ๊ตฌํํ๊ฑฐ๋ CMS๋ฅผ ์ฌ์ฉํ์ฌ ์ฝํ ์ธ ๋ฅผ ์ธ๋ถํํ ์ ์์ต๋๋ค.
์์ฃผ ๋ฌป๋ ์ง๋ฌธ
Next.js๋ next.config.js์ i18n ํ๋๊ฐ App Router์ ๋ ์ด์ ์ ์ฉ๋์ง ์์ผ๋ฏ๋ก ๋ด์ฅ ๋ฉ์์ง ๊ณ์ธต์ด ์์ผ๋ฉฐ, ๋ฐ๋ผ์ ์ง์ญํ ๊ณ์ธต์ ํญ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ํ์ ๋ฌธ์ ์
๋๋ค:
next-intl,i18next/next-i18next๋ฐreact-intl: ๋ค์์คํ์ด์ค๋ณ๋ก ๋ก๋๋๋ JSON ๋๋ ICU ๋ฉ์์ง ์นดํ๋ก๊ทธ ๊ธฐ๋ฐ์ ์ ํต์ ์ธ ์ต์ ์ ๋๋ค.Lingui: ๋น๋ ํ์์ ์ปดํ์ผ๋๋ ICU ๋ฉ์์ง๋ฅผ ์ฌ์ฉํ๋ ์ถ์ถ ๊ธฐ๋ฐ ์๋ฃจ์ ์ ๋๋ค.Intlayer: ๊ฐ์ฅ ์ง๋ณด๋ ์๋ฃจ์ ์ ๋๋ค. ์ฝ๋๋ฒ ์ด์ค ์ด๋์๋ ์ฝํ ์ธ ๋ฅผ ์ ์ธํ ์ ์์ผ๋ฉฐ(๊ฐ ์ปดํฌ๋ํธ ์ ๋๋ ์ค์ ์ง์ค์), ๋น๋ ํ์์ ์ปดํฌ๋ํธ๋ณ ์ฌ์ ์ผ๋ก ์ปดํ์ผ๋๊ณ , ์์ ํ ํ์ ์์ ์ฑ์ ์ ๊ณตํ๋ฉฐ AI ๋ฒ์ญ, ๋น์ฃผ์ผ ์๋ํฐ ๋ฐ CMS๋ฅผ ํฌํจํฉ๋๋ค.
์ค์ ์ ์ธ ์ฐจ์ด๋ ๋ธ๋ผ์ฐ์ ์ ๋๋ฌํ๋ ๋ฐ์ดํฐ์ ๋๋ค. ๋ค์์คํ์ด์ค ๊ธฐ๋ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ํ์ด์ง์ ์ ์ฒด JSON ์นดํ๋ก๊ทธ๋ฅผ ์ ์กํ์ง๋ง, Intlayer๋ ๋ ๋๋ง๋ ์ปดํฌ๋ํธ๊ฐ ์ฌ์ฉํ๋ ์ฝํ ์ธ ๋ง ์ ์กํ๋ฏ๋ก ๋ฒ๋ค ๋ฐ ํ์ด์ง ํฌ๊ธฐ๋ฅผ ์ต๋ 50%๊น์ง ์ค์ฌ์ค๋๋ค. ์ Intlayer์ธ๊ฐ์ Next.js i18n ๋ฒค์น๋งํฌ๋ฅผ ์ฐธ์กฐํ์ธ์.
๋ค์์คํ์ด์ค ๊ธฐ๋ฐ ์ค์ ๋ณด๋ค ํจ์ฌ ์ ์ต๋๋ค. ํ์ด์ง๋ ๋ ๋๋งํ์ง ์๋ ์ธ์ด์ ์นดํ๋ก๊ทธ๋ฅผ ์ ๋ ๋ค์ด๋ก๋ํ์ง ์๊ธฐ ๋๋ฌธ์
๋๋ค. Server Components๋ ์๋ฒ์์ ์ง์ ์ฝํ
์ธ ๋ฅผ ํ์ธํ๋ฉฐ, ๋น๋ ํ์ ์ปดํ์ผ๋ฌ๋ useIntlayer ํธ์ถ์ ์ปดํฌ๋ํธ๊ฐ ์ฌ์ฉํ๋ ์ ํํ ์ฌ์ ํญ๋ชฉ์ผ๋ก ๋์ฒดํ๋ฏ๋ก ์ฌ์ฉ๋์ง ์๋ ํค์ ์ธ์ด๋ ์ ๊ฑฐ๋๋ฉฐ, ๋์ ์ฌ์ ์ ํตํด ๋ก์ผ์ผ๋ณ๋ก ๋ถํ ๋ฉ๋๋ค. ์ผ๋ฐ์ ์ธ ๋์๋ค๊ณผ ๋น๊ตํ์ ๋ Intlayer๋ ๋ฒ๋ค ๋ฐ ํ์ด์ง ํฌ๊ธฐ๋ฅผ ์ต๋ 50%๊น์ง ์ค์ฌ์ค๋๋ค. ๋ฒ๋ค ์ต์ ํ์ ๋ฒค์น๋งํฌ๋ฅผ ์ฐธ์กฐํ์ธ์.
๋ค, ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ด ์์ต๋๋ค. next-intl ๋ง์ด๊ทธ๋ ์ด์
๊ฐ์ด๋ ๋๋ i18next ๋ง์ด๊ทธ๋ ์ด์
๊ฐ์ด๋๋ฅผ ํตํด ์ฝํ
์ธ ๋ฅผ ์ ์ง์ ์ผ๋ก ๋ง์ด๊ทธ๋ ์ด์
ํ ์ ์์ต๋๋ค. ๋๋ ํ์ฌ API๋ฅผ ์์ ํ ์ ์งํ ์๋ ์์ต๋๋ค. ํธํ ์ด๋ํฐ(compat adapters)๋ next-intl, react-i18next, react-intl๊ณผ ์์ ํ ๋์ผํ API๋ฅผ ๋
ธ์ถํ๋ฉด์ Intlayer ์ฌ์ ์์ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ๋ฏ๋ก, import ๊ตฌ๋ฌธ๋ง ๋ณ๊ฒฝํ๊ณ ์ปดํฌ๋ํธ ์ฝ๋๋ ๊ทธ๋๋ก ์ ์งํ ์ ์์ต๋๋ค.
๋ค. sync JSON ํ๋ฌ๊ทธ์ธ์ /messages/{locale}/{namespace}.json ํ์ผ์ ๋จ์ผ ์ง์ค ๊ณต๊ธ์(source of truth)์ผ๋ก ์ ์งํ๋ฉด์ ์๋ฐฉํฅ์ผ๋ก Intlayer ์ฌ์ ์ ์์ฑํฉ๋๋ค. sync PO ํ๋ฌ๊ทธ์ธ์ gettext ์นดํ๋ก๊ทธ์ ๋ํด ๋์ผํ ์์
์ ์ํํ๋ฉฐ, ๋ก์ผ์ผ๋ณ ํ์ผ์ ํตํด ๋ก์ผ์ผ์ ํ ํ์ผ์ ๋ชจ์ผ๋ ๋์ ์ธ์ด๋ณ๋ก ์ฝํ
์ธ ๋ฅผ ๋ถํ ํ ์๋ ์์ต๋๋ค.
์๋๋๋ค. npx intlayer extract๋ฅผ ์คํํ๋ฉด Intlayer๊ฐ ์ปดํฌ๋ํธ๋ฅผ ์ฝ๊ณ ์ฌ์ฉ์ ๋๋ฉด ๋ฌธ์์ด์ ์ถ์ถํ์ฌ ๊ฐ ์ปดํฌ๋ํธ ์์ .content ํ์ผ์ ์์ฑํ๋ฏ๋ก ์นดํ๋ก๊ทธ์ ์ผ์ผ์ด ๋ณต์ฌํ ํ์ ์์ด diff๋ง ๊ฒํ ํ๋ฉด ๋ฉ๋๋ค. ์ด ๊ฐ์ด๋์ 14๋จ๊ณ๋ฅผ ํ์ธํ์ธ์.
์์ ์๋ํ๋ ํ์ดํ๋ผ์ธ์ ์ํด Intlayer ์ปดํ์ผ๋ฌ๋ ๋น๋ ํ์์ JSX, TSX, Vue ๋ฐ Svelte ์์ค์์ ๋์ผํ ์์ ์ ์ํํ์ฌ ๋ณ๊ฒฝ๋ ๋๋ง๋ค ์ฌ์ ์ ์์ฑํ๊ณ HMR์ ํตํด ๋๊ธฐํํ๋ฏ๋ก ์๋์ผ๋ก ํค๋ฅผ ๊ด๋ฆฌํ ํ์๊ฐ ์์ต๋๋ค.
์ปดํ์ผ๋ฌ๋ฅผ ์ผ๊ธฐ ์ ์ ์์๋์ด์ผ ํ ๋ ๊ฐ์ง ์ ํ ์ฌํญ์ด ์์ต๋๋ค. ์ ์ ๋ถ์์ผ๋ก ์๋ํ๋ฏ๋ก API ์ค๋ฅ ์ฝ๋๋ CMS ํ๋์ ๊ฐ์ด ๋ฐํ์์๋ง ์กด์ฌํ๋ ๋ฌธ์์ด์ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค. ๋ํ ํฐ ์ฝ๋๋ฒ ์ด์ค์์ ๋ช ๊ฐ์ง ์ด๋
ธํ
์ด์
์ด ํ์ํ className="active" ๋๋ ์ํ ์ฝ๋์ ๊ฐ์ ์ ํ๋ฆฌ์ผ์ด์
๋ก์ง๊ณผ ์ฌ์ฉ์ ๋๋ฉด ํ
์คํธ๋ฅผ ๊ตฌ๋ถํด์ผ ํฉ๋๋ค. extract ๋ช
๋ น์ ์ฌ์ฉ์๊ฐ ์ง์ ์ ์ดํ ์ ์๋๋ก ํ์ฌ ๋ ๊ฐ์ง ๋ฌธ์ ๋ฅผ ๋ชจ๋ ๋ฐฉ์งํฉ๋๋ค.
5๊ฐ์ง ๋๊ตฌ๊ฐ ๋ชจ๋ ์ ํ ์ฌํญ์ผ๋ก ์ ๊ณต๋ฉ๋๋ค:
- VS Code ํ์ฅ ํ๋ก๊ทธ๋จ:
useIntlayerํค์์ ์ด๋ฅผ ์ ์ธํ ์ฝํ ์ธ ํ์ผ๋ก ๋ฐ๋ก ์ด๋ํ๊ณ , ์ปดํฌ๋ํธ์์ ์ฝํ ์ธ ๋ฅผ ์ถ์ถํ๋ฉฐ, ๋ช ๋ น ํ๋ ํธ๋ ์ ์ฉ Intlayer ํญ์์ build, fill, test, push, pull์ ์คํํ ์ ์์ต๋๋ค. - LSP ์๋ฒ: LSP๋ฅผ ์ง์ํ๋ ๋ชจ๋ ์๋ํฐ์์ ์ ์๋ก ์ด๋, ๋ชจ๋ ์ฐธ์กฐ ์ฐพ๊ธฐ, ๋ฒ์ญ ๊ฐ ๋ง์ฐ์ค ์ค๋ฒ ๋ฏธ๋ฆฌ๋ณด๊ธฐ, ํค ๋ฐ ํ๋ ์๋ ์์ฑ, ์ ์ธ๋์ง ์์ ํค์ ๋ํ ๊ฒฝ๊ณ ๋ฑ ๋์ผํ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค. ๋ํ
i18next,react-i18next,next-intl,use-intlํธ์ถ๋ ํด์ํ๋ฏ๋ก ๋ง์ด๊ทธ๋ ์ด์ ์ ์ ์ฉํฉ๋๋ค. - MCP ์๋ฒ: Cursor, VS Code, Claude Desktop, Claude Code, ChatGPT์ Intlayer ๋ฌธ์์ CLI๋ฅผ ๋
ธ์ถํ์ฌ AI ์ด์์คํดํธ๊ฐ ์ต์ ๋ฌธ์๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ ํํ ๋ต๋ณํ๊ณ
intlayer fill๋ฑ์ ๋ช ๋ น์ ์ง์ ์คํํ ์ ์๊ฒ ํฉ๋๋ค. - Agent Skills:
intlayer-config,intlayer-cli,intlayer-content๋ฐ ๊ฐ ํ๋ ์์ํฌ ์ ์ฉ ์คํฌ์ ํตํด AI ์์ด์ ํธ์๊ฒ ๋ผ์ฐํ ์ค์ ๊ณผ ์ฝํ ์ธ ๋ ธ๋ ํ์ ์ ํ์ต์ํต๋๋ค. - ESLint ํ๋ฌ๊ทธ์ธ:
no-raw-text๊ท์น์ผ๋ก ํ๋์ฝ๋ฉ๋ ๋ฌธ์์ด์ ํ์ํ๊ณ , ์ ์ ์ฌ์ ํค ๋ฐ ์ฌ์ฉ๋์ง ์๋ ์ฝํ ์ธ ์ ๋ํ ์ถ๊ฐ ๊ท์น์ ์ ๊ณตํฉ๋๋ค.
