feat: add deploy script
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||
import { translations, Language, TranslationKey } from '../lib/translations';
|
||||
import { detectLanguageByIP } from '../lib/ipLocation';
|
||||
|
||||
interface LanguageContextType {
|
||||
language: Language;
|
||||
@@ -10,12 +11,36 @@ interface LanguageContextType {
|
||||
const LanguageContext = createContext<LanguageContextType | undefined>(undefined);
|
||||
|
||||
export const LanguageProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
// 初始化语言:优先使用 localStorage,否则使用浏览器语言作为临时值
|
||||
const [language, setLanguageState] = useState<Language>(() => {
|
||||
const saved = localStorage.getItem('language');
|
||||
if (saved === 'en' || saved === 'zh') return saved;
|
||||
// 临时使用浏览器语言,后续会被IP检测覆盖(如果没有保存的语言)
|
||||
return navigator.language.startsWith('zh') ? 'zh' : 'en';
|
||||
});
|
||||
|
||||
// 检测IP地理位置并设置语言(仅在首次加载且没有保存的语言时)
|
||||
useEffect(() => {
|
||||
const saved = localStorage.getItem('language');
|
||||
|
||||
// 如果用户已经手动选择过语言,则不进行IP检测
|
||||
if (saved === 'en' || saved === 'zh') {
|
||||
return;
|
||||
}
|
||||
|
||||
// 异步检测IP并设置语言
|
||||
detectLanguageByIP()
|
||||
.then((detectedLang) => {
|
||||
setLanguageState(detectedLang);
|
||||
// 注意:这里不保存到 localStorage,让用户首次访问时使用IP检测的结果
|
||||
// 如果用户手动切换语言,才会保存到 localStorage
|
||||
})
|
||||
.catch((error) => {
|
||||
// IP检测失败时,保持使用浏览器语言检测的结果
|
||||
console.warn('Failed to detect language by IP:', error);
|
||||
});
|
||||
}, []); // 仅在组件挂载时执行一次
|
||||
|
||||
const setLanguage = (lang: Language) => {
|
||||
setLanguageState(lang);
|
||||
localStorage.setItem('language', lang);
|
||||
|
||||
78
src/lib/ipLocation.ts
Normal file
78
src/lib/ipLocation.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* IP 地理位置检测工具
|
||||
* 用于根据用户IP地址判断语言偏好
|
||||
*/
|
||||
|
||||
interface IPLocationResponse {
|
||||
country_code?: string;
|
||||
country?: string;
|
||||
error?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据IP地址检测用户所在国家/地区
|
||||
* 使用免费的 ipapi.co 服务(无需API key)
|
||||
*
|
||||
* @returns Promise<string | null> 返回国家代码(如 'CN', 'US'),失败返回 null
|
||||
*/
|
||||
export async function detectCountryByIP(): Promise<string | null> {
|
||||
try {
|
||||
// 使用 ipapi.co 免费服务(无需API key,有速率限制但足够使用)
|
||||
const response = await fetch('https://ipapi.co/json/', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.warn('IP location detection failed:', response.status);
|
||||
return null;
|
||||
}
|
||||
|
||||
const data: IPLocationResponse = await response.json();
|
||||
|
||||
if (data.error || !data.country_code) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return data.country_code.toUpperCase();
|
||||
} catch (error) {
|
||||
// 静默失败,不影响用户体验
|
||||
console.warn('IP location detection error:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据国家代码判断应该使用的语言
|
||||
*
|
||||
* @param countryCode 国家代码(如 'CN', 'TW', 'HK', 'SG' 等)
|
||||
* @returns 'zh' | 'en' 推荐的语言
|
||||
*/
|
||||
export function getLanguageByCountry(countryCode: string | null): 'zh' | 'en' {
|
||||
if (!countryCode) {
|
||||
return 'en';
|
||||
}
|
||||
|
||||
// 中文地区列表
|
||||
const chineseRegions = [
|
||||
'CN', // 中国大陆
|
||||
'TW', // 台湾
|
||||
'HK', // 香港
|
||||
'MO', // 澳门
|
||||
'SG', // 新加坡(主要使用中文)
|
||||
];
|
||||
|
||||
return chineseRegions.includes(countryCode) ? 'zh' : 'en';
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测用户IP并返回推荐语言
|
||||
*
|
||||
* @returns Promise<'zh' | 'en'> 推荐的语言
|
||||
*/
|
||||
export async function detectLanguageByIP(): Promise<'zh' | 'en'> {
|
||||
const countryCode = await detectCountryByIP();
|
||||
return getLanguageByCountry(countryCode);
|
||||
}
|
||||
Reference in New Issue
Block a user