add mermaid buttons (#4547)

* add mermaid buttons

* feat: Add Modal, TabButton, and ZoomControls components

* feat: Add error handling messages for image operations and file opening

* mermaid: Add drag functionality and support contious zooming

* add active color for tabbutton

* refactor zoom controls

* refactor: Remove unused svgToPng prop and simplify handleCopy function

* Move zoom to constants and increase max zoom

* feat: add save image functionality and refactor image handling

* feat: add translations

---------

Co-authored-by: Daniel Riccio <ricciodaniel98@gmail.com>
Co-authored-by: Matt Rubens <mrubens@users.noreply.github.com>
This commit is contained in:
axb 2025-06-13 01:06:27 +08:00 committed by GitHub
parent 85fd86e675
commit dfcf8fe760
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 1360 additions and 59 deletions

View file

@ -17,7 +17,8 @@ import { checkoutDiffPayloadSchema, checkoutRestorePayloadSchema, WebviewMessage
import { checkExistKey } from "../../shared/checkExistApiConfig"
import { experimentDefault } from "../../shared/experiments"
import { Terminal } from "../../integrations/terminal/Terminal"
import { openFile, openImage } from "../../integrations/misc/open-file"
import { openFile } from "../../integrations/misc/open-file"
import { openImage, saveImage } from "../../integrations/misc/image-handler"
import { selectImages } from "../../integrations/misc/process-images"
import { getTheme } from "../../integrations/theme/getTheme"
import { discoverChromeHostUrl, tryChromeHostUrl } from "../../services/browser/browserDiscovery"
@ -423,7 +424,10 @@ export const webviewMessageHandler = async (
provider.postMessageToWebview({ type: "vsCodeLmModels", vsCodeLmModels })
break
case "openImage":
openImage(message.text!)
openImage(message.text!, { values: message.values })
break
case "saveImage":
saveImage(message.dataUri!)
break
case "openFile":
openFile(message.text!, message.values as { create?: boolean; content?: string; line?: number })

View file

@ -28,6 +28,11 @@
},
"errors": {
"invalid_data_uri": "Format d'URI de dades no vàlid",
"error_copying_image": "Error copiant la imatge: {{errorMessage}}",
"error_saving_image": "Error desant la imatge: {{errorMessage}}",
"error_opening_image": "Error obrint la imatge: {{error}}",
"could_not_open_file": "No s'ha pogut obrir el fitxer: {{errorMessage}}",
"could_not_open_file_generic": "No s'ha pogut obrir el fitxer!",
"checkpoint_timeout": "S'ha esgotat el temps en intentar restaurar el punt de control.",
"checkpoint_failed": "Ha fallat la restauració del punt de control.",
"no_workspace": "Si us plau, obre primer una carpeta de projecte",
@ -71,7 +76,9 @@
"custom_storage_path_set": "Ruta d'emmagatzematge personalitzada establerta: {{path}}",
"default_storage_path": "S'ha reprès l'ús de la ruta d'emmagatzematge predeterminada",
"settings_imported": "Configuració importada correctament.",
"share_link_copied": "Enllaç de compartició copiat al portapapers"
"share_link_copied": "Enllaç de compartició copiat al portapapers",
"image_copied_to_clipboard": "URI de dades de la imatge copiada al portapapers",
"image_saved": "Imatge desada a {{path}}"
},
"answers": {
"yes": "Sí",

View file

@ -24,6 +24,11 @@
},
"errors": {
"invalid_data_uri": "Ungültiges Daten-URI-Format",
"error_copying_image": "Fehler beim Kopieren des Bildes: {{errorMessage}}",
"error_saving_image": "Fehler beim Speichern des Bildes: {{errorMessage}}",
"error_opening_image": "Fehler beim Öffnen des Bildes: {{error}}",
"could_not_open_file": "Datei konnte nicht geöffnet werden: {{errorMessage}}",
"could_not_open_file_generic": "Datei konnte nicht geöffnet werden!",
"checkpoint_timeout": "Zeitüberschreitung beim Versuch, den Checkpoint wiederherzustellen.",
"checkpoint_failed": "Fehler beim Wiederherstellen des Checkpoints.",
"no_workspace": "Bitte öffne zuerst einen Projektordner",
@ -67,7 +72,9 @@
"custom_storage_path_set": "Benutzerdefinierter Speicherpfad festgelegt: {{path}}",
"default_storage_path": "Auf Standardspeicherpfad zurückgesetzt",
"settings_imported": "Einstellungen erfolgreich importiert.",
"share_link_copied": "Share-Link in die Zwischenablage kopiert"
"share_link_copied": "Share-Link in die Zwischenablage kopiert",
"image_copied_to_clipboard": "Bild-Daten-URI in die Zwischenablage kopiert",
"image_saved": "Bild gespeichert unter {{path}}"
},
"answers": {
"yes": "Ja",

View file

@ -24,6 +24,11 @@
},
"errors": {
"invalid_data_uri": "Invalid data URI format",
"error_copying_image": "Error copying image: {{errorMessage}}",
"error_opening_image": "Error opening image: {{error}}",
"error_saving_image": "Error saving image: {{errorMessage}}",
"could_not_open_file": "Could not open file: {{errorMessage}}",
"could_not_open_file_generic": "Could not open file!",
"checkpoint_timeout": "Timed out when attempting to restore checkpoint.",
"checkpoint_failed": "Failed to restore checkpoint.",
"no_workspace": "Please open a project folder first",
@ -67,7 +72,9 @@
"custom_storage_path_set": "Custom storage path set: {{path}}",
"default_storage_path": "Reverted to using default storage path",
"settings_imported": "Settings imported successfully.",
"share_link_copied": "Share link copied to clipboard"
"share_link_copied": "Share link copied to clipboard",
"image_copied_to_clipboard": "Image data URI copied to clipboard",
"image_saved": "Image saved to {{path}}"
},
"answers": {
"yes": "Yes",

View file

@ -24,6 +24,11 @@
},
"errors": {
"invalid_data_uri": "Formato de URI de datos no válido",
"error_copying_image": "Error copiando la imagen: {{errorMessage}}",
"error_saving_image": "Error guardando la imagen: {{errorMessage}}",
"error_opening_image": "Error abriendo la imagen: {{error}}",
"could_not_open_file": "No se pudo abrir el archivo: {{errorMessage}}",
"could_not_open_file_generic": "¡No se pudo abrir el archivo!",
"checkpoint_timeout": "Se agotó el tiempo al intentar restaurar el punto de control.",
"checkpoint_failed": "Error al restaurar el punto de control.",
"no_workspace": "Por favor, abre primero una carpeta de proyecto",
@ -67,7 +72,9 @@
"custom_storage_path_set": "Ruta de almacenamiento personalizada establecida: {{path}}",
"default_storage_path": "Se ha vuelto a usar la ruta de almacenamiento predeterminada",
"settings_imported": "Configuración importada correctamente.",
"share_link_copied": "Enlace de compartir copiado al portapapeles"
"share_link_copied": "Enlace de compartir copiado al portapapeles",
"image_copied_to_clipboard": "URI de datos de imagen copiada al portapapeles",
"image_saved": "Imagen guardada en {{path}}"
},
"answers": {
"yes": "Sí",

View file

@ -24,6 +24,11 @@
},
"errors": {
"invalid_data_uri": "Format d'URI de données invalide",
"error_copying_image": "Erreur lors de la copie de l'image : {{errorMessage}}",
"error_saving_image": "Erreur lors de l'enregistrement de l'image : {{errorMessage}}",
"error_opening_image": "Erreur lors de l'ouverture de l'image : {{error}}",
"could_not_open_file": "Impossible d'ouvrir le fichier : {{errorMessage}}",
"could_not_open_file_generic": "Impossible d'ouvrir le fichier !",
"checkpoint_timeout": "Expiration du délai lors de la tentative de rétablissement du checkpoint.",
"checkpoint_failed": "Échec du rétablissement du checkpoint.",
"no_workspace": "Veuillez d'abord ouvrir un espace de travail",
@ -67,7 +72,9 @@
"custom_storage_path_set": "Chemin de stockage personnalisé défini : {{path}}",
"default_storage_path": "Retour au chemin de stockage par défaut",
"settings_imported": "Paramètres importés avec succès.",
"share_link_copied": "Lien de partage copié dans le presse-papiers"
"share_link_copied": "Lien de partage copié dans le presse-papiers",
"image_copied_to_clipboard": "URI de données d'image copiée dans le presse-papiers",
"image_saved": "Image enregistrée dans {{path}}"
},
"answers": {
"yes": "Oui",

View file

@ -24,6 +24,11 @@
},
"errors": {
"invalid_data_uri": "अमान्य डेटा URI फॉर्मेट",
"error_copying_image": "छवि कॉपी करने में त्रुटि: {{errorMessage}}",
"error_saving_image": "छवि सहेजने में त्रुटि: {{errorMessage}}",
"error_opening_image": "छवि खोलने में त्रुटि: {{error}}",
"could_not_open_file": "फ़ाइल नहीं खोली जा सकी: {{errorMessage}}",
"could_not_open_file_generic": "फ़ाइल नहीं खोली जा सकी!",
"checkpoint_timeout": "चेकपॉइंट को पुनर्स्थापित करने का प्रयास करते समय टाइमआउट हो गया।",
"checkpoint_failed": "चेकपॉइंट पुनर्स्थापित करने में विफल।",
"no_workspace": "कृपया पहले प्रोजेक्ट फ़ोल्डर खोलें",
@ -66,8 +71,10 @@
"history_cleanup": "इतिहास से गायब फाइलों वाले {{count}} टास्क साफ किए गए।",
"custom_storage_path_set": "कस्टम स्टोरेज पाथ सेट किया गया: {{path}}",
"default_storage_path": "डिफ़ॉल्ट स्टोरेज पाथ का उपयोग पुनः शुरू किया गया",
"settings_imported": "सेटिंग्स सफलतापूर्वक इम्पोर्ट की गईं.",
"share_link_copied": "साझा लिंक क्लिपबोर्ड पर कॉपी किया गया"
"settings_imported": "सेटिंग्स सफलतापूर्वक इम्पोर्ट की गईं।",
"share_link_copied": "साझा लिंक क्लिपबोर्ड पर कॉपी किया गया",
"image_copied_to_clipboard": "छवि डेटा URI क्लिपबोर्ड में कॉपी की गई",
"image_saved": "छवि {{path}} में सहेजी गई"
},
"answers": {
"yes": "हां",

View file

@ -24,6 +24,11 @@
},
"errors": {
"invalid_data_uri": "Formato URI dati non valido",
"error_copying_image": "Errore durante la copia dell'immagine: {{errorMessage}}",
"error_saving_image": "Errore durante il salvataggio dell'immagine: {{errorMessage}}",
"error_opening_image": "Errore durante l'apertura dell'immagine: {{error}}",
"could_not_open_file": "Impossibile aprire il file: {{errorMessage}}",
"could_not_open_file_generic": "Impossibile aprire il file!",
"checkpoint_timeout": "Timeout durante il tentativo di ripristinare il checkpoint.",
"checkpoint_failed": "Impossibile ripristinare il checkpoint.",
"no_workspace": "Per favore, apri prima una cartella di progetto",
@ -67,7 +72,9 @@
"custom_storage_path_set": "Percorso di archiviazione personalizzato impostato: {{path}}",
"default_storage_path": "Tornato al percorso di archiviazione predefinito",
"settings_imported": "Impostazioni importate con successo.",
"share_link_copied": "Link di condivisione copiato negli appunti"
"share_link_copied": "Link di condivisione copiato negli appunti",
"image_copied_to_clipboard": "URI dati dell'immagine copiato negli appunti",
"image_saved": "Immagine salvata in {{path}}"
},
"answers": {
"yes": "Sì",

View file

@ -24,6 +24,11 @@
},
"errors": {
"invalid_data_uri": "データURIフォーマットが無効です",
"error_copying_image": "画像のコピー中にエラーが発生しました:{{errorMessage}}",
"error_saving_image": "画像の保存中にエラーが発生しました:{{errorMessage}}",
"error_opening_image": "画像を開く際にエラーが発生しました:{{error}}",
"could_not_open_file": "ファイルを開けませんでした:{{errorMessage}}",
"could_not_open_file_generic": "ファイルを開けませんでした!",
"checkpoint_timeout": "チェックポイントの復元を試みる際にタイムアウトしました。",
"checkpoint_failed": "チェックポイントの復元に失敗しました。",
"no_workspace": "まずプロジェクトフォルダを開いてください",
@ -67,7 +72,9 @@
"custom_storage_path_set": "カスタムストレージパスが設定されました:{{path}}",
"default_storage_path": "デフォルトのストレージパスに戻りました",
"settings_imported": "設定が正常にインポートされました。",
"share_link_copied": "共有リンクがクリップボードにコピーされました"
"share_link_copied": "共有リンクがクリップボードにコピーされました",
"image_copied_to_clipboard": "画像データURIがクリップボードにコピーされました",
"image_saved": "画像を{{path}}に保存しました"
},
"answers": {
"yes": "はい",

View file

@ -24,6 +24,11 @@
},
"errors": {
"invalid_data_uri": "잘못된 데이터 URI 형식",
"error_copying_image": "이미지 복사 중 오류 발생: {{errorMessage}}",
"error_saving_image": "이미지 저장 중 오류 발생: {{errorMessage}}",
"error_opening_image": "이미지 열기 중 오류 발생: {{error}}",
"could_not_open_file": "파일을 열 수 없습니다: {{errorMessage}}",
"could_not_open_file_generic": "파일을 열 수 없습니다!",
"checkpoint_timeout": "체크포인트 복원을 시도하는 중 시간 초과되었습니다.",
"checkpoint_failed": "체크포인트 복원에 실패했습니다.",
"no_workspace": "먼저 프로젝트 폴더를 열어주세요",
@ -67,7 +72,9 @@
"custom_storage_path_set": "사용자 지정 저장 경로 설정됨: {{path}}",
"default_storage_path": "기본 저장 경로로 되돌아갔습니다",
"settings_imported": "설정이 성공적으로 가져와졌습니다.",
"share_link_copied": "공유 링크가 클립보드에 복사되었습니다"
"share_link_copied": "공유 링크가 클립보드에 복사되었습니다",
"image_copied_to_clipboard": "이미지 데이터 URI가 클립보드에 복사되었습니다",
"image_saved": "이미지가 {{path}}에 저장되었습니다"
},
"answers": {
"yes": "예",

View file

@ -24,6 +24,11 @@
},
"errors": {
"invalid_data_uri": "Ongeldig data-URI-formaat",
"error_copying_image": "Fout bij kopiëren van afbeelding: {{errorMessage}}",
"error_saving_image": "Fout bij opslaan van afbeelding: {{errorMessage}}",
"error_opening_image": "Fout bij openen van afbeelding: {{error}}",
"could_not_open_file": "Kon bestand niet openen: {{errorMessage}}",
"could_not_open_file_generic": "Kon bestand niet openen!",
"checkpoint_timeout": "Time-out bij het herstellen van checkpoint.",
"checkpoint_failed": "Herstellen van checkpoint mislukt.",
"no_workspace": "Open eerst een projectmap",
@ -67,7 +72,9 @@
"custom_storage_path_set": "Aangepast opslagpad ingesteld: {{path}}",
"default_storage_path": "Terug naar standaard opslagpad",
"settings_imported": "Instellingen succesvol geïmporteerd.",
"share_link_copied": "Deellink gekopieerd naar klembord"
"share_link_copied": "Deellink gekopieerd naar klembord",
"image_copied_to_clipboard": "Afbeelding data-URI gekopieerd naar klembord",
"image_saved": "Afbeelding opgeslagen naar {{path}}"
},
"answers": {
"yes": "Ja",

View file

@ -24,6 +24,11 @@
},
"errors": {
"invalid_data_uri": "Nieprawidłowy format URI danych",
"error_copying_image": "Błąd kopiowania obrazu: {{errorMessage}}",
"error_saving_image": "Błąd zapisywania obrazu: {{errorMessage}}",
"error_opening_image": "Błąd otwierania obrazu: {{error}}",
"could_not_open_file": "Nie można otworzyć pliku: {{errorMessage}}",
"could_not_open_file_generic": "Nie można otworzyć pliku!",
"checkpoint_timeout": "Upłynął limit czasu podczas próby przywrócenia punktu kontrolnego.",
"checkpoint_failed": "Nie udało się przywrócić punktu kontrolnego.",
"no_workspace": "Najpierw otwórz folder projektu",
@ -67,7 +72,9 @@
"custom_storage_path_set": "Ustawiono niestandardową ścieżkę przechowywania: {{path}}",
"default_storage_path": "Wznowiono używanie domyślnej ścieżki przechowywania",
"settings_imported": "Ustawienia zaimportowane pomyślnie.",
"share_link_copied": "Link udostępniania skopiowany do schowka"
"share_link_copied": "Link udostępniania skopiowany do schowka",
"image_copied_to_clipboard": "URI danych obrazu skopiowane do schowka",
"image_saved": "Obraz zapisany w {{path}}"
},
"answers": {
"yes": "Tak",

View file

@ -28,6 +28,11 @@
},
"errors": {
"invalid_data_uri": "Formato de URI de dados inválido",
"error_copying_image": "Erro ao copiar imagem: {{errorMessage}}",
"error_saving_image": "Erro ao salvar imagem: {{errorMessage}}",
"error_opening_image": "Erro ao abrir imagem: {{error}}",
"could_not_open_file": "Não foi possível abrir o arquivo: {{errorMessage}}",
"could_not_open_file_generic": "Não foi possível abrir o arquivo!",
"checkpoint_timeout": "Tempo esgotado ao tentar restaurar o ponto de verificação.",
"checkpoint_failed": "Falha ao restaurar o ponto de verificação.",
"no_workspace": "Por favor, abra primeiro uma pasta de projeto",
@ -71,7 +76,9 @@
"custom_storage_path_set": "Caminho de armazenamento personalizado definido: {{path}}",
"default_storage_path": "Retornado ao caminho de armazenamento padrão",
"settings_imported": "Configurações importadas com sucesso.",
"share_link_copied": "Link de compartilhamento copiado para a área de transferência"
"share_link_copied": "Link de compartilhamento copiado para a área de transferência",
"image_copied_to_clipboard": "URI de dados da imagem copiada para a área de transferência",
"image_saved": "Imagem salva em {{path}}"
},
"answers": {
"yes": "Sim",

View file

@ -24,6 +24,11 @@
},
"errors": {
"invalid_data_uri": "Неверный формат URI данных",
"error_copying_image": "Ошибка копирования изображения: {{errorMessage}}",
"error_saving_image": "Ошибка сохранения изображения: {{errorMessage}}",
"error_opening_image": "Ошибка открытия изображения: {{error}}",
"could_not_open_file": "Не удалось открыть файл: {{errorMessage}}",
"could_not_open_file_generic": "Не удалось открыть файл!",
"checkpoint_timeout": "Превышено время ожидания при попытке восстановления контрольной точки.",
"checkpoint_failed": "Не удалось восстановить контрольную точку.",
"no_workspace": "Пожалуйста, сначала откройте папку проекта",
@ -67,7 +72,9 @@
"custom_storage_path_set": "Установлен пользовательский путь хранения: {{path}}",
"default_storage_path": "Возвращено использование пути хранения по умолчанию",
"settings_imported": "Настройки успешно импортированы.",
"share_link_copied": "Ссылка для совместного использования скопирована в буфер обмена"
"share_link_copied": "Ссылка для совместного использования скопирована в буфер обмена",
"image_copied_to_clipboard": "URI данных изображения скопирован в буфер обмена",
"image_saved": "Изображение сохранено в {{path}}"
},
"answers": {
"yes": "Да",

View file

@ -24,6 +24,11 @@
},
"errors": {
"invalid_data_uri": "Geçersiz veri URI formatı",
"error_copying_image": "Resim kopyalanırken hata oluştu: {{errorMessage}}",
"error_saving_image": "Resim kaydedilirken hata oluştu: {{errorMessage}}",
"error_opening_image": "Resim açılırken hata oluştu: {{error}}",
"could_not_open_file": "Dosya açılamadı: {{errorMessage}}",
"could_not_open_file_generic": "Dosya açılamadı!",
"checkpoint_timeout": "Kontrol noktasını geri yüklemeye çalışırken zaman aşımına uğradı.",
"checkpoint_failed": "Kontrol noktası geri yüklenemedi.",
"no_workspace": "Lütfen önce bir proje klasörü açın",
@ -67,7 +72,9 @@
"custom_storage_path_set": "Özel depolama yolu ayarlandı: {{path}}",
"default_storage_path": "Varsayılan depolama yoluna geri dönüldü",
"settings_imported": "Ayarlar başarıyla içe aktarıldı.",
"share_link_copied": "Paylaşım bağlantısı panoya kopyalandı"
"share_link_copied": "Paylaşım bağlantısı panoya kopyalandı",
"image_copied_to_clipboard": "Resim veri URI'si panoya kopyalandı",
"image_saved": "Resim {{path}} konumuna kaydedildi"
},
"answers": {
"yes": "Evet",

View file

@ -24,6 +24,11 @@
},
"errors": {
"invalid_data_uri": "Định dạng URI dữ liệu không hợp lệ",
"error_copying_image": "Lỗi khi sao chép hình ảnh: {{errorMessage}}",
"error_saving_image": "Lỗi khi lưu hình ảnh: {{errorMessage}}",
"error_opening_image": "Lỗi khi mở hình ảnh: {{error}}",
"could_not_open_file": "Không thể mở tệp: {{errorMessage}}",
"could_not_open_file_generic": "Không thể mở tệp!",
"checkpoint_timeout": "Đã hết thời gian khi cố gắng khôi phục điểm kiểm tra.",
"checkpoint_failed": "Không thể khôi phục điểm kiểm tra.",
"no_workspace": "Vui lòng mở thư mục dự án trước",
@ -67,7 +72,9 @@
"custom_storage_path_set": "Đã thiết lập đường dẫn lưu trữ tùy chỉnh: {{path}}",
"default_storage_path": "Đã quay lại sử dụng đường dẫn lưu trữ mặc định",
"settings_imported": "Cài đặt đã được nhập thành công.",
"share_link_copied": "Liên kết chia sẻ đã được sao chép vào clipboard"
"share_link_copied": "Liên kết chia sẻ đã được sao chép vào clipboard",
"image_copied_to_clipboard": "URI dữ liệu hình ảnh đã được sao chép vào clipboard",
"image_saved": "Hình ảnh đã được lưu vào {{path}}"
},
"answers": {
"yes": "Có",

View file

@ -23,7 +23,17 @@
"this_and_subsequent": "此消息及所有后续消息"
},
"errors": {
"invalid_data_uri": "数据URI格式无效",
"invalid_mcp_config": "项目MCP配置格式无效",
"invalid_mcp_settings_format": "MCP设置JSON格式无效。请确保您的设置遵循正确的JSON格式。",
"invalid_mcp_settings_syntax": "MCP设置JSON格式无效。请检查您的设置文件是否有语法错误。",
"invalid_mcp_settings_validation": "MCP设置格式无效{{errorMessages}}",
"failed_initialize_project_mcp": "初始化项目MCP服务器失败{{error}}",
"invalid_data_uri": "数据 URI 格式无效",
"error_copying_image": "复制图片时出错:{{errorMessage}}",
"error_saving_image": "保存图片时出错:{{errorMessage}}",
"error_opening_image": "打开图片时出错:{{error}}",
"could_not_open_file": "无法打开文件:{{errorMessage}}",
"could_not_open_file_generic": "无法打开文件!",
"checkpoint_timeout": "尝试恢复检查点时超时。",
"checkpoint_failed": "恢复检查点失败。",
"no_workspace": "请先打开项目文件夹",
@ -67,7 +77,9 @@
"custom_storage_path_set": "自定义存储路径已设置:{{path}}",
"default_storage_path": "已恢复使用默认存储路径",
"settings_imported": "设置已成功导入。",
"share_link_copied": "分享链接已复制到剪贴板"
"share_link_copied": "分享链接已复制到剪贴板",
"image_copied_to_clipboard": "图片数据 URI 已复制到剪贴板",
"image_saved": "图片已保存到 {{path}}"
},
"answers": {
"yes": "是",

View file

@ -24,6 +24,11 @@
},
"errors": {
"invalid_data_uri": "資料 URI 格式無效",
"error_copying_image": "複製圖片時發生錯誤:{{errorMessage}}",
"error_saving_image": "儲存圖片時發生錯誤:{{errorMessage}}",
"error_opening_image": "開啟圖片時發生錯誤:{{error}}",
"could_not_open_file": "無法開啟檔案:{{errorMessage}}",
"could_not_open_file_generic": "無法開啟檔案!",
"checkpoint_timeout": "嘗試恢復檢查點時超時。",
"checkpoint_failed": "恢復檢查點失敗。",
"no_workspace": "請先開啟專案資料夾",
@ -67,7 +72,9 @@
"custom_storage_path_set": "自訂儲存路徑已設定:{{path}}",
"default_storage_path": "已恢復使用預設儲存路徑",
"settings_imported": "設定已成功匯入。",
"share_link_copied": "分享連結已複製到剪貼簿"
"share_link_copied": "分享連結已複製到剪貼簿",
"image_copied_to_clipboard": "圖片資料 URI 已複製到剪貼簿",
"image_saved": "圖片已儲存至 {{path}}"
},
"answers": {
"yes": "是",

View file

@ -0,0 +1,92 @@
import * as path from "path"
import * as os from "os"
import * as vscode from "vscode"
import { getWorkspacePath } from "../../utils/path"
import { t } from "../../i18n"
export async function openImage(dataUri: string, options?: { values?: { action?: string } }) {
const matches = dataUri.match(/^data:image\/([a-zA-Z]+);base64,(.+)$/)
if (!matches) {
vscode.window.showErrorMessage(t("common:errors.invalid_data_uri"))
return
}
const [, format, base64Data] = matches
const imageBuffer = Buffer.from(base64Data, "base64")
// Default behavior: open the image
const tempFilePath = path.join(os.tmpdir(), `temp_image_${Date.now()}.${format}`)
try {
await vscode.workspace.fs.writeFile(vscode.Uri.file(tempFilePath), imageBuffer)
// Check if this is a copy action
if (options?.values?.action === "copy") {
try {
// Read the image file
const imageData = await vscode.workspace.fs.readFile(vscode.Uri.file(tempFilePath))
// Convert to base64 for clipboard
const base64Image = Buffer.from(imageData).toString("base64")
const dataUri = `data:image/${format};base64,${base64Image}`
// Use vscode.env.clipboard to copy the data URI
// Note: VSCode doesn't support copying binary image data directly to clipboard
// So we copy the data URI which can be pasted in many applications
await vscode.env.clipboard.writeText(dataUri)
vscode.window.showInformationMessage(t("common:info.image_copied_to_clipboard"))
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
vscode.window.showErrorMessage(t("common:errors.error_copying_image", { errorMessage }))
} finally {
// Clean up temp file
try {
await vscode.workspace.fs.delete(vscode.Uri.file(tempFilePath))
} catch {
// Ignore cleanup errors
}
}
return
}
await vscode.commands.executeCommand("vscode.open", vscode.Uri.file(tempFilePath))
} catch (error) {
vscode.window.showErrorMessage(t("common:errors.error_opening_image", { error }))
}
}
export async function saveImage(dataUri: string) {
const matches = dataUri.match(/^data:image\/([a-zA-Z]+);base64,(.+)$/)
if (!matches) {
vscode.window.showErrorMessage(t("common:errors.invalid_data_uri"))
return
}
const [, format, base64Data] = matches
const imageBuffer = Buffer.from(base64Data, "base64")
// Get workspace path or fallback to home directory
const workspacePath = getWorkspacePath()
const defaultPath = workspacePath || os.homedir()
const defaultFileName = `mermaid_diagram_${Date.now()}.${format}`
const defaultUri = vscode.Uri.file(path.join(defaultPath, defaultFileName))
// Show save dialog
const saveUri = await vscode.window.showSaveDialog({
filters: {
Images: [format],
"All Files": ["*"],
},
defaultUri: defaultUri,
})
if (!saveUri) {
// User cancelled the save dialog
return
}
try {
// Write the image to the selected location
await vscode.workspace.fs.writeFile(saveUri, imageBuffer)
vscode.window.showInformationMessage(t("common:info.image_saved", { path: saveUri.fsPath }))
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
vscode.window.showErrorMessage(t("common:errors.error_saving_image", { errorMessage }))
}
}

View file

@ -2,23 +2,7 @@ import * as path from "path"
import * as os from "os"
import * as vscode from "vscode"
import { arePathsEqual, getWorkspacePath } from "../../utils/path"
export async function openImage(dataUri: string) {
const matches = dataUri.match(/^data:image\/([a-zA-Z]+);base64,(.+)$/)
if (!matches) {
vscode.window.showErrorMessage("Invalid data URI format")
return
}
const [, format, base64Data] = matches
const imageBuffer = Buffer.from(base64Data, "base64")
const tempFilePath = path.join(os.tmpdir(), `temp_image_${Date.now()}.${format}`)
try {
await vscode.workspace.fs.writeFile(vscode.Uri.file(tempFilePath), imageBuffer)
await vscode.commands.executeCommand("vscode.open", vscode.Uri.file(tempFilePath))
} catch (error) {
vscode.window.showErrorMessage(`Error opening image: ${error}`)
}
}
import { t } from "../../i18n"
interface OpenFileOptions {
create?: boolean
@ -151,9 +135,9 @@ export async function openFile(filePath: string, options: OpenFileOptions = {})
})
} catch (error) {
if (error instanceof Error) {
vscode.window.showErrorMessage(`Could not open file: ${error.message}`)
vscode.window.showErrorMessage(t("common:errors.could_not_open_file", { errorMessage: error.message }))
} else {
vscode.window.showErrorMessage(`Could not open file!`)
vscode.window.showErrorMessage(t("common:errors.could_not_open_file_generic"))
}
}
}

View file

@ -52,6 +52,7 @@ export interface WebviewMessage {
| "requestLmStudioModels"
| "requestVsCodeLmModels"
| "openImage"
| "saveImage"
| "openFile"
| "openMention"
| "cancelTask"
@ -164,6 +165,7 @@ export interface WebviewMessage {
text?: string
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account"
disabled?: boolean
dataUri?: string
askResponse?: ClineAskResponse
apiConfiguration?: ProviderSettings
images?: string[]

View file

@ -0,0 +1,45 @@
interface IconButtonProps {
icon: string
onClick?: (e: React.MouseEvent) => void
onMouseDown?: (e: React.MouseEvent) => void
onMouseUp?: (e: React.MouseEvent) => void
onMouseLeave?: (e: React.MouseEvent) => void
title?: string
size?: "small" | "medium"
variant?: "default" | "transparent"
}
export function IconButton({
icon,
onClick,
onMouseDown,
onMouseUp,
onMouseLeave,
title,
size = "medium",
variant = "default",
}: IconButtonProps) {
const sizeClasses = {
small: "w-6 h-6",
medium: "w-7 h-7",
}
const variantClasses = {
default: "bg-transparent hover:bg-vscode-toolbar-hoverBackground",
transparent: "bg-transparent hover:bg-vscode-toolbar-hoverBackground",
}
const handleClick = onClick || ((_event: React.MouseEvent) => {})
return (
<button
className={`${sizeClasses[size]} flex items-center justify-center border-none text-vscode-editor-foreground cursor-pointer rounded-[3px] ${variantClasses[variant]}`}
onClick={handleClick}
onMouseDown={onMouseDown}
onMouseUp={onMouseUp}
onMouseLeave={onMouseLeave}
title={title}>
<span className={`codicon codicon-${icon}`}></span>
</button>
)
}

View file

@ -0,0 +1,80 @@
import React from "react"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { IconButton } from "./IconButton"
import { ZoomControls } from "./ZoomControls"
interface MermaidActionButtonsProps {
onZoom?: (e: React.MouseEvent) => void
onZoomIn?: () => void
onZoomOut?: () => void
onCopy: (e: React.MouseEvent) => void
onSave?: (e: React.MouseEvent) => void
onViewCode: () => void
onClose?: () => void
copyFeedback: boolean
showZoomControls?: boolean
zoomLevel?: number
}
export const MermaidActionButtons: React.FC<MermaidActionButtonsProps> = ({
onZoom,
onZoomIn,
onZoomOut,
onCopy,
onSave,
onViewCode,
onClose,
copyFeedback,
showZoomControls = false,
zoomLevel,
}) => {
const { t } = useAppTranslation()
if (showZoomControls && onZoomOut && onZoomIn && zoomLevel !== undefined) {
return (
<>
<ZoomControls
zoomLevel={zoomLevel}
onZoomIn={onZoomIn}
onZoomOut={onZoomOut}
zoomInTitle={t("common:mermaid.buttons.zoomIn")}
zoomOutTitle={t("common:mermaid.buttons.zoomOut")}
/>
<IconButton
icon="code"
onClick={(e: React.MouseEvent) => {
e.stopPropagation()
onViewCode()
}}
title={t("common:mermaid.buttons.viewCode")}
/>
<IconButton
icon={copyFeedback ? "check" : "copy"}
onClick={onCopy}
title={t("common:mermaid.buttons.copy")}
/>
</>
)
}
return (
<>
{onZoom && <IconButton icon="zoom-in" onClick={onZoom} title={t("common:mermaid.buttons.zoom")} />}
<IconButton
icon="code"
onClick={(e: React.MouseEvent) => {
e.stopPropagation()
onViewCode()
}}
title={t("common:mermaid.buttons.viewCode")}
/>
<IconButton
icon={copyFeedback ? "check" : "copy"}
onClick={onCopy}
title={t("common:mermaid.buttons.copy")}
/>
{onSave && <IconButton icon="save" onClick={onSave} title={t("common:mermaid.buttons.save")} />}
{onClose && <IconButton icon="close" onClick={onClose} title={t("common:mermaid.buttons.close")} />}
</>
)
}

View file

@ -6,6 +6,7 @@ import { vscode } from "@src/utils/vscode"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { useCopyToClipboard } from "@src/utils/clipboard"
import CodeBlock from "./CodeBlock"
import { MermaidButton } from "@/components/common/MermaidButton"
// Removed previous attempts at static imports for individual diagram types
// as the paths were incorrect for Mermaid v11.4.1 and caused errors.
@ -213,7 +214,9 @@ export default function MermaidBlock({ code }: MermaidBlockProps) {
)}
</div>
) : (
<SvgContainer onClick={handleClick} ref={containerRef} $isLoading={isLoading} />
<MermaidButton containerRef={containerRef} code={code} isLoading={isLoading} svgToPng={svgToPng}>
<SvgContainer onClick={handleClick} ref={containerRef} $isLoading={isLoading}></SvgContainer>
</MermaidButton>
)}
</MermaidBlockContainer>
)
@ -243,10 +246,16 @@ async function svgToPng(svgEl: SVGElement): Promise<string> {
const serializer = new XMLSerializer()
const svgString = serializer.serializeToString(svgClone)
const svgDataUrl = "data:image/svg+xml;base64," + btoa(decodeURIComponent(encodeURIComponent(svgString)))
// Create a data URL directly
// First, ensure the SVG string is properly encoded
const encodedSvg = encodeURIComponent(svgString).replace(/'/g, "%27").replace(/"/g, "%22")
const svgDataUrl = `data:image/svg+xml;charset=utf-8,${encodedSvg}`
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = () => {
const canvas = document.createElement("canvas")
canvas.width = editorWidth

View file

@ -0,0 +1,246 @@
import { useState, useCallback } from "react"
import { useCopyToClipboard } from "@src/utils/clipboard"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { vscode } from "@src/utils/vscode"
import { MermaidActionButtons } from "./MermaidActionButtons"
import { Modal } from "./Modal"
import { TabButton } from "./TabButton"
import { IconButton } from "./IconButton"
import { ZoomControls } from "./ZoomControls"
const MIN_ZOOM = 0.5
const MAX_ZOOM = 20
export interface MermaidButtonProps {
containerRef: React.RefObject<HTMLDivElement>
code: string
isLoading: boolean
svgToPng: (svgEl: SVGElement) => Promise<string>
children: React.ReactNode
}
export function MermaidButton({ containerRef, code, isLoading, svgToPng, children }: MermaidButtonProps) {
const [showModal, setShowModal] = useState(false)
const [zoomLevel, setZoomLevel] = useState(1)
const [copyFeedback, setCopyFeedback] = useState(false)
const [isHovering, setIsHovering] = useState(false)
const [modalViewMode, setModalViewMode] = useState<"diagram" | "code">("diagram")
const [isDragging, setIsDragging] = useState(false)
const [dragPosition, setDragPosition] = useState({ x: 0, y: 0 })
const { copyWithFeedback } = useCopyToClipboard()
const { t } = useAppTranslation()
/**
* Opens a modal with the diagram for zooming
*/
const handleZoom = async (e: React.MouseEvent) => {
e.stopPropagation()
setShowModal(true)
setZoomLevel(1)
setModalViewMode("diagram")
}
/**
* Copies the diagram text to clipboard
*/
const handleCopy = async (e: React.MouseEvent) => {
e.stopPropagation()
try {
await copyWithFeedback(code, e)
// Show feedback
setCopyFeedback(true)
setTimeout(() => setCopyFeedback(false), 2000)
} catch (err) {
console.error("Error copying text:", err instanceof Error ? err.message : String(err))
}
}
/**
* Saves the diagram as an image file
*/
const handleSave = async (e: React.MouseEvent) => {
e.stopPropagation()
// Get the SVG element from the container
const svgEl = containerRef.current?.querySelector("svg")
if (!svgEl) {
console.error("SVG element not found")
return
}
try {
// Convert SVG to PNG
const pngDataUrl = await svgToPng(svgEl)
// Send message to VSCode to save the image
vscode.postMessage({
type: "saveImage",
dataUri: pngDataUrl,
})
} catch (error) {
console.error("Error saving image:", error)
}
}
/**
* Adjust zoom level in the modal
*/
const adjustZoom = (amount: number) => {
setZoomLevel((prev) => {
const newZoom = prev + amount
return Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, newZoom))
})
}
/**
* Handle wheel event for zooming with scroll wheel
*/
const handleWheel = useCallback((e: React.WheelEvent) => {
e.preventDefault()
e.stopPropagation()
// Determine zoom direction and amount
// Negative deltaY means scrolling up (zoom in), positive means scrolling down (zoom out)
const delta = e.deltaY > 0 ? -0.2 : 0.2
adjustZoom(delta)
}, [])
/**
* Handle mouse enter event for diagram container
*/
const handleMouseEnter = () => {
setIsHovering(true)
}
/**
* Handle mouse leave event for diagram container
*/
const handleMouseLeave = () => {
setIsHovering(false)
}
return (
<>
<div className="relative w-full" onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
{children}
{!isLoading && isHovering && (
<div className="absolute bottom-2 right-2 flex gap-1 bg-black/70 rounded p-0.5 z-10 opacity-100 transition-opacity duration-200 ease-in-out">
<MermaidActionButtons
onZoom={handleZoom}
onCopy={handleCopy}
onSave={handleSave}
onViewCode={() => {
setShowModal(true)
setModalViewMode("code")
setZoomLevel(1)
}}
copyFeedback={copyFeedback}
/>
</div>
)}
</div>
<Modal isOpen={showModal} onClose={() => setShowModal(false)}>
<div className="flex justify-between items-center border-b border-vscode-editorGroup-border">
<div className="flex gap-0">
<TabButton
icon="graph"
label={t("common:mermaid.tabs.diagram")}
isActive={modalViewMode === "diagram"}
onClick={() => setModalViewMode("diagram")}
/>
<TabButton
icon="code"
label={t("common:mermaid.tabs.code")}
isActive={modalViewMode === "code"}
onClick={() => setModalViewMode("code")}
/>
</div>
<div className="pr-3">
<IconButton
icon="close"
onClick={() => setShowModal(false)}
title={t("common:mermaid.buttons.close")}
/>
</div>
</div>
<div
className="flex-1 p-4 pb-[60px] overflow-auto flex items-center justify-center"
onWheel={modalViewMode === "diagram" ? handleWheel : undefined}>
{modalViewMode === "diagram" ? (
<>
<div
style={{
transform: `scale(${zoomLevel}) translate(${dragPosition.x}px, ${dragPosition.y}px)`,
transformOrigin: "center center",
transition: isDragging ? "none" : "transform 0.1s ease",
cursor: isDragging ? "grabbing" : "grab",
}}
onMouseDown={(e) => {
setIsDragging(true)
e.preventDefault()
}}
onMouseMove={(e) => {
if (isDragging) {
setDragPosition((prev) => ({
x: prev.x + e.movementX / zoomLevel,
y: prev.y + e.movementY / zoomLevel,
}))
}
}}
onMouseUp={() => setIsDragging(false)}
onMouseLeave={() => setIsDragging(false)}>
{containerRef.current && containerRef.current.innerHTML && (
<div dangerouslySetInnerHTML={{ __html: containerRef.current.innerHTML }} />
)}
</div>
<div className="absolute bottom-4 left-4 bg-vscode-editor-background border border-vscode-editorGroup-border rounded px-2 py-1 text-xs text-vscode-descriptionForeground pointer-events-none opacity-80">
{Math.round(zoomLevel * 100)}%
</div>
</>
) : (
<textarea
className="w-full min-h-[200px] bg-vscode-editor-background text-vscode-editor-foreground border border-vscode-editorGroup-border rounded-[3px] p-2 font-mono resize-y outline-none"
readOnly
value={code}
style={{ height: "100%", minHeight: "unset", fontSize: "var(--vscode-editor-font-size)" }}
/>
)}
</div>
<div className="absolute bottom-0 right-0 left-0 p-3 flex items-center justify-end gap-2 bg-vscode-editor-background border-t border-vscode-editorGroup-border rounded-b">
{modalViewMode === "diagram" ? (
<>
<ZoomControls
zoomLevel={zoomLevel}
zoomInTitle={t("common:mermaid.buttons.zoomIn")}
zoomOutTitle={t("common:mermaid.buttons.zoomOut")}
useContinuousZoom={true}
adjustZoom={adjustZoom}
zoomInStep={0.2}
zoomOutStep={-0.2}
/>
<IconButton
icon={copyFeedback ? "check" : "copy"}
onClick={handleCopy}
title={t("common:mermaid.buttons.copy")}
/>
<IconButton icon="save" onClick={handleSave} title={t("common:mermaid.buttons.save")} />
</>
) : (
<IconButton
icon={copyFeedback ? "check" : "copy"}
onClick={(e) => {
e.stopPropagation()
copyWithFeedback(code, e)
}}
title={t("common:mermaid.buttons.copy")}
/>
)}
</div>
</Modal>
</>
)
}

View file

@ -0,0 +1,20 @@
interface ModalProps {
isOpen: boolean
onClose: () => void
children: React.ReactNode
className?: string
}
export function Modal({ isOpen, onClose, children, className = "" }: ModalProps) {
if (!isOpen) return null
return (
<div className="fixed inset-0 bg-black/70 flex items-center justify-center z-[1000]" onClick={onClose}>
<div
className={`bg-vscode-editor-background rounded w-[90%] h-[90%] max-w-[1200px] flex flex-col shadow-[0_5px_15px_rgba(0,0,0,0.5)] border border-vscode-editorGroup-border relative ${className}`}
onClick={(e) => e.stopPropagation()}>
{children}
</div>
</div>
)
}

View file

@ -0,0 +1,26 @@
interface TabButtonProps {
icon: string
label: string
isActive: boolean
onClick: () => void
}
export function TabButton({ icon, label, isActive, onClick }: TabButtonProps) {
const activeClasses =
"bg-vscode-editor-background border-b-2 border-vscode-focusBorder text-vscode-editor-foreground"
const inactiveClasses =
"bg-transparent border-b-2 border-transparent text-vscode-descriptionForeground hover:text-vscode-editor-foreground hover:bg-vscode-toolbar-hoverBackground"
return (
<button
className={`px-4 py-2 border-none cursor-pointer flex items-center gap-1.5 text-[13px] transition-all duration-200 ease-in-out ${
isActive ? activeClasses : inactiveClasses
}`}
onClick={onClick}>
<span
className={`codicon codicon-${icon} text-sm`}
style={isActive ? { color: "var(--vscode-focusBorder)" } : undefined}></span>
{label}
</button>
)
}

View file

@ -0,0 +1,91 @@
import { IconButton } from "./IconButton"
import { useRef, useEffect } from "react"
interface ZoomControlsProps {
zoomLevel: number
zoomInTitle?: string
zoomOutTitle?: string
useContinuousZoom?: boolean
adjustZoom?: (amount: number) => void
zoomInStep?: number
zoomOutStep?: number
onZoomIn?: () => void
onZoomOut?: () => void
}
export function ZoomControls({
zoomLevel,
zoomInTitle,
zoomOutTitle,
useContinuousZoom = false,
adjustZoom,
zoomInStep = 0.1,
zoomOutStep = -0.1,
onZoomIn,
onZoomOut,
}: ZoomControlsProps) {
const zoomIntervalRef = useRef<NodeJS.Timeout | null>(null)
/**
* Start continuous zoom on mouse down
*/
const startContinuousZoom = (amount: number) => {
if (!useContinuousZoom || !adjustZoom) return
// Clear any existing interval first
if (zoomIntervalRef.current) {
clearInterval(zoomIntervalRef.current)
}
// Immediately apply first zoom adjustment
adjustZoom(amount)
// Set up interval for continuous zooming
zoomIntervalRef.current = setInterval(() => {
adjustZoom(amount)
}, 150) // Adjust every 150ms while button is held down
}
/**
* Stop continuous zoom on mouse up or mouse leave
*/
const stopContinuousZoom = () => {
if (zoomIntervalRef.current) {
clearInterval(zoomIntervalRef.current)
zoomIntervalRef.current = null
}
}
// Clean up interval on unmount
useEffect(() => {
return () => {
if (zoomIntervalRef.current) {
clearInterval(zoomIntervalRef.current)
}
}
}, [])
return (
<div className="flex items-center gap-2">
<IconButton
icon="zoom-out"
title={zoomOutTitle}
onClick={!useContinuousZoom ? onZoomOut || (() => adjustZoom?.(zoomOutStep)) : undefined}
onMouseDown={useContinuousZoom && adjustZoom ? () => startContinuousZoom(zoomOutStep) : undefined}
onMouseUp={useContinuousZoom && adjustZoom ? stopContinuousZoom : undefined}
onMouseLeave={useContinuousZoom && adjustZoom ? stopContinuousZoom : undefined}
/>
<div className="text-sm text-vscode-editor-foreground min-w-[50px] text-center">
{Math.round(zoomLevel * 100)}%
</div>
<IconButton
icon="zoom-in"
title={zoomInTitle}
onClick={!useContinuousZoom ? onZoomIn || (() => adjustZoom?.(zoomInStep)) : undefined}
onMouseDown={useContinuousZoom && adjustZoom ? () => startContinuousZoom(zoomInStep) : undefined}
onMouseUp={useContinuousZoom && adjustZoom ? stopContinuousZoom : undefined}
onMouseLeave={useContinuousZoom && adjustZoom ? stopContinuousZoom : undefined}
/>
</div>
)
}

View file

@ -16,6 +16,40 @@
},
"mermaid": {
"loading": "Generant diagrama mermaid...",
"render_error": "No es pot renderitzar el diagrama"
"render_error": "No es pot renderitzar el diagrama",
"buttons": {
"zoom": "Zoom",
"zoomIn": "Ampliar",
"zoomOut": "Reduir",
"copy": "Copiar",
"save": "Desar imatge",
"viewCode": "Veure codi",
"viewDiagram": "Veure diagrama",
"close": "Tancar"
},
"modal": {
"codeTitle": "Codi Mermaid"
},
"tabs": {
"diagram": "Diagrama",
"code": "Codi"
},
"feedback": {
"imageCopied": "Imatge copiada al porta-retalls",
"copyError": "Error copiant la imatge"
}
},
"file": {
"errors": {
"invalidDataUri": "Format d'URI de dades no vàlid",
"copyingImage": "Error copiant la imatge: {{error}}",
"openingImage": "Error obrint la imatge: {{error}}",
"pathNotExists": "El camí no existeix: {{path}}",
"couldNotOpen": "No s'ha pogut obrir el fitxer: {{error}}",
"couldNotOpenGeneric": "No s'ha pogut obrir el fitxer!"
},
"success": {
"imageDataUriCopied": "URI de dades de la imatge copiada al porta-retalls"
}
}
}

View file

@ -16,6 +16,40 @@
},
"mermaid": {
"loading": "Mermaid-Diagramm wird generiert...",
"render_error": "Diagramm kann nicht gerendert werden"
"render_error": "Diagramm kann nicht gerendert werden",
"buttons": {
"zoom": "Zoom",
"zoomIn": "Vergrößern",
"zoomOut": "Verkleinern",
"copy": "Kopieren",
"save": "Bild speichern",
"viewCode": "Code anzeigen",
"viewDiagram": "Diagramm anzeigen",
"close": "Schließen"
},
"modal": {
"codeTitle": "Mermaid-Code"
},
"tabs": {
"diagram": "Diagramm",
"code": "Code"
},
"feedback": {
"imageCopied": "Bild in die Zwischenablage kopiert",
"copyError": "Fehler beim Kopieren des Bildes"
}
},
"file": {
"errors": {
"invalidDataUri": "Ungültiges Daten-URI-Format",
"copyingImage": "Fehler beim Kopieren des Bildes: {{error}}",
"openingImage": "Fehler beim Öffnen des Bildes: {{error}}",
"pathNotExists": "Pfad existiert nicht: {{path}}",
"couldNotOpen": "Datei konnte nicht geöffnet werden: {{error}}",
"couldNotOpenGeneric": "Datei konnte nicht geöffnet werden!"
},
"success": {
"imageDataUriCopied": "Bild-Daten-URI in die Zwischenablage kopiert"
}
}
}

View file

@ -16,6 +16,40 @@
},
"mermaid": {
"loading": "Generating mermaid diagram...",
"render_error": "Unable to Render Diagram"
"render_error": "Unable to Render Diagram",
"buttons": {
"zoom": "Zoom",
"zoomIn": "Zoom In",
"zoomOut": "Zoom Out",
"copy": "Copy",
"save": "Save Image",
"viewCode": "View Code",
"viewDiagram": "View Diagram",
"close": "Close"
},
"modal": {
"codeTitle": "Mermaid Code"
},
"tabs": {
"diagram": "Diagram",
"code": "Code"
},
"feedback": {
"imageCopied": "Image copied to clipboard",
"copyError": "Error copying image"
}
},
"file": {
"errors": {
"invalidDataUri": "Invalid data URI format",
"copyingImage": "Error copying image: {{error}}",
"openingImage": "Error opening image: {{error}}",
"pathNotExists": "Path does not exist: {{path}}",
"couldNotOpen": "Could not open file: {{error}}",
"couldNotOpenGeneric": "Could not open file!"
},
"success": {
"imageDataUriCopied": "Image data URI copied to clipboard"
}
}
}

View file

@ -16,6 +16,40 @@
},
"mermaid": {
"loading": "Generando diagrama mermaid...",
"render_error": "No se puede renderizar el diagrama"
"render_error": "No se puede renderizar el diagrama",
"buttons": {
"zoom": "Zoom",
"zoomIn": "Ampliar",
"zoomOut": "Reducir",
"copy": "Copiar",
"save": "Guardar imagen",
"viewCode": "Ver código",
"viewDiagram": "Ver diagrama",
"close": "Cerrar"
},
"modal": {
"codeTitle": "Código Mermaid"
},
"tabs": {
"diagram": "Diagrama",
"code": "Código"
},
"feedback": {
"imageCopied": "Imagen copiada al portapapeles",
"copyError": "Error copiando la imagen"
}
},
"file": {
"errors": {
"invalidDataUri": "Formato de URI de datos inválido",
"copyingImage": "Error copiando la imagen: {{error}}",
"openingImage": "Error abriendo la imagen: {{error}}",
"pathNotExists": "La ruta no existe: {{path}}",
"couldNotOpen": "No se pudo abrir el archivo: {{error}}",
"couldNotOpenGeneric": "¡No se pudo abrir el archivo!"
},
"success": {
"imageDataUriCopied": "URI de datos de imagen copiada al portapapeles"
}
}
}

View file

@ -16,6 +16,40 @@
},
"mermaid": {
"loading": "Génération du diagramme mermaid...",
"render_error": "Impossible de rendre le diagramme"
"render_error": "Impossible de rendre le diagramme",
"buttons": {
"zoom": "Zoom",
"zoomIn": "Agrandir",
"zoomOut": "Réduire",
"copy": "Copier",
"save": "Enregistrer l'image",
"viewCode": "Voir le code",
"viewDiagram": "Voir le diagramme",
"close": "Fermer"
},
"modal": {
"codeTitle": "Code Mermaid"
},
"tabs": {
"diagram": "Diagramme",
"code": "Code"
},
"feedback": {
"imageCopied": "Image copiée dans le presse-papiers",
"copyError": "Erreur lors de la copie de l'image"
}
},
"file": {
"errors": {
"invalidDataUri": "Format d'URI de données invalide",
"copyingImage": "Erreur lors de la copie de l'image : {{error}}",
"openingImage": "Erreur lors de l'ouverture de l'image : {{error}}",
"pathNotExists": "Le chemin n'existe pas : {{path}}",
"couldNotOpen": "Impossible d'ouvrir le fichier : {{error}}",
"couldNotOpenGeneric": "Impossible d'ouvrir le fichier !"
},
"success": {
"imageDataUriCopied": "URI de données d'image copiée dans le presse-papiers"
}
}
}

View file

@ -16,6 +16,40 @@
},
"mermaid": {
"loading": "मरमेड डायग्राम जनरेट हो रहा है...",
"render_error": "डायग्राम रेंडर नहीं किया जा सकता"
"render_error": "डायग्राम रेंडर नहीं किया जा सकता",
"buttons": {
"zoom": "ज़ूम",
"zoomIn": "बड़ा करें",
"zoomOut": "छोटा करें",
"copy": "कॉपी करें",
"save": "छवि सहेजें",
"viewCode": "कोड देखें",
"viewDiagram": "डायग्राम देखें",
"close": "बंद करें"
},
"modal": {
"codeTitle": "मरमेड कोड"
},
"tabs": {
"diagram": "डायग्राम",
"code": "कोड"
},
"feedback": {
"imageCopied": "इमेज क्लिपबोर्ड में कॉपी हो गई",
"copyError": "इमेज कॉपी करने में त्रुटि"
}
},
"file": {
"errors": {
"invalidDataUri": "अमान्य डेटा URI फॉर्मेट",
"copyingImage": "इमेज कॉपी करने में त्रुटि: {{error}}",
"openingImage": "इमेज खोलने में त्रुटि: {{error}}",
"pathNotExists": "पथ मौजूद नहीं है: {{path}}",
"couldNotOpen": "फ़ाइल नहीं खोली जा सकी: {{error}}",
"couldNotOpenGeneric": "फ़ाइल नहीं खोली जा सकी!"
},
"success": {
"imageDataUriCopied": "इमेज डेटा URI क्लिपबोर्ड में कॉपी हो गया"
}
}
}

View file

@ -16,6 +16,40 @@
},
"mermaid": {
"loading": "Generazione del diagramma mermaid...",
"render_error": "Impossibile renderizzare il diagramma"
"render_error": "Impossibile renderizzare il diagramma",
"buttons": {
"zoom": "Zoom",
"zoomIn": "Ingrandisci",
"zoomOut": "Riduci",
"copy": "Copia",
"save": "Salva immagine",
"viewCode": "Visualizza codice",
"viewDiagram": "Visualizza diagramma",
"close": "Chiudi"
},
"modal": {
"codeTitle": "Codice Mermaid"
},
"tabs": {
"diagram": "Diagramma",
"code": "Codice"
},
"feedback": {
"imageCopied": "Immagine copiata negli appunti",
"copyError": "Errore nella copia dell'immagine"
}
},
"file": {
"errors": {
"invalidDataUri": "Formato URI dati non valido",
"copyingImage": "Errore nella copia dell'immagine: {{error}}",
"openingImage": "Errore nell'apertura dell'immagine: {{error}}",
"pathNotExists": "Il percorso non esiste: {{path}}",
"couldNotOpen": "Impossibile aprire il file: {{error}}",
"couldNotOpenGeneric": "Impossibile aprire il file!"
},
"success": {
"imageDataUriCopied": "URI dati immagine copiato negli appunti"
}
}
}

View file

@ -16,6 +16,40 @@
},
"mermaid": {
"loading": "Mermaidダイアグラムを生成中...",
"render_error": "ダイアグラムをレンダリングできません"
"render_error": "ダイアグラムをレンダリングできません",
"buttons": {
"zoom": "ズーム",
"zoomIn": "拡大",
"zoomOut": "縮小",
"copy": "コピー",
"save": "画像を保存",
"viewCode": "コードを表示",
"viewDiagram": "ダイアグラムを表示",
"close": "閉じる"
},
"modal": {
"codeTitle": "Mermaidコード"
},
"tabs": {
"diagram": "ダイアグラム",
"code": "コード"
},
"feedback": {
"imageCopied": "画像をクリップボードにコピーしました",
"copyError": "画像のコピーエラー"
}
},
"file": {
"errors": {
"invalidDataUri": "無効なデータURI形式",
"copyingImage": "画像のコピーエラー: {{error}}",
"openingImage": "画像を開く際のエラー: {{error}}",
"pathNotExists": "パスが存在しません: {{path}}",
"couldNotOpen": "ファイルを開けませんでした: {{error}}",
"couldNotOpenGeneric": "ファイルを開けませんでした!"
},
"success": {
"imageDataUriCopied": "画像データURIをクリップボードにコピーしました"
}
}
}

View file

@ -16,6 +16,40 @@
},
"mermaid": {
"loading": "머메이드 다이어그램 생성 중...",
"render_error": "다이어그램을 렌더링할 수 없음"
"render_error": "다이어그램을 렌더링할 수 없음",
"buttons": {
"zoom": "줌",
"zoomIn": "확대",
"zoomOut": "축소",
"copy": "복사",
"save": "이미지 저장",
"viewCode": "코드 보기",
"viewDiagram": "다이어그램 보기",
"close": "닫기"
},
"modal": {
"codeTitle": "머메이드 코드"
},
"tabs": {
"diagram": "다이어그램",
"code": "코드"
},
"feedback": {
"imageCopied": "이미지가 클립보드에 복사됨",
"copyError": "이미지 복사 오류"
}
},
"file": {
"errors": {
"invalidDataUri": "잘못된 데이터 URI 형식",
"copyingImage": "이미지 복사 오류: {{error}}",
"openingImage": "이미지 열기 오류: {{error}}",
"pathNotExists": "경로가 존재하지 않음: {{path}}",
"couldNotOpen": "파일을 열 수 없음: {{error}}",
"couldNotOpenGeneric": "파일을 열 수 없습니다!"
},
"success": {
"imageDataUriCopied": "이미지 데이터 URI가 클립보드에 복사됨"
}
}
}

View file

@ -16,6 +16,40 @@
},
"mermaid": {
"loading": "Mermaid-diagram genereren...",
"render_error": "Kan diagram niet weergeven"
"render_error": "Kan diagram niet weergeven",
"buttons": {
"zoom": "Zoom",
"zoomIn": "Inzoomen",
"zoomOut": "Uitzoomen",
"copy": "Kopiëren",
"save": "Afbeelding opslaan",
"viewCode": "Code bekijken",
"viewDiagram": "Diagram bekijken",
"close": "Sluiten"
},
"modal": {
"codeTitle": "Mermaid-code"
},
"tabs": {
"diagram": "Diagram",
"code": "Code"
},
"feedback": {
"imageCopied": "Afbeelding gekopieerd naar klembord",
"copyError": "Fout bij kopiëren van afbeelding"
}
},
"file": {
"errors": {
"invalidDataUri": "Ongeldig data-URI-formaat",
"copyingImage": "Fout bij kopiëren van afbeelding: {{error}}",
"openingImage": "Fout bij openen van afbeelding: {{error}}",
"pathNotExists": "Pad bestaat niet: {{path}}",
"couldNotOpen": "Kon bestand niet openen: {{error}}",
"couldNotOpenGeneric": "Kon bestand niet openen!"
},
"success": {
"imageDataUriCopied": "Afbeelding data-URI gekopieerd naar klembord"
}
}
}

View file

@ -16,6 +16,40 @@
},
"mermaid": {
"loading": "Generowanie diagramu mermaid...",
"render_error": "Nie można renderować diagramu"
"render_error": "Nie można renderować diagramu",
"buttons": {
"zoom": "Powiększenie",
"zoomIn": "Powiększ",
"zoomOut": "Pomniejsz",
"copy": "Kopiuj",
"save": "Zapisz obraz",
"viewCode": "Zobacz kod",
"viewDiagram": "Zobacz diagram",
"close": "Zamknij"
},
"modal": {
"codeTitle": "Kod Mermaid"
},
"tabs": {
"diagram": "Diagram",
"code": "Kod"
},
"feedback": {
"imageCopied": "Obraz skopiowany do schowka",
"copyError": "Błąd kopiowania obrazu"
}
},
"file": {
"errors": {
"invalidDataUri": "Nieprawidłowy format URI danych",
"copyingImage": "Błąd kopiowania obrazu: {{error}}",
"openingImage": "Błąd otwierania obrazu: {{error}}",
"pathNotExists": "Ścieżka nie istnieje: {{path}}",
"couldNotOpen": "Nie można otworzyć pliku: {{error}}",
"couldNotOpenGeneric": "Nie można otworzyć pliku!"
},
"success": {
"imageDataUriCopied": "URI danych obrazu skopiowane do schowka"
}
}
}

View file

@ -16,6 +16,40 @@
},
"mermaid": {
"loading": "Gerando diagrama mermaid...",
"render_error": "Não foi possível renderizar o diagrama"
"render_error": "Não foi possível renderizar o diagrama",
"buttons": {
"zoom": "Zoom",
"zoomIn": "Ampliar",
"zoomOut": "Reduzir",
"copy": "Copiar",
"save": "Salvar imagem",
"viewCode": "Ver código",
"viewDiagram": "Ver diagrama",
"close": "Fechar"
},
"modal": {
"codeTitle": "Código Mermaid"
},
"tabs": {
"diagram": "Diagrama",
"code": "Código"
},
"feedback": {
"imageCopied": "Imagem copiada para a área de transferência",
"copyError": "Erro ao copiar imagem"
}
},
"file": {
"errors": {
"invalidDataUri": "Formato de URI de dados inválido",
"copyingImage": "Erro ao copiar imagem: {{error}}",
"openingImage": "Erro ao abrir imagem: {{error}}",
"pathNotExists": "Caminho não existe: {{path}}",
"couldNotOpen": "Não foi possível abrir o arquivo: {{error}}",
"couldNotOpenGeneric": "Não foi possível abrir o arquivo!"
},
"success": {
"imageDataUriCopied": "URI de dados da imagem copiada para a área de transferência"
}
}
}

View file

@ -16,6 +16,40 @@
},
"mermaid": {
"loading": "Создание диаграммы mermaid...",
"render_error": "Не удалось отобразить диаграмму"
"render_error": "Не удалось отобразить диаграмму",
"buttons": {
"zoom": "Масштаб",
"zoomIn": "Увеличить",
"zoomOut": "Уменьшить",
"copy": "Копировать",
"save": "Сохранить изображение",
"viewCode": "Посмотреть код",
"viewDiagram": "Посмотреть диаграмму",
"close": "Закрыть"
},
"modal": {
"codeTitle": "Код Mermaid"
},
"tabs": {
"diagram": "Диаграмма",
"code": "Код"
},
"feedback": {
"imageCopied": "Изображение скопировано в буфер обмена",
"copyError": "Ошибка копирования изображения"
}
},
"file": {
"errors": {
"invalidDataUri": "Неверный формат URI данных",
"copyingImage": "Ошибка копирования изображения: {{error}}",
"openingImage": "Ошибка открытия изображения: {{error}}",
"pathNotExists": "Путь не существует: {{path}}",
"couldNotOpen": "Не удалось открыть файл: {{error}}",
"couldNotOpenGeneric": "Не удалось открыть файл!"
},
"success": {
"imageDataUriCopied": "URI данных изображения скопирован в буфер обмена"
}
}
}

View file

@ -16,6 +16,40 @@
},
"mermaid": {
"loading": "Mermaid diyagramı oluşturuluyor...",
"render_error": "Diyagram render edilemiyor"
"render_error": "Diyagram render edilemiyor",
"buttons": {
"zoom": "Yakınlaştır",
"zoomIn": "Büyüt",
"zoomOut": "Küçült",
"copy": "Kopyala",
"save": "Resmi kaydet",
"viewCode": "Kodu görüntüle",
"viewDiagram": "Diyagramı görüntüle",
"close": "Kapat"
},
"modal": {
"codeTitle": "Mermaid Kodu"
},
"tabs": {
"diagram": "Diyagram",
"code": "Kod"
},
"feedback": {
"imageCopied": "Görsel panoya kopyalandı",
"copyError": "Görsel kopyalama hatası"
}
},
"file": {
"errors": {
"invalidDataUri": "Geçersiz veri URI formatı",
"copyingImage": "Görsel kopyalama hatası: {{error}}",
"openingImage": "Görsel açma hatası: {{error}}",
"pathNotExists": "Yol mevcut değil: {{path}}",
"couldNotOpen": "Dosya açılamadı: {{error}}",
"couldNotOpenGeneric": "Dosya açılamadı!"
},
"success": {
"imageDataUriCopied": "Görsel veri URI'si panoya kopyalandı"
}
}
}

View file

@ -16,6 +16,40 @@
},
"mermaid": {
"loading": "Đang tạo biểu đồ mermaid...",
"render_error": "Không thể hiển thị biểu đồ"
"render_error": "Không thể hiển thị biểu đồ",
"buttons": {
"zoom": "Thu phóng",
"zoomIn": "Phóng to",
"zoomOut": "Thu nhỏ",
"copy": "Sao chép",
"save": "Lưu hình ảnh",
"viewCode": "Xem mã",
"viewDiagram": "Xem biểu đồ",
"close": "Đóng"
},
"modal": {
"codeTitle": "Mã Mermaid"
},
"tabs": {
"diagram": "Biểu đồ",
"code": "Mã"
},
"feedback": {
"imageCopied": "Hình ảnh đã được sao chép vào clipboard",
"copyError": "Lỗi sao chép hình ảnh"
}
},
"file": {
"errors": {
"invalidDataUri": "Định dạng URI dữ liệu không hợp lệ",
"copyingImage": "Lỗi sao chép hình ảnh: {{error}}",
"openingImage": "Lỗi mở hình ảnh: {{error}}",
"pathNotExists": "Đường dẫn không tồn tại: {{path}}",
"couldNotOpen": "Không thể mở tệp: {{error}}",
"couldNotOpenGeneric": "Không thể mở tệp!"
},
"success": {
"imageDataUriCopied": "URI dữ liệu hình ảnh đã được sao chép vào clipboard"
}
}
}

View file

@ -16,6 +16,40 @@
},
"mermaid": {
"loading": "生成 Mermaid 图表中...",
"render_error": "无法渲染图表"
"render_error": "无法渲染图表",
"buttons": {
"zoom": "缩放",
"zoomIn": "放大",
"zoomOut": "缩小",
"copy": "复制",
"save": "保存图片",
"viewCode": "查看代码",
"viewDiagram": "查看图表",
"close": "关闭"
},
"modal": {
"codeTitle": "Mermaid 代码"
},
"tabs": {
"diagram": "图表",
"code": "代码"
},
"feedback": {
"imageCopied": "图片已复制到剪贴板",
"copyError": "复制图片时出错"
}
},
"file": {
"errors": {
"invalidDataUri": "无效的数据 URI 格式",
"copyingImage": "复制图片时出错: {{error}}",
"openingImage": "打开图片时出错: {{error}}",
"pathNotExists": "路径不存在: {{path}}",
"couldNotOpen": "无法打开文件: {{error}}",
"couldNotOpenGeneric": "无法打开文件!"
},
"success": {
"imageDataUriCopied": "图片数据 URI 已复制到剪贴板"
}
}
}

View file

@ -16,6 +16,40 @@
},
"mermaid": {
"loading": "產生 Mermaid 圖表中...",
"render_error": "無法渲染圖表"
"render_error": "無法渲染圖表",
"buttons": {
"zoom": "縮放",
"zoomIn": "放大",
"zoomOut": "縮小",
"copy": "複製",
"save": "儲存圖片",
"viewCode": "檢視程式碼",
"viewDiagram": "檢視圖表",
"close": "關閉"
},
"modal": {
"codeTitle": "Mermaid 程式碼"
},
"tabs": {
"diagram": "圖表",
"code": "程式碼"
},
"feedback": {
"imageCopied": "圖片已複製到剪貼簿",
"copyError": "複製圖片時發生錯誤"
}
},
"file": {
"errors": {
"invalidDataUri": "無效的資料 URI 格式",
"copyingImage": "複製圖片時發生錯誤: {{error}}",
"openingImage": "開啟圖片時發生錯誤: {{error}}",
"pathNotExists": "路徑不存在: {{path}}",
"couldNotOpen": "無法開啟檔案: {{error}}",
"couldNotOpenGeneric": "無法開啟檔案!"
},
"success": {
"imageDataUriCopied": "圖片資料 URI 已複製到剪貼簿"
}
}
}