From 031a3ce2e933f87bdbe7feb338bea19b53c88ddc Mon Sep 17 00:00:00 2001
From: nesquena-hermes <[email protected]>
Date: Mon, 1 Jun 2026 23:03:48 +0000
Subject: [PATCH] polish(#3220): esc() download label + scrub orphaned
media_open i18n keys
Opus iter2 SHOULD-FIX (both non-blocking, applied for cleanliness):
- esc() the media_download title/aria-label so a future translator's quote
can't break out of the attribute (defense-in-depth).
- Remove the now-dead media_open key from all 11 locales + the ui.js fallback
dict (the Open button was dropped in the clean-image redesign).
---
static/i18n.js | 11 -----------
static/ui.js | 5 +++--
2 files changed, 3 insertions(+), 13 deletions(-)
diff --git a/static/i18n.js b/static/i18n.js
index 12860a37..04ff707d 100644
--- a/static/i18n.js
+++ b/static/i18n.js
@@ -1297,7 +1297,6 @@ const LOCALES = {
media_audio_label: 'Audio',
media_svg_label: 'Diagram',
media_video_label: 'Video',
- media_open: 'Open',
media_download: 'Download',
csv_loading: 'Loading CSV',
csv_too_large: 'CSV file too large for inline rendering',
@@ -2616,7 +2615,6 @@ const LOCALES = {
media_audio_label: 'Audio',
media_svg_label: 'Diagramma',
media_video_label: 'Video',
- media_open: 'Apri',
media_download: 'Scarica',
csv_loading: 'Caricamento CSV',
csv_too_large: 'File CSV troppo grande per il rendering inline',
@@ -3940,7 +3938,6 @@ const LOCALES = {
media_audio_label: '音声',
media_svg_label: '図',
media_video_label: '動画',
- media_open: '開く',
media_download: 'ダウンロード',
csv_loading: 'CSV を読み込み中',
csv_too_large: 'CSV ファイルが大きすぎてインライン表示できません',
@@ -5136,7 +5133,6 @@ const LOCALES = {
media_audio_label: 'Аудио',
media_svg_label: 'Диаграмма',
media_video_label: 'Видео',
- media_open: 'Открыть',
media_download: 'Скачать',
csv_loading: 'Загрузка CSV',
csv_too_large: 'CSV-файл слишком большой для встроенного отображения',
@@ -6380,7 +6376,6 @@ const LOCALES = {
media_audio_label: 'Audio',
media_svg_label: 'Diagrama',
media_video_label: 'Vídeo',
- media_open: 'Abrir',
media_download: 'Descargar',
csv_loading: 'Cargando CSV',
csv_too_large: 'Archivo CSV demasiado grande para mostrar en línea',
@@ -7638,7 +7633,6 @@ const LOCALES = {
media_audio_label: 'Audio',
media_svg_label: 'Diagramm',
media_video_label: 'Video',
- media_open: 'Öffnen',
media_download: 'Herunterladen',
csv_loading: 'CSV wird geladen',
csv_too_large: 'CSV-Datei zu groß für Inline-Anzeige',
@@ -8901,7 +8895,6 @@ const LOCALES = {
media_audio_label: '音频',
media_svg_label: '图表',
media_video_label: '视频',
- media_open: '打开',
media_download: '下载',
csv_loading: '加载 CSV',
csv_too_large: 'CSV 文件过大,无法内联渲染',
@@ -10217,7 +10210,6 @@ const LOCALES = {
media_audio_label: '音訊',
media_svg_label: '圖表',
media_video_label: '影片',
- media_open: '開啟',
media_download: '下載',
csv_loading: '載入 CSV',
csv_too_large: 'CSV 檔案過大,無法內嵌渲染',
@@ -12673,7 +12665,6 @@ const LOCALES = {
media_audio_label: '오디오',
media_svg_label: '다이어그램',
media_video_label: '비디오',
- media_open: '열기',
media_download: '다운로드',
csv_loading: 'CSV 로딩 중',
csv_too_large: 'CSV 파일이 너무 커서 인라인 렌더링할 수 없습니다',
@@ -13895,7 +13886,6 @@ const LOCALES = {
media_audio_label: 'Audio',
media_svg_label: 'Diagramme',
media_video_label: 'Vidéo',
- media_open: 'Ouvrir',
media_download: 'Télécharger',
csv_loading: 'Chargement du fichier CSV',
csv_too_large: 'Fichier CSV trop volumineux pour le rendu en ligne',
@@ -15245,7 +15235,6 @@ const LOCALES = {
media_audio_label: 'Ses',
media_svg_label: 'Diyagram',
media_video_label: 'Video',
- media_open: 'Aç',
media_download: 'İndir',
csv_loading: 'CSV yükleniyor',
csv_too_large: 'CSV dosyası satır içi oluşturma için çok büyük',
diff --git a/static/ui.js b/static/ui.js
index a8b6ff1c..0b4b260d 100644
--- a/static/ui.js
+++ b/static/ui.js
@@ -3507,14 +3507,15 @@ function renderMd(raw){
const localArtifactCard=(src,name)=>{
const safeSrc=esc(src);
const safeName=esc(name||'image');
- const tt=(typeof t==='function')?t:(key=>({media_open:'Open',media_download:'Download'}[key]||key));
+ const tt=(typeof t==='function')?t:(key=>({media_download:'Download'}[key]||key));
// Clean inline image (keeps the existing .msg-media-img lightbox-on-click
// behavior) with a hover/focus-revealed Download action overlaid top-right,
// matching the ChatGPT/Claude/Gemini pattern. The image stays the hero —
// no permanent card chrome. Download is the one affordance the lightbox
// (zoom-on-click) doesn't already provide.
+ const dlLabel=esc(tt('media_download'));
const dlSvg='';
- return `
${dlSvg}`;
+ return `
${dlSvg}`;
};
if(/^file:\/\//i.test(ref)){
try{