이 페이지와 원하는 AI 어시스턴트를 사용하여 문서를 요약합니다
버전 기록
- "remix-intlayer 미들웨어 및 훅 사용"v9.5.52026. 9. 19.
- "Remix 3 초기 문서"v9.5.02026. 9. 9.
이 페이지의 콘텐츠는 AI를 사용하여 번역되었습니다.
영어 원본 내용의 최신 버전을 보기이 문서를 개선할 아이디어가 있으시면 GitHub에 풀 리퀘스트를 제출하여 자유롭게 기여해 주세요.
문서에 대한 GitHub 링크문서의 Markdown을 클립보드에 복사
Intlayer를 사용하여 Remix 3 웹사이트 번역하기
이 가이드에서는 언어 인식 라우팅, 타입 안전한 콘텐츠 선언, 서버 렌더링 JSX 컴포넌트, Node.js, Bun, Deno 및 Cloudflare Workers 전반에 걸친 크로스 런타임 지원을 통해 Remix 3 애플리케이션에 Intlayer를 원활하게 통합하는 방법을 설명합니다.
Remix 3란 무엇인가요?
Remix 3는 완전히 웹 표준을 기반으로 구축된, 조합 가능하고 런타임에 구애받지 않는 웹 프레임워크로의 근본적인 아키텍처 전환을 의미합니다. 특정 번들러나 독점 서버 API에 종속되지 않고 단일 목적의 모듈형 패키지로 배포됩니다.
remix/fetch-router(또는remix/router): Fetch API(Request및Response)를 기반으로 구축된 가볍고 표준을 준수하는 라우팅.remix/ui: JSX 컴포넌트 모델(jsxImportSource: "remix/ui"). 컴포넌트는 Handle을 받아 렌더 함수를 반환하는 셋업 함수로, React와 유사하지만 상태는 순수 JavaScript 클로저에 유지됩니다.remix/middleware/render: 모든 요청에context.render(<Page />)를 설치하여 JSX 트리를 HTMLResponse로 스트리밍합니다.remix/node-fetch-server: Bun, Deno 및 엣지 런타임을 기본 지원하는 Node.js용 서버 어댑터.remix/cookie: 암호학적으로 안전한 쿠키 파싱 및 직렬화.
Intlayer와 remix-intlayer 패키지(로케일 미들웨어 및 Remix 요청 컨텍스트에 바인딩된 react-intlayer와 동일한 useIntlayer / useDictionary / useLocale 훅)를 결합하면 컴파일 타임 안전성, 자동화된 AI 번역, 오버헤드 없는 서버 렌더링 및 원활한 로케일 라우팅을 제공하는 완전한 국제화 시스템을 얻을 수 있습니다.
목차
다른 대안 대신 Intlayer를 선택해야 하는 이유
i18next나 맞춤형 번역 로더와 같은 기존 솔루션과 비교할 때 Intlayer는 최신 웹 아키텍처에 최적화된 통합 개발자 경험을 제공합니다.
Intlayer는 웹 표준(Request, Response, Headers, URL)과 원활하게 작동하도록 구축되었습니다. remix-intlayer는 가벼운 미들웨어로 Remix 3의 Fetch 라우터에 플러그인되어 URL 경로, 쿠키 또는 Accept-Language 헤더에서 로케일을 추출하고, 인수를 직접 전달하지 않고도 핸들러, 뷰, remix/ui 컴포넌트 등 요청의 나머지 부분에 로케일을 노출합니다.
느슨한 JSON 키와 런타임 키 누락 오류는 이제 잊으세요. Intlayer는 선언된 모든 언어에서 TypeScript 검사를 적용하여 번역이 누락되거나 잘못된 경우 빌드 시 경고를 표시합니다.
Remix 3는 서버에서 JSX 컴포넌트를 렌더링하고 HTML을 클라이언트로 스트리밍합니다. 요청된 언어에 대해 확인된 텍스트만 출력 스트림에 렌더링됩니다. 컴포넌트가 명시적으로 clientEntry로 표시되지 않는 한 클라이언트 하이드레이션 번들이나 무거운 번역 카탈로그가 필요하지 않습니다.
Intlayer는 콘텐츠 선언(.content.ts)을 라우트 로직과 같은 위치에 배치하여 대규모 언어 모델(LLM)에 필요한 토큰 컨텍스트를 줄입니다. intlayer fill 및 intlayer test와 같은 기본 CLI 명령을 사용하면 선택한 AI 공급자의 순수 비용으로 CI/CD 파이프라인에서 번역을 자동화할 수 있습니다.
단계별 가이드
GitHub에서 애플리케이션 템플릿을 확인하세요.
종속성 설치
원하는 패키지 관리자를 사용하여
intlayer,remix-intlayer및remix(버전 3)를 설치합니다:bash코드 복사코드를 클립보드에 복사
intlayer: 구성 관리, 사전 선언(t(),Dictionary), CLI 도구 및 런타임 인터프리터를 제공하는 핵심 국제화 엔진입니다.remix-intlayer: Remix 3 통합: 각 요청의 로케일을 확인하는intlayer()라우터 미들웨어와 이후 모든 곳에서 이를 읽는useIntlayer,useDictionary,useLocale훅.remix:remix/router,remix/routes,remix/ui,remix/middleware/render,remix/node-fetch-server를 내보내는 통합 Remix 3 프레임워크 패키지입니다.
Intlayer 구성
아키텍처
이 아키텍처에서
remix-intlayer의intlayer()미들웨어는render()미들웨어에 앞서createRouter()에 등록됩니다. 라우터가 매칭되기 전에 로케일 접두사를 제거하므로 라우트는:locale세그먼트 없이src/routes.ts에 한 번만 선언되며, 요청의 나머지 부분을AsyncLocalStorage스코프 내에서 실행하여 라우트 핸들러 및remix/ui뷰에서 인자 없이useIntlayer/useLocale이 로케일을 읽을 수 있도록 합니다. 콘텐츠 선언은src/의 뷰와 함께 배치됩니다:bash코드 복사코드를 클립보드에 복사
설정
프로젝트 루트에
intlayer.config.ts를 생성하여 지원 언어 및 국제화 설정을 선언합니다.intlayer.config.ts코드 복사코드를 클립보드에 복사
import { Locales, type IntlayerConfig } from "intlayer"; const config: IntlayerConfig = { internationalization: { locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH, Locales.KOREAN], defaultLocale: Locales.ENGLISH, }, }; export default config;추가 구성 설정은 구성 문서를 참조하세요.
다국어 콘텐츠 선언
.content.ts파일에 지역화된 콘텐츠를 선언합니다.src/home.content.ts코드 복사코드를 클립보드에 복사
import { t, type Dictionary } from "intlayer"; const homeContent = { key: "home", content: { title: t({ ko: "Remix 3에 오신 것을 환영합니다", en: "Welcome to Remix 3", fr: "Bienvenue sur Remix 3", es: "Bienvenido a Remix 3", }), description: t({ ko: "기본 i18n을 갖춘 웹 표준 기반의 조합 가능한 애플리케이션입니다.", en: "A composable, web-standard application with native i18n.", fr: "Une application composable basée sur les standards web avec i18n native.", es: "Una aplicación componible basada en estándares web con i18n nativa.", }), switchLanguage: t({ ko: "언어 변경:", en: "Switch language:", fr: "Changer de langue :", es: "Cambiar idioma:", }), }, } satisfies Dictionary; export default homeContent;Intlayer는 JSON, YAML 및 CommonJS 형식도 지원합니다. 콘텐츠 선언 문서를 참조하세요.
Intlayer 사전 빌드
사전 정의를 컴파일하여 TypeScript 타입 및 런타임 레지스트리를 생성합니다.
bash코드 복사코드를 클립보드에 복사
이 명령은 콘텐츠를
.intlayer아티팩트 디렉터리로 컴파일하여 완벽한 TypeScript 자동 완성과 빠른 사전 조회를 제공합니다.Intlayer 미들웨어 추가
Remix 3은
createRouter({ middleware: [...] })를 통해 구성 가능한 미들웨어 파이프라인을 제공합니다.remix-intlayer는intlayer()미들웨어를 제공합니다. 들어오는 각 요청에 대해 다음을 사용하여 로케일을 확인합니다:no-prefix를 제외한 모든 라우팅 모드의 URL: 경로 접두사(예:/ko또는/en) 또는?locale=검색 매개변수.- 클라이언트에 의해 저장된 로케일: 스토리지 쿠키(
INTLAYER_LOCALE) 또는 커스텀 헤더(x-intlayer-locale). - 표준
Accept-Language협상(구성된defaultLocale로 대체).
결과는
locale,defaultLocale,availableLocales와 함께 Remix 요청 컨텍스트에context.intlayer(또는context.get(Intlayer))로 저장됩니다. 그런 다음 미들웨어는 해당 컨텍스트에 바인딩된AsyncLocalStorage범위 내에서 요청의 나머지 부분을 실행하므로 라우트 핸들러, 뷰,remix/ui컴포넌트 모두에서 인수 없이 로케일을 읽을 수 있습니다:typescript코드 복사코드를 클립보드에 복사
useIntlayer("home", "fr")또는useIntlayer("faq", { item: 2 })는 단일 호출에 대해 요청 로케일을 재정의하며,useDictionary(homeContent)는 키 대신 가져온 사전을 읽습니다. 요청 외부에서 훅은 기본 로케일로 대체됩니다.미들웨어는 서버 시작 시 Intlayer 사전도 준비하므로
intlayer build가 누락되어도 레지스트리가 비어 있지 않습니다.intlayer.config.ts에서routing.enableProxy: false를 설정하면 로케일 해석만 유지하고 라우팅은 직접 처리할 수 있습니다.intlayer({ ignore })는 일치하는 요청(예: API 접두사)을 그대로 두며,intlayer({ isDevServer })는 기본enableProxy자동 모드에서 저장된 로케일이 리디렉션을 결정할지 여부를 제어합니다.타입 안전한 라우트 정의
remix/routes의route()를 사용하여 애플리케이션 라우트를 정의합니다.src/routes.ts코드 복사코드를 클립보드에 복사
import { route } from "remix/routes"; export const routes = route({ // 기본 로케일 라우트 home: "/", // 동적 :locale 세그먼트가 있는 현지화된 라우트 localizedHome: "/:locale", });route()를 사용하면 애플리케이션 전반에서 타입 안전한 URL을 생성할 수 있습니다.typescript코드 복사코드를 클립보드에 복사
JSX를 사용한 지역화된 페이지 렌더링
Remix 3는
remix/ui의 JSX 컴포넌트를 사용하여 UI를 렌더링합니다. 컴포넌트는Handle을 받아 렌더 함수를 반환하는 셋업 함수입니다. 셋업은 인스턴스당 한 번 실행되고, 렌더는 매 업데이트마다 실행되며, props는handle.props를 통해 읽습니다.미들웨어에서 확인된 로케일로부터
<html lang="..." dir="...">속성을 설정하는 공유Document셸로 시작합니다:src/views/document.tsx코드 복사코드를 클립보드에 복사
import { getHTMLTextDir } from "intlayer"; import { useLocale } from "remix-intlayer"; import type { Handle, RemixNode } from "remix/ui"; type DocumentProps = { title: string; children?: RemixNode; }; export const Document = (handle: Handle<DocumentProps>) => () => { const { title, children } = handle.props; const { locale } = useLocale(); return ( <html lang={locale} dir={getHTMLTextDir(locale)}> <head> <meta charSet="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>{title}</title> </head> <body>{children}</body> </html> ); };그런 다음 홈 페이지를 만듭니다.
useIntlayer로 현지화된 사전을 읽고 언어 전환기를 렌더링합니다:src/views/home.tsx코드 복사코드를 클립보드에 복사
import { getLocaleName, getLocalizedUrl, getPathWithoutLocale } from "intlayer"; import { useIntlayer, useLocale } from "remix-intlayer"; import { Document } from "./document"; export const HomePage = () => () => { const { locale, availableLocales } = useLocale(); const home = useIntlayer("home"); const pathWithoutLocale = getPathWithoutLocale(); return ( <Document title={home.title}> <header> <nav aria-label="Languages"> <span>{home.switchLanguage}</span> <ul> {availableLocales.map((localeItem) => { const isActive = localeItem === locale; return ( <li key={localeItem} class="p-1"> <a href={getLocalizedUrl(pathWithoutLocale, localeItem)} class={isActive ? "active" : undefined} aria-current={isActive ? "page" : undefined} > {getLocaleName(localeItem, locale)} </a> </li> ); })} </ul> </nav> </header> <main> <h1>{home.title}</h1> <p>{home.description}</p> </main> </Document> ); };Remix JSX는 React가 아닙니다:
class는 있는 그대로 작성되며(className도 허용됨), 재렌더링은handle.update()를 통해 명시적으로 트리거됩니다. 보간된 값은 자동으로 이스케이프됩니다. Intlayer 훅은 요청 범위를 읽는 일반 함수이므로 setup 함수나 render 함수 모두에서 호출할 수 있습니다.라우터 및 서버 연결
Intlayer 미들웨어 옆에
remix/middleware/render의render()미들웨어를 추가합니다. 모든 요청에context.render(node, init)을 설치하여 JSX 트리를 HTMLResponse로 스트리밍합니다(앞에<!DOCTYPE html>을 추가하고Content-Type헤더를 설정).src/router.tsx코드 복사코드를 클립보드에 복사
import { isDeclaredLocale } from "intlayer"; import { intlayer } from "remix-intlayer"; import { render } from "remix/middleware/render"; import { createRouter } from "remix/router"; import { routes } from "./routes"; import { HomePage } from "./views/home"; // 1. Initialize router with Intlayer + render middleware export const router = createRouter({ middleware: [intlayer(), render()], }); // 2. Map route handlers router.map(routes, { actions: { // Default locale route home(context) { return context.render(<HomePage />); }, // Localized route localizedHome(context) { if (!isDeclaredLocale(context.params.locale)) { return new Response("Not Found", { status: 404 }); } return context.render(<HomePage />); }, }, });context.render는 두 번째 인수로 선택적ResponseInit을 허용합니다(예:context.render(<NotFoundPage />, { status: 404 })). 확인된 로케일은 예를 들어Response.json페이로드를 생성하기 위해 핸들러에서context.intlayer.locale로 계속 접근할 수 있습니다.마지막으로 표준
fetch핸들러를 통해 라우터를 노출합니다. 동일한 라우터가 Node.js, Bun, Deno 및 Cloudflare Workers에서 실행됩니다.src/server.ts코드 복사코드를 클립보드에 복사
import * as http from "node:http"; import { createRequestListener } from "remix/node-fetch-server"; import { router } from "./router"; const PORT = Number(process.env.PORT || 3000); // Node.js const server = http.createServer( createRequestListener((request) => router.fetch(request)) ); server.listen(PORT, () => { console.log(`서버가 http://localhost:${PORT} 에서 실행 중입니다`); }); // Bun / Deno / Cloudflare Workers export default { port: PORT, fetch(request: Request) { return router.fetch(request); }, };번역 검사 및 자동 채우기
Intlayer는 누락된 번역을 감사하고 AI를 사용하여 자동으로 채울 수 있는 CLI를 제공합니다.
bash코드 복사코드를 클립보드에 복사
TypeScript 구성
JSX가 remix/ui 런타임을 가리키도록 설정하고 tsconfig.json에 생성된 .intlayer 타입이 포함되어 있는지 확인하세요.
코드를 클립보드에 복사
jsxImportSource: "remix/ui"는<HomePage />가 React 대신 Remix의createElement로 해석되도록 만듭니다.
결론
Remix 3와 Intlayer를 사용하면 개방형 웹 표준을 준수하는 가볍고 완전히 타입이 지정된 이식성 높은 스택을 확보할 수 있습니다. 간단한 마케팅 페이지부터 엣지에서 렌더링되는 전 세계 분산 서비스까지 원활하게 확장할 수 있습니다.
