feat: 发布三笙AI情报局独立版公司研发基线
同步家纺情报 v2 数据、59 个严格权威源、趋势图库、移动端交互、Google/Yandex 翻译门控及静态 SEO/GEO 构建。 补充面向 C# 研发的 README,明确当前无 LLM、多模态与生图模型,记录翻译请求参数、缓存与失败策略、数据处理边界、前端职责和无 GitHub Pages 部署流程。 新增按日期倒序维护的 CHANGELOG,记录影响、验证与代码/数据/dist 回滚方法。 验证:11 个 Python 契约测试、13 个 Node 前端测试通过;静态构建生成 1096 个 Sitemap URL。
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
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";
|
||||
}
|
||||
Reference in New Issue
Block a user