반응형
Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
Tags
- AWS
- 백준
- RiotAPI
- oAuth
- 깃
- 스프링
- 게임
- jwt
- bcrypt
- 유니티
- 게임개발
- netlify
- 프로그래밍
- react
- MongoDB
- IntelliJ
- 코딩
- spring
- c#
- 백엔드
- OAuth2.0
- unity
- 스프링부트
- node.js
- frontend
- Python
- 파이썬
- JSON
- springboot
- express
Archives
- Today
- Total
Unwound Developer
Next.js 14(App Router) + React i18n(next-intl)을 통한 다국어 처리 본문
반응형
Next.js를 활용한 SSR 다국어 처리(i18n)
1. 유저의 locale
감지 방법
애플같은 사이트를 접속하다보면, 내 국가에 따라 언어가 자동으로 설정되는 것을 흔히 볼 수 있습니다.
보통 url에서 apple.ko 혹은 apple/ko 이런 식으로 국가 코드에 따라 언어가 설정 됩니다.
그 다국어 처리를 Next.js SSR 환경에서 구현하는 것을 알아보겠습니다.
클라이언트 측 (navigator.language
)
- 브라우저에서 사용자가 어떤 언어를 쓰고 있는지 알 수 있음.
navigator.language
는 브라우저의 UI 언어 정보를 저장합니다.
브라우저 설정에 따라 달라짐.
const locale = window.navigator.language;
서버 측 (Accept-Language
)
- Next.js는 서버 사이드 렌더링을 지원하므로, HTTP 헤더의
Accept-Language
값을 이용해 유저의 언어를 감지합니다. Accept-Language
도 브라우저 설정을 기반으로 함.
주의
Accept-Language
는 핑거프린팅(추적) 가능성이 있기 때문에 직접 변경하지 않는 게 좋습니다.
2. Next.js의 Internationalized Routing
서버에서 하는 일
Accept-Language
값을 이용해 유저 언어 감지합니다.- 유저의 언어에 맞는 URL로 리다이렉트합니다.
<html>
태그에lang
속성 추가합니다.
라우팅 방식
(1) Sub-path Routing
- 언어를 URL의 서브패스로 구분.
- 예시:
- 한국어 사이트 →
interneteye.co.kr/payment
- 영어 사이트 →
interneteye.co.kr/en-us/payment
- 한국어 사이트 →
(2) Domain Routing
- 도메인 자체를 언어에 따라 다르게 설정.
- 예시:
- 글로벌 →
example.com
- 프랑스어 →
example.fr
- 글로벌 →
팁
Sub-path Routing이 더 간단하고 유지보수하기 쉽습니다.
Domain Routing은 설정해야 할 게 많아 규모가 큰 사이트가 아니면 추천하지 않습니다..
Next.js가 i18n 라우팅을 사용하는 이유
- 인앱 상태값이나 HTTP 헤더를 직접 수정하는 방법은 권장되지 않음.
- Next.js는 정적 페이지 기반 i18n을 지원하며, URL을 기반으로 언어를 구분.
예시:
example.com/ko
example.com/en-US
설정 방법 (Next.js 10 이상)
next.config.js
파일에 i18n 설정 추가.npm install next-intl
로 관련 패키지 설치.
디렉토리 구조
app router 기준으로 app 디렉토리 하위에 [locale] 이라는 디렉토리를 생성해야합니다.
반드시 해당 locale 디렉토리를 생성해야 국가 코드로 라우팅 됩니다. 예) test.com/ko test.com/en-US
// i18n.ts
import { notFound } from 'next/navigation';
import { getRequestConfig } from 'next-intl/server';
const locales = ['en', 'ko'];
export default getRequestConfig(async ({ locale }) => {
// locale` parameter 검증
if (!locales.includes(locale as any)) notFound();
// 위치 확인
return {
messages: (await import(`@/locales/${locale}/translate.json`)).default
};
});
3. NEXT_LOCALE 쿠키 저장 시점
URL
,defaultLocale
, 또는 브라우저 언어에 따라 언어 결정 후.- 미들웨어에서 리다이렉션이 발생한 후.
- 사용자가 언어를 변경했을 때.
- 초기 요청에서 Next.js가 언어를 판단했을 때.
4. 문제점 및 해결 방법
문제: 미들웨어에서 Accept-Language
값 읽기
Negotiator.language()
: 클라이언트가 요청한accept-language
헤더를 기준으로 서버에서 지원하는 언어를 매칭.- 기본 동작: 첫 번째 매칭된 언어를 반환.
하지만 예상과 다른 언어가 반환되는 경우 발생.
예시
SUPPORTED_LANGUAGES = ['en', 'ko'];
accept-language = 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7';
- 매칭 과정
ko-KR
→SUPPORTED_LANGUAGES
에 없음 → 무시.ko
→ 매칭 성공 → 반환.- 하지만 간혹
en
이 반환되는 경우 발생.
해결 방법: 매칭 로직 수정
const priorities = negotiator.languages();
const matchedLanguage = priorities.find((lang) =>
SUPPORTED_LANGUAGES.includes(lang)
);
const locale = matchedLanguage || DEFAULT_LANGUAGE;
console.log('Matched language:', locale);
- 클라이언트의 요청 언어를 순서대로 확인하고 직접 매칭.
- 매칭된 언어가 없으면
DEFAULT_LANGUAGE
반환.
5. Next.js Client Component에서 useRouter
사용 불가
문제
- Next.js 13 이후, 클라이언트 컴포넌트는
use client
를 사용해야 함. - 그러나,
use client
를 사용하면useRouter
사용 시 에러 발생:
"NextRouter was not mounted."
대안
useNavigation
사용- 하지만
queries
나dynamicPaths
같은 데이터는 포함되지 않습니다.
- 하지만
usePathname
추가 사용query
,dynamicPaths
가 필요할 경우 보완적으로 활용합니다.
// middleware.ts
import createMiddleware from 'next-intl/middleware';
import Negotiator from 'negotiator';
import { NextResponse } from 'next/server';
// 서버에서 지원하는 언어와 기본 언어 설정
const SUPPORTED_LANGUAGES = ['ko', 'en'];
const DEFAULT_LANGUAGE = 'ko';
// 기본 next-intl 미들웨어 생성
const intlMiddleware = createMiddleware({
locales: SUPPORTED_LANGUAGES,
defaultLocale: DEFAULT_LANGUAGE,
});
// Custom Negotiator를 추가한 미들웨어
export default function middleware(req : any) {
const { cookies } = req; // 요청에서 쿠키 읽기
const { pathname } = req.nextUrl;
const nextLocale = cookies.get('NEXT_LOCALE') // NEXT_LOCALE 쿠키 확인
// 이미 언어 코드가 경로에 포함되어 있다면 추가 리디렉션 방지하기 위한 변수
const isLanguagePath = SUPPORTED_LANGUAGES.some((lang) => pathname.startsWith(`/${lang}`));
if (nextLocale && SUPPORTED_LANGUAGES.includes(nextLocale.value) && !isLanguagePath) { // 쿠키에 유효한 언어 값이 있으면 해당 값 사용
const url = req.nextUrl.clone()
url.pathname = `/${nextLocale.value}`
return NextResponse.redirect(new URL(`/${nextLocale.value}`,req.url))
}
// URL에 로케일이 없으면 리다이렉트
if (!isLanguagePath) {
// 언어 코드가 포함되어 있다면, 쿠키에 값이 없으면 Accept-Language 헤더로 언어 결정
const negotiator = new Negotiator(req); // 클라이언트의 Request 읽어옴
const priorities = negotiator.languages(); // 클라이언트의 Request에서 Accept_Language 값 읽어옴
const matchedLanguage = priorities.find((lang) =>
SUPPORTED_LANGUAGES.includes(lang)
); // 가장 우선순위로 있는 언어가 SUPPORTED_LANGUAGE에 있는지 우선순위대로 find
console.log("matchedLanguage:",matchedLanguage);
const locale = matchedLanguage || DEFAULT_LANGUAGE; // matchedLanguage가 false나 undefined같은 값이라면 DEFAULT_LANGUAGE로 세팅
const url = req.nextUrl.clone();
url.pathname = `/${locale}${pathname}`;
return NextResponse.redirect(url);
}
// Default: next-intl 미들웨어 실행
return intlMiddleware(req);
}
// next-intl 미들웨어 설정
export const config = {
matcher: ['/((?!api|_next|.*\\..*).*)'], // i18n 적용 경로 설정
};
참고 사항
- Next.js에서 i18n을 제대로 활용하려면 서버와 클라이언트 모두 협력해야 함.
- 설정이 복잡할 수 있으니 간단한 구현부터 시작하는 게 좋음!
반응형