diff --git a/src/web/public/app.js b/src/web/public/app.js index d7246a4f..0381fa77 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -381,8 +381,17 @@ function applyPaneExitBadge(tab, paneExit) { tab.classList.toggle('tab-agent-exited', !!label); // The tab's aria-label overrides its contents for the accessible name, and the // badge is aria-hidden like its siblings, so the exit has to ride the label. + // Compared with the last English label set (data-aria-source, seeded by the + // full render too), never the attribute: in zh-CN the translator rewrites it, + // and writing English back on every pass would have it translate again. const name = tab.querySelector('.tab-name')?.dataset?.fullName; - if (name) tab.setAttribute('aria-label', paneExitAriaLabel(name, label)); + if (name) { + const aria = paneExitAriaLabel(name, label); + if (tab.dataset.ariaSource !== aria) { + tab.dataset.ariaSource = aria; + tab.setAttribute('aria-label', aria); + } + } if (!label) { existing?.remove(); return; @@ -391,16 +400,19 @@ function applyPaneExitBadge(tab, paneExit) { const badge = document.createElement('span'); badge.className = 'tab-exited-badge'; badge.setAttribute('aria-hidden', 'true'); - // Generated status text, like the status pills: it carries data-i18n-skip - // rather than a dictionary entry. Without it the translator would rewrite - // the badge and the next render pass would rewrite it back, because the - // comparison below is against the English string. - badge.setAttribute('data-i18n-skip', ''); + // Translated like any other text (i18n.js has "exited" and its exit-code + // forms). The comparison below is with the last English label (data-label), + // never the DOM, which holds the translation in zh-CN: a DOM compare would + // write the English back on every pass for the translator to redo. + badge.dataset.label = label; badge.textContent = label; tab.querySelector('.tab-name')?.insertAdjacentElement('afterend', badge); return; } - if (existing.textContent !== label) existing.textContent = label; + if (existing.dataset.label !== label) { + existing.dataset.label = label; + existing.textContent = label; + } } const DEFAULT_SHORTCUTS = [ @@ -6022,7 +6034,7 @@ class CodemanApp { const inlineSessionActions = this.shouldInlineSessionActions(); const tabActionsHtml = `⚙⧉×`; - rowHtml.set(id, `