Files
planet/frontend/public/earth/js/i18n.js
linkong 5bdb55f3f1
Some checks failed
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / delivery (push) Has been cancelled
release / images (push) Has been cancelled
release: bump version to 0.74.0
2026-06-30 13:52:52 +08:00

1642 lines
74 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const DEFAULT_LOCALE = "zh-CN";
const LOCALE_STORAGE_KEY = "planet-locale";
const LEGACY_DOCS_LANG_STORAGE_KEY = "docs-lang";
const LOCALE_CHANGE_EVENT = "earth:locale-change";
const LOCALE_LABELS = {
"zh-CN": "中文",
"en-US": "English",
};
const EARTH_MESSAGE_MARKER = "__planetEarthMessage";
function localizedParam(value, locale) {
if (isEarthMessage(value)) return formatEarthMessage(value, locale);
return translateText(String(value ?? ""), locale);
}
const EARTH_MESSAGE_TEMPLATES = {
"loading.default": {
zh: "正在加载...",
en: "Loading...",
},
"loading.initializing": {
zh: "正在初始化...",
en: "Initializing...",
},
"loading.cableData": {
zh: "正在加载线缆数据...",
en: "Loading cable data...",
},
"loading.vesselData": {
zh: "正在加载船只数据...",
en: "Loading vessel data...",
},
"loading.satelliteData": {
zh: "正在加载卫星数据...",
en: "Loading satellite data...",
},
"loading.realTerrainData": {
zh: "正在加载真实地形数据...",
en: "Loading real terrain data...",
},
"startup.landingPoints": {
zh: "正在加载登陆点...",
en: "Loading landing points...",
},
"startup.cables": {
zh: "正在加载海缆...",
en: "Loading subsea cables...",
},
"startup.satellites": {
zh: "正在加载卫星...",
en: "Loading satellites...",
},
"startup.vessels": {
zh: "正在加载船只...",
en: "Loading vessels...",
},
"startup.bgp": {
zh: "正在加载BGP态势...",
en: "Loading BGP situation...",
},
"startup.clouds": {
zh: "正在加载大气云图...",
en: "Loading atmosphere and clouds...",
},
"startup.landOceanBase": {
zh: "正在加载海陆基座...",
en: "Loading land/ocean base...",
},
"startup.computeCenters": {
zh: "正在加载算力中心...",
en: "Loading compute centers...",
},
"startup.hdTexture": {
zh: "正在启用高清材质...",
en: "Enabling HD texture...",
},
"startup.terrain": {
zh: "正在渲染地形...",
en: "Rendering terrain...",
},
"status.dataLoaded": {
zh: "数据已加载",
en: "Data loaded",
},
"status.layoutExpanded": {
zh: ({ expanded }) => (expanded ? "布局已最大化" : "布局已恢复"),
en: ({ expanded }) => (expanded ? "Layout maximized" : "Layout restored"),
},
"status.layerUnavailable": {
zh: ({ layer }) => `${localizedParam(layer, "zh-CN")}当前不可用`,
en: ({ layer }) => `${localizedParam(layer, "en-US")} unavailable`,
},
"status.layerVisibility": {
zh: ({ layer, visible }) => `${localizedParam(layer, "zh-CN")}${visible ? "已显示" : "已隐藏"}`,
en: ({ layer, visible }) => `${localizedParam(layer, "en-US")} ${visible ? "shown" : "hidden"}`,
},
"status.layerEnabled": {
zh: ({ layer, enabled }) => `${localizedParam(layer, "zh-CN")}${enabled ? "已启用" : "已隐藏"}`,
en: ({ layer, enabled }) => `${localizedParam(layer, "en-US")} ${enabled ? "enabled" : "hidden"}`,
},
"status.loadedCables": {
zh: ({ count }) => `成功加载 ${count} 条电缆`,
en: ({ count }) => `Loaded ${count} cables`,
},
"status.loadedLandingPoints": {
zh: ({ count }) => `成功加载 ${count} 个登陆点`,
en: ({ count }) => `Loaded ${count} landing points`,
},
"status.locked": {
zh: ({ name }) => `已锁定: ${name}`,
en: ({ name }) => `Locked: ${name}`,
},
"status.located": {
zh: ({ target, name }) => `已定位${localizedParam(target, "zh-CN")}${name}`,
en: ({ target, name }) => `Located ${localizedParam(target, "en-US").toLowerCase()}: ${name}`,
},
"status.selected": {
zh: ({ target, name }) => (target ? `已选择${localizedParam(target, "zh-CN")}: ${name}` : `已选择: ${name}`),
en: ({ target, name }) => (target ? `Selected ${localizedParam(target, "en-US")}: ${name}` : `Selected: ${name}`),
},
"status.bgpSelected": {
zh: ({ collector, regionCount, cableCount }) =>
`已选择BGP事件: ${collector} · ${regionCount}个区域 / ${cableCount}条相关海缆`,
en: ({ collector, regionCount, cableCount }) =>
`Selected BGP event: ${collector} · ${regionCount} regions / ${cableCount} related cables`,
},
"status.computeCoordinatesSaved": {
zh: "算力中心坐标已保存",
en: "Compute center coordinates saved",
},
"status.coordinatesSavedLater": {
zh: "坐标已保存,地图稍后同步",
en: "Coordinates saved; map will sync shortly",
},
"status.coordinatesSavedSyncing": {
zh: "坐标已保存,正在同步地图...",
en: "Coordinates saved; syncing map...",
},
"status.newsPanel": {
zh: ({ open }) => (open ? "Live 新闻窗口已打开" : "Live 新闻窗口已关闭"),
en: ({ open }) => (open ? "Live News panel opened" : "Live News panel closed"),
},
"status.newsRefresh": {
zh: ({ ok }) => (ok ? "态势新闻已刷新" : "态势新闻刷新失败"),
en: ({ ok }) => (ok ? "Situation news refreshed" : "Situation news refresh failed"),
},
"status.newsSyncFailed": {
zh: "态势新闻同步失败",
en: "Situation news sync failed",
},
"status.languageSwitched": {
zh: "语言已切换为中文",
en: "Language switched to English",
},
"status.copyEmpty": {
zh: "无可复制内容",
en: "Nothing to copy",
},
"status.copyValue": {
zh: ({ label, value }) => `已复制${label}${value}`,
en: ({ label, value }) => `Copied ${label}: ${value}`,
},
"status.copyFailed": {
zh: "复制失败",
en: "Copy failed",
},
"status.zoomPercent": {
zh: ({ percent }) => `缩放 ${percent}%`,
en: ({ percent }) => `Zoom ${percent}%`,
},
"status.zoomReset": {
zh: ({ zoom }) => `缩放已重置到${zoom}`,
en: ({ zoom }) => `Zoom reset to ${zoom}`,
},
"status.runtimePaused": {
zh: ({ label, active }) => `${localizedParam(label, "zh-CN")}${active ? "已恢复" : "已暂停"}`,
en: ({ label, active }) => `${localizedParam(label, "en-US")} ${active ? "resumed" : "paused"}`,
},
"status.valueChanged": {
zh: ({ label, value }) => `${localizedParam(label, "zh-CN")}已切换为:${localizedParam(value, "zh-CN")}`,
en: ({ label, value }) => `${localizedParam(label, "en-US")} switched to ${localizedParam(value, "en-US")}`,
},
"status.modulesChanged": {
zh: ({ label, value }) => `${localizedParam(label, "zh-CN")}已切换为:${value}`,
en: ({ label, value }) => `${localizedParam(label, "en-US")} changed to ${value}`,
},
"status.updated": {
zh: ({ label }) => `${localizedParam(label, "zh-CN")}已更新`,
en: ({ label }) => `${localizedParam(label, "en-US")} updated`,
},
"status.booleanSetting": {
zh: ({ label, enabled }) => `${localizedParam(label, "zh-CN")}${enabled ? "已开启" : "已关闭"}`,
en: ({ label, enabled }) => `${localizedParam(label, "en-US")} ${enabled ? "enabled" : "disabled"}`,
},
"status.motionDebugMode": {
zh: ({ enabled, pending }) =>
enabled
? (pending ? "动捕调试模式将在下次进入动捕时开启" : "动捕调试模式已开启")
: "动捕调试模式已关闭",
en: ({ enabled, pending }) =>
enabled
? (pending ? "Motion debug mode will enable next time Motion Mode starts" : "Motion debug mode enabled")
: "Motion debug mode disabled",
},
"status.motionProvider": {
zh: ({ provider }) => (provider === "motion_agent" ? "Motion Agent 已接管动捕输入" : "动捕已切换到浏览器传感器"),
en: ({ provider }) => (provider === "motion_agent" ? "Motion Agent now controls motion input" : "Motion switched to browser sensors"),
},
"status.motionDebugView": {
zh: ({ skeletonOnly }) => (skeletonOnly ? "动捕调试已切换为只显示骨骼" : "动捕调试已显示实时画面"),
en: ({ skeletonOnly }) => (skeletonOnly ? "Motion debug switched to skeleton only" : "Motion debug showing live video"),
},
"status.motionGesturesUpdated": {
zh: "动捕识别动作已更新",
en: "Motion gestures updated",
},
"status.settingsReset": {
zh: "Earth 设置已重置",
en: "Earth settings reset",
},
"status.terrainUnavailable": {
zh: "真实地形暂时不可用",
en: "Real terrain is temporarily unavailable",
},
"status.boundaryDownloaded": {
zh: "高精国界已下载并应用",
en: "High-precision boundaries downloaded and applied",
},
"status.boundaryBuildStarted": {
zh: "高精国界构建已启动",
en: "High-precision boundary build started",
},
"status.boundaryPrecision": {
zh: ({ high }) => (high ? "已切换到高精国界" : "已切换到低精国界"),
en: ({ high }) => (high ? "Switched to high-precision boundaries" : "Switched to low-precision boundaries"),
},
"status.failure": {
zh: ({ label, error }) => `${localizedParam(label, "zh-CN")}${error}`,
en: ({ label, error }) => `${localizedParam(label, "en-US")}: ${error}`,
},
"status.layerLoadFailed": {
zh: ({ layer, error }) => `${localizedParam(layer, "zh-CN")}加载失败: ${error}`,
en: ({ layer, error }) => `Failed to load ${localizedParam(layer, "en-US")}: ${error}`,
},
"status.loadFailedList": {
zh: ({ items }) => (Array.isArray(items) ? items : [])
.map(({ label, reason }) =>
`${localizedParam(label, "zh-CN")}加载失败: ${reason?.message || String(reason)}`,
)
.join(""),
en: ({ items }) => (Array.isArray(items) ? items : [])
.map(({ label, reason }) =>
`Failed to load ${localizedParam(label, "en-US")}: ${reason?.message || String(reason)}`,
)
.join("; "),
},
"status.shortcutConflict": {
zh: ({ owner }) => `快捷键已被「${owner}」使用`,
en: ({ owner }) => `Shortcut already used by "${owner}"`,
},
"status.shortcutsReset": {
zh: "快捷键已恢复默认",
en: "Shortcuts reset to defaults",
},
"status.shortcutSet": {
zh: ({ label, binding }) => `${localizedParam(label, "zh-CN")}已设置为 ${binding}`,
en: ({ label, binding }) => `${localizedParam(label, "en-US")} set to ${binding}`,
},
"status.rotateSpeed": {
zh: ({ speed }) => `旋转转速已设为 ${speed}`,
en: ({ speed }) => `Rotation speed set to ${speed}`,
},
"status.switchedTo": {
zh: ({ label }) => `已切换到${localizedParam(label, "zh-CN")}`,
en: ({ label }) => `Switched to ${localizedParam(label, "en-US")}`,
},
"status.viewReset": {
zh: "视角已重置",
en: "View reset",
},
"status.motionPrefix": {
zh: ({ text }) => `动捕: ${localizedParam(text, "zh-CN")}`,
en: ({ text }) => `Motion: ${localizedParam(text, "en-US")}`,
},
"status.motionCruiseTargetSwitched": {
zh: "动捕: 已切换巡航目标",
en: "Motion: switched cruise target",
},
"status.motionNoTarget": {
zh: ({ layer }) => (layer ? `动捕: ${localizedParam(layer, "zh-CN")}当前视野没有可选目标` : "动捕: 当前没有可用图层"),
en: ({ layer }) => (layer ? `Motion: ${localizedParam(layer, "en-US")} has no selectable targets in view` : "Motion: no available layer"),
},
"status.motionSwitchedTo": {
zh: ({ label, layer }) => `动捕: 已切换到${localizedParam(label, "zh-CN")}${layer ? "图层" : ""}`,
en: ({ label, layer }) => `Motion: switched to ${localizedParam(label, "en-US")}${layer ? " layer" : ""}`,
},
"status.motionNoVisibleLayer": {
zh: "动捕: 当前没有可切换的可见图层",
en: "Motion: no visible layer to switch to",
},
"status.motionConfirmed": {
zh: ({ label }) => `动捕: 已确认${localizedParam(label, "zh-CN")}`,
en: ({ label }) => `Motion: confirmed ${localizedParam(label, "en-US")}`,
},
"status.motionConfirmCurrent": {
zh: "动捕: 已确认当前目标",
en: "Motion: confirmed current target",
},
"status.motionSelectTargetFirst": {
zh: "动捕: 请先选择目标",
en: "Motion: select a target first",
},
"status.cruisePausedOpenCandidates": {
zh: "已暂停巡航,正在打开候选列表",
en: "Cruise paused; opening candidate list",
},
"status.motionPausedOpenCandidates": {
zh: "已暂停动捕目标展示,正在打开候选列表",
en: "Motion target display paused; opening candidate list",
},
"status.candidatesClosedCruiseResumed": {
zh: "候选列表已关闭,巡航已恢复",
en: "Candidate list closed; cruise resumed",
},
"status.cruiseModeRequired": {
zh: "切换到巡航模式后可切换卡片",
en: "Switch to Cruise Mode before switching cards",
},
"status.cruisePausedSpace": {
zh: "巡航已暂停,按空格恢复",
en: "Cruise paused. Press Space to resume.",
},
};
const EXACT_TRANSLATIONS = {
"智能星球计划 - 现实层宇宙全息感知": "Intelligent Planet - Reality Layer Situational Awareness",
"智能星球计划": "Intelligent Planet Program",
"现实层宇宙全息感知系统": "Reality Layer Situational Awareness System",
"卫星 · 海底光缆 · 算力基础设施": "Satellites · Subsea Cables · Compute Infrastructure",
"图层": "Layers",
"折叠": "Collapse",
"关闭": "Close",
"清除": "Clear",
"开启": "On",
"已显示": "Shown",
"已隐藏": "Hidden",
"折叠图层列表": "Collapse layer list",
"关闭图层面板": "Close layer panel",
"搜索图层...": "Search layers...",
"清除搜索": "Clear search",
"正在加载...": "Loading...",
"无匹配图层": "No matching layers",
"海缆": "Subsea Cables",
"卫星": "Satellites",
"算力中心": "Compute Centers",
"船只": "Vessels",
"地形": "Terrain",
"海陆基座": "Land/Ocean Base",
"高清材质": "HD Texture",
"大气云图": "Atmosphere & Clouds",
"国界线": "Borders",
"经纬线": "Graticule",
"切换海缆显示": "Toggle subsea cables",
"切换卫星显示": "Toggle satellites",
"切换算力中心显示": "Toggle compute centers",
"切换船只显示": "Toggle vessels",
"切换BGP观测显示": "Toggle BGP observations",
"BGP观测": "BGP Observations",
"BGP态势": "BGP Situation",
"观测站": "Collector",
"切换地形显示": "Toggle terrain",
"切换高清材质显示": "Toggle HD texture",
"切换大气云图显示": "Toggle atmosphere and clouds",
"切换国界显示": "Toggle borders",
"切换经纬线显示": "Toggle graticule",
"显示卫星": "Show Satellites",
"隐藏卫星": "Hide Satellites",
"显示船只": "Show Vessels",
"隐藏船只": "Hide Vessels",
"显示线缆": "Show Cables",
"隐藏线缆": "Hide Cables",
"显示经纬线": "Show Graticule",
"隐藏经纬线": "Hide Graticule",
"显示国界": "Show Borders",
"隐藏国界": "Hide Borders",
"显示国界线": "Show Border Lines",
"隐藏国界线": "Hide Border Lines",
"显示高清材质": "Show HD Texture",
"隐藏高清材质": "Hide HD Texture",
"显示大气云图": "Show Atmosphere & Clouds",
"隐藏大气云图": "Hide Atmosphere & Clouds",
"显示BGP观测": "Show BGP Observations",
"隐藏BGP观测": "Hide BGP Observations",
"显示算力中心": "Show Compute Centers",
"隐藏算力中心": "Hide Compute Centers",
"显示轨迹": "Show Trails",
"隐藏轨迹": "Hide Trails",
"显示地形": "Show Terrain",
"隐藏地形": "Hide Terrain",
"图层控制": "Layer Controls",
"搜索": "Search",
"自动旋转": "Auto Rotate",
"重新加载数据": "Reload Data",
"缩放控制": "Zoom Controls",
"缩放:": "Zoom:",
"放大": "Zoom In",
"缩小": "Zoom Out",
"设置": "Settings",
"重置视角": "Reset View",
"最大化布局": "Maximize Layout",
"工具菜单": "Tool Menu",
"展开工具菜单": "Open tool menu",
"展开图例": "Expand legend",
"折叠图例": "Collapse legend",
"关闭图例": "Close legend",
"关闭地球信息": "Close Earth info",
"Live 新闻": "Live News",
"选择新闻直播源": "Select live news source",
"刷新直播源": "Refresh live source",
"访问官网": "Visit website",
"折叠新闻直播内容": "Collapse live news details",
"关闭 Live 新闻面板": "Close Live News panel",
"暂无可用频道": "No Channel Available",
"待加载": "Pending",
"等待加载": "Waiting",
"等待加载直播源": "Waiting for live source",
"正在同步直播源...": "Syncing live sources...",
"直播已加载": "Live stream loaded",
"当前视频流不可播放,请尝试其他频道": "This stream cannot be played. Try another channel.",
"当前频道仅支持外部打开": "This channel opens externally only",
"电视直播源加载失败": "Failed to load TV streams",
"直播加载中": "Live stream loading",
"直播流缓冲停滞,正在等待数据": "Live stream stalled; waiting for data",
"直播流缓冲写入失败": "Failed to append live stream buffer",
"HLS 播放列表格式无法解析": "Unable to parse HLS playlist",
"HLS 分片格式无法解析": "Unable to parse HLS segment",
"HLS 播放流不可用": "HLS stream unavailable",
"直播放流不可用,已回退到官网直播页": "Live stream unavailable; falling back to the official live page",
"直播流网络波动,正在重试...": "Live stream network issue. Retrying...",
"直播流正在恢复...": "Live stream recovering...",
"已加载视频流,点击播放继续": "Video stream loaded. Click play to continue.",
"当前频道仅支持跳转官网或外部播放器打开。": "This channel can only open on the official site or an external player.",
"点击查看当前频道来源、目录和补充说明": "Tap to view channel source, catalog, and notes",
"点击查看完整频道信息": "Tap to view full channel info",
"展开新闻直播内容": "Expand live news details",
"展开新闻直播信息": "Expand live news details",
"折叠新闻直播信息": "Collapse live news details",
"Live 新闻窗口已打开": "Live News panel opened",
"Live 新闻窗口已关闭": "Live News panel closed",
"内置": "Built-in",
"内置源": "Built-in",
"采集": "Collected",
"采集源": "Collector source",
"频道": "Channel",
"尚未同步": "Not synced yet",
"最近同步": "Last synced",
"配置于控制台": "Configured in console",
"当前未配置可播放新闻直播源": "No playable live news source is configured",
"频道目录待同步": "Channel catalog pending sync",
"支持后台配置默认源与采集器补充源。": "Supports default channels and collector-supplied sources from the console.",
"暂无可播放直播源,请先在系统配置中添加频道。": "No playable live source. Add a channel in system configuration first.",
"打开态势新闻": "Open situation news",
"态势聚合新闻": "Situation News",
"刷新新闻源": "Refresh news sources",
"打开源站": "Open source site",
"收起态势新闻": "Collapse situation news",
"跟随地球正面视角自动切换区域新闻": "Region news follows the front-facing globe view",
"当前关注区域": "Current Focus",
"跟随当前视角聚焦全球区域新闻": "Focus regional news from the current view",
"类型": "Type",
"来源": "Source",
"查看全部": "View all",
"返回摘要": "Back to summary",
"当前区域新闻聚合中": "Aggregating regional news",
"正在同步全球态势新闻...": "Syncing global situation news...",
"正在准备全球态势新闻...": "Preparing global situation news...",
"当前新闻类型没有可显示新闻...": "No displayable news for the selected types...",
"当前区域暂无可用新闻": "No available news in this region",
"输入关键词以搜索当前地球对象": "Enter keywords to search current Earth objects",
"支持搜索海缆、登陆点、卫星、算力中心、BGP 事件与观测站。": "Search subsea cables, landing points, satellites, compute centers, BGP events, and collectors.",
"搜索海缆、登陆点、卫星、算力中心、BGP 事件...": "Search cables, landing points, satellites, compute centers, BGP events...",
"搜索海缆、登陆点、卫星、算力中心和 BGP 事件": "Search cables, landing points, satellites, compute centers, and BGP events",
"关闭搜索": "Close search",
"设置分类": "Settings categories",
"移动端设置分类": "Mobile settings categories",
"重置设置": "Reset settings",
"关闭设置": "Close settings",
"重置": "Reset",
"运行": "Runtime",
"显示": "Display",
"面板": "Panels",
"动捕": "Motion",
"快捷键": "Shortcuts",
"系统": "System",
"关于": "About",
"旋转模式": "Rotation Mode",
"巡航模式": "Cruise Mode",
"动捕模式": "Motion Mode",
"旋转、巡航和动捕是互斥运行模式": "Rotation, cruise, and motion are mutually exclusive runtime modes",
"旋转、巡航和动捕是互斥运行模式;巡航按模块轮播,动捕消费手势控制": "Rotation, cruise, and motion are mutually exclusive. Cruise rotates through modules; motion consumes gestures.",
"选择旋转模式": "Select rotation mode",
"移动端选择旋转模式": "Select rotation mode on mobile",
"旋转转速": "Rotation Speed",
"默认速率 1x": "Default speed 1x",
"默认速率 1x调高后自动旋转更快。": "Default speed is 1x; increase it for faster auto rotation.",
"调整旋转转速": "Adjust rotation speed",
"移动端调整旋转转速": "Adjust rotation speed on mobile",
"重置旋转转速为1x": "Reset rotation speed to 1x",
"重置为1x": "Reset to 1x",
"巡航模块": "Cruise Modules",
"选择哪些可交互图层参与巡航队列。": "Choose which interactive layers join the cruise queue.",
"选择哪些可交互图层参与巡航队列。默认 BGP其他图层按需加入。": "Choose which interactive layers join the cruise queue. BGP is default; add others as needed.",
"选择巡航模块": "Select cruise modules",
"移动端选择巡航模块": "Select cruise modules on mobile",
"新闻": "News",
"算力": "Compute",
"线缆": "Cables",
"轨迹": "Tracks",
"真实地形": "Real terrain",
"真实地形已显示": "Real terrain shown",
"电缆": "Cables",
"新闻类型": "News Types",
"自动旋转": "Auto Rotate",
"旋转": "Rotation",
"巡航": "Cruise",
"布局": "Layout",
"视角": "View",
"当前目标": "Current target",
"国界": "Borders",
"其他": "Other",
"巡航队列": "Cruise Queue",
"巡航大区顺序": "Cruise Region Order",
"控制巡航项按原始顺序、大区轮转或随机播放。": "Control whether cruise items play in original order, by region, or randomly.",
"控制巡航项按原始顺序、大区轮转或随机洗牌播放。": "Control whether cruise items play in original order, by region, or shuffled.",
"选择巡航队列模式": "Select cruise queue mode",
"移动端选择巡航队列模式": "Select cruise queue mode on mobile",
"默认": "Default",
"按大区": "By Region",
"随机": "Random",
"地球与地表": "Globe & Surface",
"日夜模式": "Day/Night Mode",
"按真实太阳位置区分地球昼夜明暗": "Use the real sun position for day/night shading",
"按真实太阳位置区分地球昼夜明暗,关闭后全球均匀照亮": "Use the real sun position for day/night shading; disabling evenly lights the globe.",
"地球默认大小": "Default Globe Size",
"用于重置视角、缩放重置和巡航视图": "Used for reset view, zoom reset, and cruise views",
"用于重置视角、缩放重置和巡航视图的默认缩放比例": "Default zoom for reset view, zoom reset, and cruise views",
"移动端调整地球默认大小": "Adjust default globe size on mobile",
"地形透明度": "Terrain Opacity",
"调高后会呈现更明显的绿色地形覆盖效果": "Higher values make the green terrain overlay more visible",
"移动端调整地形透明度": "Adjust terrain opacity on mobile",
"国界精度": "Boundary Precision",
"正在读取高清国界状态...": "Reading high-precision boundary status...",
"选择国界精度": "Select boundary precision",
"移动端选择国界精度": "Select boundary precision on mobile",
"低精": "Low",
"高精": "High",
"重新获取并构建高清国界": "Fetch and rebuild high-precision boundaries",
"重新获取并构建": "Fetch and rebuild",
"卫星显示风格": "Satellite Style",
"选择卫星锁定态使用自身发光或真实地表覆盖范围": "Choose self glow or true ground footprint for locked satellites",
"选择卫星锁定态使用自身发光,还是强调真实地表覆盖范围。": "Choose self glow for locked satellites or emphasize their true ground footprint.",
"选择卫星显示风格": "Select satellite display style",
"移动端选择卫星显示风格": "Select satellite display style on mobile",
"自身发光": "Self Glow",
"真实地表覆盖": "Ground Footprint",
"卫星呼吸闪烁": "Satellite Breathing",
"空闲时让卫星点缓慢明暗呼吸": "Let satellite dots breathe slowly while idle",
"空闲时让卫星点缓慢明暗呼吸,拖拽或缩放时自动稳定显示": "Let satellite dots breathe while idle; stabilize them while dragging or zooming.",
"真实卫星高度": "True Satellite Altitude",
"卫星真实高度": "True Satellite Altitude",
"旧版同层高度": "legacy shell altitude",
"按真实轨道高度压缩分层显示": "Compress and layer by true orbital altitude",
"按真实轨道高度压缩分层显示,关闭后使用旧版同层球面": "Compress and layer by true orbital altitude; disabling uses the legacy single shell.",
"轨迹显示": "Track Display",
"控制卫星轨迹线显示": "Control satellite track lines",
"控制卫星轨迹线显示,卫星图层关闭时会自动隐藏": "Control satellite track lines; hidden automatically when the satellite layer is off.",
"交互提示": "Interaction Hints",
"悬停提示": "Hover Hint",
"控制鼠标悬停地表时显示国家、位置或完整信息": "Choose whether surface hover shows country, position, or full details",
"控制鼠标悬停地表时显示国家、位置或完整信息。": "Choose whether surface hover shows country, position, or full details.",
"选择悬停提示内容": "Select hover hint content",
"移动端选择悬停提示内容": "Select hover hint content on mobile",
"国家": "Country",
"位置": "Position",
"完整": "Full",
"低缩放圆点": "Compact Dots",
"低缩放彩色圆点": "Compact color dots",
"150% 以下将可交互图标简化为对应颜色圆点": "Below 150%, simplify interactable icons to colored dots",
"150% 以下将可交互图标简化为对应颜色圆点,便于巡航和总览扫视": "Below 150%, simplify interactable icons to colored dots for cruise and overview scanning.",
"新闻类型": "News Types",
"只筛选当前浏览器的新闻面板与新闻巡航,不改变后台新闻源。": "Filters only this browser's news panel and news cruise; backend sources are unchanged.",
"选择新闻类型": "Select news types",
"政治": "Politics",
"商业": "Business",
"电商": "E-commerce",
"金融": "Finance",
"体育": "Sports",
"科技": "Technology",
"军事": "Military",
"灾害": "Disaster",
"能源": "Energy",
"社会": "Society",
"文化": "Culture",
"其他": "Other",
"其他电缆": "Other Cables",
"海缆系统": "Cable Systems",
"登陆点": "Landing Points",
"8K 卫星图": "8K Satellite",
"颗": "",
"个": "",
"艘": "",
"起": "",
"在轨卫星": "Orbiting Satellites",
"AIS 船只": "AIS Vessels",
"BGP 事件": "BGP Events",
"BGP 观测站": "BGP Collectors",
"运行中": "Running",
"当前无活跃事件": "No active events",
"AISStream 未连接": "AISStream Disconnected",
"AISStream 实时已连接 · 等待首批更新": "AISStream live connected · waiting for first updates",
"AISStream 正在重连": "AISStream reconnecting",
"AISStream 正在连接": "AISStream connecting",
"暂无可播放直播源": "No playable live source",
"打开 Live 新闻": "Open Live News",
"关闭 Live 新闻": "Close Live News",
"正在加载船只...": "Loading vessels...",
"正在加载登陆点...": "Loading landing points...",
"正在加载海缆...": "Loading subsea cables...",
"正在加载卫星...": "Loading satellites...",
"正在加载BGP态势...": "Loading BGP situation...",
"正在加载地球纹理...": "Loading Earth texture...",
"正在加载大气云图...": "Loading atmosphere and clouds...",
"正在加载海陆基座...": "Loading land/ocean base...",
"正在启用高清材质...": "Enabling HD texture...",
"正在加载算力中心...": "Loading compute centers...",
"正在加载线缆数据...": "Loading cable data...",
"正在加载卫星数据...": "Loading satellite data...",
"正在加载船只数据...": "Loading vessel data...",
"正在加载电缆数据...": "Loading cable data...",
"正在加载真实地形数据...": "Loading real terrain data...",
"正在渲染地形...": "Rendering terrain...",
"地形加载中...": "Terrain loading...",
"卫星加载中...": "Satellites loading...",
"国界加载中...": "Borders loading...",
"高清材质加载中...": "HD texture loading...",
"大气云图加载中...": "Atmosphere and clouds loading...",
"船只加载中...": "Vessels loading...",
"欢迎使用智能星球计划": "Welcome to Intelligent Planet",
"登录控制台并完成首次采集后Earth 将显示实时数据层。": "After signing in to the console and completing the first collection, Earth will display live data layers.",
"后端服务在线": "Backend service online",
"登录控制台": "Sign in to Console",
"配置或确认数据源": "Configure or confirm datasources",
"触发首次采集": "Run the first collection",
"回到 Earth 查看结果": "Return to Earth to view results",
"概要": "Summary",
"当前记录": "Current Records",
"直播源": "Live Sources",
"活跃数据源": "Active Datasources",
"登录并采集数据": "Sign in and collect data",
"先浏览 Earth": "Browse Earth first",
"控制图层控制面板显示": "Control layer panel visibility",
"控制右侧图层控制面板显示": "Control the right-side layer panel",
"图例": "Legend",
"其他电缆": "Other Cables",
"赤道轨道": "Equatorial",
"低倾角轨道": "Low Incl.",
"中倾角轨道": "Mid Incl.",
"高倾角轨道": "High Incl.",
"逆行轨道": "Retrograde",
"赤道轨道0-30°": "Equatorial (0-30 deg)",
"低倾角轨道30-60°": "Low Incl. (30-60 deg)",
"中倾角轨道60-90°": "Mid Incl. (60-90 deg)",
"高倾角轨道90-120°": "High Incl. (90-120 deg)",
"逆行轨道120-180°": "Retrograde (120-180 deg)",
"低轨": "LEO",
"中轨": "MEO",
"高轨": "HEO",
"近地轨道": "LEO",
"低地球轨道": "LEO",
"中地球轨道": "MEO",
"高地球轨道": "HEO",
"同步轨道": "GSO",
"地球同步轨道": "GSO",
"静止轨道": "GEO",
"地球静止轨道": "GEO",
"太阳同步轨道": "SSO",
"极轨": "Polar",
"低轨LEO": "LEO",
"中轨MEO": "MEO",
"高轨HEO": "HEO",
"地球同步轨道GSO": "GSO",
"地球静止轨道GEO": "GEO",
"太阳同步轨道SSO": "SSO",
"极轨Polar": "Polar",
"陆地填色": "Land Fill",
"海洋填色": "Ocean Fill",
"货轮": "Cargo Ship",
"油轮": "Tanker",
"客船": "Passenger Ship",
"渔船": "Fishing Vessel",
"军舰": "Naval Vessel",
"停泊/低速": "Anchored / Slow",
"其他船只": "Other Vessels",
"中活跃观测站": "Medium-Activity Collector",
"高活跃观测站": "High-Activity Collector",
"观测范围示意": "Observation Coverage",
"事件连线 / 枢纽": "Event Links / Hubs",
"严重事件": "Critical Events",
"高危事件": "High-Risk Events",
"中危事件": "Medium-Risk Events",
"低危事件": "Low-Risk Events",
"控制图例面板显示": "Control legend panel visibility",
"控制左下角图例面板显示": "Control the lower-left legend panel",
"全球态势": "Global Situation",
"控制全球态势统计面板显示": "Control the global situation stats panel",
"控制右下角全球态势统计面板显示": "Control the lower-right global situation stats panel",
"新闻直播": "Live News",
"控制 Live 新闻面板显示": "Control the Live News panel",
"控制右上角 Live 新闻面板显示": "Control the upper-right Live News panel",
"动捕调试模式": "Motion Debug Mode",
"显示摄像头识别到的骨架连线和匹配动作": "Show detected skeleton lines and matched gestures",
"打开骨架连线面板;未匹配为红线,匹配动作后变绿": "Open the skeleton panel; unmatched lines are red and matched gestures turn green.",
"动捕输入源": "Motion Input",
"网页摄像头无需安装Motion Agent 用于双摄或网络摄像头": "Browser camera needs no install; Motion Agent is for dual-camera or network-camera setups.",
"浏览器摄像头适合网页/SaaSMotion Agent 适合双摄、RTSP/HTTP 和客户端": "Browser camera fits web/SaaS; Motion Agent fits dual cameras, RTSP/HTTP, and clients.",
"选择动捕输入源": "Select motion input",
"移动端选择动捕输入源": "Select motion input on mobile",
"浏览器摄像头": "Browser Camera",
"识别动作": "Recognized Gestures",
"关闭后不会触发对应星球控制": "Disabled gestures will not trigger Earth controls",
"未勾选的动作不会触发星球端控制Motion Agent 会同步过滤这些动作": "Unchecked gestures will not trigger Earth controls; Motion Agent filters them too.",
"选择动捕识别动作": "Select recognized gestures",
"移动端选择动捕识别动作": "Select recognized gestures on mobile",
"左旋": "Rotate Left",
"右旋": "Rotate Right",
"上旋": "Rotate Up",
"下旋": "Rotate Down",
"上个焦点": "Previous Focus",
"下个焦点": "Next Focus",
"上一图层": "Previous Layer",
"下一图层": "Next Layer",
"确认": "Confirm",
"键盘控制": "Keyboard Controls",
"点击按键后按下新的快捷键Esc 取消录入": "Click a key, then press the new shortcut. Esc cancels capture.",
"点击按键后按下新的快捷键Esc 取消录入,冲突快捷键不会保存": "Click a key, then press the new shortcut. Esc cancels capture; conflicts are not saved.",
"恢复默认快捷键": "Restore Default Shortcuts",
"恢复默认": "Restore Defaults",
"控制台": "Console",
"打开数据源、任务和系统运维工作台": "Open the datasource, task, and operations workspace",
"文档": "Docs",
"查看使用手册与开发文档": "Read manuals and developer docs",
"语言": "Language",
"星球语言": "Earth Language",
"同步 Docs 和控制台语言偏好": "Syncs with Docs and console language preference",
"中文": "中文",
"打开控制台": "Open Console",
"访问": "Open",
"查看": "View",
"仅保留移动端仍有意义的智能星球配置": "Keeps only Earth settings that still matter on mobile",
"动作捕捉": "Motion Capture",
"查看浏览器摄像头画面、骨架连线和当前匹配动作": "View browser camera, skeleton lines, and current matched gesture",
"动捕调试画面已并入设置里的动捕模式。": "Motion debug view has moved into Motion Mode in settings.",
"频道信息": "Channel Info",
"展开查看当前频道来源、目录和补充说明": "Expand to view current channel source, catalog, and notes",
"频道摘要标签": "Channel summary tags",
"刷新频道列表": "Refresh channel list",
"访问频道官网": "Visit channel website",
"移动端新闻直播": "Mobile live news",
"移动端新闻直播和频道切换": "Mobile live news and channel switching",
"Details": "Details",
"点击地球对象后查看统一详情": "Select an Earth object to view unified details",
"对象详情": "Object Details",
"等待选择对象": "Waiting for selection",
"点击海缆、算力中心、BGP 事件或卫星后在这里查看详情。": "Select a cable, compute center, BGP event, or satellite to view details here.",
"出品方": "Producer",
"策划人": "Planners",
"产品兼开发者": "Product & Development",
"浙江大学临空智能媒体研究院": "Zhejiang University Linkong Institute of Intelligent Media",
"方兴东、黄柳青": "Fang Xingdong, Huang Liuqing",
"钱坤、张鸽、齐鹏": "Qian Kun, Zhang Ge, Qi Peng",
"面向临空场景下的智能媒体研究、全球态势感知与多源开放数据巡航,提供可视化观测、事件聚合与交互式探索能力。": "For intelligent media research, global situational awareness, and multi-source open-data cruise in linkong scenarios, providing visual observation, event aggregation, and interactive exploration.",
"暂无": "None",
"未选": "None selected",
"全部": "All",
"区域监测": "Regional Monitor",
"聚合源": "Aggregated Source",
"手动添加": "Manual",
"单源 Atom": "Single-source Atom",
"配置保留": "Reserved",
"单源 RSS": "Single-source RSS",
"关注": "Watch",
"突发": "Breaking",
"严重突发": "Critical Breaking",
"区域": "Regional",
"大洲": "Continent",
"亚洲": "Asia",
"欧洲": "Europe",
"非洲": "Africa",
"北美洲": "North America",
"南美洲": "South America",
"大洋洲": "Oceania",
"南极洲": "Antarctica",
"全球": "Global",
"美洲": "Americas",
"欧洲": "Europe",
"中东与非洲": "Middle East & Africa",
"亚太": "Asia Pacific",
"全球焦点": "Global Focus",
"源类型": "Source type",
"抓取通道": "Fetch channel",
"新闻来源": "News Sources",
"按大来源筛选,不影响后台抓取。": "Filter by source group; backend fetching is unchanged.",
"按新闻内容分类筛选。": "Filter by news content category.",
"暂无可筛选来源。": "No filterable sources.",
"当前区域暂无可用新闻,已完成一次聚合尝试": "No available news in this region; one aggregation attempt completed",
"当前未拉到可用新闻,请稍后刷新或切换视角区域。": "No available news yet. Refresh later or switch view region.",
"当前新闻类型没有可显示新闻。": "No displayable news for the selected types.",
"等待聚合新闻源": "Waiting for aggregated news sources",
"正在准备全球态势新闻聚合源...": "Preparing global situation news sources...",
"跟随当前视角自动聚焦": "Follows the current view automatically",
"刚刚同步": "Just synced",
"新闻聚合请求超时,请稍后重试": "News aggregation timed out. Try again later.",
"新闻聚合暂时不可用": "News aggregation is temporarily unavailable",
"态势新闻同步失败": "Failed to sync situation news",
"态势新闻已刷新": "Situation news refreshed",
"态势新闻刷新失败": "Failed to refresh situation news",
"快捷键已恢复默认": "Shortcuts restored to defaults",
"布局已最大化": "Layout maximized",
"布局已恢复": "Layout restored",
"视角已重置": "View reset",
"数据已加载": "Data loaded",
"巡航已暂停,按空格恢复": "Cruise paused. Press Space to resume.",
"巡航大区顺序已更新": "Cruise region order updated",
"候选列表已关闭,巡航已恢复": "Candidate list closed; cruise resumed",
"已暂停巡航,正在打开候选列表": "Cruise paused; opening candidate list",
"已暂停动捕目标展示,正在打开候选列表": "Motion target display paused; opening candidate list",
"切换到巡航模式后可切换卡片": "Switch to Cruise Mode to change cards",
"动捕识别动作已更新": "Motion gestures updated",
"动捕调试模式已开启": "Motion debug mode enabled",
"动捕调试模式已关闭": "Motion debug mode disabled",
"动捕调试模式将在下次进入动捕时开启": "Motion debug mode will enable next time Motion Mode starts",
"动捕调试已切换为只显示骨骼": "Motion debug switched to skeleton only",
"动捕调试已显示实时画面": "Motion debug now shows live video",
"动捕输入源已切换为 Motion Agent": "Motion input switched to Motion Agent",
"动捕输入源已切换为浏览器摄像头": "Motion input switched to browser camera",
"动捕: 已切换巡航目标": "Motion: cruise target changed",
"动捕: 当前没有可用图层": "Motion: no available layers",
"动捕: 当前没有可切换的可见图层": "Motion: no switchable visible layers",
"动捕: 已确认当前目标": "Motion: current target confirmed",
"动捕: 请先选择目标": "Motion: select a target first",
"Earth 设置已重置": "Earth settings reset",
"按键...": "Press key...",
"启用快捷键": "Enable shortcut",
"视角控制": "View Controls",
"工具": "Tools",
"向上旋转": "Rotate Up",
"向左旋转": "Rotate Left",
"向下旋转": "Rotate Down",
"向右旋转": "Rotate Right",
"关闭当前焦点菜单": "Close Current Focus Menu",
"打开搜索": "Open Search",
"打开/关闭图层面板": "Toggle Layer Panel",
"打开/关闭新闻直播": "Toggle Live News",
"暂停/恢复运行": "Pause/Resume Runtime",
"下一张巡航卡片": "Next Cruise Card",
"切换海缆": "Toggle Cables",
"切换卫星": "Toggle Satellites",
"切换算力中心": "Toggle Compute Centers",
"切换船只": "Toggle Vessels",
"切换 BGP观测": "Toggle BGP Observations",
"切换地形": "Toggle Terrain",
"切换高清材质": "Toggle HD Texture",
"切换大气云图": "Toggle Atmosphere & Clouds",
"切换国界线": "Toggle Borders",
"切换经纬线": "Toggle Graticule",
"卫星关闭时不可用": "Unavailable while Satellites are disabled",
"高清材质关闭时不可用": "Unavailable while HD texture is disabled",
"切换到动捕模式后可开启调试面板": "Switch to Motion Mode to enable the debug panel",
"卫星已切换为旧版同层高度": "Satellites switched to legacy same-layer altitude",
"关闭动捕调试面板": "Close motion debug panel",
"重置缩放到100%": "Reset zoom to 100%",
"移动端菜单": "Mobile menu",
"输入名称、地点、NORAD、ASN...": "Search by name, location, NORAD, ASN...",
"移动端搜索结果": "Mobile search results",
"选择移动端新闻直播源": "Select mobile live news source",
"搜索结果": "Search results",
"调整地球默认大小": "Adjust default Earth size",
"调整地形透明度": "Adjust terrain opacity",
"浙江大学临空智能媒体研究院 Logo": "Zhejiang University Linkong Institute of Intelligent Media Logo",
"Earth 页面发生未捕获错误": "Earth page uncaught error",
"Earth 页面发生未处理 Promise 错误": "Earth page unhandled Promise error",
"等待动捕数据": "Waiting for motion data",
"当前浏览器不支持 WebSocket": "This browser does not support WebSocket",
"无法创建 Motion Agent 连接": "Unable to create Motion Agent connection",
"Motion Agent 连接异常": "Motion Agent connection error",
"Motion Agent 未连接": "Motion Agent disconnected",
"演示模式已开启": "Demo mode enabled",
"新闻汉化待重试": "News localization pending retry",
"已汉化": "Localized",
"未知线缆": "Unknown Cable",
"未知登陆点": "Unknown Landing Point",
"未知节点": "Unknown Node",
"未知目标": "Unknown Target",
"未知错误": "Unknown error",
"请检查更新源": "Check update source",
"构建失败": "Build failed",
"已选择:": "Selected:",
"海底光缆系统": "Subsea Cable System",
"后端没有返回 JSON 状态": "Backend did not return JSON status",
"正在准备高清国界": "Preparing high-precision boundaries",
"当前使用高精国界;可重新获取并构建。": "Using high-precision boundaries; you can fetch and rebuild.",
"高精国界已就绪,切到高精会立即应用。": "High-precision boundaries are ready and can be applied immediately.",
"当前使用低精国界;切到高精会下载并构建。": "Using low-precision boundaries; switching to high will download and build.",
"高精国界已下载并应用": "High-precision boundaries downloaded and applied",
"正在启动高精国界构建": "Starting high-precision boundary build",
"高精国界构建已启动": "High-precision boundary build started",
"已切换到高精国界": "Switched to high-precision boundaries",
"高清国界进度读取失败": "Failed to read high-precision boundary progress",
"高清国界状态读取失败": "Failed to read high-precision boundary status",
"高精国界切换失败": "Failed to switch high-precision boundaries",
"高精国界重建启动失败": "Failed to start high-precision boundary rebuild",
"低精国界切换失败": "Failed to switch low-precision boundaries",
"已切换到低精国界": "Switched to low-precision boundaries",
"详情": "Details",
"关闭详情": "Close details",
"电缆详情": "Cable Details",
"登陆点详情": "Landing Point Details",
"卫星详情": "Satellite Details",
"BGP事件详情": "BGP Event Details",
"新闻事件详情": "News Event Details",
"BGP观测站详情": "BGP Collector Details",
"超算中心详情": "Supercomputer Details",
"GPU集群详情": "GPU Cluster Details",
"船只详情": "Vessel Details",
"交互点详情": "Interactable Details",
"交互点": "Interactable",
"数据点": "Data Point",
"名称": "Name",
"所有者": "Owner",
"状态": "Status",
"长度": "Length",
"经纬度": "Coordinates",
"海拔": "Altitude",
"投入使用": "Ready for Service",
"关联海缆数": "Related Cable Count",
"关联海缆": "Related Cables",
"星座/分组": "Constellation / Group",
"覆盖能力": "Coverage Capability",
"当前显示": "Current Display",
"覆盖模型": "Coverage Model",
"倾角": "Inclination",
"周期": "Period",
"分钟": "min",
"近地点高度": "Perigee Altitude",
"远地点高度": "Apogee Altitude",
"事件类型": "Event Type",
"严重度": "Severity",
"事件特征": "Event Feature",
"前缀": "Prefix",
"传播路径": "AS Path",
"涉及 ASN": "Origin ASN",
"关联 ASN": "Related ASN",
"置信度": "Confidence",
"主观测站": "Primary Collector",
"观测范围": "Observation Scope",
"影响区域": "Impact Region",
"附近基础设施": "Nearby Infrastructure",
"附近卫星": "Nearby Satellites",
"观测位置": "Observation Location",
"事件时间": "Event Time",
"摘要": "Summary",
"媒体来源": "Media Source",
"source": "Source",
"name": "Name",
"country": "Country",
"country_code": "Country Code",
"flag": "Flag",
"type": "Type",
"vessel_type": "Vessel Type",
"width": "Width",
"gross_tonnage": "Gross Tonnage",
"deadweight": "Deadweight",
"built_year": "Built Year",
"operator": "Operator",
"owner": "Owner",
"status": "Status",
"last_updated": "Last Updated",
"发布时间": "Published",
"发生地": "Location",
"原文链接": "Source Link",
"采集器": "Collector",
"当前事件数": "Current Events",
"观测事件数": "Observed Events",
"近24h事件数": "24h Events",
"近7d事件数": "7d Events",
"观测前缀数": "Observed Prefixes",
"观测 ASN 数": "Observed ASNs",
"主要事件类型": "Top Event Types",
"日常活跃度": "Daily Activity",
"最近事件类型": "Latest Event Type",
"最近活跃时间": "Latest Activity",
"日常覆盖范围": "Baseline Scope",
"类型": "Type",
"排名": "Rank",
"实测算力": "Measured Compute",
"估算算力": "Estimated Compute",
"厂商": "Vendor",
"运营方": "Operator",
"核心数": "Cores",
"功耗": "Power",
"城市": "City",
"位置精度": "Location Precision",
"位置来源": "Location Source",
"位置置信度": "Location Confidence",
"核验状态": "Verification",
"解析依据": "Evidence",
"位置来源说明": "Location Source Note",
"匹配的位置名称": "Matched Location",
"位置核验时间": "Verified At",
"更新时间": "Updated",
"GPU 数量": "GPU Count",
"GPU 型号": "GPU Model",
"芯片/平台": "Chip / Platform",
"旗帜": "Flag",
"船型": "Vessel Type",
"当前航速": "Speed",
"航向": "Course",
"船长": "Length",
"标识": "ID",
"纬度": "Latitude",
"经度": "Longitude",
"说明": "Description",
"字段来源": "Field Source",
"新闻信号": "News Signal",
"船舶资料": "Vessel Profile",
"资料缓存中": "Profile caching",
"资料": "Profile",
"媒体": "Media",
"无可复制内容": "Nothing to copy",
"复制失败": "Copy failed",
"未知来源": "Unknown Source",
"不适用": "N/A",
"Starlink 单星地表覆盖": "Starlink single-satellite ground footprint",
"Iridium 外圈半透明覆盖": "Iridium translucent outer coverage ring",
"锚泊": "At anchor",
"停靠": "Moored",
"航行中": "Under way",
"重新自动采集坐标": "Recollect Coordinates",
"自动采集坐标候选": "Collect Coordinate Candidates",
"采集坐标候选": "Collect coordinate candidates",
"一键定位并采用最高置信候选": "Locate all and apply highest-confidence candidates",
"一键定位": "Locate All",
"采集": "Collected",
"正在检查 WebSearch 状态,稍候即可定位。": "Checking WebSearch status; location will be available shortly.",
"WebSearch 未开启,无法进行事实核查定位。": "WebSearch is disabled, so fact-checked location is unavailable.",
"精确": "Precise",
"站点": "Site",
"候选": "Candidate",
"预览": "Preview",
"保存": "Save",
"当前没有待定位算力中心": "No compute centers need location",
"缺少可用地址字段": "Missing usable address fields",
"待定位算力中心": "Compute Centers Pending Location",
"未命名算力中心": "Unnamed Compute Center",
"超级计算机": "Supercomputer",
"GPU集群": "GPU Cluster",
"BGP路由异常": "BGP Route Anomaly",
"态势新闻": "Situation News",
"超算": "Supercomputer",
"超算中心": "Supercomputer",
"GPU 集群": "GPU Cluster",
"BGP事件": "BGP Event",
"未知": "Unknown",
"未知国家": "Unknown Country",
"未知海缆": "Unknown Cable",
"未知卫星": "Unknown Satellite",
"未知船只": "Unknown Vessel",
"未知观测站": "Unknown Collector",
"未知算力中心": "Unknown Compute Center",
"精确坐标": "Precise Coordinates",
"站点级位置": "Site-Level Location",
"城市级位置": "City-Level Location",
"位置未确认": "Location Unconfirmed",
"源数据自带坐标": "Source Coordinates",
"ROR 组织注册 API": "ROR Registry API",
"Nominatim 在线搜索": "Nominatim Online Search",
"LLM factcheck 兜底": "LLM Factcheck Fallback",
"待人工核验": "Needs Review",
"估算位置": "Estimated Location",
"已确认": "Confirmed",
"严重": "Critical",
"高": "High",
"中": "Medium",
"低": "Low",
"前缀劫持": "Prefix Hijack",
"路由泄露": "Route Leak",
"大规模撤销": "Mass Withdrawal",
"更具体前缀异常": "More-Specific Prefix Anomaly",
"路径突变": "Path Change",
"路由抖动": "Route Flap",
"活跃": "Active",
"已恢复": "Resolved",
"已抑制": "Suppressed",
"在线": "Online",
"离线": "Offline",
"静态观测站": "Static Collector",
"附近登陆点": "Nearby Landing Point",
"支持 Starlink 地表覆盖": "Starlink ground footprint supported",
"支持 Iridium 外圈覆盖": "Iridium outer ring supported",
"默认不显示 footprint": "Footprint hidden by default",
"真实地表覆盖Starlink": "Ground Footprint (Starlink)",
"真实地表覆盖Iridium 外圈)": "Ground Footprint (Iridium outer ring)",
"中国": "China",
"美国": "United States",
"英国": "United Kingdom",
"法国": "France",
"德国": "Germany",
"日本": "Japan",
"韩国": "South Korea",
"新加坡": "Singapore",
"印度": "India",
"俄罗斯": "Russia",
"加拿大": "Canada",
"澳大利亚": "Australia",
"荷兰": "Netherlands",
"瑞士": "Switzerland",
"意大利": "Italy",
"西班牙": "Spain",
"瑞典": "Sweden",
"挪威": "Norway",
"芬兰": "Finland",
"丹麦": "Denmark",
"巴西": "Brazil",
"墨西哥": "Mexico",
"南非": "South Africa",
"阿联酋": "United Arab Emirates",
"沙特阿拉伯": "Saudi Arabia",
"以色列": "Israel",
"中国台湾": "Taiwan",
"台湾": "Taiwan",
"中国香港": "Hong Kong",
"香港": "Hong Kong",
"正在初始化...": "Initializing...",
"算力中心坐标已保存": "Compute center coordinates saved",
"坐标已保存,地图稍后同步": "Coordinates saved; map will sync shortly",
"坐标已保存,正在同步地图...": "Coordinates saved; syncing map...",
};
const PATTERN_TRANSLATIONS = [
[/^切换(.+)显示$/, (match, locale) => `Toggle ${translateText(match[1], locale)} display`],
[/^(\d+) 项$/, "$1"],
[/^(\d+) 条$/, "$1"],
[/^(\d+)个$/, "$1"],
[/^(\d+) 个$/, "$1"],
[/^(\d+) 颗$/, "$1"],
[/^(\d+)颗$/, "$1"],
[/^(\d+) 艘$/, "$1"],
[/^(\d+) 起$/, "$1"],
[/^(\d+)\/(\d+) 运行中$/, "$1/$2 running"],
[/^(\d+) 起活跃事件$/, "$1 active events"],
[/^(\d+) 起活跃事件 \/ (\d+) 条异常$/, "$1 active events / $2 anomalies"],
[/^(\d+) 条活跃异常$/, "$1 active anomalies"],
[/^(.+) \((\d+) pending location\)$/, (match, locale) => `${translateText(match[1], locale)} (${match[2]} pending location)`],
[/^(.+) · (.+)$/, (match, locale) => `${translateText(match[1], locale)} · ${translateText(match[2], locale)}`],
[/^暂停(.+)$/, (match, locale) => `Pause ${translateText(match[1], locale)}`],
[/^开始(.+)$/, (match, locale) => `Start ${translateText(match[1], locale)}`],
[/^共 (\d+) 个频道 · 尚未同步$/, "$1 channels · Not synced yet"],
[/^共 (\d+) 个频道 · 最近同步 (.+)$/, "$1 channels · Last synced $2"],
[/^采集源:(.+)$/, "Collector source: $1"],
[/^采集:(.+)$/, "Collector: $1"],
[/^成功加载 (\d+) 条电缆$/, "Loaded $1 cables"],
[/^成功加载 (\d+) 个登陆点$/, "Loaded $1 landing points"],
[/^已锁定: (.+)$/, "Locked: $1"],
[/^已启用 (\d+) 个图层$/, "$1 layers enabled"],
[/^HLS 主播放列表加载失败HTTP (\d+)$/, "Failed to load HLS master playlist (HTTP $1)"],
[/^HLS 清晰度播放列表加载失败HTTP (\d+)$/, "Failed to load HLS variant playlist (HTTP $1)"],
[/^HLS 分片加载失败HTTP (\d+)$/, "Failed to load HLS segment (HTTP $1)"],
[/^HLS 主播放列表加载失败$/, "Failed to load HLS master playlist"],
[/^HLS 清晰度播放列表加载失败$/, "Failed to load HLS variant playlist"],
[/^HLS 分片加载失败$/, "Failed to load HLS segment"],
[/^HLS 资源加载失败:(.+)$/, "Failed to load HLS resource: $1"],
[/^直播流连接异常,正在重试 \((\d+)\/(\d+)\)\.\.\.$/, "Live stream connection issue. Retrying ($1/$2)..."],
[/^直播流解码异常,正在恢复 \((\d+)\/(\d+)\)\.\.\.$/, "Live stream decode issue. Recovering ($1/$2)..."],
[/^失败:(.*)$/, "Failed: $1"],
[/^高清国界更新源未配置完整:(.*)$/, "High-precision boundary sources are incomplete: $1"],
[/^高清国界下载失败:(.*)$/, "High-precision boundary download failed: $1"],
[/^高清国界进度读取失败:(.*)$/, "Failed to read high-precision boundary progress: $1"],
[/^高清国界状态读取失败:(.*)$/, "Failed to read high-precision boundary status: $1"],
[/^高精国界切换失败:(.*)$/, "Failed to switch high-precision boundaries: $1"],
[/^高精国界重建启动失败:(.*)$/, "Failed to start high-precision boundary rebuild: $1"],
[/^低精国界切换失败:(.*)$/, "Failed to switch low-precision boundaries: $1"],
[/^快捷键已被「(.+)」使用$/, "Shortcut is already used by \"$1\""],
[/^(.+)已显示$/, (match, locale) => `${translateText(match[1], locale)} shown`],
[/^(.+)已隐藏$/, (match, locale) => `${translateText(match[1], locale)} hidden`],
[/^(.+)已启用$/, (match, locale) => `${translateText(match[1], locale)} enabled`],
[/^(.+)已开启$/, (match, locale) => `${translateText(match[1], locale)} enabled`],
[/^(.+)已关闭$/, (match, locale) => `${translateText(match[1], locale)} disabled`],
[/^(.+)已暂停$/, (match, locale) => `${translateText(match[1], locale)} paused`],
[/^(.+)已恢复$/, (match, locale) => `${translateText(match[1], locale)} resumed`],
[/^(.+)当前不可用$/, (match, locale) => `${translateText(match[1], locale)} unavailable`],
[/^(.+)暂时不可用$/, (match, locale) => `${translateText(match[1], locale)} temporarily unavailable`],
[/^正在加载(.+)数据\.\.\.$/, (match, locale) => `Loading ${translateText(match[1], locale).toLowerCase()} data...`],
[/^正在加载(.+)\.\.\.$/, (match, locale) => `Loading ${translateText(match[1], locale).toLowerCase()}...`],
[/^(.+)加载失败: (.+)$/, (match, locale) => `${translateText(match[1], locale)} load failed: ${match[2]}`],
[/^已定位(.+)(.+)$/, (match, locale) => `Located ${translateText(match[1], locale).toLowerCase()}: ${match[2]}`],
[/^已选择(.+): (.+)$/, (match, locale) => `Selected ${translateText(match[1], locale).toLowerCase()}: ${match[2]}`],
[/^(.+)已切换为:(.+)$/, (match, locale) => `${translateText(match[1], locale)} switched to: ${translateText(match[2], locale)}`],
[/^(.+)已设置为 (.+)$/, "$1 set to $2"],
[/^(.+)已设为 (.+)$/, (match, locale) => `${translateText(match[1], locale)} set to ${translateText(match[2], locale)}`],
[/^重置(.+)$/, "Reset $1"],
[/^已切换到(.+)$/, (match, locale) => `Switched to ${translateText(match[1], locale)}`],
[/^最近同步 (.+)$/, "Last synced $1"],
[/^缩放 (\d+)%$/, "Zoom $1%"],
[/^AISStream 实时已连接 · (\d+) 次更新$/, "AISStream live connected · $1 updates"],
[/^AISStream 实时已连接 · (\d+) 次更新 · (.+)$/, "AISStream live connected · $1 updates · $2"],
[/^AISStream 后台已连接$/, "AISStream backend connected"],
[/^AISStream 后台已连接 · 最近 (.+)$/, "AISStream backend connected · last seen $1"],
[/^(\d+) 秒前$/, "$1s ago"],
[/^(\d+) 分钟前$/, "$1m ago"],
[/^已选择BGP事件: (.+) · (\d+)个区域 \/ (\d+)条相关海缆$/, "Selected BGP event: $1 · $2 regions / $3 related cables"],
[/^已选择观测站: (.+)$/, "Selected collector: $1"],
[/^新闻源请求失败: (\d+)$/, "News request failed: $1"],
[/^来源 (.+)$/, "Source $1"],
[/^更新 (.+)$/, "Updated $1"],
[/^置信 (.+)$/, "Confidence $1"],
[/^已复制(.+)(.+)$/, "Copied $1: $2"],
[/^已选择: (.+)$/, "Selected: $1"],
[/^动捕: (.+)当前视野没有可选目标$/, (match, locale) => `Motion: ${translateText(match[1], locale)} has no selectable targets in view`],
[/^动捕: 已切换到(.+)图层$/, (match, locale) => `Motion: switched to ${translateText(match[1], locale)} layer`],
[/^动捕: 已切换到(.+)$/, (match, locale) => `Motion: switched to ${translateText(match[1], locale)}`],
[/^动捕: 已确认(.+)$/, (match, locale) => `Motion: confirmed ${translateText(match[1], locale)}`],
[/^共找到 (\d+) 个候选位置$/, "Found $1 candidate locations"],
[/^(\d+) 个算力中心没有可信坐标$/, "$1 compute centers lack trusted coordinates"],
[/^(\d+) 个待定位$/, "$1 pending location"],
[/^正在定位并采用最高置信候选 (\d+)\/(\d+)\.\.\.$/, "Locating and applying highest-confidence candidate $1/$2..."],
[/^一键定位进行中 (\d+)\/(\d+)\.\.\.$/, "Batch location in progress $1/$2..."],
[/^找到 (\d+) 个候选,正在保存最高置信位置\.\.\.$/, "Found $1 candidates; saving highest-confidence location..."],
[/^坐标已保存,等待图层刷新$/, "Coordinates saved; waiting for layer refresh"],
[/^坐标已保存$/, "Coordinates saved"],
[/^正在保存所选坐标\.\.\.$/, "Saving selected coordinates..."],
[/^正在采集坐标候选\.\.\.$/, "Collecting coordinate candidates..."],
[/^采集失败:(.+)$/, "Collection failed: $1"],
[/^保存失败:(.+)$/, "Save failed: $1"],
[/^未能采集到坐标:(.+)$/, "Could not collect coordinates: $1"],
[/^未找到可采用候选:(.+)$/, "No applicable candidate found: $1"],
[/^未找到包含有效经纬度的候选$/, "No candidate includes valid coordinates"],
[/^常规来源没有可用坐标候选$/, "No coordinate candidates from regular sources"],
[/^常规来源无结果LLM 兜底未生成可用候选:(.+)$/, "Regular sources returned no result; LLM fallback produced no usable candidate: $1"],
[/^常规来源无结果LLM 兜底已尝试但没有返回可用候选。(.+)$/, "Regular sources returned no result; LLM fallback was attempted but returned no usable candidate. $1"],
[/^(\d+)个观测站 \((.+)\)$/, "$1 collectors ($2)"],
[/^(.+) 等(\d+)地$/, "$1 + $2 regions"],
[/^(.+) 等(\d+)处$/, "$1 + $2 sites"],
[/^近24h (\d+)条事件 \/ (\d+)个前缀$/, "24h $1 events / $2 prefixes"],
[/^一键定位进行中 (\d+)\/(\d+),已保存 (\d+) 个,失败 (\d+) 个$/, "Batch location in progress $1/$2, saved $3, failed $4"],
[/^一键定位进行中 (\d+)\/(\d+),已保存 (\d+) 个$/, "Batch location in progress $1/$2, saved $3"],
[/^已定位并采用 (\d+) 个最高置信候选,(\d+) 个仍需手动处理$/, "Applied $1 highest-confidence candidates; $2 still need manual review"],
[/^已定位并采用 (\d+) 个最高置信候选$/, "Applied $1 highest-confidence candidates"],
[/^(\d+) 个都没有可自动采用的候选,需要手动处理$/, "$1 items need manual handling; no automatic candidates were usable"],
[/^一键定位失败:(.+)$/, "Batch location failed: $1"],
[/^一键采用失败:(.+)$/, "Batch apply failed: $1"],
[/^未找到可采用候选$/, "No applicable candidate found"],
];
const SKIP_SELECTOR = [
"script",
"style",
"svg",
"canvas",
"video",
"iframe",
".material-symbols-rounded",
].join(",");
const TRANSLATABLE_ATTRIBUTES = ["title", "aria-label", "placeholder", "alt"];
let activeLocale = readStoredLocale();
let observer = null;
let translating = false;
function normalizeText(value) {
return String(value ?? "").replace(/\s+/g, " ").trim();
}
export function hasCjkText(value) {
return /[\u4e00-\u9fff]/.test(String(value ?? ""));
}
export function normalizeLocale(value) {
const normalized = String(value || "").toLowerCase();
if (normalized === "en" || normalized === "en-us" || normalized.startsWith("en-")) return "en-US";
if (normalized === "zh" || normalized === "zh-cn" || normalized.startsWith("zh-")) return "zh-CN";
return DEFAULT_LOCALE;
}
export function readStoredLocale() {
try {
const storedLocale = window.localStorage?.getItem(LOCALE_STORAGE_KEY);
if (storedLocale) return normalizeLocale(storedLocale);
const docsLang = window.localStorage?.getItem(LEGACY_DOCS_LANG_STORAGE_KEY);
if (docsLang === "en") return "en-US";
if (docsLang === "zh") return "zh-CN";
} catch {
// Ignore storage failures and fall back to the default language.
}
return DEFAULT_LOCALE;
}
export function getEarthLocale() {
return activeLocale;
}
export function docsLangFromLocale(locale) {
return normalizeLocale(locale) === "en-US" ? "en" : "zh";
}
export function getEarthLocaleLabel(locale = activeLocale) {
return LOCALE_LABELS[normalizeLocale(locale)] || LOCALE_LABELS[DEFAULT_LOCALE];
}
function syncLocaleSegmentedSliders() {
document.querySelectorAll(".earth-settings-segmented, .earth-mobile-settings-segmented").forEach((segmented) => {
if (!(segmented instanceof HTMLElement)) return;
const buttons = Array.from(segmented.querySelectorAll("[data-earth-locale]"));
if (buttons.length === 0) return;
const activeIndex = Math.max(0, buttons.findIndex((button) => button.classList.contains("is-active")));
segmented.style.setProperty("--item-count", String(buttons.length));
segmented.style.setProperty("--active-index", String(activeIndex));
});
}
export function translateText(value, locale = activeLocale) {
const text = normalizeText(value);
if (!text || normalizeLocale(locale) === "zh-CN") return text;
if (EXACT_TRANSLATIONS[text]) return EXACT_TRANSLATIONS[text];
for (const [pattern, replacement] of PATTERN_TRANSLATIONS) {
const match = text.match(pattern);
if (!match) continue;
if (typeof replacement === "function") return replacement(match, locale);
return text.replace(pattern, replacement);
}
return text;
}
export function earthMessage(key, params = {}) {
return {
[EARTH_MESSAGE_MARKER]: true,
key,
params,
};
}
export function isEarthMessage(value) {
return Boolean(value && typeof value === "object" && value[EARTH_MESSAGE_MARKER] === true);
}
function interpolateEarthMessage(template, params = {}, locale = activeLocale) {
return String(template).replace(/\{([A-Za-z0-9_]+)\}/g, (_, key) => {
const value = params[key];
return value === undefined || value === null ? "" : String(value);
});
}
export function formatEarthMessage(value, locale = activeLocale) {
if (!isEarthMessage(value)) {
return translateText(value, locale);
}
const normalizedLocale = normalizeLocale(locale);
const localeKey = normalizedLocale === "en-US" ? "en" : "zh";
const template = EARTH_MESSAGE_TEMPLATES[value.key];
if (!template) return translateText(value.key, normalizedLocale);
const params = value.params || {};
const renderer = template[localeKey] || template.zh || template.en;
if (typeof renderer === "function") {
return normalizeText(renderer(params, normalizedLocale));
}
return normalizeText(interpolateEarthMessage(renderer, params, normalizedLocale));
}
export function localizeCountryName(value, locale = activeLocale) {
const normalizedLocale = normalizeLocale(locale);
if (!value) return "";
if (typeof value === "object") {
const source = value || {};
if (normalizedLocale === "en-US") {
return normalizeText(
source.nameEn ||
source.name ||
source.NAME_EN ||
source.ADMIN ||
source.nameZh ||
source.NAME_ZH ||
"",
);
}
return normalizeText(
source.nameZh ||
source.NAME_ZH ||
source.name ||
source.nameEn ||
source.NAME_EN ||
source.ADMIN ||
"",
);
}
return translateText(value, locale);
}
export function formatLocaleDateTime(value, options = {}) {
const date = value instanceof Date ? value : new Date(value);
if (Number.isNaN(date.getTime())) return "";
return date.toLocaleString(activeLocale, { hour12: false, ...options });
}
function rememberOriginalText(node) {
if (!node.parentElement) return "";
const originals = node.parentElement.__planetEarthI18nOriginalText || new WeakMap();
if (!node.parentElement.__planetEarthI18nOriginalText) {
node.parentElement.__planetEarthI18nOriginalText = originals;
}
const current = normalizeText(node.nodeValue);
if (!originals.has(node)) {
originals.set(node, current);
return current;
}
const original = originals.get(node);
const translated = translateText(original, activeLocale);
const currentLooksSource = hasCjkText(current);
if (current !== translated && current !== original && currentLooksSource) {
originals.set(node, current);
return current;
}
return original;
}
function translateTextNode(node) {
if (!node.parentElement || node.parentElement.closest(SKIP_SELECTOR)) return;
const original = rememberOriginalText(node);
if (!original) return;
const translated = translateText(original, activeLocale);
if (node.nodeValue !== translated) {
node.nodeValue = translated;
}
}
function rememberOriginalAttribute(element, attribute) {
const dataKey = `i18nOriginal${attribute
.replace(/(^|-)([a-z])/g, (_, __, letter) => letter.toUpperCase())}`;
const current = normalizeText(element.getAttribute(attribute));
if (!element.dataset[dataKey]) {
element.dataset[dataKey] = current;
return current;
}
const original = element.dataset[dataKey];
const translated = translateText(original, activeLocale);
const currentLooksSource = hasCjkText(current);
if (current && current !== translated && current !== original && currentLooksSource) {
element.dataset[dataKey] = current;
return current;
}
return original;
}
function translateAttributes(element) {
TRANSLATABLE_ATTRIBUTES.forEach((attribute) => {
if (!element.hasAttribute(attribute)) return;
const original = rememberOriginalAttribute(element, attribute);
if (!original) return;
const translated = translateText(original, activeLocale);
if (element.getAttribute(attribute) !== translated) {
element.setAttribute(attribute, translated);
}
});
}
function translateElementTree(root) {
if (!(root instanceof Element)) return;
if (root.closest(SKIP_SELECTOR)) return;
translateAttributes(root);
root.querySelectorAll("*").forEach((element) => {
if (element instanceof Element && !element.closest(SKIP_SELECTOR)) {
translateAttributes(element);
}
});
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
acceptNode(node) {
if (!normalizeText(node.nodeValue)) return NodeFilter.FILTER_REJECT;
if (node.parentElement?.closest(SKIP_SELECTOR)) return NodeFilter.FILTER_REJECT;
return NodeFilter.FILTER_ACCEPT;
},
});
const nodes = [];
while (walker.nextNode()) nodes.push(walker.currentNode);
nodes.forEach(translateTextNode);
}
function syncDocumentLocale() {
document.documentElement.lang = activeLocale;
document.title = translateText("智能星球计划 - 现实层宇宙全息感知", activeLocale);
}
function persistLocale(locale) {
try {
window.localStorage?.setItem(LOCALE_STORAGE_KEY, locale);
window.localStorage?.setItem(LEGACY_DOCS_LANG_STORAGE_KEY, docsLangFromLocale(locale));
} catch {
// Ignore storage failures; the current page still changes language.
}
}
export function applyEarthI18n(root = document.body) {
if (!root) return;
if (translating) return;
translating = true;
try {
syncDocumentLocale();
if (root === document || root === document.body || root instanceof Document) {
translateElementTree(document.body);
} else if (root instanceof Element) {
translateElementTree(root);
}
syncEarthLocaleControls();
} finally {
translating = false;
}
}
function observeMutations() {
if (observer || !document.body) return;
observer = new MutationObserver((mutations) => {
if (translating) return;
const roots = new Set();
let attributeChanged = false;
mutations.forEach((mutation) => {
if (mutation.type === "childList") {
mutation.addedNodes.forEach((node) => {
if (node instanceof Element) roots.add(node);
else if (node.parentElement) roots.add(node.parentElement);
});
} else if (mutation.type === "characterData" && mutation.target.parentElement) {
roots.add(mutation.target.parentElement);
} else if (mutation.type === "attributes" && mutation.target instanceof Element) {
roots.add(mutation.target);
attributeChanged = true;
}
});
if (roots.size === 0 && !attributeChanged) return;
roots.forEach((root) => applyEarthI18n(root));
});
observer.observe(document.body, {
attributes: true,
attributeFilter: TRANSLATABLE_ATTRIBUTES,
childList: true,
characterData: true,
subtree: true,
});
}
export function setEarthLocale(nextLocale, { persist = true, announce = true } = {}) {
const normalized = normalizeLocale(nextLocale);
if (normalized === activeLocale) {
applyEarthI18n();
return activeLocale;
}
activeLocale = normalized;
if (persist) persistLocale(normalized);
applyEarthI18n();
window.dispatchEvent(new CustomEvent(LOCALE_CHANGE_EVENT, { detail: { locale: activeLocale } }));
if (announce) {
window.dispatchEvent(
new CustomEvent("earth:status", {
detail: { message: earthMessage("status.languageSwitched") },
}),
);
}
return activeLocale;
}
export function syncEarthLocaleControls() {
document.querySelectorAll("[data-earth-locale]").forEach((button) => {
if (!(button instanceof HTMLButtonElement)) return;
const locale = normalizeLocale(button.dataset.earthLocale);
const active = locale === activeLocale;
button.classList.toggle("is-active", active);
button.setAttribute("aria-pressed", active ? "true" : "false");
});
syncLocaleSegmentedSliders();
document.querySelectorAll("[data-earth-locale-switch]").forEach((input) => {
if (!(input instanceof HTMLInputElement)) return;
const checked = activeLocale === "en-US";
input.checked = checked;
input.setAttribute("aria-checked", checked ? "true" : "false");
input.closest(".earth-settings-switch, .earth-mobile-settings-switch")
?.classList.toggle("is-checked", checked);
});
}
export function setupEarthLocaleControls(root = document) {
root.querySelectorAll("[data-earth-locale]").forEach((button) => {
if (!(button instanceof HTMLButtonElement) || button.dataset.localeBound === "true") return;
button.dataset.localeBound = "true";
button.addEventListener("click", () => {
setEarthLocale(button.dataset.earthLocale || DEFAULT_LOCALE);
});
});
root.querySelectorAll("[data-earth-locale-switch]").forEach((input) => {
if (!(input instanceof HTMLInputElement) || input.dataset.localeBound === "true") return;
input.dataset.localeBound = "true";
input.addEventListener("change", () => {
setEarthLocale(input.checked ? "en-US" : "zh-CN");
});
});
syncEarthLocaleControls();
}
export function onEarthLocaleChange(callback) {
const handler = (event) => callback(event.detail?.locale || activeLocale);
window.addEventListener(LOCALE_CHANGE_EVENT, handler);
return () => window.removeEventListener(LOCALE_CHANGE_EVENT, handler);
}
export function initEarthI18n() {
activeLocale = readStoredLocale();
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
applyEarthI18n();
setupEarthLocaleControls();
observeMutations();
}, { once: true });
} else {
applyEarthI18n();
setupEarthLocaleControls();
observeMutations();
}
window.addEventListener("storage", (event) => {
if (event.key !== LOCALE_STORAGE_KEY && event.key !== LEGACY_DOCS_LANG_STORAGE_KEY) return;
setEarthLocale(readStoredLocale(), { persist: false, announce: false });
});
}