diff --git a/changelog.d/1298-simplified-chinese.md b/changelog.d/1298-simplified-chinese.md new file mode 100644 index 000000000..d35d08cf1 --- /dev/null +++ b/changelog.d/1298-simplified-chinese.md @@ -0,0 +1,4 @@ +- Add Simplified Chinese (zh-CN) to the language picker, with localized dates + and readable Chinese labels. Based on translations by @abiyeenzo (#914) + and @EDIT-Aeterna (#979). + (German, @gekh, #1298). diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 519500553..6edc47165 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -3538,7 +3538,7 @@ test:track` 43 tracking invariants · headless QA harnesses under ### Interface localization (October 2026) -Shipped locales: **en** (source and fallback), **es** and **ru**. The code lives in +Shipped locales: **en** (source and fallback), **es**, **ru** and **zh-CN**. The code lives in `src/i18n/`. How to mark UI text: [`docs/I18N.md`](./I18N.md); translator workflow: [`docs/TRANSLATORS.md`](./TRANSLATORS.md). @@ -3549,7 +3549,8 @@ Shipped locales: **en** (source and fallback), **es** and **ru**. The code lives value). English has no catalog; every other locale is `src/i18n/locales/.json`, and a missing entry falls back to English. - **Locale choice.** `?lang=` → stored choice (`gev:locale:v1`) → - `navigator.languages` → English. `?lang=` is not stored, and share links drop + `navigator.languages` → English. Chinese tags map to `zh-CN`; tags containing + `Hant`, `TW`, `HK` or `MO` are skipped. `?lang=` is not stored, and share links drop it, so a recipient keeps their own language. The globe picker in the bottom-right corner (`src/ui/localePicker.js`) stores the choice, drops `?lang=` (keeps it only when storage is blocked) and reloads. It asks first diff --git a/docs/TRANSLATORS.md b/docs/TRANSLATORS.md index 2131ab7a3..538f8c50e 100644 --- a/docs/TRANSLATORS.md +++ b/docs/TRANSLATORS.md @@ -1,6 +1,6 @@ # Translating God's Eye View -Shipped languages: **English** (source and fallback), **Spanish** (`es`) and **Russian** (`ru`). +Shipped languages: **English** (source and fallback), **Spanish** (`es`), **Russian** (`ru`) and **Simplified Chinese** (`zh-CN`). ## How it works @@ -63,4 +63,4 @@ These are contracts, not copy: layer ids and registry keys, status enums and oth ## Language notes -Each language keeps its glossary, plural forms, units and style in its own file, `docs/translations/.md`: [Spanish](./translations/es.md), [Russian](./translations/ru.md). Read it before translating, and add a term there when you settle on one. +Each language keeps its glossary, plural forms, units and style in its own file, `docs/translations/.md`: [Spanish](./translations/es.md), [Russian](./translations/ru.md), [Simplified Chinese](./translations/zh-CN.md). Read it before translating, and add a term there when you settle on one. diff --git a/docs/translations/zh-CN.md b/docs/translations/zh-CN.md new file mode 100644 index 000000000..e3b7bcad1 --- /dev/null +++ b/docs/translations/zh-CN.md @@ -0,0 +1,77 @@ +# Simplified Chinese (`zh-CN`) + +Catalog: `src/i18n/locales/zh-CN.json`. General rules: [TRANSLATORS.md](../TRANSLATORS.md). + +Translation sources: abiyeenzo (#914) and EDIT-Aeterna (#979). + +## Style + +- Mainland usage and Simplified characters. Address the user neutrally, without 您/你 where a plain imperative works: 「点击地图」, 「重试」. +- Chinese has no case: an UPPERCASE English HUD label becomes the same short Chinese label. Keep labels as short as the English: 「实时」, 「过期」, 「关」. +- Full-width punctuation in Chinese text: ,。:;?!()“”. A dash is 「——」 without spaces; keep `·` as the HUD separator and `…` for ellipsis. +- One space between Chinese and Latin letters or digits: 「AIS 船舶」, 「24 小时」, 「{count} 个」. No space next to full-width punctuation. +- Units: km, m, km/h, m/s, kn, ft, MW, °, % stay as written; time words are 小时, 分钟, 秒 after a number (「5 分钟前」). +- Product names, provider names and codes stay as they are: Google, Cesium, CelesTrak, NOAA, FIRMS, AIS, ADS-B, GNSS, CCTV, HUD, CRT, NVG, FLIR, WHOT/BHOT, Ironbow, CONUS. + +## Plurals + +`Intl.PluralRules('zh-CN')` has one category, `other`: a `tn()` entry has only that form. Use a measure word next to the number where Chinese needs one: 「{count} 个密钥」, 「{count} 架飞机」, 「{count} 艘船」. + +## Glossary + +| English | 中文 | +| ------------------------------------- | --------------------------- | +| layer / data layer | 图层 / 数据图层 | +| feed | 数据源 | +| source / data source | 来源 / 数据源 | +| LIVE (status) / live data | 实时 / 实时数据 | +| SIMULATED | 模拟 | +| stale / degraded / unavailable | 过期 / 降级 / 不可用 | +| retry | 重试 | +| key (API key) / POWER UP | 密钥(API 密钥)/ 增强 | +| Provider Settings | 服务商设置 | +| globe / map / map source / basemap | 地球 / 地图 / 地图源 / 底图 | +| aircraft / flight / military flights | 飞机 / 航班 / 军用航班 | +| vessel, ship | 船舶 | +| satellite | 卫星 | +| launch (rocket) / space mission | 发射 / 太空任务 | +| contact (tracked object) | 目标 | +| installation, site | 设施 | +| camera / CCTV camera | 摄像头 / CCTV 摄像头 | +| earthquake / active fire | 地震 / 活跃火点 | +| scene / shot | 场景 / 镜头 | +| briefing / panel / dock | 简报 / 面板 / 停靠栏 | +| preset / visual preset | 预设 / 视觉预设 | +| Display / HUD layout | 显示 / HUD 布局 | +| Tactical / Operator / Minimal / Cyber | 战术 / 操作员 / 精简 / 赛博 | +| Scope (circular viewport mask) / keyhole | 视域 | +| Clean UI | 纯净界面 | +| cockpit | 座舱 | +| heading / altitude / ground speed | 航向 / 高度 / 地速 | +| map heading / North up | 方位 / 正北朝上 | +| Street Level / Street View (Google) | 街景 / 街景(Google) | +| Recent Imagery | 近期影像 | +| Global Context / Context (panel) | 全局态势 / 态势 | +| radio (station) | 电台 | +| transit / bikeshare | 公共交通 / 共享单车 | +| Previous / Next | 上一个 / 下一个 | +| compass points (N, NNE, …) | 北, 北东北, … | +| weather field / FIELD (selector) | 气象场 / 要素 | +| weather model / forecast lead hours | 模式 / 预报时效 | +| valid (forecast time) | 有效时间 | +| scene cloud / mosaic | 整景云量 / 镶嵌图 | +| pin (Recent Imagery) | 固定 | +| THIS (voice pointer) | 所指 | +| outline / trace an outline | 轮廓 / 绘制轮廓 | +| mark (map annotation) | 标注 | +| camera badge | 摄像头标记 | +| provider / embedded provider controls | 服务商 / 平台控件 | +| share (a view or link) | 分享 | +| AI agent | AI 智能体 | +| stage (rocket) / downrange | 子级 / 航程 | +| capture shot / camera move | 保存镜头 / 运镜 | +| story beat / evidence beat | 事件节点 / 证据节点 | +| camera pose | 位姿 | +| forecast uncertainty cone | 预报不确定范围 | +| seed (catalog data) | 预置 | +| frame (bring into view) | 将…纳入视野 | diff --git a/scripts/package-boundaries.json b/scripts/package-boundaries.json index 9dc4bd27d..a7c35c286 100644 --- a/scripts/package-boundaries.json +++ b/scripts/package-boundaries.json @@ -437,7 +437,8 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", - "src/i18n/locales/ru.json" + "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json" ], "external": [] }, @@ -462,7 +463,8 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", - "src/i18n/locales/ru.json" + "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json" ], "external": ["cesium"] }, @@ -478,7 +480,8 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", - "src/i18n/locales/ru.json" + "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json" ], "external": [] }, @@ -507,6 +510,7 @@ "src/i18n/locale.js", "src/i18n/locales/es.json", "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json", "src/ui/unitLabels.js", "src/ui/keyGuidance.js", "src/ui/statusText.js", @@ -526,7 +530,8 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", - "src/i18n/locales/ru.json" + "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json" ], "external": [] }, @@ -541,7 +546,8 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", - "src/i18n/locales/ru.json" + "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json" ], "external": [] }, @@ -560,6 +566,7 @@ "src/i18n/locale.js", "src/i18n/locales/es.json", "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json", "src/ui/unitLabels.js", "src/ui/statusBadge.js" ], @@ -585,6 +592,7 @@ "src/i18n/locale.js", "src/i18n/locales/es.json", "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json", "src/ui/layerNames.js" ], "external": [] @@ -618,6 +626,7 @@ "src/i18n/locale.js", "src/i18n/locales/es.json", "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json", "src/ui/unitLabels.js" ], "external": ["cesium"] @@ -633,6 +642,7 @@ "src/i18n/locale.js", "src/i18n/locales/es.json", "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json", "src/ui/unitLabels.js" ], "external": [] @@ -785,6 +795,7 @@ "src/i18n/locale.js", "src/i18n/locales/es.json", "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json", "src/ui/unitLabels.js", "src/ui/keyGuidance.js", "src/ui/layerNames.js", @@ -904,6 +915,7 @@ "src/i18n/locale.js", "src/i18n/locales/es.json", "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json", "src/ui/unitLabels.js" ], "external": ["cesium"] @@ -969,6 +981,7 @@ "src/i18n/locale.js", "src/i18n/locales/es.json", "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json", "src/ui/unitLabels.js" ], "external": ["cesium"] @@ -1014,7 +1027,8 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", - "src/i18n/locales/ru.json" + "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json" ], "external": ["cesium"] }, @@ -1074,7 +1088,8 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", - "src/i18n/locales/ru.json" + "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json" ], "external": ["cesium"] }, @@ -1103,6 +1118,7 @@ "src/i18n/locale.js", "src/i18n/locales/es.json", "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json", "src/ui/unitLabels.js", "src/layers/recentImagery/display.js" ], @@ -1131,7 +1147,8 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", - "src/i18n/locales/ru.json" + "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json" ], "external": ["cesium"] }, @@ -1162,6 +1179,7 @@ "src/i18n/locale.js", "src/i18n/locales/es.json", "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json", "src/ui/unitLabels.js" ], "external": ["cesium", "satellite.js"] @@ -1193,7 +1211,8 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", - "src/i18n/locales/ru.json" + "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json" ], "external": ["cesium"] }, @@ -1235,6 +1254,7 @@ "src/i18n/locale.js", "src/i18n/locales/es.json", "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json", "src/layers/installations/classLabel.js" ], "external": ["cesium", "@mapbox/vector-tile", "pbf"] @@ -1269,6 +1289,7 @@ "src/i18n/locale.js", "src/i18n/locales/es.json", "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json", "src/ui/unitLabels.js" ], "external": ["cesium"] @@ -1317,7 +1338,8 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", - "src/i18n/locales/ru.json" + "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json" ], "external": ["cesium", "pbf", "@mapbox/vector-tile"] }, @@ -1345,7 +1367,8 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", - "src/i18n/locales/ru.json" + "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json" ], "external": ["cesium"] }, @@ -1392,7 +1415,8 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", - "src/i18n/locales/ru.json" + "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json" ], "external": ["hls.js", "cesium"] }, @@ -1453,6 +1477,7 @@ "src/i18n/locale.js", "src/i18n/locales/es.json", "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json", "src/layers/streetLevel/providers/mapillary/policy.js" ], "external": ["cesium", "pbf", "@mapbox/vector-tile", "mapillary-js"] @@ -1480,7 +1505,8 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", - "src/i18n/locales/ru.json" + "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json" ], "external": ["cesium", "@mapbox/vector-tile", "pbf"] }, @@ -1519,7 +1545,8 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", - "src/i18n/locales/ru.json" + "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json" ], "external": ["cesium"] }, @@ -1540,7 +1567,8 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", - "src/i18n/locales/ru.json" + "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json" ], "external": ["cesium"] }, @@ -1595,7 +1623,8 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", - "src/i18n/locales/ru.json" + "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json" ], "external": ["cesium"] }, @@ -1651,6 +1680,7 @@ "src/i18n/locale.js", "src/i18n/locales/es.json", "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json", "src/ui/statusText.js", "src/voice/providerCommands.js", "src/voice/providerCapabilities.js", @@ -2512,6 +2542,7 @@ "src/i18n/locale.js", "src/i18n/locales/es.json", "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json", "src/ui/unitLabels.js", "src/layers/recentImagery/display.js", "src/ui/keyGuidance.js", @@ -3087,6 +3118,7 @@ "src/i18n/locale.js", "src/i18n/locales/es.json", "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json", "src/ui/unitLabels.js", "src/layers/recentImagery/display.js", "src/layers/installations/classLabel.js", @@ -3193,6 +3225,7 @@ "src/i18n/locale.js", "src/i18n/locales/es.json", "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json", "src/i18n/index.js" ], "external": ["cesium"] @@ -3213,7 +3246,8 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", - "src/i18n/locales/ru.json" + "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json" ], "external": [] }, @@ -3247,7 +3281,8 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", - "src/i18n/locales/ru.json" + "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json" ], "external": ["cesium"] }, @@ -3270,7 +3305,8 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", - "src/i18n/locales/ru.json" + "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json" ], "external": [] }, @@ -3361,6 +3397,7 @@ "src/i18n/locale.js", "src/i18n/locales/es.json", "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json", "src/ui/unitLabels.js" ], "external": ["cesium"] @@ -3415,6 +3452,7 @@ "src/i18n/locale.js", "src/i18n/locales/es.json", "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json", "src/ui/statusText.js", "src/voice/providerCapabilities.js" ], @@ -3667,7 +3705,8 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", - "src/i18n/locales/ru.json" + "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json" ], "external": ["cesium", "egm96-universal", "pbf"] }, @@ -3709,7 +3748,8 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", - "src/i18n/locales/ru.json" + "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json" ], "external": ["cesium", "pbf"] }, @@ -3761,7 +3801,8 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", - "src/i18n/locales/ru.json" + "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json" ], "external": ["cesium"] }, @@ -3777,6 +3818,7 @@ "src/i18n/locale.js", "src/i18n/locales/es.json", "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json", "src/ui/statusText.js", "src/ui/layerNames.js" ], @@ -3969,6 +4011,7 @@ "src/i18n/locale.js", "src/i18n/locales/es.json", "src/i18n/locales/ru.json", + "src/i18n/locales/zh-CN.json", "src/ui/statusText.js", "src/ui/layerNames.js" ], diff --git a/src/data/bhoteKoshiEvent.js b/src/data/bhoteKoshiEvent.js index 2baaf7031..434aff6d0 100644 --- a/src/data/bhoteKoshiEvent.js +++ b/src/data/bhoteKoshiEvent.js @@ -412,14 +412,18 @@ const IMAGERY_LABEL_NAMES = Object.freeze([ [/^(\d{4}) POST-EVENT$/, msg('{year} POST-EVENT')], ]); -/** Day, short month and year in the active language: 16 OCT 2021, 16 ОКТ. 2021. */ +/** Day, short month and year in the active language: 16 OCT 2021, 16 ОКТ. 2021, 2021年10月16日. */ function imageryDateText(observedAt) { const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(observedAt || '')); if (!match) return ''; const date = new Date(Date.UTC(+match[1], +match[2] - 1, +match[3])); if (Number.isNaN(date.getTime())) return ''; const part = (options) => formatDate(date, { timeZone: 'UTC', ...options }); - return `${part({ day: 'numeric' })} ${part({ month: 'short' })} ${part({ year: 'numeric' })}`.toUpperCase(); + const year = part({ year: 'numeric' }); + // Year-first languages (2021年10月16日) keep their own order and units. + const whole = part({ day: 'numeric', month: 'short', year: 'numeric' }); + if (whole.startsWith(year)) return whole; + return `${part({ day: 'numeric' })} ${part({ month: 'short' })} ${year}`.toUpperCase(); } /** @@ -1029,7 +1033,7 @@ function createPanel(event, handlers) { ${escapeMarkup(t('GEOLOCATIONS · {credit}', { credit: 'GEO GEORGE SHADRACH' }))} -

${escapeMarkup(t('Clouds are preserved from the source imagery.'))}

+

${escapeMarkup(t(' Clouds are preserved from the source imagery.'))}

`; const refs = collectPanelReferences(panel); diff --git a/src/hudLocalityFormat.js b/src/hudLocalityFormat.js index 4a82fdbb3..b508f8d59 100644 --- a/src/hudLocalityFormat.js +++ b/src/hudLocalityFormat.js @@ -15,7 +15,9 @@ export function hudQuantity(value, unit, digits = 0) { minimumFractionDigits: digits, maximumFractionDigits: digits, }); - return `${number}${unitLabel(unit).toUpperCase()}`; + const label = unitLabel(unit); + if (getLocale() === 'zh-CN') return label ? `${number} ${label}` : number; + return `${number}${label.toUpperCase()}`; } /** One hemisphere-tagged coordinate, e.g. `21.33N` / `21,33С`. */ @@ -35,7 +37,16 @@ export const LOCALIZED_LOCALITY = Object.freeze({ }), sector: ({ latDeg, lonDeg }) => t('SECTOR {lat} {lon}', { - lat: hudCoordinate(latDeg, t('N'), t('S')), - lon: hudCoordinate(lonDeg, t('E'), t('W')), + // Hemisphere letters, not compass words: 21.33N, never 21.33北. + lat: hudCoordinate( + latDeg, + t('N', { context: 'hemisphere' }), + t('S', { context: 'hemisphere' }), + ), + lon: hudCoordinate( + lonDeg, + t('E', { context: 'hemisphere' }), + t('W', { context: 'hemisphere' }), + ), }), }); diff --git a/src/i18n/firms-live-zh.test.mjs b/src/i18n/firms-live-zh.test.mjs new file mode 100644 index 000000000..649842e7f --- /dev/null +++ b/src/i18n/firms-live-zh.test.mjs @@ -0,0 +1,43 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { getLocale, setLocale } from './index.js'; +import { createModel } from '../layers/firms/model.js'; +import { createQueries } from '../layers/firms/queries.js'; + +const cases = [ + [0, '不到 1 分钟前', '<1m ago'], + [120000, '2 分钟前', '2m ago'], + [7200000, '2 小时前', '2h ago'], +]; + +for (const [age, zh, en] of cases) { + test(`FIRMS live age ${age} ms has one relative-time suffix`, (context) => { + const now = 1800000000000; + context.mock.method(Date, 'now', () => now); + const previousLocale = getLocale(); + const layerState = { + _loading: false, + _stale: false, + _keyRequired: false, + _error: null, + _lastUpdate: now - age, + _count: 1, + _cellCount: 0, + _fires: [], + }; + const model = createModel({ services: { anchors: {} } }); + const { methods } = createQueries({ + layerState, + components: { model }, + config: {}, + }); + try { + setLocale('en'); + assert.equal(methods.getStats().loadingLabel, `LIVE · updated ${en}`); + setLocale('zh-CN'); + assert.equal(methods.getStats().loadingLabel, `实时 · 更新于 ${zh}`); + } finally { + setLocale(previousLocale); + } + }); +} diff --git a/src/i18n/i18n.test.mjs b/src/i18n/i18n.test.mjs index a662130a1..70eaf7e86 100644 --- a/src/i18n/i18n.test.mjs +++ b/src/i18n/i18n.test.mjs @@ -35,6 +35,12 @@ test('normalizeLocale folds regional variants and rejects unshipped tags', () => assert.equal(normalizeLocale(' RU '), 'ru'); assert.equal(normalizeLocale('en_GB'), 'en'); assert.equal(normalizeLocale('xx'), null); + // Simplified Chinese by default; Traditional tags wait for zh-TW. + assert.equal(normalizeLocale('zh'), 'zh-CN'); + assert.equal(normalizeLocale('zh-CN'), 'zh-CN'); + assert.equal(normalizeLocale('zh-Hans-SG'), 'zh-CN'); + assert.equal(normalizeLocale('zh-TW'), null); + assert.equal(normalizeLocale('zh-Hant-HK'), null); assert.equal(normalizeLocale(undefined), null); }); diff --git a/src/i18n/index.js b/src/i18n/index.js index ccc54a5e6..d5698aa5b 100644 --- a/src/i18n/index.js +++ b/src/i18n/index.js @@ -18,8 +18,9 @@ import { } from './locale.js'; import es from './locales/es.json' with { type: 'json' }; import ru from './locales/ru.json' with { type: 'json' }; +import zhCN from './locales/zh-CN.json' with { type: 'json' }; -const CATALOGS = Object.freeze({ es, ru }); +const CATALOGS = Object.freeze({ es, ru, 'zh-CN': zhCN }); let currentLocale = DEFAULT_LOCALE; diff --git a/src/i18n/locale.js b/src/i18n/locale.js index 626303ad7..fab95e5cc 100644 --- a/src/i18n/locale.js +++ b/src/i18n/locale.js @@ -8,25 +8,32 @@ export const LOCALE_STORAGE_KEY = 'gev:locale:v1'; export const DEFAULT_LOCALE = 'en'; /** Shipped locales; order does not matter. Adding one: docs/TRANSLATORS.md. */ -export const LOCALES = Object.freeze(['en', 'es', 'ru']); +export const LOCALES = Object.freeze(['en', 'es', 'ru', 'zh-CN']); /** Each locale named in its own language, for the selector. */ export const LOCALE_NAMES = Object.freeze({ en: 'English', es: 'Español', ru: 'Русский', + 'zh-CN': '简体中文', }); /** Right-to-left locales. */ const RTL_LOCALES = Object.freeze([]); /** - * Map any language tag to a shipped locale: 'ru-RU' and 'RU' → 'ru'. + * Map any language tag to a shipped locale: 'ru-RU' and 'RU' → 'ru', + * 'zh', 'zh-CN' and 'zh-Hans-SG' → 'zh-CN'. * @returns {string|null} */ export function normalizeLocale(candidate) { if (typeof candidate !== 'string') return null; - const primary = candidate.trim().toLowerCase().split(/[-_]/)[0]; + const [primary, ...rest] = candidate.trim().toLowerCase().split(/[-_]/); + // Traditional Chinese readers are not given Simplified: no zh-TW yet. + if (primary === 'zh') + return rest.some((part) => ['hant', 'tw', 'hk', 'mo'].includes(part)) + ? null + : 'zh-CN'; return LOCALES.includes(primary) ? primary : null; } diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json index 76ed72b3d..3c5602ae7 100644 --- a/src/i18n/locales/es.json +++ b/src/i18n/locales/es.json @@ -1086,7 +1086,6 @@ "FIELD REPORTS · {count}": "INFORMES DE CAMPO · {count}", "GEOLOCATIONS · {credit}": "GEOLOCALIZACIONES · {credit}", "↗ OPEN PUBLIC MAP": "↗ ABRIR MAPA PÚBLICO", - "Clouds are preserved from the source imagery.": "Las nubes provienen de las imágenes de origen.", "Show the Nepal context callout without a border or country fill": "Muestra la etiqueta de contexto de Nepal sin frontera ni relleno del país", "Introduce Nepal, draw its border, then reveal the Bhote Koshi incident": "Presenta Nepal, dibuja su frontera y luego revela el incidente del Bhote Koshi", "CITIES": "CIUDADES", @@ -2937,5 +2936,12 @@ "Gemini access was refused. Check the API key and model access.": "Se denegó el acceso a Gemini. Comprueba la clave de API y el acceso al modelo.", "Gemini Live is unavailable for the configured model.": "Gemini Live no está disponible para el modelo configurado.", "Token request is too large": "La solicitud de credencial es demasiado grande", - "Use an empty JSON token request": "Usa una solicitud de credencial con un objeto JSON vacío" + "Use an empty JSON token request": "Usa una solicitud de credencial con un objeto JSON vacío", + " Clouds are preserved from the source imagery.": " Las nubes provienen de las imágenes de origen.", + "hemisphere|N": "N", + "hemisphere|S": "S", + "hemisphere|E": "E", + "hemisphere|W": "O", + "OK": "OK", + "{category} ({count})": "{category} ({count})" } diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index 81cd13b55..7a790e04e 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -1100,7 +1100,6 @@ "FIELD REPORTS · {count}": "СООБЩЕНИЯ С МЕСТА · {count}", "GEOLOCATIONS · {credit}": "ГЕОЛОКАЦИИ · {credit}", "↗ OPEN PUBLIC MAP": "↗ ОТКРЫТЬ ПУБЛИЧНУЮ КАРТУ", - "Clouds are preserved from the source imagery.": "Облака оставлены такими, как на исходных снимках.", "Show the Nepal context callout without a border or country fill": "Показать выноску с контекстом Непала без границы и заливки страны", "Introduce Nepal, draw its border, then reveal the Bhote Koshi incident": "Представить Непал, нарисовать его границу, затем показать инцидент на Bhote Koshi", "CITIES": "ГОРОДА", @@ -3004,5 +3003,12 @@ "Gemini access was refused. Check the API key and model access.": "Доступ к Gemini запрещён. Проверьте ключ API и доступ к модели.", "Gemini Live is unavailable for the configured model.": "Gemini Live недоступен для выбранной модели.", "Token request is too large": "Запрос токена слишком велик", - "Use an empty JSON token request": "Используйте запрос токена с пустым объектом JSON" + "Use an empty JSON token request": "Используйте запрос токена с пустым объектом JSON", + " Clouds are preserved from the source imagery.": " Облака оставлены такими, как на исходных снимках.", + "hemisphere|N": "С", + "hemisphere|S": "Ю", + "hemisphere|E": "В", + "hemisphere|W": "З", + "OK": "НОРМА", + "{category} ({count})": "{category} ({count})" } diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json new file mode 100644 index 000000000..46ccf96a7 --- /dev/null +++ b/src/i18n/locales/zh-CN.json @@ -0,0 +1,2813 @@ +{ + "Pick a shape, then click the map.": "先选择形状,再在地图上点击。", + "Enter to place the pin, Esc to cancel.": "按 Enter 放置图钉,按 Esc 取消。", + "Click where the pin goes.": "点击图钉的放置位置。", + "Click {count} more points.": { + "other": "再点击 {count} 个点。" + }, + "Those points are in a line — move one off it to enclose an area.": "这些点都在同一条直线上——请移开其中一个点才能围出区域。", + "That line has no length — click somewhere further away.": "这条线没有长度——请在更远处点击。", + " · {max}-point limit reached": " · 已达 {max} 个点上限", + "{measure} · double-click or Enter to finish, Backspace undoes, Esc cancels.{full}": "{measure} · 双击或按 Enter 完成,按 Backspace 撤销,按 Esc 取消。{full}", + "drawings on the map": "地图上的绘图", + "That shape already has {count} points — finish it or press Backspace.": "该形状已有 {count} 个点——请完成绘制或按 Backspace。", + "That point is off the globe — click on the world.": "该点在地球之外——请点击地球上的位置。", + "That shape could not be placed.": "该形状无法放置。", + "Could not place the shape: {error}": "无法放置形状:{error}", + "Board cleared.": "画板已清除。", + "{owner} is using the pointer — close it first.": "{owner} 正在使用指针——请先关闭它。", + "{label} · Detailed outline unavailable": "{label} · 无法获取详细轮廓", + "Flying to Austin, TX...": "正在飞往德克萨斯州奥斯汀…", + "Restoring shared view...": "正在恢复分享视图…", + "Configuring viewer...": "正在配置视图…", + "Loading Google 3D Tiles...": "正在加载 Google 3D Tiles…", + "Loading the keyless globe...": "正在加载免密钥地球…", + "Google 3D Tiles unavailable ({detail}). Loading the keyless globe...": "Google 3D Tiles 不可用({detail})。正在加载免密钥地球…", + "Initializing systems...": "正在初始化系统…", + "Space Missions isolates replay data. Exit the mode to enable {layer}.": "太空任务模式会隔离回放数据。退出该模式后才能启用“{layer}”。", + "Data attribution": "数据来源说明", + "Data provided by:": "数据提供方:", + "Close data attribution": "关闭数据来源说明", + "Light aircraft": "轻型飞机", + "Glider": "滑翔机", + "Turboprop": "涡桨飞机", + "Airliner": "民航客机", + "Widebody": "宽体机", + "Four-engine jet": "四发喷气机", + "Helicopter": "直升机", + "Fast jet": "喷气战机", + "Business jet": "公务机", + "Drone": "无人机", + "General emergency": "一般紧急情况", + "Unlawful interference": "非法干扰", + "Radio failure": "无线电失效", + "Downed aircraft": "飞机坠毁", + "Minimum fuel": "最低油量", + "Lifeguard (medical priority)": "救护飞行(医疗优先)", + "Rasuwagadhi witness video": "Rasuwagadhi 目击视频", + "Bhote Koshi valley footage": "Bhote Koshi 河谷画面", + "Flood footage": "洪水画面", + "Compilation and casualty claims": "视频合集与伤亡说法", + "SOURCE GEOCONFIRMED · WORLD-PINNED": "来源位置已核实 · 已在地图上标注", + "SOURCE CREDITED · LOCATION UNRESOLVED": "来源已署名 · 位置未确定", + "REFERENCE ONLY · CLAIMS UNVERIFIED": "仅供参考 · 说法未核实", + "SOURCE GEOCONFIRMED": "来源位置已核实", + "2021 historical reference versus 2026 post-event imagery; changes are cumulative and include the July 2025 Rasuwagadhi flood. Schematic downstream progression, not modeled arrival time; not an official hazard model.": "2021 年历史参考影像与 2026 年灾后影像对比;变化为累计结果,包含 2025 年 7 月的 Rasuwagadhi 洪水。下游演进为示意,并非模拟的到达时间;也不是官方灾害模型。", + "Immediate collapse viewpoint": "近距离崩塌观察点", + "Collapse viewpoint": "崩塌观察点", + "Debris-dammed lake": "碎屑堰塞湖", + "Dammed lake": "堰塞湖", + "Second landslide": "第二次滑坡", + "Gyirong border gate": "Gyirong 边境口岸", + "Border gate": "边境口岸", + "Timure evidence cluster": "Timure 证据群", + "Mailung / Upper Trishuli bridge": "Mailung / Upper Trishuli 桥", + "Dandagaun viewpoint restaurant": "Dandagaun 观景餐厅", + "Dandagaun viewpoint": "Dandagaun 观景点", + "Bidur - Trishuli bridge": "Bidur - Trishuli 桥", + "Bidur bridge": "Bidur 桥", + "Devighat / Taadi Khola Bridge": "Devighat / Taadi Khola 桥", + "Devighat bridge": "Devighat 桥", + "Facebook player API did not initialize.": "Facebook 播放器 API 未能初始化。", + "Facebook player API could not load.": "无法加载 Facebook 播放器 API。", + "Facebook player API timed out.": "Facebook 播放器 API 超时。", + "X embed API did not initialize.": "X 嵌入 API 未能初始化。", + "X embed could not load.": "无法加载 X 嵌入内容。", + "X embed timed out.": "X 嵌入内容超时。", + "UNAVAILABLE": "不可用", + "TIMEOUT": "超时", + "BLOCKED": "被拦截", + "SOURCE": "来源", + "Facebook embed unavailable. The post may be private, removed, region-restricted, or blocked.": "Facebook 嵌入内容不可用。该帖子可能为私密、已删除、受地区限制或被拦截。", + "Preloading {provider} media": "正在预加载 {provider} 媒体", + "LOADING X POST…": "正在加载 X 帖子…", + "The X post is unavailable or cannot be embedded.": "该 X 帖子不可用或无法嵌入。", + "X POST · PROVIDER CONTROLS": "X 帖子 · 平台控件", + "{reason} OPEN ORIGINAL.": "{reason} 请打开原始来源。", + "X embed unavailable": "X 嵌入内容不可用。", + "FACEBOOK · PROVIDER CONTROLS": "FACEBOOK · 平台控件", + "Facebook embed unavailable": "Facebook 嵌入内容不可用。", + "AUTOPLAY BLOCKED · USE PROVIDER CONTROLS": "自动播放被拦截 · 请使用平台控件", + "Embedded {provider} media for {source}": "{source} 的嵌入 {provider} 媒体", + "witness source": "目击来源", + "Witness source": "目击来源", + "{provider} · EMBEDDED SOURCE": "{provider} · 嵌入来源", + "OPEN ORIGINAL ↗": "打开原始来源 ↗", + "{provider} media: {source}": "{provider} 媒体:{source}", + "{provider} · PROVIDER CONTROLS": "{provider} · 平台控件", + "YOUTUBE {state} · OPEN ORIGINAL": "YOUTUBE {state} · 打开原始来源", + "INITIATION": "起始", + "DAMMING": "堰塞", + "SECONDARY FAILURE": "第二次滑坡", + "BORDER ARRIVAL": "抵达边境", + "PROPAGATION": "演进", + "DOWNSTREAM PASSAGE": "向下游推进", + "CONSEQUENCE": "后果", + "DOWNSTREAM CONSEQUENCE": "下游后果", + "EVIDENCE": "证据", + "{year} HISTORICAL REFERENCE": "{year} 年历史参考", + "{year} POST-EVENT": "{year} 年灾后", + "{count} GEOLOCATED SOURCE-MAP PLACEMENTS": { + "other": "{count} 个地理定位来源点位" + }, + "NO PREVIEW": "无预览", + "YouTube source": "YouTube 来源", + "Facebook source": "Facebook 来源", + "SOURCE LINK": "来源链接", + "{publisher} / OPEN": "{publisher} / 打开", + "Bhote Koshi flood reconstruction controls": "Bhote Koshi 洪水重构控制", + "EVENT RECONSTRUCTION · 26 AUG 2026": "事件重构 · 2026-08-26", + "BHOTE KOSHI OUTBURST FLOOD": "BHOTE KOSHI 溃决洪水", + "Close event layer": "关闭事件图层", + "OBSERVED IMAGERY": "观测影像", + "SCHEMATIC CORRIDOR": "示意走廊", + "2021 HISTORICAL REFERENCE / 2026 POST-EVENT": "2021 年历史参考 / 2026 年灾后", + "Historical reference and post-event image split": "历史参考与灾后影像的分割", + "RECONSTRUCTION CLOCK": "重构时钟", + "Schematic downstream progression": "下游演进示意", + "Story beat navigation": "事件节点导航", + "Previous story beat": "上一个事件节点", + "CAUSE": "成因", + "CAPTURE TIME UNVERIFIED": "拍摄时间未核实", + "Next story beat": "下一个事件节点", + "▶ PLAY": "▶ 播放", + "Play the next shot and continue through this scene": "播放下一个镜头,继续播放整个场景", + "▶ PLAY SCENE": "▶ 播放场景", + "◉ CINEMATIC": "◉ 运镜", + "↺ FULL STORY": "↺ 完整故事", + "↗ OPEN ORIGINAL": "↗ 打开原始来源", + "⌖ LOWER GORGE": "⌖ 下游峡谷", + "FIELD REPORTS · {count}": "现场报道 · {count}", + "GEOLOCATIONS · {credit}": "地理定位 · {credit}", + "↗ OPEN PUBLIC MAP": "↗ 打开公共地图", + " Clouds are preserved from the source imagery.": "云层按源影像原样保留。", + "Rasuwagadhi witness": "Rasuwagadhi 目击者", + "Open original source for {source}; capture time unverified": "打开 {source} 的原始来源;拍摄时间未核实", + "Historical reference and post-event image divider": "历史参考与灾后影像的分割线", + "A historical reference {before} percent, B post-event {after} percent": "A 历史参考 {before}%,B 灾后 {after}%", + "■ RELEASE CAMERA": "■ 释放相机", + "Seek Nepal scene clock": "定位尼泊尔场景时钟", + "NEPAL SCENE CLOCK": "尼泊尔场景时钟", + "… PLAYING SHOT": "… 镜头播放中", + "▶ PLAY SHOT": "▶ 播放镜头", + "Ⅱ PAUSE": "Ⅱ 暂停", + "↺ REPLAY CINEMATIC": "↺ 重播运镜", + "↺ REPLAY": "↺ 重播", + "… PLAYING SCENE": "… 场景播放中", + "Continue {scene} from next shot": "从下一镜头继续播放 {scene}", + "current scene": "当前场景", + "Replay the full reconstruction": "重播完整重构", + "SCENE SHOT": "场景镜头", + "TIME UNVERIFIED": "拍摄时间未核实", + "{count} SOURCES": { + "other": "{count} 个来源" + }, + "OUTSIDE IMAGERY SWIPE": "不在影像对比范围内", + "{scene} / AUTHORED SHOT": "{scene} / 编排镜头", + "NEPAL FLOOD INCIDENT": "尼泊尔洪水事件", + "Play {shot}": "播放 {shot}", + "current scene shot": "当前场景镜头", + "Play reconstruction": "播放重构", + "Load previous scene shot": "加载上一个场景镜头", + "Load next scene shot": "加载下一个场景镜头", + "SCHEMATIC CORRIDOR, NOT MODELED ARRIVAL TIME.": "示意走廊,并非模拟的到达时间。", + "Rasuwa · 26 Aug 2026": "Rasuwa · 2026-08-26", + "Event reconstruction": "事件重构", + "BHOTE KOSHI FLOOD INCIDENT": "BHOTE KOSHI 洪水事件", + "NEPAL · 26 AUG 2026": "尼泊尔 · 2026-08-26", + "USGS INITIAL REPORT": "USGS 初始报告", + "26 AUG 2026 · 08:37 NPT": "2026-08-26 · 08:37 NPT", + "28.2710° N · 85.5150° E": "北纬 28.2710° · 东经 85.5150°", + "INITIAL M4.4 · RECLASSIFIED M5.2 LANDSLIDE": "初报 M4.4 · 重新归类为 M5.2 滑坡", + "USGS EVENT us7000tbwb · OPEN SOURCE": "USGS 事件 us7000tbwb · 打开来源", + "Open the USGS initial report for the 26 August 2026 Nepal landslide": "打开 USGS 关于 2026 年 8 月 26 日尼泊尔滑坡的初始报告", + "Show the Nepal context callout without a border or country fill": "显示尼泊尔背景标注,不绘制边界和国家填充", + "Introduce Nepal, draw its border, then reveal the Bhote Koshi incident": "介绍尼泊尔,绘制其边界,再展示 Bhote Koshi 事件", + "CITIES": "城市", + "Keep the regional incident context and introduce nearby city labels": "保留区域事件背景,并加入附近城市标注", + "PLACES": "地点", + "Introduce the reconstruction scene key places from trigger to downstream": "依次介绍重构场景的关键地点,从触发点到下游", + "PATH": "路径", + "Animate the sourced cyan river-centerline route to Trishuli Bazaar": "以动画展示有来源依据的青色河道中线路径,直至 Trishuli Bazaar", + "OVERVIEW": "总览", + "Keep the completed flood path and location labels visible": "保持完整的洪水路径和地点标注可见", + "RECORD": "记录", + "Reveal the trigger record while making a slow camera approach": "镜头缓慢推近,同时展示触发事件记录", + "{speed} kn": "{speed} kn", + "That file is not valid JSON.": "该文件不是有效的 JSON。", + "That JSON file is not GeoJSON.": "该 JSON 文件不是 GeoJSON。", + "That FeatureCollection has no features list.": "该 FeatureCollection 没有 features 列表。", + "Every GeoJSON feature must have type Feature.": "每个 GeoJSON 要素的类型都必须是 Feature。", + "That GeoJSON has invalid geometries.": "该 GeoJSON 包含无效的几何图形。", + "That GeoJSON contains an unsupported geometry.": "该 GeoJSON 包含不受支持的几何图形。", + "That GeoJSON has invalid coordinates.": "该 GeoJSON 包含无效坐标。", + "That file creates more than {limit} map objects (the limit).": "该文件生成的地图对象超过上限 {limit} 个。", + "That file has more than {limit} style definitions and image links after cleanup (the limit).": "清理后,该文件的样式定义和图像链接仍超过上限 {limit} 个。", + "That file has more than {limit} features (the limit).": "该文件的要素超过上限 {limit} 个。", + "That file has more than {limit} points (the limit) across {features} features.": "该文件的点数超过上限 {limit} 个(共 {features} 个要素)。", + "That file has more than {limit} points (the limit).": "该文件的点数超过上限 {limit} 个。", + "That KMZ unpacks to more than the import limit.": "该 KMZ 解压后超过导入上限。", + "That KMZ is damaged and could not be unpacked.": "该 KMZ 已损坏,无法解压。", + "That KMZ is not a readable zip archive.": "该 KMZ 不是可读取的 zip 压缩包。", + "That KMZ uses a zip format this import does not read.": "该 KMZ 使用的 zip 格式无法导入。", + "That KMZ is encrypted.": "该 KMZ 已加密。", + "That KMZ uses a compression method this import does not read.": "该 KMZ 使用的压缩方式无法导入。", + "That KML has invalid geometry bounds.": "该 KML 的几何范围无效。", + "That KML has invalid coordinates.": "该 KML 包含无效坐标。", + "That GPX file has no document.": "该 GPX 文件没有 文档。", + "That {format} file is not valid XML.": "该 {format} 文件不是有效的 XML。", + "That file has an unreadable image or exceeds the 16-megapixel image limit.": "该文件包含无法读取的图像,或图像超过 1600 万像素上限。", + "That KML could not be read.": "无法读取该 KML。", + "That file has invalid point coordinates.": "该文件的点坐标无效。", + "Ground heights are not available for that file. Try again when the map has loaded.": "无法获取该文件的地面高度。请在地图加载完成后重试。", + "That file took too long to read.": "读取该文件耗时过长。", + "Nothing to import.": "没有可导入的内容。", + "That file is larger than {size} MiB.": "该文件大于 {size} MiB。", + "Only GeoJSON, KML, KMZ and GPX files can be imported.": "只能导入 GeoJSON、KML、KMZ 和 GPX 文件。", + "That file has nothing to draw.": "该文件没有可在地图上绘制的内容。", + "That KMZ holds no KML document.": "该 KMZ 中没有 KML 文档。", + "That {format} file could not be read.": "无法读取该 {format} 文件。", + "That file has {count} map objects; the limit is {limit}.": "该文件有 {count} 个地图对象,上限为 {limit} 个。", + "Ground heights could not be read. Try that file again.": "无法读取地面高度。请重新导入该文件。", + "That file has invalid point graphics.": "该文件的点样式无效。", + "Overpass rate-limited": "Overpass 已被限流", + "Overpass timed out": "Overpass 超时", + "Overpass could not complete the query": "Overpass 无法完成查询", + "Map tiles temporarily unavailable": "地图瓦片暂时不可用", + "Mapped names temporarily unavailable": "地点名称暂时不可用", + "Overpass temporarily unavailable": "Overpass 暂时不可用", + "Retrying mapped sites…": "正在重试加载地图标注设施…", + "Fetching mapped sites…": "正在获取地图标注设施…", + "{reason} — retrying in {seconds}s": "{reason}——{seconds} 秒后重试", + "{reason} — retry pending": "{reason}——等待重试", + "Zoom in to search mapped installations": "请放大地图以搜索地图标注的设施", + "Showing cached mapped sites": "正在显示缓存的地图标注设施", + "Mapped sites not loaded": "地图标注设施未加载", + "Mapped sites loaded": "已加载地图标注设施", + "Military airfield": "军用机场", + "Naval base": "海军基地", + "Firing range": "靶场", + "Military land": "军事用地", + "Mapped installation": "地图标注设施", + "Military area": "军事区域", + "flight": "航班", + "military flight": "军用航班", + "satellite": "卫星", + "feed unavailable": "数据源不可用", + "feed stale": "数据源过期", + "observed or mapped nearby context": "附近的观测或地图标注态势", + "no observed or mapped objects in current feeds": "当前数据源中没有观测或地图标注的对象", + "Draw": "绘制", + "Directions": "路线导航", + "Recent imagery": "近期影像", + "STATION": "空间站", + "Crewed stations and their visiting vehicles": "载人空间站及其来访航天器", + "NAV": "导航", + "GNSS navigation — GPS, GLONASS, Galileo": "GNSS 导航:GPS、GLONASS、Galileo", + "GEO": "GEO", + "Geostationary belt — comms and weather, fixed over the equator": "地球静止轨道带:通信和气象,固定在赤道上空", + "VISUAL": "目视可见", + "Brightest naked-eye objects — CelesTrak visual group": "最亮的肉眼可见天体:CelesTrak visual 组", + "COMMS": "通信", + "Broadband constellation shell — shown only in DENSE mode": "宽带星座壳层——仅在“密集”模式下显示", + "FISHING": "渔船", + "TOWING": "拖带", + "DREDGER": "挖泥船", + "DIVE OPS": "潜水作业", + "MILITARY": "军用", + "SAILING": "帆船", + "PLEASURE": "游艇", + "PILOT": "引航", + "SAR": "搜救", + "TUG": "拖轮", + "PORT TENDER": "港口供应船", + "ANTI-POLLUTION": "防污染", + "LAW ENFORCE": "执法", + "MEDICAL": "医疗", + "HIGH-SPEED": "高速", + "PASSENGER": "客船", + "CARGO": "货船", + "TANKER": "油轮", + "OTHER": "其他", + "Included in bundle": "已包含在资源包中", + "Missing bundle file — reimport its bundle": "缺少资源包文件——请重新导入其资源包", + "Source configured; file checked when loaded": "数据源已配置;文件在加载时校验", + "Source unavailable": "数据源不可用", + "ENVIRONMENTAL": "环境", + "EARTH WATCH": "地球监测", + "ACTIVE EVENTS": "活跃事件", + "Starting live contacts…": "正在接入实时目标…", + "Opening space missions…": "正在打开太空任务…", + "Scanning active events…": "正在扫描活跃事件…", + "Live earthquakes and active fires, from USGS and NASA": "来自 USGS 和 NASA 的实时地震与活跃火点", + "It feels like a forbidden cockpit—then you realize the sources are public and the data is real.": "仿佛闯进了一个机密座舱——随后你会发现,数据来源全部公开,数据全部真实。", + "Working…": "处理中…", + "Could not open that mission{detail}. Retry or explore manually.": "无法打开该任务{detail}。请重试或手动探索。", + "This browser is blocking storage, so that could not be saved.": "此浏览器阻止了存储,因此无法保存。", + "NORMAL": "常规", + "CRT": "CRT", + "NVG": "NVG", + "FLIR": "FLIR", + "ANIME": "动漫", + "NOIR": "黑白", + "SNOW": "雪景", + "ARCTIC": "北极", + "ANTARCTIC": "南极", + "NORTH AMERICA": "北美洲", + "SOUTH AMERICA": "南美洲", + "EUROPE": "欧洲", + "AFRICA": "非洲", + "ASIA": "亚洲", + "OCEANIA": "大洋洲", + "NORTHERN OCEANIC GRID": "北方海洋网格", + "SOUTHERN OCEANIC GRID": "南方海洋网格", + "LOADING": "加载中", + "DEGRADED": "降级", + "STALE": "过期", + "PARTIAL": "部分", + "FALLBACK": "备用", + "{state} {layers}": "{state} {layers}", + "view band|STREET": "街道", + "view band|CITY": "城市", + "view band|METRO": "都市圈", + "view band|REGIONAL": "区域", + "view band|GLOBAL": "全球", + "SUMMARY": "摘要", + "Awaiting telemetry...": "等待遥测数据…", + "REC": "REC", + "AIS: --": "AIS: --", + "N/A": "无", + "{mode} {band} {locality} | {region} | ALT {alt} | WINDOW {window} | SUN {sun}° | ONA {ona}° | {zone}": "{mode} {band} {locality} | {region} | ALT {alt} | 窗口 {window} | SUN {sun}° | ONA {ona}° | {zone}", + "NEAR {poi} ({city}) {distance}": "{poi}({city})附近 {distance}", + "SECTOR {lat} {lon}": "扇区 {lat} {lon}", + "N": "北", + "S": "南", + "E": "东", + "W": "西", + "POWER UP · {count} KEYS WAITING": { + "other": "增强 · {count} 个密钥待配置" + }, + "POWERED UP": "已增强", + "Provider Settings does not answer proxied requests": "服务商设置不响应经代理的请求", + "Provider Settings is disabled while sharing is enabled": "启用分享时,服务商设置不可用", + "Provider Settings answers only the machine running the server": "服务商设置仅响应运行服务器的那台机器", + "Provider Settings answers only local hostnames": "服务商设置只响应本地主机名", + "Provider Settings requires an exact local Origin": "服务商设置要求完全匹配的本地 Origin", + "Unrecognized Origin refused": "已拒绝无法识别的 Origin", + "Cross-origin requests are refused": "拒绝跨源请求", + "Content-Type must be application/json": "Content-Type 必须为 application/json", + "Method not allowed": "不允许的方法", + "Request too large": "请求过大", + "Invalid JSON": "无效的 JSON", + "Body must be a JSON object of {ENV_VAR: value}": "请求体必须是 {ENV_VAR: value} 形式的 JSON 对象", + "No keys provided": "未提供密钥", + "At most {limit} keys per save": "每次最多保存 {limit} 个密钥", + "Unknown key: {name}": "未知密钥:{name}", + "{name} must be a string": "{name} 必须是字符串", + "{name} is empty": "{name} 为空", + "{name} is longer than any real key ({limit} max)": "{name} 比任何真实密钥都长(最多 {limit} 个字符)", + "{name} may only contain printable characters with no spaces": "{name} 只能包含可打印字符,且不能有空格", + "{name} contains a character that is not valid in a key (#, quotes, $, \\, or backtick)": "{name} 包含密钥中不允许的字符(#、引号、$、\\ 或反引号)", + "{name} is configured outside Provider Settings and can only be changed where it was set": "{name} 是在服务商设置之外配置的,只能在原设置处修改", + "The key was not saved: could not restrict the credential file to your account; nothing was saved": "密钥未保存:无法将凭据文件限制为仅你的账户可访问;未保存任何内容", + "The key was not saved: the existing configuration could not be read, so nothing was changed": "密钥未保存:无法读取现有配置,因此未做任何更改", + "Could not write the {store} store": "无法写入 {store} 存储", + "Save failed ({status}).": "保存失败({status})。", + "OAuth check failed: {detail}": "OAuth 检查失败:{detail}", + "Could not check ChatGPT sign-in. Try again.": "无法检查 ChatGPT 登录状态,请重试。", + "VOICE AUTH · CHATGPT OAUTH": "语音认证 · CHATGPT OAUTH", + "VOICE AUTH · API KEY": "语音认证 · API 密钥", + "USE API KEY": "使用 API 密钥", + "USE CHATGPT OAUTH": "使用 CHATGPT OAUTH", + "Use OPENAI_API_KEY for the next cloud voice session": "下次云语音会话使用 OPENAI_API_KEY", + "Use the signed-in local ChatGPT/Codex OAuth session for the next cloud voice session": "下次云语音会话使用本机已登录的 ChatGPT/Codex OAuth 会话", + "Metered — a billing-enabled account": "按量计费——需要启用结算的账号", + "Optional paid token — public access works without one": "可选的付费令牌——无需令牌也可公开访问", + "Free key — register, paste, done": "免费密钥——注册、粘贴,即可使用", + "browser-side": "浏览器端", + "This key runs in the browser by design — restrict it at the provider (see SECURITY.md)": "此密钥设计上就在浏览器中运行——请在服务商处设置限制(见 SECURITY.md)", + "configured externally": "由外部配置", + "Supplied by your environment, Keychain, or launcher — change it where it was set": "由你的环境、钥匙串或启动器提供——请在原设置处修改", + "MANAGE ↗": "管理 ↗", + "GET KEY ↗": "获取密钥 ↗", + "{envVar} saved — paste to replace": "{envVar} 已保存——粘贴可替换", + "paste {envVar}": "粘贴 {envVar}", + "REMOVE": "移除", + "Remove {title} from this app's saved keys": "从本应用已保存的密钥中移除 {title}", + "unsaved provider keys": "未保存的服务商密钥", + "Project keys: {status}": "项目密钥:{status}", + "your app configuration": "你的应用配置", + "your local .env": "你的本地 .env", + "Saving…": "正在保存…", + "Save failed: {detail}": "保存失败:{detail}", + "Paste at least one key first.": "请先粘贴至少一个密钥。", + "Saved to {store}. Restarting — this page reloads itself.": "已保存到 {store}。正在重启——本页面将自动重新加载。", + "Cloud voice will use OPENAI_API_KEY on the next session.": "云语音将在下次会话使用 OPENAI_API_KEY。", + "Checking local ChatGPT OAuth sign-in…": "正在检查本机 ChatGPT OAuth 登录…", + "Could not check ChatGPT sign-in.": "无法检查 ChatGPT 登录状态。", + "ChatGPT OAuth selected for cloud voice. Your API key stays saved and available.": "已为云语音选择 ChatGPT OAuth。你的 API 密钥仍会保留并可用。", + "ChatGPT sign-in expired. Opening sign-in again…": "ChatGPT 登录已过期。正在重新打开登录…", + "Opening ChatGPT sign-in in your browser…": "正在浏览器中打开 ChatGPT 登录…", + "Could not start ChatGPT sign-in on this machine.": "无法在本机启动 ChatGPT 登录。", + "Finish ChatGPT sign-in in the browser. Waiting for it to complete…": "请在浏览器中完成 ChatGPT 登录,正在等待其完成…", + "ChatGPT sign-in timed out. Click {button} to try again.": "ChatGPT 登录超时。点击“{button}”重试。", + "ChatGPT sign-in complete. OAuth will be used for the next cloud voice session.": "ChatGPT 登录完成。下次云语音会话将使用 OAuth。", + "Remove this key from your saved configuration?": "要从已保存的配置中移除此密钥吗?", + "Removed from {store}. Restarting — this page reloads itself.": "已从 {store} 中移除。正在重启——本页面将自动重新加载。", + "The photorealistic 3D planet, place search + Street View": "照片级逼真 3D 地球、地点搜索 + 街景", + "Places context + Street View fallback; optional separate key": "地点信息 + 街景备用;可选的独立密钥", + "Voice control — API key or ChatGPT OAuth": "语音控制——API 密钥或 ChatGPT OAuth", + "Live ships, worldwide": "全球实时船舶", + "Live active-fire detections": "实时活跃火点探测", + "Real live traffic (keyless runs a simulation)": "真实实时路况(无密钥时为模拟)", + "Bing imagery map stacks + world terrain": "Bing 影像地图组合 + 全球地形", + "More flight-polling credits (anonymous works without)": "更多航班轮询额度(无密钥也可匿名使用)", + "Street-level imagery and coverage in the Street Level layer. Free: register an app in the Mapillary developer dashboard and paste its Client Token": "街景影像与街景图层中的覆盖范围。免费:在 Mapillary 开发者控制台注册一个应用,并粘贴其 Client Token", + "Public space-missions access needs no key. Leave blank for standard use; paid tokens only raise request limits.": "公开的太空任务数据无需密钥。常规使用请留空;付费令牌只会提高请求上限。", + "Air quality request failed": "空气质量请求失败", + "No readings available": "暂无读数", + "No current readings": "暂无最新读数", + "{count} stations reporting": { + "other": "{count} 个站点上报数据" + }, + "Highest air-quality readings in view": "视野内空气质量读数最高的站点", + "Low": "低", + "Low health risk": "低健康风险", + "Moderate": "中等", + "Moderate health risk": "中等健康风险", + "High": "高", + "High health risk": "高健康风险", + "Very High": "极高", + "Very High health risk": "极高健康风险", + "Air quality": "空气质量", + "Canada only · ECCC AQHI network": "仅限加拿大 · ECCC AQHI 站网", + "ECCC request timed out": "ECCC 请求超时", + "Malformed AQHI station response": "AQHI 站点响应格式错误", + "No AQHI stations available": "没有可用的 AQHI 站点", + "Malformed AQHI observation response": "AQHI 观测响应格式错误", + "Camera source temporarily unavailable": "摄像头数据源暂时不可用", + "Mapped camera locations": "地图标注的摄像头位置", + "SHOW NEAREST": "显示最近", + "Stop following the current object before navigating to a camera": "请先停止跟踪当前目标,再前往摄像头", + "Move to the nearest loaded camera and show its details": "前往最近已加载的摄像头并显示详情", + "Camera badges": "摄像头标记", + "Cyan cameras turn coral when selected. Wedges illustrate mapped direction, not measured coverage. Nearby cameras may be outside the screen.": "选中后青色摄像头变为珊瑚色。扇形仅示意已标绘方向,并非实测覆盖范围。附近的摄像头可能不在屏幕内。", + "{count} nearby · {onScreen} on screen": { + "other": "附近 {count} 个 · 屏幕上 {onScreen} 个" + }, + "{count} nearby": { + "other": "附近 {count} 个" + }, + "retrying mapped ALPR cameras": "正在重试加载地图标注的 ALPR 摄像头", + "loading mapped ALPR cameras": "正在加载地图标注的 ALPR 摄像头", + "No ALPR data for this area — US and Canada only": "此区域没有 ALPR 数据——仅覆盖美国和加拿大", + "Zoom in to load mapped cameras": "放大以加载地图标注的摄像头", + "Showing cached locations": "当前显示的是缓存位置", + "Coverage limited — zoom in": "覆盖受限——请放大", + "No ALPR data for this area": "此区域没有 ALPR 数据", + "None on screen — nearby cameras are outside the view": "屏幕上没有摄像头——附近的摄像头在视野之外", + "ALPR camera": "ALPR 摄像头", + "Camera coverage unavailable": "摄像头覆盖范围不可用", + "OpenStreetMap · community mapped": "OpenStreetMap · 社区标注", + "Overpass returned an incomplete camera response": "Overpass 返回的摄像头数据不完整", + "At least {count}": "至少 {count}", + "CONTEXT READY": "态势就绪", + "GLOBAL CONTEXT OFF": "全局态势已关闭", + "SELECT A FLIGHT, VESSEL, OR MAPPED INSTALLATION": "选择航班、船舶或地图标注设施", + "ENABLE TO LOAD OBSERVED / MAPPED PROXIMITY": "启用以加载观测 / 地图标注的邻近信息", + "Unavailable": "不可用", + "Focus {label}": "聚焦 {label}", + "Global Context navigation": "全局态势导航", + "Previous — prior visited contact in the 250 km window": "上一个:250 km 范围内先前访问过的目标", + "PREVIOUS": "上一个", + "FOCUS": "聚焦", + "Next — nearest unvisited contact in the 250 km window": "下一个:250 km 范围内最近的未访问目标", + "NEXT": "下一个", + "Named areas ({n})": "已命名区域({n})", + "{distance} FLIGHT / VESSEL WINDOW": "{distance} 航班 / 船舶监视窗口", + "Open-source mapped/observed context. Missing broadcasts, unloaded map areas, or unmapped sites are not evidence of absence.": "开源的地图标注/观测态势信息。缺失的广播、未加载的地图区域或未标注的设施不能作为不存在的证据。", + "mapped matches within {km} km of the subject": "所选对象 {km} km 内的地图标注匹配", + "none mapped within {km} km; not a complete 250 km survey": "{km} km 内无地图标注;并非完整的 250 km 普查", + "mapped matches from the loaded viewport": "来自已加载视口的地图标注匹配", + "viewport feed is not a complete 250 km survey": "视口数据源并非完整的 250 km 普查", + "HDG {value}°": "HDG {value}°", + "BRG {value}°": "BRG {value}°", + "BRG —": "BRG —", + "CRS {value}°": "CRS {value}°", + "CURRENT VIEWPORT ONLY": "仅限当前视口", + "WITHIN {km} KM": "{km} km 以内", + "Flights": "航班", + "Military flights": "军用航班", + "AIS vessels": "AIS 船舶", + "Mapped installations": "地图标注设施", + "syncing {count} city feeds...": "正在同步 {count} 个城市数据源…", + "scanning nearby systems...": "正在扫描附近系统…", + "GBFS fetch error": "GBFS 获取错误", + "Station {id}": "站点 {id}", + "Station": "站点", + "🚲 {bikes} avail · {docks} docks · {capacity} cap": "🚲 可用 {bikes} · 车桩 {docks} · 容量 {capacity}", + "⚠️ Not installed": "⚠️ 未安装", + "⚠️ Not renting": "⚠️ 暂停租车", + "⚠️ Not returning": "⚠️ 暂停还车", + "HDG {value}° (ESTIMATED)": "HDG {value}°(估算)", + "{count} CAMERAS STANDING BY · NO CAMERA SELECTED · CLICK A CAMERA TO ACTIVATE": "{count} 个摄像头待命 · 未选择摄像头 · 点击摄像头以激活", + "No cameras available in catalog.": "目录中没有可用摄像头。", + "{city} CCTV": "{city} CCTV", + "FOV {value}° (DATASHEET)": "FOV {value}°(规格书)", + "FOV {value}°": "FOV {value}°", + "SPEC FOV {span}° (CAPABILITY)": "规格 FOV {span}°(可调范围)", + "COVERAGE {value}km²": "覆盖 {value}km²", + "OVERLAP {count} cams": "重叠 {count} 个摄像头", + "ISOLATED VIEW": "独立视角", + "PROJ MONITOR": "投影:监视器", + "PROJ OFF": "投影:关", + "VIEWSHED": "可视域", + "CAL {value}": "CAL {value}", + "SRC {value}": "SRC {value}", + "{value} CONTEXT": "{value} 态势", + "{spec} · SPECS: CCTV-DATABASE.COM": "{spec} · 规格:CCTV-DATABASE.COM", + "Atlantic and eastern/central North Pacific; not worldwide cyclone coverage.": "仅覆盖大西洋及北太平洋东部和中部,并非全球气旋覆盖。", + "Cyclone advisories unavailable": "气旋公告不可用", + "Cyclone data unavailable": "气旋数据不可用", + "Potential tropical cyclone": "潜在热带气旋", + "Hurricane": "飓风", + "Tropical storm": "热带风暴", + "Tropical depression": "热带低压", + "Subtropical storm": "亚热带风暴", + "Subtropical depression": "亚热带低压", + "Track and cone match this advisory": "路径和预报不确定范围与本期公告一致", + "Track/cone awaiting advisory {number}": "路径/预报不确定范围等待第 {number} 号公告", + "Track/cone unavailable": "路径/预报不确定范围不可用", + "Cached advisory · stale source": "缓存公告 · 数据源已过期", + "Loading advisories…": "正在加载公告…", + "{count} active storms": { + "other": "{count} 个活跃气旋" + }, + "Position as of {time}": "位置(截至 {time})", + "Maximum sustained wind: {wind} · Pressure: {pressure}": "最大持续风速:{wind} · 气压:{pressure}", + "{name} · {classification} · Advisory {number} · issued {time}": "{name} · {classification} · 第 {number} 号公告 · 发布于 {time}", + "No active NHC/CPHC systems": "当前没有活跃的 NHC/CPHC 气旋", + "Advisories unavailable": "公告不可用", + "Cyclones · NHC / CPHC": "气旋 · NHC / CPHC", + "Atlantic · E/C Pacific": "大西洋 · 东/中太平洋", + "{storms} · {name} selected": "{storms} · 已选择 {name}", + "Official advisory ↗": "官方公告 ↗", + "Active NHC and CPHC cyclone advisories": "活跃的 NHC 与 CPHC 气旋公告", + "Wind unavailable": "风场数据不可用", + "Advisory center / forecast track": "公告中的气旋中心 / 预报路径", + "Center-track uncertainty cone": "中心路径预报不确定范围", + "Select a storm on the map, or choose a storm in the list to select it and move the camera. Click empty map space to clear the selection. NOAA NHC/CPHC advisory context. The cone describes forecast center-track uncertainty, not storm size or the full hazard area. Forecast point labels are source lead hours, not times computed from advisory issuance. Geometry follows the surface; height is not weather altitude. Consult the official advisory.": "在地图上点选气旋,或在列表中点选气旋,即可选中并移动相机。点击空白地图区域可清除选择。内容来自 NOAA NHC/CPHC 公告。该区域表示气旋中心路径预报的不确定范围,不代表气旋大小或全部灾害影响范围。预报点标注的是数据源的预报时效(小时),不是按公告发布时刻推算的时间。几何图形贴地显示;显示高度并非气象高度。请以官方公告为准。", + "DRIVE": "驾车", + "Drive": "驾车", + "WALK": "步行", + "Walk": "步行", + "BIKE": "骑行", + "Bike": "骑行", + "rate limited": "已被限流", + "routing service is rate limited": "路线服务已被限流", + "route leg too long": "路段过长", + "route too long": "路线过长", + "{error} — try again in a moment": "{error}——请稍后重试", + "Routing is rate limited — try again in a moment": "路线请求过于频繁——请稍后重试", + "Routing unavailable (HTTP {status})": "路线规划不可用(HTTP {status})", + "Routing unavailable": "路线规划不可用", + "direction|left": "向左", + "direction|right": "向右", + "direction|slightly left": "向左前方", + "direction|slightly right": "向右前方", + "direction|sharply left": "向左后方", + "direction|sharply right": "向右后方", + "direction|straight": "向前", + "direction|around": "向后", + "fork direction|left": "靠左行驶", + "fork direction|right": "靠右行驶", + "fork direction|slightly left": "稍靠左行驶", + "fork direction|slightly right": "稍靠右行驶", + "fork direction|sharply left": "向左后方行驶", + "fork direction|sharply right": "向右后方行驶", + "fork direction|straight": "直行", + "lane direction|left": "左转", + "lane direction|right": "右转", + "lane direction|slightly left": "稍左转", + "lane direction|slightly right": "稍右转", + "lane direction|sharply left": "急左转", + "lane direction|sharply right": "急右转", + "lane direction|straight": "直行", + "ordinal|{number}st": "第 {number} 个", + "ordinal|{number}nd": "第 {number} 个", + "ordinal|{number}rd": "第 {number} 个", + "ordinal|{number}th": "第 {number} 个", + "Head out on {road}": "沿 {road} 出发", + "Head out": "出发", + "Arrive at your destination, on the left": "到达目的地,位于左侧", + "Arrive at your destination, on the right": "到达目的地,位于右侧", + "Arrive at your destination": "到达目的地", + "Make a U-turn onto {road}": "掉头进入 {road}", + "Make a U-turn": "掉头", + "Continue straight onto {road}": "直行进入 {road}", + "Continue straight": "继续直行", + "Turn {direction} onto {road}": "{direction}转,进入 {road}", + "Turn {direction}": "{direction}转", + "Turn onto {road}": "转弯进入 {road}", + "maneuver|Turn": "转弯", + "Continue {direction} onto {road}": "继续{direction}行驶,进入 {road}", + "Continue {direction}": "继续{direction}行驶", + "At the end of the road, turn {direction} onto {road}": "在道路尽头{direction}转,进入 {road}", + "At the end of the road, turn {direction}": "在道路尽头{direction}转", + "At the end of the road, continue onto {road}": "在道路尽头继续行驶,进入 {road}", + "At the end of the road, continue": "在道路尽头继续行驶", + "Keep {direction} at the fork onto {road}": "在岔路口{direction},进入 {road}", + "Keep {direction} at the fork": "在岔路口{direction}", + "Continue at the fork onto {road}": "在岔路口继续行驶,进入 {road}", + "Continue at the fork": "在岔路口继续行驶", + "Merge {direction} onto {road}": "{direction}并道,进入 {road}", + "Merge {direction}": "{direction}并道", + "Merge onto {road}": "并入 {road}", + "maneuver|Merge": "并道", + "Take the ramp on the {direction} onto {road}": "{direction}驶入匝道,进入 {road}", + "Take the ramp on the {direction}": "{direction}驶入匝道", + "Take the ramp onto {road}": "驶入匝道,进入 {road}", + "Take the ramp": "驶入匝道", + "Take the exit on the {direction} onto {road}": "从出口{direction}驶出,进入 {road}", + "Take the exit on the {direction}": "从出口{direction}驶出", + "Take the exit onto {road}": "从出口驶出,进入 {road}", + "Take the exit": "从出口驶出", + "At the roundabout, take the {exit} exit onto {road}": "在环岛从{exit}出口驶出,进入 {road}", + "At the roundabout, take the {exit} exit": "在环岛从{exit}出口驶出", + "Enter the roundabout onto {road}": "驶入环岛,进入 {road}", + "Enter the roundabout": "驶入环岛", + "At the roundabout, turn {direction} onto {road}": "在环岛{direction}转,进入 {road}", + "At the roundabout, turn {direction}": "在环岛{direction}转", + "At the roundabout, continue onto {road}": "在环岛继续行驶,进入 {road}", + "At the roundabout, continue": "在环岛继续行驶", + "Exit the roundabout onto {road}": "驶出环岛,进入 {road}", + "Exit the roundabout": "驶出环岛", + "Use the {direction} lane onto {road}": "使用{direction}车道,进入 {road}", + "Use the {direction} lane": "使用{direction}车道", + "Continue onto {road}": "继续行驶,进入 {road}", + "maneuver|Continue": "继续行驶", + "{mode} — reroute for {modeLower}": "{mode}——按{modeLower}重新规划路线", + "CLICK MAP": "点击地图", + "SET A": "设置 A", + "Click a spot on the globe to place A (click again to cancel)": "点击地球上的位置放置 A(再次点击取消)", + "Then click the globe to place the start": "然后点击地球放置起点", + "SET B": "设置 B", + "Click a spot on the globe to place B (click again to cancel)": "点击地球上的位置放置 B(再次点击取消)", + "Then click the globe to place the destination": "然后点击地球放置终点", + "Swap A and B": "交换 A 和 B", + "FLY ···": "飞行 ···", + "FLYING": "飞行中", + "FLY": "飞行", + "Fly the camera along the route": "让相机沿路线飞行", + "Place A and B first": "请先放置 A 和 B", + "action|CLEAR": "清除", + "Remove the route and both markers": "移除路线和两个标注点", + "press Escape twice to leave Draw": "按两次 Escape 退出“绘制”", + "{name} is active — turn {name} off, then set {endpoint}": "{name} 正在使用——请先关闭 {name},再设置 {endpoint}", + "{name} is active — {leave}, then set {endpoint}": "{name} 正在使用——请先{leave},再设置 {endpoint}", + "Only the first {count} turns of this route are shown": "此路线仅显示前 {count} 个转向", + "Turn-by-turn directions": "分步导航", + "Another map tool is using clicks — close it, then SET A again": "另一个地图工具正在拦截点击——请先关闭它,再重新设置 A", + "Routing…": "正在规划路线…", + "No route found": "未找到路线", + "{distance} · {duration} · {mode} · first {count} turns": "{distance} · {duration} · {mode} · 前 {count} 个转向", + "{distance} · {duration} · {mode}": "{distance} · {duration} · {mode}", + "Click the globe to place {endpoint}": "点击地球放置 {endpoint}", + "SET B, then click the globe": "设置 B,然后点击地球", + "SET A, then click the globe": "设置 A,然后点击地球", + "Step {step} of {total} · then {leg}": "第 {step}/{total} 步 · 随后 {leg}", + "Step {step} of {total}": "第 {step} 步,共 {total} 步", + "Then: {instruction}": "接下来:{instruction}", + "No route found between A and B": "在 A 和 B 之间未找到路线", + "Routing failed: {error}": "路线规划失败:{error}", + "Routing timed out": "路线规划超时", + "Routing unavailable — no response from the routing service": "路线规划不可用——路线服务无响应", + "Earthquake source unavailable": "地震数据源不可用", + "Focus fire detection {title}, {details}": "聚焦火点探测 {title},{details}", + "live feed unavailable": "实时数据源不可用", + "{value} conf": "置信度 {value}", + "{value} ago": "{value}前", + "sensor n/a": "传感器 n/a", + "FIRE · {frp} MW": "火点 · {frp} MW", + "NIGHT": "夜间", + "▲ {frp} MW": "▲ {frp} MW", + "FIRE": "火点", + "FIRES": "火点", + "max {value} MW": "最大 {value} MW", + "new {value}": "最新({value}前)", + "{value}°N": "{value}°N", + "{value}°S": "{value}°S", + "{value}°E": "{value}°E", + "{value}°W": "{value}°W", + "just now": "刚刚", + "<1m ago": "不到 1 分钟前", + "{count}m ago": "{count} 分钟前", + "{count}h ago": "{count} 小时前", + "fire confidence|high": "高", + "fire confidence|nominal": "中", + "fire confidence|low": "低", + "age|<1h": "不到 1 小时", + "age|{count}h": "{count} 小时", + "age|{count}d": "{count} 天", + "Fire · FRP {frp} MW": "火点 · FRP {frp} MW", + "STALE · cached {age}": "过期 · {age}前缓存", + "refreshing...": "刷新中…", + "loading...": "加载中…", + "KEY REQUIRED": "需要密钥", + "LIVE · updated {age}": "实时 · 更新于 {age}", + "Source snapshot time unavailable": "来源快照时间不可用", + "Live data unavailable": "实时数据不可用", + "Low-accuracy score < 2%": "低精度评分 < 2%", + "Low-accuracy score 2–10%": "低精度评分 2–10%", + "Low-accuracy score > 10%": "低精度评分 > 10%", + "GNSS integrity source unavailable": "GNSS 完好性数据源不可用", + "Regional sample · 250 nm radius · cells from visited views over 30 min": "区域采样 · 半径 250 海里 · 30 分钟内已访问视图中的网格", + "Retrying automatically at {time} UTC": "将于 {time} UTC 自动重试", + "Retrying automatically after provider cooldown": "服务商冷却结束后自动重试", + "Updates automatically after pan or zoom and once a minute. The public feed covers a 250-nautical-mile circle around the view centre; zooming out does not make it a worldwide snapshot. Blank cells are outside sampled coverage or have insufficient observations.": "平移或缩放后以及每分钟自动更新。公共数据源覆盖以视野中心为圆心、半径 250 海里的范围;缩小地图并不会使其成为全球快照。空白网格位于采样覆盖范围之外,或观测数据不足。", + "Low-accuracy score = 100 × max(0, bad − 1) / total: one aircraft is discounted, so this is not the raw share. Cells need {aircraft} aircraft over {minutes} minutes. Navigation-integrity anomalies are not a detection of jamming or spoofing.": "低精度评分 = 100 × max(0, bad − 1) / total:扣除一架飞机,因此并非原始占比。每个网格需在 {minutes} 分钟内有 {aircraft} 架飞机。导航完好性异常不等于检测到干扰或欺骗。", + "GNSS snapshot needs a view anchor": "GNSS 快照需要视图锚点", + "GNSS feed rate limited (HTTP 429)": "GNSS 数据源已被限流(HTTP 429)", + "Malformed GNSS integrity snapshot": "GNSS 完好性快照格式错误", + "GNSS integrity snapshot has no observation time": "GNSS 完好性快照缺少观测时间", + "installation class|AIRFIELD": "机场", + "installation class|NAVAL BASE": "海军基地", + "installation class|RANGE": "靶场", + "installation class|MILITARY LAND": "军事用地", + "installation class|BASE": "基地", + "installation class|BARRACKS": "营房", + "installation class|TRAINING AREA": "训练区", + "installation class|PLACES CANDIDATE": "Places 候选", + "installation class|INSTALLATION": "设施", + "No mapped sites within {km} km of the contact": "目标 {km} km 内无地图标注设施", + "{count} mapped sites within {km} km of the contact": { + "other": "目标 {km} km 内有 {count} 个地图标注设施" + }, + "No mapped sites in view": "视野内无地图标注设施", + "{count} mapped sites in view": { + "other": "视野内有 {count} 个地图标注设施" + }, + "loading mapped installation context": "正在加载地图标注设施信息", + "Too many mapped sites in view to list them all": "视野内地图标注设施过多,无法全部列出", + "Installation context unavailable": "设施信息不可用", + "MAPPED INSTALLATION": "地图标注设施", + "Unknown": "未知", + "LAUNCH SITE · {site}": "发射场 · {site}", + "LAUNCH SITE": "发射场", + "T−{time} · {mission}": "T−{time} · {mission}", + "LAUNCH STANDBY": "发射待命", + "LIFTOFF · {mission}": "点火升空 · {mission}", + "ASCENT REPLAY · {mission}": "上升回放 · {mission}", + "ASCENT ESTIMATE · {mission}": "上升估算 · {mission}", + "STAGE RE-ENTRY / RECOVERY · {mission}": "子级再入 / 回收 · {mission}", + "ORBIT REPLAY · {mission}": "轨道回放 · {mission}", + "PAUSED · {title}": "已暂停 · {title}", + "UNSPECIFIED": "未指明", + "+{count} additional payload records": "另有 {count} 条有效载荷记录", + "PAYLOAD DATA UNAVAILABLE": "有效载荷数据不可用", + "{value} KM DOWNRANGE": "落点距离 {value} km", + "POSITION UNAVAILABLE": "位置不可用", + "FLIGHT {count}": "第 {count} 次飞行", + "REUSED": "复用", + "NO STAGE RE-ENTRY / RECOVERY DATA": "无子级再入 / 回收数据", + "{count} / 30D": "{count} / 30 天", + "NO MISSIONS AVAILABLE IN THE CURRENT 30-DAY WINDOW": "当前 30 天窗口内没有可用任务", + "DATE UNAVAILABLE": "日期不可用", + "UNSPECIFIED OPERATOR": "运营方未指明", + "Select {mission}": "选择 {mission}", + "{value} KM": "{value} km", + "{value} KM/S": "{value} km/s", + "Selected Space Mission": "已选太空任务", + "SELECTED SPACE MISSION": "已选太空任务", + "Show all missions": "显示所有任务", + "Deselect mission": "取消选择任务", + "MISSION": "任务", + "STATUS · ": "状态 · ", + "LAUNCH SITE · ": "发射场 · ", + "LAUNCH TIME · ": "发射时间 · ", + "ORBIT · ": "轨道 · ", + "ASCENT PATH · ": "上升轨迹 · ", + "CURRENT DISTANCE FROM EARTH · ": "当前与地球的距离 · ", + "SATELLITE SPEED · ": "卫星速度 · ", + "PAYLOAD": "有效载荷", + "NAME": "名称", + "TYPE": "类型", + "DESTINATION": "目的地", + "STAGE / RE-ENTRY / RECOVERY": "子级 / 再入 / 回收", + "STAGE": "子级", + "STATUS": "状态", + "FINAL POSITION": "最终位置", + "REPLAY SPEED": "回放速度", + "Replay speed multiplier": "回放速度倍率", + "REPLAY ASCENT": "回放上升过程", + "Previous mission": "上一个任务", + "PREV": "上一个", + "Next mission": "下一个任务", + "SHOW ALL / DESELECT": "显示全部 / 取消选择", + "Pause replay": "暂停回放", + "Cancel replay": "取消回放", + "EST. DOWNRANGE": "预估落点距离", + "PLANNED · ": "计划 · ", + "SUPPLIED TRAJECTORY POINTS": "提供的轨迹点", + "RECONSTRUCTED ESTIMATE": "重建估算", + "Replay the estimated ascent with a following camera": "以跟随相机回放估算的上升过程", + "Resume replay": "继续回放", + "T minus {seconds}": "T−{seconds}", + "Preparing launch site": "正在准备发射场", + "Liftoff": "点火升空", + "Ascent replay": "上升回放", + "Orbit replay": "轨道回放", + ", paused": ",已暂停", + "Malformed launch snapshot": "发射快照格式错误", + "Heard by your receiver": "由你的接收机接收", + "1090 MHz": "1090 MHz", + "978 MHz UAT": "978 MHz UAT", + "browser SDR": "浏览器 SDR", + "decoder feed": "解码器数据源", + "Your RTL-SDR receiver and decoder feed": "你的 RTL-SDR 接收机和解码器数据源", + "Your decoder feed": "你的解码器数据源", + "Your RTL-SDR receiver": "你的 RTL-SDR 接收机", + "NO CALLSIGN": "无呼号", + "ALT {altitude} FT · GS {speed} KT · TRK {track}": "高度 {altitude} ft · 地速 {speed} kn · 航迹角 {track}", + "V/S {rate}": "升降率 {rate}", + "V/S {rate} FPM": "升降率 {rate} ft/min", + "POSITION {age}": "位置 {age}前", + "POSITION {seconds} S AGO": "位置 {seconds} 秒前", + "{count} MSGS": { + "other": "{count} 条消息" + }, + "Decoder feeds": "解码器数据源", + "WebUSB + decoder feeds": "WebUSB + 解码器数据源", + "opening receiver": "正在打开接收机", + "checking decoder feeds": "正在检查解码器数据源", + "WebUSB needs desktop Chrome or Edge": "WebUSB 需要桌面版 Chrome 或 Edge", + "connect a receiver in Radio": "在电台面板连接接收机", + "receiver is in FM mode": "接收机处于 FM 模式", + "listening": "监听中", + "USB error": "USB 错误", + "Alt unknown": "高度未知", + "Type unknown": "类型未知", + "Reg unknown": "注册号未知", + "Operator unknown": "运营方未知", + "${amount}K": "{amount}K 美元", + "${amount}M": "{amount}M 美元", + "${amount}B": "{amount}B 美元", + "fire cause|Human": "人为", + "fire cause|Natural": "自然", + "fire cause|Undetermined": "待定", + "fire cause|Unknown": "未知", + "fire behavior|Minimal": "微弱", + "fire behavior|Moderate": "中等", + "fire behavior|Active": "活跃", + "fire behavior|Extreme": "极端", + "Type {level} Incident": "{level} 类事件", + "age|{count}m": "{count} 分钟", + "{acres} ac": { + "other": "{acres} 英亩" + }, + "{percent}% contained": "已控制 {percent}%", + "containment unknown": "控制比例未知", + "{cause} cause": "起火原因:{cause}", + "{personnel} personnel": { + "other": "{personnel} 名人员" + }, + "{county} County": "{county} 县", + "{cost} to date": "迄今扑救费用 {cost}", + "discovered {age} ago": "发现于 {age}前", + "updated {age} ago": "更新于 {age}前", + "part of {complex}": "属于 {complex} 复合火场", + "InciWeb ↗ · click card to open": "InciWeb ↗ · 点击卡片打开", + "FIRE · {name}": "火场 · {name}", + "Unnamed incident": "未命名火情", + "Open {title} on InciWeb": "在 InciWeb 上打开 {title}", + "Perimeter source unavailable": "周界数据源不可用", + "Not contained or unknown": "未控制或未知", + "Under 50% contained": "控制低于 50%", + "50–99% contained": "控制 50–99%", + "Fully contained": "已全部控制", + "Colour shows reported containment. Perimeters are simplified to about 100 m.": "颜色表示报告的控制比例。周界简化至约 100 m。", + "Malformed perimeter snapshot": "周界快照格式错误", + "radio tag|All": "全部", + "News": "新闻", + "Talk": "脱口秀", + "Weather / Emergency": "天气 / 应急", + "Public Safety": "公共安全", + "Aviation / Marine": "航空 / 海事", + "Traffic / Transit": "交通 / 公共交通", + "Music": "音乐", + "Other": "其他", + "Directory refresh degraded; showing the previous station catalog.": "目录刷新降级;正在显示上一次的电台目录。", + "Radio directory coverage is degraded.": "电台目录覆盖已降级。", + "Directory refresh failed; showing the previous station catalog.": "目录刷新失败;正在显示上一次的电台目录。", + "Radio directory is temporarily unavailable.": "电台目录暂时不可用。", + "radio|NEWS": "新闻", + "radio|TALK": "脱口秀", + "radio|WEATHER": "天气", + "radio|SAFETY": "安全", + "radio|AIR / SEA": "航空 / 海事", + "radio|TRANSIT": "公共交通", + "radio|MUSIC": "音乐", + "radio|OTHER": "其他", + "Broadcaster stream is unavailable or blocked by the browser.": "广播流不可用,或被浏览器阻止。", + "Playback requires a direct click or tap.": "需直接点击或轻触才能播放。", + "Broadcaster stream could not be started.": "无法启动广播流。", + "Alternative": "另类", + "Ambient": "氛围", + "Blues": "蓝调", + "Classical": "古典", + "Country": "乡村", + "Dance": "舞曲", + "Electronic": "电子", + "Folk": "民谣", + "Funk": "放克", + "Hip-Hop": "嘻哈", + "House": "House", + "Indie": "独立音乐", + "Jazz": "爵士", + "Latin": "拉丁", + "Metal": "金属乐", + "Oldies": "怀旧金曲", + "Pop": "流行", + "Punk": "朋克", + "R&B": "R&B", + "Reggae": "雷鬼", + "Rock": "摇滚", + "Soul": "灵魂乐", + "Techno": "Techno", + "Trance": "Trance", + "World": "世界音乐", + "overview": "概览", + "cloud unknown": "云量未知", + "{percent}% scene cloud": "整景云量 {percent}%", + "{min}–{max}% scene cloud": "整景云量 {min}–{max}%", + "Comparison in use by another scene": "对比功能正被另一个场景占用", + "Esri map unavailable · no swipe": "Esri 地图不可用 · 无法滑动对比", + "Swipe needs a globe map": "滑动对比需要地球地图", + "Imagery stays on Esri · CLEAR to use Google 3D": "影像保留在 Esri · 清除后改用 Google 3D", + "Imagery catalog unavailable": "影像目录不可用", + "Catalog truncated · a newer clear day may exist": "目录已截断 · 可能还有更近的少云日期", + "{label} catalog unavailable · {detail}": "{label} 目录不可用 · {detail}", + "Daily overview for today may still be empty until the pass": "卫星过境前,今天的每日概览可能仍为空", + "Box and images cleared": "框选及影像已清除", + "MORE DETAIL · 30 m": "更多细节 · 30 m", + "Sentinel-2 and Landsat via HLS, 30 m, every 2–6 days": "Sentinel-2 与 Landsat,经 HLS,30 m,每 2–6 天", + "DAILY OVERVIEW · 250 m": "每日概览 · 250 m", + "VIIRS daily mosaic, 250 m, follows broad changes; little detail under 25 km": "VIIRS 每日镶嵌图,250 m,反映大范围变化;25 km 以下几乎没有细节", + "Sentinel-2": "Sentinel-2", + "Sentinel-2 via HLS": "Sentinel-2(经 HLS)", + "Landsat 8/9": "Landsat 8/9", + "Landsat 8/9 via HLS": "Landsat 8/9(经 HLS)", + "Daily overview": "每日概览", + "Box edges must be finite": "框选边界必须是有限值", + "Longitudes must be within ±180°": "经度必须在 ±180° 以内", + "Select one side of the dateline": "请只选择日界线的一侧", + "Box must enclose an area": "框选必须包含一定面积", + "Add the full Starlink broadband shell (thousands of extra points)": "添加完整的 Starlink 宽带星座(数千个额外点位)", + "Loading the Starlink shell…": "正在加载 Starlink 星座…", + "Starlink {detail} — click to retry": "Starlink {detail}——点击重试", + "load failed": "加载失败", + "Showing the full Starlink shell — click for the core catalog only": "正在显示完整的 Starlink 星座——点击仅显示核心目录", + "DENSE ···": "密集 ···", + "DENSE ✕": "密集 ✕", + "DENSE": "密集", + "CelesTrak unreachable": "CelesTrak 无法连接", + "DOCKED · {name} · +{extra}": "对接 · {name} · +{extra}", + "DOCKED · {name}": "对接 · {name}", + "{name} needs the Google 3D map: choose it under MAP SOURCE": "{name} 需要 Google 3D 地图:请在地图源(MAP SOURCE)中选择", + "zoom in to a street to open {name}": "放大到街道以打开 {name}", + "KEY REJECTED": "密钥被拒绝", + "loading coverage...": "正在加载覆盖范围…", + "No street-level imagery within {radius} m of that point": "该点 {radius} m 范围内没有街景影像", + "No street-level imagery within {radius} m of the view centre": "视野中心 {radius} m 范围内没有街景影像", + "Coverage tile failed": "覆盖瓦片加载失败", + "The photo could not be loaded": "照片无法加载", + "{name} photo. ← and → step along the sequence. On a 360° photo, drag or use Shift+← →, ↑ ↓ to look around, and + − to zoom": "{name} 照片。← 和 → 沿序列切换。在 360° 照片上,拖动或使用 Shift+← →、↑ ↓ 环视,+ − 缩放", + "{name} street-level photo": "{name} 街景照片", + "Previous photo in this sequence (←)": "此序列中的上一张照片(←)", + "Next photo in this sequence (→)": "此序列中的下一张照片(→)", + "Aim the middle of the view at a street to look for imagery there": "将视野中心对准街道以查找该处影像", + "GOOGLE_MAPS_API_KEY is not set": "未设置 GOOGLE_MAPS_API_KEY", + "The Google Maps JavaScript API could not load": "Google Maps JavaScript API 无法加载", + "Google Street View": "Google 街景", + "GOOGLE STREET VIEW": "Google 街景", + "Google refused GOOGLE_MAPS_API_KEY for Street View — check that the Maps JavaScript API is enabled for it and this site is an allowed referrer": "Google 拒绝了用于街景的 GOOGLE_MAPS_API_KEY——请确认已为该密钥启用 Maps JavaScript API,且本站点在允许的引荐来源(referrer)中", + "Street View did not answer — check the network, and that the Maps JavaScript API is enabled for GOOGLE_MAPS_API_KEY": "街景无响应——请检查网络,并确认 GOOGLE_MAPS_API_KEY 已启用 Maps JavaScript API", + "click a street to open Street View": "点击街道以打开街景", + "Street View has no imagery for this place": "街景没有此地的影像", + "KartaView has no position for this photo": "KartaView 没有此照片的位置", + "The KartaView photo could not be loaded": "KartaView 照片无法加载", + "zoom in to see KartaView coverage": "放大以查看 KartaView 覆盖范围", + "no KartaView photos in view": "视野内没有 KartaView 照片", + "KartaView is rate-limiting requests — they resume on their own": "KartaView 已被限流——稍后会自动恢复", + "KartaView’s photos here are archived and cannot be shown": "KartaView 在此处的照片已归档,无法显示", + "KartaView did not filter this area in time — move the map to ask again": "KartaView 未能及时筛选此区域——移动地图以重新请求", + "None of KartaView’s photos here pass the imagery filter": "此处没有 KartaView 照片通过影像筛选", + "KartaView did not answer in time — try again": "KartaView 未及时响应——请重试", + "KartaView could not be reached — check the network": "无法连接 KartaView——请检查网络", + "KartaView sent an answer that could not be read": "KartaView 返回的响应无法读取", + "KartaView is rate-limiting requests": "KartaView 已被限流", + "KartaView sent too much data": "KartaView 返回的数据过多", + "KartaView is rate-limiting requests — try again in a moment": "KartaView 已被限流——请稍后重试", + "KartaView has no such photo": "KartaView 没有这张照片", + "Mapillary rejected MAPILLARY_CLIENT_TOKEN — replace it in Provider Settings": "Mapillary 拒绝了 MAPILLARY_CLIENT_TOKEN——请在服务商设置中更换", + "Coverage requests are being rate-limited — they resume on their own": "覆盖范围请求已被限流——稍后会自动恢复", + "The Panoramax photo could not be loaded": "Panoramax 照片无法加载", + "Panoramax is busy — coverage resumes on its own": "Panoramax 繁忙——覆盖范围稍后会自动恢复", + "no Panoramax photos in view": "视野内没有 Panoramax 照片", + "no Panoramax photos here match the filters": "此处没有符合筛选条件的 Panoramax 照片", + "Panoramax sent a map tile that could not be read": "Panoramax 返回的地图瓦片无法读取", + "This Panoramax photo is no longer available": "这张 Panoramax 照片已不可用", + "zoom in to see Panoramax coverage": "放大以查看 Panoramax 覆盖范围", + "Panoramax did not answer in time — try again": "Panoramax 未及时响应——请重试", + "Panoramax could not be reached — check the connection": "无法连接 Panoramax——请检查网络连接", + "Panoramax sent too much data": "Panoramax 返回的数据过多", + "Panoramax is busy": "Panoramax 繁忙", + "Panoramax sent an unreadable answer": "Panoramax 返回的响应无法读取", + "No street-level imagery providers configured": "未配置街景影像服务商", + "(by upload date)": "(按上传日期)", + "Open the Street Level panel to view imagery": "打开街景面板以查看影像", + "Image could not be opened": "图像无法打开", + "TeleGeography load failed": "TeleGeography 加载失败", + "UNAVAILABLE · {source} · Roads unavailable": "不可用 · {source} · 道路不可用", + "Roads: {source}": "道路:{source}", + "TomTom roads unavailable while the traffic service is unreachable": "交通服务无法连接期间,TomTom 道路不可用", + "Hybrid unavailable while the traffic service is unreachable": "交通服务无法连接期间,混合模式不可用", + "TomTom roads need a TomTom key": "TomTom 道路需要 TomTom 密钥", + "Hybrid needs a TomTom key": "混合模式需要 TomTom 密钥", + "Local surface still loading": "本地路面仍在加载", + "Partial coverage": "部分覆盖", + "Detailed roads unavailable": "详细道路不可用", + "Reduced detail coverage": "详细覆盖范围受限", + "TomTom roads only, each with its live flow": "仅 TomTom 道路,各自带实时路况", + "TomTom roads only, each with its live flow (needs a TomTom key)": "仅 TomTom 道路,各自带实时路况(需要 TomTom 密钥)", + "OpenStreetMap roads, with TomTom flow matched when available": "OpenStreetMap 道路,可用时匹配 TomTom 路况", + "TomTom roads first; OpenStreetMap fills the rest, simulated": "优先使用 TomTom 道路及实时路况;缺失路段由 OpenStreetMap 补充,并模拟其路况", + "TomTom roads first; OpenStreetMap fills the rest, simulated (needs a TomTom key)": "优先使用 TomTom 道路及实时路况;缺失路段由 OpenStreetMap 补充,并模拟其路况(需要 TomTom 密钥)", + "Hybrid": "混合", + "TomTom key unavailable": "TomTom 密钥不可用", + "TomTom daily budget reached": "已达 TomTom 每日配额", + "TomTom upstream unreachable": "TomTom 上游服务无法连接", + "TomTom flow unavailable": "TomTom 路况不可用", + "SIMULATED — {reason}": "模拟——{reason}", + "Syncing flow · Roads: {source}": "正在同步路况 · 道路:{source}", + "LIVE · Roads: {source} · Roads without flow hidden": "实时 · 道路:{source} · 已隐藏无路况道路", + "LIVE · Roads: {source} · No flow roads in view": "实时 · 道路:{source} · 视野内没有带路况数据的道路", + "LIVE · Roads: {source} · Flow {percent}%": "实时 · 道路:{source} · 路况 {percent}%", + "SIMULATED · Roads: {source} · Flow {percent}%": "模拟 · 道路:{source} · 路况 {percent}%", + "Syncing flow · Roads: OpenStreetMap · Flow: TomTom · Unmatched: simulated": "正在同步路况 · 道路:OpenStreetMap · 路况:TomTom · 未匹配:模拟", + "LIVE · Roads: OpenStreetMap · Flow: TomTom · {percent}% cov · Unmatched: simulated": "实时 · 道路:OpenStreetMap · 路况:TomTom · 覆盖 {percent}% · 未匹配:模拟", + "LIVE · Roads: OpenStreetMap · Flow: TomTom · {percent}% cov": "实时 · 道路:OpenStreetMap · 路况:TomTom · 覆盖 {percent}%", + "SIMULATED · Roads: OpenStreetMap · Flow: TomTom (no matches)": "模拟 · 道路:OpenStreetMap · 路况:TomTom(无匹配)", + "SIMULATED — traffic service unreachable": "模拟——交通服务无法连接", + "SIMULATED — add TomTom key for live": "模拟——添加 TomTom 密钥以获取实时数据", + "OpenFreeMap tiles rate-limited": "OpenFreeMap 瓦片已被限流", + "OpenFreeMap tiles timed out": "OpenFreeMap 瓦片超时", + "OpenFreeMap tiles unavailable": "OpenFreeMap 瓦片不可用", + "occupancy|empty": "空载", + "occupancy|many seats available": "空座较多", + "occupancy|few seats available": "空座较少", + "occupancy|standing room only": "仅剩站位", + "occupancy|crushed standing room only": "极度拥挤,仅剩站位", + "occupancy|full": "已满", + "occupancy|not accepting passengers": "不载客", + "occupancy|not boardable": "无法上车", + "Route {route}": "线路 {route}", + "Vehicle {vehicle}": "车辆 {vehicle}", + "vehicle motion|Stopped": "已停车", + "No recent fix": "无近期定位", + "Waiting for update": "等待更新", + "Moving": "行驶中", + "reported {speed} km/h": "上报 {speed} km/h", + "reported hdg {heading}°": "上报航向 {heading}°", + "Last report: stopped at stop {stop}": "最近上报:停靠站点 {stop}", + "Last report: arriving at stop {stop}": "最近上报:即将到达站点 {stop}", + "Last report: next stop {stop}": "最近上报:下一站 {stop}", + "Shown at street level · depth not in the feed": "按地面高度显示 · 数据源未提供深度", + "Reported {seconds} s ago": "{seconds} 秒前上报", + "Reported {minutes} min ago": "{minutes} 分钟前上报", + "shown {seconds} s behind": "显示滞后 {seconds} 秒", + "{reported} (feed time)": "{reported}(数据源时间)", + "Available trail: {minutes} min (limited)": "可用轨迹:{minutes} 分钟(受限)", + "Available trail: {minutes} min": "可用轨迹:{minutes} 分钟", + "Bus": "公交车", + "Tram": "有轨电车", + "Subway": "地铁", + "Train": "火车", + "Ferry": "渡轮", + "Transit vehicle": "公共交通车辆", + "AIS: {name}": "AIS: {name}", + "{type} SPD: {speed} HDG: {heading}": "{type} SPD: {speed} HDG: {heading}", + "VESSEL": "船舶", + "{speed}KT": "{speed} kn", + "{heading}DEG": "{heading}°", + "POS: LIVE": "POS:实时", + "POS: {time}Z": "POS:{time}Z", + "AIS live load failed": "AIS 实时数据加载失败", + "awaiting first AIS position…": "等待首个 AIS 位置…", + "awaiting usable AIS positions…": "等待可用的 AIS 位置…", + "awaiting first AIS message…": "等待首条 AIS 消息…", + "AISSTREAM_API_KEY not set": "未设置 AISSTREAM_API_KEY", + "live feed unsupported": "不支持实时数据源", + "connecting to feed…": "正在连接数据源…", + "feed disconnected": "数据源已断开", + "feed down": "数据源中断", + "feed idle": "数据源空闲", + "API key rejected — check AISSTREAM_API_KEY": "API 密钥被拒绝——请检查 AISSTREAM_API_KEY", + "feed silent {seconds}s — no AIS data": "数据源已静默 {seconds} 秒——无 AIS 数据", + "feed silent — no AIS data": "数据源已静默——无 AIS 数据", + "feed down — retrying slowly (attempt {attempt})": "数据源中断——正在低频重试(第 {attempt} 次)", + "reconnecting to feed… (attempt {attempt})": "正在重新连接数据源…(第 {attempt} 次)", + "feed down — retrying slowly": "数据源中断——正在低频重试", + "reconnecting to feed…": "正在重新连接数据源…", + "Focus vessel {name}, MMSI {mmsi}": "聚焦船舶 {name},MMSI {mmsi}", + "Hidden by this map source · choose a globe map": "已被当前地图源隐藏 · 请选择地球地图", + "Weather image HTTP {status}": "天气图像 HTTP {status}", + "Weather HTTP {status}": "天气 HTTP {status}", + "Frame unavailable; previous observation retained": "帧不可用;保留上一帧观测", + "Weather imagery unavailable": "天气影像不可用", + "Weather source unavailable; previous observation retained": "天气数据源不可用;保留上一帧观测", + "Weather unavailable": "天气数据不可用", + "No frame within {gap} min of {time}": "{time} 前后 {gap} 分钟内没有帧", + "No frame within {gap} h of {time}": "{time} 前后 {gap} 小时内没有帧", + "{hours}h {minutes}m ago": "{hours} 小时 {minutes} 分钟前", + "{minutes}m ago": "{minutes} 分钟前", + "{state} · {time} · {lag}": "{state} · {time} · {lag}", + "Observed": "观测", + "History": "历史", + "Observation unavailable": "观测不可用", + "Waiting for observation": "等待观测数据", + "{detail} · synced": "{detail} · 已同步", + "{detail} · nearest": "{detail} · 最接近", + "RADAR REFLECTIVITY · dBZ": "雷达反射率 · dBZ", + "LIGHTNING DENSITY · 15 min accumulation": "闪电密度 · 15 分钟累计", + "GLOBAL INFRARED · hourly": "全球红外 · 每小时", + "GOES INFRARED · ~5 min": "GOES 红外 · 约 5 分钟", + "{state}: {time}": "{state}:{time}", + "Latest observation": "最新观测", + "frame {index}/{total}": "第 {index}/{total} 帧", + "loading": "加载中", + "Observation: unavailable": "观测:不可用", + "Contiguous US · gaps ≠ no rain": "美国本土 · 数据缺失 ≠ 无降雨", + "Americas + Pacific · not individual strikes": "美洲与太平洋 · 非单次闪击", + "Color: strikes/km²/min ×10³": "颜色:闪击次数/km²/分钟 ×10³", + "60°S–60°N · typically 2–3 h delayed": "60°S–60°N · 通常延迟 2–3 小时", + "North America · infrared imagery": "北美洲 · 红外影像", + "Reduced motion · history playback unavailable": "减弱动效 · 历史回放不可用", + "Reduced motion · manual history available": "减弱动效 · 可手动浏览历史", + "Rain radar · US": "降雨雷达 · 美国", + "Lightning density · 15 min": "闪电密度 · 15 分钟", + "Satellite clouds": "卫星云图", + "Americas + Pacific": "美洲与太平洋", + "Global · 60°S–60°N": "全球 · 60°S–60°N", + "North America": "北美洲", + "Source observations delayed": "数据源观测延迟", + "Stale source": "数据源已过期", + "Loading next frame…": "正在加载下一帧…", + "Map center outside coverage": "地图中心在覆盖范围之外", + "strikes/km²/min ×10³": "闪击次数/km²/分钟 ×10³", + "N. America": "北美", + "Global": "全球", + "Hourly global mosaic; usually 2–3 hours delayed": "每小时全球镶嵌图;通常延迟 2–3 小时", + "GOES regional clouds; approximately 5-minute updates": "GOES 区域云图;约 5 分钟更新", + "Clouds only": "仅云区", + "Apply the full cloud brightness key; 100% filter strength": "应用完整的云层亮度映射;滤镜强度 100%", + "Full": "完整", + "Show the complete infrared image; 0% filter strength": "显示完整红外图像;滤镜强度 0%", + "Soft": "柔和", + "Vivid": "鲜明", + "Image opacity; does not alter the observed values": "图像不透明度;不改变观测值", + "View US radar": "查看美国雷达", + "View Americas & Pacific": "查看美洲与太平洋", + "View coverage": "查看覆盖范围", + "{value} strikes/km²/min ×10³ (15-minute density)": "{value} 次闪击/km²/分钟 ×10³(15 分钟密度)", + "{value} dBZ radar reflectivity": "{value} dBZ 雷达反射率", + "STALE · cached source metadata": "已过期 · 数据源元数据来自缓存", + "Map center is outside source coverage": "地图中心在数据源覆盖范围之外", + "NOAA/NWS 15-minute lightning density derived from Vaisala NLDN/GLD360. Coverage 110°E across the Pacific/Americas to 0°, 25°S–80°N. Not a live strike count, global coverage or a safety warning.": "NOAA/NWS 15 分钟闪电密度,来源为 Vaisala NLDN/GLD360。覆盖自 110°E 经太平洋/美洲至 0°,25°S–80°N。不是实时闪击计数、不是全球覆盖,也不构成安全警示。", + "NOAA MRMS radar echoes indicate precipitation patterns, not rain rate, a storm warning or a future forecast. Native source approximately 1 km; display is limited to level 6. Frames use exact advertised observation times.": "NOAA MRMS 雷达回波反映降水形态,不代表降雨强度、风暴警报或未来预报。原始数据分辨率约 1 km;显示仅限缩放级别 6。各帧使用数据源公布的精确观测时间。", + "GOES-19/18 longwave infrared Band 14 regional; NESDIS global longwave mosaic. Clouds only dims everything but bright, cold cloud tops; a brightness filter, not a cloud mask. Coverage and freshness differ by region.": "GOES-19/18 第 14 波段长波红外区域图;NESDIS 全球长波镶嵌图。“仅云区”会调暗除明亮冷云顶之外的一切;这是亮度滤镜,不是云掩膜。覆盖范围与新鲜度因区域而异。", + "REGION": "区域", + "IMAGE": "图像", + "FILTER": "滤镜", + "Cloud filter strength": "云层滤镜强度", + "OPACITY": "不透明度", + "Infrared decode failed": "红外图像解码失败", + "Weather prefetch unavailable": "天气预取不可用", + "Weather tiles unavailable · previous frame retained": "天气瓦片不可用 · 保留上一帧", + "Some weather tiles unavailable": "部分天气瓦片不可用", + "Malformed weather manifest": "天气清单格式错误", + "Weather request timed out": "天气请求超时", + "Temperature field unavailable": "温度场不可用", + "Mean sea level pressure field unavailable": "平均海平面气压场不可用", + "Wind upstream unavailable": "风场上游数据源不可用", + "{source} HTTP {status}": "{source} HTTP {status}", + "Wind": "风", + "Wind source unavailable": "风场数据源不可用", + "Forecast · does not follow history": "预报 · 不随观测历史变化", + "Wind motion": "风场运动", + "Wind speed": "风速", + "Sea-level pressure": "海平面气压", + "Air temperature · 2 m": "气温 · 2 m", + "Global · 1° grid": "全球 · 1° 网格", + "{model} forecast · {time}": "{model} 预报 · {time}", + "Loading forecast": "正在加载预报", + "Preparing flow": "正在准备流场", + "Selected field unavailable": "所选气象场不可用", + "Cached forecast · stale": "缓存预报 · 已过期", + "ECMWF IFS surface forecast": "ECMWF IFS 地面预报", + "NOAA GFS surface forecast": "NOAA GFS 地面预报", + "Reduced motion": "减弱动效", + "Resume": "继续", + "Pause": "暂停", + "Pause visual flow; forecast time does not advance with animation": "暂停流动动画;预报时次不会随动画推进", + "None": "无", + "Speed": "风速", + "Pressure": "气压", + "Temperature": "气温", + "Wind trails with no field shading": "仅显示风场流线,不叠加气象场填色", + "How hard the surface wind is blowing": "地面风有多大", + "Sea-level air pressure: broad highs and lows": "海平面气压:大范围的高压与低压", + "Air temperature two meters above the surface": "地面以上两米的气温", + "Wind speed units": "风速单位", + "Read wind at map center": "读取地图中心风况", + "Read the forecast at the center of the map without changing selection": "读取地图中心的预报值,不改变当前选择", + "{model} forecast · {field} ({unit})": "{model} 预报 · {field}({unit})", + "Valid: {time}": "有效时间:{time}", + "preparing": "准备中", + "Issued: {time}": "发布时间:{time}", + "Selected field unavailable · wind remains visible": "所选气象场不可用 · 风场仍可见", + "{error} · wind remains visible": "{error} · 风场仍可见", + "Surface wind at 10 m. Approximately 1° global grid. Curves follow the 10 m wind field, lifted 12 km for visibility; display height is not weather altitude. View lighting is for readability. Animation shows flow through one fixed forecast; it does not advance time. Color fields drape the globe basemap or the active photorealistic 3D Tiles.": "10 m 高度的地面风。约 1° 全球网格。流线沿 10 m 风场绘制,为便于观看抬升至 12 km;显示高度并非气象高度。视图光照仅为提高可读性。动画展示的是单一固定预报内的流场,不推进时间。气象场填色叠加于地球底图或当前的照片级逼真 3D Tiles。", + "MODEL": "模式", + "FIELD": "要素", + "UNITS": "单位", + "MOTION": "运动", + "Forecast": "预报", + "NNE": "北东北", + "NE": "东北", + "ENE": "东东北", + "ESE": "东东南", + "SE": "东南", + "SSE": "南东南", + "SSW": "南西南", + "SW": "西南", + "WSW": "西西南", + "WNW": "西西北", + "NW": "西北", + "NNW": "北西北", + "No surface reading": "暂无地表读数", + "Aim the map center at Earth": "请将地图中心对准地球", + "A location reading needs a loaded forecast and the Earth at the center of the view.": "位置读数需要已加载的预报,且地图中心对准地球。", + "Interpolated model forecast on an approximately 1° grid. Broad weather patterns, not a street-level measurement.": "按约 1° 网格插值的模式预报。反映大范围天气形势,不是街道级实测。", + " · calm": " · 静风", + " from {direction}": ",{direction}风", + "WIND AT {coordinates}": "{coordinates} 的风", + "{model} · valid {time}": "{model} · 有效时间 {time}", + "Wind speed field unavailable": "风速场不可用", + "Globe imagery unavailable": "地球影像不可用", + "Globe field image unavailable": "地球气象场图像不可用", + "Unknown wind model": "未知风场模式", + "Unknown weather overlay": "未知天气叠加层", + "Wind request timed out": "风场请求超时", + "Malformed wind manifest": "风场清单格式错误", + "Malformed wind grid": "风场网格格式错误", + "Error: {message}": "错误:{message}", + "Needs {envVars} — add it in Provider Settings": "需要 {envVars},请在服务商设置中添加", + "{label} is unavailable": "{label} 不可用", + "This map stack": "此地图组合", + "{label} unavailable: {hint}": "{label} 不可用:{hint}", + "Google 3D tiles unavailable — check the key's API restrictions, quota, or network": "Google 3D 瓦片不可用——请检查密钥的 API 限制、配额或网络", + "Needs {envVars} — add it in Provider Settings — or a Cesium ion token for the ion-hosted route": "需要 {envVars}(请在服务商设置中添加),或使用 Cesium ion 令牌走 ion 托管通道", + "Exit cockpit to fly to a camera": "请先退出座舱,再飞往摄像头", + "Exit cockpit to fly to a location": "请先退出座舱,再飞往该地点", + "Exit cockpit to fly to a photo": "请先退出座舱,再飞往照片", + "Exit cockpit to fly to a shared view": "请先退出座舱,再飞往分享的视图", + "Exit cockpit to fly to a target": "请先退出座舱,再飞往目标", + "Exit cockpit to fly to a vessel": "请先退出座舱,再飞往船舶", + "Exit cockpit to fly to a fire": "请先退出座舱,再飞往火点", + "Exit cockpit to fly to a route": "请先退出座舱,再飞往路线", + "Exit cockpit to fly to a file": "请先退出座舱,再飞往文件位置", + "Exit cockpit to fly to a scene": "请先退出座舱,再飞往场景", + "Exit cockpit to view weather coverage": "请先退出座舱,再查看天气覆盖范围", + "Exit cockpit to fly to a cyclone": "请先退出座舱,再飞往气旋", + "Exit cockpit to fly to an aircraft": "请先退出座舱,再飞往飞机", + "Exit cockpit to fly to a satellite": "请先退出座舱,再飞往卫星", + "Exit cockpit to frame contacts": "请先退出座舱,再将目标纳入视野", + "Visible map targets": "可见的地图目标", + "Focusing {label}": "正在聚焦 {label}", + "Source": "来源", + "Camera ownership changed": "相机控制权已变更", + "Saved project could not be read; storage preserved. Import a valid file to resume saving.": "已保存的项目无法读取;存储已保留。请导入有效文件以恢复保存。", + "Ready": "就绪", + "Scene layer turned off": "场景图层已关闭", + "Stopped": "已停止", + "Scene not saved — existing saved project could not be read. Export edits or import a valid file.": "场景未保存——已保存的项目无法读取。请先导出修改或导入有效文件。", + "Scene not saved — {reason}": "场景未保存——{reason}", + "Scene not saved — browser storage unavailable": "场景未保存——浏览器存储不可用", + "Loaded: {scene} / {shot}": "已加载:{scene} / {shot}", + "Cannot update {scene}: shot inventory changed": "无法更新 {scene}:镜头清单已变化", + "Cannot update {scene}: evidence beats changed": "无法更新 {scene}:证据节点已变化", + "Cannot update {scene}: shot bindings are incomplete": "无法更新 {scene}:镜头绑定不完整", + "Updated {count} shots: {scene}": { + "other": "已更新 {count} 个镜头:{scene}" + }, + "Appended {count} shots: {scene}": { + "other": "已追加 {count} 个镜头:{scene}" + }, + "Cannot capture shot: camera not ready": "无法保存镜头:相机未就绪", + "Captured: {scene} / {shot}": "已保存:{scene} / {shot}", + "Select a shot first": "请先选择一个镜头", + "Updated: {scene} / {shot}": "已更新:{scene} / {shot}", + "Could not leave scene: {scene}": "无法离开场景:{scene}", + "Seeked: {scene} / {shot}": "已定位:{scene} / {shot}", + "Seeking scene clock": "正在定位场景时钟", + "Camera unavailable — exit cockpit first": "相机不可用——请先退出座舱", + "No shots to run": "没有可运行的镜头", + "Scene transition limit reached — load a shot to reset": "已达到场景切换上限——请加载一个镜头以重置", + "Export failed: {error}": "导出失败:{error}", + "Project exported": "项目已导出", + "Importing project": "正在导入项目", + "Imported {file}": "已导入 {file}", + "Import failed: {error}": "导入失败:{error}", + "Import failed (could not read JSON file)": "导入失败(无法读取 JSON 文件)", + "Layers refused: {layers}": "被拒绝的图层:{layers}", + "Could not exit {mode} — scene layers may be refused": "无法退出 {mode}——场景图层可能被拒绝", + "Camera move interrupted": "相机移动被中断", + "Scene media timed out": "场景媒体播放超时", + "Action complete": "动作已完成", + "Action unavailable or cancelled": "动作不可用或已取消", + "Scene actions": "场景动作", + "Select a feature or use Tab and Enter to choose an action.": "选择一个要素,或使用 Tab 和 Enter 选择动作。", + "Selected feature: {feature}. Choose an action.": "已选要素:{feature}。请选择动作。", + "Running {index}/{total}: {scene} / {shot}": "正在运行 {index}/{total}:{scene} / {shot}", + "Scene run complete": "场景运行完成", + "Bhote Koshi Evidence Sequence": "Bhote Koshi 证据序列", + "Nepal Flood Incident": "尼泊尔洪水事件", + "Global Flights Radar": "全球航班雷达", + "Orbital Watch": "轨道监视", + "Thermal Threat Board": "热源威胁看板", + "City Overload": "城市过载", + "Omniscience Pullback": "全知拉远", + "Global Incident Context": "全球事件背景", + "Nepal-Focused Globe Rotation": "聚焦尼泊尔的地球旋转", + "Bhote Koshi Regional Approach": "Bhote Koshi 区域推近", + "Bhote Koshi Nearby Cities": "Bhote Koshi 周边城市", + "Bhote Koshi Incident Corridor": "Bhote Koshi 事件走廊", + "Bhote Koshi Flood Path": "Bhote Koshi 洪水路径", + "Bhote Koshi Corridor Overview": "Bhote Koshi 走廊概览", + "Bhote Koshi Upper Valley": "Bhote Koshi 上游河谷", + "Upper Valley Collapse": "上游河谷崩塌", + "Debris-Dammed Lake": "碎屑堰塞湖", + "Nepal-China Border Gate": "中尼边境口岸", + "Timure Evidence Cluster": "Timure 证据群", + "Syabru Besi Passage": "Syabru Besi 通道", + "Bidur / Trishuli Consequence": "Bidur / Trishuli 后果", + "Final view": "最终视图", + "Scene {n}": "场景 {n}", + "Shot {n}": "镜头 {n}", + "the title of scene {scene} is empty or longer than {max} characters": "场景 {scene} 的标题为空或超过 {max} 个字符", + "the title of shot {shot} in scene {scene} is empty or longer than {max} characters": "场景 {scene} 中镜头 {shot} 的标题为空或超过 {max} 个字符", + "the project is larger than 5 MiB": "项目大于 5 MiB", + "the project has too many shots": "项目中的镜头过多", + "the scene data failed validation": "场景数据未通过校验", + "Could not complete this action. Check the file, references and selected assets.": "无法完成此操作。请检查文件、引用和所选资源。", + "{count} scenes": { + "other": "{count} 个场景" + }, + "{count} shots": { + "other": "{count} 个镜头" + }, + "{count} data packs": { + "other": "{count} 个数据包" + }, + "{scene} / {pack}: {status}. File: {path}. {attribution} · {license}": "{scene} / {pack}:{status}。文件:{path}。{attribution} · {license}", + "Unavailable layers: {layers}": "不可用图层:{layers}", + "Uses registered content or linked media. Those external files are not included in a scene bundle. Their original notices still apply.": "使用了已注册内容或链接媒体。这些外部文件不包含在场景资源包中。其原始声明仍然适用。", + "{bytes} bundled bytes verified. Files stay in memory for this session. Reimport the bundle after reloading the app.": "已校验打包内容 {bytes} 字节。文件仅保留在本次会话的内存中。重新加载应用后请再次导入资源包。", + "Review scene import": "场景导入预览", + "Nothing is loaded or changed until you apply this file.": "确认导入此文件前,不会加载或更改任何内容。", + "Ready to import": "可以导入", + "Apply replaces the current project. Export your current project first if you want to keep both.": "应用后将替换当前项目。如需同时保留两者,请先导出当前项目。", + "Apply import": "确认导入", + "Import was not applied. The current project may have changed.": "未执行导入。当前项目可能已发生变化。", + "Select a scene and shot first": "请先选择场景和镜头", + "Edit scene details": "编辑场景详情", + "Camera positions use degrees and meters above the ellipsoid. Drafts are validated before they replace your saved scene.": "相机经纬度以度为单位,高度以米为单位(相对椭球面)。草稿通过校验后才会替换已保存的场景。", + "Anchors and data packs": "锚点与数据包", + "Shot camera, timing, packs and actions": "镜头相机、时序、数据包与动作", + "New anchor ID": "新锚点 ID", + "Capture camera as anchor": "将当前相机位置设为锚点", + "Anchor added to draft": "锚点已加入草稿", + "Set move start to current camera": "将运镜起点设为当前相机位置", + "Move added to draft; destination remains the shot camera": "运镜已加入草稿;终点仍为该镜头的相机位置", + "Use ordinary flight": "使用普通飞行", + "Invalid shot JSON": "镜头 JSON 无效", + "Apply details": "应用详情", + "Select a scene first": "请先选择一个场景", + "Share selected scene": "分享所选场景", + "Scene JSON preserves authored settings and attribution. It does not include asset files. Bundles include only pack files you choose here or previously imported bundle files.": "场景 JSON 保留创作设置与署名信息。它不包含资源文件。资源包仅包含你在此选择的包文件或先前导入的资源包文件。", + "Download scene JSON": "下载场景 JSON", + "Choose data-pack files": "选择数据包文件", + "Or choose a data-pack folder": "或选择数据包文件夹", + "Download asset bundle": "下载资源包", + "Asset bundle downloaded": "资源包已下载", + "No RTL-SDR device selected": "未选择 RTL-SDR 设备", + "WebUSB requires desktop Chrome or Edge on localhost/HTTPS": "WebUSB 需要在 localhost/HTTPS 上使用桌面版 Chrome 或 Edge", + "RTL-SDR access failed; close other SDR apps and check USB permissions": "RTL-SDR 访问失败;请关闭其他 SDR 应用并检查 USB 权限", + "RTL-SDR connection failed": "RTL-SDR 连接失败", + "Receiving 1090 MHz ADS-B": "正在接收 1090 MHz ADS-B", + "Receiving broadcast FM": "正在接收广播 FM", + "Connect an RTL-SDR to begin.": "连接 RTL-SDR 即可开始。", + "WebUSB is unavailable in this browser": "此浏览器不支持 WebUSB", + "USB work abandoned after a stalled transfer": "USB 传输停滞,已放弃操作", + "SDR decoder worker failed": "SDR 解码器工作线程失败", + "SDR decoder did not start; receiver stopped": "SDR 解码器未能启动;接收机已停止", + "SDR decoder stopped responding; receiver stopped": "SDR 解码器停止响应;接收机已停止", + "SDR decoder fell behind; receiver stopped": "SDR 解码器处理滞后;接收机已停止", + "WebUSB requires desktop Chrome or Edge": "WebUSB 需要桌面版 Chrome 或 Edge", + "Waiting for an RTL-SDR device…": "正在等待 RTL-SDR 设备…", + "Browser audio is suspended; press Connect again to resume it": "浏览器音频已挂起;再次按“连接”以恢复", + "RTL-SDR sample stream stopped": "RTL-SDR 采样流已停止", + "Switching to {mode}…": "正在切换到 {mode}…", + "Browser audio is suspended; select FM again to resume it": "浏览器音频已挂起;再次选择 FM 以恢复", + "RTL-SDR gain change failed": "RTL-SDR 增益调整失败", + "Tuned to {mhz} MHz FM": "已调谐到 FM {mhz} MHz", + "Seeking up…": "正在向上搜台…", + "Seeking down…": "正在向下搜台…", + "Seek stopped": "搜台已停止", + "Scanning {mhz} MHz…": "正在扫描 {mhz} MHz…", + "Found {mhz} MHz · {snr} dB": "找到 {mhz} MHz · {snr} dB", + "FM signal found at {mhz} MHz": "在 {mhz} MHz 找到 FM 信号", + "No FM signal found": "未找到 FM 信号", + "Browser location is unavailable": "浏览器位置不可用", + "Receiver location was not granted": "未授予接收机位置权限", + "RTL-SDR disconnected": "RTL-SDR 已断开", + "Upstream response too large": "上游响应过大", + "Vector tile request timed out": "矢量瓦片请求超时", + "Vector tiles unavailable": "矢量瓦片不可用", + "Vector tile metadata unavailable": "矢量瓦片元数据不可用", + "Zoom in for vector tile coverage": "放大以查看矢量瓦片覆盖", + "Saturation": "饱和度", + "Edge Thickness": "描边粗细", + "Contrast": "对比度", + "Grain": "颗粒", + "Vignette": "暗角", + "Pixelation": "像素化", + "Distortion": "畸变", + "Instability": "抖动", + "Density": "密度", + "Gain": "增益", + "Bloom": "泛光", + "Scanlines": "扫描线", + "Sensitivity": "灵敏度", + "WHOT/BHOT": "WHOT/BHOT", + "Ironbow": "Ironbow", + "unsaved CCTV calibration": "未保存的 CCTV 校准", + "the open cockpit": "当前打开的座舱", + "Switching language reloads the page. This will be lost: {items}. Switch anyway?": "切换语言会重新加载页面。以下内容将会丢失:{items}。仍要切换吗?", + "VISUAL PRESETS": "视觉预设", + "LOCATION": "位置", + "CONTACTS — nearest planes · vessels · sites": "目标:最近的飞机 · 船舶 · 设施", + "SPACE MISSIONS — launches & orbital assets": "太空任务:发射与在轨资产", + "LIVE SIGNALS": "实时信号", + "CCTV layer unavailable": "CCTV 图层不可用", + "Link copied!": "链接已复制!", + "Copy failed": "复制失败", + "Return map to straight-down view": "将地图恢复为垂直俯视", + "Tilt map to oblique view": "将地图倾斜至斜视角", + "Reset map to north up. Current heading {heading} degrees": "将地图重置为正北朝上。当前方位 {heading} 度", + "Tilted view": "倾斜视角", + "Straight-down view": "垂直俯视", + "North up": "正北朝上", + "CCTV calibration saved": "CCTV 校准已保存", + "HDG": "HDG", + "PITCH": "俯仰", + "FOV": "FOV", + "RANGE": "范围", + "HGT": "高度", + "ΔN": "ΔN", + "ΔE": "ΔE", + "{value}m": "{value} m", + "CCTV calibration reset": "CCTV 校准已重置", + "ADJUST ON": "调整开", + "ADJUST": "调整", + "SNAPSHOT": "快照", + "LIVE": "实时", + "UPSTREAM": "上游", + "SYNTHETIC": "合成", + "CONFIGURED": "已配置", + "SEED": "预置", + "UNKNOWN": "未知", + "ERROR": "错误", + "Live HLS connected": "实时 HLS 已连接", + "Live HLS unavailable": "实时 HLS 不可用", + "No stream URL configured": "未配置流 URL", + "Live stream connected": "实时流已连接", + "Snapshot feed connected": "快照数据源已连接", + "Media fetch failed": "媒体获取失败", + "Upstream snapshot active": "上游快照已启用", + "Fallback Street View frame": "备用街景画面", + "Upstream unavailable": "上游不可用", + "No source configured": "未配置来源", + "Configured source": "已配置来源", + "Configured Source": "已配置数据源", + "Synthetic fallback": "合成备用", + "Upstream HTTP {status}": "上游 HTTP {status}", + "Unexpected media type {type}": "意外的媒体类型 {type}", + "SOURCE · UNKNOWN": "来源 · 未知", + "FRAME · LOADING": "画面 · 加载中", + "FRAME · UNAVAILABLE": "画面 · 不可用", + "CALIBRATED": "已校准", + "CURATED": "精选", + "RAW PRIOR": "初始估计", + "CCTV ON": "CCTV 开", + "CCTV OFF": "CCTV 关", + "VIEWSHED ON": "可视域开", + "COVERAGE ON": "覆盖范围开", + "COVERAGE OFF": "覆盖范围关", + "AUTO HOP ON": "自动跳转开", + "AUTO HOP OFF": "自动跳转关", + "PROJECTION ON": "投影开", + "PROJECTION OFF": "投影关", + "CAL · EDITED (UNSAVED)": "CAL · 已编辑(未保存)", + "CAL · {badge}": "CAL · {badge}", + "MONITOR": "监视器", + "OFF": "关", + "{city} · HDG {heading} · FOV {fov} · RANGE {range}m · {projection}{calBadge} · {provider}{status}": "{city} · HDG {heading} · FOV {fov} · 范围 {range} m · {projection}{calBadge} · {provider}{status}", + "{count} cameras loaded · click a camera to activate": { + "other": "已加载 {count} 个摄像头 · 点击摄像头以激活" + }, + "{count} cameras loaded · enable CCTV to activate": { + "other": "已加载 {count} 个摄像头 · 启用 CCTV 以激活" + }, + "Enable CCTV to load camera intersections": "启用 CCTV 以加载路口摄像头", + "Enable CCTV to start camera-linked intelligence summaries.": "启用 CCTV 以开始生成与摄像头关联的情报摘要。", + "No summary available.": "暂无摘要。", + "loading frames": "正在加载画面", + "camera grid ready": "摄像头网格就绪", + "Clearing selected data layers": "正在清除已选数据图层", + "Clear selected data layers": "清除已选数据图层", + "CONDITIONS UNKNOWN": "天气状况未知", + "CLEAR": "晴", + "PARTLY CLOUDY": "晴间多云", + "OVERCAST": "阴", + "FOG": "雾", + "DRIZZLE": "毛毛雨", + "RAIN": "雨", + "RAIN SHOWERS": "阵雨", + "SNOW SHOWERS": "阵雪", + "THUNDERSTORM": "雷暴", + "MIXED CONDITIONS": "混合天气", + "CYCLE ON": "轮换开", + "CYCLE OFF": "轮换关", + "Stop automatic page cycling. Previous, Next, and the SIG/NEWS/LOCAL tabs stay available.": "停止自动翻页。上一页、下一页以及“信号”“新闻”“本地”标签页仍可使用。", + "Cycle briefing pages automatically every 9 seconds (Signals → News → Local). Pauses while you hover or focus the panel. Live signal data refreshes continuously either way.": "每 9 秒自动轮换简报页面(信号 → 新闻 → 本地)。悬停或聚焦面板时暂停。无论是否轮换,实时信号数据都会持续刷新。", + "ACQUIRING REGIONAL NEWS": "正在获取地区新闻", + "REGIONAL NEWS UNAVAILABLE": "地区新闻不可用", + "RESOLVING REGION": "正在解析区域", + "REGION UNAVAILABLE": "区域不可用", + "NO RECENT LOCATION MATCHES": "暂无近期位置匹配结果", + "{domain} · {age}": "{domain} · {age}", + "{value} KM/H": "{value} km/h", + "CLOUD {pct}%": "云量 {pct}%", + "CLOUD UNKNOWN": "云量未知", + "{source} · LOCATION QUERY": "{source} · 位置查询", + "REGIONAL NEWS": "地区新闻", + "{label} · {radius} AIR/SEA WINDOW": "{label} · {radius} 空/海监视窗口", + "{scope} · INSTALLATIONS {coverage}": "{scope} · 设施 {coverage}", + "CONTEXT STANDBY": "态势待机", + "ENABLE GLOBAL CONTEXT FOR PROXIMITY PINGS": "启用全局态势以接收邻近目标提醒", + "CONTACT LOST · LAST KNOWN READOUT · NOT AN ALL-CLEAR": "目标丢失 · 最后已知读数 · 不代表安全无虞", + "CONTACT LOST · {subject}": "目标丢失 · {subject}", + "SUBJECT LEFT ITS FEED · READOUT HOLDING LAST KNOWN": "目标已脱离其数据源 · 读数保持最后已知值", + "{cohort} · {contact}": "{cohort} · {contact}", + "NO AVAILABLE EXAMPLE": "无可用示例", + "{cohort}, Unavailable": "{cohort},不可用", + "AHEAD": "正前方", + "{side} {angle}": "{side} {angle}", + "L": "左", + "R": "右", + "{count} INPUTS UNKNOWN · NOT AN ALL-CLEAR": { + "other": "{count} 项输入未知 · 不代表安全无虞" + }, + "AVAILABLE INPUTS CURRENT · NOT AN ALL-CLEAR": "可用输入均为最新 · 不代表安全无虞", + "SQUAWK {code} · {status}": "应答码 {code} · {status}", + "ADS-B PRIORITY · {status}": "ADS-B 优先级 · {status}", + "ADS-B EMERGENCY · {status}": "ADS-B 紧急 · {status}", + "AIRCRAFT": "飞机", + "ACQUIRING SURFACE": "正在获取地面数据", + "SURFACE FALLBACK": "地面数据备用", + "STALE FEED": "数据源过期", + "LIVE TRACK": "实时航迹", + "COMMERCIAL": "商用", + "{aircraftClass} · {emergency} · {feedState}": "{aircraftClass} · {emergency} · {feedState}", + "{aircraftClass} · {feedState} · COURSE ALIGNED": "{aircraftClass} · {feedState} · 航向对准", + "ARROW · ESTIMATED DIRECTION": "箭头 · 预估方向", + "ROUTE DATA UNAVAILABLE": "航线数据不可用", + "DEST {bearing}": "目的地 {bearing}", + "Disable cockpit weather effects": "停用座舱天气效果", + "Enable cockpit weather effects": "启用座舱天气效果", + "ON": "开", + "Normal": "常规", + "Night vision": "夜视", + "Thermal": "热成像", + "Anime": "动漫", + "Noir": "黑白", + "Snow": "雪景", + "Current cockpit vision style: {style}. Activate for next style.": "当前座舱视觉样式:{style}。点击可切换到下一个样式。", + "Current style: {style} — click for next": "当前样式:{style}——点击切换到下一个", + "Collapse Contact panel": "收起目标面板", + "Expand Contact panel": "展开目标面板", + "Collapse contact panel": "收起目标面板", + "Expand contact panel": "展开目标面板", + "Collapse cockpit briefing panel": "收起座舱简报面板", + "Expand cockpit briefing panel": "展开座舱简报面板", + "Collapse briefing panel": "收起简报面板", + "Expand briefing panel": "展开简报面板", + "OBSERVED / MAPPED PINGS": "观测 / 地图标注目标", + "SOURCE-BACKED EVENTS · NO SYNTHETIC NEWS": "来源可查的事件 · 无合成新闻", + "LATEST LOCATION-MATCHED REPORTING": "当前地区最新报道", + "{source} · LOCATION QUERY · RECENT": "{source} · 位置查询 · 近期", + "LOCAL INFO": "本地信息", + "PLACE / CONDITIONS / POSITION": "地点 / 状况 / 位置", + "TIME UNKNOWN": "时间未知", + "{count}M AGO": "{count} 分钟前", + "{count}H AGO": "{count} 小时前", + "{count}D AGO": "{count} 天前", + "DIR UNKNOWN": "风向未知", + "Select flight {title}": "选择航班 {title}", + "{aircraftClass} · CURRENT": "{aircraftClass} · 当前", + "MILITARY FLIGHT": "军用航班", + "COMMERCIAL FLIGHT": "商用航班", + "{aircraftClass} · {distance}": "{aircraftClass} · {distance}", + "DISTANCE UNKNOWN": "距离未知", + "{count} INPUTS UNKNOWN": { + "other": "{count} 项输入未知" + }, + "SOURCE STATUS UNAVAILABLE": "数据源状态不可用", + "Restore real aircraft": "恢复真实飞机", + "Reclassify as TR-3B": "重新归类为 TR-3B", + "TRACK ACQUIRED": "已获取航迹", + "{label} · COURSE {heading}°": "{label} · 航向 {heading}°", + "Context could not restore every layer; try again": "态势未能恢复所有图层,请重试", + "No selected data layers": "没有已选数据图层", + "{count} data layers could not be cleared": { + "other": "{count} 个数据图层无法清除" + }, + "Cleared {count} data layers": { + "other": "已清除 {count} 个数据图层" + }, + "Selected data layers could not be cleared": "无法清除已选数据图层", + "Contacts could not complete the requested transition; try again": "目标未能完成所请求的切换,请重试", + "Space Missions could not complete the requested transition; try again": "太空任务未能完成所请求的切换,请重试", + "Nearby installations refreshed": "附近设施已刷新", + "Nearby installations could not be refreshed; try again": "无法刷新附近设施,请重试", + "Space Missions cancellation could not restore the previous layer state": "取消太空任务后无法恢复先前的图层状态", + "That layer is unavailable in the current Context mode": "该图层在当前态势模式下不可用", + "{layerId} could not {action} cleanly": "{layerId} 未能正常{action}", + "start": "启动", + "stop": "停止", + "{mode} could not start because another layer did not stop cleanly": "{mode} 无法启动,因为另一个图层未能正常停止", + "Space Missions": "太空任务", + "Context": "态势", + "Rendered globe frames per second · toggle with `": "每秒渲染的地球帧数 · 按 ` 键切换", + "imported map files": "已导入的地图文件", + "Ground heights could not be refreshed. Try the file again when the map has loaded.": "无法刷新地面高度。请在地图加载完成后重新尝试该文件。", + "{count} map objects": { + "other": "{count} 个地图对象" + }, + ", drawn with its own styles": ",使用其自带样式绘制", + "Hide {name}": "隐藏 {name}", + "Show {name}": "显示 {name}", + "Fly to {name}": "飞往 {name}", + "Remove {name}": "移除 {name}", + "At most {count} files at once — remove one first.": "一次最多 {count} 个文件——请先移除一个。", + "Reading {name}…": "正在读取 {name}…", + "That file could not be read.": "无法读取该文件。", + "Added {count} files.": { + "other": "已添加 {count} 个文件。" + }, + "{count} files could not be added ({name}: {error})": { + "other": "{count} 个文件无法添加({name}:{error})" + }, + "Some KML content was left out.": "部分 KML 内容未被导入。", + "Press on the ground, not the sky": "请按在地面,而不是天空", + "Drag to size the box": "拖动以调整框选大小", + "light {source} to use your {name} key": "开启 {source} 以使用你的 {name} 密钥", + "light {source}": "开启 {source}", + ", or ": ",或", + "{requirement}, or {offer}": "{requirement},或{offer}", + "Live Flights": "实时航班", + "Military Flights": "军用航班", + "Earthquakes (24h)": "地震(24 小时)", + "Satellites": "卫星", + "Space Missions (30d)": "太空任务(30 天)", + "Street Traffic": "街道交通", + "CCTV": "CCTV", + "Radio": "电台", + "Bikeshare": "共享单车", + "Live AIS Vessels": "实时 AIS 船舶", + "Mapped Installations": "地图标注设施", + "Global Context": "全局态势", + "Datacenters": "数据中心", + "Dams": "水坝", + "Submarine Cables": "海底电缆", + "FIRMS Active Fires": "FIRMS 活跃火点", + "Local ADS-B": "本地 ADS-B", + "Transit": "公共交通", + "Recent Imagery": "近期影像", + "Fire Perimeters": "火场周界", + "GNSS Integrity": "GNSS 完好性", + "Rain radar": "降雨雷达", + "Lightning density": "闪电密度", + "Cyclone advisories": "气旋预警", + "ALPR Cameras": "ALPR 摄像头", + "Live Vessels": "实时船舶", + "Bike Share": "共享单车", + "Cameras": "摄像头", + "Street Level": "街景", + "Mapped ALPR Cameras": "地图标注 ALPR 摄像头", + "Data Centers": "数据中心", + "Active Fires": "活跃火点", + "Military aircraft": "军用飞机", + "Vessels": "船舶", + "CCTV + Street View fallback": "CCTV + 街景备用", + "OpenStreetMap + optional Google Maps Places": "OpenStreetMap + 可选 Google Maps Places", + "GFS / ECMWF IFS · FORECAST": "GFS / ECMWF IFS · 预报", + "NOAA nowCOAST · OBSERVED": "NOAA nowCOAST · 观测", + "NASA FIRMS · LIVE": "NASA FIRMS · 实时", + "OSM routing": "OSM 路线规划", + "data source|Local": "本地", + "ENABLING": "启用中", + "DISABLING": "停用中", + "Movement": "动态目标", + "Infrastructure": "基础设施", + "Events": "事件", + "Weather": "天气", + "Utilities": "工具", + "Other layers": "其他图层", + "{state} · {source}": "{state} · {source}", + "UNCERTAIN · {source} · lifecycle state requires reconciliation": "不确定 · {source} · 生命周期状态需要核对", + "{state} · {source} · {detail} · retry {seconds}s": "{state} · {source} · {detail} · {seconds} 秒后重试", + "{state} · {source} · {detail}": "{state} · {source} · {detail}", + "never": "未更新", + "{source} · {loadingLabel}": "{source} · {loadingLabel}", + "{accepted} of {raw} records accepted": "已接受 {accepted} / {raw} 条记录", + "incomplete snapshot": "快照不完整", + "{state} · {source} · {detail} · {ago}": "{state} · {source} · {detail} · {ago}", + "{state} · {source} · {ago} · retrying in {seconds}s": "{state} · {source} · {ago} · {seconds} 秒后重试", + "{state} · {source} · {ago}": "{state} · {source} · {ago}", + "{source} · {ago}": "{source} · {ago}", + "UNCERTAIN": "不确定", + "status chip|UNAVAILABLE": "不可用", + "Data source unavailable": "数据源不可用", + "{name}: {state}": "{name}:{state}", + "{count}s ago": "{count} 秒前", + "Local FM stopped: internet radio started": "本地 FM 已停止:网络电台已开始播放", + "IDLE": "空闲", + "CONNECTING": "连接中", + "TUNING": "调谐中", + "STREAMING": "接收中", + "UNSUPPORTED": "不支持", + "idle": "空闲", + "running": "运行中", + "suspended": "已挂起", + "closed": "已关闭", + "feed status|live": "实时", + "feed status|stale": "过期", + "feed status|unreachable": "无法连接", + "feed status|invalid": "无效", + "waiting for IQ": "等待 IQ 数据", + "{count} messages": { + "other": "{count} 条消息" + }, + "DSP {count} blocks": { + "other": "DSP {count} 个数据块" + }, + "DSP waiting": "DSP 等待中", + "{level} dBFS audio": "音频 {level} dBFS", + "audio signal --": "音频信号 --", + "audio {state}": "音频 {state}", + "{count} HEARD": { + "other": "已接收 {count} 架飞机的信号" + }, + "DISCONNECT": "断开连接", + "CONNECT": "连接", + "LOCATING…": "定位中…", + "LOCATED": "已定位", + "LOCATE": "定位", + " · read while Local ADS-B is on": " · 仅在本地 ADS-B 开启时读取", + "Decoder feeds: {feeds}": "解码器数据源:{feeds}", + "Language: {language}": "语言:{language}", + "Change the interface language": "更改界面语言", + "Interface language": "界面语言", + "Search languages": "搜索语言", + "No matching languages": "没有匹配的语言", + "📍 Location: --": "📍 位置:--", + "Landmark: --": "地标:--", + "Searched location": "已搜索的位置", + "ORBIT": "环绕", + "Location not found": "未找到位置", + "Search failed": "搜索失败", + "Detailed outline unavailable": "无法获取详细轮廓", + "Fly to a POI first": "请先飞往一个 POI", + "Reset to full globe view": "重置为完整地球视图", + "Reset cockpit to full globe view": "将座舱重置为完整地球视图", + "Resetting to full globe view": "正在重置为完整地球视图", + "Resetting cockpit to full globe view": "正在将座舱重置为完整地球视图", + "MAP": "地图", + "panel": "面板", + "Expand {name}": "展开 {name}", + "Collapse {name}": "收起 {name}", + "Expand Radio": "展开电台", + "Collapse Radio": "收起电台", + "Expand Radio section": "展开电台区域", + "Collapse Radio section": "收起电台区域", + "Panel layout updated — positions reset to new defaults": "面板布局已更新——位置已重置为新的默认值", + "Austin": "奥斯汀", + "San Francisco": "旧金山", + "New York": "纽约", + "Tokyo": "东京", + "London": "伦敦", + "Paris": "巴黎", + "Dubai": "迪拜", + "Washington DC": "华盛顿特区", + "Tallinn": "塔林", + "Los Angeles": "洛杉矶", + "Collapse Cockpit display options": "收起座舱显示选项", + "Expand Cockpit display options": "展开座舱显示选项", + "Collapse Cockpit Radio controls": "收起座舱电台控件", + "Expand Cockpit Radio controls": "展开座舱电台控件", + "CH {index} / {total}": "频道 {index} / {total}", + "NO STATIONS": "无电台", + "NO STATION AVAILABLE": "无可用电台", + "{name}, station {index} of {total}": "{name},第 {index} 个电台,共 {total} 个", + "No station available": "没有可用电台", + "OFF AIR": "停播", + "STATION UNAVAILABLE": "电台不可用", + "Station unavailable after directory refresh": "目录刷新后该电台不可用", + "Radio could not {action} cleanly": "电台未能正常{action}", + "Go to expanded Radio section": "前往展开的电台区域", + "Expand Radio section in Context": "在态势面板中展开电台区域", + "Close compact Radio controls": "关闭精简电台控件", + "Open compact Radio controls": "打开精简电台控件", + "SYNC": "同步", + "RECONCILE": "核对", + "DISABLE": "停用", + "ENABLE": "启用", + "Reconcile Radio — lifecycle uncertain": "核对电台——生命周期状态不确定", + "Disable Radio": "停用电台", + "Enable Radio": "启用电台", + "DIRECTORY BAND": "目录频段", + "{category} BAND": "{category} 频段", + "NO STATION SELECTED": "未选择电台", + "Directory metadata only": "仅目录元数据", + "Loading station directory…": "正在加载电台目录…", + "Choose a globe marker or use next.": "请选择地球上的标注或使用“下一个”。", + "TAGS · {tags}": "标签 · {tags}", + "Play": "播放", + "PAUSE": "暂停", + "RESUME": "继续", + "PLAY": "播放", + "selected": "所选", + "nearest": "最近", + "{action} {target} radio station": "{action}{target}电台", + "RADIO STATE UNCERTAIN": "电台状态不确定", + "SYNCING DIRECTORY": "正在同步目录", + "RADIO READY": "电台就绪", + "SYNCING": "同步中", + "READY": "就绪", + "station": "电台", + " · stale/degraded directory": " · 目录过期/降级", + " · degraded directory": " · 目录降级", + " · stale directory": " · 目录过期", + " · outside current filter": " · 不在当前筛选范围内", + "Ready — playback starts only from your action": "就绪——手动操作后开始播放", + "Radio off": "电台已关闭", + "Connecting directly to broadcaster…": "正在直接连接广播方…", + "Buffering broadcaster stream…": "正在缓冲广播流…", + "Playing {station}": "正在播放 {station}", + "Paused {station}": "已暂停 {station}", + "Broadcaster stream unavailable": "广播流不可用", + " · muted during voice interaction": " · 语音交互期间已静音", + " · restoring volume after voice": " · 语音结束后正在恢复音量", + " · static indicates no broadcaster audio": " · 杂音表示广播方没有音频", + " · tuning static until broadcaster starts": " · 广播方启动前为调谐杂音", + "Station unavailable after directory refresh — choose another channel": "目录刷新后电台不可用——请选择其他频道", + "Radio is enabling…": "电台正在启用…", + "Radio is disabling…": "电台正在停用…", + "Radio lifecycle is uncertain — use Enable or Disable to reconcile": "电台生命周期状态不确定——请使用“启用”或“停用”进行核对", + "Clear reading": "清除读数", + "Earlier observation": "较早观测", + "Observed history": "观测历史", + "Later observation": "较晚观测", + "Latest": "最新", + "Newest frame per product": "每个产品的最新一帧", + "Replay observed history": "回放观测历史", + "Imagery on Esri · Google 3D returns when cleared": "影像显示在 Esri 底图上 · 清除后恢复 Google 3D", + "Point the camera at the ground to use the view": "将相机对准地面才能使用当前视野", + "That point cannot anchor a box": "该点无法作为框选锚点", + "Imagery stops at ±{limit}° latitude": "影像仅覆盖到纬度 ±{limit}°", + "{owner} is using the pointer — close it first": "{owner} 正在使用指针——请先将其关闭", + "Box is {size} wide · limit {limit}": "框选宽 {size} · 上限 {limit}", + "View is {size} wide · limit {limit}": "视野宽 {size} · 上限 {limit}", + "VS BASEMAP": "对比底图", + "A / B": "A / B", + "Newest low-cloud day for this box · scene cloud, not box cloud": "框选区域内最近的少云日期 · 云量指整景影像,并非框选区域", + "Newest day covering this box · cloudier than 20%": "覆盖此框选的最新一天 · 云量高于 20%", + "Newest day with imagery for this box · partial coverage": "此框选有影像的最新一天 · 部分覆盖", + "Newest daily overview for this box": "此框选的最新每日概览", + "newest clear day": "最近少云日期", + "newest day (cloudy)": "最新一天(多云)", + "newest day (partial coverage)": "最新一天(部分覆盖)", + "newest overview": "最新概览", + "No imagery": "无影像", + "Loading": "加载中", + "coverage|full": "完整", + "coverage|partial": "部分", + "unknown": "未知", + "no imagery": "无影像", + "{percent}% cloud": "{percent}% 云量", + "{min}–{max}% cloud": "{min}–{max}% 云量", + "NO BOX": "无框选", + "SEARCHING": "搜索中", + "{count} DAYS": { + "other": "{count} 天" + }, + "Zoom in or draw a smaller box": "放大或绘制更小的框选", + "Select a box or use the view": "框选区域,或使用当前视野", + "Searching the last 30 days": "正在搜索最近 30 天", + "No days to show for this box": "此框选没有可显示的日期", + "Drag the divider · SWAP trades sides": "拖动分割线 · “交换”可互换两侧", + "← → preview · A or B pins the focused day": "← → 预览 · A 或 B 固定当前所选日期", + "← → preview the other side · A or B pins it": "← → 预览另一侧 · A 或 B 将其固定", + "S on another day replaces it · × unpins": "在其他日期按 S 替换 · × 取消固定", + "← → preview · S shows the focused day": "← → 预览 · S 显示当前所选日期", + "ZOOM IN": "放大", + "Fly in until the view fits the 1,000 km limit": "拉近直到视野不超过 1,000 km 上限", + "Imagery days, newest first · arrows move, S or A and B pin": "影像日期(最新在前)· 方向键移动,S、A、B 固定", + "On the map": "地图上", + "Mode": "模式", + "Imagery opacity": "影像不透明度", + "SWAP": "交换", + "Trade the two sides of the divider": "互换分割线两侧", + "SELECT BOX": "框选区域", + "Drag a box on the map (Esc cancels)": "在地图上拖出框选(Esc 取消)", + "USE VIEW": "使用视野", + "Use the current view as the box": "以当前视野作为框选", + "Forget the box and its images": "清除框选及其影像", + "START HERE": "从此开始", + "Checking": "检查中", + "PREVIEW": "预览", + "Newest day with imagery for this box": "此框选有影像的最新一天", + "SHOW": "显示", + "Pin {day} as {slot}": "将 {day} 固定为 {slot}", + "Show {day}": "显示 {day}", + "pinned {slot}": "已固定为 {slot}", + "shown": "显示中", + "preview": "预览", + "start here": "从此开始", + "No box": "无框选", + "Searching": "搜索中", + "Every day is empty here": "此处每天都为空", + "Sources off": "数据源已关闭", + "VS": "对比", + "Not set": "未设置", + "Basemap": "底图", + "Source off": "数据源已关闭", + "Unpin {slot}": "取消固定 {slot}", + "Unpin the image": "取消固定图像", + "EXPORT {slot}": "导出 {slot}", + "EXPORT": "导出", + "Download {slot} as a PNG": "将 {slot} 下载为 PNG", + "Download the image as a PNG": "将图像下载为 PNG", + "Box {width} × {height} km": "框选 {width} × {height} km", + "Acquired {time}": "获取于 {time}", + "Coverage {coverage}": "覆盖 {coverage}", + "START HERE · {reason}": "从此开始 · {reason}", + "Daily overview shows little detail in a box this small": "在这么小的框选内,每日概览几乎看不出细节", + "Imagery: NASA GIBS and Worldview · HLS (Sentinel-2, Landsat 8/9) and VIIRS": "影像:NASA GIBS 与 Worldview · HLS(Sentinel-2、Landsat 8/9)与 VIIRS", + "Details": "详情", + "Times, coverage, sources": "时间、覆盖与数据源", + "HIDE EMPTY DAYS": "隐藏空白日", + "SHOW EMPTY DAYS · {count}": "显示空白日 · {count}", + "Days the probe found empty in this box": "探测发现此框选内为空的日期", + "image": "图像", + "BASEMAP": "底图", + "The basemap": "底图", + "basemap": "底图", + "Recent imagery against the basemap divider": "近期影像与底图对比分割线", + "Recent imagery A and B divider": "近期影像 A / B 分割线", + "{before} {left} percent, {after} {right} percent": "{before} {left}%,{after} {right}%", + "Export failed · {error}": "导出失败 · {error}", + "Scene action failed": "场景操作失败", + "New scene name": "新场景名称", + "Delete scene \"{scene}\" and all shots?": "删除场景“{scene}”及其全部镜头?", + "Delete shot \"{shot}\"?": "删除镜头“{shot}”?", + "Stopped (Esc)": "已停止(Esc)", + "SPARSE": "稀疏", + "BALANCED": "均衡", + "No shots yet. Use CAPTURE SHOT to save current look.": "暂无镜头。使用“保存镜头”保存当前视角及设置。", + "Double-click to rename": "双击重命名", + "Shot name": "镜头名称", + "LOAD": "加载", + "DEL": "删除", + "{duration}s + {hold}s": "{duration} 秒 + {hold} 秒", + "Choose files": "选择文件", + "{count} files selected": { + "other": "已选择 {count} 个文件" + }, + "No file chosen": "未选择文件", + "Cancel": "取消", + "EDIT DETAILS": "编辑详情", + "SHARE SCENE": "分享场景", + "Shared flight follow expired": "分享的航班跟随已过期", + "Shared military flight follow expired": "分享的军用航班跟随已过期", + "Shared satellite follow expired": "分享的卫星跟随已过期", + "Shared flight could not be restored — feed unavailable": "无法恢复分享的航班——数据源不可用", + "Shared military flight could not be restored — feed unavailable": "无法恢复分享的军用航班——数据源不可用", + "Shared satellite could not be restored — feed unavailable": "无法恢复分享的卫星——数据源不可用", + "Shared flight is unavailable": "分享的航班不可用", + "Shared military flight is unavailable": "分享的军用航班不可用", + "Shared satellite is unavailable": "分享的卫星不可用", + "entity": "实体", + "Shared {subject} follow expired": "分享的 {subject} 跟随已过期", + "Shared {subject} could not be restored — feed unavailable": "无法恢复分享的 {subject}——数据源不可用", + "Shared {subject} is unavailable": "分享的 {subject} 不可用", + "ACQUIRING": "获取中", + "SHARED {subject}": "分享的 {subject}", + "subject": "目标", + "LOADING LIVE DATA": "正在加载实时数据", + "LOAD COMPLETE": "加载完成", + "LOAD CANCELLED": "加载已取消", + "LOAD FAILED": "加载失败", + "LIVE DATA OFF": "实时数据已关闭", + "MAPPED SITES LOADED": "已加载地图标注设施", + "RETRYING MAPPED SITES": "正在重试地图标注设施", + "FETCHING MAPPED SITES": "正在获取地图标注设施", + "RETRYING ALPR CAMERAS": "正在重试 ALPR 摄像头", + "FETCHING ALPR CAMERAS": "正在获取 ALPR 摄像头", + "OVERPASS TEMPORARILY UNAVAILABLE": "Overpass 暂时不可用", + "OVERPASS RATE-LIMITED": "Overpass 已被限流", + "OVERPASS TIMED OUT": "Overpass 超时", + "OVERPASS COULD NOT COMPLETE THE QUERY": "Overpass 无法完成查询", + "MAP TILES TEMPORARILY UNAVAILABLE": "地图瓦片暂时不可用", + "MAPPED NAMES TEMPORARILY UNAVAILABLE": "地点名称暂时不可用", + "TURNING OFF LIVE DATA": "正在关闭实时数据", + "REFRESHING LIVE DATA": "正在刷新实时数据", + "syncing road network": "正在同步路网", + "retry pending": "等待重试", + "retrying in {seconds}s": "{seconds} 秒后重试", + "ALPR cameras": "ALPR 摄像头", + "OpenSky OAuth client missing/invalid": "OpenSky OAuth 客户端缺失/无效", + "OpenSky OAuth rejected credentials": "OpenSky OAuth 拒绝了凭据", + "OpenSky username/password rejected": "OpenSky 用户名/密码被拒绝", + "OpenSky auth missing": "OpenSky 缺少认证", + "OpenSky auth required": "OpenSky 需要认证", + "OpenSky auth invalid": "OpenSky 认证无效", + "OpenSky auth failed": "OpenSky 认证失败", + "KartaView answered HTTP {status}": "KartaView 返回 HTTP {status}", + "Panoramax answered HTTP {status}": "Panoramax 返回 HTTP {status}", + "KartaView refused the request (code {code})": "KartaView 拒绝了请求(代码 {code})", + "Vector tiles unavailable (HTTP {status})": "矢量瓦片不可用(HTTP {status})", + "GNSS feed unavailable (HTTP {status})": "GNSS 数据源不可用(HTTP {status})", + "{count} CelesTrak groups unavailable": { + "other": "{count} 个 CelesTrak 分组不可用" + }, + "{feed} feed unavailable": "{feed} 数据源不可用", + "Source snapshot {minutes} min old": "数据源快照:{minutes} 分钟前", + "{source} rate limited (anonymous)": "{source} 已被限流(匿名)", + "{source} rate limited": "{source} 已被限流", + "{source} network error": "{source} 网络错误", + "Malformed {source} aircraft rows": "{source} 飞机数据行格式错误", + "Malformed {source} response": "{source} 响应格式错误", + "entry {n}": "条目 {n}", + "feeds {names} stale": { + "other": "数据源 {names} 过期" + }, + "feeds {names} unreachable": { + "other": "数据源 {names} 无法连接" + }, + "feeds {names} invalid": { + "other": "数据源 {names} 无效" + }, + "Loading {feeds}": "正在加载 {feeds}", + "{count} heard": { + "other": "已接收 {count} 架飞机的信号" + }, + "USB {rate} msg/s": "USB {rate} msg/s", + "{rate} msg/s": "{rate} msg/s", + "{count} feeds live": { + "other": "实时数据源 {count} 个" + }, + "{feed} {count} ({seconds}s old)": "{feed} {count}({seconds} 秒前)", + "Street Level could not start": "街景未能启动", + "Street Level toggle failed": "街景切换失败", + "SHRINK": "缩小", + "EXPAND": "展开", + "Shrink": "缩小", + "Expand": "展开", + "ANY DATE": "任意日期", + "LAST 10 YEARS": "最近 10 年", + "LAST 5 YEARS": "最近 5 年", + "LAST 3 YEARS": "最近 3 年", + "LAST 2 YEARS": "最近 2 年", + "LAST YEAR": "最近一年", + "LAST 6 MONTHS": "最近 6 个月", + "LAST 3 MONTHS": "最近 3 个月", + "LAST MONTH": "最近一个月", + "LAST {count} DAYS": { + "other": "最近 {count} 天" + }, + "{window} · SINCE {date}": "{window} · 自 {date} 起", + "Turn Street Level off": "关闭街景", + "Turn Street Level on": "开启街景", + "{name} imagery on": "{name} 影像已开启", + "{name} imagery off": "{name} 影像已关闭", + "Image by {creator}": "图像作者:{creator}", + " or ": " 或 ", + "OPEN {labels}": "打开 {labels}", + "Open {names} at the centre of the view": "在视野中心打开 {names}", + "Zoom in to a street to open {names} here": "放大到街道以在此处打开 {names}", + "Loading this sequence…": "正在加载此序列…", + "{n} images in this sequence · Esc clears": { + "other": "此序列共 {n} 张图像 · Esc 清除" + }, + "{n} streets in view · click a line for its nearest photo": { + "other": "视野内共 {n} 条街道 · 点击线段查看距其最近的照片" + }, + "{n} sequences in view · click a line for its nearest photo": { + "other": "视野内共 {n} 条序列 · 点击线段查看距其最近的照片" + }, + "{n} sequences in view · click a line for its photos": { + "other": "视野内共 {n} 条序列 · 点击线段查看其照片" + }, + "km/h": "km/h", + "m/s": "m/s", + "mph": "mph", + "hPa": "hPa", + "kt": "kn", + "kts": "kn", + "ft": "ft", + "km": "km", + "m": "m", + "h": "小时", + "min": "分钟", + "s": "秒", + "dB": "dB", + "Celestial ring — reveal the full globe": "天球环——显示完整地球", + "Celestial ring — available in Normal style": "天球环——仅在常规样式下可用", + "Detection overlay: {mode}": "探测叠加层:{mode}", + "Detection overlay: off": "探测叠加层:关闭", + "DETECT": "探测", + "{time} · {minutes} min ago": "{time} · {minutes} 分钟前", + "Active weather": "已启用的天气图层", + "LATEST · newest per product": "最新 · 每个产品的最新一帧", + "Forecast · valid {valid} · issued {issued}": "预报 · 有效时间 {valid} · 发布时间 {issued}", + "{detail} · Does not follow history": "{detail} · 不随观测历史变化", + "Forecast · valid {valid}": "预报 · 有效时间 {valid}", + "voice|OFF": "关", + "voice|CONNECTING": "连接中", + "voice|LISTENING": "聆听中", + "voice|EXECUTING": "执行中", + "voice|ERROR": "错误", + "AI AGENT": "AI 智能体", + "Voice model tier — applies next session": "语音模型档位——下次会话生效", + "STD": "标准", + "Estimated session cost": "预估会话费用", + "Voice control — activate to toggle voice; hold Space to speak": "语音控制——点击可切换语音;按住空格键说话", + "ON/OFF": "开/关", + "VOICE STANDBY": "语音待机", + "VOICE CONTROL": "语音控制", + "Hold Space to speak · tap Space to activate focused controls": "按住空格键说话 · 轻按空格键激活当前聚焦的控件", + "Voice assistant": "语音助手", + "VOICE": "语音", + "Dismiss voice card": "关闭语音卡片", + "YOU": "你", + "THIS": "所指", + "Plan": "计划", + "Referenced items": "引用条目", + "NOTES & SOURCES": "说明与来源", + "VOICE SYSTEM ERROR": "语音系统错误", + "DISMISS": "关闭", + "Check microphone permission and network access, then try again.": "请检查麦克风权限和网络连接,然后重试。", + "Stopped by voice": "已通过语音停止", + "WebRTC microphone support unavailable": "当前浏览器不支持通过 WebRTC 使用麦克风", + "Requesting microphone": "正在请求麦克风", + "Release Space to send": "松开空格键发送", + "Hold Space to talk": "按住空格键说话", + "Ask or command": "提问或下达指令", + "Voice off": "语音已关闭", + "VOICE UNAVAILABLE": "语音不可用", + "VOICE ACTIVE": "语音已启用", + "Voice session could not be started.": "无法启动语音会话。", + "MINI": "轻量", + "Next session: {pendingId} — this session stays on {modelId}": "下次会话:{pendingId}——本次会话仍使用 {modelId}", + "Voice model: {pendingId} — click to switch to {target}; applies next session": "语音模型:{pendingId}——点击切换到 {target};下次会话生效", + "standard": "标准", + "mini": "轻量", + "COST UNKNOWN": "费用未知", + "ChatGPT OAuth session: {responses} response(s), {input} input and {output} output tokens, {captions} caption transcription(s) reported.": "ChatGPT OAuth 会话:已报告 {responses} 个响应、{input} 个输入 token、{output} 个输出 token、{captions} 次字幕转写。", + "USD cost and the API spend cap are unavailable for this auth mode.": "此认证模式下无法提供美元费用与 API 消费上限。", + "Usage is incomplete because a response was still in flight when the session ended.": "用量不完整,因为会话结束时仍有响应在处理中。", + "Estimated session cost on {modelId} — {responses} response(s). Warns at {warn}, ends the session at {cap}.": "{modelId} 的预估会话费用——{responses} 次响应。达到 {warn} 时警告,达到 {cap} 时结束会话。", + "Estimate is incomplete — a response was still in flight when the session ended, so its usage was never reported.": "估算不完整——会话结束时仍有响应在处理中,因此其用量从未上报。", + "STANDARD": "标准", + "{tier} applies next session": "{tier} 将在下次会话生效", + "Session ended — cost cap {cost}": "会话已结束——已达费用上限 {cost}", + "MIC": "麦克风", + "Radio did not start": "电台未能启动", + "Running command": "正在执行指令", + "{n} aircraft": { + "other": "{n} 架飞机" + }, + "{n} military aircraft": { + "other": "{n} 架军用飞机" + }, + "{n} ships": { + "other": "{n} 艘船舶" + }, + "{n} satellites": { + "other": "{n} 颗卫星" + }, + "{n} launches": { + "other": "{n} 次发射" + }, + "{n} earthquakes": { + "other": "{n} 次地震" + }, + "{n} fires": { + "other": "{n} 处火点" + }, + "{n} fire perimeters": { + "other": "{n} 处火场周界" + }, + "{n} cyclones": { + "other": "{n} 个气旋" + }, + "{n} buses": { + "other": "{n} 辆公交车" + }, + "{n} bike share": { + "other": "{n} 个共享单车站点" + }, + "{n} flock cameras": { + "other": "{n} 个 Flock 摄像头" + }, + "{n} military bases": { + "other": "{n} 处军事基地" + }, + "{n} datacenters": { + "other": "{n} 个数据中心" + }, + "{n} dams": { + "other": "{n} 座水坝" + }, + "{n} air quality": { + "other": "{n} 个空气质量站点" + }, + "{n} results": { + "other": "{n} 条结果" + }, + "anywhere in the loaded data": "已加载数据中的任意位置", + "in view": "视野内", + "within {radius} of the view centre": "视野中心 {radius} 范围内", + "within {radius} of {place} (Contacts window)": "{place} {radius} 范围内(目标窗口)", + "within {radius} of {place}": "{place} {radius} 范围内", + "within {radius}": "{radius} 范围内", + "over {region}": "{region} 范围内", + "At least {items}": "至少 {items}", + "{items} {scope}": "{items} {scope}", + "last 24 h": "过去 24 小时", + "past 30 d and upcoming": "过去 30 天及未来", + "the flights layer loads by viewport": "航班图层按视口加载", + "distance is ground distance, not overhead": "距离为地面距离,而非空中直线距离", + "the count is a lower bound because at least one layer reached its retained limit.": "该计数为下限,因为至少一个图层已达到其保留上限。", + "{label} off": "{label} 已关闭", + "{label} loading": "{label} 加载中", + "{label} error": "{label} 出错", + "counted the first {returned} loaded records": "已统计最先加载的 {returned} 条记录", + "counted {returned} of {total} loaded records": "已统计 {total} 条已加载记录中的 {returned} 条", + "{layers} just turned on — still loading, counts will rise": "{layers} 刚开启——仍在加载,计数还会增加", + "Contacts panel snapshot at {time} — the same retained cohort supplies the card, speech, and follow-up memory": "目标面板快照({time})——卡片、语音播报和后续追问记忆均来自同一组保留目标", + "Contacts panel snapshot — the same retained cohort supplies the card, speech, and follow-up memory": "目标面板快照——卡片、语音播报和后续追问记忆均来自同一组保留目标", + "Fly below {km} km to a covered region": "请降至 {km} km 以下并飞往有覆盖的区域", + "No feed here yet · {n} regions available": { + "other": "此处暂无数据源 · 有 {n} 个覆盖区域" + }, + "degraded": "降级", + "stale": "过期", + "partial": "部分可用", + "fallback": "备用", + "unavailable": "不可用", + "Not answered: {layers}": "未响应:{layers}", + "Toggle voice control": "切换语音控制", + "Activate to toggle voice": "点击切换语音", + "Voice active": "语音已启用", + "Voice could not be started.": "无法启动语音。", + "the active voice session": "当前语音会话", + "{value} feet": "{value} 英尺", + "The flights layer is not enabled": "航班图层未开启", + "The military layer is not enabled": "军用航班图层未开启", + "The satellites layer is not enabled": "卫星图层未开启", + "The ais-live-vessels layer is not enabled": "实时 AIS 船舶图层未开启", + "Could not enable the requested layer": "无法开启所请求的图层", + "Could not disable the requested layer": "无法关闭所请求的图层", + "Radio layer unavailable": "电台图层不可用", + "Camera navigation is unavailable in the current view": "当前视图下无法进行相机导航", + "Camera navigation policy unavailable": "相机导航策略不可用", + "Nearest-aircraft selection supports Flights or Military Flights only": "最近飞机选择仅支持航班或军用航班", + "Nearest-aircraft selection needs a preset, place name, or latitude and longitude": "最近飞机选择需要预设、地名或经纬度", + "Could not arrive at the requested place": "无法抵达所请求的地点", + "The nearest airborne aircraft could not be selected": "无法选中最近的空中飞机", + "No flights within {radius} of the current view": "当前视野 {radius} 范围内没有航班", + "No military within {radius} of the current view": "当前视野 {radius} 范围内没有军用航班", + "No satellites within {radius} of the current view": "当前视野 {radius} 范围内没有卫星", + "No vessels within {radius} of the current view": "当前视野 {radius} 范围内没有船舶", + "{layer} is enabled on the {source} feed, but no airborne aircraft is loaded in the {place} view yet": "{layer} 已在 {source} 数据源上开启,但 {place} 视野中尚未加载空中飞机", + "{layer} is enabled, but no airborne aircraft is loaded in the {place} view yet": "{layer} 已开启,但 {place} 视野中尚未加载空中飞机", + "{layer} is enabled, but its aircraft feed is unavailable": "{layer} 已开启,但其飞机数据源不可用", + "{layer} is enabled, but its destination refresh did not complete": "{layer} 已开启,但目的地刷新未完成", + "{layer} is enabled, but {source} is unavailable": "{layer} 已开启,但 {source} 不可用", + "Could not arrive at {place}": "无法抵达 {place}", + "{name} is no longer loaded": "{name} 已不在加载数据中", + "Unknown target layer: {target}": "未知的目标图层:{target}", + "nominal": "正常", + "{label} on": "{label} 已开启", + "Frame aircraft": "将飞机纳入视野", + "Frame military aircraft": "将军用飞机纳入视野", + "Frame ships": "将船舶纳入视野", + "Frame satellites": "将卫星纳入视野", + "Frame contacts": "将目标纳入视野", + "{count} aircraft in frame": { + "other": "画面中 {count} 架飞机" + }, + "{count} military aircraft in frame": { + "other": "画面中 {count} 架军用飞机" + }, + "{count} ships in frame": { + "other": "画面中 {count} 艘船舶" + }, + "{count} satellites in frame": { + "other": "画面中 {count} 颗卫星" + }, + "{count} contacts in frame": { + "other": "画面中 {count} 个目标" + }, + "Nearest military aircraft": "最近的军用飞机", + "Nearest ships": "最近的船舶", + "Nearest satellites": "最近的卫星", + "Nearest aircraft": "最近的飞机", + "{first} and {second}": "{first} 和 {second}", + "Layer": "图层", + "there": "那里", + "Camera range {range}": "相机距离 {range}", + "Within {radius} of the view": "当前视野 {radius} 范围内", + "Labels on": "标签已开启", + "Framing uses the nearest 80 loaded contacts": "调整视野时仅包含已加载的最近 80 个目标", + "{distance} from {location}": "距 {location} {distance}", + "Altitude {altitude}": "高度 {altitude}", + "{origin} to {destination}": "{origin} 至 {destination}", + "from {origin}": "从 {origin} 出发", + "to {destination}": "前往 {destination}", + "Registration {registration}": "注册号 {registration}", + "View state": "视野状态", + "Marked {count} places": { + "other": "已标注 {count} 个地点" + }, + "Nothing marked": "没有标注", + "Not found: {places}": "未找到:{places}", + "Tracing outlines": "正在绘制轮廓", + "Frame flights": "将航班纳入视野", + "Frame military": "将军用航班纳入视野", + "Frame vessels": "将船舶纳入视野", + "Frame {target}": "将 {target} 纳入视野", + "Zoom out to globe": "缩小至地球视图", + "Open data layers": "打开数据图层", + "Open or close a panel": "打开或关闭面板", + "Change context mode": "切换态势模式", + "Control cockpit": "控制座舱", + "Change visual style": "切换视觉样式", + "Adjust HUD": "调整 HUD", + "Adjust cyber sonar": "调整赛博声呐", + "Adjust detection overlay": "调整探测叠加层", + "Change map stack": "切换地图组合", + "Adjust post-processing": "调整后期处理", + "Control scene": "控制场景", + "Control CCTV": "控制 CCTV", + "Control radio": "控制电台", + "Track contact": "跟踪目标", + "Stop tracking": "停止跟踪", + "Clear marks": "清除标注", + "Move camera": "移动相机", + "Fly route": "沿路线飞行", + "Analyst query": "分析查询", + "Next ISS pass": "下一次 ISS 过境", + "Next satellite pass": "下一次卫星过境", + "Mark the map": "在地图上标注", + "Mark {targets} +{more}": "标注 {targets} +{more}", + "Mark {targets}": "标注 {targets}", + "Fly to {place}": "飞往 {place}", + "Fly to Location": "飞往地点", + "Nearest aircraft to {place}": "距离 {place} 最近的飞机", + "Nearest aircraft to Here": "距离此处最近的飞机", + "Layer on": "图层已开启", + "Layer off": "图层已关闭", + "Zoom out": "缩小", + "Zoom in": "放大", + "Read the view": "播报当前视野", + "Check view state": "检查视野状态", + "Action": "操作", + "Finding places": "正在查找地点", + "Tracing outline": "正在绘制轮廓", + "Looking up place": "正在查询地点", + "Flying": "飞行中", + "Turning on layer": "正在开启图层", + "Loading aircraft": "正在加载飞机", + "Picking nearest": "正在选取最近目标", + "Working": "处理中", + "in progress": "进行中", + "done": "完成", + "failed": "失败", + "cancelled": "已取消", + "Outline traced": "轮廓已绘制", + "Outline unavailable": "轮廓不可用", + "Source: {label}": "来源:{label}", + "WORKING": "处理中", + "DONE": "完成", + "HERE": "此处", + "Aircraft cockpit view": "飞机座舱视图", + "LEVEL": "水平", + "Estimated destination direction": "预估目的地方向", + "DEST ---°": "目的地 ---°", + "OPTICAL PLANE · 01": "光学平面 · 01", + "VISOR LOCK · ACTIVE": "面罩锁定 · 激活", + "GROUND SPEED · KTS": "地速 · kn", + "ALTITUDE · FT": "高度 · ft", + "FIRST PERSON": "第一人称", + "LIVE TRACK · COURSE ALIGNED": "实时航迹 · 航向对准", + "Cockpit vision style": "座舱视觉样式", + "Previous cockpit vision style": "上一个座舱视觉样式", + "Previous vision style": "上一个视觉样式", + "Current cockpit vision style: NORMAL. Activate for next style.": "当前座舱视觉样式:常规。点击可切换到下一个样式。", + "Current style: NORMAL — click for next": "当前样式:常规——点击切换到下一个", + "CURRENT": "当前", + "Next cockpit vision style": "下一个座舱视觉样式", + "Next vision style": "下一个视觉样式", + "Current aircraft heading": "飞机当前航向", + "GROUND SPEED": "地速", + "KTS": "kn", + "ALTITUDE": "高度", + "FT": "ft", + "Contact cockpit summary": "目标座舱摘要", + "CONTACT": "目标", + "CONTACTS · 250 KM": "目标 · 250 km", + "Contact navigation": "目标导航", + "CONTEXT ONLY": "仅供参考", + "Nearby cohort counts": "附近各类目标数量", + "FLT": "航班", + "MIL": "军用", + "SITE": "设施", + "NEAREST OBSERVED / MAPPED": "最近的观测 / 地图标注目标", + "AVAILABLE INPUTS ONLY · NOT AN ALL-CLEAR": "仅限可用输入 · 不代表安全无虞", + "WX": "天气", + "Cockpit briefing carousel": "座舱简报轮播", + "Estimated flight plan": "预估飞行计划", + "ESTIMATED FLIGHT PLAN": "预估飞行计划", + "FROM": "出发", + "TO": "到达", + "Cockpit briefing controls": "座舱简报控件", + "Previous briefing page": "简报上一页", + "Next briefing page": "简报下一页", + "Live signals": "实时信号", + "Latest regional news": "最新地区新闻", + "Location-based information": "基于位置的信息", + "TEMP": "气温", + "WIND": "风", + "SKY": "天空", + "PRECIP": "降水", + "MM": "mm", + "Weather data by Open-Meteo.com": "天气数据由 Open-Meteo.com 提供", + "Cockpit briefing pages": "座舱简报页面", + "SIG": "信号", + "Show Live Signals": "显示实时信号", + "NEWS": "新闻", + "Show Regional News": "显示地区新闻", + "LOCAL": "本地", + "Show Local Info": "显示本地信息", + "Cockpit display and Radio controls": "座舱显示与电台控件", + "DISPLAY": "显示", + "Cockpit display options": "座舱显示选项", + "RADIO": "电台", + "Cockpit compact Radio controls": "座舱精简电台控件", + "Previous filtered radio station": "上一个已筛选电台", + "Play selected radio station": "播放所选电台", + "Next filtered radio station": "下一个已筛选电台", + "VOLUME": "音量", + "Cockpit Radio volume": "座舱电台音量", + "ESC EXIT": "ESC 退出", + "C TOGGLE": "C 切换", + "View switcher": "视图切换", + "Exit cockpit and return to full globe view": "退出座舱并返回完整地球视图", + "RESET": "重置", + "Exit cockpit view": "退出座舱视图", + "EXIT COCKPIT": "退出座舱", + "Navigation, voice, and visual preset controls": "导航、语音和视觉预设控件", + "Expand Visual Presets": "展开视觉预设", + "Pin visual presets": "固定视觉预设", + "Keep visual presets open": "保持视觉预设展开", + "Show the globe without a visual filter.": "显示不带视觉滤镜的地球。", + "Emulate a green phosphor CRT with scanlines and screen curvature.": "模拟带扫描线和屏幕弧度的绿色荧光 CRT。", + "Simulate night-vision goggles with green intensification and a tube vignette.": "模拟夜视仪,带绿色增强和镜筒暗角。", + "Simulate FLIR-style thermal contrast. Turn up Ironbow for color.": "模拟 FLIR 风格的热成像对比。调高 Ironbow 可显示色彩。", + "Apply bright cel-shaded color and illustrated outlines.": "应用明亮的赛璐珞色彩和插画轮廓线。", + "Apply high-contrast monochrome film-noir grading.": "应用高对比度的黑白电影风格调色。", + "Add a cold, snowy whiteout treatment to the scene.": "为场景添加寒冷的暴雪白化效果。", + "MAP SOURCE": "地图源", + "Map source": "地图源", + "Style": "样式", + "Expand LOCATION": "展开位置", + "Collapse panel": "收起面板", + "Pin location tray": "固定位置托盘", + "Keep location tray open": "保持位置托盘展开", + "Search any location": "搜索任意位置", + "Search location by name or coordinates": "按名称或坐标搜索位置", + "Search any location...": "搜索任意位置…", + "WEATHER": "天气", + "Active weather products": "已启用的天气产品", + "RECENT IMAGERY": "近期影像", + "Imagery days in the box": "视野内的影像天数", + "CONTEXT": "态势", + "Compact Radio controls": "精简电台控件", + "Open detailed Radio controls": "打开详细电台控件", + "Previous station": "上一个电台", + "Next station": "下一个电台", + "Compact Radio volume": "精简电台音量", + "Expand panel": "展开面板", + "Context mode": "态势模式", + "CONTACTS": "目标", + "Cycles the nearest contacts of whatever type you select — planes, vessels, installations. Satellites track independently.": "循环切换你所选类型中最近的目标,包括飞机、船舶、设施。卫星独立跟踪。", + "SPACE MISSIONS": "太空任务", + "SELECT CONTEXT": "选择态势", + "Contact Context actions": "目标态势操作", + "COCKPIT": "座舱", + "SEARCH NEARBY SITES": "搜索附近设施", + "Reclassify tracked contact as TR-3B": "将跟踪目标重新归类为 TR-3B", + "CONTACTS CONTEXT OFF": "目标态势已关闭", + "SELECT CONTACTS TO LOAD OBSERVED / MAPPED PROXIMITY": "选择目标以加载观测 / 地图标注的邻近信息", + "Available Space Missions": "可用太空任务", + "AVAILABLE MISSIONS": "可用任务", + "SELECT A MISSION TO INSPECT": "选择一项任务以查看", + "LOADING 30-DAY MISSION INDEX": "正在加载 30 天任务索引", + "TAB PREVIEWS · ENTER / SPACE SELECTS": "TAB 预览 · ENTER / 空格键选择", + "Internet radio and local SDR": "网络电台与本地 SDR", + "STATION TAG": "电台标签", + "Filter stations by station tag": "按电台标签筛选电台", + "All": "全部", + "Enable Radio, then choose a globe marker or use next.": "请先启用电台,然后选择地球上的标注或使用“下一个”。", + "DRAG TO TUNE": "拖动调谐", + "Tune available internet radio stations": "调谐可用的网络电台", + "ALL · DRAG THE NEEDLE": "全部 · 拖动指针", + "SNAPS TO AVAILABLE STATIONS": "自动吸附到可用电台", + "Radio playback": "电台播放", + "Previous filtered station": "上一个已筛选电台", + "Play selected station": "播放所选电台", + "Next filtered station": "下一个已筛选电台", + "STOP": "停止", + "Stop radio playback": "停止电台播放", + "Radio volume": "电台音量", + "STATION SITE": "电台网站", + "DIRECTORY: RADIO BROWSER": "目录:RADIO BROWSER", + "Audio connects directly to the broadcaster after you press play. Your IP is visible to that broadcaster.": "点击播放后,音频将直接连接到广播方。广播方可以看到你的 IP。", + "Local RTL-SDR receiver": "本地 RTL-SDR 接收机", + "LOCAL RTL-SDR": "本地 RTL-SDR", + "Chrome/Edge · WebUSB · one local tuner shared by FM and the Local ADS-B layer.": "Chrome/Edge · WebUSB · 一个本地调谐器由 FM 与本地 ADS-B 图层共享。", + "Use your location for faster ADS-B position decoding": "使用你的位置,加快 ADS-B 位置解码", + "CHANGE DEVICE": "更换设备", + "Choose a different RTL-SDR or receiver channel": "选择其他 RTL-SDR 或接收通道", + "RTL-SDR receiver mode": "RTL-SDR 接收模式", + "GAIN": "增益", + "RTL-SDR tuner gain for the current mode": "当前模式下的 RTL-SDR 调谐器增益", + "AUTO": "自动", + "MSG/S": "msg/s", + "HEARD": "已接收信号", + "POSITIONED": "已定位", + "FM MHZ": "FM MHz", + "TUNE": "调谐", + "Seek broadcast FM stations": "搜索广播 FM 电台", + "◀ SEEK": "◀ 搜台", + "SEEK ▶": "搜台 ▶", + "SDR VOL": "SDR 音量", + "Local SDR volume": "本地 SDR 音量", + "Intelligence HUD (H)": "情报 HUD(H)", + "Layout": "布局", + "HUD layout": "HUD 布局", + "Tactical": "战术", + "Operator": "操作员", + "Minimal": "精简", + "Cyber": "赛博", + "Sonar": "声呐", + "Cyber sonar contact scanner": "赛博声呐目标扫描器", + "Rings": "圈数", + "Sonar ring count": "声呐圈数", + "Range": "范围", + "Sonar ring range": "声呐圈范围", + "Power": "强度", + "Sonar intensity": "声呐强度", + "Opacity": "不透明度", + "Cyber contact and label opacity": "赛博目标与标签不透明度", + "Minimum opacity for map dots, icons, models, brackets, and labels between sonar passes": "两次声呐扫描之间地图点、图标、模型、括号标记与标签的最低不透明度", + "Sector": "扇区", + "Sonar sector width": "声呐扇区宽度", + "Detection Overlay (D)": "探测叠加层(D)", + "Detection overlay": "探测叠加层", + "Detection label density": "探测标签密度", + "Allocation": "分配", + "Detection label allocation": "探测标签分配方式", + "Elastic": "弹性", + "Weighted": "加权", + "Fade": "渐隐", + "Detection fade distance": "探测渐隐距离", + "World-overlay fade distance outside the keyhole as a percentage of its radius": "视域之外的世界叠加层渐隐距离,以其半径的百分比表示", + "Outside": "外部", + "Detection opacity outside the keyhole": "视域之外的探测不透明度", + "World-overlay label and card opacity beyond the fade distance": "超出渐隐距离后世界叠加层标签和卡片的不透明度", + "PARAMETERS": "参数", + "3D aircraft — flat icons zoomed out, 3D models up close": "3D 飞机——拉远显示平面图标,拉近显示 3D 模型", + "Models": "模型", + "3D model coverage": "3D 模型覆盖范围", + "Proximity": "近处", + "Scope — the circular viewport mask": "视域——圆形视口遮罩", + "Scope": "视域", + "Feather": "羽化", + "Scope edge feather": "视域边缘羽化", + "Scope edge feather as a percentage of the keyhole radius": "视域边缘羽化,以视域半径的百分比表示", + "Draw on the world — click vertices, double-click or Enter to finish, Esc to cancel": "在地球上绘制——单击放置顶点,双击或按 Enter 完成,按 Esc 取消", + "Shape": "形状", + "Shape to draw": "要绘制的形状", + "Area": "面", + "Line": "线", + "Pin": "图钉", + "Label (optional)": "标签(可选)", + "Label for the drawn shape": "绘制形状的标签", + "Colour of the drawn shape": "绘制形状的颜色", + "Primary": "主题色", + "Amber": "琥珀色", + "Cyan": "青色", + "Green": "绿色", + "Red": "红色", + "Clear": "清除", + "Remove every mark from the board": "清除画板上的所有标注", + "Show your own GeoJSON, KML, KMZ or GPX files on the globe (or drop them onto it). File bytes stay on this device; overlays can appear in map screenshots shared with voice.": "在地球上显示自己的 GeoJSON、KML、KMZ 或 GPX 文件(也可直接拖放到地球上)。文件内容仅保留在本设备上;叠加层可能出现在随语音分享的地图截图中。", + "Import": "导入", + "Choose GeoJSON, KML, KMZ or GPX files": "选择 GeoJSON、KML、KMZ 或 GPX 文件", + "Imported files": "已导入的文件", + "Celestial": "天球", + "Hide UI chrome": "隐藏界面控件", + "Clean UI": "纯净界面", + "Bloom / Glow": "泛光 / 辉光", + "Bloom intensity": "泛光强度", + "Sharpening": "锐化", + "Sharpen": "锐化", + "Sharpen intensity": "锐化强度", + "EXIT CLEAN VIEW": "退出纯净界面", + "Return UI controls": "恢复界面控件", + "Initializing photorealistic world...": "正在初始化照片级逼真世界…", + "DATA LAYERS": "数据图层", + "ACQUIRING FRAME": "正在获取画面", + "NEAREST": "最近", + "CCTV camera": "CCTV 摄像头", + "CALIBRATION": "校准", + "Drag the camera in the world: rings rotate, arrows move, handles set range/FOV": "在世界中拖动摄像头:圆环旋转,箭头平移,控制点设置范围/FOV", + "Camera pose — click a value to type": "摄像头位姿——点击数值可输入", + "Heading (compass °) — click to type": "航向(罗盘 °)——点击输入", + "Pitch (° up/down) — click to type": "俯仰(° 上/下)——点击输入", + "Horizontal FOV (°) — click to type": "水平 FOV(°)——点击输入", + "Range / monitor-plane distance (m) — click to type": "范围 / 监视器平面距离(m)——点击输入", + "Mount height above ground (m) — click to type": "安装高度(距地面,m)——点击输入", + "North offset from catalog position (m) — click to type": "相对目录位置的北向偏移(m)——点击输入", + "East offset from catalog position (m) — click to type": "相对目录位置的东向偏移(m)——点击输入", + "SAVE CAL": "保存校准", + "RESET CAL": "重置校准", + "SCENE SUMMARY": "场景摘要", + "STREET LEVEL": "街景", + "Turn Street Level on or off": "开启或关闭街景", + "Image fit": "图像适配", + "FIT": "适应", + "Fit: show the whole photo, with dark bars where its shape differs from the frame": "适应:显示整张照片,在比例与画框不符处留黑边", + "FILL": "填充", + "Fill: fill the frame edge to edge, cropping the photo": "填充:图像铺满画框并裁剪照片", + "Close the image and deselect it on the map": "关闭图像并取消其在地图上的选中状态", + "Close the street-level image": "关闭街景图像", + "Street-level viewer": "街景查看器", + "Open this image on the provider's site": "在服务商网站上打开此图像", + "Imagery providers": "影像服务商", + "PROVIDERS": "服务商", + "Imagery filters": "影像筛选", + "IMAGERY": "影像", + "Panorama filter": "全景筛选", + "ALL": "全部", + "FLAT": "平面", + "SINCE": "起始时间", + "SCENES": "场景", + "Scene recipe": "场景方案", + "NEW": "新建", + "CAPTURE SHOT": "保存镜头", + "UPDATE SHOT": "更新镜头", + "START": "启动", + "EXPORT PRESETS": "导出预设", + "IMPORT": "导入", + "RUN LOG": "运行日志", + "POWER UP": "增强", + "GROUND STATION · PROVIDER SETTINGS": "地面站 · 服务商设置", + "Close key setup": "关闭密钥设置", + "Power up the globe": "增强地球", + "The globe already flies keyless. Every key below switches on another real feed — paste one and it's saved into this app's local configuration, then the server restarts itself. Server-side keys stay on this machine; Google Maps and Cesium ion run in the browser and must be provider-restricted. Keys you configured elsewhere are shown but never touched.": "地球无需密钥即可运行。下面的每个密钥都会启用一个新的真实数据源:粘贴后会保存到本应用的本地配置,随后服务器自动重启。服务端密钥只保存在本机;Google Maps 和 Cesium ion 在浏览器中运行,必须设置服务商限制。你在其他位置配置的密钥会显示,但不会被改动。", + "SAVE KEYS": "保存密钥", + "ESC to close": "按 ESC 关闭", + "The Google Maps key buys the photorealistic planet — everything else stacks on top.": "Google Maps 密钥提供照片级逼真 3D 地球,其余数据源都叠加在其上。", + "NO PLACE LEFT BEHIND": "不遗漏任何地方", + "ACTIVE STYLE": "当前样式", + "Globe actions": "地球操作", + "Turn off all selected data layers": "关闭所有已选数据图层", + "Copy share link": "复制分享链接", + "Toggle straight-down and tilted map views": "在垂直俯视与倾斜视角之间切换", + "Reset map to north up": "将地图重置为正北朝上", + "Reset map bearing to north": "重置地图方位为正北", + "Reset camera and return to full globe view": "重置相机并返回完整地球视图", + "MISSION CONTROL · FIRST LAUNCH": "任务控制 · 首次启动", + "Choose your first view": "选择初始视图", + "LIVE CONTACTS": "实时目标", + "Aircraft, vessels and nearby intelligence": "飞机、船舶及周边情报", + "Launches, spacecraft and orbital context": "发射、航天器与轨道态势", + "EXPLORE MANUALLY": "手动探索", + "Begin with a clean globe": "从空白地球开始", + "Don't show this again": "不再显示", + "ESC to dismiss": "按 ESC 关闭", + "Tip: the GEV MIC button in the dock lets you talk to the map.": "提示:可通过停靠栏中的 GEV 麦克风按钮用语音操控地图。", + "hemisphere|N": "N", + "hemisphere|S": "S", + "hemisphere|E": "E", + "hemisphere|W": "W", + "Gemini Live voice control": "Gemini Live 语音控制", + "Zoom in to see street-level coverage": "放大地图以查看街景覆盖范围", + "Point the camera at the ground for street-level coverage": "将相机朝向地面以查看街景覆盖范围", + "Sequence images unavailable": "序列影像不可用", + "Switch Street Level on to draw its coverage.": "开启“街景”以绘制其覆盖范围。", + "Every provider is switched off: light a chip to draw its coverage.": "所有服务商均已关闭:点亮一个标签即可绘制其覆盖范围。", + "No sequences in view match the filter": "视野内没有符合筛选条件的序列", + "No coverage in view": "视野内无覆盖范围", + "Voice provider": "语音服务商", + "Changing provider reconnects voice if it is on.": "如语音已开启,切换服务商会重新连接语音。", + "Gemini returned an oversized audio chunk": "Gemini 返回的音频块过大", + "Gemini returned an unsupported audio format": "Gemini 返回了不支持的音频格式", + "Gemini returned an invalid audio sample rate": "Gemini 返回了无效的音频采样率", + "Gemini returned incomplete PCM audio": "Gemini 返回的 PCM 音频不完整", + "This browser does not support Gemini microphone audio": "当前浏览器不支持 Gemini 麦克风音频", + "Microphone audio processing failed": "麦克风音频处理失败", + "Gemini audio playback fell behind. Reconnect to continue.": "Gemini 音频播放滞后。请重新连接以继续。", + "Gemini token response did not include an ephemeral credential": "Gemini 令牌响应中没有临时凭据", + "Invalid Gemini connection endpoint": "Gemini 连接端点无效", + "Gemini voice could not connect. Try again.": "Gemini 语音无法连接。请重试。", + "Gemini voice requires a same-origin app request": "Gemini 语音需要与本应用同源的请求", + "Add a Gemini API key in Provider Settings": "请在服务商设置中添加 Gemini API 密钥", + "The Gemini Live model configuration is invalid": "Gemini Live 模型配置无效", + "Gemini voice is starting too often. Try again shortly.": "Gemini 语音启动过于频繁。请稍后重试。", + "Gemini quota is unavailable. Try again later.": "Gemini 配额不可用。请稍后重试。", + "Gemini access was refused. Check the API key and model access.": "Gemini 拒绝了访问。请检查 API 密钥和模型访问权限。", + "Gemini Live is unavailable for the configured model.": "所配置的模型无法使用 Gemini Live。", + "Token request is too large": "令牌请求过大", + "Use an empty JSON token request": "请使用空的 JSON 令牌请求", + "Voice command context is too large": "语音命令上下文过大", + "Gemini reliable transport exceeded its queue limit. Start voice again.": "Gemini 可靠传输超出了队列上限。请重新启动语音。", + "Gemini reliable transport did not drain. Start voice again.": "Gemini 可靠传输的队列未能清空。请重新启动语音。", + "Gemini token request failed (HTTP {status})": "Gemini 令牌请求失败(HTTP {status})", + "Gemini token response did not include a valid model": "Gemini 令牌响应中没有有效的模型", + "Gemini token response did not include session settings": "Gemini 令牌响应中没有会话设置", + "Gemini session settings do not match the input mode": "Gemini 会话设置与输入模式不匹配", + "Gemini could not submit this input. Start voice again.": "Gemini 无法提交此输入。请重新启动语音。", + "Connecting to Gemini": "正在连接 Gemini", + "Gemini returned an oversized message": "Gemini 返回的消息过大", + "Gemini Live connection failed. Check network access, then start voice again.": "Gemini Live 连接失败。请检查网络连接,然后重新启动语音。", + "Gemini Live connection ended. Start voice again for a new conversation.": "Gemini Live 连接已结束。请重新启动语音以开始新对话。", + "Gemini Live could not start. Start voice again to request a fresh session.": "Gemini Live 无法启动。请重新启动语音以申请新的会话。", + "Start voice again for a fresh token and a new conversation.": "请重新启动语音,以获取新令牌并开始新对话。", + "Start voice again to request a fresh session credential.": "请重新启动语音以申请新的会话凭据。", + "Microphone permission was denied. Allow microphone access and try again.": "麦克风权限被拒绝。请允许访问麦克风后重试。", + "Gemini connection timed out. Try again.": "Gemini 连接超时。请重试。", + "Gemini voice could not start": "Gemini 语音无法启动", + "Gemini did not finish the interrupted reply. Start voice again.": "Gemini 未能完成被打断的回复。请重新启动语音。", + "Gemini session expiring; finishing the current reply": "Gemini 会话即将到期,正在完成当前回复", + "Gemini rejected the voice session. Start voice again to request a fresh session.": "Gemini 拒绝了语音会话。请重新启动语音以申请新的会话。", + "Start voice again for a fresh session credential. If it repeats, check Gemini model and API access.": "请重新启动语音以获取新的会话凭据。如问题重复出现,请检查 Gemini 模型和 API 访问权限。", + "Gemini action history is full. Start voice again for a fresh session.": "Gemini 操作历史已满。请重新启动语音以开始新的会话。", + "Gemini requested too many simultaneous actions": "Gemini 请求的同时操作过多", + "Voice text command is too long": "语音文本命令过长", + "Gemini voice is not connected": "Gemini 语音未连接", + "Gemini interrupted input exceeded its buffer or wait limit. Start voice again.": "Gemini 被打断的输入超出了缓冲区或等待上限。请重新启动语音。", + "Gemini Live connection is expiring. Start voice again for a new conversation.": "Gemini Live 连接即将到期。请重新启动语音以开始新对话。", + "Gemini voice sessions have a time limit. When a session expires, start voice again for a new conversation.": "Gemini 语音会话有时长限制。会话到期后,请重新启动语音以开始新对话。", + "Show the street-level view full screen (Esc shrinks it)": "全屏显示街景视图(按 Esc 缩小)", + "OK": "正常", + "{category} ({count})": "{category}({count})" +} diff --git a/src/i18n/same-as-english.json b/src/i18n/same-as-english.json index c159beaab..c331cd070 100644 --- a/src/i18n/same-as-english.json +++ b/src/i18n/same-as-english.json @@ -63,6 +63,7 @@ "NVG", "Noir", "Normal", + "OK", "POS: {time}Z", "Plan", "Pop", @@ -98,6 +99,9 @@ "fire confidence|nominal", "ft", "hPa", + "hemisphere|E", + "hemisphere|N", + "hemisphere|S", "installation class|BASE", "km", "km/h", @@ -133,5 +137,60 @@ "REC", "VS", "WHOT/BHOT" + ], + "zh-CN": [ + "1090 MHz", + "978 MHz UAT", + "AIS: --", + "AIS: {name}", + "BRG {value}°", + "BRG —", + "CAL {value}", + "CAL · {badge}", + "CCTV", + "CRS {value}°", + "CRT", + "FLIR", + "FOV", + "FOV {value}°", + "FT", + "GEO", + "HDG", + "HDG {value}°", + "House", + "Ironbow", + "KTS", + "Landsat 8/9", + "MM", + "NVG", + "REC", + "SRC {value}", + "Sentinel-2", + "Techno", + "Trance", + "USB {rate} msg/s", + "WHOT/BHOT", + "dB", + "ft", + "hPa", + "hemisphere|E", + "hemisphere|N", + "hemisphere|S", + "hemisphere|W", + "km", + "km/h", + "kt", + "kts", + "mph", + "{city} CCTV", + "{rate} msg/s", + "{source} HTTP {status}", + "{speed} kn", + "{type} SPD: {speed} HDG: {heading}", + "{value} KM", + "{value} KM/S", + "ΔE", + "ΔN", + "▲ {frp} MW" ] } diff --git a/src/i18n/zh-CN-display.test.mjs b/src/i18n/zh-CN-display.test.mjs new file mode 100644 index 000000000..b9e55058d --- /dev/null +++ b/src/i18n/zh-CN-display.test.mjs @@ -0,0 +1,106 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { setLocale, t } from './index.js'; +import { hudQuantity, LOCALIZED_LOCALITY } from '../hudLocalityFormat.js'; +import { formatCockpitDistance } from '../ui/cockpitPresentation.js'; +import { cctvSourceStatusLabel } from '../ui/cctvFrames.js'; +import { READOUT_WORDS } from '../layers/recentImagery/display.js'; +import { createModel } from '../layers/firms/model.js'; +import { createQueries } from '../layers/firms/queries.js'; +import { renderRadioState } from '../ui/radioPresentation.js'; + +test('Chinese HUD and cockpit units keep their case without spacing coordinates', () => { + try { + setLocale('zh-CN'); + assert.equal(hudQuantity(0, 'km'), '0 km'); + assert.equal(hudQuantity(2069, 'km', 1), '2069.0 km'); + assert.equal(formatCockpitDistance(14000), '14\u00a0km'); + assert.equal(LOCALIZED_LOCALITY.sector({ latDeg: 21.33, lonDeg: -77.5 }), + '扇区 21.33N 77.50W'); + setLocale('en'); + assert.equal(hudQuantity(0, 'km'), '0KM'); + assert.equal(hudQuantity(2069, 'km', 1), '2069.0KM'); + assert.equal(formatCockpitDistance(14000), '14\u00a0KM'); + } finally { + setLocale('en'); + } +}); + +test('FIRMS stale cache age uses localized durations while English stays compact', () => { + const now = Date.now(); + const layerState = { _stale: true }; + const components = { + model: createModel({ services: { anchors: {} } }), + }; + const { methods } = createQueries({ layerState, components, config: {} }); + try { + for (const [hours, zh, en] of [[0.5, '不到 1 小时', '<1h'], [2, '2 小时', '2h'], [72, '3 天', '3d']]) { + layerState._lastUpdate = now - hours * 3600000; + setLocale('zh-CN'); + const expected = t('STALE · cached {age}', { age: zh }); + assert.equal(methods.getStats().loadingLabel, expected); + assert.equal(methods.getStats().error, expected); + setLocale('en'); + assert.equal(methods.getStats().loadingLabel, `STALE · cached ${en}`); + } + } finally { + setLocale('en'); + } +}); + +test('Recent Imagery spaces numeric Chinese ages and retains natural relative dates', () => { + try { + setLocale('zh-CN'); + assert.equal(READOUT_WORDS.daysAgo(9), '9 天前'); + assert.equal(READOUT_WORDS.daysAgo(0), '今天'); + assert.equal(READOUT_WORDS.daysAgo(1), '昨天'); + setLocale('en'); + assert.equal(READOUT_WORDS.daysAgo(9), '9 days ago'); + assert.equal(READOUT_WORDS.daysAgo(0), 'today'); + } finally { + setLocale('en'); + } +}); + +test('CCTV translates the healthy status and preserves unknown provider codes', () => { + try { + for (const [locale, expected] of [['zh-CN', '正常'], ['ru', 'НОРМА'], ['es', 'OK'], ['en', 'OK']]) { + setLocale(locale); + assert.equal(cctvSourceStatusLabel('ok'), expected); + assert.equal(cctvSourceStatusLabel('CUSTOM'), 'CUSTOM'); + } + } finally { + setLocale('en'); + } +}); + +test('radio option text and accessible name use the same localized parentheses', () => { + const originalDocument = globalThis.document; + const element = () => ({ + style: {}, dataset: {}, attributes: {}, + classList: { contains: () => true, toggle() {} }, + setAttribute(name, value) { this.attributes[name] = value; }, + replaceChildren(...children) { this.children = children; }, + }); + globalThis.document = { createElement: element, getElementById: () => null }; + try { + for (const [locale, label, expected] of [['zh-CN', '全部', '全部(750)'], ['en', 'All', 'All (750)']]) { + setLocale(locale); + const host = { + _radioPanel: element(), _radioFilter: element(), + _syncContextRadioLauncherState() {}, + actions: { getLifecycle: () => null, scheduleLayout() {} }, + }; + renderRadioState.call(host, { + enabled: true, categories: [{ id: 'all', label, count: 750, color: '' }], + filter: 'all', filteredCount: 750, stationCount: 750, volume: 1, + }); + const option = host._radioFilter.children[0]; + assert.equal(option.textContent, `● ${expected}`); + assert.equal(option.attributes['aria-label'], expected); + } + } finally { + globalThis.document = originalDocument; + setLocale('en'); + } +}); diff --git a/src/layers/firms/model.js b/src/layers/firms/model.js index 3a1df327b..226c32286 100644 --- a/src/layers/firms/model.js +++ b/src/layers/firms/model.js @@ -591,6 +591,7 @@ export function createModel({ formatLatLon, formatFrp, formatAge, + formatAgeLabel, formatAgoMinutes, confidenceBucket, }; diff --git a/src/layers/firms/queries.js b/src/layers/firms/queries.js index a7d22589e..c4a793f9d 100644 --- a/src/layers/firms/queries.js +++ b/src/layers/firms/queries.js @@ -64,7 +64,8 @@ export function createQueries({ const staleText = layerState._lastUpdate ? t('STALE · cached {age}', { age: - components.model.formatAge(now - layerState._lastUpdate) || '<1h', + components.model.formatAgeLabel(now - layerState._lastUpdate) || + t('<1h', { context: 'age' }), }) : t('STALE'); let loadingLabel = ''; diff --git a/src/layers/recentImagery/display.js b/src/layers/recentImagery/display.js index 5b2b5ce39..5c18d8b3f 100644 --- a/src/layers/recentImagery/display.js +++ b/src/layers/recentImagery/display.js @@ -3,7 +3,12 @@ * day's readout. The model (`model.js`) stays English and i18n-free because AI * tool services import it outside the browser; this module is page-only. */ -import { formatDate, formatRelativeTime, t } from '../../i18n/index.js'; +import { + formatDate, + formatRelativeTime, + getLocale, + t, +} from '../../i18n/index.js'; import { formatQuantity } from '../../ui/unitLabels.js'; import { dayTime } from './model.js'; @@ -26,7 +31,10 @@ export const displayMetres = (value) => formatQuantity(value, 'm'); /** The readout's words in the page's language; the model keeps them English. */ export const READOUT_WORDS = Object.freeze({ date: (day) => displayDay(day, LONG_DAY), - daysAgo: (delta) => formatRelativeTime(delta > 0 ? -delta : 0, 'day'), + daysAgo: (delta) => { + const text = formatRelativeTime(delta > 0 ? -delta : 0, 'day'); + return getLocale() === 'zh-CN' ? text.replace(/(\d)(?=天)/g, '$1 ') : text; + }, // "Sentinel-2" never breaks at its hyphen: a word joiner follows it // (JetBrains Mono has no U+2011 non-breaking hyphen). sensor: (sensor) => diff --git a/src/ui/cctvFrames.js b/src/ui/cctvFrames.js index ee324e0ad..ac6687f54 100644 --- a/src/ui/cctvFrames.js +++ b/src/ui/cctvFrames.js @@ -20,6 +20,7 @@ const SOURCE_KIND_LABELS = Object.freeze({ }); const SOURCE_STATUS_LABELS = Object.freeze({ + ok: msg('OK'), degraded: msg('DEGRADED'), error: msg('ERROR'), unknown: msg('UNKNOWN'), @@ -52,7 +53,7 @@ export function cctvSourceKindLabel(kind) { : key.toUpperCase(); } -/** "DEGRADED" / "ПЕРЕБОИ"; OK and unknown codes stay as uppercase codes. */ +/** "DEGRADED" / "ПЕРЕБОИ"; unknown codes stay as uppercase codes. */ export function cctvSourceStatusLabel(status) { const key = String(status || 'unknown').toLowerCase(); return SOURCE_STATUS_LABELS[key] diff --git a/src/ui/cockpitContext.js b/src/ui/cockpitContext.js index 57db349ee..852cc32a8 100644 --- a/src/ui/cockpitContext.js +++ b/src/ui/cockpitContext.js @@ -1,6 +1,6 @@ /** Contact readouts and nearby-signal presentation for the Cockpit controller. */ import * as Cesium from 'cesium'; -import { formatNumber, t, tn } from '../i18n/index.js'; +import { formatNumber, getLocale, t, tn } from '../i18n/index.js'; import { unitLabel } from './unitLabels.js'; import { bearingBetweenCoordinates, @@ -15,7 +15,7 @@ const LOCALIZED_CONTEXT_SCOPE = Object.freeze({ window: ({ label, radiusKm }) => t('{label} · {radius} AIR/SEA WINDOW', { label, - radius: `${radiusKm === null ? '—' : formatNumber(radiusKm)}\u00a0${unitLabel('km').toUpperCase()}`, + radius: `${radiusKm === null ? '—' : formatNumber(radiusKm)}\u00a0${getLocale() === 'zh-CN' ? unitLabel('km') : unitLabel('km').toUpperCase()}`, }), installations: ({ scope, coverage }) => t('{scope} · INSTALLATIONS {coverage}', { scope, coverage }), diff --git a/src/ui/cockpitPresentation.js b/src/ui/cockpitPresentation.js index f8930d4e0..8285b74b3 100644 --- a/src/ui/cockpitPresentation.js +++ b/src/ui/cockpitPresentation.js @@ -1,5 +1,5 @@ /** Pure formatting and fixed presentation constants used by Cockpit components. */ -import { formatNumber, t, msg } from '../i18n/index.js'; +import { formatNumber, getLocale, t, msg } from '../i18n/index.js'; import { formatQuantity } from './unitLabels.js'; export const COCKPIT_HEADING_SLEW_DPS = 28; @@ -145,10 +145,11 @@ export function formatCockpitCoordinate(value, positive, negative) { */ export function formatCockpitDistance(distanceM) { const digits = distanceM < 10000 ? 1 : 0; - return formatQuantity(distanceM / 1000, 'km', { + const distance = formatQuantity(distanceM / 1000, 'km', { minimumFractionDigits: digits, maximumFractionDigits: digits, - }).toUpperCase(); + }); + return getLocale() === 'zh-CN' ? distance : distance.toUpperCase(); } export function formatCockpitWindDirection(value) { diff --git a/src/ui/localePicker.test.mjs b/src/ui/localePicker.test.mjs index 35b473e42..7f6d11f9b 100644 --- a/src/ui/localePicker.test.mjs +++ b/src/ui/localePicker.test.mjs @@ -40,7 +40,7 @@ test('the current language comes first, the rest by their own names', () => { nativeNames: LOCALE_NAMES, }), ), - ['ru', 'en', 'es'], + ['ru', 'en', 'es', 'zh-CN'], ); assert.deepEqual( codes( @@ -50,7 +50,7 @@ test('the current language comes first, the rest by their own names', () => { nativeNames: LOCALE_NAMES, }), ), - ['es', 'en', 'ru'], + ['es', 'en', 'ru', 'zh-CN'], ); assert.deepEqual( codes( @@ -60,7 +60,7 @@ test('the current language comes first, the rest by their own names', () => { nativeNames: LOCALE_NAMES, }), ), - ['en', 'es', 'ru'], + ['en', 'es', 'ru', 'zh-CN'], ); const rows = buildLocaleOptions({ locales: MANY, @@ -263,7 +263,7 @@ test('keyboard model: open on the current language, move, choose, close', () => assert.deepEqual(model.keydown({ key: 'ArrowDown' }), { handled: true }); assert.equal(model.activeOption.code, 'en'); model.keydown({ key: 'End' }); - assert.equal(model.activeOption.code, 'es'); + assert.equal(model.activeOption.code, 'zh-CN'); model.keydown({ key: 'Home' }); assert.equal(model.activeOption.code, 'ru'); model.keydown({ key: 'ArrowUp' }); diff --git a/src/ui/radioPresentation.js b/src/ui/radioPresentation.js index 94431fdec..54ee379e8 100644 --- a/src/ui/radioPresentation.js +++ b/src/ui/radioPresentation.js @@ -114,13 +114,14 @@ export function renderRadioState(state) { ...state.categories.map((category) => { const option = document.createElement('option'); option.value = category.id; - option.textContent = `● ${category.label} (${category.count})`; + const label = t('{category} ({count})', { + category: category.label, + count: category.count, + }); + option.textContent = `● ${label}`; option.dataset.radioColor = category.color; option.style.color = category.color; - option.setAttribute( - 'aria-label', - `${category.label} (${category.count})`, - ); + option.setAttribute('aria-label', label); return option; }), ); diff --git a/src/ui/styles/cjk.css b/src/ui/styles/cjk.css new file mode 100644 index 000000000..3c01d3987 --- /dev/null +++ b/src/ui/styles/cjk.css @@ -0,0 +1,51 @@ +/* Tight tracking keeps Chinese labels readable; preserve Latin tracking for + intel HUD readouts and content marked translate="no", including its containers. */ +html:lang(zh) + body + :not( + #intel-hud, + #intel-hud *, + [translate='no'], + [translate='no'] *, + :has([translate='no']) + ) { + letter-spacing: 0.02em !important; +} + +/* Keep small Chinese labels legible at a minimum of 9px. */ +html:lang(zh) #command-dock .gev-mic-label, +html:lang(zh) #command-dock #gev-voice-detail, +html:lang(zh) .gev-voice-tier-btn, +html:lang(zh) .panel-layer-toggle { + font-size: 9px; +} + +/* A two-glyph button label must not break between its glyphs. */ +html:lang(zh) .pp-mode-btn { + white-space: nowrap; +} + +/* Keep unit symbols in their catalog case, including the cockpit readouts. */ +html:lang(zh) #intel-hud *, +html:lang(zh) #cockpit-hud * { + text-transform: none; +} + +/* Move short travel modes (步行 / 骑行) to the next line as whole words. */ +html:lang(zh) [data-layer-id='directions'] .data-toggle-meta { + word-break: keep-all; + overflow-wrap: break-word; +} + +/* Leave room for the complete sensor name and resolution on two lines. */ +html:lang(zh) .recent-imagery-readout .ri-strip { + height: 131px; +} +html:lang(zh) .recent-imagery-readout .ri-card { + height: 123px; + grid-template-rows: 42px 13px 22px 11px 18px; +} +html:lang(zh) .recent-imagery-readout .ri-card-sensor { + white-space: normal; + text-overflow: clip; +} diff --git a/style.css b/style.css index 7f041cabe..0117150ce 100644 --- a/style.css +++ b/style.css @@ -32,4 +32,5 @@ @import './src/ui/styles/weather.css'; @import './src/ui/styles/panel-surfaces.css'; @import './src/ui/styles/segmented-control.css'; +@import './src/ui/styles/cjk.css'; @import './src/ui/styles/cyber.css';