75 lines
2.9 KiB
JavaScript
75 lines
2.9 KiB
JavaScript
let visitorCountryPromise = null;
|
|||
|
|
const IP_LOOKUP_TIMEOUT_MS = 3500;
|
||
|
|
|
||
|
|
async function fetchWithTimeout(url, options = {}) {
|
||
|
|
const controller = new AbortController();
|
||
|
|
const timeout = window.setTimeout(() => controller.abort(), IP_LOOKUP_TIMEOUT_MS);
|
||
|
|
try {
|
||
|
|
return await fetch(url, { ...options, signal: controller.signal });
|
||
|
|
} finally {
|
||
|
|
window.clearTimeout(timeout);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function countryFromCloudflare() {
|
||
|
|
for (const url of ["/cdn-cgi/trace", "https://www.cloudflare.com/cdn-cgi/trace"]) {
|
||
|
|
try {
|
||
|
|
const response = await fetchWithTimeout(url, { credentials: "omit", headers: { Accept: "text/plain" }, mode: "cors" });
|
||
|
|
if (!response.ok) continue;
|
||
|
|
const country = String((await response.text()).match(/^loc=([A-Z]{2})$/m)?.[1] || "").toUpperCase();
|
||
|
|
if (country) return country;
|
||
|
|
} catch {}
|
||
|
|
}
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
|
||
|
|
async function countryFromCountryIs() {
|
||
|
|
const response = await fetchWithTimeout("https://api.country.is/", { credentials: "omit", mode: "cors" });
|
||
|
|
if (!response.ok) return "";
|
||
|
|
return String((await response.json())?.country || "").toUpperCase();
|
||
|
|
}
|
||
|
|
|
||
|
|
async function countryFromIpWho() {
|
||
|
|
const response = await fetchWithTimeout("https://ipwho.is/?fields=success,country_code", {
|
||
|
|
credentials: "omit",
|
||
|
|
headers: { Accept: "application/json" },
|
||
|
|
mode: "cors",
|
||
|
|
});
|
||
|
|
if (!response.ok) return "";
|
||
|
|
const payload = await response.json();
|
||
|
|
return payload?.success ? String(payload.country_code || "").toUpperCase() : "";
|
||
|
|
}
|
||
|
|
|
||
|
|
export function detectVisitorCountry() {
|
||
|
|
if (visitorCountryPromise) return visitorCountryPromise;
|
||
|
|
if (typeof window === "undefined" || typeof fetch !== "function") return Promise.resolve("");
|
||
|
|
visitorCountryPromise = (async () => {
|
||
|
|
for (const detector of [countryFromCloudflare, countryFromCountryIs, countryFromIpWho]) {
|
||
|
|
try {
|
||
|
|
const country = await detector();
|
||
|
|
if (/^[A-Z]{2}$/.test(country)) return country;
|
||
|
|
} catch (error) {
|
||
|
|
console.debug("[家纺跨境情报] IP 区域检测源不可用,继续尝试备用源:", error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return "UNKNOWN";
|
||
|
|
})();
|
||
|
|
return visitorCountryPromise;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function visitorCanUseGoogleFallback() {
|
||
|
|
if (typeof navigator === "undefined" || typeof Intl === "undefined") return false;
|
||
|
|
const language = String(navigator.languages?.[0] || navigator.language || "").toLowerCase();
|
||
|
|
const timeZone = String(Intl.DateTimeFormat().resolvedOptions().timeZone || "");
|
||
|
|
const nonChineseLanguage = Boolean(language) && !language.startsWith("zh");
|
||
|
|
const outsideChinaTimeZone = Boolean(timeZone) && !["Asia/Shanghai", "Asia/Urumqi"].includes(timeZone);
|
||
|
|
return nonChineseLanguage || outsideChinaTimeZone;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function translationProvider(countryCode, googleFallback = visitorCanUseGoogleFallback()) {
|
||
|
|
if (countryCode === "CN") return "yandex";
|
||
|
|
if (countryCode && countryCode !== "UNKNOWN") return "google";
|
||
|
|
if ((!countryCode || countryCode === "UNKNOWN") && googleFallback) return "google";
|
||
|
|
return "yandex";
|
||
|
|
}
|