From 9939fd23aa81a3f7b4bb94fc564a51650a0797f3 Mon Sep 17 00:00:00 2001 From: jinyaqiu Date: Mon, 23 Jun 2025 11:23:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20localstorage=E4=B8=8ESecureStore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.json | 7 +++++ i18n/LanguageContext.tsx | 63 ++++++++++++++++++++++++++++++---------- i18n/index.ts | 22 ++++++++++++-- package-lock.json | 10 +++++++ package.json | 5 ++-- 5 files changed, 88 insertions(+), 19 deletions(-) diff --git a/app.json b/app.json index ccc4545..7e058a3 100644 --- a/app.json +++ b/app.json @@ -34,6 +34,13 @@ "resizeMode": "contain", "backgroundColor": "#ffffff" } + ], + [ + "expo-secure-store", + { + "configureAndroidBackup": true, + "faceIDPermission": "Allow $(PRODUCT_NAME) to access your Face ID biometric data." + } ] ], "experiments": { diff --git a/i18n/LanguageContext.tsx b/i18n/LanguageContext.tsx index 2d195ca..a3fbd0c 100644 --- a/i18n/LanguageContext.tsx +++ b/i18n/LanguageContext.tsx @@ -1,35 +1,68 @@ -import React, { createContext, useState, useContext, useEffect } from 'react'; +import * as SecureStore from 'expo-secure-store'; +import { Platform } from 'react-native'; +import React, { createContext, useContext, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; type LanguageContextType = { currentLanguage: string; changeLanguage: (lang: string) => void; }; +// 保存语言设置 +async function save(key: string, value: string) { + if (Platform.OS === 'web') { + localStorage.setItem(key, value); + } else { + await SecureStore.setItemAsync(key, value); + } +} +// 获取语言设置 +async function load(key: string): Promise { + if (Platform.OS === 'web') { + return localStorage.getItem(key); + } else { + return await SecureStore.getItemAsync(key); + } +} const LanguageContext = createContext(undefined); export const LanguageProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const { i18n } = useTranslation(); - // 从localStorage获取保存的语言设置,如果没有则使用当前语言或默认为'en' - const savedLanguage = localStorage.getItem('i18nextLng') || i18n.language || 'en'; - const [currentLanguage, setCurrentLanguage] = useState(savedLanguage); + const [currentLanguage, setCurrentLanguage] = useState(i18n.language || 'en'); + + // 初始化时加载保存的语言设置 + useEffect(() => { + const loadSavedLanguage = async () => { + try { + const savedLang = await load('i18nextLng'); + if (savedLang) { + setCurrentLanguage(savedLang); + i18n.changeLanguage(savedLang); + } + } catch (error) { + console.error('Failed to load language setting:', error); + } + }; + + loadSavedLanguage(); + }, [i18n]); const changeLanguage = (lang: string) => { i18n.changeLanguage(lang); - // 将语言设置保存到localStorage - localStorage.setItem('i18nextLng', lang); + // 保存语言设置 + save('i18nextLng', lang).catch(error => { + console.error('Failed to save language setting:', error); + }); }; useEffect(() => { - // 确保初始化时使用正确的语言 - if (savedLanguage && i18n.language !== savedLanguage) { - i18n.changeLanguage(savedLanguage); - } - const handleLanguageChanged = () => { - setCurrentLanguage(i18n.language); - // 将更改后的语言保存到localStorage - localStorage.setItem('i18nextLng', i18n.language); + const newLanguage = i18n.language; + setCurrentLanguage(newLanguage); + // 将更改后的语言保存到存储 + save('i18nextLng', newLanguage).catch(error => { + console.error('Failed to save language setting:', error); + }); }; i18n.on('languageChanged', handleLanguageChanged); @@ -37,7 +70,7 @@ export const LanguageProvider: React.FC<{ children: React.ReactNode }> = ({ chil return () => { i18n.off('languageChanged', handleLanguageChanged); }; - }, [i18n, savedLanguage]); + }, [i18n]); return ( diff --git a/i18n/index.ts b/i18n/index.ts index 6ddfeb8..8b5ff26 100644 --- a/i18n/index.ts +++ b/i18n/index.ts @@ -1,6 +1,7 @@ import i18n from 'i18next'; import LanguageDetector from 'i18next-browser-languagedetector'; import { initReactI18next } from 'react-i18next'; +import { Platform } from 'react-native'; // 自动生成的导入 import translations from './translations-generated'; @@ -28,13 +29,30 @@ i18n escapeValue: false, // 不需要转义,React已经处理了 }, - detection: { + detection: Platform.OS === 'web' ? { + // Web 环境使用 localStorage order: ['localStorage', 'navigator'], caches: ['localStorage'], lookupLocalStorage: 'i18nextLng', - // 确保语言设置被保存到localStorage lookupFromPathIndex: 0, lookupFromSubdomainIndex: 0 + } : { + // 原生环境使用自定义存储 + order: ['customStorage', 'device'], + caches: ['customStorage'], + // 使用类型断言解决类型问题 + ...({ + lookupCustomStorage: { + getItem: async (key: string) => { + const { getItemAsync } = await import('expo-secure-store'); + return getItemAsync(key); + }, + setItem: async (key: string, value: string) => { + const { setItemAsync } = await import('expo-secure-store'); + await setItemAsync(key, value); + } + } + } as any) // 使用类型断言绕过类型检查 } }); diff --git a/package-lock.json b/package-lock.json index cafbbc1..7e973e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "expo-linking": "~7.1.5", "expo-localization": "^16.1.5", "expo-router": "~5.1.0", + "expo-secure-store": "~14.2.3", "expo-splash-screen": "~0.30.9", "expo-status-bar": "~2.2.3", "expo-symbols": "~0.4.5", @@ -7262,6 +7263,15 @@ "node": ">=10" } }, + "node_modules/expo-secure-store": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-14.2.3.tgz", + "integrity": "sha512-hYBbaAD70asKTFd/eZBKVu+9RTo9OSTMMLqXtzDF8ndUGjpc6tmRCoZtrMHlUo7qLtwL5jm+vpYVBWI8hxh/1Q==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-splash-screen": { "version": "0.30.9", "resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-0.30.9.tgz", diff --git a/package.json b/package.json index a1549fb..9515233 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,8 @@ "react-native-safe-area-context": "^5.4.0", "react-native-screens": "~4.11.1", "react-native-web": "~0.20.0", - "react-native-webview": "13.13.5" + "react-native-webview": "13.13.5", + "expo-secure-store": "~14.2.3" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -61,4 +62,4 @@ "typescript": "~5.8.3" }, "private": true -} \ No newline at end of file +}