From 7ebd898c5e1bc7918b219e9007a9e693c8f3df39 Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Sat, 4 Jul 2026 13:37:39 -0400 Subject: [PATCH 1/4] feat(#5533): make preview file paths copyable --- static/index.html | 1 + static/style.css | 2 +- static/workspace.js | 29 ++++++++ .../test_issue5533_preview_copy_file_path.py | 69 +++++++++++++++++++ 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 tests/test_issue5533_preview_copy_file_path.py diff --git a/static/index.html b/static/index.html index 4d40fed8..7e24f0fc 100644 --- a/static/index.html +++ b/static/index.html @@ -1645,6 +1645,7 @@
+ diff --git a/static/style.css b/static/style.css index 70509695..d7943d8c 100644 --- a/static/style.css +++ b/static/style.css @@ -3377,7 +3377,7 @@ body.tts-enabled .msg-tts-btn{display:inline-flex;align-items:center;} .preview-path{display:flex;align-items:center;gap:6px;flex-wrap:nowrap;overflow:hidden;min-width:0;} .preview-path #previewPathText{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;} .preview-path #previewBadge{flex-shrink:0;white-space:nowrap;} -.preview-path #btnDownloadFile,.preview-path #btnEditFile,.preview-path #btnRenderMarkdownAnyway{flex-shrink:0;white-space:nowrap;} +.preview-path #btnCopyPreviewPath,.preview-path #btnDownloadFile,.preview-path #btnEditFile,.preview-path #btnRenderMarkdownAnyway{flex-shrink:0;white-space:nowrap;} /* Preview badge typography */ .preview-badge{font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;} diff --git a/static/workspace.js b/static/workspace.js index 296d6be7..f3c60a8e 100644 --- a/static/workspace.js +++ b/static/workspace.js @@ -1216,6 +1216,35 @@ function openInBrowser(){ } // openInBrowser keeps the helper-based raw path, which expands to an explicit &inline=1 URL. +async function copyPreviewFilePath(){ + if(!_previewCurrentPath||!S.session) return; + try{ + const r=await api('/api/file/path',{method:'POST',body:JSON.stringify({session_id:S.session.session_id,path:_previewCurrentPath})}); + const abs=(r&&r.path)||_previewCurrentPath; + if(typeof _copyTextWithFallback==='function'){ + await _copyTextWithFallback(abs,t('path_copied'),t('path_copy_failed')); + return; + } + try{ + await navigator.clipboard.writeText(abs); + showToast(t('path_copied')); + }catch(clipErr){ + const ta=document.createElement('textarea'); + ta.value=abs; + ta.style.cssText='position:fixed;left:-9999px;top:-9999px;'; + document.body.appendChild(ta); + ta.select(); + let copied=false; + try{copied=document.execCommand('copy');}catch(_){} + ta.remove(); + if(copied) showToast(t('path_copied')); + else showToast(t('path_copy_failed')+(clipErr&&clipErr.message?clipErr.message:String(clipErr))); + } + }catch(err){ + showToast(t('path_copy_failed')+(err.message||err)); + } +} + // ── Workspace upload ────────────────────────────────────────────────── function triggerWorkspaceUpload() { if(_workspacePathIsReadOnly(S.currentDir || '.')){ diff --git a/tests/test_issue5533_preview_copy_file_path.py b/tests/test_issue5533_preview_copy_file_path.py new file mode 100644 index 00000000..9c1999c1 --- /dev/null +++ b/tests/test_issue5533_preview_copy_file_path.py @@ -0,0 +1,69 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +INDEX = (ROOT / "static" / "index.html").read_text(encoding="utf-8") +WORKSPACE_JS = (ROOT / "static" / "workspace.js").read_text(encoding="utf-8") +STYLE = (ROOT / "static" / "style.css").read_text(encoding="utf-8") + + +def _function_body(src: str, name: str) -> str: + start = src.index(f"function {name}(") + brace = src.index("{", start) + depth = 0 + in_string = "" + escape = False + for idx in range(brace, len(src)): + ch = src[idx] + if in_string: + if escape: + escape = False + elif ch == "\\": + escape = True + elif ch == in_string: + in_string = "" + continue + if ch in "'\"`": + in_string = ch + continue + if ch == "{": + depth += 1 + elif ch == "}": + depth -= 1 + if depth == 0: + return src[start : idx + 1] + raise AssertionError(f"{name} body did not close") + + +def test_preview_toolbar_has_copy_file_path_button(): + assert 'id="btnCopyPreviewPath"' in INDEX + assert 'onclick="copyPreviewFilePath()"' in INDEX + assert 'data-i18n="copy_file_path"' in INDEX + assert "Copy file path" in INDEX + + +def test_preview_copy_file_path_uses_server_resolved_current_preview_path(): + body = _function_body(WORKSPACE_JS, "copyPreviewFilePath") + assert "if(!_previewCurrentPath||!S.session) return;" in body + assert "api('/api/file/path'" in body + assert "session_id:S.session.session_id" in body + assert "path:_previewCurrentPath" in body + assert "const abs=(r&&r.path)||_previewCurrentPath" in body + + +def test_preview_copy_file_path_reuses_clipboard_fallback_and_toasts(): + body = _function_body(WORKSPACE_JS, "copyPreviewFilePath") + assert "typeof _copyTextWithFallback==='function'" in body + assert "_copyTextWithFallback(abs,t('path_copied'),t('path_copy_failed'))" in body + assert "navigator.clipboard.writeText(abs)" in body + assert "document.execCommand('copy')" in body + assert "t('path_copied')" in body + assert "t('path_copy_failed')" in body + + +def test_preview_toolbar_keeps_copy_button_from_shrinking_path_layout(): + assert ".preview-path #btnCopyPreviewPath" in STYLE + selector_start = STYLE.index(".preview-path #btnCopyPreviewPath") + selector_block = STYLE[selector_start : STYLE.index("}", selector_start) + 1] + assert "flex-shrink:0" in selector_block + assert "white-space:nowrap" in selector_block From 670976a28097d6a3ca8f35af4f54272ce4318a26 Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Sat, 4 Jul 2026 13:55:28 -0400 Subject: [PATCH 2/4] Prevent duplicate preview path copy requests --- static/workspace.js | 5 ++++ .../test_issue5533_preview_copy_file_path.py | 27 +++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/static/workspace.js b/static/workspace.js index f3c60a8e..35b0c4bf 100644 --- a/static/workspace.js +++ b/static/workspace.js @@ -1218,6 +1218,9 @@ function openInBrowser(){ async function copyPreviewFilePath(){ if(!_previewCurrentPath||!S.session) return; + const btn=$('btnCopyPreviewPath'); + if(btn&&btn.disabled) return; + if(btn) btn.disabled=true; try{ const r=await api('/api/file/path',{method:'POST',body:JSON.stringify({session_id:S.session.session_id,path:_previewCurrentPath})}); const abs=(r&&r.path)||_previewCurrentPath; @@ -1242,6 +1245,8 @@ async function copyPreviewFilePath(){ } }catch(err){ showToast(t('path_copy_failed')+(err.message||err)); + }finally{ + if(btn) btn.disabled=false; } } diff --git a/tests/test_issue5533_preview_copy_file_path.py b/tests/test_issue5533_preview_copy_file_path.py index 9c1999c1..b611234e 100644 --- a/tests/test_issue5533_preview_copy_file_path.py +++ b/tests/test_issue5533_preview_copy_file_path.py @@ -35,6 +35,10 @@ def _function_body(src: str, name: str) -> str: raise AssertionError(f"{name} body did not close") +def _compact(src: str) -> str: + return "".join(src.split()) + + def test_preview_toolbar_has_copy_file_path_button(): assert 'id="btnCopyPreviewPath"' in INDEX assert 'onclick="copyPreviewFilePath()"' in INDEX @@ -44,11 +48,30 @@ def test_preview_toolbar_has_copy_file_path_button(): def test_preview_copy_file_path_uses_server_resolved_current_preview_path(): body = _function_body(WORKSPACE_JS, "copyPreviewFilePath") - assert "if(!_previewCurrentPath||!S.session) return;" in body + compact = _compact(body) + + assert "_previewCurrentPath" in body + assert "S.session" in body + assert body.index("_previewCurrentPath") < body.index("api('/api/file/path'") assert "api('/api/file/path'" in body assert "session_id:S.session.session_id" in body assert "path:_previewCurrentPath" in body - assert "const abs=(r&&r.path)||_previewCurrentPath" in body + assert "constabs=(r&&r.path)||_previewCurrentPath" in compact + + +def test_preview_copy_file_path_disables_button_while_request_is_in_flight(): + body = _function_body(WORKSPACE_JS, "copyPreviewFilePath") + compact = _compact(body) + + guard = "if(btn&&btn.disabled)return;" + disable = "if(btn)btn.disabled=true;" + enable = "finally{if(btn)btn.disabled=false;}" + assert "$('btnCopyPreviewPath')" in body + assert guard in compact + assert disable in compact + assert enable in compact + assert compact.index(guard) < compact.index(disable) + assert compact.index(disable) < compact.index("api('/api/file/path'") def test_preview_copy_file_path_reuses_clipboard_fallback_and_toasts(): From 71a6259c84104f1372990d1783f33d6fdc52bfac Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Sat, 4 Jul 2026 15:26:25 -0400 Subject: [PATCH 3/4] Align copied paths with chat references --- static/i18n.js | 1 + static/index.html | 2 +- static/style.css | 2 +- static/ui.js | 16 ++++++ static/workspace.js | 15 +++--- .../test_issue5533_preview_copy_file_path.py | 51 ++++++++++--------- 6 files changed, 54 insertions(+), 33 deletions(-) diff --git a/static/i18n.js b/static/i18n.js index 4055d47a..5ef083ff 100644 --- a/static/i18n.js +++ b/static/i18n.js @@ -484,6 +484,7 @@ const LOCALES = { reveal_in_finder: 'Reveal in File Manager', reveal_failed: 'Failed to reveal: ', copy_file_path: 'Copy file path', + copy_relative_path: 'Copy relative path', open_in_vscode: 'Open in VS Code', open_in_vscode_failed: 'Failed to open in VS Code: ', download_folder: 'Download Folder', diff --git a/static/index.html b/static/index.html index 7e24f0fc..e439b89a 100644 --- a/static/index.html +++ b/static/index.html @@ -1645,7 +1645,7 @@
- + diff --git a/static/style.css b/static/style.css index d7943d8c..3bdc84ed 100644 --- a/static/style.css +++ b/static/style.css @@ -3377,7 +3377,7 @@ body.tts-enabled .msg-tts-btn{display:inline-flex;align-items:center;} .preview-path{display:flex;align-items:center;gap:6px;flex-wrap:nowrap;overflow:hidden;min-width:0;} .preview-path #previewPathText{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;} .preview-path #previewBadge{flex-shrink:0;white-space:nowrap;} -.preview-path #btnCopyPreviewPath,.preview-path #btnDownloadFile,.preview-path #btnEditFile,.preview-path #btnRenderMarkdownAnyway{flex-shrink:0;white-space:nowrap;} +.preview-path #btnCopyPreviewRelPath,.preview-path #btnDownloadFile,.preview-path #btnEditFile,.preview-path #btnRenderMarkdownAnyway{flex-shrink:0;white-space:nowrap;} /* Preview badge typography */ .preview-badge{font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;} diff --git a/static/ui.js b/static/ui.js index 170eb14e..78b34ea3 100644 --- a/static/ui.js +++ b/static/ui.js @@ -17301,6 +17301,22 @@ function _showFileContextMenu(e, item){ } }; menu.appendChild(copyPathItem); + + const copyRelPathItem=document.createElement('div'); + copyRelPathItem.textContent=t('copy_relative_path'); + copyRelPathItem.style.cssText='padding:7px 14px;cursor:pointer;font-size:13px;color:var(--text);'; + copyRelPathItem.onmouseenter=()=>copyRelPathItem.style.background='var(--hover-bg)'; + copyRelPathItem.onmouseleave=()=>copyRelPathItem.style.background=''; + copyRelPathItem.onclick=async()=>{ + menu.remove(); + try{ + const rel=_normalizeWorkspaceRelPath(item.path)||item.path; + await _copyTextWithFallback(rel,t('path_copied'),t('path_copy_failed')); + }catch(err){ + showToast(t('path_copy_failed')+(err.message||err)); + } + }; + menu.appendChild(copyRelPathItem); } if(isDirLike){ diff --git a/static/workspace.js b/static/workspace.js index 35b0c4bf..97dbc4af 100644 --- a/static/workspace.js +++ b/static/workspace.js @@ -1216,24 +1216,23 @@ function openInBrowser(){ } // openInBrowser keeps the helper-based raw path, which expands to an explicit &inline=1 URL. -async function copyPreviewFilePath(){ - if(!_previewCurrentPath||!S.session) return; - const btn=$('btnCopyPreviewPath'); +async function copyPreviewRelativePath(){ + if(!_previewCurrentPath) return; + const btn=$('btnCopyPreviewRelPath'); if(btn&&btn.disabled) return; if(btn) btn.disabled=true; try{ - const r=await api('/api/file/path',{method:'POST',body:JSON.stringify({session_id:S.session.session_id,path:_previewCurrentPath})}); - const abs=(r&&r.path)||_previewCurrentPath; + const rel=_normalizeWorkspaceRelPath(_previewCurrentPath)||_previewCurrentPath; if(typeof _copyTextWithFallback==='function'){ - await _copyTextWithFallback(abs,t('path_copied'),t('path_copy_failed')); + await _copyTextWithFallback(rel,t('path_copied'),t('path_copy_failed')); return; } try{ - await navigator.clipboard.writeText(abs); + await navigator.clipboard.writeText(rel); showToast(t('path_copied')); }catch(clipErr){ const ta=document.createElement('textarea'); - ta.value=abs; + ta.value=rel; ta.style.cssText='position:fixed;left:-9999px;top:-9999px;'; document.body.appendChild(ta); ta.select(); diff --git a/tests/test_issue5533_preview_copy_file_path.py b/tests/test_issue5533_preview_copy_file_path.py index b611234e..9d4ad821 100644 --- a/tests/test_issue5533_preview_copy_file_path.py +++ b/tests/test_issue5533_preview_copy_file_path.py @@ -4,6 +4,7 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[1] INDEX = (ROOT / "static" / "index.html").read_text(encoding="utf-8") WORKSPACE_JS = (ROOT / "static" / "workspace.js").read_text(encoding="utf-8") +UI_JS = (ROOT / "static" / "ui.js").read_text(encoding="utf-8") STYLE = (ROOT / "static" / "style.css").read_text(encoding="utf-8") @@ -39,54 +40,58 @@ def _compact(src: str) -> str: return "".join(src.split()) -def test_preview_toolbar_has_copy_file_path_button(): - assert 'id="btnCopyPreviewPath"' in INDEX - assert 'onclick="copyPreviewFilePath()"' in INDEX - assert 'data-i18n="copy_file_path"' in INDEX - assert "Copy file path" in INDEX +def test_preview_toolbar_has_copy_relative_path_button(): + assert 'id="btnCopyPreviewRelPath"' in INDEX + assert 'onclick="copyPreviewRelativePath()"' in INDEX + assert 'data-i18n="copy_relative_path"' in INDEX + assert "Copy relative path" in INDEX -def test_preview_copy_file_path_uses_server_resolved_current_preview_path(): - body = _function_body(WORKSPACE_JS, "copyPreviewFilePath") +def test_preview_copy_relative_path_uses_current_preview_path(): + body = _function_body(WORKSPACE_JS, "copyPreviewRelativePath") compact = _compact(body) assert "_previewCurrentPath" in body - assert "S.session" in body - assert body.index("_previewCurrentPath") < body.index("api('/api/file/path'") - assert "api('/api/file/path'" in body - assert "session_id:S.session.session_id" in body - assert "path:_previewCurrentPath" in body - assert "constabs=(r&&r.path)||_previewCurrentPath" in compact + assert "_normalizeWorkspaceRelPath(_previewCurrentPath)" in body + assert "api('/api/file/path'" not in body + assert "constrel=_normalizeWorkspaceRelPath(_previewCurrentPath)||_previewCurrentPath" in compact -def test_preview_copy_file_path_disables_button_while_request_is_in_flight(): - body = _function_body(WORKSPACE_JS, "copyPreviewFilePath") +def test_preview_copy_relative_path_disables_button_while_request_is_in_flight(): + body = _function_body(WORKSPACE_JS, "copyPreviewRelativePath") compact = _compact(body) guard = "if(btn&&btn.disabled)return;" disable = "if(btn)btn.disabled=true;" enable = "finally{if(btn)btn.disabled=false;}" - assert "$('btnCopyPreviewPath')" in body + assert "$('btnCopyPreviewRelPath')" in body assert guard in compact assert disable in compact assert enable in compact assert compact.index(guard) < compact.index(disable) - assert compact.index(disable) < compact.index("api('/api/file/path'") + assert compact.index(disable) < compact.index("_normalizeWorkspaceRelPath") -def test_preview_copy_file_path_reuses_clipboard_fallback_and_toasts(): - body = _function_body(WORKSPACE_JS, "copyPreviewFilePath") +def test_preview_copy_relative_path_reuses_clipboard_fallback_and_toasts(): + body = _function_body(WORKSPACE_JS, "copyPreviewRelativePath") assert "typeof _copyTextWithFallback==='function'" in body - assert "_copyTextWithFallback(abs,t('path_copied'),t('path_copy_failed'))" in body - assert "navigator.clipboard.writeText(abs)" in body + assert "_copyTextWithFallback(rel,t('path_copied'),t('path_copy_failed'))" in body + assert "navigator.clipboard.writeText(rel)" in body assert "document.execCommand('copy')" in body assert "t('path_copied')" in body assert "t('path_copy_failed')" in body +def test_tree_context_menu_keeps_absolute_copy_and_adds_relative_copy(): + assert "copyPathItem.textContent=t('copy_file_path')" in UI_JS + assert "copyRelPathItem.textContent=t('copy_relative_path')" in UI_JS + assert "const rel=_normalizeWorkspaceRelPath(item.path)||item.path" in UI_JS + assert "_copyTextWithFallback(rel,t('path_copied'),t('path_copy_failed'))" in UI_JS + + def test_preview_toolbar_keeps_copy_button_from_shrinking_path_layout(): - assert ".preview-path #btnCopyPreviewPath" in STYLE - selector_start = STYLE.index(".preview-path #btnCopyPreviewPath") + assert ".preview-path #btnCopyPreviewRelPath" in STYLE + selector_start = STYLE.index(".preview-path #btnCopyPreviewRelPath") selector_block = STYLE[selector_start : STYLE.index("}", selector_start) + 1] assert "flex-shrink:0" in selector_block assert "white-space:nowrap" in selector_block From bac619e7677b41926e20f375b959ed08c41403b6 Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Sat, 4 Jul 2026 15:47:50 -0400 Subject: [PATCH 4/4] fix(i18n): add relative path labels (#5533) --- static/i18n.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/static/i18n.js b/static/i18n.js index 5ef083ff..83d412d2 100644 --- a/static/i18n.js +++ b/static/i18n.js @@ -2162,6 +2162,7 @@ const LOCALES = { reveal_in_finder: 'Mostra nel File Manager', reveal_failed: 'Mostra fallito: ', copy_file_path: 'Copia percorso file', + copy_relative_path: 'Copia percorso relativo', open_in_vscode: 'Apri in VS Code', open_in_vscode_failed: 'Apertura in VS Code fallita: ', download_folder: 'Download Folder', // TODO: translate @@ -3830,6 +3831,7 @@ const LOCALES = { reveal_in_finder: 'ファイルマネージャーで表示', reveal_failed: '表示に失敗しました: ', copy_file_path: 'ファイルパスをコピー', + copy_relative_path: '相対パスをコピー', open_in_vscode: 'VS Codeで開く', open_in_vscode_failed: 'VS Codeで開けませんでした: ', download_folder: 'フォルダをダウンロード', @@ -5422,6 +5424,7 @@ const LOCALES = { reveal_in_finder: 'Показать в файловом менеджере', reveal_failed: 'Не удалось открыть: ', copy_file_path: 'Копировать путь к файлу', + copy_relative_path: 'Копировать относительный путь', open_in_vscode: 'Открыть в VS Code', open_in_vscode_failed: 'Не удалось открыть в VS Code: ', download_folder: 'Download Folder', // TODO: translate @@ -7007,6 +7010,7 @@ const LOCALES = { reveal_in_finder: 'Mostrar en el gestor de archivos', reveal_failed: 'Error al mostrar: ', copy_file_path: 'Copiar ruta del archivo', + copy_relative_path: 'Copiar ruta relativa', open_in_vscode: 'Abrir en VS Code', open_in_vscode_failed: 'Error al abrir en VS Code: ', download_folder: 'Download Folder', // TODO: translate @@ -8590,6 +8594,7 @@ const LOCALES = { reveal_in_finder: 'Im Dateimanager anzeigen', reveal_failed: 'Anzeige fehlgeschlagen: ', copy_file_path: 'Dateipfad kopieren', + copy_relative_path: 'Relativen Pfad kopieren', open_in_vscode: 'In VS Code öffnen', open_in_vscode_failed: 'In VS Code öffnen fehlgeschlagen: ', download_folder: 'Download Folder', // TODO: translate @@ -10238,6 +10243,7 @@ const LOCALES = { reveal_in_finder: '在文件管理器中显示', reveal_failed: '显示失败:', copy_file_path: '\u590d\u5236\u6587\u4ef6\u8def\u5f84', + copy_relative_path: '\u590d\u5236\u76f8\u5bf9\u8def\u5f84', open_in_vscode: '在VS Code中打开', open_in_vscode_failed: '在VS Code中打开失败:', download_folder: '下载文件夹', @@ -11920,6 +11926,7 @@ const LOCALES = { reveal_in_finder: '在檔案管理員中顯示', reveal_failed: '顯示失敗:', copy_file_path: '複製檔案路徑', + copy_relative_path: '複製相對路徑', open_in_vscode: '在 VS Code 中開啟', open_in_vscode_failed: '在 VS Code 中開啟失敗:', download_folder: '下載資料夾', @@ -13489,6 +13496,7 @@ const LOCALES = { reveal_in_finder: 'Mostrar no gerenciador de arquivos', reveal_failed: 'Falha ao mostrar: ', copy_file_path: 'Copiar caminho do arquivo', + copy_relative_path: 'Copiar caminho relativo', open_in_vscode: 'Abrir no VS Code', open_in_vscode_failed: 'Falha ao abrir no VS Code: ', download_folder: 'Download Folder', // TODO: translate @@ -15051,6 +15059,7 @@ const LOCALES = { reveal_in_finder: '파일 관리자에서 열기', reveal_failed: '표시 실패: ', copy_file_path: '파일 경로 복사', + copy_relative_path: '상대 경로 복사', open_in_vscode: 'VS Code에서 열기', open_in_vscode_failed: 'VS Code에서 열기 실패: ', download_folder: 'Download Folder', // TODO: translate @@ -16732,6 +16741,7 @@ const LOCALES = { reveal_in_finder: 'Révéler dans le gestionnaire de fichiers', reveal_failed: 'Échec de la révélation : ', copy_file_path: 'Copier le chemin du fichier', + copy_relative_path: 'Copier le chemin relatif', open_in_vscode: 'Ouvrir dans VS Code', open_in_vscode_failed: 'Échec de l\'ouverture dans VS Code : ', download_folder: 'Télécharger le dossier', @@ -18369,6 +18379,7 @@ const LOCALES = { reveal_in_finder: 'Dosya Yöneticisinde Göster', reveal_failed: 'Açıklanamadı:', copy_file_path: 'Dosya yolunu kopyala', + copy_relative_path: 'Göreli yolu kopyala', download_folder: 'İndirme klasörü', path_copied: 'Dosya yolu panoya kopyalandı', path_copy_failed: 'Yol kopyalanamadı:', @@ -20051,6 +20062,7 @@ const LOCALES = { reveal_in_finder: 'Pokaż w Menedżerze Plików', reveal_failed: 'Nie udało się pokazać: ', copy_file_path: 'Kopiuj ścieżkę pliku', + copy_relative_path: 'Kopiuj ścieżkę względną', open_in_vscode: 'Otwórz w VS Code', open_in_vscode_failed: 'Nie udało się otworzyć w VS Code: ', download_folder: 'Pobierz folder', @@ -21671,6 +21683,7 @@ const LOCALES = { reveal_in_finder: 'Hiện trong trình quản lý tệp', reveal_failed: 'Hiển thị thất bại: ', copy_file_path: 'Sao chép đường dẫn file', + copy_relative_path: 'Sao chép đường dẫn tương đối', open_in_vscode: 'Mở bằng VS Code', open_in_vscode_failed: 'Mở bằng VS Code thất bại: ', download_folder: 'Tải thư mục',