import AsyncStorage from '@react-native-async-storage/async-storage'; import * as Localization from 'expo-localization'; import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; // 用 require 避免 TS 的 json module 配置差异导致无法编译 // eslint-disable-next-line @typescript-eslint/no-var-requires const all = require('./locales/all.json') as { en: Record; 'zh-TW': Record }; /** * 语言码约定: * - 繁体中文:zh-TW * - 英语:en */ export type AppLanguage = 'zh-TW' | 'en'; export const SUPPORTED_LANGUAGES: readonly AppLanguage[] = [ 'zh-TW', 'en', ] as const; const DEFAULT_FALLBACK_LANGUAGE: AppLanguage = 'en'; const STORAGE_KEY_LANGUAGE = 'settings.language'; function isSupportedLanguage(lang: string): lang is AppLanguage { return (SUPPORTED_LANGUAGES as readonly string[]).includes(lang); } function normalizeDeviceLanguageTagToAppLanguage(languageTag: string): AppLanguage { const tag = languageTag.toLowerCase(); // 中文:当前仅支持繁体中文(zh-TW) if (tag.startsWith('zh')) { return 'zh-TW'; } // 其他语言:按前缀匹配(当前仅支持英文) if (tag.startsWith('en')) return 'en'; return DEFAULT_FALLBACK_LANGUAGE; } function getDeviceLanguage(): AppLanguage { // expo-localization 返回系统 locale 列表,取第一个作为当前设备偏好 const locales = Localization.getLocales(); const first = locales?.[0]?.languageTag; if (first) { return normalizeDeviceLanguageTagToAppLanguage(first); } // 兜底:少数情况下 locales 为空 return DEFAULT_FALLBACK_LANGUAGE; } export async function getLanguagePreference(): Promise { const value = await AsyncStorage.getItem(STORAGE_KEY_LANGUAGE); if (!value) return null; return isSupportedLanguage(value) ? value : null; } export async function setLanguagePreference(lang: AppLanguage): Promise { await AsyncStorage.setItem(STORAGE_KEY_LANGUAGE, lang); } export async function clearLanguagePreference(): Promise { await AsyncStorage.removeItem(STORAGE_KEY_LANGUAGE); } /** * 初始化 i18n(只会初始化一次) * * 语言选择优先级: * 1) 用户设置(若存在) * 2) 设备语言(在支持列表内时生效;否则会被 normalize 到默认回退) * 3) 默认回退(en) */ export async function initI18n(): Promise { if (i18n.isInitialized) return; const userLang = await getLanguagePreference(); const deviceLang = getDeviceLanguage(); const initialLang = userLang ?? deviceLang; await i18n.use(initReactI18next).init({ resources: { 'zh-TW': { translation: all['zh-TW'] as any }, en: { translation: all.en as any }, }, lng: initialLang, fallbackLng: DEFAULT_FALLBACK_LANGUAGE, interpolation: { // React Native 不需要对内容做 HTML 转义 escapeValue: false, }, }); } /** * 用户手动切换语言:立即生效 + 持久化 */ export async function changeLanguage(lang: AppLanguage): Promise { await setLanguagePreference(lang); await i18n.changeLanguage(lang); }