mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
Merge remote-tracking branch 'origin/main' into ux/context-panel-cleanup
This commit is contained in:
commit
a370742e25
121 files changed
+20577
-547
No files matched your search
+77
-2
@@ -5,6 +5,7 @@ import {
|
||||
applyDocumentTranslations,
|
||||
formatDate,
|
||||
formatNumber,
|
||||
formatRelativeTime,
|
||||
msg,
|
||||
persistLocaleAndReload,
|
||||
setLocale,
|
||||
@@ -33,8 +34,23 @@ function memoryStorage(initial = {}) {
|
||||
test('normalizeLocale folds regional variants and rejects unshipped tags', () => {
|
||||
assert.equal(normalizeLocale('ru-RU'), 'ru');
|
||||
assert.equal(normalizeLocale(' RU '), 'ru');
|
||||
assert.equal(normalizeLocale('uk'), 'uk');
|
||||
assert.equal(normalizeLocale('uk-UA'), 'uk');
|
||||
assert.equal(normalizeLocale('en_GB'), 'en');
|
||||
assert.equal(normalizeLocale('de'), 'de');
|
||||
assert.equal(normalizeLocale('de-AT'), 'de');
|
||||
assert.equal(normalizeLocale('de_CH'), 'de');
|
||||
assert.equal(normalizeLocale('fr'), 'fr');
|
||||
assert.equal(normalizeLocale('fr-CA'), 'fr');
|
||||
assert.equal(normalizeLocale('FR_be'), 'fr');
|
||||
assert.equal(normalizeLocale('pt-BR'), 'pt');
|
||||
assert.equal(normalizeLocale('pt_PT'), 'pt');
|
||||
assert.equal(normalizeLocale('xx'), null);
|
||||
assert.equal(normalizeLocale('it-IT'), 'it');
|
||||
assert.equal(normalizeLocale('it_CH'), 'it');
|
||||
assert.equal(normalizeLocale('ja'), 'ja');
|
||||
assert.equal(normalizeLocale('ja-JP'), 'ja');
|
||||
assert.equal(normalizeLocale('JA_jp'), 'ja');
|
||||
// Explicit script tags take precedence over regional defaults.
|
||||
assert.equal(normalizeLocale('zh'), 'zh-CN');
|
||||
assert.equal(normalizeLocale('zh-CN'), 'zh-CN');
|
||||
@@ -68,7 +84,7 @@ test('resolveLocale: ?lang= wins, then the stored choice, then browser languages
|
||||
resolveLocale({
|
||||
location: { search: '?lang=xx' },
|
||||
storage: memoryStorage(),
|
||||
languages: ['de-DE', 'ru-RU'],
|
||||
languages: ['ko-KR', 'ru-RU'],
|
||||
}),
|
||||
'ru',
|
||||
);
|
||||
@@ -76,7 +92,15 @@ test('resolveLocale: ?lang= wins, then the stored choice, then browser languages
|
||||
resolveLocale({
|
||||
location: { search: '' },
|
||||
storage: memoryStorage(),
|
||||
languages: ['de'],
|
||||
languages: ['de-DE', 'ru-RU'],
|
||||
}),
|
||||
'de',
|
||||
);
|
||||
assert.equal(
|
||||
resolveLocale({
|
||||
location: { search: '' },
|
||||
storage: memoryStorage(),
|
||||
languages: ['ko'],
|
||||
}),
|
||||
'en',
|
||||
);
|
||||
@@ -142,6 +166,36 @@ test('tn() picks English and Russian plural forms', () => {
|
||||
assert.match(label(21), / 21 КЛЮЧ(?![А-ЯЁ])/);
|
||||
});
|
||||
|
||||
test('tn() picks the Portuguese one, many and other forms', () => {
|
||||
const label = (count) =>
|
||||
tn(
|
||||
count,
|
||||
'POWER UP · {count} KEY WAITING',
|
||||
'POWER UP · {count} KEYS WAITING',
|
||||
);
|
||||
setLocale('pt');
|
||||
assert.equal(label(0), 'ATIVAR · 0 CHAVE PENDENTE');
|
||||
assert.equal(label(1), 'ATIVAR · 1 CHAVE PENDENTE');
|
||||
assert.equal(label(3), 'ATIVAR · 3 CHAVES PENDENTES');
|
||||
assert.equal(label(1_000_000), 'ATIVAR · 1000000 DE CHAVES PENDENTES');
|
||||
});
|
||||
|
||||
test('tn() picks the four Ukrainian plural forms', () => {
|
||||
const label = (count) =>
|
||||
tn(
|
||||
count,
|
||||
'POWER UP · {count} KEY WAITING',
|
||||
'POWER UP · {count} KEYS WAITING',
|
||||
);
|
||||
setLocale('uk');
|
||||
assert.match(label(1), / 1 КЛЮЧ(?![А-ЯҐЄІЇ])/);
|
||||
assert.match(label(3), / 3 КЛЮЧІ(?![А-ЯҐЄІЇ])/);
|
||||
assert.match(label(5), / 5 КЛЮЧІВ(?![А-ЯҐЄІЇ])/);
|
||||
assert.match(label(11), / 11 КЛЮЧІВ(?![А-ЯҐЄІЇ])/);
|
||||
assert.match(label(21), / 21 КЛЮЧ(?![А-ЯҐЄІЇ])/);
|
||||
assert.match(label(1.5), / 1\.5 КЛЮЧА(?![А-ЯҐЄІЇ])/);
|
||||
});
|
||||
|
||||
test('t() context separates identical English with different meanings', () => {
|
||||
assert.equal(t('CLEAR', { context: 'action' }), 'CLEAR');
|
||||
setLocale('ru');
|
||||
@@ -164,7 +218,13 @@ test('Intl formatters follow the active locale', () => {
|
||||
assert.equal(formatNumber(1234.5), '1,234.5');
|
||||
setLocale('ru');
|
||||
assert.equal(formatDate(date, options), '15 января 2026 г.');
|
||||
assert.equal(formatRelativeTime(-5, 'minute'), '5 минут назад');
|
||||
assert.equal(formatNumber(1234.5), '1 234,5');
|
||||
// Japanese keeps a number and its unit together, as the catalog does.
|
||||
setLocale('ja');
|
||||
assert.equal(formatDate(date, options), '2026年1月15日');
|
||||
assert.equal(formatRelativeTime(-5, 'minute'), '5分前');
|
||||
assert.equal(formatRelativeTime(-3, 'day'), '3日前');
|
||||
});
|
||||
|
||||
function fakeElement({ text = '', attributes = {} } = {}) {
|
||||
@@ -200,6 +260,21 @@ test('applyDocumentTranslations translates opted-in text and attributes in place
|
||||
assert.equal(unknown.textContent, 'No such message');
|
||||
});
|
||||
|
||||
test('applyDocumentTranslations reads data-i18n-context for element text', () => {
|
||||
const option = fakeElement({
|
||||
text: 'All',
|
||||
attributes: { 'data-i18n-context': 'radio tag' },
|
||||
});
|
||||
const doc = {
|
||||
querySelectorAll: (selector) =>
|
||||
selector === '[data-i18n]' ? [option] : [],
|
||||
};
|
||||
setLocale('pt');
|
||||
applyDocumentTranslations(doc);
|
||||
assert.equal(option.textContent, t('All', { context: 'radio tag' }));
|
||||
assert.notEqual(option.textContent, t('All'));
|
||||
});
|
||||
|
||||
test('persistLocaleAndReload keeps the choice in ?lang when storage is blocked', () => {
|
||||
const calls = [];
|
||||
const location = {
|
||||
|
||||
+33
-5
@@ -16,12 +16,29 @@ import {
|
||||
normalizeLocale,
|
||||
writeStoredLocale,
|
||||
} from './locale.js';
|
||||
import de from './locales/de.json' with { type: 'json' };
|
||||
import es from './locales/es.json' with { type: 'json' };
|
||||
import fr from './locales/fr.json' with { type: 'json' };
|
||||
import it from './locales/it.json' with { type: 'json' };
|
||||
import ja from './locales/ja.json' with { type: 'json' };
|
||||
import pt from './locales/pt.json' with { type: 'json' };
|
||||
import ru from './locales/ru.json' with { type: 'json' };
|
||||
import uk from './locales/uk.json' with { type: 'json' };
|
||||
import zhCN from './locales/zh-CN.json' with { type: 'json' };
|
||||
import zhHant from './locales/zh-Hant.json' with { type: 'json' };
|
||||
|
||||
const CATALOGS = Object.freeze({ es, ru, 'zh-CN': zhCN, 'zh-Hant': zhHant });
|
||||
const CATALOGS = Object.freeze({
|
||||
de,
|
||||
es,
|
||||
fr,
|
||||
it,
|
||||
ja,
|
||||
pt,
|
||||
ru,
|
||||
uk,
|
||||
'zh-CN': zhCN,
|
||||
'zh-Hant': zhHant,
|
||||
});
|
||||
|
||||
let currentLocale = DEFAULT_LOCALE;
|
||||
|
||||
@@ -201,13 +218,19 @@ export function formatUtcDayMonth(value) {
|
||||
|
||||
/**
|
||||
* Locale-aware relative time (Intl.RelativeTimeFormat), e.g.
|
||||
* formatRelativeTime(-5, 'minute') → '5 minutes ago' / '5 минут назад'.
|
||||
* formatRelativeTime(-5, 'minute') → '5 minutes ago' / '5 минут назад' /
|
||||
* '5分前'.
|
||||
*/
|
||||
export function formatRelativeTime(value, unit = 'second', options) {
|
||||
return cachedFormatter('relative', Intl.RelativeTimeFormat, {
|
||||
const text = cachedFormatter('relative', Intl.RelativeTimeFormat, {
|
||||
numeric: 'auto',
|
||||
...options,
|
||||
}).format(value, unit);
|
||||
// CLDR spaces Japanese (5 分前); the ja catalog writes the number and its
|
||||
// unit together (5分前), so the two must not differ on one screen.
|
||||
return currentLocale === 'ja'
|
||||
? text.replace(/(?<=\d) (?=\p{Script=Han})/gu, '')
|
||||
: text;
|
||||
}
|
||||
|
||||
/** Collapse markup whitespace so a message reads the same in HTML and JS. */
|
||||
@@ -221,7 +244,9 @@ function normalizeMarkupText(text) {
|
||||
* Markup opt-in: the element's own English text or attribute is the source.
|
||||
* <span data-i18n>Normal</span>
|
||||
* <button title="Pin" data-i18n-title>…</button>
|
||||
* Also data-i18n-aria-label and data-i18n-placeholder.
|
||||
* Also data-i18n-aria-label and data-i18n-placeholder. data-i18n-context
|
||||
* gives the element's text a context, as t(text, { context }) does:
|
||||
* <option data-i18n data-i18n-context="radio tag">All</option>
|
||||
*/
|
||||
const DOCUMENT_TRANSLATION_TARGETS = Object.freeze([
|
||||
['data-i18n', null],
|
||||
@@ -246,7 +271,10 @@ export function applyDocumentTranslations(doc) {
|
||||
? element.getAttribute(attribute)
|
||||
: normalizeMarkupText(element.textContent);
|
||||
if (!source) continue;
|
||||
const text = t(source);
|
||||
const context = attribute
|
||||
? null
|
||||
: element.getAttribute?.('data-i18n-context');
|
||||
const text = context ? t(source, { context }) : t(source);
|
||||
if (text === source) continue;
|
||||
if (attribute) element.setAttribute(attribute, text);
|
||||
else element.textContent = text;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { setLocale } from './index.js';
|
||||
import {
|
||||
cockpitUnitCase,
|
||||
formatCockpitDistance,
|
||||
} from '../ui/cockpitPresentation.js';
|
||||
|
||||
test('Japanese cockpit distances keep the unit in its own case', () => {
|
||||
try {
|
||||
setLocale('ja');
|
||||
assert.equal(formatCockpitDistance(14000), '14 km');
|
||||
assert.equal(formatCockpitDistance(9500), '9.5 km');
|
||||
assert.equal(cockpitUnitCase('km'), 'km');
|
||||
setLocale('en');
|
||||
assert.equal(formatCockpitDistance(14000), '14 KM');
|
||||
assert.equal(cockpitUnitCase('km'), 'KM');
|
||||
} finally {
|
||||
setLocale('en');
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { setLocale } from './index.js';
|
||||
import { hudQuantity, LOCALIZED_LOCALITY } from '../hudLocalityFormat.js';
|
||||
import { formatCockpitDistance } from '../ui/cockpitPresentation.js';
|
||||
|
||||
// revmain-opus-ja-i1-1: ja has no case, so HUD units keep the catalog case
|
||||
// (km), as the cockpit already does and docs/translations/ja.md requires.
|
||||
test('Japanese HUD units keep their case, like the cockpit', () => {
|
||||
try {
|
||||
setLocale('ja');
|
||||
assert.equal(formatCockpitDistance(14000), '14 km');
|
||||
assert.equal(hudQuantity(13, 'km'), '13 km');
|
||||
assert.equal(hudQuantity(1.5, 'km', 1), '1.5 km');
|
||||
assert.equal(
|
||||
LOCALIZED_LOCALITY.near({ poi: 'Shibuya', city: 'Tokyo', distKm: 13 }),
|
||||
'SHIBUYA(東京)付近 13 km',
|
||||
);
|
||||
} finally {
|
||||
setLocale('en');
|
||||
}
|
||||
});
|
||||
+21
-1
@@ -8,13 +8,31 @@ 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', 'zh-CN', 'zh-Hant']);
|
||||
export const LOCALES = Object.freeze([
|
||||
'en',
|
||||
'de',
|
||||
'es',
|
||||
'fr',
|
||||
'it',
|
||||
'ja',
|
||||
'pt',
|
||||
'ru',
|
||||
'uk',
|
||||
'zh-CN',
|
||||
'zh-Hant',
|
||||
]);
|
||||
|
||||
/** Each locale named in its own language, for the selector. */
|
||||
export const LOCALE_NAMES = Object.freeze({
|
||||
en: 'English',
|
||||
de: 'Deutsch',
|
||||
es: 'Español',
|
||||
fr: 'Français',
|
||||
it: 'Italiano',
|
||||
ja: '日本語',
|
||||
pt: 'Português (Brasil)',
|
||||
ru: 'Русский',
|
||||
uk: 'Українська',
|
||||
'zh-CN': '中文(简体)',
|
||||
'zh-Hant': '中文(繁體)',
|
||||
});
|
||||
@@ -24,6 +42,8 @@ const RTL_LOCALES = Object.freeze([]);
|
||||
|
||||
/**
|
||||
* Map any language tag to a shipped locale: 'ru-RU' and 'RU' → 'ru',
|
||||
* 'de-AT' and 'de_CH' → 'de', 'ja-JP' → 'ja',
|
||||
* 'pt-BR' and 'pt-PT' → 'pt' (Brazilian Portuguese),
|
||||
* 'zh', 'zh-CN' and 'zh-Hans-SG' → 'zh-CN';
|
||||
* 'zh-Hant', 'zh-TW', 'zh-HK' and 'zh-MO' → 'zh-Hant'.
|
||||
* @returns {string|null}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -66,8 +66,6 @@
|
||||
"Anime": "Anime",
|
||||
"Apply high-contrast monochrome film-noir grading.": "Aplica una gradación monocroma de alto contraste estilo film noir.",
|
||||
"Noir": "Noir",
|
||||
"Add a cold, snowy whiteout treatment to the scene.": "Añade a la escena un tratamiento frío de ventisca blanca.",
|
||||
"Snow": "Nieve",
|
||||
"MAP SOURCE": "FUENTE DEL MAPA",
|
||||
"Map source": "Fuente del mapa",
|
||||
"Style": "Estilo",
|
||||
@@ -76,7 +74,6 @@
|
||||
"Layout": "Disposición",
|
||||
"HUD layout": "Disposición del HUD",
|
||||
"Tactical": "Táctica",
|
||||
"Operator": "De operador",
|
||||
"Minimal": "Mínima",
|
||||
"Detection Overlay (D)": "Superposición de detección (D)",
|
||||
"Detection overlay": "Superposición de detección",
|
||||
@@ -2217,6 +2214,8 @@
|
||||
"GOOGLE_MAPS_API_KEY is not set": "GOOGLE_MAPS_API_KEY no está configurada",
|
||||
"The Google Maps JavaScript API could not load": "No se pudo cargar la API de Maps JavaScript de Google",
|
||||
"Weather request timed out": "La petición del clima agotó el tiempo de espera",
|
||||
"Cyclone request timed out": "La petición de ciclones agotó el tiempo de espera",
|
||||
"Malformed cyclone snapshot": "Instantánea de ciclones mal formada",
|
||||
"Malformed weather manifest": "Manifiesto del clima mal formado",
|
||||
"Weather HTTP {status}": "Clima: HTTP {status}",
|
||||
"Weather image HTTP {status}": "Imagen del clima: HTTP {status}",
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -416,7 +416,6 @@
|
||||
"Thermal": "Тепловизор",
|
||||
"Anime": "Аниме",
|
||||
"Noir": "Нуар",
|
||||
"Snow": "Снег",
|
||||
"Current cockpit vision style: {style}. Activate for next style.": "Текущий стиль обзора кабины: {style}. Активируйте, чтобы переключить на следующий.",
|
||||
"Current style: {style} — click for next": "Текущий стиль: {style} — нажмите для следующего",
|
||||
"AIRCRAFT": "САМОЛЁТ",
|
||||
@@ -685,7 +684,6 @@
|
||||
"Simulate FLIR-style thermal contrast. Turn up Ironbow for color.": "Имитирует тепловой контраст в стиле FLIR. Для цвета увеличьте Ironbow.",
|
||||
"Apply bright cel-shaded color and illustrated outlines.": "Применяет яркие cel-shading-цвета и рисованные контуры.",
|
||||
"Apply high-contrast monochrome film-noir grading.": "Применяет высококонтрастную монохромную цветокоррекцию в стиле нуар.",
|
||||
"Add a cold, snowy whiteout treatment to the scene.": "Добавляет в сцену холодный снежный эффект «белой мглы».",
|
||||
"Expand LOCATION": "Развернуть панель «Место»",
|
||||
"Collapse panel": "Свернуть панель",
|
||||
"Pin location tray": "Закрепить панель места",
|
||||
@@ -767,7 +765,6 @@
|
||||
"Fade": "Затухание",
|
||||
"Outside": "Снаружи",
|
||||
"Tactical": "Тактический",
|
||||
"Operator": "Операторский",
|
||||
"Minimal": "Минимальный",
|
||||
"HUD layout": "Макет HUD",
|
||||
"Layout": "Макет",
|
||||
@@ -2253,6 +2250,8 @@
|
||||
"GOOGLE_MAPS_API_KEY is not set": "GOOGLE_MAPS_API_KEY не задан",
|
||||
"The Google Maps JavaScript API could not load": "Не удалось загрузить Google Maps JavaScript API",
|
||||
"Weather request timed out": "Истекло время ожидания запроса погоды",
|
||||
"Cyclone request timed out": "Истекло время ожидания запроса циклонов",
|
||||
"Malformed cyclone snapshot": "Некорректный снимок циклонов",
|
||||
"Malformed weather manifest": "Некорректный манифест погоды",
|
||||
"Weather HTTP {status}": "Погода: HTTP {status}",
|
||||
"Weather image HTTP {status}": "Снимок погоды: HTTP {status}",
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1203,6 +1203,8 @@
|
||||
"Some weather tiles unavailable": "部分天气瓦片不可用",
|
||||
"Malformed weather manifest": "天气清单格式错误",
|
||||
"Weather request timed out": "天气请求超时",
|
||||
"Cyclone request timed out": "气旋请求超时",
|
||||
"Malformed cyclone snapshot": "气旋快照格式错误",
|
||||
"Temperature field unavailable": "温度场不可用",
|
||||
"Mean sea level pressure field unavailable": "平均海平面气压场不可用",
|
||||
"Wind upstream unavailable": "风场上游数据源不可用",
|
||||
@@ -1624,7 +1626,6 @@
|
||||
"Thermal": "热成像",
|
||||
"Anime": "动漫",
|
||||
"Noir": "黑白",
|
||||
"Snow": "雪景",
|
||||
"Current cockpit vision style: {style}. Activate for next style.": "当前座舱视觉样式:{style}。点击可切换到下一个样式。",
|
||||
"Current style: {style} — click for next": "当前样式:{style}——点击切换到下一个",
|
||||
"Collapse Contact panel": "收起目标面板",
|
||||
@@ -2528,7 +2529,6 @@
|
||||
"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": "样式",
|
||||
@@ -2604,7 +2604,6 @@
|
||||
"Layout": "布局",
|
||||
"HUD layout": "HUD 布局",
|
||||
"Tactical": "战术",
|
||||
"Operator": "操作员",
|
||||
"Minimal": "精简",
|
||||
"Cyber": "赛博",
|
||||
"Sonar": "声呐",
|
||||
|
||||
@@ -1198,6 +1198,8 @@
|
||||
"Some weather tiles unavailable": "部分天氣瓦片不可用",
|
||||
"Malformed weather manifest": "天氣清單格式錯誤",
|
||||
"Weather request timed out": "天氣請求超時",
|
||||
"Cyclone request timed out": "氣旋請求超時",
|
||||
"Malformed cyclone snapshot": "氣旋快照格式錯誤",
|
||||
"Temperature field unavailable": "溫度場不可用",
|
||||
"Mean sea level pressure field unavailable": "平均海平面氣壓場不可用",
|
||||
"Wind upstream unavailable": "風場上游數據源不可用",
|
||||
@@ -1614,7 +1616,6 @@
|
||||
"Thermal": "熱成像",
|
||||
"Anime": "動漫",
|
||||
"Noir": "黑白",
|
||||
"Snow": "雪景",
|
||||
"Current cockpit vision style: {style}. Activate for next style.": "當前座艙視覺樣式:{style}。點擊可切換到下一個樣式。",
|
||||
"Current style: {style} — click for next": "當前樣式:{style}——點擊切換到下一個",
|
||||
"Collapse Contact panel": "收起目標面板",
|
||||
@@ -2516,7 +2517,6 @@
|
||||
"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": "樣式",
|
||||
@@ -2592,7 +2592,6 @@
|
||||
"Layout": "佈局",
|
||||
"HUD layout": "HUD 佈局",
|
||||
"Tactical": "戰術",
|
||||
"Operator": "操作員",
|
||||
"Minimal": "精簡",
|
||||
"Cyber": "賽博",
|
||||
"Sonar": "聲吶",
|
||||
|
||||
@@ -1,4 +1,170 @@
|
||||
{
|
||||
"de": [
|
||||
"1090 MHz",
|
||||
"978 MHz UAT",
|
||||
"AIS: --",
|
||||
"AIS: {name}",
|
||||
"ANIME",
|
||||
"AUTO",
|
||||
"Alternative",
|
||||
"Ambient",
|
||||
"Anime",
|
||||
"Austin",
|
||||
"BRG {value}°",
|
||||
"BRG —",
|
||||
"Bloom",
|
||||
"Blues",
|
||||
"Bus",
|
||||
"CCTV",
|
||||
"COCKPIT",
|
||||
"CRS {value}°",
|
||||
"CRT",
|
||||
"Country",
|
||||
"Cyan",
|
||||
"Cyber",
|
||||
"DEST ---°",
|
||||
"DEST {bearing}",
|
||||
"Dance",
|
||||
"Details",
|
||||
"Dubai",
|
||||
"EXPORT",
|
||||
"EXPORT {slot}",
|
||||
"FALLBACK",
|
||||
"FILTER",
|
||||
"FLIR",
|
||||
"FOV",
|
||||
"FOV {value}°",
|
||||
"FT",
|
||||
"Folk",
|
||||
"Funk",
|
||||
"GEO",
|
||||
"GOOGLE STREET VIEW",
|
||||
"Global",
|
||||
"Global · 60°S–60°N",
|
||||
"Google Street View",
|
||||
"HDG",
|
||||
"HDG {value}°",
|
||||
"Hip-Hop",
|
||||
"House",
|
||||
"Hybrid",
|
||||
"Indie",
|
||||
"Ironbow",
|
||||
"Jazz",
|
||||
"LIVE",
|
||||
"Landsat 8/9",
|
||||
"Latin",
|
||||
"Layout",
|
||||
"London",
|
||||
"Los Angeles",
|
||||
"MIL",
|
||||
"MINI",
|
||||
"MISSION",
|
||||
"MM",
|
||||
"MONITOR",
|
||||
"Metal",
|
||||
"Minimal",
|
||||
"NAME",
|
||||
"NASA FIRMS · LIVE",
|
||||
"NAV",
|
||||
"NEWS",
|
||||
"NNW",
|
||||
"NOIR",
|
||||
"NORMAL",
|
||||
"NVG",
|
||||
"NW",
|
||||
"New York",
|
||||
"Noir",
|
||||
"Normal",
|
||||
"OK",
|
||||
"ORBIT",
|
||||
"ORBIT · ",
|
||||
"Oldies",
|
||||
"OpenStreetMap + optional Google Maps Places",
|
||||
"Operator",
|
||||
"PAUSE",
|
||||
"PLAY",
|
||||
"POS: LIVE",
|
||||
"POS: {time}Z",
|
||||
"POSITION {age}",
|
||||
"PROJ MONITOR",
|
||||
"Paris",
|
||||
"Pause",
|
||||
"Pin",
|
||||
"Plan",
|
||||
"Pop",
|
||||
"Punk",
|
||||
"RADIO",
|
||||
"REC",
|
||||
"REGION",
|
||||
"RESET",
|
||||
"Radio",
|
||||
"Reggae",
|
||||
"Rock",
|
||||
"S2 GhostMaps",
|
||||
"SAR",
|
||||
"SIG",
|
||||
"SNAPSHOT",
|
||||
"SQUAWK {code} · {status}",
|
||||
"SSW",
|
||||
"STANDARD",
|
||||
"START",
|
||||
"STATION",
|
||||
"STATUS",
|
||||
"STATUS · ",
|
||||
"STD",
|
||||
"SW",
|
||||
"SYNC",
|
||||
"San Francisco",
|
||||
"Scanlines",
|
||||
"Scope",
|
||||
"Sentinel-2",
|
||||
"Sonar",
|
||||
"Soul",
|
||||
"Station",
|
||||
"Station {id}",
|
||||
"T minus {seconds}",
|
||||
"TAGS · {tags}",
|
||||
"TANKER",
|
||||
"TEMP",
|
||||
"Talk",
|
||||
"Tallinn",
|
||||
"Techno",
|
||||
"Trance",
|
||||
"Turboprop",
|
||||
"UPSTREAM",
|
||||
"Upstream HTTP {status}",
|
||||
"Vignette",
|
||||
"WHOT/BHOT",
|
||||
"WIND",
|
||||
"WNW",
|
||||
"WSW",
|
||||
"Washington DC",
|
||||
"Wind",
|
||||
"dB",
|
||||
"feed status|live",
|
||||
"fire behavior|Minimal",
|
||||
"ft",
|
||||
"hPa",
|
||||
"km",
|
||||
"km/h",
|
||||
"min",
|
||||
"mph",
|
||||
"radio|NEWS",
|
||||
"radio|TALK",
|
||||
"view band|GLOBAL",
|
||||
"{city} CCTV",
|
||||
"{county} County",
|
||||
"{source} HTTP {status}",
|
||||
"{speed} kn",
|
||||
"{type} SPD: {speed} HDG: {heading}",
|
||||
"{value} KM",
|
||||
"{value} KM DOWNRANGE",
|
||||
"{value} KM/H",
|
||||
"{value} KM/S",
|
||||
"ΔN",
|
||||
"Ⅱ PAUSE",
|
||||
"▲ {frp} MW"
|
||||
],
|
||||
"es": [
|
||||
"1090 MHz",
|
||||
"978 MHz UAT",
|
||||
@@ -124,6 +290,531 @@
|
||||
"ΔN",
|
||||
"▲ {frp} MW"
|
||||
],
|
||||
"fr": [
|
||||
"1090 MHz",
|
||||
"978 MHz UAT",
|
||||
"ALTITUDE",
|
||||
"ALTITUDE · FT",
|
||||
"ANIME",
|
||||
"AUTO",
|
||||
"Action",
|
||||
"Allocation",
|
||||
"Altitude {altitude}",
|
||||
"Ambient",
|
||||
"Anime",
|
||||
"Austin",
|
||||
"Aviation / Marine",
|
||||
"BRG {value}°",
|
||||
"BRG —",
|
||||
"Bloom",
|
||||
"Blues",
|
||||
"Bus",
|
||||
"CAL {value}",
|
||||
"CAL · {badge}",
|
||||
"CALIBRATION",
|
||||
"CARGO",
|
||||
"CAUSE",
|
||||
"CCTV",
|
||||
"CCTV OFF",
|
||||
"CCTV ON",
|
||||
"COCKPIT",
|
||||
"COMMERCIAL",
|
||||
"CONTACT",
|
||||
"CONTACTS",
|
||||
"CONTACTS · 250 KM",
|
||||
"CRS {value}°",
|
||||
"CRT",
|
||||
"Country",
|
||||
"Cyan",
|
||||
"Cyber",
|
||||
"Cyclones · NHC / CPHC",
|
||||
"DENSE",
|
||||
"DENSE ···",
|
||||
"DENSE ✕",
|
||||
"DEST ---°",
|
||||
"DEST {bearing}",
|
||||
"DESTINATION",
|
||||
"Dance",
|
||||
"Drone",
|
||||
"ENE",
|
||||
"ESE",
|
||||
"EUROPE",
|
||||
"FLIR",
|
||||
"FM MHZ",
|
||||
"FOV",
|
||||
"FOV {value}°",
|
||||
"FT",
|
||||
"Ferry",
|
||||
"Folk",
|
||||
"Funk",
|
||||
"GAIN",
|
||||
"GEO",
|
||||
"GOOGLE STREET VIEW",
|
||||
"Gain",
|
||||
"Google Street View",
|
||||
"Grain",
|
||||
"HDG",
|
||||
"HDG {value}°",
|
||||
"Hip-Hop",
|
||||
"House",
|
||||
"IMAGE",
|
||||
"Indie",
|
||||
"Ironbow",
|
||||
"Jazz",
|
||||
"LOCAL",
|
||||
"Landsat 8/9",
|
||||
"Landsat 8/9 via HLS",
|
||||
"Los Angeles",
|
||||
"MIC",
|
||||
"MIL",
|
||||
"MINI",
|
||||
"MISSION",
|
||||
"MM",
|
||||
"MSG/S",
|
||||
"Metal",
|
||||
"Minimal",
|
||||
"Minutes",
|
||||
"Mode",
|
||||
"NAV",
|
||||
"NE",
|
||||
"NNE",
|
||||
"NOIR",
|
||||
"NORMAL",
|
||||
"NVG",
|
||||
"New York",
|
||||
"Noir",
|
||||
"Normal",
|
||||
"OFF",
|
||||
"OK",
|
||||
"ON",
|
||||
"ON/OFF",
|
||||
"Oldies",
|
||||
"PAUSE",
|
||||
"POSITION {age}",
|
||||
"PROJ OFF",
|
||||
"PROJECTION OFF",
|
||||
"PROJECTION ON",
|
||||
"PROPAGATION",
|
||||
"Paris",
|
||||
"Pause",
|
||||
"Plan",
|
||||
"Pop",
|
||||
"Punk",
|
||||
"RADIO",
|
||||
"REC",
|
||||
"Radio",
|
||||
"Reggae",
|
||||
"Rock",
|
||||
"S2 GhostMaps",
|
||||
"SAR",
|
||||
"SE",
|
||||
"SIG",
|
||||
"SOURCE",
|
||||
"SQUAWK {code} · {status}",
|
||||
"SRC {value}",
|
||||
"SSE",
|
||||
"STANDARD",
|
||||
"STATION",
|
||||
"STD",
|
||||
"San Francisco",
|
||||
"Satellites",
|
||||
"Saturation",
|
||||
"Sonar",
|
||||
"Soul",
|
||||
"Source",
|
||||
"Station",
|
||||
"Station {id}",
|
||||
"Style",
|
||||
"TAGS · {tags}",
|
||||
"TEMP",
|
||||
"TYPE",
|
||||
"Tallinn",
|
||||
"Techno",
|
||||
"Tokyo",
|
||||
"Train",
|
||||
"Trance",
|
||||
"USB {rate} msg/s",
|
||||
"VOLUME",
|
||||
"VS",
|
||||
"WHOT/BHOT",
|
||||
"audio {state}",
|
||||
"dB",
|
||||
"ft",
|
||||
"hPa",
|
||||
"image",
|
||||
"installation class|BASE",
|
||||
"installation class|INSTALLATION",
|
||||
"km",
|
||||
"km/h",
|
||||
"max {value} MW",
|
||||
"min",
|
||||
"mini",
|
||||
"mph",
|
||||
"nominal",
|
||||
"satellite",
|
||||
"standard",
|
||||
"station",
|
||||
"voice|OFF",
|
||||
"{count} MSGS",
|
||||
"{count} SOURCES",
|
||||
"{count} messages",
|
||||
"{n} cyclones",
|
||||
"{n} satellites",
|
||||
"{rate} msg/s",
|
||||
"{value} KM",
|
||||
"{value} KM/H",
|
||||
"{value} KM/S",
|
||||
"ΔE",
|
||||
"ΔN",
|
||||
"Ⅱ PAUSE",
|
||||
"▲ {frp} MW"
|
||||
],
|
||||
"it": [
|
||||
"1090 MHz",
|
||||
"978 MHz UAT",
|
||||
"AFRICA",
|
||||
"AIS: --",
|
||||
"AIS: {name}",
|
||||
"ALT {altitude} FT · GS {speed} KT · TRK {track}",
|
||||
"ANIME",
|
||||
"ASIA",
|
||||
"AUTO",
|
||||
"AUTO HOP OFF",
|
||||
"AUTO HOP ON",
|
||||
"Alternative",
|
||||
"Ambient",
|
||||
"Anime",
|
||||
"Area",
|
||||
"Austin",
|
||||
"BRG {value}°",
|
||||
"BRG —",
|
||||
"Bloom",
|
||||
"Blues",
|
||||
"Business jet",
|
||||
"CAL {value}",
|
||||
"CAL · {badge}",
|
||||
"CARGO",
|
||||
"CCTV",
|
||||
"CCTV OFF",
|
||||
"CCTV ON",
|
||||
"CH {index} / {total}",
|
||||
"COCKPIT",
|
||||
"CRS {value}°",
|
||||
"CRT",
|
||||
"Country",
|
||||
"Cyber",
|
||||
"DEST ---°",
|
||||
"DEST {bearing}",
|
||||
"DISPLAY",
|
||||
"Dance",
|
||||
"Drone",
|
||||
"Dubai",
|
||||
"ENE",
|
||||
"ESE",
|
||||
"FLIR",
|
||||
"FM MHZ",
|
||||
"FOCUS",
|
||||
"FOV",
|
||||
"FOV {value}°",
|
||||
"FT",
|
||||
"Folk",
|
||||
"Funk",
|
||||
"GEO",
|
||||
"GOOGLE STREET VIEW",
|
||||
"Google Street View",
|
||||
"HDG",
|
||||
"HDG {value}°",
|
||||
"Hip-Hop",
|
||||
"House",
|
||||
"Indie",
|
||||
"Ironbow",
|
||||
"Jazz",
|
||||
"KTS",
|
||||
"LIVE",
|
||||
"Landsat 8/9",
|
||||
"Layout",
|
||||
"Los Angeles",
|
||||
"MIC",
|
||||
"MIL",
|
||||
"MINI",
|
||||
"MM",
|
||||
"MONITOR",
|
||||
"MSG/S",
|
||||
"Metal",
|
||||
"NASA FIRMS · LIVE",
|
||||
"NAV",
|
||||
"NE",
|
||||
"NNE",
|
||||
"NOIR",
|
||||
"NVG",
|
||||
"New York",
|
||||
"Noir",
|
||||
"OCEANIA",
|
||||
"OFF",
|
||||
"OK",
|
||||
"ON",
|
||||
"ON/OFF",
|
||||
"Oldies",
|
||||
"PAYLOAD",
|
||||
"PLAY",
|
||||
"POS: LIVE",
|
||||
"POS: {time}Z",
|
||||
"PRECIP",
|
||||
"Pop",
|
||||
"Punk",
|
||||
"RADIO",
|
||||
"REC",
|
||||
"RESET",
|
||||
"RESET CAL",
|
||||
"Radio",
|
||||
"Reggae",
|
||||
"Rock",
|
||||
"S2 GhostMaps",
|
||||
"SAR",
|
||||
"SE",
|
||||
"SIG",
|
||||
"SNAPSHOT",
|
||||
"SQUAWK {code} · {status}",
|
||||
"SSE",
|
||||
"STANDARD",
|
||||
"STD",
|
||||
"STOP",
|
||||
"SYNC",
|
||||
"San Francisco",
|
||||
"Scope",
|
||||
"Sentinel-2",
|
||||
"Sonar",
|
||||
"Soul",
|
||||
"TEMP",
|
||||
"TIMEOUT",
|
||||
"Talk",
|
||||
"Tallinn",
|
||||
"Techno",
|
||||
"Tokyo",
|
||||
"Tram",
|
||||
"Trance",
|
||||
"UPSTREAM",
|
||||
"USB {rate} msg/s",
|
||||
"Upstream HTTP {status}",
|
||||
"V/S {rate} FPM",
|
||||
"VIEWSHED",
|
||||
"VIEWSHED ON",
|
||||
"VOLUME",
|
||||
"VS",
|
||||
"WHOT/BHOT",
|
||||
"Washington DC",
|
||||
"World",
|
||||
"audio {state}",
|
||||
"dB",
|
||||
"feed status|live",
|
||||
"ft",
|
||||
"hPa",
|
||||
"installation class|BASE",
|
||||
"km",
|
||||
"km/h",
|
||||
"max {value} MW",
|
||||
"min",
|
||||
"mini",
|
||||
"mph",
|
||||
"radio|TALK",
|
||||
"satellite",
|
||||
"standard",
|
||||
"view band|METRO",
|
||||
"voice|OFF",
|
||||
"{count} MSGS",
|
||||
"{count} feeds live",
|
||||
"{count} scenes",
|
||||
"{rate} msg/s",
|
||||
"{scene} / {pack}: {status}. File: {path}. {attribution} · {license}",
|
||||
"{source} HTTP {status}",
|
||||
"{speed} kn",
|
||||
"{speed}KT",
|
||||
"{value} KM",
|
||||
"{value} KM DOWNRANGE",
|
||||
"{value} KM/H",
|
||||
"{value} KM/S",
|
||||
"{value} conf",
|
||||
"ΔE",
|
||||
"ΔN",
|
||||
"▲ {frp} MW"
|
||||
],
|
||||
"ja": [
|
||||
"1090 MHz",
|
||||
"978 MHz UAT",
|
||||
"AIS: --",
|
||||
"AIS: {name}",
|
||||
"BRG {value}°",
|
||||
"BRG —",
|
||||
"CAL {value}",
|
||||
"CAL · {badge}",
|
||||
"CCTV",
|
||||
"CH {index} / {total}",
|
||||
"CRS {value}°",
|
||||
"CRT",
|
||||
"FLIR",
|
||||
"FLT",
|
||||
"FM MHZ",
|
||||
"FOV",
|
||||
"FOV {value}°",
|
||||
"FT",
|
||||
"GEO",
|
||||
"HDG",
|
||||
"HDG {value}°",
|
||||
"Ironbow",
|
||||
"Landsat 8/9",
|
||||
"MSG/S",
|
||||
"NVG",
|
||||
"POS: {time}Z",
|
||||
"REC",
|
||||
"S2 GhostMaps",
|
||||
"SRC {value}",
|
||||
"Sentinel-2",
|
||||
"USB {rate} msg/s",
|
||||
"WHOT/BHOT",
|
||||
"dB",
|
||||
"ft",
|
||||
"hPa",
|
||||
"km",
|
||||
"km/h",
|
||||
"kt",
|
||||
"mph",
|
||||
"{city} CCTV",
|
||||
"{rate} msg/s",
|
||||
"{source} HTTP {status}",
|
||||
"{speed} kn",
|
||||
"{type} SPD: {speed} HDG: {heading}",
|
||||
"ΔE",
|
||||
"ΔN",
|
||||
"▲ {frp} MW"
|
||||
],
|
||||
"pt": [
|
||||
"1090 MHz",
|
||||
"978 MHz UAT",
|
||||
"AIS: --",
|
||||
"AIS: {name}",
|
||||
"ALT {altitude} FT · GS {speed} KT · TRK {track}",
|
||||
"ALTITUDE",
|
||||
"ALTITUDE · FT",
|
||||
"ANIME",
|
||||
"AUTO",
|
||||
"Altitude {altitude}",
|
||||
"Anime",
|
||||
"Austin",
|
||||
"BIKE",
|
||||
"BRG {value}°",
|
||||
"BRG —",
|
||||
"Bloom",
|
||||
"Blues",
|
||||
"CAL {value}",
|
||||
"CAL · {badge}",
|
||||
"CCTV",
|
||||
"CRS {value}°",
|
||||
"CRT",
|
||||
"Country",
|
||||
"DEST ---°",
|
||||
"DEST {bearing}",
|
||||
"Dance",
|
||||
"Drone",
|
||||
"Dubai",
|
||||
"ENE",
|
||||
"ESE",
|
||||
"FLIR",
|
||||
"FLT",
|
||||
"FM MHZ",
|
||||
"FOV",
|
||||
"FOV {value}°",
|
||||
"FT",
|
||||
"Folk",
|
||||
"Funk",
|
||||
"GEO",
|
||||
"GOOGLE STREET VIEW",
|
||||
"Global",
|
||||
"Global · 60°S–60°N",
|
||||
"Google Street View",
|
||||
"HDG",
|
||||
"HDG {value}°",
|
||||
"Hip-Hop",
|
||||
"House",
|
||||
"Indie",
|
||||
"Ironbow",
|
||||
"Jazz",
|
||||
"LOCAL",
|
||||
"Landsat 8/9",
|
||||
"Landsat 8/9 via HLS",
|
||||
"Layout",
|
||||
"Los Angeles",
|
||||
"MIC",
|
||||
"MIL",
|
||||
"MINI",
|
||||
"MM",
|
||||
"MONITOR",
|
||||
"MSG/S",
|
||||
"Metal",
|
||||
"NAV",
|
||||
"NE",
|
||||
"NNE",
|
||||
"NOIR",
|
||||
"NORMAL",
|
||||
"NVG",
|
||||
"Noir",
|
||||
"Normal",
|
||||
"OCEANIA",
|
||||
"OK",
|
||||
"POS: {time}Z",
|
||||
"PRECIP",
|
||||
"Paris",
|
||||
"Pop",
|
||||
"Punk",
|
||||
"RANGE",
|
||||
"REC",
|
||||
"Reggae",
|
||||
"Rock",
|
||||
"S2 GhostMaps",
|
||||
"SAR",
|
||||
"SE",
|
||||
"SIG",
|
||||
"SNAPSHOT",
|
||||
"SQUAWK {code} · {status}",
|
||||
"SSE",
|
||||
"STATUS",
|
||||
"STATUS · ",
|
||||
"Sentinel-2",
|
||||
"Sentinel-2 via HLS",
|
||||
"Sonar",
|
||||
"Soul",
|
||||
"TAGS · {tags}",
|
||||
"TEMP",
|
||||
"Tallinn",
|
||||
"Techno",
|
||||
"Trance",
|
||||
"USB {rate} msg/s",
|
||||
"V/S {rate}",
|
||||
"VISUAL",
|
||||
"VOLUME",
|
||||
"VS",
|
||||
"WHOT/BHOT",
|
||||
"WX",
|
||||
"dB",
|
||||
"fire confidence|nominal",
|
||||
"ft",
|
||||
"hPa",
|
||||
"installation class|BASE",
|
||||
"km",
|
||||
"km/h",
|
||||
"kt",
|
||||
"min",
|
||||
"mini",
|
||||
"mph",
|
||||
"nominal",
|
||||
"view band|GLOBAL",
|
||||
"{rate} msg/s",
|
||||
"{type} SPD: {speed} HDG: {heading}",
|
||||
"{value} KM",
|
||||
"{value} KM/H",
|
||||
"{value} KM/S",
|
||||
"ΔN",
|
||||
"▲ {frp} MW"
|
||||
],
|
||||
"ru": [
|
||||
"AIS: --",
|
||||
"AIS: {name}",
|
||||
@@ -142,6 +833,19 @@
|
||||
"VS",
|
||||
"WHOT/BHOT"
|
||||
],
|
||||
"uk": [
|
||||
"AIS: --",
|
||||
"AIS: {name}",
|
||||
"CCTV",
|
||||
"CRT",
|
||||
"FLIR",
|
||||
"Ironbow",
|
||||
"Landsat 8/9",
|
||||
"NVG",
|
||||
"REC",
|
||||
"S2 GhostMaps",
|
||||
"WHOT/BHOT"
|
||||
],
|
||||
"zh-CN": [
|
||||
"1090 MHz",
|
||||
"978 MHz UAT",
|
||||
|
||||
@@ -17,6 +17,9 @@ test('Chinese HUD and cockpit units keep their case without spacing coordinates'
|
||||
assert.equal(formatCockpitDistance(14000), '14\u00a0km');
|
||||
assert.equal(LOCALIZED_LOCALITY.sector({ latDeg: 21.33, lonDeg: -77.5 }),
|
||||
'扇区 21.33N 77.50W');
|
||||
setLocale('ja');
|
||||
assert.equal(hudQuantity(13, 'km'), '13 km');
|
||||
assert.equal(hudQuantity(1.5, 'km', 1), '1.5 km');
|
||||
setLocale('en');
|
||||
assert.equal(hudQuantity(0, 'km'), '0KM');
|
||||
assert.equal(hudQuantity(2069, 'km', 1), '2069.0KM');
|
||||
|
||||
Reference in new issue
Block a user