Nextjs 다국어 적용하기 - 1

choi seung-i·2024년 11월 12일
0

작업로그

목록 보기
17/18

Nextjs 14.1, AppRoute

두개의 프로젝트에서 조금 다르게 언어변환을 적용해 보았다.

1. 유저 선호언어를 위해 클라이언트를 거쳐야만 했다.

기존에 언어변환을 적용했던 프로젝트가 있었다.
해당 프로젝트에서는 기본언어의 우선순위가 이랬다.

  1. 기존에 방문했던 유저라면 그당시 설정한 언어로
  2. 처음 방문시 유저의 브라우저 설정의 언어로

만약 1번만 했었더라면, 쿠키사용정도로 middleware에서만 해결했을수도 있을 것 같았는데....
2번을 하기위해 여러 방법을 써봤는데 결론은 실패.

로컬에서 할 때에는 request.headers.get("accept-language") 에서 확인이 가능하였으나, aws의 amplify로 배포하면 해당 accept-language가 없어지고 들어오지 않는것이 문제였다.

일단 롤백하고 3일정도를 amplify.yml수정도해보고 유저에이전트가 ‘Amazon CloudFront’여서 CloudFront도 건드려보았지만 답이 나오지않았고, 문서찾아봤을때 amplify에서는 유저 각각에 대한 정보들은 헤더에서 날려버린다는것 같았다.
CDN 캐싱을 사용하면서 공통된 정보 전달을 위해 그런건가..? 라는 생각을 해보며 aws쪽 공부를 언젠가는 꼭 해야겠다고 다짐하게 되었던 계기였다...
(이 부분에 대해 아시는분은 알려주시면 감사하겠습니다 ㅠㅠ)

더이상 시간을 끌 순 없다!
middleware(서버)와 cookies(서버+클라)와 web Api인 navigator(클라)를 섞어서 적용하였다.

Middleware

export const locales = ["ko", "ja", "en"] as const;
export const DEFAULT_LOCALE = "en";

export async function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  const pathnameHasLocale = locales.some(
    (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
  );

  if (pathnameHasLocale) return;

  /** 패스에 로케일 정보가 없을 때
   * 쿠키 값이 있을 경우 : 쿠키를 넣어줌 (전에 방문 이력)
   * 쿠키 값이 없을 경우 : DEFAULT_LOCALE로 넣어줌 (이후 클라이언트에서 브라우저 선호로 바꿈)
   */
  const COOKIE_LOCALE = request.cookies.get("xxx_locale")?.value;

  request.nextUrl.pathname = `/${COOKIE_LOCALE || DEFAULT_LOCALE}${pathname}`;
  return Response.redirect(request.nextUrl);
}

RootLayout

쿠키를 가져오고, setCookie라는 클라이언트측컴포넌트를 만들어 해당 컴포넌트 내에서 처리해주었다.

const SetCookie = ({
  locale,
  cookieLocale,
}: {
  locale: string;
  cookieLocale: string | undefined;
}) => {
  const pathname = usePathname();

  useEffect(() => {
    if (!cookieLocale) {
      // 1 :: 쿠키가 없다면, 브라우저 선호 언어로 쿠키 생성
      const userLocale = getUserLocale();
      
      if (userLocale !== locale) {
        // 1-1 :: 브라우저 선호 언어가 현재 locale이 아니라면 리다이렉트
        setCookie(userLocale);
        redirect(changeLocaleHandler(pathname, userLocale));
      }
    } else if (cookieLocale !== locale) {
      // 2 :: 쿠키와 현재 패스의 언어가 다르다면, 현재 패스 언어로 쿠키 변경 (직접 주소창 입력으로 들어왔을 경우 & lang선택 변경)
      setCookie(locale);
    }
    
    // 3 :: 쿠키와 패스가 같다면 통과!
  }, []);

  return <></>;
};

export const changeLocaleHandler = (pathname: string, lang: string) => {
  let setPath = pathname;
  locales.forEach((locale) => {
    if (pathname.startsWith(`/${locale}`)) {
      setPath = pathname.replace(`/${locale}`, `/${lang}`);
    }
  });
  return setPath;
};

export const getUserLocale = () => {
  return navigator.language.substring(0, 2);
};

이렇게해서 적용은 되었지만,
쿠키가 없는 초기상태이면서 locale없는 메인도메인으로 왔을경우에만 default로 영문을 보여줬다가 유저선호랑 맞지않으면 바로 redirect 해주기때문에 깜빡이는것을 겪을수밖에 없어서 나중에 한번 리팩토링을 거쳐야할 것 같다.

페이지에서 언어 사용하기

export type DicObjType = { [key: string]: any };
let dic: DicObjType = {};
let nowLocale: Locale;

// nav, footer와 또는 페이지마다 공통으로 들어가는 부분들
export const getCommonDictionary = async (locale: Locale) => {
  await import(`@/dictionaries/common/${locale}.json`).then((module) => {
    dic = { ...dic, ...module.default };
    return dic;
  });
};

export const getDictionary = async (path: string, locale: Locale) =>
  await import(
    `@/dictionaries/${path === "/" ? "main" : path}/${locale}.json`
  ).then((module) => {
    dic = { ...dic, ...module.default };
    nowLocale = locale;
    return dic;
  });


export const t = (k: string) => dic[k];
export const lo = () => nowLocale;

export const isLocale = () => {
  const isKo = nowLocale === "ko";
  const isJa = nowLocale === "ja";
  const isEn = nowLocale === "en";
  return { isKo, isJa, isEn };
};
// 서버컴포넌트 Page에서 params로 가져와서 해당 페이지 dictionary호출
const Contact = async ({
  params: { lang },
}: {
  params: { lang: Locale };
}) => {
  await getDictionary("contact", lang);
  // ...
  
  
  const dic = t("contact")["partnership"];
  <p>{dic["desc"]}</p>
  
  // 이미지도 언어별로 보여줄 때
  const { isKo, isJa } = isLocale();
  const localeImg = isKo ? Image_KO : isJa ? Image_JA : Image_EN;

ex) /dictionaries/contact/ko.json

{
  "contact" : {
    "title": "문의하기",
    "partnership": {
      "desc": "내용내용"
    }
  }
}

공부하며 정리&기록하는 ._. 씅로그

profile
Front-end

0개의 댓글