이 페이지와 원하는 AI 어시스턴트를 사용하여 문서를 요약합니다
버전 기록
- "초기 버전"v9.5.102026. 9. 26.
이 페이지의 콘텐츠는 AI를 사용하여 번역되었습니다.
영어 원본 내용의 최신 버전을 보기이 문서를 개선할 아이디어가 있으시면 GitHub에 풀 리퀘스트를 제출하여 자유롭게 기여해 주세요.
문서에 대한 GitHub 링크문서의 Markdown을 클립보드에 복사
2026년에 Lingui를 사용하여 TanStack Start 애플리케이션 국제화하는 방법
목차
Lingui란 무엇인가요?
Lingui는 매크로와 메시지 추출(extraction)을 중심으로 구축된 i18n 라이브러리입니다. 컴포넌트 내에 원본 텍스트( t`Hello` , <Trans>Hello</Trans>)를 직접 작성하면, lingui extract가 모든 메시지를 카탈로그(기본값은 PO 파일)로 수집하고, 번역가가 이를 채우며, Vite 플러그인이 이를 컴팩트한 JavaScript로 컴파일합니다. 메시지는 ICU MessageFormat을 사용하므로 복수형과 select 구문이 지원됩니다.
TanStack Start에는 내장된 i18n 계층이 없으므로, 이 가이드에서는 처음부터 Lingui를 연결합니다:
@rolldown/plugin-babel을 통해 Babel로 컴파일되는 매크로 (@vitejs/plugin-reactv6 및 Vite 8에서 필요).- 선택적
{-$locale}세그먼트를 사용한 로케일 라우팅 (/about,/fr/about). - 로케일당 하나의 카탈로그를 필요 시(on demand) 로드하고 렌더링당 하나의
I18n인스턴스를 사용하여 동시 SSR 요청이 로케일을 공유하지 않도록 방지. - 완벽한 다국어 SEO: 번역된
<title>및 description, 표준 URL(canonical),x-default가 포함된hreflang, Open Graph 로케일, JSON-LD, 사이트맵,robots.txt, 사전 렌더링(pre-rendering) 및 현지화된 404 페이지.
다른 스택을 찾고 계신가요? TanStack Start + use-intl 가이드, TanStack Start + Paraglide 가이드, 또는 TanStack Start + Intlayer 가이드를 확인하세요.
Next.js를 사용 중이신가요? Next.js + Lingui 가이드를 확인하세요. 라이브러리 비교는 Lingui vs Intlayer를 참고하세요.
TanStack Start에서 Lingui에 대한 벤치마크 결과
i18n 벤치마크는 동일한 10개 페이지, 10개 로케일의 TanStack Start 앱을 주요 라이브러리로 실행하여 브라우저가 실제로 다운로드하는 크기를 측정합니다.
동적 JSON 로드
런타임에 번역을 지연 로드합니다
범위가 지정된 JSON (네임스페이싱)
페이지별 번역 네임스페이스
I18n 성능 벤치마크
이 측정항목은 무엇인가요?
국제화 라이브러리 번들의 총 gzip 압축 크기입니다. 여기에는 트리 쉐이킹 및 미니피케이션 후의 프로바이더 및 콘텐츠 검색 로직만 포함됩니다.
왜 중요한가요?
라이브러리 크기가 작으면 초기 JavaScript 페이로드가 줄어들어 클라이언트에서 다운로드 및 실행 시간이 빨라집니다.
보기 형식
2026-09-26 기준 @lingui/core@6.6.0 주요 수치 (gzip):
테이블을 모달로 열어 모든 데이터를 명확하게 확인
| 설정 | 라이브러리 크기 | 페이지당 JS | 타 로케일 누출 | 타 페이지 누출 |
|---|---|---|---|---|
| i18n 미적용 (기본 앱) | - | 111.0 KB | 0% | 0% |
| Lingui (본 가이드의 설정) | 56.7 KB | 115.2 KB | 9.3% | 0% |
@intlayer/lingui (호환 모드) | 9.8 KB | 136.7 KB | 9.9% | 0% |
react-intlayer (네이티브 Intlayer) | 4.5 KB | 126.8 KB | 0% | 0% |
주요 시사점:
- 로케일당 하나의 카탈로그를 필요 시 로드합니다. 이를 통해 페이지 크기를 기본 앱 크기에 가깝게 유지할 수 있습니다.
- 런타임은 여전히 무겁습니다 (~57 KB gzip).
@intlayer/lingui호환 어댑터(16단계)는 매크로를 그대로 유지하면서 런타임을 ~10 KB로 줄여줍니다.
전체 데이터 확인: TanStack Start 벤치마크 보고서 및 벤치마크 저장소.
TanStack Start에서의 기능 비교
TanStack Start에서 일반적으로 사용되는 다른 라이브러리들과 Lingui의 비교:
테이블을 모달로 열어 모든 데이터를 명확하게 확인
| 기능 | react-intlayer (Intlayer) | use-intl | Paraglide JS | Lingui |
|---|---|---|---|---|
| 컴포넌트 인근 번역 관리 | ✅ 동일 위치 배치(Co-located) | ❌ 중앙 집중식 JSON | ❌ 로케일당 하나의 JSON 파일 | ⚠️ 컴포넌트 내 원본 텍스트 |
| TypeScript 통합 | ✅ 자동 생성 타입 | ✅ AppConfig 경유 | ✅ 타입이 지정된 메시지 함수 | ⚠️ 매크로 전용 |
| 누락된 번역 감지 | ✅ 타입 에러 및 빌드 경고 | ⚠️ 런타임 폴백 | ⚠️ 기본 로케일로 폴백 | ⚠️ 원본 텍스트로 폴백 |
| 리치 콘텐츠 (JSX, Markdown) | ✅ 직접 지원 | ⚠️ t.rich 태그 방식 | ⚠️ 문자열 | ✅ <Trans> 내부 JSX |
| 현지화된 라우팅 | ✅ 내장 지원 | ❌ 수동 {-$locale} | ✅ urlPatterns + 라우터 재작성 | ❌ 수동 {-$locale} |
| 새로고침 없는 로케일 전환 | ✅ 지원 | ✅ 지원 | ❌ 전체 페이지 새로고침 | ✅ 지원 |
| 복수형 처리 | ✅ 열거형 기반 | ✅ ICU | ✅ 변형(Variants) | ✅ ICU |
| ICU MessageFormat | ✅ format: "icu" 경유 | ✅ 네이티브 | ⚠️ inlang 플러그인 경유 | ✅ 네이티브 |
| 콘텐츠 포맷 | ✅ .ts, .json, .md, .yaml... | ⚠️ .json | ⚠️ inlang JSON | ✅ PO, JSON, CSV |
| AI 번역 | ✅ 사용자 지정 제공업체 및 키 | ❌ 미지원 | ❌ 미지원 | ❌ 미지원 |
| 시각적 에디터 / CMS | ✅ 로컬 에디터 + 선택적 CMS | ❌ 외부 플랫폼 | ⚠️ inlang 생태계 앱 | ❌ 외부 플랫폼 |
| SEO 헬퍼 (hreflang, sitemap) | ✅ 내장 지원 | ❌ 수동 구현 | ⚠️ 현지화된 URL, 나머지 수동 | ❌ 수동 구현 |
| 런타임 크기 (gzip, 벤치마크) | 4.5 KB | 75.9 KB | 1.8 KB | 56.7 KB |
| 최적 설정 누출률 (로케일 / 페이지) | 0% / 0% | 0% / 0% | 49.7% / 0% | 8.6% / 0% |
| CI에서 누락된 번역 감지 | ✅ npx intlayer test | ⚠️ 미내장 | ⚠️ 미내장 | ✅ lingui compile --strict |
런타임 크기 및 누출률 수치는 TanStack Start 벤치마크에서 가져왔습니다. 누출률은 각 라이브러리의 최적 설정에서 측정되었습니다.
기타 TanStack Start 가이드: use-intl, Paraglide JS, 그리고 Intlayer.
권장 모범 사례
- 라우트 로케일을 기반으로
<html>의lang및dir을 설정하여 서버 HTML에서 올바르게 렌더링되도록 합니다. - 접두사를 사용하여 로케일당 하나의 URL을 유지하여 모든 언어 버전이 색인 가능하도록 합니다.
- 로케일당 하나의
I18n인스턴스를 생성하고, SSR 중에 전역 인스턴스를 절대 변조하지 마세요. 두 개의 동시 요청이 서로의 로케일을 덮어쓸 수 있습니다. - 클라이언트 코드에서 모든 카탈로그를 가져오지 말고, 활성 카탈로그만 로드하세요.
- 하나의 매크로 스타일을 선택하고(컴포넌트에서는
useLingui+t, 지연 디스크립터에서는msg) 일관되게 유지하세요.t,i18n._,i18n.t,<Trans>를 혼용하면 사람과 AI 어시스턴트 모두 코드를 읽기 어려워집니다. - CI에서
lingui extract를 실행하여 새로운 메시지가 번역되지 않은 채 배포되지 않도록 하세요. - 메타데이터를 번역하고, 모든 페이지에
canonical,hreflang,x-default를 선언하세요. - 다국어 sitemap과 robots.txt를 생성하고, 모든 로케일을 사전 렌더링하세요.
- 로케일 전환기에 실제 링크를 사용하여 크롤러가 모든 언어를 발견할 수 있도록 하세요.
국제화 및 SEO 가이드와 hreflang 가이드를 참고하세요.
TanStack Start 애플리케이션에 Lingui를 설정하는 단계별 가이드
생성할 프로젝트 구조는 다음과 같습니다:
코드를 클립보드에 복사
의존성 설치
bash코드 복사코드를 클립보드에 복사
- @lingui/core / @lingui/react: 런타임,
I18nProvider및 매크로(@lingui/core/macro,@lingui/react/macro). - @lingui/cli: 메시지를 카탈로그로 수집하기 위한
lingui extract. - @lingui/vite-plugin: 가져오기 시
.po카탈로그를 컴파일하므로lingui compile이 필요하지 않습니다. - @lingui/babel-plugin-lingui-macro + @rolldown/plugin-babel: 빌드 타임에 매크로를 변환합니다.
- @lingui/core / @lingui/react: 런타임,
로케일 설정 중앙화
기본 로케일은 접두사 없이 유지되고(
/about), 기타 로케일에는 접두사가 붙습니다(/fr/about).src/i18n/config.ts코드 복사코드를 클립보드에 복사
Lingui 설정
Lingui 설정은 동일한 로케일 목록을 재사용하므로 카탈로그, 라우터, 사이트맵 간에 불일치가 발생하지 않습니다.
lingui.config.ts코드 복사코드를 클립보드에 복사
추출 스크립트를 추가합니다:
package.json코드 복사코드를 클립보드에 복사
i18n:check는 컴포넌트에 추출 및 커밋되지 않은 메시지가 포함되어 있을 때 CI에서 실패합니다.Vite 설정
@vitejs/plugin-reactv6부터는 Babel이 더 이상 내장되어 있지 않습니다.@rolldown/plugin-babel이 Lingui 매크로 플러그인을 실행하며,linguiTransformerBabelPreset은 매크로를 가져오는 파일만 처리하여 빌드 속도를 빠르게 유지합니다.vite.config.ts코드 복사코드를 클립보드에 복사
로케일별 카탈로그 로드
import()의 템플릿 리터럴을 통해 Vite는 로케일당 하나의 청크를 생성하며, Lingui 플러그인이.po파일을 컴파일합니다. 프랑스어 방문자는 프랑스어 카탈로그만 다운로드합니다.컴파일된 메시지는 일반 데이터이므로 라우트 로더에서 반환하고, HTML로 직렬화하며, 하이드레이션 시 재사용할 수 있습니다.
src/i18n/lingui.ts코드 복사코드를 클립보드에 복사
TypeScript에서
.po가져오기를 허용하려면 모듈을 한 번 선언하세요:src/i18n/po.d.ts코드 복사코드를 클립보드에 복사
루트 도큐먼트 생성
루트 라우트는 선택적 로케일 파라미터를 읽어 서버 렌더링된
<html>에lang과dir을 설정합니다.src/routes/__root.tsx코드 복사코드를 클립보드에 복사
로케일 레이아웃 라우트 생성
{-$locale}폴더는 선택적 경로 세그먼트를 생성합니다./about과/fr/about은 모두/{-$locale}/about과 일치합니다. 레이아웃은 알 수 없는 접두사를 거부하고, 현재 로케일의 카탈로그를 로드하며, 전용I18n인스턴스를 제공합니다.src/routes/{-$locale}/route.tsx코드 복사코드를 클립보드에 복사
페이지에서 번역 활용
컴포넌트에 원본 텍스트를 작성합니다. 매크로는 빌드 타임에 이를 메시지 ID로 변환하고,
lingui extract가 이를 수집합니다.- 중첩 요소를 포함한 JSX 콘텐츠를 위한
<Trans>; - 문자열(속성, props)을 위한
useLingui().t; - ICU 복수형을 위한
<Plural>.
src/routes/{-$locale}/about.tsx코드 복사코드를 클립보드에 복사
카탈로그의 동적
import()는 모듈 시스템에 의해 캐시되므로 여러 로더에서loadI18n을 호출해도 카탈로그를 두 번 다운로드하지 않습니다.- 중첩 요소를 포함한 JSX 콘텐츠를 위한
메시지 추출 및 번역
추출 명령을 실행합니다. Lingui는 모든 메시지를 각 로케일 카탈로그에 작성합니다:
bash코드 복사코드를 클립보드에 복사
그런 다음 각 항목의
msgstr을 번역합니다:src/locales/fr/messages.po코드 복사코드를 클립보드에 복사
src/locales/es/messages.po코드 복사코드를 클립보드에 복사
기본적으로 메시지 ID는 원본 텍스트의 해시값입니다. 영어 텍스트를 변경하면 새 메시지가 생성됩니다. 자주 변경되는 텍스트에는 명시적 ID(
<Trans id="about.title">About us</Trans>)를 사용하세요.현지화된 링크 컴포넌트 구축
선택사항모든 라우트는
{-$locale}아래에 위치하므로 링크는 현재 로케일 파라미터를 전달해야 합니다.src/components/LocalizedLink.tsx코드 복사코드를 클립보드에 복사
콘텐츠 언어 변경
선택사항크롤러가 모든 언어 버전을 찾을 수 있도록 전환기를 링크로 렌더링합니다.
to="."는 현재 페이지를 유지하고 로케일 파라미터를 교체합니다. 그런 다음 로케일 레이아웃의 로더가 새 카탈로그를 가져옵니다.src/components/LocaleSwitcher.tsx코드 복사코드를 클립보드에 복사
메타데이터 국제화
선택사항모든 페이지가 번역된
<title>과 description, 자체 참조 canonical, 로케일당 하나의hreflang및x-default, Open Graph 로케일,inLanguage가 포함된 JSON-LD를 제공한다면 각 언어 버전이 개별적으로 검색 순위에 오를 수 있습니다. 메타데이터는 로더(8단계)에서 번역되며, 이 헬퍼 함수가 나머지를 구성합니다:src/i18n/seo.ts코드 복사코드를 클립보드에 복사
사이트맵 및 robots.txt 국제화
선택사항사이트맵은 모든 로케일의 모든 URL을 나열하며, 각 항목은
xhtml:link로 모든 대체 언어를 선언합니다.robots.txt는 모든 언어의 비공개 경로를 차단하고 사이트맵을 가리킵니다. 스타터 템플릿에서public/robots.txt를 생성했다면 이를 삭제하세요.src/routes/sitemap[.]xml.ts코드 복사코드를 클립보드에 복사
src/routes/robots[.]txt.ts코드 복사코드를 클립보드에 복사
모든 로케일 사전 렌더링
선택사항TanStack Start가 빌드 타임에 모든 언어 버전을 사전 렌더링할 수 있도록 모든 현지화된 경로를 나열합니다:
vite.config.ts코드 복사코드를 클립보드에 복사
첫 방문자 리디렉션 및 404 페이지 처리
선택사항요청 미들웨어는
/에 도달한 방문자를 기본 설정 언어로 리디렉션합니다(쿠키 우선, 그다음Accept-Language). 딥 링크는 절대 리디렉션되지 않으므로 크롤러와 공유 URL은 항상 요청한 페이지를 그대로 받습니다.src/i18n/negotiateLocale.ts코드 복사코드를 클립보드에 복사
src/start.ts코드 복사코드를 클립보드에 복사
404 페이지의 경우, catch-all 라우트가 레이아웃의 현지화된
notFoundComponent를 렌더링합니다.noindex로 표시하세요. React 19는<meta>를<head>로 호이스팅합니다.src/components/NotFound.tsx코드 복사코드를 클립보드에 복사
src/routes/{-$locale}/$.tsx코드 복사코드를 클립보드에 복사
Intlayer로 매크로를 유지하면서 런타임 줄이기
선택사항@intlayer/lingui호환 어댑터는 소스 코드를 그대로 유지합니다. 매크로는 이전과 동일하게 컴파일되며, 결과로 생성되는i18n._(),useLingui(),<Trans>호출은 컴파일된 Intlayer 딕셔너리에 의해 제공됩니다. 벤치마크에서 런타임은 ~56.7 KB에서 ~9.8 KB gzip으로 감소합니다.bash코드 복사코드를 클립보드에 복사
매크로 변환 이후에 플러그인을 추가하여
@lingui/core와@lingui/react를 어댑터로 별칭(alias) 지정하도록 합니다:vite.config.ts코드 복사코드를 클립보드에 복사
카탈로그는 sync JSON 플러그인 (JSON 카탈로그) 또는 sync PO 플러그인 (PO 카탈로그)을 통해 동기화됩니다. 전체 설정은 Lingui 호환 가이드를 참고하고, 나란히 비교한 내용은 Lingui vs @intlayer/lingui에서 확인하세요.
Intlayer를 활용한 번역 자동화
선택사항Lingui는 메시지를 추출하지만, 수십 개의 카탈로그를 수작업으로 채우는 데 대부분의 시간이 소요됩니다. Intlayer는 무료이며 오픈 소스로, Lingui와 함께 작동하는 도구를 제공합니다:
- 자체 API 키와 제공업체를 사용하여 AI로 번역. auto fill 및 CLI를 참고하세요.
- sync PO 플러그인을 사용하여 PO 파일을 단일 진실 공급원(source of truth)으로 유지.
- CI에서 누락된 번역 테스트. 번역 테스트 가이드를 참고하세요.
- scan 명령어를 사용하여 누락된
hreflang, 잘못된 canonical, 로케일 누출에 대해 배포된 사이트 감사.
자주 묻는 질문
네. Lingui에는 전용 TanStack Start 통합이 없지만, Vite 플러그인과 Babel 매크로 플러그인이 그대로 작동합니다. 올바르게 설정해야 할 두 가지 핵심 사항은 @rolldown/plugin-babel을 통해 매크로를 실행하는 것(Vite 8 및 @vitejs/plugin-react v6에는 Babel이 더 이상 포함되지 않음)과 SSR 중에 전역 인스턴스를 활성화하는 대신 로케일당 하나의 I18n 인스턴스를 생성하는 것입니다.
서버에서는 단일 프로세스가 동시에 여러 요청을 렌더링합니다. 공유 객체에서 i18n.activate("fr")를 호출하면 병렬로 영어로 렌더링 중인 요청의 언어가 변경될 수 있습니다. setupI18n은 로케일당 격리된 인스턴스를 생성하므로 안전합니다.
아니요. @lingui/vite-plugin은 .po 카탈로그를 가져올 때 컴파일합니다. 새로운 메시지를 수집할 때 lingui extract만 실행하면 됩니다.
msg 매크로로 선언하고 라우트 로더에서 i18n._(msg`...`)로 번역합니다. 로더는 일반 문자열을 반환하므로 head()는 동기적으로 유지되고 값은 하이드레이션을 위해 직렬화됩니다. 8단계와 12단계에서 전체 설정을 보여줍니다.
벤치마크에서는 런타임으로 ~56.7 KB gzip이 측정됩니다. 로케일당 하나의 카탈로그를 필요 시 로드하면 페이지 크기는 i18n이 없는 111 KB 대비 ~115 KB가 됩니다. 모든 카탈로그를 정적으로 가져오면 ~152 KB로 증가합니다.
네. @intlayer/lingui 어댑터는 매크로를 유지하고 런타임을 교체합니다. 그런 다음 컴포넌트를 하나씩 useIntlayer로 이전할 수 있습니다. 호환 어댑터를 참고하세요.
댓글
아직 댓글이 없습니다. 첫 번째로 의견을 나눠보세요.
