refactor(i18n): key messages by English text; add Russian

Replace the semantic keys with the English source text, so code reads as the
UI does and English needs no catalog: t('Save keys'), tn(count, one, other)
for plurals, msg() for text stored in data, and bare data-i18n* markup that
translates the element's own English. Other languages are one flat JSON each.

- Simplify src/i18n: drop namespaces, the configurable locale pair
  (GEV_DEFAULT_LOCALE / GEV_SECONDARY_LOCALE) and its build defines. Locale
  comes from ?lang=, the stored choice, then browser languages.
- Add scripts/i18n.mjs (npm run i18n): extracts every message from t/tn/msg
  and markup, and fails on missing, unused, placeholder-mismatched or
  wrongly-shaped plural entries. npm test runs it via catalog.test.mjs.
- Add Russian (src/i18n/locales/ru.json, 833 messages), started from the
  community catalog in #312 and reviewed against the current English.
- Fix data-i18n placed on parents that would wipe their children (cockpit
  LEVEL and local weather cells, first-run EXPLORE MANUALLY tile), and a
  doubled degree sign in the TRACK ACQUIRED signal.
- Rewrite docs/TRANSLATORS.md for the new workflow; drop sections the earlier
  merge resurrected in docs/CURRENT-STATE.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
German KhokhlovandClaude Opus 5.5 committed 2026-10-12 13:48:56 +04:00
1 parent dfbaeafbd8
commit 13efd411b6
112 files changed
+4419 -4856

No files matched your search

+17 -21
View File
@@ -1,4 +1,4 @@
import { t } from '../i18n/index.js';
import { t, msg } from '../i18n/index.js';
import {
radioTunerSlot,
radioTunerCommitSlot,
@@ -53,14 +53,14 @@ export function bindRadioControls() {
'aria-label',
t(
displayOpen
? 'cockpit.utility.displayToggleCollapseAriaLabel'
: 'cockpit.utility.displayToggleAriaLabel',
? msg('Collapse Cockpit display options')
: msg('Expand Cockpit display options'),
),
);
this._cockpitDisplayToggleBtn.title = t(
displayOpen
? 'cockpit.utility.displayToggleCollapseAriaLabel'
: 'cockpit.utility.displayToggleAriaLabel',
? msg('Collapse Cockpit display options')
: msg('Expand Cockpit display options'),
);
}
if (this._cockpitRadioToggleBtn) {
@@ -69,14 +69,14 @@ export function bindRadioControls() {
'aria-label',
t(
radioOpen
? 'cockpit.utility.radioToggleCollapseAriaLabel'
: 'cockpit.utility.radioToggleAriaLabel',
? msg('Collapse Cockpit Radio controls')
: msg('Expand Cockpit Radio controls'),
),
);
this._cockpitRadioToggleBtn.title = t(
radioOpen
? 'cockpit.utility.radioToggleCollapseAriaLabel'
: 'cockpit.utility.radioToggleAriaLabel',
? msg('Collapse Cockpit Radio controls')
: msg('Expand Cockpit Radio controls'),
);
}
if (!expanded && returnFocus) {
@@ -155,21 +155,21 @@ export function bindRadioControls() {
if (this._radioTunerValue) {
this._radioTunerValue.textContent = station
? `CH ${String(slot.stationIndex + 1).padStart(2, '0')} / ${String(this._radioTunerStations.length).padStart(2, '0')}`
: t('cockpit.radio.tunerNoStations');
: t('NO STATIONS');
}
if (this._radioTunerStation)
this._radioTunerStation.textContent =
station?.name || t('layers.radio.noStationAvailable');
station?.name || t('NO STATION AVAILABLE');
if (this._radioTunerSlider) {
this._radioTunerSlider.setAttribute(
'aria-valuetext',
station
? t('cockpit.radio.tunerStationAria', {
? t('{name}, station {index} of {total}', {
name: station.name,
index: slot.stationIndex + 1,
total: this._radioTunerStations.length,
})
: t('cockpit.radio.tunerNoStationAria'),
: t('No station available'),
);
}
if (syncStatic)
@@ -320,11 +320,9 @@ export function bindRadioControls() {
this._radioTunerBandPinnedForNavigation = false;
if (result.reason === 'station-unavailable') {
if (this._radioTunerValue)
this._radioTunerValue.textContent = t('cockpit.radio.tunerOffAir');
this._radioTunerValue.textContent = t('OFF AIR');
if (this._radioTunerStation)
this._radioTunerStation.textContent = t(
'cockpit.radio.tunerStationUnavailable',
);
this._radioTunerStation.textContent = t('STATION UNAVAILABLE');
this._radioTunerSlider?.setAttribute(
'aria-valuetext',
'Station unavailable after directory refresh',
@@ -367,10 +365,8 @@ export function bindRadioControls() {
// (identical value under 'en').
`Radio could not ${enabling ? 'start' : 'stop'} cleanly`,
{
localizedMessage: t('cockpit.radio.toastLifecycleFailed', {
action: enabling
? t('cockpit.context.actionStart')
: t('cockpit.context.actionStop'),
localizedMessage: t('Radio could not {action} cleanly', {
action: enabling ? t('start') : t('stop'),
}),
},
);