ui: fix net-sim i18n — decode-entity key match + label coverage (v2.7.6)

The network-simulation description never switched to Russian: its
data-l10n attribute carries " (decoded to " by the browser), while the
LANG_RU key is raw text inside <script> and keeps the literal &quot;, so
translatePage()'s lookup missed and t() returned the English key.

- LANG_RU: the description key now carries real quotes, matching the
  decoded attribute value (RU value unchanged)
- add the missing RU entries for the net-sim labels AUTHENTICATE, MCC,
  MNC, LAC, Cell ID, TAC, RAC (acronyms map to themselves; Cell ID ->
  «Идентификатор соты»)
- html.test.js: strict cross-check that every data-l10n attribute
  (entity-decoded, as the browser does) is a LANG_RU key
- sw.js cache simple-v209; version trio 2.7.6
This commit is contained in:
2026-09-20 14:05:27 +03:00
parent 6131d34d28
commit a688c63919
5 changed files with 31 additions and 5 deletions
+9 -2
View File
@@ -1413,7 +1413,7 @@
// ===== Version ===== // ===== Version =====
// Single source of truth for the PWA version: shown in the header and used // Single source of truth for the PWA version: shown in the header and used
// by the server version check in pysimConnect(). // by the server version check in pysimConnect().
const SIMPLE_VERSION = '2.7.5'; const SIMPLE_VERSION = '2.7.6';
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION; document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
// ===== Tab switching ===== // ===== Tab switching =====
@@ -13032,7 +13032,7 @@ const LANG_RU = {
'No events configured.': 'Нет настроенных событий.', 'No events configured.': 'Нет настроенных событий.',
'Fetched proactive commands:': 'Извлечённые проактивные команды:', 'Fetched proactive commands:': 'Извлечённые проактивные команды:',
'Network simulation': 'Симуляция сети', 'Network simulation': 'Симуляция сети',
'Replays the write patterns a real phone performs on network-condition changes (EPS attach, service loss, roaming denial, 2G fallback, SMS delivery, cell-broadcast reconfiguration, AUTHENTICATE). Only UPDATE BINARY/RECORD, ENVELOPE and AUTHENTICATE are sent; FPLMN is written only by the permanent &quot;PLMN not allowed&quot; rejection (TS 31.102 4.2.16), and the 5GS location files are never touched.': 'Воспроизводит шаблоны записи реального телефона при смене сетевых условий (подключение EPS, потеря сервиса, запрет роуминга, откат на 2G, приём SMS, перенастройка cell broadcast, AUTHENTICATE). Отправляются только UPDATE BINARY/RECORD, ENVELOPE и AUTHENTICATE; FPLMN записывается только при постоянном отказе «PLMN not allowed» (TS 31.102 4.2.16), а 5GS location-файлы не затрагиваются.', 'Replays the write patterns a real phone performs on network-condition changes (EPS attach, service loss, roaming denial, 2G fallback, SMS delivery, cell-broadcast reconfiguration, AUTHENTICATE). Only UPDATE BINARY/RECORD, ENVELOPE and AUTHENTICATE are sent; FPLMN is written only by the permanent "PLMN not allowed" rejection (TS 31.102 4.2.16), and the 5GS location files are never touched.': 'Воспроизводит шаблоны записи реального телефона при смене сетевых условий (подключение EPS, потеря сервиса, запрет роуминга, откат на 2G, приём SMS, перенастройка cell broadcast, AUTHENTICATE). Отправляются только UPDATE BINARY/RECORD, ENVELOPE и AUTHENTICATE; FPLMN записывается только при постоянном отказе «PLMN not allowed» (TS 31.102 4.2.16), а 5GS location-файлы не затрагиваются.',
'Cold boot': 'Холодная загрузка', 'Cold boot': 'Холодная загрузка',
'EPS attach': 'Подключение EPS', 'EPS attach': 'Подключение EPS',
'2G attach': 'Подключение 2G', '2G attach': 'Подключение 2G',
@@ -13042,9 +13042,16 @@ const LANG_RU = {
'Churn': 'Серия переподключений', 'Churn': 'Серия переподключений',
'SMS received': 'Принято SMS', 'SMS received': 'Принято SMS',
'CB reconfig': 'Перенастройка CB', 'CB reconfig': 'Перенастройка CB',
'AUTHENTICATE': 'AUTHENTICATE',
'Parameters': 'Параметры', 'Parameters': 'Параметры',
'Operator search (country / brand / MCC-MNC)': 'Поиск оператора (страна / бренд / MCC-MNC)', 'Operator search (country / brand / MCC-MNC)': 'Поиск оператора (страна / бренд / MCC-MNC)',
'Sets the PLMN written to LOCI/PSLOCI/EPSLOCI. Used by EPS attach, 2G attach and SMS received (real LAI/RAI/TAI; SMS only when its location rewrite is enabled), and by Cold boot, Service lost, Limited service and Roaming denied (the dummy LOCI/PSLOCI keep this PLMN, the EPSLOCI dummy wipes the TAI PLMN). Churn, CB reconfig and AUTHENTICATE don\'t use it.': 'Задаёт PLMN, записываемый в LOCI/PSLOCI/EPSLOCI. Используется в сценариях «Подключение EPS», «Подключение 2G» и «Принято SMS» (реальные LAI/RAI/TAI; в SMS — только при включённой перезаписи location), а также «Холодная загрузка», «Потеря сервиса», «Ограниченный сервис» и «Роуминг запрещён» (фиктивные LOCI/PSLOCI сохраняют этот PLMN, фиктивный EPSLOCI стирает TAI PLMN). «Серия переподключений», «Перенастройка CB» и AUTHENTICATE его не используют.', 'Sets the PLMN written to LOCI/PSLOCI/EPSLOCI. Used by EPS attach, 2G attach and SMS received (real LAI/RAI/TAI; SMS only when its location rewrite is enabled), and by Cold boot, Service lost, Limited service and Roaming denied (the dummy LOCI/PSLOCI keep this PLMN, the EPSLOCI dummy wipes the TAI PLMN). Churn, CB reconfig and AUTHENTICATE don\'t use it.': 'Задаёт PLMN, записываемый в LOCI/PSLOCI/EPSLOCI. Используется в сценариях «Подключение EPS», «Подключение 2G» и «Принято SMS» (реальные LAI/RAI/TAI; в SMS — только при включённой перезаписи location), а также «Холодная загрузка», «Потеря сервиса», «Ограниченный сервис» и «Роуминг запрещён» (фиктивные LOCI/PSLOCI сохраняют этот PLMN, фиктивный EPSLOCI стирает TAI PLMN). «Серия переподключений», «Перенастройка CB» и AUTHENTICATE его не используют.',
'MCC': 'MCC',
'MNC': 'MNC',
'LAC': 'LAC',
'Cell ID': 'Идентификатор соты',
'TAC': 'TAC',
'RAC': 'RAC',
'Random roaming operator': 'Случайный роуминг-оператор', 'Random roaming operator': 'Случайный роуминг-оператор',
'Advanced identity (empty = random)': 'Дополнительные идентификаторы (пусто = случайно)', 'Advanced identity (empty = random)': 'Дополнительные идентификаторы (пусто = случайно)',
'Algorithm': 'Алгоритм', 'Algorithm': 'Алгоритм',
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'simple-v208'; const CACHE = 'simple-v209';
const URLS = [ const URLS = [
'index.html', 'index.html',
'help.html', 'help.html',
+19
View File
@@ -298,3 +298,22 @@ test('the network monitor labels its refresh timestamp and has no area line', ()
assert.match(html, /data-l10n="Last refresh">Last refresh<\/span>: <span id="netstate-read"><\/span>/); assert.match(html, /data-l10n="Last refresh">Last refresh<\/span>: <span id="netstate-read"><\/span>/);
assert.ok(!/id="netstate-area"/.test(html), 'the area line is gone; LAI/RAI/TAI live in the EF rows'); assert.ok(!/id="netstate-area"/.test(html), 'the area line is gone; LAI/RAI/TAI live in the EF rows');
}); });
test('every data-l10n attribute resolves in LANG_RU after HTML decoding', () => {
// translatePage() looks up el.getAttribute('data-l10n') — the browser
// decodes entities in attribute values, but NOT inside the <script>
// block where LANG_RU lives. Mirror that lookup exactly: a key written
// as &quot; in the markup and &quot; in the dict never matches.
const dictMatch = html.match(/const LANG_RU = \{([\s\S]*?)\n\};/);
assert.ok(dictMatch, 'LANG_RU literal not found');
const dict = eval('({' + dictMatch[1] + '})');
const entities = {
quot: '"', '#34': '"', amp: '&', '#38': '&', apos: "'", '#39': "'",
lt: '<', '#60': '<', gt: '>', '#62': '>', nbsp: ' ',
};
const decode = s => s.replace(/&([a-z]+|#[0-9]+);/gi,
(m, e) => entities[e] !== undefined ? entities[e] : m);
const attrs = [...html.matchAll(/data-l10n="([^"]*)"/g)].map(m => decode(m[1]));
const missing = [...new Set(attrs.filter(k => !(k in dict)))];
assert.deepStrictEqual(missing, [], 'data-l10n keys with no LANG_RU entry:\n' + missing.join('\n'));
});
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "pysim-simple-server" name = "pysim-simple-server"
version = "2.7.5" version = "2.7.6"
description = "HTTP REST server wrapping pysim for the SIMple PWA" description = "HTTP REST server wrapping pysim for the SIMple PWA"
requires-python = ">=3.8" requires-python = ">=3.8"
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh. # pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
+1 -1
View File
@@ -25,7 +25,7 @@ from osmocom.construct import GsmOrUcs2Adapter
from osmocom.tlv import BER_TLV_IE from osmocom.tlv import BER_TLV_IE
VERSION = '2.7.5' VERSION = '2.7.6'
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE