Merge origin/master into fix-issue-385-instance-index

This commit is contained in:
Danila Poyarkov 2026-08-12 14:57:42 +03:00
commit 8bba716db0
41 changed files with 671 additions and 220 deletions

View file

@ -5,6 +5,8 @@
### Fixed
- Keep imported Figma instances linked to their remapped source components so later component edits update existing instances. (#385)
- Restore native copy, cut, and paste shortcuts in desktop text inputs while preserving design clipboard handling on the canvas.
- Complete translated app, accessibility, font, color, collaboration, import, connection-test, and browser fallback text across all supported locales, and keep the document language synchronized with the selected locale.
## 0.14.0 - 2026-08-10

View file

@ -1,7 +1,7 @@
use serde::Deserialize;
use tauri::menu::{MenuBuilder, MenuItemBuilder, Submenu, SubmenuBuilder};
#[cfg(target_os = "macos")]
#[cfg(any(target_os = "macos", target_os = "windows"))]
use tauri::menu::PredefinedMenuItem;
#[derive(Deserialize)]
@ -41,12 +41,38 @@ fn build_submenu<R: tauri::Runtime>(
continue;
}
// Ordinary menu accelerators emit a frontend command without preserving the
// WebView's focused editing control. Native edit items let WebKit/WebView2
// route copy, cut, and paste to inputs or dispatch canvas clipboard events.
// Keep Linux on the frontend path: muda's native edit items require optional
// X11 automation and are explicitly unsupported on Wayland.
#[cfg(any(target_os = "macos", target_os = "windows"))]
if let Some(id) = entry.id.as_deref() {
let native_edit_item = match id {
"copy" => Some(PredefinedMenuItem::copy(app, Some(label))?),
"cut" => Some(PredefinedMenuItem::cut(app, Some(label))?),
"paste" => Some(PredefinedMenuItem::paste(app, Some(label))?),
_ => None,
};
if let Some(item) = native_edit_item {
builder = builder.item(&item);
continue;
}
}
let mut item = MenuItemBuilder::new(label);
if let Some(id) = &entry.id {
item = item.id(id);
}
if let Some(accelerator) = &entry.accelerator {
item = item.accelerator(accelerator);
// On Linux, muda's predefined edit commands depend on optional X11
// automation and do not work on Wayland. Leave these accelerators to the
// WebView instead, which preserves native input and canvas paste events.
let webview_handles_accelerator = cfg!(target_os = "linux")
&& matches!(entry.id.as_deref(), Some("copy" | "cut" | "paste"));
if !webview_handles_accelerator {
if let Some(accelerator) = &entry.accelerator {
item = item.accelerator(accelerator);
}
}
builder = builder.item(&item.build(app)?);
}

View file

@ -22,19 +22,19 @@
"selectLayerForJSX": "Wähle eine Ebene, um ihren JSX-Code zu sehen",
"connectAIProvider": "Verbinde einen KI-Anbieter, um den Chat zu starten.",
"connect": "Verbinden",
"testConnection": "Test connection",
"testingConnection": "Testing…",
"connectionTestSuccess": "Connected successfully. Model is reachable.",
"connectionTestMissingAPIKey": "Enter an API key before testing.",
"connectionTestMissingBaseURL": "Enter a base URL before testing.",
"connectionTestMissingModel": "Enter a model ID before testing.",
"connectionTestInvalidBaseURL": "Base URL is invalid. Use a full URL like https://api.example.com/v1.",
"connectionTestAuthFailed": "Authentication failed. Check your API key.",
"connectionTestModelNotFound": "Model not found. Check the model ID.",
"connectionTestAPITypeMismatch": "This endpoint does not appear to support the selected API type. Try Completions or Responses.",
"connectionTestBrowserNetworkFailed": "Could not reach this endpoint from the browser. Try the desktop app or use an endpoint with CORS enabled.",
"connectionTestNetworkFailed": "Could not reach the endpoint. Check the URL and your network connection.",
"connectionTestUnknownFailed": "Connection test failed. Check the provider settings and try again.",
"testConnection": "Verbindung testen",
"testingConnection": "Verbindung wird getestet…",
"connectionTestSuccess": "Verbindung erfolgreich. Das Modell ist erreichbar.",
"connectionTestMissingAPIKey": "Gib vor dem Testen einen API-Schlüssel ein.",
"connectionTestMissingBaseURL": "Gib vor dem Testen eine Basis-URL ein.",
"connectionTestMissingModel": "Gib vor dem Testen eine Modell-ID ein.",
"connectionTestInvalidBaseURL": "Die Basis-URL ist ungültig. Verwende eine vollständige URL wie https://api.example.com/v1.",
"connectionTestAuthFailed": "Authentifizierung fehlgeschlagen. Prüfe deinen API-Schlüssel.",
"connectionTestModelNotFound": "Modell nicht gefunden. Prüfe die Modell-ID.",
"connectionTestAPITypeMismatch": "Dieser Endpunkt scheint den ausgewählten API-Typ nicht zu unterstützen. Versuche Completions oder Responses.",
"connectionTestBrowserNetworkFailed": "Dieser Endpunkt ist vom Browser aus nicht erreichbar. Versuche die Desktop-App oder einen Endpunkt mit aktiviertem CORS.",
"connectionTestNetworkFailed": "Der Endpunkt ist nicht erreichbar. Prüfe die URL und deine Netzwerkverbindung.",
"connectionTestUnknownFailed": "Verbindungstest fehlgeschlagen. Prüfe die Anbietereinstellungen und versuche es erneut.",
"getAPIKey": "Einen {provider}-API-Schlüssel erhalten →",
"oneKeyManyModels": "Ein Schlüssel für über 100 Modelle aller Anbieter.",
"describeChange": "Beschreibe eine Änderung…",
@ -188,10 +188,10 @@
"onlineFontProviders": "Online-Schriftanbieter",
"downloadMissingWebFonts": "Fehlende Web-Schriften über aktivierte Anbieter herunterladen.",
"webFontProvidersRequireDesktopApp": "Online-Schriftanbieter-Kataloge sind in der Web-App nicht verfügbar. Lade die Desktop-App herunter, um Anbieter-Schriften zu durchsuchen und zu laden.",
"clipboardImageUnavailableWeb": "Pasted design includes 1 image that cannot be loaded in the web app. Use the desktop app to include it.",
"clipboardImagesUnavailableWeb": "Pasted design includes {count} images that cannot be loaded in the web app. Use the desktop app to include them.",
"clipboardImageFetchFailed": "Failed to fetch 1 image from Figma. Check that the source file is accessible and try again.",
"clipboardImagesFetchFailed": "Failed to fetch {count} images from Figma. Check that the source file is accessible and try again.",
"clipboardImageUnavailableWeb": "Das eingefügte Design enthält 1 Bild, das in der Web-App nicht geladen werden kann. Verwende die Desktop-App, um es einzuschließen.",
"clipboardImagesUnavailableWeb": "Das eingefügte Design enthält {count} Bilder, die in der Web-App nicht geladen werden können. Verwende die Desktop-App, um sie einzuschließen.",
"clipboardImageFetchFailed": "1 Bild konnte nicht aus Figma abgerufen werden. Prüfe, ob die Quelldatei zugänglich ist, und versuche es erneut.",
"clipboardImagesFetchFailed": "{count} Bilder konnten nicht aus Figma abgerufen werden. Prüfe, ob die Quelldatei zugänglich ist, und versuche es erneut.",
"enable": "Aktivieren",
"disable": "Deaktivieren",
"fallbackPacks": "Fallback-Pakete",
@ -213,5 +213,30 @@
"you": "Du",
"youSuffix": "du",
"followingPeerStop": "Folge {name} (zum Stoppen klicken)",
"clickToFollowPeer": "Klicken, um {name} zu folgen"
"clickToFollowPeer": "Klicken, um {name} zu folgen",
"importLabel": "Importieren",
"importHTMLCSS": "HTML/CSS importieren",
"importHTMLCSSDescription": "HTML sowie optionales CSS oder kompiliertes Tailwind-CSS einfügen.",
"paste": "Einfügen",
"importFailed": "Import fehlgeschlagen. Prüfe HTML und CSS und versuche es erneut.",
"importReplacesDocument": "Der Import ersetzt das aktuelle Dokument.",
"importing": "Wird importiert…",
"importToCanvas": "Auf die Arbeitsfläche importieren",
"inThisRoom": "In diesem Raum",
"disconnect": "Verbindung trennen",
"toolFinished": "Fertig",
"unknownTool": "Unbekanntes Werkzeug",
"permissionRequest": "{tool} fordert eine Berechtigung an.",
"mobilePanelNavigation": "Navigation der mobilen Seitenleiste",
"toolOptions": "Optionen für {tool}",
"removeGradientStop": "Farbverlaufspunkt entfernen",
"notifications": "Benachrichtigungen",
"saveAsPrompt": "Speichern unter:",
"browserFileAPINotSupported": "Dein Browser unterstützt die lokale Datei-API nicht. Dateien werden heruntergeladen, statt direkt gespeichert zu werden.",
"useChrome": "Chrome verwenden",
"orEdgeForFullSupport": "oder Edge für vollständige Unterstützung.",
"dismiss": "Schließen",
"toolRunning": "Wird ausgeführt…",
"toolError": "Fehler",
"permissionRequestTitle": "Berechtigungsanfrage"
}

View file

@ -263,5 +263,18 @@
"blendModeHue": "Farbton",
"blendModeSaturation": "Sättigung",
"blendModeColor": "Farbe",
"blendModeLuminosity": "Luminanz"
"blendModeLuminosity": "Luminanz",
"noFontsFound": "Keine Schriften gefunden",
"noLocalFontsAvailable": "Keine lokalen Schriften verfügbar.",
"localFontsAccessHint": "Verwende die Desktop-App oder Chrome/Edge, um auf Systemschriften zuzugreifen.",
"editColor": "Farbe bearbeiten",
"red": "Rot",
"green": "Grün",
"blue": "Blau",
"hue": "Farbton",
"saturation": "Sättigung",
"brightness": "Helligkeit",
"lightness": "Helligkeit",
"chroma": "Chroma",
"alpha": "Alpha"
}

View file

@ -22,19 +22,19 @@
"selectLayerForJSX": "Selecciona una capa para ver su código JSX",
"connectAIProvider": "Conecta un proveedor de IA para empezar a chatear.",
"connect": "Conectar",
"testConnection": "Test connection",
"testingConnection": "Testing…",
"connectionTestSuccess": "Connected successfully. Model is reachable.",
"connectionTestMissingAPIKey": "Enter an API key before testing.",
"connectionTestMissingBaseURL": "Enter a base URL before testing.",
"connectionTestMissingModel": "Enter a model ID before testing.",
"connectionTestInvalidBaseURL": "Base URL is invalid. Use a full URL like https://api.example.com/v1.",
"connectionTestAuthFailed": "Authentication failed. Check your API key.",
"connectionTestModelNotFound": "Model not found. Check the model ID.",
"connectionTestAPITypeMismatch": "This endpoint does not appear to support the selected API type. Try Completions or Responses.",
"connectionTestBrowserNetworkFailed": "Could not reach this endpoint from the browser. Try the desktop app or use an endpoint with CORS enabled.",
"connectionTestNetworkFailed": "Could not reach the endpoint. Check the URL and your network connection.",
"connectionTestUnknownFailed": "Connection test failed. Check the provider settings and try again.",
"testConnection": "Probar conexión",
"testingConnection": "Probando…",
"connectionTestSuccess": "Conexión correcta. Se puede acceder al modelo.",
"connectionTestMissingAPIKey": "Introduce una clave API antes de probar.",
"connectionTestMissingBaseURL": "Introduce una URL base antes de probar.",
"connectionTestMissingModel": "Introduce un ID de modelo antes de probar.",
"connectionTestInvalidBaseURL": "La URL base no es válida. Usa una URL completa como https://api.example.com/v1.",
"connectionTestAuthFailed": "Error de autenticación. Comprueba tu clave API.",
"connectionTestModelNotFound": "No se ha encontrado el modelo. Comprueba el ID del modelo.",
"connectionTestAPITypeMismatch": "Este endpoint no parece admitir el tipo de API seleccionado. Prueba Completions o Responses.",
"connectionTestBrowserNetworkFailed": "No se ha podido acceder a este endpoint desde el navegador. Prueba la aplicación de escritorio o usa un endpoint con CORS habilitado.",
"connectionTestNetworkFailed": "No se ha podido acceder al endpoint. Comprueba la URL y la conexión de red.",
"connectionTestUnknownFailed": "La prueba de conexión ha fallado. Comprueba los ajustes del proveedor e inténtalo de nuevo.",
"getAPIKey": "Obtener una clave API de {provider} →",
"oneKeyManyModels": "Una clave para más de 100 modelos de todos los proveedores.",
"describeChange": "Describe un cambio…",
@ -188,10 +188,10 @@
"onlineFontProviders": "Proveedores de fuentes en línea",
"downloadMissingWebFonts": "Descarga fuentes web faltantes mediante los proveedores activados.",
"webFontProvidersRequireDesktopApp": "Los catálogos de proveedores de fuentes en línea no están disponibles en la app web. Descarga la app de escritorio para explorar y cargar fuentes de proveedores.",
"clipboardImageUnavailableWeb": "Pasted design includes 1 image that cannot be loaded in the web app. Use the desktop app to include it.",
"clipboardImagesUnavailableWeb": "Pasted design includes {count} images that cannot be loaded in the web app. Use the desktop app to include them.",
"clipboardImageFetchFailed": "Failed to fetch 1 image from Figma. Check that the source file is accessible and try again.",
"clipboardImagesFetchFailed": "Failed to fetch {count} images from Figma. Check that the source file is accessible and try again.",
"clipboardImageUnavailableWeb": "El diseño pegado contiene 1 imagen que no se puede cargar en la aplicación web. Usa la aplicación de escritorio para incluirla.",
"clipboardImagesUnavailableWeb": "El diseño pegado contiene {count} imágenes que no se pueden cargar en la aplicación web. Usa la aplicación de escritorio para incluirlas.",
"clipboardImageFetchFailed": "No se ha podido obtener 1 imagen de Figma. Comprueba que el archivo de origen sea accesible e inténtalo de nuevo.",
"clipboardImagesFetchFailed": "No se han podido obtener {count} imágenes de Figma. Comprueba que el archivo de origen sea accesible e inténtalo de nuevo.",
"enable": "Activar",
"disable": "Desactivar",
"fallbackPacks": "Paquetes de respaldo",
@ -213,5 +213,30 @@
"you": "Tú",
"youSuffix": "tú",
"followingPeerStop": "Siguiendo a {name} (haz clic para detener)",
"clickToFollowPeer": "Haz clic para seguir a {name}"
"clickToFollowPeer": "Haz clic para seguir a {name}",
"importLabel": "Importar",
"importHTMLCSS": "Importar HTML/CSS",
"importHTMLCSSDescription": "Pega HTML y, opcionalmente, CSS o CSS de Tailwind compilado.",
"paste": "Pegar",
"importFailed": "La importación ha fallado. Comprueba el HTML y el CSS e inténtalo de nuevo.",
"importReplacesDocument": "La importación reemplaza el documento actual.",
"importing": "Importando…",
"importToCanvas": "Importar al lienzo",
"inThisRoom": "En esta sala",
"disconnect": "Desconectar",
"toolFinished": "Hecho",
"unknownTool": "Herramienta desconocida",
"permissionRequest": "{tool} solicita permiso.",
"mobilePanelNavigation": "Navegación del panel móvil",
"toolOptions": "Opciones de {tool}",
"removeGradientStop": "Eliminar parada de degradado",
"notifications": "Notificaciones",
"saveAsPrompt": "Guardar como:",
"browserFileAPINotSupported": "Tu navegador no admite la API de archivos locales. Los archivos se descargarán en lugar de guardarse en el mismo lugar.",
"useChrome": "Usar Chrome",
"orEdgeForFullSupport": "o Edge para disponer de compatibilidad completa.",
"dismiss": "Descartar",
"toolRunning": "Ejecutando…",
"toolError": "Error",
"permissionRequestTitle": "Solicitud de permiso"
}

View file

@ -263,5 +263,18 @@
"blendModeHue": "Tono",
"blendModeSaturation": "Saturación",
"blendModeColor": "Color",
"blendModeLuminosity": "Luminosidad"
"blendModeLuminosity": "Luminosidad",
"noFontsFound": "No se han encontrado fuentes",
"noLocalFontsAvailable": "No hay fuentes locales disponibles.",
"localFontsAccessHint": "Usa la aplicación de escritorio o Chrome/Edge para acceder a las fuentes del sistema.",
"editColor": "Editar color",
"red": "Rojo",
"green": "Verde",
"blue": "Azul",
"hue": "Tono",
"saturation": "Saturación",
"brightness": "Brillo",
"lightness": "Luminosidad",
"chroma": "Croma",
"alpha": "Alfa"
}

View file

@ -22,19 +22,19 @@
"selectLayerForJSX": "Sélectionnez un calque pour voir son code JSX",
"connectAIProvider": "Connectez un fournisseur dIA pour commencer à discuter.",
"connect": "Connecter",
"testConnection": "Test connection",
"testingConnection": "Testing…",
"connectionTestSuccess": "Connected successfully. Model is reachable.",
"connectionTestMissingAPIKey": "Enter an API key before testing.",
"connectionTestMissingBaseURL": "Enter a base URL before testing.",
"connectionTestMissingModel": "Enter a model ID before testing.",
"connectionTestInvalidBaseURL": "Base URL is invalid. Use a full URL like https://api.example.com/v1.",
"connectionTestAuthFailed": "Authentication failed. Check your API key.",
"connectionTestModelNotFound": "Model not found. Check the model ID.",
"connectionTestAPITypeMismatch": "This endpoint does not appear to support the selected API type. Try Completions or Responses.",
"connectionTestBrowserNetworkFailed": "Could not reach this endpoint from the browser. Try the desktop app or use an endpoint with CORS enabled.",
"connectionTestNetworkFailed": "Could not reach the endpoint. Check the URL and your network connection.",
"connectionTestUnknownFailed": "Connection test failed. Check the provider settings and try again.",
"testConnection": "Tester la connexion",
"testingConnection": "Test en cours…",
"connectionTestSuccess": "Connexion réussie. Le modèle est accessible.",
"connectionTestMissingAPIKey": "Saisissez une clé API avant le test.",
"connectionTestMissingBaseURL": "Saisissez une URL de base avant le test.",
"connectionTestMissingModel": "Saisissez un ID de modèle avant le test.",
"connectionTestInvalidBaseURL": "LURL de base est invalide. Utilisez une URL complète comme https://api.example.com/v1.",
"connectionTestAuthFailed": "Échec de lauthentification. Vérifiez votre clé API.",
"connectionTestModelNotFound": "Modèle introuvable. Vérifiez son ID.",
"connectionTestAPITypeMismatch": "Ce point de terminaison ne semble pas prendre en charge le type dAPI sélectionné. Essayez Completions ou Responses.",
"connectionTestBrowserNetworkFailed": "Impossible datteindre ce point de terminaison depuis le navigateur. Essayez lapplication de bureau ou un point de terminaison avec CORS activé.",
"connectionTestNetworkFailed": "Impossible datteindre le point de terminaison. Vérifiez lURL et votre connexion réseau.",
"connectionTestUnknownFailed": "Échec du test de connexion. Vérifiez les paramètres du fournisseur, puis réessayez.",
"getAPIKey": "Obtenir une clé API {provider} →",
"oneKeyManyModels": "Une clé pour plus de 100 modèles de tous les fournisseurs.",
"describeChange": "Décrivez un changement…",
@ -188,10 +188,10 @@
"onlineFontProviders": "Fournisseurs de polices en ligne",
"downloadMissingWebFonts": "Télécharger les polices web manquantes via les fournisseurs activés.",
"webFontProvidersRequireDesktopApp": "Les catalogues de fournisseurs de polices en ligne ne sont pas disponibles dans lapp web. Téléchargez lapp de bureau pour parcourir et charger les polices des fournisseurs.",
"clipboardImageUnavailableWeb": "Pasted design includes 1 image that cannot be loaded in the web app. Use the desktop app to include it.",
"clipboardImagesUnavailableWeb": "Pasted design includes {count} images that cannot be loaded in the web app. Use the desktop app to include them.",
"clipboardImageFetchFailed": "Failed to fetch 1 image from Figma. Check that the source file is accessible and try again.",
"clipboardImagesFetchFailed": "Failed to fetch {count} images from Figma. Check that the source file is accessible and try again.",
"clipboardImageUnavailableWeb": "Le design collé contient 1 image qui ne peut pas être chargée dans lapplication web. Utilisez lapplication de bureau pour linclure.",
"clipboardImagesUnavailableWeb": "Le design collé contient {count} images qui ne peuvent pas être chargées dans lapplication web. Utilisez lapplication de bureau pour les inclure.",
"clipboardImageFetchFailed": "Impossible de récupérer 1 image depuis Figma. Vérifiez que le fichier source est accessible, puis réessayez.",
"clipboardImagesFetchFailed": "Impossible de récupérer {count} images depuis Figma. Vérifiez que le fichier source est accessible, puis réessayez.",
"enable": "Activer",
"disable": "Désactiver",
"fallbackPacks": "Packs de secours",
@ -213,5 +213,30 @@
"you": "Vous",
"youSuffix": "vous",
"followingPeerStop": "Suit {name} (cliquer pour arrêter)",
"clickToFollowPeer": "Cliquer pour suivre {name}"
"clickToFollowPeer": "Cliquer pour suivre {name}",
"importLabel": "Importer",
"importHTMLCSS": "Importer du HTML/CSS",
"importHTMLCSSDescription": "Collez du HTML avec, si besoin, du CSS ou du CSS Tailwind compilé.",
"paste": "Coller",
"importFailed": "Échec de limportation. Vérifiez le HTML et le CSS, puis réessayez.",
"importReplacesDocument": "Limportation remplace le document actuel.",
"importing": "Importation…",
"importToCanvas": "Importer dans le canevas",
"inThisRoom": "Dans cette salle",
"disconnect": "Déconnecter",
"toolFinished": "Terminé",
"unknownTool": "Outil inconnu",
"permissionRequest": "{tool} demande une autorisation.",
"mobilePanelNavigation": "Navigation du panneau mobile",
"toolOptions": "Options de {tool}",
"removeGradientStop": "Supprimer le point de dégradé",
"notifications": "Notifications",
"saveAsPrompt": "Enregistrer sous :",
"browserFileAPINotSupported": "Votre navigateur ne prend pas en charge lAPI de fichiers locaux. Les fichiers seront téléchargés au lieu dêtre enregistrés sur place.",
"useChrome": "Utiliser Chrome",
"orEdgeForFullSupport": "ou Edge pour une prise en charge complète.",
"dismiss": "Ignorer",
"toolRunning": "Exécution…",
"toolError": "Erreur",
"permissionRequestTitle": "Demande dautorisation"
}

View file

@ -263,5 +263,18 @@
"blendModeHue": "Teinte",
"blendModeSaturation": "Saturation",
"blendModeColor": "Couleur",
"blendModeLuminosity": "Luminosité"
"blendModeLuminosity": "Luminosité",
"noFontsFound": "Aucune police trouvée",
"noLocalFontsAvailable": "Aucune police locale disponible.",
"localFontsAccessHint": "Utilisez lapplication de bureau ou Chrome/Edge pour accéder aux polices système.",
"editColor": "Modifier la couleur",
"red": "Rouge",
"green": "Vert",
"blue": "Bleu",
"hue": "Teinte",
"saturation": "Saturation",
"brightness": "Luminosité",
"lightness": "Clarté",
"chroma": "Chroma",
"alpha": "Alpha"
}

View file

@ -22,19 +22,19 @@
"selectLayerForJSX": "Seleziona un livello per vedere il suo codice JSX",
"connectAIProvider": "Collega un provider AI per iniziare a chattare.",
"connect": "Connetti",
"testConnection": "Test connection",
"testingConnection": "Testing…",
"connectionTestSuccess": "Connected successfully. Model is reachable.",
"connectionTestMissingAPIKey": "Enter an API key before testing.",
"connectionTestMissingBaseURL": "Enter a base URL before testing.",
"connectionTestMissingModel": "Enter a model ID before testing.",
"connectionTestInvalidBaseURL": "Base URL is invalid. Use a full URL like https://api.example.com/v1.",
"connectionTestAuthFailed": "Authentication failed. Check your API key.",
"connectionTestModelNotFound": "Model not found. Check the model ID.",
"connectionTestAPITypeMismatch": "This endpoint does not appear to support the selected API type. Try Completions or Responses.",
"connectionTestBrowserNetworkFailed": "Could not reach this endpoint from the browser. Try the desktop app or use an endpoint with CORS enabled.",
"connectionTestNetworkFailed": "Could not reach the endpoint. Check the URL and your network connection.",
"connectionTestUnknownFailed": "Connection test failed. Check the provider settings and try again.",
"testConnection": "Verifica connessione",
"testingConnection": "Verifica in corso…",
"connectionTestSuccess": "Connessione riuscita. Il modello è raggiungibile.",
"connectionTestMissingAPIKey": "Inserisci una chiave API prima della verifica.",
"connectionTestMissingBaseURL": "Inserisci un URL di base prima della verifica.",
"connectionTestMissingModel": "Inserisci un ID modello prima della verifica.",
"connectionTestInvalidBaseURL": "LURL di base non è valido. Usa un URL completo come https://api.example.com/v1.",
"connectionTestAuthFailed": "Autenticazione non riuscita. Controlla la chiave API.",
"connectionTestModelNotFound": "Modello non trovato. Controlla lID modello.",
"connectionTestAPITypeMismatch": "Questo endpoint non sembra supportare il tipo di API selezionato. Prova Completions o Responses.",
"connectionTestBrowserNetworkFailed": "Impossibile raggiungere questo endpoint dal browser. Prova lapp desktop o usa un endpoint con CORS abilitato.",
"connectionTestNetworkFailed": "Impossibile raggiungere lendpoint. Controlla lURL e la connessione di rete.",
"connectionTestUnknownFailed": "Verifica della connessione non riuscita. Controlla le impostazioni del provider e riprova.",
"getAPIKey": "Ottieni una chiave API {provider} →",
"oneKeyManyModels": "Una chiave per oltre 100 modelli di tutti i provider.",
"describeChange": "Descrivi una modifica…",
@ -188,10 +188,10 @@
"onlineFontProviders": "Provider di font online",
"downloadMissingWebFonts": "Scarica i font web mancanti tramite i provider abilitati.",
"webFontProvidersRequireDesktopApp": "I cataloghi dei provider di font online non sono disponibili nellapp web. Scarica lapp desktop per sfogliare e caricare i font dei provider.",
"clipboardImageUnavailableWeb": "Pasted design includes 1 image that cannot be loaded in the web app. Use the desktop app to include it.",
"clipboardImagesUnavailableWeb": "Pasted design includes {count} images that cannot be loaded in the web app. Use the desktop app to include them.",
"clipboardImageFetchFailed": "Failed to fetch 1 image from Figma. Check that the source file is accessible and try again.",
"clipboardImagesFetchFailed": "Failed to fetch {count} images from Figma. Check that the source file is accessible and try again.",
"clipboardImageUnavailableWeb": "Il design incollato contiene 1 immagine che non può essere caricata nellapp web. Usa lapp desktop per includerla.",
"clipboardImagesUnavailableWeb": "Il design incollato contiene {count} immagini che non possono essere caricate nellapp web. Usa lapp desktop per includerle.",
"clipboardImageFetchFailed": "Impossibile recuperare 1 immagine da Figma. Controlla che il file sorgente sia accessibile e riprova.",
"clipboardImagesFetchFailed": "Impossibile recuperare {count} immagini da Figma. Controlla che il file sorgente sia accessibile e riprova.",
"enable": "Abilita",
"disable": "Disabilita",
"fallbackPacks": "Pacchetti di fallback",
@ -213,5 +213,30 @@
"you": "Tu",
"youSuffix": "tu",
"followingPeerStop": "Segui {name} (clic per interrompere)",
"clickToFollowPeer": "Fai clic per seguire {name}"
"clickToFollowPeer": "Fai clic per seguire {name}",
"importLabel": "Importa",
"importHTMLCSS": "Importa HTML/CSS",
"importHTMLCSSDescription": "Incolla HTML con CSS facoltativo o CSS Tailwind compilato.",
"paste": "Incolla",
"importFailed": "Importazione non riuscita. Controlla lHTML e il CSS e riprova.",
"importReplacesDocument": "Limportazione sostituisce il documento corrente.",
"importing": "Importazione…",
"importToCanvas": "Importa nellarea di lavoro",
"inThisRoom": "In questa stanza",
"disconnect": "Disconnetti",
"toolFinished": "Fine",
"unknownTool": "Strumento sconosciuto",
"permissionRequest": "{tool} richiede lautorizzazione.",
"mobilePanelNavigation": "Navigazione pannello mobile",
"toolOptions": "Opzioni di {tool}",
"removeGradientStop": "Rimuovi punto sfumatura",
"notifications": "Notifiche",
"saveAsPrompt": "Salva con nome:",
"browserFileAPINotSupported": "Il browser non supporta lAPI dei file locali. I file verranno scaricati anziché salvati nella posizione corrente.",
"useChrome": "Usa Chrome",
"orEdgeForFullSupport": "o Edge per il supporto completo.",
"dismiss": "Chiudi",
"toolRunning": "Esecuzione…",
"toolError": "Errore",
"permissionRequestTitle": "Richiesta di autorizzazione"
}

View file

@ -263,5 +263,18 @@
"blendModeHue": "Tonalità",
"blendModeSaturation": "Saturazione",
"blendModeColor": "Colore",
"blendModeLuminosity": "Luminosità"
"blendModeLuminosity": "Luminosità",
"noFontsFound": "Nessun font trovato",
"noLocalFontsAvailable": "Nessun font locale disponibile.",
"localFontsAccessHint": "Usa lapp desktop o Chrome/Edge per accedere ai font di sistema.",
"editColor": "Modifica colore",
"red": "Rosso",
"green": "Verde",
"blue": "Blu",
"hue": "Tonalità",
"saturation": "Saturazione",
"brightness": "Luminosità",
"lightness": "Chiarezza",
"chroma": "Croma",
"alpha": "Alfa"
}

View file

@ -22,19 +22,19 @@
"selectLayerForJSX": "JSXコードを表示するレイヤーを選択してください",
"connectAIProvider": "チャットを始めるには、AIプロバイダーを接続してください。",
"connect": "接続",
"testConnection": "Test connection",
"testingConnection": "Testing…",
"connectionTestSuccess": "Connected successfully. Model is reachable.",
"connectionTestMissingAPIKey": "Enter an API key before testing.",
"connectionTestMissingBaseURL": "Enter a base URL before testing.",
"connectionTestMissingModel": "Enter a model ID before testing.",
"connectionTestInvalidBaseURL": "Base URL is invalid. Use a full URL like https://api.example.com/v1.",
"connectionTestAuthFailed": "Authentication failed. Check your API key.",
"connectionTestModelNotFound": "Model not found. Check the model ID.",
"connectionTestAPITypeMismatch": "This endpoint does not appear to support the selected API type. Try Completions or Responses.",
"connectionTestBrowserNetworkFailed": "Could not reach this endpoint from the browser. Try the desktop app or use an endpoint with CORS enabled.",
"connectionTestNetworkFailed": "Could not reach the endpoint. Check the URL and your network connection.",
"connectionTestUnknownFailed": "Connection test failed. Check the provider settings and try again.",
"testConnection": "接続をテスト",
"testingConnection": "テスト中…",
"connectionTestSuccess": "接続しました。モデルにアクセスできます。",
"connectionTestMissingAPIKey": "テストする前にAPIキーを入力してください。",
"connectionTestMissingBaseURL": "テストする前にベースURLを入力してください。",
"connectionTestMissingModel": "テストする前にモデルIDを入力してください。",
"connectionTestInvalidBaseURL": "ベースURLが無効です。https://api.example.com/v1 のような完全なURLを使用してください。",
"connectionTestAuthFailed": "認証に失敗しました。APIキーを確認してください。",
"connectionTestModelNotFound": "モデルが見つかりません。モデルIDを確認してください。",
"connectionTestAPITypeMismatch": "このエンドポイントは選択したAPIタイプに対応していないようです。CompletionsまたはResponsesをお試しください。",
"connectionTestBrowserNetworkFailed": "ブラウザからこのエンドポイントに接続できませんでした。デスクトップアプリを試すか、CORSが有効なエンドポイントを使用してください。",
"connectionTestNetworkFailed": "エンドポイントに接続できませんでした。URLとネットワーク接続を確認してください。",
"connectionTestUnknownFailed": "接続テストに失敗しました。プロバイダー設定を確認して、もう一度お試しください。",
"getAPIKey": "{provider} からAPIキーを取得 →",
"oneKeyManyModels": "1つのキーで、すべてのプロバイダーの100以上のモデルに対応。",
"describeChange": "変更内容を入力…",
@ -188,10 +188,10 @@
"onlineFontProviders": "オンラインフォントプロバイダー",
"downloadMissingWebFonts": "有効なプロバイダーから不足しているWebフォントをダウンロードします。",
"webFontProvidersRequireDesktopApp": "オンラインフォントプロバイダーのカタログはWebアプリでは利用できません。プロバイダーのフォントを参照して読み込むにはデスクトップアプリをダウンロードしてください。",
"clipboardImageUnavailableWeb": "Pasted design includes 1 image that cannot be loaded in the web app. Use the desktop app to include it.",
"clipboardImagesUnavailableWeb": "Pasted design includes {count} images that cannot be loaded in the web app. Use the desktop app to include them.",
"clipboardImageFetchFailed": "Failed to fetch 1 image from Figma. Check that the source file is accessible and try again.",
"clipboardImagesFetchFailed": "Failed to fetch {count} images from Figma. Check that the source file is accessible and try again.",
"clipboardImageUnavailableWeb": "貼り付けたデザインには、Webアプリで読み込めない画像が1件含まれています。その画像を含めるにはデスクトップアプリを使用してください。",
"clipboardImagesUnavailableWeb": "貼り付けたデザインには、Webアプリで読み込めない画像が{count}件含まれています。それらの画像を含めるにはデスクトップアプリを使用してください。",
"clipboardImageFetchFailed": "Figmaから画像1件を取得できませんでした。元のファイルにアクセスできることを確認して、もう一度お試しください。",
"clipboardImagesFetchFailed": "Figmaから画像{count}件を取得できませんでした。元のファイルにアクセスできることを確認して、もう一度お試しください。",
"enable": "有効にする",
"disable": "無効にする",
"fallbackPacks": "フォールバックパック",
@ -213,5 +213,30 @@
"you": "あなた",
"youSuffix": "あなた",
"followingPeerStop": "{name} をフォロー中 (クリックして停止)",
"clickToFollowPeer": "クリックして {name} をフォロー"
"clickToFollowPeer": "クリックして {name} をフォロー",
"importLabel": "インポート",
"importHTMLCSS": "HTML/CSSをインポート",
"importHTMLCSSDescription": "HTMLと、必要に応じてCSSまたはコンパイル済みTailwind CSSを貼り付けます。",
"paste": "貼り付け",
"importFailed": "インポートに失敗しました。HTMLとCSSを確認して、もう一度お試しください。",
"importReplacesDocument": "インポートすると現在のドキュメントが置き換えられます。",
"importing": "インポート中…",
"importToCanvas": "キャンバスにインポート",
"inThisRoom": "このルーム内",
"disconnect": "切断",
"toolFinished": "完了",
"unknownTool": "不明なツール",
"permissionRequest": "{tool}が権限を要求しています。",
"mobilePanelNavigation": "モバイルパネルのナビゲーション",
"toolOptions": "{tool}のオプション",
"removeGradientStop": "グラデーション停止点を削除",
"notifications": "通知",
"saveAsPrompt": "名前を付けて保存:",
"browserFileAPINotSupported": "このブラウザはローカルファイルAPIに対応していません。ファイルは同じ場所に保存されず、ダウンロードされます。",
"useChrome": "Chromeを使用",
"orEdgeForFullSupport": "またはEdgeを使用するとすべての機能を利用できます。",
"dismiss": "閉じる",
"toolRunning": "実行中…",
"toolError": "エラー",
"permissionRequestTitle": "権限リクエスト"
}

View file

@ -263,5 +263,18 @@
"blendModeHue": "色相",
"blendModeSaturation": "彩度",
"blendModeColor": "カラー",
"blendModeLuminosity": "輝度"
"blendModeLuminosity": "輝度",
"noFontsFound": "フォントが見つかりません",
"noLocalFontsAvailable": "利用できるローカルフォントがありません。",
"localFontsAccessHint": "システムフォントにアクセスするには、デスクトップアプリまたはChrome/Edgeを使用してください。",
"editColor": "色を編集",
"red": "赤",
"green": "緑",
"blue": "青",
"hue": "色相",
"saturation": "彩度",
"brightness": "明度",
"lightness": "明度",
"chroma": "彩度",
"alpha": "アルファ"
}

View file

@ -22,19 +22,19 @@
"selectLayerForJSX": "Wybierz warstwę, aby zobaczyć jej kod JSX",
"connectAIProvider": "Połącz dostawcę AI, aby rozpocząć czat.",
"connect": "Połącz",
"testConnection": "Test connection",
"testingConnection": "Testing…",
"connectionTestSuccess": "Connected successfully. Model is reachable.",
"connectionTestMissingAPIKey": "Enter an API key before testing.",
"connectionTestMissingBaseURL": "Enter a base URL before testing.",
"connectionTestMissingModel": "Enter a model ID before testing.",
"connectionTestInvalidBaseURL": "Base URL is invalid. Use a full URL like https://api.example.com/v1.",
"connectionTestAuthFailed": "Authentication failed. Check your API key.",
"connectionTestModelNotFound": "Model not found. Check the model ID.",
"connectionTestAPITypeMismatch": "This endpoint does not appear to support the selected API type. Try Completions or Responses.",
"connectionTestBrowserNetworkFailed": "Could not reach this endpoint from the browser. Try the desktop app or use an endpoint with CORS enabled.",
"connectionTestNetworkFailed": "Could not reach the endpoint. Check the URL and your network connection.",
"connectionTestUnknownFailed": "Connection test failed. Check the provider settings and try again.",
"testConnection": "Testuj połączenie",
"testingConnection": "Testowanie…",
"connectionTestSuccess": "Połączono pomyślnie. Model jest dostępny.",
"connectionTestMissingAPIKey": "Wprowadź klucz API przed rozpoczęciem testu.",
"connectionTestMissingBaseURL": "Wprowadź bazowy adres URL przed rozpoczęciem testu.",
"connectionTestMissingModel": "Wprowadź identyfikator modelu przed rozpoczęciem testu.",
"connectionTestInvalidBaseURL": "Bazowy adres URL jest nieprawidłowy. Użyj pełnego adresu, np. https://api.example.com/v1.",
"connectionTestAuthFailed": "Uwierzytelnianie nie powiodło się. Sprawdź klucz API.",
"connectionTestModelNotFound": "Nie znaleziono modelu. Sprawdź jego identyfikator.",
"connectionTestAPITypeMismatch": "Ten punkt końcowy prawdopodobnie nie obsługuje wybranego typu API. Spróbuj Completions lub Responses.",
"connectionTestBrowserNetworkFailed": "Nie udało się połączyć z tym punktem końcowym z przeglądarki. Spróbuj użyć aplikacji komputerowej lub punktu końcowego z włączonym CORS.",
"connectionTestNetworkFailed": "Nie udało się połączyć z punktem końcowym. Sprawdź adres URL i połączenie sieciowe.",
"connectionTestUnknownFailed": "Test połączenia nie powiódł się. Sprawdź ustawienia dostawcy i spróbuj ponownie.",
"getAPIKey": "Pobierz klucz API {provider} →",
"oneKeyManyModels": "Jeden klucz do ponad 100 modeli od wszystkich dostawców.",
"describeChange": "Opisz zmianę…",
@ -188,10 +188,10 @@
"onlineFontProviders": "Dostawcy czcionek online",
"downloadMissingWebFonts": "Pobieraj brakujące czcionki webowe przez włączonych dostawców.",
"webFontProvidersRequireDesktopApp": "Katalogi dostawców czcionek online nie są dostępne w aplikacji webowej. Pobierz aplikację desktopową, aby przeglądać i ładować czcionki dostawców.",
"clipboardImageUnavailableWeb": "Pasted design includes 1 image that cannot be loaded in the web app. Use the desktop app to include it.",
"clipboardImagesUnavailableWeb": "Pasted design includes {count} images that cannot be loaded in the web app. Use the desktop app to include them.",
"clipboardImageFetchFailed": "Failed to fetch 1 image from Figma. Check that the source file is accessible and try again.",
"clipboardImagesFetchFailed": "Failed to fetch {count} images from Figma. Check that the source file is accessible and try again.",
"clipboardImageUnavailableWeb": "Wklejony projekt zawiera 1 obraz, którego nie można wczytać w aplikacji internetowej. Użyj aplikacji komputerowej, aby go dołączyć.",
"clipboardImagesUnavailableWeb": "Wklejony projekt zawiera {count} obrazy, których nie można wczytać w aplikacji internetowej. Użyj aplikacji komputerowej, aby je dołączyć.",
"clipboardImageFetchFailed": "Nie udało się pobrać 1 obrazu z Figmy. Sprawdź, czy plik źródłowy jest dostępny, i spróbuj ponownie.",
"clipboardImagesFetchFailed": "Nie udało się pobrać {count} obrazów z Figmy. Sprawdź, czy plik źródłowy jest dostępny, i spróbuj ponownie.",
"enable": "Włącz",
"disable": "Wyłącz",
"fallbackPacks": "Pakiety zapasowe",
@ -213,5 +213,30 @@
"you": "Ty",
"youSuffix": "ty",
"followingPeerStop": "Obserwujesz {name} (kliknij, aby zatrzymać)",
"clickToFollowPeer": "Kliknij, aby obserwować {name}"
"clickToFollowPeer": "Kliknij, aby obserwować {name}",
"importLabel": "Importuj",
"importHTMLCSS": "Importuj HTML/CSS",
"importHTMLCSSDescription": "Wklej HTML oraz opcjonalnie CSS lub skompilowany CSS Tailwind.",
"paste": "Wklej",
"importFailed": "Importowanie nie powiodło się. Sprawdź HTML i CSS, a następnie spróbuj ponownie.",
"importReplacesDocument": "Importowanie zastąpi bieżący dokument.",
"importing": "Importowanie…",
"importToCanvas": "Importuj na obszar roboczy",
"inThisRoom": "W tym pokoju",
"disconnect": "Rozłącz",
"toolFinished": "Gotowe",
"unknownTool": "Nieznane narzędzie",
"permissionRequest": "{tool} prosi o uprawnienie.",
"mobilePanelNavigation": "Nawigacja panelu mobilnego",
"toolOptions": "Opcje narzędzia {tool}",
"removeGradientStop": "Usuń punkt gradientu",
"notifications": "Powiadomienia",
"saveAsPrompt": "Zapisz jako:",
"browserFileAPINotSupported": "Twoja przeglądarka nie obsługuje interfejsu API plików lokalnych. Pliki będą pobierane zamiast zapisywane w bieżącym miejscu.",
"useChrome": "Użyj Chrome",
"orEdgeForFullSupport": "lub Edge, aby uzyskać pełną obsługę.",
"dismiss": "Zamknij",
"toolRunning": "Uruchamianie…",
"toolError": "Błąd",
"permissionRequestTitle": "Prośba o uprawnienie"
}

View file

@ -263,5 +263,18 @@
"blendModeHue": "Barwa",
"blendModeSaturation": "Nasycenie",
"blendModeColor": "Kolor",
"blendModeLuminosity": "Jasność"
"blendModeLuminosity": "Jasność",
"noFontsFound": "Nie znaleziono czcionek",
"noLocalFontsAvailable": "Brak dostępnych czcionek lokalnych.",
"localFontsAccessHint": "Użyj aplikacji komputerowej lub Chrome/Edge, aby uzyskać dostęp do czcionek systemowych.",
"editColor": "Edytuj kolor",
"red": "Czerwony",
"green": "Zielony",
"blue": "Niebieski",
"hue": "Odcień",
"saturation": "Nasycenie",
"brightness": "Jasność",
"lightness": "Jasność",
"chroma": "Chroma",
"alpha": "Alfa"
}

View file

@ -22,19 +22,19 @@
"selectLayerForJSX": "Выберите слой, чтобы увидеть его JSX-код",
"connectAIProvider": "Подключите AI-провайдера, чтобы начать чат.",
"connect": "Подключить",
"testConnection": "Test connection",
"testingConnection": "Testing…",
"connectionTestSuccess": "Connected successfully. Model is reachable.",
"connectionTestMissingAPIKey": "Enter an API key before testing.",
"connectionTestMissingBaseURL": "Enter a base URL before testing.",
"connectionTestMissingModel": "Enter a model ID before testing.",
"connectionTestInvalidBaseURL": "Base URL is invalid. Use a full URL like https://api.example.com/v1.",
"connectionTestAuthFailed": "Authentication failed. Check your API key.",
"connectionTestModelNotFound": "Model not found. Check the model ID.",
"connectionTestAPITypeMismatch": "This endpoint does not appear to support the selected API type. Try Completions or Responses.",
"connectionTestBrowserNetworkFailed": "Could not reach this endpoint from the browser. Try the desktop app or use an endpoint with CORS enabled.",
"connectionTestNetworkFailed": "Could not reach the endpoint. Check the URL and your network connection.",
"connectionTestUnknownFailed": "Connection test failed. Check the provider settings and try again.",
"testConnection": "Проверить подключение",
"testingConnection": "Проверка…",
"connectionTestSuccess": "Подключение установлено. Модель доступна.",
"connectionTestMissingAPIKey": "Введите API-ключ перед проверкой.",
"connectionTestMissingBaseURL": "Введите базовый URL перед проверкой.",
"connectionTestMissingModel": "Введите ID модели перед проверкой.",
"connectionTestInvalidBaseURL": "Недопустимый базовый URL. Используйте полный URL, например https://api.example.com/v1.",
"connectionTestAuthFailed": "Не удалось пройти аутентификацию. Проверьте API-ключ.",
"connectionTestModelNotFound": "Модель не найдена. Проверьте ID модели.",
"connectionTestAPITypeMismatch": "Эта конечная точка, вероятно, не поддерживает выбранный тип API. Попробуйте Completions или Responses.",
"connectionTestBrowserNetworkFailed": "Не удалось подключиться к этой конечной точке из браузера. Попробуйте настольное приложение или конечную точку с поддержкой CORS.",
"connectionTestNetworkFailed": "Не удалось подключиться к конечной точке. Проверьте URL и подключение к сети.",
"connectionTestUnknownFailed": "Не удалось проверить подключение. Проверьте настройки провайдера и повторите попытку.",
"getAPIKey": "Получить API-ключ {provider} →",
"oneKeyManyModels": "Один ключ для 100+ моделей от разных провайдеров.",
"describeChange": "Опишите изменение…",
@ -188,10 +188,10 @@
"onlineFontProviders": "Онлайн-провайдеры шрифтов",
"downloadMissingWebFonts": "Загружайте отсутствующие веб-шрифты через включённых провайдеров.",
"webFontProvidersRequireDesktopApp": "Каталоги провайдеров онлайн-шрифтов недоступны в веб-приложении. Скачайте настольное приложение, чтобы просматривать и загружать шрифты провайдеров.",
"clipboardImageUnavailableWeb": "Pasted design includes 1 image that cannot be loaded in the web app. Use the desktop app to include it.",
"clipboardImagesUnavailableWeb": "Pasted design includes {count} images that cannot be loaded in the web app. Use the desktop app to include them.",
"clipboardImageFetchFailed": "Failed to fetch 1 image from Figma. Check that the source file is accessible and try again.",
"clipboardImagesFetchFailed": "Failed to fetch {count} images from Figma. Check that the source file is accessible and try again.",
"clipboardImageUnavailableWeb": "Вставленный дизайн содержит 1 изображение, которое нельзя загрузить в веб-приложении. Используйте настольное приложение, чтобы включить его.",
"clipboardImagesUnavailableWeb": "Вставленный дизайн содержит {count} изображений, которые нельзя загрузить в веб-приложении. Используйте настольное приложение, чтобы включить их.",
"clipboardImageFetchFailed": "Не удалось получить 1 изображение из Figma. Проверьте доступность исходного файла и повторите попытку.",
"clipboardImagesFetchFailed": "Не удалось получить {count} изображений из Figma. Проверьте доступность исходного файла и повторите попытку.",
"enable": "Включить",
"disable": "Отключить",
"fallbackPacks": "Резервные наборы",
@ -213,5 +213,30 @@
"you": "Вы",
"youSuffix": "вы",
"followingPeerStop": "Следите за {name} (нажмите, чтобы остановить)",
"clickToFollowPeer": "Нажмите, чтобы следить за {name}"
"clickToFollowPeer": "Нажмите, чтобы следить за {name}",
"importLabel": "Импорт",
"importHTMLCSS": "Импорт HTML/CSS",
"importHTMLCSSDescription": "Вставьте HTML и, при необходимости, CSS или скомпилированный Tailwind CSS.",
"paste": "Вставить",
"importFailed": "Не удалось импортировать. Проверьте HTML и CSS и повторите попытку.",
"importReplacesDocument": "Импорт заменит текущий документ.",
"importing": "Импорт…",
"importToCanvas": "Импортировать на холст",
"inThisRoom": "В этой комнате",
"disconnect": "Отключиться",
"toolFinished": "Готово",
"unknownTool": "Неизвестный инструмент",
"permissionRequest": "Инструмент «{tool}» запрашивает разрешение.",
"mobilePanelNavigation": "Навигация мобильной панели",
"toolOptions": "Параметры инструмента «{tool}»",
"removeGradientStop": "Удалить точку градиента",
"notifications": "Уведомления",
"saveAsPrompt": "Сохранить как:",
"browserFileAPINotSupported": "Ваш браузер не поддерживает API локальных файлов. Вместо сохранения на месте файлы будут скачиваться.",
"useChrome": "Использовать Chrome",
"orEdgeForFullSupport": "или Edge для полной поддержки.",
"dismiss": "Закрыть",
"toolRunning": "Выполняется…",
"toolError": "Ошибка",
"permissionRequestTitle": "Запрос разрешения"
}

View file

@ -263,5 +263,18 @@
"blendModeHue": "Тон",
"blendModeSaturation": "Насыщенность",
"blendModeColor": "Цвет",
"blendModeLuminosity": "Яркость"
"blendModeLuminosity": "Яркость",
"noFontsFound": "Шрифты не найдены",
"noLocalFontsAvailable": "Локальные шрифты недоступны.",
"localFontsAccessHint": "Используйте настольное приложение или Chrome/Edge для доступа к системным шрифтам.",
"editColor": "Изменить цвет",
"red": "Красный",
"green": "Зелёный",
"blue": "Синий",
"hue": "Тон",
"saturation": "Насыщенность",
"brightness": "Яркость",
"lightness": "Светлота",
"chroma": "Цветность",
"alpha": "Альфа"
}

View file

@ -22,19 +22,19 @@
"selectLayerForJSX": "选择一个图层以查看其 JSX 代码",
"connectAIProvider": "连接 AI 提供商以开始聊天。",
"connect": "连接",
"testConnection": "Test connection",
"testingConnection": "Testing…",
"connectionTestSuccess": "Connected successfully. Model is reachable.",
"connectionTestMissingAPIKey": "Enter an API key before testing.",
"connectionTestMissingBaseURL": "Enter a base URL before testing.",
"connectionTestMissingModel": "Enter a model ID before testing.",
"connectionTestInvalidBaseURL": "Base URL is invalid. Use a full URL like https://api.example.com/v1.",
"connectionTestAuthFailed": "Authentication failed. Check your API key.",
"connectionTestModelNotFound": "Model not found. Check the model ID.",
"connectionTestAPITypeMismatch": "This endpoint does not appear to support the selected API type. Try Completions or Responses.",
"connectionTestBrowserNetworkFailed": "Could not reach this endpoint from the browser. Try the desktop app or use an endpoint with CORS enabled.",
"connectionTestNetworkFailed": "Could not reach the endpoint. Check the URL and your network connection.",
"connectionTestUnknownFailed": "Connection test failed. Check the provider settings and try again.",
"testConnection": "测试连接",
"testingConnection": "正在测试…",
"connectionTestSuccess": "连接成功。模型可访问。",
"connectionTestMissingAPIKey": "请在测试前输入 API 密钥。",
"connectionTestMissingBaseURL": "请在测试前输入基础 URL。",
"connectionTestMissingModel": "请在测试前输入模型 ID。",
"connectionTestInvalidBaseURL": "基础 URL 无效。请使用完整 URL例如 https://api.example.com/v1。",
"connectionTestAuthFailed": "身份验证失败。请检查 API 密钥。",
"connectionTestModelNotFound": "找不到模型。请检查模型 ID。",
"connectionTestAPITypeMismatch": "此端点似乎不支持所选的 API 类型。请尝试 Completions 或 Responses。",
"connectionTestBrowserNetworkFailed": "无法从浏览器访问此端点。请尝试桌面应用,或使用已启用 CORS 的端点。",
"connectionTestNetworkFailed": "无法访问端点。请检查 URL 和网络连接。",
"connectionTestUnknownFailed": "连接测试失败。请检查提供商设置,然后重试。",
"getAPIKey": "获取 {provider} API 密钥 →",
"oneKeyManyModels": "一个密钥即可使用来自所有提供商的 100+ 模型。",
"describeChange": "描述一个更改…",
@ -188,10 +188,10 @@
"onlineFontProviders": "在线字体提供商",
"downloadMissingWebFonts": "通过已启用的提供商下载缺失的网页字体。",
"webFontProvidersRequireDesktopApp": "网页版暂不支持在线字体提供商目录。请下载桌面应用来浏览和加载提供商字体。",
"clipboardImageUnavailableWeb": "Pasted design includes 1 image that cannot be loaded in the web app. Use the desktop app to include it.",
"clipboardImagesUnavailableWeb": "Pasted design includes {count} images that cannot be loaded in the web app. Use the desktop app to include them.",
"clipboardImageFetchFailed": "Failed to fetch 1 image from Figma. Check that the source file is accessible and try again.",
"clipboardImagesFetchFailed": "Failed to fetch {count} images from Figma. Check that the source file is accessible and try again.",
"clipboardImageUnavailableWeb": "粘贴的设计包含 1 张无法在网页应用中加载的图片。请使用桌面应用以包含该图片。",
"clipboardImagesUnavailableWeb": "粘贴的设计包含 {count} 张无法在网页应用中加载的图片。请使用桌面应用以包含这些图片。",
"clipboardImageFetchFailed": "无法从 Figma 获取 1 张图片。请检查源文件是否可访问,然后重试。",
"clipboardImagesFetchFailed": "无法从 Figma 获取 {count} 张图片。请检查源文件是否可访问,然后重试。",
"enable": "启用",
"disable": "停用",
"fallbackPacks": "后备字体包",
@ -213,5 +213,30 @@
"you": "你",
"youSuffix": "你",
"followingPeerStop": "正在跟随 {name}(点击停止)",
"clickToFollowPeer": "点击跟随 {name}"
"clickToFollowPeer": "点击跟随 {name}",
"importLabel": "导入",
"importHTMLCSS": "导入 HTML/CSS",
"importHTMLCSSDescription": "粘贴 HTML并可选择添加 CSS 或编译后的 Tailwind CSS。",
"paste": "粘贴",
"importFailed": "导入失败。请检查 HTML 和 CSS然后重试。",
"importReplacesDocument": "导入将替换当前文档。",
"importing": "正在导入…",
"importToCanvas": "导入到画布",
"inThisRoom": "此房间中",
"disconnect": "断开连接",
"toolFinished": "完成",
"unknownTool": "未知工具",
"permissionRequest": "{tool} 正在请求权限。",
"mobilePanelNavigation": "移动面板导航",
"toolOptions": "{tool}选项",
"removeGradientStop": "删除渐变色标",
"notifications": "通知",
"saveAsPrompt": "另存为:",
"browserFileAPINotSupported": "你的浏览器不支持本地文件 API。文件将被下载而不是保存在原位置。",
"useChrome": "使用 Chrome",
"orEdgeForFullSupport": "或使用 Edge 以获得完整支持。",
"dismiss": "关闭",
"toolRunning": "正在运行…",
"toolError": "错误",
"permissionRequestTitle": "权限请求"
}

View file

@ -263,5 +263,18 @@
"blendModeHue": "色相",
"blendModeSaturation": "饱和度",
"blendModeColor": "颜色",
"blendModeLuminosity": "明度"
"blendModeLuminosity": "明度",
"noFontsFound": "未找到字体",
"noLocalFontsAvailable": "没有可用的本地字体。",
"localFontsAccessHint": "请使用桌面应用或 Chrome/Edge 访问系统字体。",
"editColor": "编辑颜色",
"red": "红色",
"green": "绿色",
"blue": "蓝色",
"hue": "色相",
"saturation": "饱和度",
"brightness": "亮度",
"lightness": "明度",
"chroma": "色度",
"alpha": "透明度"
}

View file

@ -35,6 +35,14 @@ export const dialogMessageDefaults = {
setDefaultMode: 'Set as default',
selectLayerForJSX: 'Select a layer to see its JSX code',
copyJSXReference: 'Copy JSX prop reference to clipboard',
importLabel: 'Import',
importHTMLCSS: 'Import HTML/CSS',
importHTMLCSSDescription: 'Paste HTML plus optional CSS or compiled Tailwind CSS.',
paste: 'Paste',
importFailed: 'Import failed. Check the HTML and CSS, then try again.',
importReplacesDocument: 'Import replaces the current document.',
importing: 'Importing…',
importToCanvas: 'Import to canvas',
newTab: 'New tab',
closeTab: params('Close {name}'),
showUI: params('Show UI ({shortcut})'),
@ -93,6 +101,8 @@ export const dialogMessageDefaults = {
downloadedFontCacheClearFailed: 'Could not clear downloaded font cache.',
you: 'You',
youSuffix: 'you',
inThisRoom: 'In this room',
disconnect: 'Disconnect',
followingPeerStop: params('Following {name} (click to stop)'),
clickToFollowPeer: params('Click to follow {name}'),
connectAIProvider: 'Connect an AI provider to start chatting.',
@ -120,6 +130,12 @@ export const dialogMessageDefaults = {
describeCreateOrChange: 'Describe what you want to create or change.',
stopGenerating: 'Stop generating',
sendMessage: 'Send message',
toolFinished: 'Done',
toolRunning: 'Running…',
toolError: 'Error',
unknownTool: 'Unknown tool',
permissionRequestTitle: 'Permission request',
permissionRequest: params('{tool} is requesting permission.'),
baseURLPlaceholder: 'Base URL (e.g. http://localhost:11434/v1)',
modelIDPlaceholder: 'Model ID (e.g. llama-3.3-70b)',
aiProvider: 'AI Provider',
@ -187,6 +203,16 @@ export const dialogMessageDefaults = {
credentialBackendMemory: 'this session only',
rememberCredentials: 'Remember credentials on this browser',
done: 'Done',
mobilePanelNavigation: 'Mobile panel navigation',
toolOptions: params('{tool} options'),
removeGradientStop: 'Remove gradient stop',
notifications: 'Notifications',
saveAsPrompt: 'Save as:',
browserFileAPINotSupported:
"Your browser doesn't support the local file API. Files will be downloaded instead of saved in place.",
useChrome: 'Use Chrome',
orEdgeForFullSupport: 'or Edge for full support.',
dismiss: 'Dismiss',
apiKey: 'API Key',
apiType: 'API Type',
baseURL: 'Base URL',

View file

@ -278,7 +278,20 @@ export const panelMessageDefaults = {
gridGutter: 'Gutter',
gridMargin: 'Margin',
gridSectionSize: 'Section size',
searchFonts: 'Search fonts...'
searchFonts: 'Search fonts...',
noFontsFound: 'No fonts found',
noLocalFontsAvailable: 'No local fonts available.',
localFontsAccessHint: 'Use the desktop app or Chrome/Edge to access system fonts.',
editColor: 'Edit color',
red: 'Red',
green: 'Green',
blue: 'Blue',
hue: 'Hue',
saturation: 'Saturation',
brightness: 'Brightness',
lightness: 'Lightness',
chroma: 'Chroma',
alpha: 'Alpha'
} as const
export const panelMessages = i18n('panels', panelMessageDefaults)

View file

@ -12,10 +12,14 @@ import { useAppTheme } from '@/app/shell/theme'
import { scheduleStartupUpdateCheck } from '@/app/shell/updater'
import { kickSyncEngine } from '@/app/storage/sync'
useHead({ titleTemplate: (title) => (title ? `${title} — OpenPencil` : 'OpenPencil') })
const store = useEditorStore()
const { dialogs } = useI18n()
const { dialogs, locale } = useI18n()
useHead({
titleTemplate: (title) => (title ? `${title} — OpenPencil` : 'OpenPencil'),
htmlAttrs: { lang: locale }
})
provideEditor(store)
useAppTheme()

View file

@ -1,4 +1,5 @@
import type { EditorState } from '@open-pencil/core/editor'
import { dialogMessages } from '@open-pencil/vue'
import { downloadBlob } from '@/app/document/io/browser'
import { documentNameFromFigPath } from '@/app/document/io/names'
@ -82,7 +83,7 @@ export function createSaveActions({
return
}
const filename = prompt('Save as:', getDownloadName() ?? 'Untitled.fig')
const filename = prompt(dialogMessages.get().saveAsPrompt, getDownloadName() ?? 'Untitled.fig')
if (!filename) return
setStorageBinding(null)
setDownloadName(filename)

View file

@ -1,7 +1,19 @@
export function isEditing(e: Event) {
return e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement
function isEditableTarget(target: EventTarget | null | undefined): boolean {
return (
target instanceof HTMLInputElement ||
target instanceof HTMLTextAreaElement ||
(target instanceof HTMLElement && target.isContentEditable)
)
}
export function isInputElement(el: EventTarget | null | undefined): boolean {
return el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement
export function isEditing(event: Event) {
return event.composedPath().some(isEditableTarget)
}
export function isInputElement(element: EventTarget | null | undefined): boolean {
return (
element instanceof HTMLInputElement ||
element instanceof HTMLTextAreaElement ||
(element instanceof HTMLElement && element.isContentEditable)
)
}

View file

@ -50,7 +50,7 @@ watch([importHTML, importCSS], () => {
function errorMessage(error: unknown) {
if (error instanceof Error && error.message) return error.message
return 'Import failed. Check the HTML and CSS, then try again.'
return dialogs.value.importFailed
}
function toggleImporter() {
@ -114,7 +114,7 @@ function copyReference() {
@click="toggleImporter"
>
<icon-lucide-file-input class="size-3" />
Import
{{ dialogs.importLabel }}
</AppTextButton>
<Tip :label="dialogs.copyJSXReference">
<AppTextButton
@ -147,9 +147,9 @@ function copyReference() {
>
<div class="mb-2 flex items-center justify-between gap-2">
<div class="min-w-0">
<div class="text-xs font-medium text-surface">Import HTML/CSS</div>
<div class="text-xs font-medium text-surface">{{ dialogs.importHTMLCSS }}</div>
<div class="text-[11px] text-muted">
Paste HTML plus optional CSS or compiled Tailwind CSS.
{{ dialogs.importHTMLCSSDescription }}
</div>
</div>
<AppTextButton
@ -157,7 +157,7 @@ function copyReference() {
:ui="{ base: 'rounded px-1.5 py-0.5 text-[11px] hover:bg-hover' }"
@click="pasteImportHTML"
>
Paste
{{ dialogs.paste }}
</AppTextButton>
</div>
<textarea
@ -182,7 +182,7 @@ function copyReference() {
{{ importError }}
</div>
<div class="flex items-center justify-between gap-2">
<span class="text-[11px] text-muted">Import replaces the current document.</span>
<span class="text-[11px] text-muted">{{ dialogs.importReplacesDocument }}</span>
<AppTextButton
data-test-id="code-panel-import"
:ui="{
@ -195,7 +195,7 @@ function copyReference() {
}"
@click="importCode"
>
{{ importing ? 'Importing…' : 'Import to canvas' }}
{{ importing ? dialogs.importing : dialogs.importToCanvas }}
</AppTextButton>
</div>
</div>

View file

@ -1,5 +1,5 @@
<script setup lang="ts">
import { ColorPickerRoot } from '@open-pencil/vue'
import { ColorPickerRoot, useI18n } from '@open-pencil/vue'
import ColorPickerPanel from '@/components/color-picker-panel/ColorPickerPanel.vue'
import { usePopoverUI } from '@/components/ui/popover'
@ -8,6 +8,7 @@ import type { Color } from '@open-pencil/scene-graph/primitives'
import type { OkHCLControls } from '@open-pencil/vue'
import type { VNode } from 'vue'
const { panels } = useI18n()
const { color, okhcl = null } = defineProps<{ color: Color; okhcl?: OkHCLControls | null }>()
defineSlots<{
trigger?(props: { style: { background: string } }): VNode[]
@ -23,6 +24,7 @@ const cls = usePopoverUI({ content: 'w-56 p-2' })
<template>
<ColorPickerRoot
:color="color"
:label="panels.editColor"
:ui="{
content: cls.content,
swatch: 'size-5 shrink-0 cursor-pointer rounded border border-border p-0'

View file

@ -4,6 +4,7 @@ import { TabsContent, TabsList, TabsRoot, TabsTrigger } from 'reka-ui'
import { motion } from 'motion-v'
import type { PanInfo } from 'motion-v'
import { computed, ref } from 'vue'
import { useI18n } from '@open-pencil/vue'
import ChatPanel from './ChatPanel.vue'
import CodePanel from './CodePanel.vue'
@ -24,6 +25,7 @@ type Snap = 'closed' | 'half' | 'full'
type DrawerTab = 'layers' | 'design' | 'code' | 'ai'
const store = useEditorStore()
const { dialogs } = useI18n()
const headerRef = ref<HTMLElement | null>(null)
@ -119,7 +121,11 @@ const drawerTransition = {
@panEnd="onPanEnd"
>
<TabsRoot :model-value="getDrawerTab()" class="flex min-h-0 flex-1 flex-col">
<nav ref="headerRef" aria-label="Mobile panel navigation" class="flex shrink-0 flex-col">
<nav
ref="headerRef"
:aria-label="dialogs.mobilePanelNavigation"
class="flex shrink-0 flex-col"
>
<div class="flex w-full justify-center pt-2">
<div class="h-1 w-8 rounded-full bg-muted/40" />
</div>

View file

@ -32,7 +32,9 @@ function peerAvatarClass(following: boolean) {
align="center"
:class="styles.presenceContent()"
>
<div class="mb-2 text-[11px] tracking-wider text-muted uppercase">In this room</div>
<div class="mb-2 text-[11px] tracking-wider text-muted uppercase">
{{ hud.dialogs.inThisRoom }}
</div>
<div class="flex flex-col gap-2">
<div class="flex items-center gap-2">
<div
@ -44,7 +46,7 @@ function peerAvatarClass(following: boolean) {
<span class="min-w-0 flex-1 truncate text-xs text-surface">
{{ hud.collabState.localName || 'You' }}
</span>
<span class="text-[10px] text-muted">you</span>
<span class="text-[10px] text-muted">{{ hud.dialogs.youSuffix }}</span>
</div>
<div
@ -67,7 +69,9 @@ function peerAvatarClass(following: boolean) {
</div>
</div>
<button :class="styles.disconnect()" @click="hud.disconnect">Disconnect</button>
<button :class="styles.disconnect()" @click="hud.disconnect">
{{ hud.dialogs.disconnect }}
</button>
</PopoverContent>
</PopoverPortal>
</PopoverRoot>

View file

@ -2,7 +2,9 @@
import { useLocalStorage } from '@vueuse/core'
import { IS_BROWSER, IS_TAURI } from '@/constants'
import { useI18n } from '@open-pencil/vue'
const { dialogs } = useI18n()
const dismissed = useLocalStorage('safari-banner-dismissed', false)
const show = !IS_TAURI && IS_BROWSER && !window.showSaveFilePicker
</script>
@ -14,19 +16,18 @@ const show = !IS_TAURI && IS_BROWSER && !window.showSaveFilePicker
class="flex items-center gap-2 border-b border-[var(--color-warning-border)] bg-[var(--color-warning-bg)] px-3 py-1.5 text-xs text-[var(--color-warning-text)]"
>
<span class="flex-1">
Your browser doesn't support the local file API. Files will be downloaded instead of saved in
place.
<a href="https://www.google.com/chrome/" target="_blank" class="font-medium underline"
>Use Chrome</a
>
or Edge for full support.
{{ dialogs.browserFileAPINotSupported }}
<a href="https://www.google.com/chrome/" target="_blank" class="font-medium underline">
{{ dialogs.useChrome }}
</a>
{{ dialogs.orEdgeForFullSupport }}
</span>
<button
data-test-id="safari-banner-dismiss"
class="shrink-0 rounded px-1.5 py-0.5 font-medium text-[var(--color-warning-action)] transition-colors hover:bg-amber-500/20"
@click="dismissed = true"
>
Dismiss
{{ dialogs.dismiss }}
</button>
</div>
</template>

View file

@ -65,6 +65,7 @@ function toastClass(tone: ToastVariant) {
</ToastRoot>
<ToastViewport
:label="`${dialogs.notifications} (F8)`"
class="fixed top-2 left-1/2 z-[9999] flex -translate-x-1/2 flex-col items-center gap-1.5"
/>
</ToastProvider>

View file

@ -22,6 +22,7 @@ import {
toolbarFlyoutItemTestId,
toolbarFlyoutTestId,
toolbarToolTestId,
useI18n,
vTestId
} from '@open-pencil/vue'
@ -50,6 +51,7 @@ const {
}>()
const toolbar = tv(toolbarTheme)
const { dialogs } = useI18n()
const triggerActive = computed(() => isToolbarToolActive(tool, activeTool))
const styles = computed(() => toolbar({ active: triggerActive.value, mobile }))
@ -87,7 +89,7 @@ function flyoutItemClass() {
<button
v-test-id="toolbarFlyoutTestId(tool.key, mobile)"
:data-mobile="mobile || undefined"
:aria-label="`${toolLabels[tool.key]} options`"
:aria-label="dialogs.toolOptions({ tool: toolLabels[tool.key] })"
:class="styles.flyoutTrigger({ class: ui?.flyoutTrigger })"
>
<IconChevronDown :class="styles.flyoutTriggerIcon({ class: ui?.flyoutTriggerIcon })" />

View file

@ -6,7 +6,7 @@ import {
AlertDialogTitle
} from 'reka-ui'
import { computed } from 'vue'
import { acpPermissionOptionTestId, vTestId } from '@open-pencil/vue'
import { acpPermissionOptionTestId, useI18n, vTestId } from '@open-pencil/vue'
import {
currentPermission,
@ -16,6 +16,7 @@ import {
import { AppAlertDialogRoot } from '@/components/ui/dialog'
const open = computed(() => currentPermission.value !== null)
const { dialogs } = useI18n()
interface ToolCallInfo {
title?: string
rawInput?: unknown
@ -25,7 +26,7 @@ const toolCall = computed(
(): ToolCallInfo => (currentPermission.value?.request.toolCall as ToolCallInfo) ?? {}
)
const toolName = computed(() => toolCall.value.title ?? 'Unknown tool')
const toolName = computed(() => toolCall.value.title ?? dialogs.value.unknownTool)
const toolInput = computed(() => {
const raw = toolCall.value.rawInput
@ -59,11 +60,11 @@ function handleDismiss() {
@escape-key-down="handleDismiss"
>
<AlertDialogTitle class="text-sm font-semibold text-surface">
Permission Request
{{ dialogs.permissionRequestTitle }}
</AlertDialogTitle>
<AlertDialogDescription class="mt-2 text-xs text-muted">
<span class="font-medium text-surface">{{ toolName }}</span> is requesting permission.
{{ dialogs.permissionRequest({ tool: toolName }) }}
</AlertDialogDescription>
<pre

View file

@ -2,12 +2,13 @@
import { isTextUIPart, isToolUIPart, getToolName } from 'ai'
import { CollapsibleContent, CollapsibleRoot, CollapsibleTrigger } from 'reka-ui'
import { Markdown } from 'vue-stream-markdown'
import { vTestId } from '@open-pencil/vue'
import { useI18n, vTestId } from '@open-pencil/vue'
import 'vue-stream-markdown/index.css'
import type { UIDataTypes, UIMessage, UIMessagePart, UITools } from 'ai'
const { message } = defineProps<{ message: UIMessage }>()
const { dialogs } = useI18n()
type ToolPart = Extract<UIMessagePart<UIDataTypes, UITools>, { toolCallId: string }>
@ -74,10 +75,10 @@ function partKey(part: UIMessagePart<UIDataTypes, UITools>, index: number): stri
<span class="text-[10px] text-muted">
{{
toolState(part) === 'pending'
? 'Running…'
? dialogs.toolRunning
: toolState(part) === 'done'
? 'Done'
: 'Error'
? dialogs.toolFinished
: dialogs.toolError
}}
</span>
<icon-lucide-chevron-down

View file

@ -1,11 +1,12 @@
<script setup lang="ts">
import { inputNumberValue } from '@open-pencil/vue'
import { inputNumberValue, useI18n } from '@open-pencil/vue'
import { colorToCSS } from '@open-pencil/core/color'
import StandardColorSlider from '@/components/color-picker-panel/StandardColorSlider.vue'
import { useColorPickerPanelContext } from '@/components/color-picker-panel/context'
const ctx = useColorPickerPanelContext()
const { panels } = useI18n()
</script>
<template>
@ -39,7 +40,7 @@ const ctx = useColorPickerPanelContext()
</div>
<StandardColorSlider
label="Saturation"
:label="panels.saturation"
:model-value="ctx.rekaColor"
channel="saturation"
color-space="hsb"
@ -55,7 +56,7 @@ const ctx = useColorPickerPanelContext()
/>
<StandardColorSlider
label="Brightness"
:label="panels.brightness"
:model-value="ctx.rekaColor"
channel="brightness"
color-space="hsb"

View file

@ -1,11 +1,12 @@
<script setup lang="ts">
import { inputNumberValue } from '@open-pencil/vue'
import { inputNumberValue, useI18n } from '@open-pencil/vue'
import { colorToCSS } from '@open-pencil/core/color'
import StandardColorSlider from '@/components/color-picker-panel/StandardColorSlider.vue'
import { useColorPickerPanelContext } from '@/components/color-picker-panel/context'
const ctx = useColorPickerPanelContext()
const { panels } = useI18n()
</script>
<template>
@ -39,7 +40,7 @@ const ctx = useColorPickerPanelContext()
</div>
<StandardColorSlider
label="Saturation"
:label="panels.saturation"
:model-value="ctx.rekaColor"
channel="saturation"
color-space="hsl"
@ -55,7 +56,7 @@ const ctx = useColorPickerPanelContext()
/>
<StandardColorSlider
label="Lightness"
:label="panels.lightness"
:model-value="ctx.rekaColor"
channel="lightness"
color-space="hsl"

View file

@ -1,16 +1,18 @@
<script setup lang="ts">
import { useI18n } from '@open-pencil/vue'
import { colorToCSS } from '@open-pencil/core/color'
import StandardColorSlider from '@/components/color-picker-panel/StandardColorSlider.vue'
import { useColorPickerPanelContext } from '@/components/color-picker-panel/context'
const ctx = useColorPickerPanelContext()
const { panels } = useI18n()
</script>
<template>
<template v-if="ctx.fieldFormat !== 'okhcl'">
<StandardColorSlider
label="Hue"
:label="panels.hue"
:model-value="ctx.rekaColor"
channel="hue"
color-space="hsl"
@ -24,7 +26,7 @@ const ctx = useColorPickerPanelContext()
/>
<StandardColorSlider
label="Alpha"
:label="panels.alpha"
:model-value="ctx.rekaColor"
channel="alpha"
color-space="rgb"

View file

@ -1,18 +1,19 @@
<script setup lang="ts">
import { colorToCSS } from '@open-pencil/core/color'
import { fromPercent, toPercent } from '@open-pencil/vue'
import { fromPercent, toPercent, useI18n } from '@open-pencil/vue'
import OkhclChannelSlider from '@/components/color-picker-panel/OkhclChannelSlider.vue'
import { useColorPickerPanelContext } from '@/components/color-picker-panel/context'
const ctx = useColorPickerPanelContext()
const { panels } = useI18n()
const percentText = (value: number) => `${Math.round(toPercent(value))}%`
</script>
<template>
<div v-if="ctx.isOkHCLFormat && ctx.okhcl?.okhcl" class="flex flex-col gap-2">
<OkhclChannelSlider
label="Hue"
:label="panels.hue"
:model-value="ctx.okhcl.okhcl.h"
:min="0"
:max="360"
@ -28,7 +29,7 @@ const percentText = (value: number) => `${Math.round(toPercent(value))}%`
/>
<OkhclChannelSlider
label="Chroma"
:label="panels.chroma"
:model-value="ctx.okhcl.okhcl.c"
:min="0"
:max="0.4"
@ -46,7 +47,7 @@ const percentText = (value: number) => `${Math.round(toPercent(value))}%`
/>
<OkhclChannelSlider
label="Lightness"
:label="panels.lightness"
:model-value="ctx.okhcl.okhcl.l"
:min="0"
:max="1"
@ -64,7 +65,7 @@ const percentText = (value: number) => `${Math.round(toPercent(value))}%`
/>
<OkhclChannelSlider
label="Alpha"
:label="panels.alpha"
:model-value="ctx.okhcl.okhcl.a ?? 1"
:min="0"
:max="1"

View file

@ -1,8 +1,9 @@
<script setup lang="ts">
import { inputNumberValue } from '@open-pencil/vue'
import { inputNumberValue, useI18n } from '@open-pencil/vue'
import { useColorPickerPanelContext } from '@/components/color-picker-panel/context'
const ctx = useColorPickerPanelContext()
const { panels } = useI18n()
</script>
<template>
@ -11,7 +12,7 @@ const ctx = useColorPickerPanelContext()
>
<input
type="number"
aria-label="Red"
:aria-label="panels.red"
class="bg-input px-2 py-1 text-xs text-surface outline-none"
:value="Math.round(ctx.rgbColor.r)"
min="0"
@ -20,7 +21,7 @@ const ctx = useColorPickerPanelContext()
/>
<input
type="number"
aria-label="Green"
:aria-label="panels.green"
class="bg-input px-2 py-1 text-xs text-surface outline-none"
:value="Math.round(ctx.rgbColor.g)"
min="0"
@ -29,7 +30,7 @@ const ctx = useColorPickerPanelContext()
/>
<input
type="number"
aria-label="Blue"
:aria-label="panels.blue"
class="bg-input px-2 py-1 text-xs text-surface outline-none"
:value="Math.round(ctx.rgbColor.b)"
min="0"

View file

@ -19,7 +19,7 @@ import type { Fill } from '@open-pencil/scene-graph'
const { fill } = defineProps<{ fill: Fill }>()
const emit = defineEmits<{ update: [fill: Fill] }>()
const { panels } = useI18n()
const { panels, dialogs } = useI18n()
const fillPicker = tv(fillPickerTheme)
function barStopClass(active: boolean, dragging: boolean) {
@ -131,7 +131,7 @@ function listStopClass(active: boolean) {
<button
v-if="root.stops.length > 2"
class="flex size-4 cursor-pointer items-center justify-center rounded border-none bg-transparent p-0 text-muted hover:text-surface"
aria-label="Remove gradient stop"
:aria-label="dialogs.removeGradientStop"
@click.stop="s.actions.remove"
>
<icon-lucide-minus class="size-3" />

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import { computed } from 'vue'
import { FontPickerRoot } from '@open-pencil/vue'
import { FontPickerRoot, useI18n } from '@open-pencil/vue'
import { useSelectUI } from '@/components/ui/select'
import { usePopoverUI } from '@/components/ui/popover'
@ -15,7 +15,9 @@ import { WEB_FONT_PROVIDER_IDS } from '@open-pencil/core/text'
import type { FontPickerUI } from '@open-pencil/vue'
const { label = 'Font family' } = defineProps<{ label?: string }>()
const { panels } = useI18n()
const { label: labelProp } = defineProps<{ label?: string }>()
const label = computed(() => labelProp ?? panels.value.fontFamily)
const modelValue = defineModel<string>({ required: true })
const emit = defineEmits<{ select: [family: string] }>()
@ -59,7 +61,10 @@ function loadPreviewFont(family: string, source: string) {
:list-families="listFamilies"
:local-font-access="localFontAccess"
:ui="ui"
empty-fonts-hint="Use the desktop app or Chrome/Edge to access system fonts."
:search-placeholder="panels.searchFonts"
:empty-search-text="panels.noFontsFound"
:empty-fonts-text="panels.noLocalFontsAvailable"
:empty-fonts-hint="panels.localFontsAccessHint"
@select="emit('select', $event)"
>
<template #trigger>

View file

@ -38,13 +38,14 @@ export interface NumberFieldSlots {
<script setup lang="ts">
import { computed, normalizeClass, useAttrs } from 'vue'
import { tv } from 'tailwind-variants'
import { NumberFieldRoot, NumberFieldInput, NumberFieldValue } from '@open-pencil/vue'
import { NumberFieldRoot, NumberFieldInput, NumberFieldValue, useI18n } from '@open-pencil/vue'
import { useEditorStore } from '@/app/editor/active-store'
import theme from '@/theme/number-field'
const attrs = useAttrs()
const slots = defineSlots<NumberFieldSlots>()
const store = useEditorStore()
const { panels } = useI18n()
const {
modelValue,
@ -86,7 +87,7 @@ defineOptions({ inheritAttrs: false })
:max="max"
:step="step"
:sensitivity="sensitivity"
:placeholder="placeholder"
:placeholder="placeholder ?? panels.mixed"
:aria-label="accessibleLabel"
:disabled="disabled"
:bound="bound"

View file

@ -107,10 +107,19 @@ function testClipboard(page: Page) {
return page.evaluate(() => window.__OPENPENCIL_TEST_CLIPBOARD__)
}
async function dispatchClipboardEvent(page: Page, type: 'copy' | 'cut' | 'paste') {
await page.evaluate((eventType) => {
window.dispatchEvent(new ClipboardEvent(eventType, { bubbles: true, cancelable: true }))
}, type)
async function dispatchClipboardEvent(
page: Page,
type: 'copy' | 'cut' | 'paste',
target = 'window'
) {
await page.evaluate(
({ eventType, selector }) => {
const eventTarget = selector === 'window' ? window : document.querySelector(selector)
if (!eventTarget) throw new Error(`Clipboard event target not found: ${selector}`)
eventTarget.dispatchEvent(new ClipboardEvent(eventType, { bubbles: true, cancelable: true }))
},
{ eventType: type, selector: target }
)
}
test('Tauri copy writes selected design HTML without requiring ClipboardEvent.clipboardData', async ({
@ -172,6 +181,30 @@ test('Tauri cut writes design data and deletes selection only after clipboard wr
canvas.assertNoErrors()
})
test('Tauri clipboard events from editable fields keep native text behavior', async ({ page }) => {
const canvas = await createTauriEditorPage(page)
await canvas.drawRect(160, 160, 96, 72)
const before = await testClipboard(page)
await page.evaluate(() => {
const host = document.createElement('div')
host.innerHTML =
'<input data-clipboard-probe="input"><textarea data-clipboard-probe="textarea"></textarea><div contenteditable="true"><span>Text</span></div>'
document.body.append(host)
})
await dispatchClipboardEvent(page, 'copy', '[data-clipboard-probe="input"]')
expect(await testClipboard(page)).toEqual(before)
await dispatchClipboardEvent(page, 'copy', '[data-clipboard-probe="textarea"]')
expect(await testClipboard(page)).toEqual(before)
await dispatchClipboardEvent(page, 'cut', '[contenteditable] span')
expect(await testClipboard(page)).toEqual(before)
await dispatchClipboardEvent(page, 'paste', '[contenteditable] span')
expect(await testClipboard(page)).toEqual(before)
expect(await pageChildren(page)).toHaveLength(1)
})
test('Tauri context-menu copy uses plugin clipboard fallback instead of blocked browser command', async ({
page
}) => {