๐ŸŒ 48. React Native ๋‹ค๊ตญ์–ด(i18n) ์ง€์› ์™„์ „ ์ •๋ฆฌ โ€” i18next, ์–ธ์–ด ๊ฐ์ง€, ์–ธ์–ด ์ „ํ™˜๊นŒ์ง€

JM_Devยท2025๋…„ 6์›” 10์ผ
0
post-thumbnail

์•ฑ์„ ์ „ ์„ธ๊ณ„๋กœ ๋ฐฐํฌํ•˜๊ฑฐ๋‚˜,
๊ธฐ๋ณธ์ ์œผ๋กœ ํ•œ๊ตญ์–ด/์˜์–ด๋ฅผ ๋ชจ๋‘ ์ง€์›ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด
๋‹ค๊ตญ์–ด ์ฒ˜๋ฆฌ(i18n: Internationalization)๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

์›น์—์„œ๋Š” ๊ฐ„๋‹จํ•œ ๋ฌธ์ž์—ด ์น˜ํ™˜ ์ •๋„์˜€๋˜ ๊ฒŒ,
React Native์—์„œ๋Š” ์–ธ์–ด ๊ฐ์ง€, ๋ณ€๊ฒฝ, ์ƒํƒœ ์ €์žฅ๊นŒ์ง€ ๋” ๊ณ ๋ คํ•ด์•ผ ํ•  ์š”์†Œ๊ฐ€ ๋งŽ๋‹ค.

์ด๋ฒˆ ๊ธ€์€ React Native์—์„œ ๋‹ค๊ตญ์–ด ์ฒ˜๋ฆฌ ๊ตฌ์กฐ๋ฅผ ์ฒ˜์Œ๋ถ€ํ„ฐ ์„ค๊ณ„ํ•˜๊ณ  ์ ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ •๋ฆฌํ•œ ๊ธ€์ด๋‹ค.


โœ… ๊ธฐ๋ณธ i18n ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ: react-i18next + i18next

npm install i18next react-i18next i18next-browser-languagedetector
npm install @react-native-async-storage/async-storage

Expo๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ expo-localization๋„ ํ•จ๊ป˜ ์‚ฌ์šฉ

npx expo install expo-localization

๐Ÿ”ง i18n ์ดˆ๊ธฐ ์„ค์ • (i18n.ts)

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import * as Localization from 'expo-localization';
import AsyncStorage from '@react-native-async-storage/async-storage';

import en from './locales/en.json';
import ko from './locales/ko.json';

i18n
  .use(initReactI18next)
  .init({
    compatibilityJSON: 'v3',
    lng: Localization.locale.split('-')[0], // ex: 'ko', 'en'
    fallbackLng: 'en',
    resources: {
      en: { translation: en },
      ko: { translation: ko },
    },
    interpolation: {
      escapeValue: false,
    },
  });

export default i18n;

๐Ÿ—‚๏ธ ๋‹ค๊ตญ์–ด ๋ฆฌ์†Œ์Šค ์˜ˆ์‹œ

locales/
โ”œโ”€โ”€ en.json
โ””โ”€โ”€ ko.json
// en.json
{
  "greeting": "Hello!",
  "login": {
    "title": "Login",
    "button": "Sign in"
  }
}
// ko.json
{
  "greeting": "์•ˆ๋…•ํ•˜์„ธ์š”!",
  "login": {
    "title": "๋กœ๊ทธ์ธ",
    "button": "๋กœ๊ทธ์ธ"
  }
}

๐Ÿ”ค ์‹ค์ œ ์‚ฌ์šฉ ์˜ˆ์‹œ

import { useTranslation } from 'react-i18next';

const { t } = useTranslation();

<Text>{t('greeting')}</Text>
<Text>{t('login.title')}</Text>

๐ŸŒ ์–ธ์–ด ๋ณ€๊ฒฝ ๊ธฐ๋Šฅ ์ถ”๊ฐ€

const changeLanguage = (lng: 'en' | 'ko') => {
  i18n.changeLanguage(lng);
  AsyncStorage.setItem('lang', lng); // ์„ ํƒํ•œ ์–ธ์–ด ์ €์žฅ
};
  • ์•ฑ ์„ค์ • ํŽ˜์ด์ง€์— ์–ธ์–ด ๋ณ€๊ฒฝ ๋ฒ„ํŠผ UI ์ถ”๊ฐ€
  • ์•ฑ ์žฌ์‹œ์ž‘ ์‹œ AsyncStorage๋กœ ์ €์žฅ๋œ ์–ธ์–ด ์ ์šฉ ๊ฐ€๋Šฅ

๐Ÿง  ์‹ค์ „ ์„ค๊ณ„ ํŒ

ํ•ญ๋ชฉ์ „๋žต
๊ธฐ๋ณธ ์–ธ์–ดexpo-localization or ๋ธŒ๋ผ์šฐ์ € ์–ธ์–ด ๊ธฐ๋ฐ˜ ์„ค์ •
fallback ์–ธ์–ด'en' ๋˜๋Š” 'ko'๋กœ ๋ช…ํ™•ํžˆ ์„ค์ •
๋‹ค๊ตญ์–ด ํ‚ค ์ด๋ฆ„์†Œ๋ฌธ์ž + dot notation (login.title) ์ถ”์ฒœ
ํ…์ŠคํŠธ ๋‹ค์ˆ˜์ผ ๊ฒฝ์šฐuseTranslation() ๋Œ€์‹  Trans ์ปดํฌ๋„ŒํŠธ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
ํ…Œ๋งˆ, ์–ธ์–ด, ๋ชจ๋“œ ๊ฐ™์ด ๊ด€๋ฆฌZustand ๋˜๋Š” Context๋กœ appSettings ๋ถ„๋ฆฌ ๊ฐ€๋Šฅ

๐Ÿ“ ๋‚ด๊ฐ€ ๋А๋‚€ ์ 

๋‹ค๊ตญ์–ด ์ฒ˜๋ฆฌ๋ฅผ ๋‹จ์ˆœํžˆ "๋ฌธ์ž์—ด ๋ฐ”๊พธ๊ธฐ"๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๋Š”๋ฐ,
์‹ค์ œ๋กœ ํ•ด๋ณด๋ฉด ์•ฑ์˜ ์ „๋ฐ˜์ ์ธ ๊ตฌ์กฐ์— ์˜ํ–ฅ์„ ์ฃผ๋Š” ์š”์†Œ๋ผ๋Š” ๊ฑธ ์•Œ๊ฒŒ ๋๋‹ค.

ํŠนํžˆ ์–ธ์–ด ๋ณ€๊ฒฝ ํ›„ ์•ฑ ์ „์ฒด ๋ฆฌ๋ Œ๋”๋ง ์ฒ˜๋ฆฌ,
๋น„๋™๊ธฐ ๋กœ๋”ฉ, ์ €์žฅ ๋ฐฉ์‹ ๋“ฑ์—์„œ ์‹ ๊ฒฝ ์จ์•ผ ํ•  ์ ์ด ๋งŽ์•˜๋‹ค.

์ง€๊ธˆ์€ ์ƒํƒœ ์ €์žฅ์€ AsyncStorage,
์ดˆ๊ธฐ ์–ธ์–ด ๊ฐ์ง€๋Š” expo-localization,
๋ฌธ์ž์—ด์€ json ๊ด€๋ฆฌ๋กœ ์•ˆ์ •์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค.


๐ŸŒ โ€œ์•ฑ์˜ ํ™•์žฅ์„ฑ์€ i18n ์„ค๊ณ„์—์„œ ์‹œ์ž‘๋œ๋‹ค.โ€


profile
๊ฐœ๋ฐœ์ž๋กœ ์ทจ์—…์„ ์ค€๋น„ ์ค‘ ์ด๋ฉฐ, ์—ด์‹ฌํžˆ ๊ณต๋ถ€ ์ค‘ ์ž…๋‹ˆ๋‹ค!

0๊ฐœ์˜ ๋Œ“๊ธ€