- 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>
84 lines
3.0 KiB
TypeScript
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;
|