๊ตญ์ œํ™” ๋„์ž…ํ•˜๊ธฐ

๊ธฐ์กด์— ์šด์˜ํ•˜๋˜ ์›น์‚ฌ์ดํŠธ์— ๊ตญ์ œํ™” ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค. ๊ธฐ์กด ์‚ฌ์ดํŠธ๋Š” ์˜๋ฌธ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด google translate API๊ฐ€ ์ž‘๋™ํ•˜๋Š” ๋ฐฉ์‹์ด์—ˆ๋‹ค.

๋ฌธ์ œ์ 

1. ์–ด์ƒ‰ํ•œ ๋ฒˆ์—ญ

์•„๋ฌด๋ž˜๋„ ๋ฒˆ์—ญ๊ธฐ๋ฅผ ๋Œ๋ฆฌ๋Š” ๋ฐฉ์‹์ด๋ผ ๋ฒˆ์—ญ์ด ๋งค์šฐ ์–ด์ƒ‰ํ–ˆ๋‹ค. ํ•œ๊ธ€ ๋ฌธ๋‹จ์„ ์ธ์‹ํ•ด ๋ฒˆ์—ญํ•ด ๋ฒˆ์—ญ๋˜๋ฉด ์•ˆ๋˜๋Š” ๋ถ€๋ถ„(ํšŒ์‚ฌ๋ช… ๊ฐ™์€ ๋ถ€๋ถ„)๋„ ๊ฐ™์ด ๋ฒˆ์—ญ๋˜์—ˆ๋‹ค. ์‚ฌ์ดํŠธ ํƒ€๊ฒŸ์ธต์ด ์šฐ๋ฆฌ๋‚˜๋ผ๋งŒ ์žˆ๋Š”๊ฒƒ์ด ์•„๋‹ˆ๋ผ ํ‹€๋ฆฐ ์˜๋ฌธ๋ฒ•์ด ๋ณด์ด๋Š”๊ฑธ ๊ฐฑ์žฅํžˆ ์‹ซ์–ดํ•˜์…จ๋‹ค,,,

2. ๋А๋ฆฐ ์†๋„

google translate API๋ฅผ ์‚ฌ์šฉํ•œ ๋ฒˆ์—ญ ๊ธฐ๋Šฅ์€ ํŽ˜์ด์ง€์— ๋ Œ๋”๋ง๋œ ํ•œ๊ธ€์„ ์ธ์‹ํ•ด ์˜์–ด๋กœ ๋ฒˆ์—ญํ•˜๋Š” ๋ฐฉ์‹์ด๊ธฐ ๋•Œ๋ฌธ์— ์‹œ๊ฐ„์ด ์˜ค๋ž˜๊ฑธ๋ ธ๋‹ค. ๋‹ค๋ฅธํŽ˜์ด์ง€๋กœ ์ด๋™ํ›„ ๊ธฐ์กด ์‚ฌ์ดํŠธ๋กœ ์ด๋™ํ•ด๋„ ๋‹ค์‹œ ๋ฒˆ์—ญ๋˜์–ด ์‹œ๊ฐ„์ด ๊ฑธ๋ ธ๋‹ค.

Next.js ๊ตญ์ œํ™”

๋ฐฉ๋ฒ• 1. ์˜์–ด ์ปดํฌ๋„ŒํŠธ๋งŒ ์ถ”๊ฐ€ํ•˜๊ธฐ

<p>{lang === "ko" ? "์•ˆ๋…•ํ•˜์„ธ์š”" : "Hello"}</p>

๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์ด๋‹ค. ๊ธฐ์กด ํŒŒ์ผ๋„ ์ „๋ถ€ ํ•œ๊ธ€์ด ํ•˜๋“œ์ฝ”๋”ฉ ๋˜์–ด์žˆ์–ด์„œ ๊ฑฐ๊ธฐ์— ์˜๋ฌธ ๋ฒ„์ „ ์ปดํฌ๋„ŒํŠธ๋งŒ ์ถ”๊ฐ€ํ•˜๋ฉด ๋œ๋‹คโ€ฆ ํ•˜์ง€๋งŒ 1) ์ฝ”๋“œ ๊ธธ์ด๊ฐ€ ๊ธธ์–ด์ ธ ๊ฐ€๋…์„ฑ์ด ์•ˆ์ข‹์•„์ง€๊ณ , 2) ์˜๋ฌธ ์ด์™ธ ๋‹ค๋ฅธ ์–ธ์–ด๋ฅผ ์ถ”๊ฐ€ํ•  ๋•Œ ์ดํ›„ ์œ ์ง€๋ณด์ˆ˜๋ฅผ ์ƒ๊ฐํ•ด์„œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ ์šฉํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

๋ฐฉ๋ฒ• 2. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉํ•˜๊ธฐ

๊ธฐ์กด ์‚ฌ์ดํŠธ๋Š” Next.js๋กœ ๋งŒ๋“ค์–ด์ ธ ์ฐพ์•„๋ณด๋‹ˆ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๊ตญ์ œํ™” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์ด ์žˆ์—ˆ๋‹ค. ๋Œ€ํ‘œ์ ์œผ๋กœ next-i18next, next-intl, next-translate๊ฐ€ ์žˆ์—ˆ๋‹ค.

~~๋ฌธ์ œ๋Š” ๋‚˜ ํฌํ•จ ์ธํ„ด๋“ค์ด ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž๋ผ Next.js๋Š” ๋ฌธ์™ธํ•œ์ด๋ผ๋Š” ๊ฒƒ์ด์—ˆ๋‹คโ€ฆ ๊ฒฐ๊ตญ ๋Ÿฌ๋‹์ปค๋ธŒ๋•Œ๋ฌธ์— ๋„์ž…๊นŒ์ง€ ์ดํ‹€์ด ๊ฑธ๋ ธ๋‹ค. ~~ ์„ธ ๊ฐ€์ง€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ค‘ next-intl์„ ์„ ํƒํ–ˆ๊ณ  Docs๋ฅผ ์ฐฌ์ฐฌํžˆ ๋œฏ์–ด๋ณด๋ฉฐ ๊ตญ์ œํ™”๋ฅผ ์‹œ์ž‘ํ–ˆ๋‹ค.

next-intl ๋„์ž…

  1. ๋จผ์ € ํ”„๋กœ์ ํŠธ์— next-intl์„ import ํ•˜๊ณ  ํŒŒ์ผ ๊ตฌ์กฐ๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ๋งž์ถฐ์ค€๋‹ค.
npm install next-intl
โ”œโ”€โ”€ messages
โ”‚   โ”œโ”€โ”€ en.json
โ”‚   โ””โ”€โ”€ ...
โ”œโ”€โ”€ next.config.ts
โ””โ”€โ”€ src
    โ”œโ”€โ”€ i18n
    โ”‚   โ”œโ”€โ”€ routing.ts
    โ”‚   โ”œโ”€โ”€ navigation.ts
    โ”‚   โ””โ”€โ”€ request.ts
    โ”œโ”€โ”€ middleware.ts
    โ””โ”€โ”€ app
        โ””โ”€โ”€ [locale]
            โ”œโ”€โ”€ layout.tsx
            โ””โ”€โ”€ page.tsx
  1. messages ํด๋”์— ์–ธ์–ด๋ณ„ json ํŒŒ์ผ์„ ์ƒ์„ฑํ•œ๋‹ค.
 
// en.json
"Home": {
    "message" : "hello",
}
 
// ko.json
"Home": {
    "message" : "์•ˆ๋…•ํ•˜์„ธ์š”",
}

๋™์ผํ•œ key ๊ฐ’๊ณผ ๋งค์นญ๋˜๋Š” value๋ฅผ ์ €์žฅํ•ด์ฃผ์—ˆ๋‹ค.

  1. next.config.ts
import {NextConfig} from 'next';
import createNextIntlPlugin from 'next-intl/plugin';
 
const nextConfig: NextConfig = {};
 
const withNextIntl = createNextIntlPlugin();
export default withNextIntl(nextConfig);
  1. routing.ts
import {defineRouting} from 'next-intl/routing';
import {createNavigation} from 'next-intl/navigation';
 
export const routing = defineRouting({
  // A list of all locales that are supported
  locales: ['en', 'ko'],
 
  // Used when no locale matches
  defaultLocale: 'ko'
});
  1. navigation.ts
import {createNavigation} from 'next-intl/navigation';
import {routing} from './routing';
 
// Lightweight wrappers around Next.js' navigation
// APIs that consider the routing configuration
export const {Link, redirect, usePathname, useRouter, getPathname} =
  createNavigation(routing);

๊ธฐ์กด์— ์‚ฌ์šฉํ•˜๋˜ navigation๋“ค๋„ next-intl/navigation์œผ๋กœ ๋ฐ”๊ฟ”์ฃผ์—ˆ๋‹ค.

  1. middleware.ts
import createMiddleware from 'next-intl/middleware';
import {routing} from './i18n/routing';
 
export default createMiddleware(routing);
 
export const config = {
  matcher: [
	'/', // ๋ฃจํŠธ ๊ฒฝ๋กœ
	'/(ko|en)/:path*', // ๊ตญ์ œํ™” ๊ฒฝ๋กœ
	'/((?!api|_next/static|_next/image|favicon.ico).*)'
]
};

๊ธฐ์กด ๋ฏธ๋“ค์›จ์–ด๋„ ์ˆ˜์ •ํ•˜์—ฌ ๊ธฐ์กด ๊ฒฝ๋กœ๋กœ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ๋“ค์„ ๊ตญ์ œํ™” ๊ฒฝ๋กœ๋กœ ๋งคํ•‘๋˜๋„๋ก ์ˆ˜์ •ํ•˜์˜€๋‹ค.

  1. request.ts
import {getRequestConfig} from 'next-intl/server';
import {hasLocale} from 'next-intl';
import {routing} from './routing';
 
export default getRequestConfig(async ({requestLocale}) => {
  // Typically corresponds to the `[locale]` segment
  const requested = await requestLocale;
  const locale = hasLocale(routing.locales, requested)
    ? requested
    : routing.defaultLocale;
 
  return {
    locale,
    messages: (await import(`../../messages/${locale}.json`)).default
  };
});
  1. ํŽ˜์ด์ง€์—์„œ ์‚ฌ์šฉ
import {useTranslations} from 'next-intl';
const t = useTranslations("Home")

useTranslations๋ฅผ Importํ•˜๊ณ  ๋ถˆ๋Ÿฌ์˜ฌ json์˜ key๊ฐ’์œผ๋กœ ์„ค์ •ํ•ด์ค€๋‹ค.

{t('message')}

์ด๋Ÿฐ ์‹์œผ๋กœ ์‚ฌ์šฉํ•˜๋ฉด ๋์ด๋‹ค. ํ˜„์žฌ ์„ค์ •๋œ locale์— ๋”ฐ๋ผ json ํŒŒ์ผ์—์„œ ๊ฐ’์„ ์ฝ์–ด์˜จ๋‹ค.

๊ฐœ์„ ๋œ ์ 

๊ธฐ์กด ์ฒซ html ๋ Œ๋”๋ง 84ms์—, ๋ฒˆ์—ญ ์‹œ๊ฐ„ 400ms ์ •๋„ ๊ฑธ๋ฆฌ๋˜ ์‹œ๊ฐ„์„ 47.11ms๋กœ ๋‹จ์ถ•ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. ๋˜ ์˜์–ด๋„ ๋ฒˆ์—ญ๊ธฐ ์˜์–ด๊ฐ€ ์•„๋‹Œ ์ง์ ‘ ์˜์–ด ๋ฌธ๋‹จ์„ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

๋งˆ๋ฌด๋ฆฌ

Next.js๋Š” ๋„ˆ๋ฌด ์˜ค๋žœ๋งŒ์ด๋ผ ๊ธฐ์ˆ  ๋„์ž…์— ์‹œ๊ฐ„์ด ์ข€ ๊ฑธ๋ฆฐ ๊ฒƒ ๊ฐ™๋‹ค.. ๋˜ Next.js๋Š” ํ”„๋ก ํŠธ๋กœ๋งŒ ์‚ฌ์šฉํ•ด๋ดค์ง€ ๋ฐฑ์—”๋“œ ๋กœ์ง๊นŒ์ง€ ํ•จ๊ป˜ ์žˆ๋Š” ํ”„๋กœ์ ํŠธ๋Š” ์ฒ˜์Œ์ด์—ˆ์ง€๋งŒ ์–ด๋–ค ์‹์œผ๋กœ ๊ตฌ์„ฑ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. ๋˜ ๋‚ด๊ฐ€ ๋งŒ๋“  ์‚ฌ์ดํŠธ๊ฐ€ ์‹ค์ œ๋กœ ์šด์˜๋œ๋‹ค๋Š” ์ ์ด ์ œ์ผ ์‹ ๊ธฐํ•˜๊ณ  ์žฌ๋ฏธ์žˆ์—ˆ๋‹ค. ๊ธฐํšŒ๊ฐ€ ๋œ๋‹ค๋ฉด Next.js์™€ TypeScript๋„ ๋ฐฐ์›Œ์„œ ํ’€์Šคํƒ์œผ๋กœ ์ „ํ–ฅํ•˜๊ณ  ์‹ถ๋‹ค.