i18n: re-render visible dynamic views on language switch
translatePage() only rewrites static [data-l10n] elements, so labels built at render time (Check card/Edit/Export/Delete, Open, Remove, Send, ...) stayed in the old language. toggleLang() now calls refreshDynamicI18n(), which re-renders only the visible dynamic views: - profiler sub-tab: current view (list, snapshot list, editor, results via the new profilerRenderResultsView with profilerResults stored, snapshot via profilerRenderSnapshotData which leaves the name input alone) - open scan modal: profilerScanRefreshOptions re-translates the mask labels without touching checkbox state - cards table (cardsRender), proactive events/log/PLI (async, only when the sub-tab is visible) Also removed the duplicated translatePage() call in toggleLang. New test for profilerScanRefreshOptions. SW cache v83 -> v84.
This commit is contained in:
+58
-12
@@ -7225,6 +7225,16 @@ function profilerScanSetTarget(target) {
|
|||||||
document.getElementById('profiler-scan-options').classList.toggle('hidden', target === 'snapshot');
|
document.getElementById('profiler-scan-options').classList.toggle('hidden', target === 'snapshot');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-translate the dynamically built mask labels in the open scan modal,
|
||||||
|
// preserving the checkbox state (ignore-list labels are plain file names).
|
||||||
|
function profilerScanRefreshOptions() {
|
||||||
|
if (_scanTarget === 'snapshot') return;
|
||||||
|
document.querySelectorAll('#profiler-scan-mask input[data-mask-fid]').forEach(cb => {
|
||||||
|
const span = cb.parentElement ? cb.parentElement.querySelector('span') : null;
|
||||||
|
if (span) span.textContent = t('Match first 4 bytes for') + ' ' + PROFILER_MASK_PREFIX4_FIDS[cb.getAttribute('data-mask-fid')];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function profilerFromCard() {
|
function profilerFromCard() {
|
||||||
profilerScanSetTarget('profile');
|
profilerScanSetTarget('profile');
|
||||||
document.getElementById('profiler-scan-name').value = '';
|
document.getElementById('profiler-scan-name').value = '';
|
||||||
@@ -7594,6 +7604,14 @@ function snapshotOpen(i) {
|
|||||||
snapshotViewId = s.id;
|
snapshotViewId = s.id;
|
||||||
profilerSetView('snapshot');
|
profilerSetView('snapshot');
|
||||||
document.getElementById('snapshot-name').value = s.name;
|
document.getElementById('snapshot-name').value = s.name;
|
||||||
|
profilerRenderSnapshotData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-render the immutable part of the snapshot view (ICCID + file cards)
|
||||||
|
// without touching the (editable) name input.
|
||||||
|
function profilerRenderSnapshotData() {
|
||||||
|
const s = snapshots.find(x => x.id === snapshotViewId);
|
||||||
|
if (!s) return;
|
||||||
document.getElementById('snapshot-iccid').textContent = s.iccid || '';
|
document.getElementById('snapshot-iccid').textContent = s.iccid || '';
|
||||||
document.getElementById('snapshot-files').innerHTML = profilerRenderSnapshotFiles(s);
|
document.getElementById('snapshot-files').innerHTML = profilerRenderSnapshotFiles(s);
|
||||||
}
|
}
|
||||||
@@ -7863,26 +7881,31 @@ async function profilerCheck(i) {
|
|||||||
profilerSetView('results');
|
profilerSetView('results');
|
||||||
document.getElementById('profiler-results-title').textContent = p.name;
|
document.getElementById('profiler-results-title').textContent = p.name;
|
||||||
const prog = document.getElementById('profiler-progress');
|
const prog = document.getElementById('profiler-progress');
|
||||||
const summary = document.getElementById('profiler-summary');
|
document.getElementById('profiler-summary').innerHTML = '';
|
||||||
const report = document.getElementById('profiler-report');
|
document.getElementById('profiler-report').innerHTML = '';
|
||||||
report.innerHTML = '';
|
|
||||||
summary.innerHTML = '';
|
|
||||||
const results = [];
|
const results = [];
|
||||||
let passed = 0, failed = 0, errors = 0;
|
|
||||||
for (let ri = 0; ri < p.rules.length; ri++) {
|
for (let ri = 0; ri < p.rules.length; ri++) {
|
||||||
const r = p.rules[ri];
|
const r = p.rules[ri];
|
||||||
prog.textContent = t('Checking') + ' ' + (ri + 1) + '/' + p.rules.length + ' — ' + r.path;
|
prog.textContent = t('Checking') + ' ' + (ri + 1) + '/' + p.rules.length + ' — ' + r.path;
|
||||||
const res = await profilerRunRule(r);
|
results.push(await profilerRunRule(r));
|
||||||
results.push(res);
|
}
|
||||||
|
prog.textContent = '';
|
||||||
|
profilerResults = results;
|
||||||
|
profilerRenderResultsView();
|
||||||
|
}
|
||||||
|
|
||||||
|
function profilerRenderResultsView() {
|
||||||
|
if (!profilerResults) return;
|
||||||
|
let passed = 0, failed = 0, errors = 0;
|
||||||
|
for (const res of profilerResults) {
|
||||||
if (res.status === 'pass') passed++;
|
if (res.status === 'pass') passed++;
|
||||||
else if (res.status === 'fail') failed++;
|
else if (res.status === 'fail') failed++;
|
||||||
else errors++;
|
else errors++;
|
||||||
}
|
}
|
||||||
prog.textContent = '';
|
document.getElementById('profiler-summary').innerHTML = '<span class="text-emerald-600 font-semibold">' + passed + ' ' + t('passed') + '</span>' +
|
||||||
summary.innerHTML = '<span class="text-emerald-600 font-semibold">' + passed + ' ' + t('passed') + '</span>' +
|
|
||||||
' · <span class="text-red-600 font-semibold">' + failed + ' ' + t('failed') + '</span>' +
|
' · <span class="text-red-600 font-semibold">' + failed + ' ' + t('failed') + '</span>' +
|
||||||
' · <span class="text-yellow-600 font-semibold">' + errors + ' ' + t('errors') + '</span>';
|
' · <span class="text-yellow-600 font-semibold">' + errors + ' ' + t('errors') + '</span>';
|
||||||
report.innerHTML = profilerRenderReport(results);
|
document.getElementById('profiler-report').innerHTML = profilerRenderReport(profilerResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function profilerRunRule(r) {
|
async function profilerRunRule(r) {
|
||||||
@@ -8660,9 +8683,9 @@ function t(text) {
|
|||||||
function toggleLang() {
|
function toggleLang() {
|
||||||
currentLang = currentLang === 'en' ? 'ru' : 'en';
|
currentLang = currentLang === 'en' ? 'ru' : 'en';
|
||||||
localStorage.setItem('lang', currentLang);
|
localStorage.setItem('lang', currentLang);
|
||||||
document.getElementById('lang-btn').textContent = currentLang.toUpperCase();
|
document.getElementById('lang-btn').textContent = currentLang.toUpperCase();
|
||||||
translatePage();
|
|
||||||
translatePage();
|
translatePage();
|
||||||
|
refreshDynamicI18n();
|
||||||
updateHelpLink();
|
updateHelpLink();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8688,6 +8711,29 @@ function translatePage() {
|
|||||||
if (slogan) slogan.textContent = t('SIM OTA with a Human Face');
|
if (slogan) slogan.textContent = t('SIM OTA with a Human Face');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isViewVisible(id) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
return !!el && !el.classList.contains('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamic views build their labels with t() at render time; re-render the
|
||||||
|
// visible ones after a language switch so they don't stay in the old language.
|
||||||
|
function refreshDynamicI18n() {
|
||||||
|
if (isViewVisible('pysim-sub-profiler')) {
|
||||||
|
if (profilerView === 'list') { profilerRenderList(); profilerRenderSnapshotList(); }
|
||||||
|
else if (profilerView === 'editor') profilerRenderEditor();
|
||||||
|
else if (profilerView === 'results') profilerRenderResultsView();
|
||||||
|
else if (profilerView === 'snapshot') profilerRenderSnapshotData();
|
||||||
|
}
|
||||||
|
if (isViewVisible('profiler-scan-modal')) profilerScanRefreshOptions();
|
||||||
|
if (isViewVisible('scp80-sub-cards')) cardsRender();
|
||||||
|
if (isViewVisible('pysim-sub-proactive')) {
|
||||||
|
pysimEventsRender();
|
||||||
|
pysimProactiveLogRender();
|
||||||
|
pysimPliRender();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Init language
|
// Init language
|
||||||
const userLang = (navigator.language || '').split('-')[0];
|
const userLang = (navigator.language || '').split('-')[0];
|
||||||
currentLang = localStorage.getItem('lang') || (userLang === 'ru' ? 'ru' : 'en');
|
currentLang = localStorage.getItem('lang') || (userLang === 'ru' ? 'ru' : 'en');
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const CACHE = 'otaman-v83';
|
const CACHE = 'otaman-v84';
|
||||||
const URLS = [
|
const URLS = [
|
||||||
'index.html',
|
'index.html',
|
||||||
'help.html',
|
'help.html',
|
||||||
|
|||||||
@@ -21,13 +21,14 @@ function extractFunc(src, name, asyncFn) {
|
|||||||
return (asyncFn ? 'async ' : '') + src.slice(m.index, i + 1);
|
return (asyncFn ? 'async ' : '') + src.slice(m.index, i + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const FNS = ['profilerNormHex', 'profilerNormHexStrict', 'profilerMatch', 'profilerMatchMin', 'profilerMaskPrefix4', 'profilerFileFields', 'profilerContentKindForFileType', 'profilerEmptyRecordContent', 'profilerValidateProfile', 'profilerCustomNameForPath', 'profilerUpdateRulePath', 'profilerResultAspects', 'profilerAspectSummary', 'profilerNumRanges', 'esc', 'escHtml', 'profilerRawDataCheck', 'profilerRenderReport', 'parseBerLen', 'parseTlvList', 'fcpInt', 'fcpParseTlvs', 'fcpFileDescriptor', 'fcpLifeCycle', 'fcpSfi', 'fcpDo', 'fcpDecode', 'fcpDiffHtml', 'profilerFciPreviewItems', 'profilerUpdateFciPreview', 'profilerUpdateRule', 'profilerFciInput', 'profilerScanToggleAll', 'profilerScanIgnoreAllState', 'swapNibbles', 'decIccid', 'profilerSnapshotIccid', 'profilerValidateSnapshot', 'profilerListSwitch'];
|
const FNS = ['profilerNormHex', 'profilerNormHexStrict', 'profilerMatch', 'profilerMatchMin', 'profilerMaskPrefix4', 'profilerFileFields', 'profilerContentKindForFileType', 'profilerEmptyRecordContent', 'profilerValidateProfile', 'profilerCustomNameForPath', 'profilerUpdateRulePath', 'profilerResultAspects', 'profilerAspectSummary', 'profilerNumRanges', 'esc', 'escHtml', 'profilerRawDataCheck', 'profilerRenderReport', 'parseBerLen', 'parseTlvList', 'fcpInt', 'fcpParseTlvs', 'fcpFileDescriptor', 'fcpLifeCycle', 'fcpSfi', 'fcpDo', 'fcpDecode', 'fcpDiffHtml', 'profilerFciPreviewItems', 'profilerUpdateFciPreview', 'profilerUpdateRule', 'profilerFciInput', 'profilerScanToggleAll', 'profilerScanIgnoreAllState', 'swapNibbles', 'decIccid', 'profilerSnapshotIccid', 'profilerValidateSnapshot', 'profilerListSwitch', 'profilerScanRefreshOptions'];
|
||||||
let code = '';
|
let code = '';
|
||||||
for (const f of FNS) code += extractFunc(html, f) + '\n';
|
for (const f of FNS) code += extractFunc(html, f) + '\n';
|
||||||
code += extractFunc(html, 'profilerBuildFileRule', true) + '\n';
|
code += extractFunc(html, 'profilerBuildFileRule', true) + '\n';
|
||||||
code += extractFunc(html, 'profilerRunRule', true) + '\n';
|
code += extractFunc(html, 'profilerRunRule', true) + '\n';
|
||||||
code += extractFunc(html, 'profilerScanCard', true) + '\n';
|
code += extractFunc(html, 'profilerScanCard', true) + '\n';
|
||||||
code += extractFunc(html, 'profilerBuildSnapshotFile', true) + '\n';
|
code += extractFunc(html, 'profilerBuildSnapshotFile', true) + '\n';
|
||||||
|
code += "var _scanTarget = 'profile';\n";
|
||||||
code += html.match(/const PROFILER_MASK_PREFIX4_FIDS = \{[\s\S]*?\n\};/)[0] + '\n';
|
code += html.match(/const PROFILER_MASK_PREFIX4_FIDS = \{[\s\S]*?\n\};/)[0] + '\n';
|
||||||
eval(code);
|
eval(code);
|
||||||
|
|
||||||
@@ -942,3 +943,21 @@ test('profilerListSwitch toggles the profiles/snapshots tabs', () => {
|
|||||||
|
|
||||||
delete global.document;
|
delete global.document;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('profilerScanRefreshOptions re-translates mask labels without touching checkbox state', () => {
|
||||||
|
global.t = s => (s === 'Match first 4 bytes for' ? 'FIRST4' : s);
|
||||||
|
const span = { textContent: '' };
|
||||||
|
const cb = {
|
||||||
|
checked: true,
|
||||||
|
parentElement: { querySelector: sel => (sel === 'span' ? span : null) },
|
||||||
|
getAttribute: () => '6F07',
|
||||||
|
};
|
||||||
|
global.document = {
|
||||||
|
querySelectorAll: sel => (sel === '#profiler-scan-mask input[data-mask-fid]' ? [cb] : []),
|
||||||
|
};
|
||||||
|
profilerScanRefreshOptions();
|
||||||
|
assert.strictEqual(span.textContent, 'FIRST4 EF.IMSI');
|
||||||
|
assert.strictEqual(cb.checked, true);
|
||||||
|
delete global.document;
|
||||||
|
delete global.t;
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user