작가:
    생성:2026-09-09마지막 업데이트:2026-09-27

    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 트리를 HTML Response로 스트리밍합니다.
    • 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 파이프라인에서 번역을 자동화할 수 있습니다.

    코드 우선 워크플로 외에도 Intlayer는 자체 호스팅 가능한 비주얼 에디터 및 원격 CMS를 제공하여 비개발자인 편집자와 번역가가 코드를 다시 배포하지 않고도 콘텐츠를 업데이트할 수 있습니다.

    단계별 가이드

    ide.intlayer.org
    intlayer-remix-3-template.vercel.app

    GitHub에서 애플리케이션 템플릿을 확인하세요.

    1. 종속성 설치

      원하는 패키지 관리자를 사용하여 intlayer, remix-intlayer 및 remix(버전 3)를 설치합니다:

      bash
      npm install intlayer remix-intlayer remix@next
      
      • 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 프레임워크 패키지입니다.
    2. Intlayer 구성

      아키텍처

      이 아키텍처에서 remix-intlayer의 intlayer() 미들웨어는 render() 미들웨어에 앞서 createRouter()에 등록됩니다. 라우터가 매칭되기 전에 로케일 접두사를 제거하므로 라우트는 :locale 세그먼트 없이 src/routes.ts에 한 번만 선언되며, 요청의 나머지 부분을 AsyncLocalStorage 스코프 내에서 실행하여 라우트 핸들러 및 remix/ui 뷰에서 인자 없이 useIntlayer / useLocale이 로케일을 읽을 수 있도록 합니다. 콘텐츠 선언은 src/의 뷰와 함께 배치됩니다:

      bash
      .
      ├── src
      │   ├── home.content.ts               # Home page content declaration
      │   ├── router.tsx                    # createRouter() with the intlayer() and render() middleware
      │   ├── routes.ts                     # Type-safe routes, declared once without locale segment
      │   ├── server.ts                     # fetch handler (Node.js, Bun, Deno, Cloudflare Workers)
      │   └── views
      │       ├── document.tsx              # HTML shell setting <html lang dir> from the locale
      │       └── home.tsx                  # Localized page using useIntlayer / useLocale
      ├── intlayer.config.ts
      ├── package.json
      └── tsconfig.json
      

      설정

      프로젝트 루트에 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;
      
      추가 구성 설정은 구성 문서를 참조하세요.
    3. 다국어 콘텐츠 선언

      .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 형식도 지원합니다. 콘텐츠 선언 문서를 참조하세요.
    4. Intlayer 사전 빌드

      사전 정의를 컴파일하여 TypeScript 타입 및 런타임 레지스트리를 생성합니다.

      bash
      npx intlayer build
      

      이 명령은 콘텐츠를 .intlayer 아티팩트 디렉터리로 컴파일하여 완벽한 TypeScript 자동 완성과 빠른 사전 조회를 제공합니다.

    5. Intlayer 미들웨어 추가

      Remix 3은 createRouter({ middleware: [...] })를 통해 구성 가능한 미들웨어 파이프라인을 제공합니다.

      remix-intlayer는 intlayer() 미들웨어를 제공합니다. 들어오는 각 요청에 대해 다음을 사용하여 로케일을 확인합니다:

      1. no-prefix를 제외한 모든 라우팅 모드의 URL: 경로 접두사(예: /ko 또는 /en) 또는 ?locale= 검색 매개변수.
      2. 클라이언트에 의해 저장된 로케일: 스토리지 쿠키(INTLAYER_LOCALE) 또는 커스텀 헤더(x-intlayer-locale).
      3. 표준 Accept-Language 협상(구성된 defaultLocale로 대체).

      결과는 locale, defaultLocale, availableLocales와 함께 Remix 요청 컨텍스트에 context.intlayer(또는 context.get(Intlayer))로 저장됩니다. 그런 다음 미들웨어는 해당 컨텍스트에 바인딩된 AsyncLocalStorage 범위 내에서 요청의 나머지 부분을 실행하므로 라우트 핸들러, 뷰, remix/ui 컴포넌트 모두에서 인수 없이 로케일을 읽을 수 있습니다:

      typescript
      import { useIntlayer, useLocale } from "remix-intlayer";
      
      // 미들웨어 이후 어디에서나
      const { locale, availableLocales } = useLocale();
      const { title } = useIntlayer("home");
      

      useIntlayer("home", "fr") 또는 useIntlayer("faq", { item: 2 })는 단일 호출에 대해 요청 로케일을 재정의하며, useDictionary(homeContent)는 키 대신 가져온 사전을 읽습니다. 요청 외부에서 훅은 기본 로케일로 대체됩니다.

      미들웨어는 서버 시작 시 Intlayer 사전도 준비하므로 intlayer build가 누락되어도 레지스트리가 비어 있지 않습니다.
      intlayer.config.ts에서 routing.enableProxy: false를 설정하면 로케일 해석만 유지하고 라우팅은 직접 처리할 수 있습니다. intlayer({ ignore })는 일치하는 요청(예: API 접두사)을 그대로 두며, intlayer({ isDevServer })는 기본 enableProxy 자동 모드에서 저장된 로케일이 리디렉션을 결정할지 여부를 제어합니다.
    6. 타입 안전한 라우트 정의

      remix/routes의 route()를 사용하여 애플리케이션 라우트를 정의합니다.

      src/routes.ts
      import { route } from "remix/routes";
      
      export const routes = route({
        // 기본 로케일 라우트
        home: "/",
      
        // 동적 :locale 세그먼트가 있는 현지화된 라우트
        localizedHome: "/:locale",
      });
      

      route()를 사용하면 애플리케이션 전반에서 타입 안전한 URL을 생성할 수 있습니다.

      typescript
      routes.home.href(); // "/"
      routes.localizedHome.href({ locale: "ko" }); // "/ko"
      
    7. 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 함수 모두에서 호출할 수 있습니다.
    8. 라우터 및 서버 연결

      Intlayer 미들웨어 옆에 remix/middleware/render의 render() 미들웨어를 추가합니다. 모든 요청에 context.render(node, init)을 설치하여 JSX 트리를 HTML Response로 스트리밍합니다(앞에 <!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);
        },
      };
      
    9. 번역 검사 및 자동 채우기

      Intlayer는 누락된 번역을 감사하고 AI를 사용하여 자동으로 채울 수 있는 CLI를 제공합니다.

      bash
      # 누락된 번역 확인
      npx intlayer test
      
      # AI를 사용하여 누락된 번역 채우기
      npx intlayer fill
      

    TypeScript 구성

    JSX가 remix/ui 런타임을 가리키도록 설정하고 tsconfig.json에 생성된 .intlayer 타입이 포함되어 있는지 확인하세요.

    tsconfig.json
    {
      "compilerOptions": {
        "moduleResolution": "Bundler",
        "module": "ESNext",
        "target": "ESNext",
        "jsx": "react-jsx",
        "jsxImportSource": "remix/ui",
        "skipLibCheck": true,
        "strict": true
      },
      "include": ["src/**/*", ".intlayer/**/*.ts"]
    }
    
    jsxImportSource: "remix/ui"는 <HomePage />가 React 대신 Remix의 createElement로 해석되도록 만듭니다.

    결론

    Remix 3와 Intlayer를 사용하면 개방형 웹 표준을 준수하는 가볍고 완전히 타입이 지정된 이식성 높은 스택을 확보할 수 있습니다. 간단한 마케팅 페이지부터 엣지에서 렌더링되는 전 세계 분산 서비스까지 원활하게 확장할 수 있습니다.