index.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import i18n from 'i18next';
  2. import { initReactI18next } from 'react-i18next';
  3. import * as Localization from 'expo-localization';
  4. import AsyncStorage from '@react-native-async-storage/async-storage';
  5. import { resources } from './locales';
  6. import { Language, SUPPORTED_LANGUAGES, defaultNS, Locale } from './types';
  7. // AsyncStorage 键名
  8. const LANGUAGE_PREFERENCE_KEY = '@app_language_preference';
  9. // 获取设备区域设置信息
  10. export const getDeviceLocale = (): Locale => {
  11. const locale = Localization.getLocales()[0];
  12. return {
  13. languageCode: (locale.languageCode || 'en') as Language,
  14. languageTag: locale.languageTag || 'en',
  15. regionCode: locale.regionCode || undefined,
  16. // currencyCode: Localization.currency,
  17. // currencySymbol: Localization.currency || '$',
  18. // decimalSeparator: Localization.decimalSeparator || '.',
  19. // digitGroupingSeparator: Localization.digitGroupingSeparator || ',',
  20. textDirection: locale.textDirection || (locale.languageTag?.toLowerCase().includes('ar') ? 'rtl' : 'ltr'),
  21. measurementSystem: locale.regionCode ? 'metric' : 'us',
  22. temperatureUnit: locale.regionCode === 'US' ? 'fahrenheit' : 'celsius',
  23. };
  24. };
  25. // 获取设备语言
  26. export const getDeviceLanguage = (): Language => {
  27. const deviceLocale = getDeviceLocale();
  28. const deviceLanguage = deviceLocale.languageCode;
  29. // 检查是否支持设备语言
  30. const supportedLanguageCodes = SUPPORTED_LANGUAGES.map(lang => lang.code);
  31. const isSupported = supportedLanguageCodes.includes(deviceLanguage);
  32. return isSupported ? deviceLanguage : 'en';
  33. };
  34. // 保存语言偏好
  35. export const saveLanguagePreference = async (language: Language): Promise<void> => {
  36. try {
  37. await AsyncStorage.setItem(LANGUAGE_PREFERENCE_KEY, language);
  38. } catch (error) {
  39. console.error('Failed to save language preference:', error);
  40. }
  41. };
  42. // 获取保存的语言偏好
  43. export const getLanguagePreference = async (): Promise<Language | null> => {
  44. try {
  45. return await AsyncStorage.getItem(LANGUAGE_PREFERENCE_KEY) as Language | null;
  46. } catch (error) {
  47. console.error('Failed to get language preference:', error);
  48. return null;
  49. }
  50. };
  51. // 自定义语言检测器(适配 Expo)
  52. const languageDetector = {
  53. type: 'languageDetector' as const,
  54. async: true,
  55. init: () => {},
  56. detect: async (callback: (lang: Language) => void) => {
  57. try {
  58. // 1. 先尝试获取保存的语言偏好
  59. const savedLanguage = await getLanguagePreference();
  60. if (savedLanguage) {
  61. callback(savedLanguage);
  62. return;
  63. }
  64. // 2. 使用设备语言
  65. const deviceLanguage = getDeviceLanguage();
  66. callback(deviceLanguage);
  67. } catch (error) {
  68. console.error('Language detection failed:', error);
  69. callback('en'); // 默认回退到英语
  70. }
  71. },
  72. cacheUserLanguage: async (language: Language) => {
  73. await saveLanguagePreference(language);
  74. },
  75. };
  76. // i18n 初始化配置
  77. export const initI18n = async () => {
  78. await i18n
  79. .use(languageDetector)
  80. .use(initReactI18next)
  81. .init({
  82. // compatibilityJSON: 'v3', // 重要:解决 React Native 中的复数问题
  83. resources,
  84. fallbackLng: 'zh-TW',
  85. defaultNS,
  86. // 调试配置
  87. debug: __DEV__,
  88. // 插值配置
  89. interpolation: {
  90. escapeValue: false, // React 已经处理了转义
  91. },
  92. // React 配置
  93. react: {
  94. useSuspense: false, // Expo/React Native 中建议关闭
  95. },
  96. // 语言检测配置
  97. detection: {
  98. order: ['localStorage', 'navigator'],
  99. caches: ['localStorage'],
  100. },
  101. // 支持的语言
  102. supportedLngs: SUPPORTED_LANGUAGES.map(lang => lang.code),
  103. // 不加载所有语言,按需加载
  104. partialBundledLanguages: true,
  105. ns: [defaultNS],
  106. });
  107. return i18n;
  108. };
  109. // 语言切换函数
  110. export const changeLanguage = async (language: Language): Promise<boolean> => {
  111. try {
  112. await i18n.changeLanguage(language);
  113. await saveLanguagePreference(language);
  114. return true;
  115. } catch (error) {
  116. console.error('Language change failed:', error);
  117. return false;
  118. }
  119. };
  120. // 获取当前语言
  121. export const getCurrentLanguage = (): Language => {
  122. return (i18n.language || 'en') as Language;
  123. };
  124. // 获取当前区域设置信息
  125. export const getCurrentLocale = (): Locale => {
  126. return getDeviceLocale();
  127. };
  128. // 监听语言变化
  129. export const onLanguageChanged = (callback: (language: Language) => void) => {
  130. i18n.on('languageChanged', callback);
  131. return () => i18n.off('languageChanged', callback);
  132. };
  133. // 初始化 i18n
  134. initI18n();
  135. export default i18n;