kintex/mobile/lib/i18n/index.ts
zio 490b723a45 chore(mobile): v0.2.0 source snapshot for reproducibility
- app.json: version 0.2.0 / versionCode 2, drop expo-screen-capture plugin
- add lib/i18n (ko/en/ja/zh), lib/roleTrack, LanguageContext + LanguageSelector
- profile/security hardening across screens (login/register/forgot/profile/tabs)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 00:29:38 +09:00

84 lines
3.0 KiB
TypeScript

/*
* i18n 초기화 — react-i18next + expo-localization.
* · 지원 언어: ko(기본/폴백)·en·zh·ja
* · 최초 렌더 전 동기 초기화(initImmediate:false) — expo-localization getLocales()는 동기.
* · 사용자가 고른 언어는 AsyncStorage 키 `kintex_lang`에 지속. 없으면 디바이스 로케일 감지.
* 보안: 번역 리소스에 시크릿/자격증명 없음. 외부 네트워크 로드 없음(리소스 번들 내장).
*/
import AsyncStorage from '@react-native-async-storage/async-storage';
import { getLocales } from 'expo-localization';
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import { en } from './locales/en';
import { ja } from './locales/ja';
import { ko } from './locales/ko';
import { zh } from './locales/zh';
import { DEFAULT_LANG, SUPPORTED_LANGS, type Lang } from './types';
export const LANG_STORAGE_KEY = 'kintex_lang';
export const resources = {
ko: { translation: ko },
en: { translation: en },
zh: { translation: zh },
ja: { translation: ja },
} as const;
/** 지원 언어로 정규화(미지원 → 기본). 'zh-Hans' 등 지역 태그는 앞 2자로 축약. */
export function normalizeLang(code: string | null | undefined): Lang {
if (!code) return DEFAULT_LANG;
const base = code.toLowerCase().split(/[-_]/)[0];
return (SUPPORTED_LANGS as readonly string[]).includes(base) ? (base as Lang) : DEFAULT_LANG;
}
/** 디바이스 로케일(동기) → 지원 언어. */
function detectDeviceLang(): Lang {
try {
const locales = getLocales();
return normalizeLang(locales?.[0]?.languageCode ?? locales?.[0]?.languageTag);
} catch {
return DEFAULT_LANG;
}
}
// ── 동기 초기화(디바이스 로케일 기준). 저장된 사용자 선택은 부팅 직후 loadStoredLanguage로 반영. ──
if (!i18n.isInitialized) {
i18n.use(initReactI18next).init({
resources,
lng: detectDeviceLang(),
fallbackLng: DEFAULT_LANG,
supportedLngs: SUPPORTED_LANGS as unknown as string[],
defaultNS: 'translation',
interpolation: { escapeValue: false },
returnNull: false,
initImmediate: false, // 최초 렌더에서 t()가 즉시 동작하도록 동기 초기화
});
}
/** 저장된 사용자 선택 언어를 로드해 적용(부팅 시 1회). 없으면 디바이스 언어 유지. */
export async function loadStoredLanguage(): Promise<Lang> {
try {
const saved = await AsyncStorage.getItem(LANG_STORAGE_KEY);
if (saved) {
const lang = normalizeLang(saved);
if (i18n.language !== lang) await i18n.changeLanguage(lang);
return lang;
}
} catch {
/* graceful — 디바이스 언어 유지 */
}
return normalizeLang(i18n.language);
}
/** 언어 변경 + 지속. 셀렉터 UI에서 호출. */
export async function setAppLanguage(lang: Lang): Promise<void> {
await i18n.changeLanguage(lang);
try {
await AsyncStorage.setItem(LANG_STORAGE_KEY, lang);
} catch {
/* graceful — 메모리 상 언어는 이미 반영됨 */
}
}
export default i18n;