From 79c9132d2f7cd66166db4c895bccd65643eb4304 Mon Sep 17 00:00:00 2001 From: witch-factory Date: Fri, 24 Jan 2025 11:03:12 +0900 Subject: [PATCH 01/28] =?UTF-8?q?=EC=96=B8=EC=96=B4=EC=99=80=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EB=A6=AC=EB=8B=A4?= =?UTF-8?q?=EC=9D=B4=EB=A0=89=ED=8A=B8=ED=95=A0=20URL=EC=9D=84=20=EB=A7=8C?= =?UTF-8?q?=EB=93=9C=EB=8A=94=20=ED=95=A8=EC=88=98=EB=A5=BC=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/generateRedirectPath.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/utils/generateRedirectPath.ts diff --git a/src/utils/generateRedirectPath.ts b/src/utils/generateRedirectPath.ts new file mode 100644 index 0000000..efe70dc --- /dev/null +++ b/src/utils/generateRedirectPath.ts @@ -0,0 +1,14 @@ +import { i18n, Locale } from '@/types/i18n'; + +export function generateRedirectPath(pathname: string, selectedLocale: Locale) { + const pathSegments = pathname.split('/').filter(Boolean); // 경로를 '/'로 나누고 빈 값 제거 + const currentLangIndex = i18n.locales.includes(pathSegments[0] as Locale) ? 0 : -1; + + // 경로에 언어가 없는 경우 추가 + if (currentLangIndex === -1) { + return selectedLocale === i18n.defaultLocale ? pathname : `/${selectedLocale}${pathname}`; + } + + pathSegments[currentLangIndex] = selectedLocale === i18n.defaultLocale ? '' : selectedLocale; + return `/${pathSegments.filter(Boolean).join('/')}`; +} From 196333a920d365d0ce217dbe2a1017b752147d8e Mon Sep 17 00:00:00 2001 From: witch-factory Date: Fri, 24 Jan 2025 11:29:59 +0900 Subject: [PATCH 02/28] =?UTF-8?q?=EC=96=B8=EC=96=B4=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=20=EC=B5=9C=EC=A0=81=ED=99=94=20=EC=8B=9C=EB=8F=84=20-=20?= =?UTF-8?q?=EB=9D=BC=EC=9A=B0=ED=8A=B8=20=ED=95=B8=EB=93=A4=EB=9F=AC=20?= =?UTF-8?q?=EC=BA=90=EC=8B=B1,=20=ED=81=B4=EB=9D=BC=EC=9D=B4=EC=96=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=82=AC=EC=9D=B4=EB=93=9C=20=EB=84=A4=EB=B9=84?= =?UTF-8?q?=EA=B2=8C=EC=9D=B4=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/[lang]/api/language/route.ts | 52 +++++++++++++--------------- src/components/langSwitch/index.tsx | 28 +++++++++------ src/utils/generateRedirectPath.ts | 2 ++ 3 files changed, 43 insertions(+), 39 deletions(-) diff --git a/src/app/[lang]/api/language/route.ts b/src/app/[lang]/api/language/route.ts index d1e8685..6da2ec7 100644 --- a/src/app/[lang]/api/language/route.ts +++ b/src/app/[lang]/api/language/route.ts @@ -1,49 +1,45 @@ -import { headers } from 'next/headers'; import { NextRequest, NextResponse } from 'next/server'; -import { blogConfig } from '@/config/blogConfig'; import { i18n, Locale, LOCALE_COOKIE_NAME } from '@/types/i18n'; -function generateRedirectPath(pathname: string, selectedLocale: Locale) { - const pathSegments = pathname.split('/').filter(Boolean); // 경로를 '/'로 나누고 빈 값 제거 - const currentLangIndex = i18n.locales.includes(pathSegments[0] as Locale) ? 0 : -1; - - // 경로에 언어가 없는 경우 추가 - if (currentLangIndex === -1) { - return selectedLocale === i18n.defaultLocale ? pathname : `/${selectedLocale}${pathname}`; - } - - pathSegments[currentLangIndex] = selectedLocale === i18n.defaultLocale ? '' : selectedLocale; - return `/${pathSegments.filter(Boolean).join('/')}`; -} +export const dynamic = 'force-static'; // api/language?locale=ko 등 locale 쿼리스트링을 통해 언어 변경 -export function GET(request: NextRequest) { - const searchParams = request.nextUrl.searchParams; - const selectedLocale = searchParams.get('locale') as Locale | undefined; +export function GET(request: NextRequest, { params }: { + params: { lang: Locale }, +}) { + const selectedLocale = params.lang; // 유효하지 않은 로케일이면 406 Not Acceptable 에러 - if (!selectedLocale || !i18n.locales.includes(selectedLocale)) { + if (!i18n.locales.includes(selectedLocale)) { return NextResponse.json( { error: 'Invalid locale' }, { status: 406 }, ); } - // 이전 페이지의 URL을 referer 헤더를 통해 가져옴 - const headersList = headers(); - const refererUrl = new URL(headersList.get('referer') ?? blogConfig.ko.url); - const { origin, pathname } = refererUrl; - - const newPath = generateRedirectPath(pathname, selectedLocale); - const redirectUrl = new URL(newPath, origin); - - const response = NextResponse.redirect(redirectUrl); + const response = NextResponse.json({ locale: selectedLocale }); response.cookies.set(LOCALE_COOKIE_NAME, selectedLocale, { path: '/', maxAge: 60 * 60 * 24 * 30, // 1달 sameSite: 'lax', }); - return response; + + // // 이전 페이지의 URL을 referer 헤더를 통해 가져옴 + // const headersList = headers(); + // const refererUrl = new URL(headersList.get('referer') ?? blogConfig.ko.url); + // const { origin, pathname } = refererUrl; + + // const newPath = generateRedirectPath(pathname, selectedLocale); + // const redirectUrl = new URL(newPath, origin); + + // const response = NextResponse.redirect(redirectUrl); + // response.cookies.set(LOCALE_COOKIE_NAME, selectedLocale, { + // path: '/', + // maxAge: 60 * 60 * 24 * 30, // 1달 + // sameSite: 'lax', + // }); + + // return response; } diff --git a/src/components/langSwitch/index.tsx b/src/components/langSwitch/index.tsx index dcda2aa..41805b0 100644 --- a/src/components/langSwitch/index.tsx +++ b/src/components/langSwitch/index.tsx @@ -1,9 +1,10 @@ 'use client'; -import { useRouter } from 'next/navigation'; +import { usePathname, useRouter } from 'next/navigation'; import { useTransition } from 'react'; import { i18n, Locale } from '@/types/i18n'; +import { generateRedirectPath } from '@/utils/generateRedirectPath'; import * as styles from './styles.css'; @@ -24,23 +25,28 @@ const content = { export default function LanguageSwitcher({ lang }: { lang: Locale }) { const router = useRouter(); + const pathname = usePathname(); const [isPending, startTransition] = useTransition(); // 언어 교체 - const toggleLanguage = async (newLang: Locale) => { + const toggleLanguage = (newLang: Locale) => { if (lang === newLang) return; // 같은 언어일 경우 무시 + const redirectPath = generateRedirectPath(pathname, newLang); try { - const response = await fetch(`/api/language?locale=${newLang}`); - if (!response.ok) { - throw new Error('Language change failed'); - } - const redirectUrl = response.url; + fetch(`/${newLang}/api/language`).catch((error: unknown) => { + console.error('Failed to change language:', error); + }); + startTransition(() => { - router.push(redirectUrl); - // scroll: false로 변경하면 페이지 이동 시 스크롤이 맨 위로 이동하지 않음 - // router.push(redirectUrl, { scroll: false }); + router.replace(redirectPath); }); + // const redirectUrl = response.url; + // startTransition(() => { + // router.replace(redirectUrl); + // // scroll: false로 변경하면 페이지 이동 시 스크롤이 맨 위로 이동하지 않음 + // // router.push(redirectUrl, { scroll: false }); + // }); } catch (error) { console.error('Failed to change language:', error); @@ -53,7 +59,7 @@ export default function LanguageSwitcher({ lang }: { lang: Locale }) {