feat: localstorage与SecureStore
This commit is contained in:
parent
6ab9f5db6a
commit
9939fd23aa
7
app.json
7
app.json
@ -34,6 +34,13 @@
|
|||||||
"resizeMode": "contain",
|
"resizeMode": "contain",
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#ffffff"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"expo-secure-store",
|
||||||
|
{
|
||||||
|
"configureAndroidBackup": true,
|
||||||
|
"faceIDPermission": "Allow $(PRODUCT_NAME) to access your Face ID biometric data."
|
||||||
|
}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"experiments": {
|
"experiments": {
|
||||||
|
|||||||
@ -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';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type LanguageContextType = {
|
type LanguageContextType = {
|
||||||
currentLanguage: string;
|
currentLanguage: string;
|
||||||
changeLanguage: (lang: string) => void;
|
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<string | null> {
|
||||||
|
if (Platform.OS === 'web') {
|
||||||
|
return localStorage.getItem(key);
|
||||||
|
} else {
|
||||||
|
return await SecureStore.getItemAsync(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
const LanguageContext = createContext<LanguageContextType | undefined>(undefined);
|
const LanguageContext = createContext<LanguageContextType | undefined>(undefined);
|
||||||
|
|
||||||
export const LanguageProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
export const LanguageProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
// 从localStorage获取保存的语言设置,如果没有则使用当前语言或默认为'en'
|
const [currentLanguage, setCurrentLanguage] = useState(i18n.language || 'en');
|
||||||
const savedLanguage = localStorage.getItem('i18nextLng') || i18n.language || 'en';
|
|
||||||
const [currentLanguage, setCurrentLanguage] = useState(savedLanguage);
|
// 初始化时加载保存的语言设置
|
||||||
|
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) => {
|
const changeLanguage = (lang: string) => {
|
||||||
i18n.changeLanguage(lang);
|
i18n.changeLanguage(lang);
|
||||||
// 将语言设置保存到localStorage
|
// 保存语言设置
|
||||||
localStorage.setItem('i18nextLng', lang);
|
save('i18nextLng', lang).catch(error => {
|
||||||
|
console.error('Failed to save language setting:', error);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 确保初始化时使用正确的语言
|
|
||||||
if (savedLanguage && i18n.language !== savedLanguage) {
|
|
||||||
i18n.changeLanguage(savedLanguage);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleLanguageChanged = () => {
|
const handleLanguageChanged = () => {
|
||||||
setCurrentLanguage(i18n.language);
|
const newLanguage = i18n.language;
|
||||||
// 将更改后的语言保存到localStorage
|
setCurrentLanguage(newLanguage);
|
||||||
localStorage.setItem('i18nextLng', i18n.language);
|
// 将更改后的语言保存到存储
|
||||||
|
save('i18nextLng', newLanguage).catch(error => {
|
||||||
|
console.error('Failed to save language setting:', error);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
i18n.on('languageChanged', handleLanguageChanged);
|
i18n.on('languageChanged', handleLanguageChanged);
|
||||||
@ -37,7 +70,7 @@ export const LanguageProvider: React.FC<{ children: React.ReactNode }> = ({ chil
|
|||||||
return () => {
|
return () => {
|
||||||
i18n.off('languageChanged', handleLanguageChanged);
|
i18n.off('languageChanged', handleLanguageChanged);
|
||||||
};
|
};
|
||||||
}, [i18n, savedLanguage]);
|
}, [i18n]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LanguageContext.Provider value={{ currentLanguage, changeLanguage }}>
|
<LanguageContext.Provider value={{ currentLanguage, changeLanguage }}>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import i18n from 'i18next';
|
import i18n from 'i18next';
|
||||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||||
import { initReactI18next } from 'react-i18next';
|
import { initReactI18next } from 'react-i18next';
|
||||||
|
import { Platform } from 'react-native';
|
||||||
// 自动生成的导入
|
// 自动生成的导入
|
||||||
import translations from './translations-generated';
|
import translations from './translations-generated';
|
||||||
|
|
||||||
@ -28,13 +29,30 @@ i18n
|
|||||||
escapeValue: false, // 不需要转义,React已经处理了
|
escapeValue: false, // 不需要转义,React已经处理了
|
||||||
},
|
},
|
||||||
|
|
||||||
detection: {
|
detection: Platform.OS === 'web' ? {
|
||||||
|
// Web 环境使用 localStorage
|
||||||
order: ['localStorage', 'navigator'],
|
order: ['localStorage', 'navigator'],
|
||||||
caches: ['localStorage'],
|
caches: ['localStorage'],
|
||||||
lookupLocalStorage: 'i18nextLng',
|
lookupLocalStorage: 'i18nextLng',
|
||||||
// 确保语言设置被保存到localStorage
|
|
||||||
lookupFromPathIndex: 0,
|
lookupFromPathIndex: 0,
|
||||||
lookupFromSubdomainIndex: 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) // 使用类型断言绕过类型检查
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
10
package-lock.json
generated
10
package-lock.json
generated
@ -23,6 +23,7 @@
|
|||||||
"expo-linking": "~7.1.5",
|
"expo-linking": "~7.1.5",
|
||||||
"expo-localization": "^16.1.5",
|
"expo-localization": "^16.1.5",
|
||||||
"expo-router": "~5.1.0",
|
"expo-router": "~5.1.0",
|
||||||
|
"expo-secure-store": "~14.2.3",
|
||||||
"expo-splash-screen": "~0.30.9",
|
"expo-splash-screen": "~0.30.9",
|
||||||
"expo-status-bar": "~2.2.3",
|
"expo-status-bar": "~2.2.3",
|
||||||
"expo-symbols": "~0.4.5",
|
"expo-symbols": "~0.4.5",
|
||||||
@ -7262,6 +7263,15 @@
|
|||||||
"node": ">=10"
|
"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": {
|
"node_modules/expo-splash-screen": {
|
||||||
"version": "0.30.9",
|
"version": "0.30.9",
|
||||||
"resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-0.30.9.tgz",
|
"resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-0.30.9.tgz",
|
||||||
|
|||||||
@ -46,7 +46,8 @@
|
|||||||
"react-native-safe-area-context": "^5.4.0",
|
"react-native-safe-area-context": "^5.4.0",
|
||||||
"react-native-screens": "~4.11.1",
|
"react-native-screens": "~4.11.1",
|
||||||
"react-native-web": "~0.20.0",
|
"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": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.25.2",
|
||||||
@ -61,4 +62,4 @@
|
|||||||
"typescript": "~5.8.3"
|
"typescript": "~5.8.3"
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user