mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
Merge branch 'main' into fix/remove-snow-preset
This commit is contained in:
23 files changed
+3467
-28
No files matched your search
@@ -0,0 +1,4 @@
|
|||||||
|
- Add Brazilian Portuguese (pt) to the language picker, with every screen
|
||||||
|
translated and Portuguese plural forms. The CCTV summary line now names its
|
||||||
|
calibration, source and view in the page language. Based on translations by
|
||||||
|
@abiyeenzo (#909) (German, @gekh, #1475).
|
||||||
@@ -3784,7 +3784,7 @@ test:track` 43 tracking invariants · headless QA harnesses under
|
|||||||
|
|
||||||
### Interface localization (October 2026)
|
### Interface localization (October 2026)
|
||||||
|
|
||||||
Shipped locales: **en** (source and fallback), **es**, **it**, **ru**, **uk**, **zh-CN** and **zh-Hant**. The code lives in
|
Shipped locales: **en** (source and fallback), **es**, **it**, **pt** (Brazilian Portuguese), **ru**, **uk**, **zh-CN** and **zh-Hant**. The code lives in
|
||||||
`src/i18n/`. How to mark UI text: [`docs/I18N.md`](./I18N.md); translator workflow:
|
`src/i18n/`. How to mark UI text: [`docs/I18N.md`](./I18N.md); translator workflow:
|
||||||
[`docs/TRANSLATORS.md`](./TRANSLATORS.md).
|
[`docs/TRANSLATORS.md`](./TRANSLATORS.md).
|
||||||
|
|
||||||
@@ -3795,7 +3795,7 @@ Shipped locales: **en** (source and fallback), **es**, **it**, **ru**, **uk**, *
|
|||||||
value). English has no catalog; every other locale is
|
value). English has no catalog; every other locale is
|
||||||
`src/i18n/locales/<code>.json`, and a missing entry falls back to English.
|
`src/i18n/locales/<code>.json`, and a missing entry falls back to English.
|
||||||
- **Locale choice.** `?lang=` → stored choice (`gev:locale:v1`) →
|
- **Locale choice.** `?lang=` → stored choice (`gev:locale:v1`) →
|
||||||
`navigator.languages` → English. Chinese tags default to `zh-CN`; Traditional Chinese tags
|
`navigator.languages` → English. Any `pt` tag (`pt-BR`, `pt-PT`) selects `pt`. Chinese tags default to `zh-CN`; Traditional Chinese tags
|
||||||
select `zh-Hant`. Explicit `Hans` selects `zh-CN` regardless of region. `?lang=` is not stored, and share links drop
|
select `zh-Hant`. Explicit `Hans` selects `zh-CN` regardless of region. `?lang=` is not stored, and share links drop
|
||||||
it, so a recipient keeps their own language. The globe picker in the
|
it, so a recipient keeps their own language. The globe picker in the
|
||||||
bottom-right corner (`src/ui/localePicker.js`) stores the choice, drops
|
bottom-right corner (`src/ui/localePicker.js`) stores the choice, drops
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ A module the server, tools or other portable code also imports must not load the
|
|||||||
<input placeholder="Search any location..." data-i18n-placeholder />
|
<input placeholder="Search any location..." data-i18n-placeholder />
|
||||||
```
|
```
|
||||||
|
|
||||||
`data-i18n` replaces the element's text, so put it on the element that directly holds the text, never on a parent with child elements. `data-i18n-title`, `data-i18n-aria-label` and `data-i18n-placeholder` translate that attribute. Markup is translated once at boot, and switching language reloads the page. A module that holds work a reload would lose (an unsaved edit, an open session) registers it with `registerUnsavedWork(() => busy ? msg('unsaved …') : null)` from `src/i18n/index.js`, and the language picker asks before reloading.
|
`data-i18n` replaces the element's text, so put it on the element that directly holds the text, never on a parent with child elements. `data-i18n-title`, `data-i18n-aria-label` and `data-i18n-placeholder` translate that attribute. `data-i18n-context="radio tag"` gives the element's text a context, the catalog entry `radio tag|All`, as `t('All', { context: 'radio tag' })` does in code. Markup is translated once at boot, and switching language reloads the page. A module that holds work a reload would lose (an unsaved edit, an open session) registers it with `registerUnsavedWork(() => busy ? msg('unsaved …') : null)` from `src/i18n/index.js`, and the language picker asks before reloading.
|
||||||
|
|
||||||
## Rules
|
## Rules
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
# Translating God's Eye View
|
# Translating God's Eye View
|
||||||
|
|
||||||
Shipped languages: **English** (source and fallback), **Spanish** (`es`), **Italian** (`it`), **Russian** (`ru`), **Ukrainian** (`uk`), **Simplified Chinese** (`zh-CN`) and **Traditional Chinese** (`zh-Hant`).
|
Shipped languages: **English** (source and fallback), **Spanish** (`es`), **Italian** (`it`), **Brazilian Portuguese** (`pt`), **Russian** (`ru`), **Ukrainian** (`uk`), **Simplified Chinese** (`zh-CN`) and **Traditional Chinese** (`zh-Hant`).
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
@@ -63,4 +63,4 @@ These are contracts, not copy: layer ids and registry keys, status enums and oth
|
|||||||
|
|
||||||
## Language notes
|
## Language notes
|
||||||
|
|
||||||
Each language keeps its glossary, plural forms, units and style in its own file, `docs/translations/<code>.md`: [Spanish](./translations/es.md), [Italian](./translations/it.md), [Russian](./translations/ru.md), [Ukrainian](./translations/uk.md), [Simplified Chinese](./translations/zh-CN.md), [Traditional Chinese](./translations/zh-Hant.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/<code>.md`: [Spanish](./translations/es.md), [Italian](./translations/it.md), [Brazilian Portuguese](./translations/pt.md), [Russian](./translations/ru.md), [Ukrainian](./translations/uk.md), [Simplified Chinese](./translations/zh-CN.md), [Traditional Chinese](./translations/zh-Hant.md). Read it before translating, and add a term there when you settle on one.
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
# Brazilian Portuguese (`pt`)
|
||||||
|
|
||||||
|
Catalog: `src/i18n/locales/pt.json`. General rules: [TRANSLATORS.md](../TRANSLATORS.md).
|
||||||
|
|
||||||
|
Translation source: abiyeenzo (#909).
|
||||||
|
|
||||||
|
## Style
|
||||||
|
|
||||||
|
- Brazilian Portuguese (pt-BR): câmera, tela, compartilhar, arquivo, baixar, ônibus, metrô. Every `pt` tag, `pt-PT` included, gets this catalog, so avoid slang and regionalisms.
|
||||||
|
- Address the user with «você» imperatives: «Clique no mapa», «Ative o CCTV», «Tente novamente». Tooltips that name an action use the infinitive: «Expandir painel», «Redefinir a câmera».
|
||||||
|
- Sentence case, not Title Case: "Live Flights" → «Voos ao vivo». UPPERCASE English stays UPPERCASE: «AO VIVO», «SEM DADOS».
|
||||||
|
- Quotes inside Portuguese text are straight double quotes: «Excluir o plano "{shot}"?». A sentence that names a panel quotes it: «o painel "Configurações de provedores"» as the subject, so the verb agrees, and «adicione em "Configurações de provedores"» after a preposition.
|
||||||
|
- A dash between clauses is a spaced em dash « — », even where the English runs it in.
|
||||||
|
- A date or time after a participle takes a preposition: «emitido em {time}», «válida para {valid}», «Posição em {time}», «onde estava em {time}». Use «em», not «às», wherever {time} can include a date.
|
||||||
|
- A count in a string with no plural form (no `tn()`) goes after a colon, so it reads right for 1: «sincronizando feeds de cidades: {count}...», «CÂMERAS EM ESPERA: {count}».
|
||||||
|
- A placeholder that holds a layer or tool name of unknown gender and number gets a noun to agree with: «{label}: camada ativada», «A ferramenta "{owner}" está usando o ponteiro — feche-a primeiro».
|
||||||
|
- Short labels, chips and HUD readouts stay short. A standard abbreviation with a period beats a wrapped line: ANT., PRÓX., DESLIG., EXCL., INCL., ALT., VEL., câm.
|
||||||
|
- The HUD codes HDG, BRG, CRS, FOV, DEST, SPD, RANGE, ALT, GS, TRK and V/S stay in English, as in cockpit avionics («ALT {altitude} FT · GS {speed} KT · TRK {track}», «V/S {rate} FT/MIN»), and so does the HUD recording mark REC. Words used in a sentence are translated: heading → «rumo», range → «alcance».
|
||||||
|
- Keyboard keys stay in English: Space, Esc, Enter, Tab, Shift, Backspace, H, 1–9 («Segure Space para falar»).
|
||||||
|
- Common English tech words used in Brazil stay: feed, stream, snapshot, briefing, dock, layout, bloom, status, app, link, token.
|
||||||
|
- Units: km, m, km/h, m/s, kt (knots), ft (feet), h, min, s, always spaced from the number («Terremotos (24 h)», «Missões espaciais (30 d)»; in the layer names the space is a no-break space, U+00A0, so the unit never wraps away from the number); in UPPERCASE text KM, KM/S, KT, FT, MIN, H. "m" is always metres, so minutes are «min» («há 5 min»). MW stays MW. `°` and `%` stay. Decimal comma in written numbers («M4,4»).
|
||||||
|
- "ago" goes before the time: «há {count} min», «mais recente há {value}». When a placeholder already holds a localized "ago" text (`{age}`, `{ago}`), do not add «há» again.
|
||||||
|
- Money: «US$ {amount} mil / mi / bi» for thousands, millions and billions.
|
||||||
|
- Compass points use Leste/Oeste: N, NNE, NE, ENE, L, ESE, SE, SSE, S, SSO, SO, OSO, O, ONO, NO, NNO; hemisphere letters N, S, L, O.
|
||||||
|
|
||||||
|
## Plurals
|
||||||
|
|
||||||
|
A `tn()` entry needs three forms. `Intl.PluralRules('pt')` follows Brazilian usage:
|
||||||
|
|
||||||
|
| Form | Counts | Example |
|
||||||
|
| ------- | -------------------------------------- | ------------------ |
|
||||||
|
| `one` | 0, 1 and anything below 2: 0, 1, 1.5 | 0 câmera, 1 câmera |
|
||||||
|
| `many` | 1 000 000, 2 000 000… (exact millions) | 1000000 de câmeras |
|
||||||
|
| `other` | everything else: 2–999 999, 2.5… | 3 câmeras |
|
||||||
|
|
||||||
|
The `many` form is the `other` text with «de» after the number: «{count} de câmeras carregadas», «{count} DE CHAVES PENDENTES». A `one` form is also read for 0, so keep it natural with zero («0 arquivo selecionado» is standard Brazilian usage).
|
||||||
|
|
||||||
|
## Glossary
|
||||||
|
|
||||||
|
| English | Português |
|
||||||
|
| --------------------------------------------- | ------------------------------------------------------------ |
|
||||||
|
| layer / data layer | camada / camada de dados |
|
||||||
|
| feed / source / data source | feed / fonte / fonte de dados |
|
||||||
|
| LIVE (status) / live data | AO VIVO / dados ao vivo |
|
||||||
|
| SIMULATED | SIMULADO |
|
||||||
|
| stale / degraded / unavailable | desatualizado / degradado / indisponível |
|
||||||
|
| retry / retrying | nova tentativa / tentando de novo |
|
||||||
|
| retrying (a fetch) / retrying slowly | buscando de novo / tentando de novo em intervalos maiores |
|
||||||
|
| rate-limited | «X: limite de requisições atingido» |
|
||||||
|
| dismiss (window, card, error) | fechar |
|
||||||
|
| key (API key) / POWER UP / POWERED UP | chave / ATIVAR / ATIVADO |
|
||||||
|
| Provider Settings | Configurações de provedores |
|
||||||
|
| ON / OFF (state chips) | ATIVO / INATIVO |
|
||||||
|
| enable / disable | ativar / desativar |
|
||||||
|
| globe / map / map source / map stack | globo / mapa / fonte do mapa / pilha de mapas |
|
||||||
|
| basemap / tile | mapa base / bloco |
|
||||||
|
| aircraft, plane / flight / military flights | aeronave, avião / voo / voos militares |
|
||||||
|
| vessel, ship / TANKER (AIS class) | embarcação / NAVIO-TANQUE |
|
||||||
|
| satellite / shell (Starlink) / DENSE | satélite / camada orbital / DENSO |
|
||||||
|
| SPARSE / BALANCED / DENSE (detection modes) | ESPARSO / EQUILIBRADO / DENSO (o modo) |
|
||||||
|
| launch (rocket) / launch site / space mission | lançamento / local de lançamento / missão espacial |
|
||||||
|
| KM DOWNRANGE / EST. DOWNRANGE | KM DO LOCAL DE LANÇAMENTO / EST. PELA DIST. DO LANÇAMENTO |
|
||||||
|
| ascent / replay / payload / stage | subida / reprise / carga útil / estágio |
|
||||||
|
| contact / tracking / track | contato / rastreamento / rastro |
|
||||||
|
| installation / site | instalação / local |
|
||||||
|
| PLACES CANDIDATE (installation class) | CANDIDATO DO GOOGLE PLACES |
|
||||||
|
| mapped / observed | mapeado / observado |
|
||||||
|
| camera / CCTV camera | câmera / câmera de CCTV |
|
||||||
|
| CALIBRATED / CURATED (CCTV badge) | CALIBRADA / COM CURADORIA |
|
||||||
|
| AUTO HOP ON / OFF (CCTV) | TROCA AUTO. LIGADA / DESLIG. (as the other CCTV toggles) |
|
||||||
|
| earthquake / active fire | terremoto / incêndio ativo |
|
||||||
|
| advisory (NHC/CPHC cyclone) | boletim |
|
||||||
|
| E/C Pacific (NHC/CPHC coverage) | Pacífico Leste/Central (no «L/C») |
|
||||||
|
| weather (layer, card) / weather satellite | clima / meteorologia, meteorológico |
|
||||||
|
| FOG / cloud unknown | NEVOEIRO / nebulosidade desconhecida |
|
||||||
|
| AQHI bands Low … Very High | Risco baixo … Risco muito alto |
|
||||||
|
| radio station / broadcaster | estação de rádio / emissora |
|
||||||
|
| Oldies / World (radio tags) | Flashback / Músicas do mundo |
|
||||||
|
| radio genre tags agree with «música» | Alternativa, Clássica, Eletrônica, Latina |
|
||||||
|
| road / flow (traffic) | via / fluxo |
|
||||||
|
| Street Level (layer) | Nível da rua |
|
||||||
|
| scene / shot / scene recipe | cena / plano / receita de cena |
|
||||||
|
| briefing / panel / dock | briefing / painel / dock |
|
||||||
|
| cockpit | cabine |
|
||||||
|
| preset / visual preset / EXPORT PRESETS | predefinição / predefinição visual / EXPORTAR PREDEF. |
|
||||||
|
| VISUAL PRESETS (dock caption) | PREDEF. VISUAIS |
|
||||||
|
| Scope (circular viewport mask) / keyhole | mira / visor |
|
||||||
|
| feather / fade | suavização / esmaecimento |
|
||||||
|
| detection overlay | sobreposição de detecção |
|
||||||
|
| pin (Draw tool) / box (Recent Imagery) | marcador / área |
|
||||||
|
| Cyber (HUD layout, effect) | Ciber |
|
||||||
|
| heading / course / altitude / ground speed | rumo / curso / altitude / velocidade solo (VELOC. SOLO) |
|
||||||
|
| map heading / bearing (compass, north up) | orientação (do mapa) |
|
||||||
|
| HDG / BRG / CRS / FOV / DEST / SPD / RANGE | HDG / BRG / CRS / FOV / DEST / SPD / RANGE (unchanged) |
|
||||||
|
| V/S (vertical speed code) | V/S (unchanged) |
|
||||||
|
| PREV / NEXT | ANT. / PRÓX. |
|
||||||
|
| Previous / Next (button text) | Anterior / Próximo (Próxima) |
|
||||||
|
| L / R (relative bearing) | E / D |
|
||||||
|
| SIG / NEWS / LOCAL (briefing tabs) | SIG / NOTÍCIAS / LOCAL |
|
||||||
|
| REGIONAL NEWS (fits the 16-character kicker) | NOTÍCIAS DA ÁREA |
|
||||||
|
| PITCH / HGT (CCTV calibration chips) | INCL. / ALT. |
|
||||||
|
| NOT AN ALL-CLEAR | NÃO GARANTE SEGURANÇA |
|
||||||
|
| Last report (transit vehicle position) | Último registro |
|
||||||
|
| intersection (CCTV) | cruzamento |
|
||||||
|
| {layers} just turned on (any list of layers) | «{layers}: ativação recente» (no agreement needed) |
|
||||||
|
| {label} on / off (one layer) | «{label}: camada ativada / desativada» |
|
||||||
|
| turn-by-turn: turn left / keep left / lane | vire à esquerda / mantenha-se à esquerda / faixa da esquerda |
|
||||||
+5
-2
@@ -125,8 +125,11 @@ export function extractMessages() {
|
|||||||
const html = readFileSync(path.join(ROOT, file), 'utf8');
|
const html = readFileSync(path.join(ROOT, file), 'utf8');
|
||||||
for (const [, tag, attrs, text] of html.matchAll(TAG)) {
|
for (const [, tag, attrs, text] of html.matchAll(TAG)) {
|
||||||
const where = `${file} <${tag}>`;
|
const where = `${file} <${tag}>`;
|
||||||
if (/\sdata-i18n(\s|=|$)/.test(attrs))
|
if (/\sdata-i18n(\s|=|$)/.test(attrs)) {
|
||||||
add(normalize(decodeHtml(text)), where);
|
const context = attrs.match(/\sdata-i18n-context="([^"]*)"/);
|
||||||
|
const message = normalize(decodeHtml(text));
|
||||||
|
add(context ? `${decodeHtml(context[1])}|${message}` : message, where);
|
||||||
|
}
|
||||||
for (const [, name] of attrs.matchAll(
|
for (const [, name] of attrs.matchAll(
|
||||||
/\sdata-i18n-(title|aria-label|placeholder)\b/g,
|
/\sdata-i18n-(title|aria-label|placeholder)\b/g,
|
||||||
)) {
|
)) {
|
||||||
|
|||||||
@@ -441,6 +441,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -482,6 +483,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -514,6 +516,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -538,6 +541,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -557,6 +561,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -579,6 +584,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -608,6 +614,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -645,6 +652,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -664,6 +672,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -824,6 +833,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -949,6 +959,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -1019,6 +1030,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -1070,6 +1082,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -1135,6 +1148,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -1167,6 +1181,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -1200,6 +1215,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -1233,6 +1249,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -1269,6 +1286,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -1314,6 +1332,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -1353,6 +1372,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -1406,6 +1426,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -1438,6 +1459,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -1489,6 +1511,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -1555,6 +1578,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -1587,6 +1611,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -1630,6 +1655,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -1655,6 +1681,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -1714,6 +1741,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -1779,6 +1807,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -2664,6 +2693,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -3252,6 +3282,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -3365,6 +3396,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -3391,6 +3423,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -3429,6 +3462,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -3459,6 +3493,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -3553,6 +3588,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -3611,6 +3647,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -3868,6 +3905,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -3914,6 +3952,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -3970,6 +4009,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -3989,6 +4029,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -4051,6 +4092,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
@@ -4252,6 +4294,7 @@
|
|||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ test('normalizeLocale folds regional variants and rejects unshipped tags', () =>
|
|||||||
assert.equal(normalizeLocale('uk'), 'uk');
|
assert.equal(normalizeLocale('uk'), 'uk');
|
||||||
assert.equal(normalizeLocale('uk-UA'), 'uk');
|
assert.equal(normalizeLocale('uk-UA'), 'uk');
|
||||||
assert.equal(normalizeLocale('en_GB'), 'en');
|
assert.equal(normalizeLocale('en_GB'), 'en');
|
||||||
|
assert.equal(normalizeLocale('pt-BR'), 'pt');
|
||||||
|
assert.equal(normalizeLocale('pt_PT'), 'pt');
|
||||||
assert.equal(normalizeLocale('xx'), null);
|
assert.equal(normalizeLocale('xx'), null);
|
||||||
assert.equal(normalizeLocale('it-IT'), 'it');
|
assert.equal(normalizeLocale('it-IT'), 'it');
|
||||||
assert.equal(normalizeLocale('it_CH'), 'it');
|
assert.equal(normalizeLocale('it_CH'), 'it');
|
||||||
@@ -146,6 +148,20 @@ test('tn() picks English and Russian plural forms', () => {
|
|||||||
assert.match(label(21), / 21 КЛЮЧ(?![А-ЯЁ])/);
|
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', () => {
|
test('tn() picks the four Ukrainian plural forms', () => {
|
||||||
const label = (count) =>
|
const label = (count) =>
|
||||||
tn(
|
tn(
|
||||||
@@ -220,6 +236,21 @@ test('applyDocumentTranslations translates opted-in text and attributes in place
|
|||||||
assert.equal(unknown.textContent, 'No such message');
|
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', () => {
|
test('persistLocaleAndReload keeps the choice in ?lang when storage is blocked', () => {
|
||||||
const calls = [];
|
const calls = [];
|
||||||
const location = {
|
const location = {
|
||||||
|
|||||||
+9
-2
@@ -18,6 +18,7 @@ import {
|
|||||||
} from './locale.js';
|
} from './locale.js';
|
||||||
import es from './locales/es.json' with { type: 'json' };
|
import es from './locales/es.json' with { type: 'json' };
|
||||||
import it from './locales/it.json' with { type: 'json' };
|
import it from './locales/it.json' with { type: 'json' };
|
||||||
|
import pt from './locales/pt.json' with { type: 'json' };
|
||||||
import ru from './locales/ru.json' with { type: 'json' };
|
import ru from './locales/ru.json' with { type: 'json' };
|
||||||
import uk from './locales/uk.json' with { type: 'json' };
|
import uk from './locales/uk.json' with { type: 'json' };
|
||||||
import zhCN from './locales/zh-CN.json' with { type: 'json' };
|
import zhCN from './locales/zh-CN.json' with { type: 'json' };
|
||||||
@@ -26,6 +27,7 @@ import zhHant from './locales/zh-Hant.json' with { type: 'json' };
|
|||||||
const CATALOGS = Object.freeze({
|
const CATALOGS = Object.freeze({
|
||||||
es,
|
es,
|
||||||
it,
|
it,
|
||||||
|
pt,
|
||||||
ru,
|
ru,
|
||||||
uk,
|
uk,
|
||||||
'zh-CN': zhCN,
|
'zh-CN': zhCN,
|
||||||
@@ -230,7 +232,9 @@ function normalizeMarkupText(text) {
|
|||||||
* Markup opt-in: the element's own English text or attribute is the source.
|
* Markup opt-in: the element's own English text or attribute is the source.
|
||||||
* <span data-i18n>Normal</span>
|
* <span data-i18n>Normal</span>
|
||||||
* <button title="Pin" data-i18n-title>…</button>
|
* <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([
|
const DOCUMENT_TRANSLATION_TARGETS = Object.freeze([
|
||||||
['data-i18n', null],
|
['data-i18n', null],
|
||||||
@@ -255,7 +259,10 @@ export function applyDocumentTranslations(doc) {
|
|||||||
? element.getAttribute(attribute)
|
? element.getAttribute(attribute)
|
||||||
: normalizeMarkupText(element.textContent);
|
: normalizeMarkupText(element.textContent);
|
||||||
if (!source) continue;
|
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 (text === source) continue;
|
||||||
if (attribute) element.setAttribute(attribute, text);
|
if (attribute) element.setAttribute(attribute, text);
|
||||||
else element.textContent = text;
|
else element.textContent = text;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export const LOCALES = Object.freeze([
|
|||||||
'en',
|
'en',
|
||||||
'es',
|
'es',
|
||||||
'it',
|
'it',
|
||||||
|
'pt',
|
||||||
'ru',
|
'ru',
|
||||||
'uk',
|
'uk',
|
||||||
'zh-CN',
|
'zh-CN',
|
||||||
@@ -23,6 +24,7 @@ export const LOCALE_NAMES = Object.freeze({
|
|||||||
en: 'English',
|
en: 'English',
|
||||||
es: 'Español',
|
es: 'Español',
|
||||||
it: 'Italiano',
|
it: 'Italiano',
|
||||||
|
pt: 'Português (Brasil)',
|
||||||
ru: 'Русский',
|
ru: 'Русский',
|
||||||
uk: 'Українська',
|
uk: 'Українська',
|
||||||
'zh-CN': '中文(简体)',
|
'zh-CN': '中文(简体)',
|
||||||
@@ -34,6 +36,7 @@ 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',
|
||||||
|
* 'pt-BR' and 'pt-PT' → 'pt' (Brazilian Portuguese),
|
||||||
* 'zh', 'zh-CN' and 'zh-Hans-SG' → 'zh-CN';
|
* 'zh', 'zh-CN' and 'zh-Hans-SG' → 'zh-CN';
|
||||||
* 'zh-Hant', 'zh-TW', 'zh-HK' and 'zh-MO' → 'zh-Hant'.
|
* 'zh-Hant', 'zh-TW', 'zh-HK' and 'zh-MO' → 'zh-Hant'.
|
||||||
* @returns {string|null}
|
* @returns {string|null}
|
||||||
|
|||||||
@@ -2225,6 +2225,8 @@
|
|||||||
"GOOGLE_MAPS_API_KEY is not set": "GOOGLE_MAPS_API_KEY no está configurada",
|
"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",
|
"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",
|
"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",
|
"Malformed weather manifest": "Manifiesto del clima mal formado",
|
||||||
"Weather HTTP {status}": "Clima: HTTP {status}",
|
"Weather HTTP {status}": "Clima: HTTP {status}",
|
||||||
"Weather image HTTP {status}": "Imagen del clima: HTTP {status}",
|
"Weather image HTTP {status}": "Imagen del clima: HTTP {status}",
|
||||||
|
|||||||
@@ -3004,5 +3004,7 @@
|
|||||||
"Begin with a clean globe": "Inizia con un globo pulito",
|
"Begin with a clean globe": "Inizia con un globo pulito",
|
||||||
"Don't show this again": "Non mostrare più",
|
"Don't show this again": "Non mostrare più",
|
||||||
"ESC to dismiss": "ESC per chiudere",
|
"ESC to dismiss": "ESC per chiudere",
|
||||||
"Tip: the GEV MIC button in the dock lets you talk to the map.": "Suggerimento: il pulsante GEV MIC nel dock ti permette di parlare con la mappa."
|
"Tip: the GEV MIC button in the dock lets you talk to the map.": "Suggerimento: il pulsante GEV MIC nel dock ti permette di parlare con la mappa.",
|
||||||
|
"Malformed cyclone snapshot": "Snapshot dei cicloni non valido",
|
||||||
|
"Cyclone request timed out": "La richiesta dei cicloni è andata in timeout"
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -2261,6 +2261,8 @@
|
|||||||
"GOOGLE_MAPS_API_KEY is not set": "GOOGLE_MAPS_API_KEY не задан",
|
"GOOGLE_MAPS_API_KEY is not set": "GOOGLE_MAPS_API_KEY не задан",
|
||||||
"The Google Maps JavaScript API could not load": "Не удалось загрузить Google Maps JavaScript API",
|
"The Google Maps JavaScript API could not load": "Не удалось загрузить Google Maps JavaScript API",
|
||||||
"Weather request timed out": "Истекло время ожидания запроса погоды",
|
"Weather request timed out": "Истекло время ожидания запроса погоды",
|
||||||
|
"Cyclone request timed out": "Истекло время ожидания запроса циклонов",
|
||||||
|
"Malformed cyclone snapshot": "Некорректный снимок циклонов",
|
||||||
"Malformed weather manifest": "Некорректный манифест погоды",
|
"Malformed weather manifest": "Некорректный манифест погоды",
|
||||||
"Weather HTTP {status}": "Погода: HTTP {status}",
|
"Weather HTTP {status}": "Погода: HTTP {status}",
|
||||||
"Weather image HTTP {status}": "Снимок погоды: HTTP {status}",
|
"Weather image HTTP {status}": "Снимок погоды: HTTP {status}",
|
||||||
|
|||||||
@@ -3073,5 +3073,7 @@
|
|||||||
"Begin with a clean globe": "Почніть із чистого глобуса",
|
"Begin with a clean globe": "Почніть із чистого глобуса",
|
||||||
"Don't show this again": "Більше не показувати",
|
"Don't show this again": "Більше не показувати",
|
||||||
"ESC to dismiss": "ESC — приховати",
|
"ESC to dismiss": "ESC — приховати",
|
||||||
"Tip: the GEV MIC button in the dock lets you talk to the map.": "Порада: кнопка МІК у доку дає змогу говорити з картою."
|
"Tip: the GEV MIC button in the dock lets you talk to the map.": "Порада: кнопка МІК у доку дає змогу говорити з картою.",
|
||||||
|
"Malformed cyclone snapshot": "Некоректний знімок циклонів",
|
||||||
|
"Cyclone request timed out": "Минув час очікування запиту циклонів"
|
||||||
}
|
}
|
||||||
@@ -1203,6 +1203,8 @@
|
|||||||
"Some weather tiles unavailable": "部分天气瓦片不可用",
|
"Some weather tiles unavailable": "部分天气瓦片不可用",
|
||||||
"Malformed weather manifest": "天气清单格式错误",
|
"Malformed weather manifest": "天气清单格式错误",
|
||||||
"Weather request timed out": "天气请求超时",
|
"Weather request timed out": "天气请求超时",
|
||||||
|
"Cyclone request timed out": "气旋请求超时",
|
||||||
|
"Malformed cyclone snapshot": "气旋快照格式错误",
|
||||||
"Temperature field unavailable": "温度场不可用",
|
"Temperature field unavailable": "温度场不可用",
|
||||||
"Mean sea level pressure field unavailable": "平均海平面气压场不可用",
|
"Mean sea level pressure field unavailable": "平均海平面气压场不可用",
|
||||||
"Wind upstream unavailable": "风场上游数据源不可用",
|
"Wind upstream unavailable": "风场上游数据源不可用",
|
||||||
|
|||||||
@@ -1198,6 +1198,8 @@
|
|||||||
"Some weather tiles unavailable": "部分天氣瓦片不可用",
|
"Some weather tiles unavailable": "部分天氣瓦片不可用",
|
||||||
"Malformed weather manifest": "天氣清單格式錯誤",
|
"Malformed weather manifest": "天氣清單格式錯誤",
|
||||||
"Weather request timed out": "天氣請求超時",
|
"Weather request timed out": "天氣請求超時",
|
||||||
|
"Cyclone request timed out": "氣旋請求超時",
|
||||||
|
"Malformed cyclone snapshot": "氣旋快照格式錯誤",
|
||||||
"Temperature field unavailable": "溫度場不可用",
|
"Temperature field unavailable": "溫度場不可用",
|
||||||
"Mean sea level pressure field unavailable": "平均海平面氣壓場不可用",
|
"Mean sea level pressure field unavailable": "平均海平面氣壓場不可用",
|
||||||
"Wind upstream unavailable": "風場上游數據源不可用",
|
"Wind upstream unavailable": "風場上游數據源不可用",
|
||||||
|
|||||||
@@ -293,6 +293,134 @@
|
|||||||
"ΔN",
|
"ΔN",
|
||||||
"▲ {frp} MW"
|
"▲ {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": [
|
"ru": [
|
||||||
"AIS: --",
|
"AIS: --",
|
||||||
"AIS: {name}",
|
"AIS: {name}",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { CCTV_AMBIENT_CARD_MAX } from '../../data/cctvLod.js';
|
import { CCTV_AMBIENT_CARD_MAX } from '../../data/cctvLod.js';
|
||||||
import { t } from '../../i18n/index.js';
|
import { msg, t } from '../../i18n/index.js';
|
||||||
import { ACTIVE_FRAME_REFRESH_MS, IDLE_FRAME_REFRESH_MS } from './policy.js';
|
import { ACTIVE_FRAME_REFRESH_MS, IDLE_FRAME_REFRESH_MS } from './policy.js';
|
||||||
import { headingHudToken, isHeadingEstimated } from './headingConfidence.js';
|
import { headingHudToken, isHeadingEstimated } from './headingConfidence.js';
|
||||||
import {
|
import {
|
||||||
@@ -8,6 +8,59 @@ import {
|
|||||||
isDatasheetFov,
|
isDatasheetFov,
|
||||||
} from './modelSpecs.js';
|
} from './modelSpecs.js';
|
||||||
|
|
||||||
|
// The summary line names the CAL badge, the source kind and the view band in
|
||||||
|
// the page language; the codes underneath stay English.
|
||||||
|
const CAL_BADGE_LABELS = Object.freeze({
|
||||||
|
calibrated: msg('CALIBRATED'),
|
||||||
|
curated: msg('CURATED'),
|
||||||
|
'raw-prior': msg('RAW PRIOR'),
|
||||||
|
});
|
||||||
|
|
||||||
|
// The same source-kind words as the CCTV panel (src/ui/cctvFrames.js).
|
||||||
|
const SOURCE_KIND_LABELS = Object.freeze({
|
||||||
|
snapshot: msg('SNAPSHOT'),
|
||||||
|
live: msg('LIVE'),
|
||||||
|
fallback: msg('FALLBACK'),
|
||||||
|
upstream: msg('UPSTREAM'),
|
||||||
|
synthetic: msg('SYNTHETIC'),
|
||||||
|
configured: msg('CONFIGURED'),
|
||||||
|
seed: msg('SEED'),
|
||||||
|
image: msg('IMAGE'),
|
||||||
|
unknown: msg('UNKNOWN'),
|
||||||
|
});
|
||||||
|
|
||||||
|
/** CAL badge id ('raw-prior') as the summary shows it; unknown ids stay codes. */
|
||||||
|
export function summaryCalLabel(badge) {
|
||||||
|
const key = String(badge || '');
|
||||||
|
return CAL_BADGE_LABELS[key]
|
||||||
|
? t(CAL_BADGE_LABELS[key])
|
||||||
|
: key.replace('-', ' ').toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Source kind ('image', 'snapshot') for the summary; HLS, MJPEG… stay codes. */
|
||||||
|
export function summarySourceLabel(kind) {
|
||||||
|
const key = String(kind || 'image').toLowerCase();
|
||||||
|
return SOURCE_KIND_LABELS[key]
|
||||||
|
? t(SOURCE_KIND_LABELS[key])
|
||||||
|
: key.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** View band id ('street', 'city'…) as the HUD names it. */
|
||||||
|
export function summaryViewBandLabel(band) {
|
||||||
|
switch (band) {
|
||||||
|
case 'street':
|
||||||
|
return t('STREET', { context: 'view band' });
|
||||||
|
case 'city':
|
||||||
|
return t('CITY', { context: 'view band' });
|
||||||
|
case 'regional':
|
||||||
|
return t('REGIONAL', { context: 'view band' });
|
||||||
|
case 'global':
|
||||||
|
return t('GLOBAL', { context: 'view band' });
|
||||||
|
default:
|
||||||
|
return String(band || '').toUpperCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function createPresentation({
|
export function createPresentation({
|
||||||
state: layerState,
|
state: layerState,
|
||||||
services,
|
services,
|
||||||
@@ -72,17 +125,13 @@ export function createPresentation({
|
|||||||
: t('ISOLATED VIEW'),
|
: t('ISOLATED VIEW'),
|
||||||
layerState._showProjection ? t('PROJ MONITOR') : t('PROJ OFF'),
|
layerState._showProjection ? t('PROJ MONITOR') : t('PROJ OFF'),
|
||||||
layerState._coverageMode === 'viewshed' ? t('VIEWSHED') : null,
|
layerState._coverageMode === 'viewshed' ? t('VIEWSHED') : null,
|
||||||
t('CAL {value}', {
|
t('CAL {value}', { value: summaryCalLabel(calBadge) }),
|
||||||
value: calBadge.replace('-', ' ').toUpperCase(),
|
t('SRC {value}', {
|
||||||
|
value: summarySourceLabel(
|
||||||
|
health?.sourceKind || active.camera.feedType || 'image',
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
health?.sourceKind
|
t('{value} CONTEXT', { value: summaryViewBandLabel(viewBand) }),
|
||||||
? t('SRC {value}', {
|
|
||||||
value: String(health.sourceKind).toUpperCase(),
|
|
||||||
})
|
|
||||||
: t('SRC {value}', {
|
|
||||||
value: String(active.camera.feedType || 'image').toUpperCase(),
|
|
||||||
}),
|
|
||||||
t('{value} CONTEXT', { value: viewBand.toUpperCase() }),
|
|
||||||
// Identified hardware: append the model's datasheet specs + attribution.
|
// Identified hardware: append the model's datasheet specs + attribution.
|
||||||
active.camera.spec
|
active.camera.spec
|
||||||
? t('{spec} · SPECS: CCTV-DATABASE.COM', {
|
? t('{spec} · SPECS: CCTV-DATABASE.COM', {
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// The CCTV summary line names the CAL badge, the source kind and the view
|
||||||
|
// band in the page language; unknown codes stay as uppercase codes.
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
import { setLocale } from '../../i18n/index.js';
|
||||||
|
import {
|
||||||
|
summaryCalLabel,
|
||||||
|
summarySourceLabel,
|
||||||
|
summaryViewBandLabel,
|
||||||
|
} from './presentation.js';
|
||||||
|
|
||||||
|
function inLocale(locale, run) {
|
||||||
|
setLocale(locale);
|
||||||
|
try {
|
||||||
|
return run();
|
||||||
|
} finally {
|
||||||
|
setLocale('en');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('English keeps the summary codes', () => {
|
||||||
|
assert.equal(summaryCalLabel('raw-prior'), 'RAW PRIOR');
|
||||||
|
assert.equal(summarySourceLabel('image'), 'IMAGE');
|
||||||
|
assert.equal(summarySourceLabel('hls'), 'HLS');
|
||||||
|
assert.equal(summaryViewBandLabel('street'), 'STREET');
|
||||||
|
assert.equal(summaryViewBandLabel('none'), 'NONE');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Portuguese summary values are translated', () => {
|
||||||
|
inLocale('pt', () => {
|
||||||
|
assert.equal(summaryCalLabel('raw-prior'), 'ESTIMATIVA BRUTA');
|
||||||
|
assert.equal(summaryCalLabel('calibrated'), 'CALIBRADA');
|
||||||
|
assert.equal(summarySourceLabel('IMAGE'), 'IMAGEM');
|
||||||
|
assert.equal(summarySourceLabel('mjpeg'), 'MJPEG');
|
||||||
|
assert.equal(summaryViewBandLabel('street'), 'RUA');
|
||||||
|
assert.equal(summaryViewBandLabel('city'), 'CIDADE');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
import { readResponseJsonCapped } from '../../sources/httpBody.js';
|
import { readResponseJsonCapped } from '../../sources/httpBody.js';
|
||||||
|
|
||||||
|
// Shared with the AI tools, so no i18n import: these errors stay English here
|
||||||
|
// and the cyclones layer translates them where it shows them (translateKnown).
|
||||||
|
const msg = (text) => text;
|
||||||
|
|
||||||
export const CYCLONE_RESPONSE_LIMIT = 4 * 1024 * 1024;
|
export const CYCLONE_RESPONSE_LIMIT = 4 * 1024 * 1024;
|
||||||
const malformed = () => new Error('Malformed cyclone snapshot');
|
const malformed = () => new Error(msg('Malformed cyclone snapshot'));
|
||||||
const text = (value, max = 160) => {
|
const text = (value, max = 160) => {
|
||||||
if (
|
if (
|
||||||
typeof value !== 'string' ||
|
typeof value !== 'string' ||
|
||||||
@@ -215,7 +219,7 @@ export function createCycloneSource({
|
|||||||
const abort = () => controller.abort(signal.reason);
|
const abort = () => controller.abort(signal.reason);
|
||||||
signal?.addEventListener('abort', abort, { once: true });
|
signal?.addEventListener('abort', abort, { once: true });
|
||||||
const timer = setTimeout(
|
const timer = setTimeout(
|
||||||
() => controller.abort(new Error('Cyclone request timed out')),
|
() => controller.abort(new Error(msg('Cyclone request timed out'))),
|
||||||
timeoutMs,
|
timeoutMs,
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ test('the current language comes first, the rest by their own names', () => {
|
|||||||
nativeNames: LOCALE_NAMES,
|
nativeNames: LOCALE_NAMES,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
['ru', 'uk', 'en', 'es', 'it', 'zh-CN', 'zh-Hant'],
|
['ru', 'uk', 'en', 'es', 'it', 'pt', 'zh-CN', 'zh-Hant'],
|
||||||
);
|
);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
codes(
|
codes(
|
||||||
@@ -50,7 +50,7 @@ test('the current language comes first, the rest by their own names', () => {
|
|||||||
nativeNames: LOCALE_NAMES,
|
nativeNames: LOCALE_NAMES,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
['es', 'en', 'it', 'ru', 'uk', 'zh-CN', 'zh-Hant'],
|
['es', 'en', 'it', 'pt', 'ru', 'uk', 'zh-CN', 'zh-Hant'],
|
||||||
);
|
);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
codes(
|
codes(
|
||||||
@@ -60,7 +60,7 @@ test('the current language comes first, the rest by their own names', () => {
|
|||||||
nativeNames: LOCALE_NAMES,
|
nativeNames: LOCALE_NAMES,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
['en', 'es', 'it', 'ru', 'uk', 'zh-CN', 'zh-Hant'],
|
['en', 'es', 'it', 'pt', 'ru', 'uk', 'zh-CN', 'zh-Hant'],
|
||||||
);
|
);
|
||||||
const rows = buildLocaleOptions({
|
const rows = buildLocaleOptions({
|
||||||
locales: MANY,
|
locales: MANY,
|
||||||
|
|||||||
@@ -122,7 +122,7 @@
|
|||||||
<button id="radio-enable-btn" class="panel-layer-toggle" type="button" aria-pressed="false" data-i18n>ENABLE</button>
|
<button id="radio-enable-btn" class="panel-layer-toggle" type="button" aria-pressed="false" data-i18n>ENABLE</button>
|
||||||
<label class="radio-filter-label" for="radio-filter" data-i18n>STATION TAG</label>
|
<label class="radio-filter-label" for="radio-filter" data-i18n>STATION TAG</label>
|
||||||
<select class="gev-field" id="radio-filter" aria-label="Filter stations by station tag" disabled data-i18n-aria-label>
|
<select class="gev-field" id="radio-filter" aria-label="Filter stations by station tag" disabled data-i18n-aria-label>
|
||||||
<option value="all" data-i18n>All</option>
|
<option value="all" data-i18n data-i18n-context="radio tag">All</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div id="radio-station-card" class="radio-station-card" aria-live="polite">
|
<div id="radio-station-card" class="radio-station-card" aria-live="polite">
|
||||||
|
|||||||
Reference in new issue
Block a user