diff --git a/Cargo.lock b/Cargo.lock index 9a3896b8..5a77cda3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1527,6 +1527,7 @@ dependencies = [ "windows", "winit", "winresource", + "zbus", ] [[package]] diff --git a/apps/figcraft/Cargo.toml b/apps/figcraft/Cargo.toml index c13c8832..bd8c09c8 100644 --- a/apps/figcraft/Cargo.toml +++ b/apps/figcraft/Cargo.toml @@ -36,6 +36,12 @@ env_logger = { version = "0.11", default-features = false, features = ["auto-col craft-update = { workspace = true } windows = { version = "0.62", default-features = false, features = ["UI_ViewManagement", "System_UserProfile", "Foundation_Collections", "Gaming_Input"] } +# The system colour scheme (org.freedesktop.portal.Settings): winit reports no theme on Linux and +# the BSDs, so the interface's "system" mode follows the portal's instead. zbus comes in through +# accesskit already; this only makes it direct. +[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies] +zbus = { version = "5", default-features = false, features = ["async-io", "blocking-api"] } + # Finder double-clicks, Open With, Dock drops and `open -a FigCraft file.figcraft` arrive as a # kAEOpenDocuments Apple event, which winit 0.30 doesn't handle. This crate (also used by GridCraft, # EffectCraft and PhotoCraft) registers the handler behind a safe API (Objective-C interop needs diff --git a/apps/figcraft/src/main.rs b/apps/figcraft/src/main.rs index 6c9bdf93..de999375 100644 --- a/apps/figcraft/src/main.rs +++ b/apps/figcraft/src/main.rs @@ -170,21 +170,23 @@ fn main() -> eframe::Result { // System fonts: index them in the background, then wake the UI (an idle window draws no frames) // to lay text out again with them. let ctx_slot: Arc>> = Arc::new(Mutex::new(None)); - let fonts_slot = ctx_slot.clone(); + // Wakes the window (once it is there) from another thread. + let wake = |slot: &Arc>>| { + let slot = slot.clone(); + move || { + if let Some(c) = slot.lock().unwrap_or_else(std::sync::PoisonError::into_inner).as_ref() { + c.request_repaint(); + } + } + }; + let fonts_wake = wake(&ctx_slot); std::thread::spawn(move || { figcraft_text::scan_system_fonts(); - if let Some(c) = fonts_slot.lock().unwrap_or_else(std::sync::PoisonError::into_inner).as_ref() { - c.request_repaint(); - } + fonts_wake(); }); let mut session = Session::empty(); // Live sessions: the network threads wake the window when something arrives. - let wake_slot = ctx_slot.clone(); - session.live.install(Box::new(figcraft_collab_net::NetLink::new(Arc::new(move || { - if let Some(c) = wake_slot.lock().unwrap_or_else(std::sync::PoisonError::into_inner).as_ref() { - c.request_repaint(); - } - })))); + session.live.install(Box::new(figcraft_collab_net::NetLink::new(Arc::new(wake(&ctx_slot))))); for f in &args.files { if let Err(e) = session.execute("file.open", &json!({"path": f})) { log::error!("{f}: {e}"); @@ -196,6 +198,7 @@ fn main() -> eframe::Result { // The preferences first: they hold the online files a recovered document may be one of. let recovery = recovery::Shared::default(); let svc = services::Desktop::load(recovery.clone()); + svc.follow_system_dark(wake(&ctx_slot)); if let Some(prefs) = svc.prefs.clone() { session.prefs = prefs; } diff --git a/apps/figcraft/src/services.rs b/apps/figcraft/src/services.rs index d9cd53ea..0818c048 100644 --- a/apps/figcraft/src/services.rs +++ b/apps/figcraft/src/services.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::path::{Path, PathBuf}; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use std::sync::{Arc, Mutex}; use figcraft_engine::{Prefs, Session}; @@ -16,6 +16,8 @@ pub struct Desktop { pub prefs: Option, /// The system's reduce-motion setting (read in the background at startup). reduce_motion: Arc, + /// The system's colour scheme ([`watch_system_dark`]): 0 unknown, 1 dark, 2 light. + dark: Arc, /// The system's interface language (read at startup). language: Option, /// Recovery copies of unsaved files (written at once after a panic). @@ -56,16 +58,33 @@ impl Desktop { let reduce_motion = Arc::new(AtomicBool::new(false)); let flag = reduce_motion.clone(); std::thread::spawn(move || flag.store(system_reduce_motion(), Ordering::Relaxed)); + let dark = Arc::new(AtomicU8::new(DARK_UNKNOWN)); Self { clipboard: arboard::Clipboard::new().ok(), dir, prefs, reduce_motion, + dark, language: system_language(), recovery, last_folder: Arc::default(), } } + + /// winit reports no system theme on Linux and the BSDs: there the freedesktop colour scheme + /// answers instead, read once and then watched in the background, calling `wake` when it + /// changes (an idle window draws no frames). Elsewhere this does nothing. + pub fn follow_system_dark(&self, wake: impl Fn() + Send + 'static) { + #[cfg(all(unix, not(target_os = "macos")))] + if let Err(e) = std::thread::Builder::new().name("figcraft-theme".into()).spawn({ + let dark = self.dark.clone(); + move || watch_system_dark(&dark, &wake) + }) { + log::info!("colour scheme: can't start the watcher: {e}"); + } + #[cfg(not(all(unix, not(target_os = "macos"))))] + let _ = wake; + } } /// The system's preferred interface languages, most preferred first, comma-separated @@ -146,6 +165,71 @@ fn command_says(program: &str, args: &[&str], want: &str) -> bool { .is_some_and(|o| String::from_utf8_lossy(&o.stdout).trim() == want) } +/// [`Desktop::dark`]: the colour scheme is unknown, dark, or light. +const DARK_UNKNOWN: u8 = 0; +const DARK_DARK: u8 = 1; +const DARK_LIGHT: u8 = 2; + +/// The desktop's colour scheme through the freedesktop settings portal +/// (`org.freedesktop.appearance` / `color-scheme`: 1 dark, 2 light, 0 no preference, which is +/// light), read once and then watched for changes, each one calling `wake`. Without a session bus +/// or a portal the scheme stays unknown and the UI keeps its light fallback. +#[cfg(all(unix, not(target_os = "macos")))] +fn watch_system_dark(dark: &AtomicU8, wake: &dyn Fn()) { + use zbus::zvariant::Value; + const PORTAL: &str = "org.freedesktop.portal.Desktop"; + const SETTINGS: &str = "org.freedesktop.portal.Settings"; + const APPEARANCE: &str = "org.freedesktop.appearance"; + // `Read` (portals before 1.17, which lack `ReadOne`) wraps the value in one more variant. + let scheme = |v: &Value| match v { + Value::U32(x) => *x, + Value::Value(inner) => match **inner { + Value::U32(x) => x, + _ => 0, + }, + _ => 0, + }; + let store = |v: u32| { + let new = match v { + 1 => DARK_DARK, + 2 => DARK_LIGHT, + _ => DARK_UNKNOWN, + }; + if dark.swap(new, Ordering::Relaxed) != new { + wake(); + } + }; + let Ok(conn) = zbus::blocking::connection::Builder::session().and_then(|b| b.build()) else { + log::info!("colour scheme: no session bus: the system theme isn't followed"); + return; + }; + let read = |method: &str| -> zbus::Result { + let reply = conn.call_method(Some(PORTAL), "/org/freedesktop/portal/desktop", Some(SETTINGS), method, &(APPEARANCE, "color-scheme"))?; + let body = reply.body(); + Ok(scheme(&body.deserialize::()?)) + }; + match read("ReadOne").or_else(|_| read("Read")) { + Ok(v) => store(v), + Err(e) => log::info!("colour scheme: can't read the system setting: {e}"), + } + let watch = || -> zbus::Result<()> { + let rule = + zbus::MatchRule::builder().msg_type(zbus::message::Type::Signal).sender(PORTAL)?.interface(SETTINGS)?.member("SettingChanged")?.build(); + for msg in zbus::blocking::MessageIterator::for_match_rule(rule, &conn, Some(8))? { + let msg = msg?; + let body = msg.body(); + let Ok((namespace, key, value)) = body.deserialize::<(String, String, Value<'_>)>() else { continue }; + if namespace == APPEARANCE && key == "color-scheme" { + store(scheme(&value)); + } + } + Ok(()) + }; + if let Err(e) = watch() { + log::info!("colour scheme: can't watch the system setting: {e}"); + } +} + /// Show `path` in the file manager: selected in Finder and Explorer, its folder elsewhere. fn reveal(path: &Path) -> std::io::Result<()> { #[cfg(target_os = "macos")] @@ -372,6 +456,13 @@ impl Services for Desktop { fn reduce_motion(&self) -> bool { self.reduce_motion.load(Ordering::Relaxed) } + fn system_dark(&self) -> Option { + match self.dark.load(Ordering::Relaxed) { + DARK_DARK => Some(true), + DARK_LIGHT => Some(false), + _ => None, + } + } fn gamepad_held(&mut self) -> u32 { crate::gamepad::held() } diff --git a/contributors/people.toml b/contributors/people.toml index 7491a05c..1bc75c24 100644 --- a/contributors/people.toml +++ b/contributors/people.toml @@ -80,3 +80,6 @@ sync_github_name = false [people.dotCooCoo] real_name = "Robert Lee" sync_github_name = true + +[people.leandrodslv] +sync_github_name = true diff --git a/crates/doc/src/text.rs b/crates/doc/src/text.rs index d2abfed9..24d7e088 100644 --- a/crates/doc/src/text.rs +++ b/crates/doc/src/text.rs @@ -253,10 +253,17 @@ pub struct TextStyle { pub style_id: Option, } +/// The font new text starts in. A build can name another (`FIGCRAFT_DEFAULT_FONT="Noto Sans Thai"`, +/// for a distribution whose users mostly write a script Inter lacks); empty means Inter. +pub const DEFAULT_FONT: &str = match option_env!("FIGCRAFT_DEFAULT_FONT") { + Some(family) if !family.is_empty() => family, + _ => "Inter", +}; + impl Default for TextStyle { fn default() -> Self { Self { - font_family: "Inter".into(), + font_family: DEFAULT_FONT.into(), font_style: "Regular".into(), font_size: 12.0, line_height: LineHeight::Auto, @@ -790,6 +797,18 @@ mod case_tests { assert!(!valid_feature_tag("lig") && !valid_feature_tag("ligatures") && !valid_feature_tag("li\u{e9}")); } + #[test] + fn new_text_starts_in_the_builds_default_font() { + // Inter, unless the build names another with `FIGCRAFT_DEFAULT_FONT` (empty means Inter). + let named = option_env!("FIGCRAFT_DEFAULT_FONT").filter(|f| !f.is_empty()); + assert_eq!(DEFAULT_FONT, named.unwrap_or("Inter")); + let st = TextStyle::default(); + assert_eq!(st.font_family, DEFAULT_FONT); + // The family is always written, so a saved file keeps it in a build with another default. + let saved = serde_json::to_value(&st).unwrap(); + assert_eq!(saved["font_family"], DEFAULT_FONT); + } + #[test] fn variations_are_backward_compatible_and_imply_weight() { // Files written before variable fonts load with no custom axes, and stay byte-identical. diff --git a/crates/engine/src/cmd/create.rs b/crates/engine/src/cmd/create.rs index a6e7932a..414a2310 100644 --- a/crates/engine/src/cmd/create.rs +++ b/crates/engine/src/cmd/create.rs @@ -83,7 +83,7 @@ pub fn specs() -> Vec { spec!( "image.place", "Place image", - "{data (base64) | path, x?, y?, width?, height?, parent?, mode? (fill|fit|crop|tile)} → {id, image, width, height, imageProfile (its embedded colour profile; null: taken as sRGB)}", + "{data (base64) | path, x?, y?, width?, height?, parent?, target? (an existing layer: the image is added on top of its fills instead of making a layer), mode? (fill|fit|crop|tile)} → {id, image, width, height, imageProfile (its embedded colour profile; null: taken as sRGB)}", true, place_image ), @@ -394,9 +394,28 @@ fn read_path(path: &str) -> Result> { Err(crate::EngineError::Other(format!("{path}: files can't be read in the browser"))) } +/// Layers that can show an image inside their own outline. +pub fn accepts_image_fill(kind: &NodeKind) -> bool { + kind.is_shape() || kind.is_frame_like() || matches!(kind, NodeKind::Text(_) | NodeKind::BooleanOp { .. }) +} + +/// Add an image fill on top of the layer's fills: the others keep their place and variable +/// bindings; a linked fill style no longer describes the list, so the link goes. +fn add_image_fill(node: &mut Node, image: String, mode: ImageScaleMode) { + node.fills.push(Paint::image(image, mode)); + node.styles.fill = None; +} + /// Place an image as a rectangle with an image fill (Figma's image layer). pub fn place_image(s: &mut Session, p: &Value) -> Result { let cmd = "image.place"; + let target = opt_id(cmd, p, "target")?; + if let Some(id) = target { + let node = s.doc()?.node(id).ok_or(crate::EngineError::NoNode(id))?; + if !accepts_image_fill(&node.kind) { + return Err(bad(cmd, "`target` must be a layer that can have a fill")); + } + } let bytes = if let Some(b64) = string(p, "data") { use base64::Engine as _; let b64 = b64.split_once(',').map(|(_, d)| d.to_string()).unwrap_or(b64); @@ -412,6 +431,14 @@ pub fn place_image(s: &mut Session, p: &Value) -> Result { "tile" => ImageScaleMode::Tile, _ => ImageScaleMode::Fill, }; + if let Some(id) = target { + let doc = s.doc_mut()?; + let (hash, _, _) = figcraft_format::add_image(doc, bytes).map_err(|e| bad(cmd, e.to_string()))?; + let profile = figcraft_render::images::profile_name(&hash, doc.images.get(&hash)); + let node = doc.get_mut(id)?; + add_image_fill(node, hash.clone(), mode); + return Ok(json!({"id": id, "image": hash, "width": node.width, "height": node.height, "imageProfile": profile})); + } let page = s.page()?; let parent = crate::params::opt_id(cmd, p, "parent")?.unwrap_or(page); let ds = s.ds_mut()?; diff --git a/crates/engine/src/cmd/structure.rs b/crates/engine/src/cmd/structure.rs index 28a242af..700825f2 100644 --- a/crates/engine/src/cmd/structure.rs +++ b/crates/engine/src/cmd/structure.rs @@ -456,16 +456,19 @@ fn outline_stroke(s: &mut Session, p: &Value) -> Result { continue; } let closed = figcraft_doc::geometry::is_closed(&n); - let Some(geom) = (match &n.kind { - NodeKind::Text(_) | NodeKind::BooleanOp { .. } => figcraft_render::node_outline(doc, &n).map(|(p, _)| p), - _ => figcraft_doc::geometry::stroke_geometry(&n), + let Some((area, rule)) = (match &n.kind { + NodeKind::Text(_) => figcraft_render::node_outline(doc, &n) + .map(|(p, _)| (figcraft_render::draw::stroke_area(&n, &p, closed), figcraft_render::FillRule::Winding)), + _ => figcraft_render::stroke_fill(doc, &n), }) else { continue; }; - let area = figcraft_render::draw::stroke_area(&n, &geom, closed); let mut path = (*area).clone(); - // Union the pieces so the outline is one clean shape. - if let Ok(u) = figcraft_pathops::simplify(&path) { + // Simplify non-zero strokes into even-odd contours. Inside/outside rings already use + // even-odd: interpreting their same-direction contours as non-zero would fill the hole. + if rule == figcraft_render::FillRule::Winding + && let Ok(u) = figcraft_pathops::simplify(&path) + { path = u; } let bb = path.bounding_box(); diff --git a/crates/engine/src/cmd/styles.rs b/crates/engine/src/cmd/styles.rs index e5ac145e..9c2922d2 100644 --- a/crates/engine/src/cmd/styles.rs +++ b/crates/engine/src/cmd/styles.rs @@ -1009,8 +1009,8 @@ fn bound_field(n: &Node, field: &str) -> Option { let cl = &n.child_layout; let pad = |i: usize| layout.and_then(|l| l.padding.get(i).copied()); Some(match field { - "width" => format!("{}", n.width), - "height" => format!("{}", n.height), + "width" | "w" => format!("{}", n.width), + "height" | "h" => format!("{}", n.height), "minWidth" => format!("{:?}", cl.min_w), "maxWidth" => format!("{:?}", cl.max_w), "minHeight" => format!("{:?}", cl.min_h), diff --git a/crates/engine/src/text_behaviour_tests.rs b/crates/engine/src/text_behaviour_tests.rs index f15817e8..92a43306 100644 --- a/crates/engine/src/text_behaviour_tests.rs +++ b/crates/engine/src/text_behaviour_tests.rs @@ -288,3 +288,25 @@ fn a_drag_with_the_text_tool_makes_a_fixed_size_box() { let (b3, _) = text_tool((100.0, 100.0), (180.0, 101.0), "A"); assert_eq!((b3.width(), b3.height()), (80.0, line.height())); } + +#[test] +fn new_text_starts_in_the_builds_default_font() { + // `text.create` without a fontFamily and a click with the Text tool both start in + // `figcraft_doc::DEFAULT_FONT` (Inter, unless the build names another with + // `FIGCRAFT_DEFAULT_FONT`), and the family is written with the text. + use crate::tools::{PointerEvent, PointerKind, ToolId}; + let mut s = Session::new(); + let made = text(&mut s, "Text", json!({})); + s.set_tool(ToolId::Text); + s.pointer(&PointerEvent::at(PointerKind::Down, 300.0, 300.0, 1.0)).unwrap(); + s.pointer(&PointerEvent::at(PointerKind::Up, 300.0, 300.0, 1.0)).unwrap(); + crate::textedit::insert(&mut s, "Tool").unwrap(); + let typed = s.ds().unwrap().text_edit.as_ref().unwrap().id; + assert_ne!(made, typed); + let doc = s.doc().unwrap(); + for t in [made, typed] { + let td = doc.node(t).unwrap().kind.text().unwrap(); + assert_eq!(td.style.font_family, figcraft_doc::DEFAULT_FONT); + assert_eq!(serde_json::to_value(td).unwrap()["style"]["font_family"], figcraft_doc::DEFAULT_FONT); + } +} diff --git a/crates/engine/src/tools.rs b/crates/engine/src/tools.rs index b6a6d101..8988653b 100644 --- a/crates/engine/src/tools.rs +++ b/crates/engine/src/tools.rs @@ -4,6 +4,7 @@ use std::collections::BTreeMap; +use figcraft_color::{ImageScaleMode, PaintKind}; use figcraft_doc::geometry::select_target; use figcraft_doc::*; use figcraft_geom::{Affine, Point, Rect, Vec2}; @@ -1726,6 +1727,44 @@ impl Session { if flip_y { t *= Affine::new([1.0, 0.0, 0.0, -1.0, 0.0, nh]); } + // Text reflow and auto layout can recompute this box after the preview (a size bound + // to a variable lets go of it: the resize is an explicit edit). + let n = doc.get(*id)?; + let crop_images = ev.cmd + && !matches!(n.kind, NodeKind::Text(_)) + && !std::iter::once(*id).chain(doc.ancestors(*id)).any(|a| doc.node(a).is_some_and(|n| n.layout.is_some())); + if crop_images { + // Crop the layer's image fills without moving their pixels on the page. + // Work from the interaction snapshot, just like the geometry on every move. + let (w, h) = (n.width.max(1e-6), n.height.max(1e-6)); + let to_old = + Affine::scale_non_uniform(1.0 / w, 1.0 / h) * world.inverse() * t * Affine::scale_non_uniform(nw.max(0.01), nh.max(0.01)); + let mut fills = n.fills.clone(); + for paint in &mut fills { + let PaintKind::Image(img) = &mut paint.kind else { continue }; + // A repeating image cannot be represented by a single Crop placement. + if img.scale_mode == ImageScaleMode::Tile { + continue; + } + let Some(data) = doc.images.get(&img.image) else { continue }; + let (iw, ih) = (f64::from(data.width), f64::from(data.height)); + let crop = if img.scale_mode == ImageScaleMode::Crop { + img.effective_crop(iw, ih, w, h) + } else { + let (rw, rh) = img.rotated_size(iw.max(1.0), ih.max(1.0)); + let s = if img.scale_mode == ImageScaleMode::Fit { (w / rw).min(h / rh) } else { (w / rw).max(h / rh) }; + [w / (rw * s), 0.0, 0.0, h / (rh * s), (rw * s - w) / (2.0 * rw * s), (rh * s - h) / (2.0 * rh * s)] + }; + // A degenerate (zero-scale) layer has no inverse: its image keeps its placement. + let crop = (Affine::new(crop) * to_old).as_coeffs(); + if !crop.iter().all(|c| c.is_finite()) { + continue; + } + img.crop = Some(crop); + img.scale_mode = ImageScaleMode::Crop; + } + doc.get_mut(*id)?.fills = fills; + } doc.set_world_transform(*id, t)?; if kept.is_empty() { crate::cmd::props::resize_node(doc, *id, nw.max(0.01), nh.max(0.01))?; diff --git a/crates/engine/src/transform_behaviour_tests.rs b/crates/engine/src/transform_behaviour_tests.rs index 47270a42..3e724244 100644 --- a/crates/engine/src/transform_behaviour_tests.rs +++ b/crates/engine/src/transform_behaviour_tests.rs @@ -98,6 +98,224 @@ const ALT: Mods = Mods { shift: false, alt: true, cmd: false }; const BOTH: Mods = Mods { shift: true, alt: true, cmd: false }; const CMD: Mods = Mods { shift: false, alt: false, cmd: true }; +/// An asymmetric synthetic image: every column is different, so recentering is visible. +fn image_layer(s: &mut Session) -> NodeId { + use base64::Engine as _; + let image = image::RgbaImage::from_fn(200, 100, |x, y| image::Rgba([x as u8, y as u8, 120, 255])); + let mut bytes = std::io::Cursor::new(Vec::new()); + image.write_to(&mut bytes, image::ImageFormat::Png).unwrap(); + id(&ex( + s, + "file.import", + json!({"data": base64::engine::general_purpose::STANDARD.encode(bytes.into_inner()), "name": "crop.png", "x": 0, "y": 0}), + )) +} + +#[test] +fn command_resize_crops_image_pixels_and_round_trips() { + let mut s = session(); + let layer = image_layer(&mut s); + let original = s.doc().unwrap().clone(); + let before = figcraft_render::render_node(&original, layer, 1.0).unwrap(); + let steps = undo_steps(&s); + // Public crop documentation: Command (macOS) / Ctrl (elsewhere) quickly crops an image. + // Moving the right edge must keep the left half's pixels, not recenter or scale them. + s.pointer(&ev(PointerKind::Down, Point::new(200.0, 50.0), CMD)).unwrap(); + for x in [150.0, 100.0] { + s.pointer(&ev(PointerKind::Move, Point::new(x, 50.0), CMD)).unwrap(); + } + s.pointer(&ev(PointerKind::Up, Point::new(100.0, 50.0), CMD)).unwrap(); + assert_eq!(size(&s, layer), (100.0, 100.0)); + let after = figcraft_render::render_node(s.doc().unwrap(), layer, 1.0).unwrap(); + for y in 0..100usize { + assert_eq!(&after.data()[y * 400..(y + 1) * 400], &before.data()[y * 800..y * 800 + 400], "row {y}: the image stays put"); + } + assert_eq!(s.doc().unwrap().images, original.images, "cropping keeps the source bytes"); + assert_eq!(undo_steps(&s), steps + 1); + let cropped = s.doc().unwrap().clone(); + ex(&mut s, "edit.undo", json!({})); + assert_eq!(s.doc().unwrap(), &original); + ex(&mut s, "edit.redo", json!({})); + assert_eq!(s.doc().unwrap(), &cropped); + let path = std::env::temp_dir().join(format!("figcraft-command-crop-{}.figcraft", std::process::id())); + ex(&mut s, "file.saveAs", json!({"path": path})); + let mut reopened = session(); + ex(&mut reopened, "file.open", json!({"path": path})); + assert_eq!(reopened.doc().unwrap().node(layer), cropped.node(layer)); + assert_eq!(reopened.doc().unwrap().images, cropped.images); + assert_eq!(figcraft_render::render_node(reopened.doc().unwrap(), layer, 1.0).unwrap().data(), after.data()); + std::fs::remove_file(path).unwrap(); +} + +#[test] +fn command_crop_keeps_image_placement_in_the_layers_axes() { + use figcraft_doc::color::{ImageScaleMode, PaintKind}; + use figcraft_geom::Affine; + // Each row exercises a distinct placement: Fill, Fit, and an existing affine Crop. + // Geometry follows the existing resize rules; image pixels stay fixed in page space. + let rows = [ + ("fill", None, 0.0, Point::new(0.0, 50.0), Point::new(40.0, 50.0), CMD, (40.0, 0.0, 160.0, 150.0)), + ("fit", None, 30.0, Point::new(100.0, 0.0), Point::new(100.0, 20.0), CMD, (0.0, 20.0, 200.0, 130.0)), + ("fill", None, 0.0, Point::new(0.0, 50.0), Point::new(240.0, 50.0), CMD, (240.0, 0.0, 40.0, 150.0)), + ( + "crop", + Some([0.8, 0.1, -0.2, 0.9, 0.1, 0.05]), + 30.0, + Point::new(200.0, 150.0), + Point::new(150.0, 112.5), + Mods { alt: true, ..CMD }, + (50.0, 37.5, 100.0, 75.0), + ), + ]; + for (mode, crop, angle, from, to, mods, (x, y, w, h)) in rows { + let mut s = session(); + let layer = image_layer(&mut s); + let mut fills = s.doc().unwrap().node(layer).unwrap().fills.clone(); + let PaintKind::Image(img) = &mut fills[0].kind else { panic!("image fill") }; + img.scale_mode = match mode { + "fit" => ImageScaleMode::Fit, + "crop" => ImageScaleMode::Crop, + _ => ImageScaleMode::Fill, + }; + img.crop = crop; + set(&mut s, &[layer], json!({"height": 150, "rotation": angle, "fills": fills})); + let doc = s.doc().unwrap(); + let world = doc.world_transform(layer); + let n = doc.node(layer).unwrap(); + let old_crop = crop.unwrap_or(if mode == "fit" { [1.0, 0.0, 0.0, 1.5, 0.0, -0.25] } else { [2.0 / 3.0, 0.0, 0.0, 1.0, 1.0 / 6.0, 0.0] }); + let old_mapping = Affine::new(old_crop) * Affine::scale_non_uniform(1.0 / n.width, 1.0 / n.height) * world.inverse(); + drag(&mut s, world * from, world * to, mods); + let doc = s.doc().unwrap(); + let n = doc.node(layer).unwrap(); + assert!(near(n.width, w) && near(n.height, h), "{mode}: crop box size"); + assert!((doc.world_transform(layer) * Point::ZERO - world * Point::new(x, y)).hypot() < 1e-6, "{mode}: crop box anchor"); + let PaintKind::Image(img) = &n.fills[0].kind else { panic!("image fill") }; + assert_eq!(img.scale_mode, ImageScaleMode::Crop, "{mode}"); + let mapping = + Affine::new(img.crop.unwrap()) * Affine::scale_non_uniform(1.0 / n.width, 1.0 / n.height) * doc.world_transform(layer).inverse(); + for p in [Point::ZERO, Point::new(80.0, 60.0), Point::new(170.0, 90.0)] { + assert!((mapping * p - old_mapping * p).hypot() < 1e-6, "{mode}: same page point samples the same source pixel"); + } + } +} + +#[test] +fn ordinary_resize_and_scale_keep_image_fill_settings() { + for (tool, cmd, ctrl) in [("move", false, false), ("move", false, true), ("scale", true, false)] { + let mut s = session(); + let layer = image_layer(&mut s); + let fills = s.doc().unwrap().node(layer).unwrap().fills.clone(); + ex(&mut s, "tool.set", json!({"tool": tool})); + let to = Point::new(100.0, 50.0); + for (kind, p) in [(PointerKind::Down, Point::new(200.0, 100.0)), (PointerKind::Move, to), (PointerKind::Up, to)] { + let mut event = ev(kind, p, Mods { cmd, ..NONE }); + event.ctrl = ctrl; + s.pointer(&event).unwrap(); + } + assert_eq!(s.doc().unwrap().node(layer).unwrap().fills, fills, "{tool}, cmd={cmd}, ctrl={ctrl}"); + assert_eq!(size(&s, layer), (100.0, 50.0)); + } +} + +#[test] +fn command_crop_preserves_each_image_fill_and_leaves_tiles() { + use figcraft_doc::color::{Color, ImageScaleMode, Paint, PaintKind}; + let mut s = session(); + let layer = image_layer(&mut s); + let image = s.doc().unwrap().node(layer).unwrap().fills[0].clone(); + let mut rotated = image.clone(); + rotated.opacity = 0.4; + let PaintKind::Image(img) = &mut rotated.kind else { panic!("image fill") }; + img.rotation = 1; + let mut tile = image.clone(); + let PaintKind::Image(img) = &mut tile.kind else { panic!("image fill") }; + img.scale_mode = ImageScaleMode::Tile; + let mut expected = vec![Paint::solid(Color::from_rgba8(20, 40, 60, 255)), rotated, image, tile]; + set(&mut s, &[layer], json!({"fills": expected})); + drag(&mut s, Point::new(200.0, 50.0), Point::new(100.0, 50.0), CMD); + for (i, crop) in [(1, [0.5, 0.0, 0.0, 0.25, 0.0, 0.375]), (2, [0.5, 0.0, 0.0, 1.0, 0.0, 0.0])] { + let PaintKind::Image(img) = &mut expected[i].kind else { panic!("image fill") }; + img.scale_mode = ImageScaleMode::Crop; + img.crop = Some(crop); + } + assert_eq!(s.doc().unwrap().node(layer).unwrap().fills, expected); +} + +#[test] +fn command_resize_leaves_fills_when_text_or_auto_layout_recomputes_the_box() { + for case in ["text", "child", "frame"] { + let mut s = session(); + let image = image_layer(&mut s); + let fills = s.doc().unwrap().node(image).unwrap().fills.clone(); + let layer = match case { + "text" => id(&ex( + &mut s, + "text.create", + json!({"text": "hello world hello world hello world", "props": { + "width": 200, "height": 100, "fontSize": 24, "textAutoResize": "none", "fills": fills + }}), + )), + _ => { + let frame = id(&ex(&mut s, "frame.create", json!({"x": 0, "y": 0, "width": 200, "height": 100}))); + set(&mut s, &[frame], json!({"layout": "horizontal", "padding": 20})); + if case == "child" { + ex(&mut s, "node.reparent", json!({"ids": [image], "parent": frame})); + set(&mut s, &[image], json!({"minWidth": 150})); + image + } else { + set(&mut s, &[frame], json!({"fills": fills})); + frame + } + } + }; + ex(&mut s, "select.set", json!({"ids": [layer]})); + let doc = s.doc().unwrap(); + let world = doc.world_transform(layer); + let n = doc.node(layer).unwrap(); + let from = world * Point::new(n.width, n.height / 2.0); + let to = world * Point::new(10.0, n.height / 2.0); + drag(&mut s, from, to, CMD); + assert_eq!(s.doc().unwrap().node(layer).unwrap().fills, fills, "{case}: keep the existing resize behaviour"); + } +} + +/// A resize is an explicit edit of the size: a size bound to a variable lets go of it (#809), so +/// Cmd/Ctrl crops a bound layer's image as any other's; other bindings stay. +#[test] +fn image_crop_detaches_a_bound_size_and_crops() { + for (field, value, from, to, (w, h)) in [ + ("width", 200, Point::new(200.0, 50.0), Point::new(100.0, 50.0), (100usize, 100usize)), + ("height", 100, Point::new(100.0, 100.0), Point::new(100.0, 50.0), (200, 50)), + ("w", 200, Point::new(200.0, 50.0), Point::new(100.0, 50.0), (100, 100)), + ("h", 100, Point::new(100.0, 100.0), Point::new(100.0, 50.0), (200, 50)), + ("opacity", 100, Point::new(200.0, 50.0), Point::new(100.0, 50.0), (100, 100)), + ] { + for mods in [CMD, NONE] { + let mut s = session(); + let layer = image_layer(&mut s); + ex(&mut s, "variable.createCollection", json!({"name": "Tokens"})); + ex(&mut s, "variable.create", json!({"collection": "Tokens", "name": "bound", "type": "number", "value": value})); + ex(&mut s, "variable.bind", json!({"ids": [layer], "field": field, "variable": "bound"})); + let before = s.doc().unwrap().clone(); + let pixels = figcraft_render::render_node(&before, layer, 1.0).unwrap(); + drag(&mut s, from, to, mods); + let doc = s.doc().unwrap(); + let n = doc.node(layer).unwrap(); + assert_eq!(n.bound.is_empty(), field != "opacity", "{field}: a size binding goes, others stay"); + assert_eq!(doc.variables, before.variables, "{field}: do not edit the variable"); + assert_eq!((n.width, n.height), (w as f64, h as f64), "{field}"); + if mods.cmd { + let after = figcraft_render::render_node(doc, layer, 1.0).unwrap(); + for y in 0..h { + assert_eq!(&after.data()[y * w * 4..(y + 1) * w * 4], &pixels.data()[y * 800..y * 800 + w * 4], "{field}: the image stays put"); + } + } else { + assert_eq!(n.fills, before.node(layer).unwrap().fills, "{field}: keep the existing resize behaviour"); + } + } + } +} + #[test] fn handle_drags_resize_by_the_documented_rules() { // A 100×50 box; (handle, pointer in the box's axes, modifiers, new box x0 y0 x1 y1, derivation). diff --git a/crates/engine/tests/it/main.rs b/crates/engine/tests/it/main.rs index a97e3434..34a12847 100644 --- a/crates/engine/tests/it/main.rs +++ b/crates/engine/tests/it/main.rs @@ -6,6 +6,8 @@ mod empty_text_box_undo; mod library_style_acceptance; #[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] mod list_indent_response; +#[allow(clippy::unwrap_used)] +mod outline_stroke; #[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] mod scope_reset; #[allow(clippy::unwrap_used)] diff --git a/crates/engine/tests/it/outline_stroke.rs b/crates/engine/tests/it/outline_stroke.rs new file mode 100644 index 00000000..9efb58c9 --- /dev/null +++ b/crates/engine/tests/it/outline_stroke.rs @@ -0,0 +1,172 @@ +//! Outline stroke keeps the renderer's holes and existing property ownership. + +use figcraft_doc::{Document, NodeId, NodeKind}; +use figcraft_engine::Session; +use figcraft_render::{Options, View, render_view}; +use serde_json::{Value, json}; + +fn run(s: &mut Session, command: &str, params: Value) -> Value { + s.execute(command, ¶ms).unwrap() +} + +fn rectangle(s: &mut Session, opacity: f64) -> NodeId { + NodeId( + run( + s, + "shape.rectangle", + json!({ + "x": 24, "y": 24, "width": 64, "height": 64, + "props": {"fill": "#FF0000", "stroke": "#0000FF", "strokeWeight": 8, "opacity": opacity} + }), + )["id"] + .as_u64() + .unwrap(), + ) +} + +fn pixels(doc: &Document) -> Vec { + render_view(doc, doc.first_page().unwrap(), &View { origin: (0.0, 0.0), scale: 1.0, width: 112, height: 112 }, &Options::default()) + .unwrap() + .data() + .to_vec() +} + +fn outlined(s: &mut Session, id: NodeId) -> NodeId { + NodeId(run(s, "vector.outlineStroke", json!({"ids": [id]}))["ids"][0].as_u64().unwrap()) +} + +fn same_pixels(actual: &[u8], expected: &[u8]) { + assert_eq!(actual.len(), expected.len()); + let differences: Vec<_> = actual.iter().zip(expected).enumerate().filter(|(_, (a, b))| a != b).take(8).collect(); + assert!(differences.is_empty(), "rendered channels changed (byte, actual/expected): {differences:?}"); +} + +#[test] +fn outline_keeps_rings_through_undo_and_native_reopen() { + for vector in [false, true] { + let mut s = Session::new(); + let mut id = rectangle(&mut s, 1.0); + if vector { + id = NodeId(run(&mut s, "vector.flatten", json!({"ids": [id]}))["id"].as_u64().unwrap()); + } + let before = s.doc().unwrap().clone(); + let expected = pixels(&before); + let depth = s.undo_depth(); + let group = outlined(&mut s, id); + let after = s.doc().unwrap().clone(); + same_pixels(&pixels(&after), &expected); + assert!(after.node(id).unwrap().strokes.is_empty()); + assert_eq!(s.selection(), vec![group]); + assert_eq!(s.undo_depth(), (depth.0 + 1, 0)); + let (loaded, notes) = figcraft_format::load_bytes(&figcraft_format::save_bytes(&after, None).unwrap()).unwrap(); + assert!(notes.is_empty(), "{notes:?}"); + same_pixels(&pixels(&loaded), &expected); + assert!(s.undo().unwrap()); + assert_eq!(serde_json::to_value(s.doc().unwrap()).unwrap(), serde_json::to_value(&before).unwrap()); + assert!(s.redo().unwrap()); + assert_eq!(serde_json::to_value(s.doc().unwrap()).unwrap(), serde_json::to_value(&after).unwrap()); + } +} + +#[test] +fn stroke_only_keeps_holes_at_each_opacity() { + for opacity in [0.0, 0.25, 0.5, 1.0] { + let mut s = Session::new(); + let id = rectangle(&mut s, opacity); + run(&mut s, "object.removeFill", json!({"ids": [id]})); + let before = pixels(s.doc().unwrap()); + let vector = outlined(&mut s, id); + let doc = s.doc().unwrap(); + assert!(matches!(doc.node(vector).unwrap().kind, NodeKind::Vector { .. })); + assert_eq!(doc.node(vector).unwrap().opacity, opacity as f32); + same_pixels(&pixels(doc), &before); + } +} + +#[test] +fn outline_keeps_stroke_holes_and_alignment() { + for props in [ + json!({"strokeAlign": "inside"}), + json!({"strokeAlign": "outside"}), + json!({"strokeAlign": "center"}), + json!({"strokeSides": [2, 4, 8, 16]}), + json!({"strokeAlign": "center", "dashes": [8, 4]}), + ] { + for filled in [false, true] { + let mut s = Session::new(); + let id = rectangle(&mut s, 1.0); + run(&mut s, "node.set", json!({"ids": [id], "props": props})); + if !filled { + run(&mut s, "object.removeFill", json!({"ids": [id]})); + } + let before = pixels(s.doc().unwrap()); + outlined(&mut s, id); + same_pixels(&pixels(s.doc().unwrap()), &before); + } + } +} + +#[test] +fn outlining_an_instance_keeps_its_inherited_opacity_link() { + let mut s = Session::new(); + let component = NodeId( + run( + &mut s, + "frame.create", + json!({ + "width": 64, "height": 64, + "props": {"fill": "#FF0000", "stroke": "#0000FF", "strokeWeight": 8, "opacity": 0.5} + }), + )["id"] + .as_u64() + .unwrap(), + ); + run(&mut s, "component.create", json!({"ids": [component]})); + let instance = NodeId(run(&mut s, "instance.create", json!({"component": component, "x": 200}))["id"].as_u64().unwrap()); + outlined(&mut s, instance); + assert_eq!(s.doc().unwrap().node(instance).unwrap().opacity, 0.5); + assert!(!s.doc().unwrap().node(instance).unwrap().overrides.contains("opacity")); + run(&mut s, "node.set", json!({"id": component, "props": {"opacity": 0.25}})); + let n = s.doc().unwrap().node(instance).unwrap(); + assert_eq!(n.opacity, 0.25, "the instance still follows its main"); + assert!(!n.overrides.contains("opacity")); +} + +#[test] +fn outlining_a_main_layer_keeps_its_instance_override() { + let mut s = Session::new(); + let frame = NodeId(run(&mut s, "frame.create", json!({"width": 100, "height": 100}))["id"].as_u64().unwrap()); + let layer = NodeId( + run( + &mut s, + "shape.rectangle", + json!({ + "parent": frame, "x": 24, "y": 24, "width": 64, "height": 64, + "props": {"fill": "#FF0000", "stroke": "#0000FF", "strokeWeight": 8} + }), + )["id"] + .as_u64() + .unwrap(), + ); + let component = run(&mut s, "component.create", json!({"ids": [frame]}))["id"].clone(); + run(&mut s, "instance.create", json!({"component": component, "x": 200})); + let copy = s.doc().unwrap().nodes().find(|n| n.source == Some(layer)).unwrap().id; + run(&mut s, "node.set", json!({"id": copy, "props": {"opacity": 0.25}})); + let before = s.doc().unwrap().clone(); + let view = View { origin: (0.0, 0.0), scale: 1.0, width: 312, height: 112 }; + let expected = render_view(&before, s.page().unwrap(), &view, &Options::default()).unwrap(); + let group = outlined(&mut s, layer); + let doc = s.doc().unwrap(); + let actual = render_view(doc, s.page().unwrap(), &view, &Options::default()).unwrap(); + same_pixels(actual.data(), expected.data()); + let instance_group = doc.node(copy).unwrap(); + assert!(matches!(instance_group.kind, NodeKind::Group)); + assert_eq!(instance_group.source, Some(group)); + assert_eq!(instance_group.opacity, 0.25); + assert!(instance_group.overrides.contains("opacity")); + assert!(s.undo().unwrap()); + assert_eq!(serde_json::to_value(s.doc().unwrap()).unwrap(), serde_json::to_value(&before).unwrap()); + assert!(s.redo().unwrap()); + let actual = render_view(s.doc().unwrap(), s.page().unwrap(), &view, &Options::default()).unwrap(); + same_pixels(actual.data(), expected.data()); +} diff --git a/crates/text/src/behaviour_tests.rs b/crates/text/src/behaviour_tests.rs index bc0c1d80..9160e8ae 100644 --- a/crates/text/src/behaviour_tests.rs +++ b/crates/text/src/behaviour_tests.rs @@ -265,6 +265,37 @@ fn hangul_comes_from_a_korean_font_when_one_is_installed() { } } +#[test] +fn thai_comes_from_a_thai_font_when_one_is_installed() { + // The default (Latin) face has no Thai. With a Thai face (Noto Sans Thai from craft-fonts or + // Linux, Leelawadee UI or Tahoma on Windows, Thonburi on macOS) the fallback draws every Thai + // cluster, marks included, instead of the missing-glyph box. Skips on a machine without one. + // "ภาษาไทย" (the Thai language) and "น้ำ" (water: a tone mark over SARA AM). + let s = "Ab \u{0E20}\u{0E32}\u{0E29}\u{0E32}\u{0E44}\u{0E17}\u{0E22} \u{0E19}\u{0E49}\u{0E33}"; + let chars: Vec = s.chars().collect(); + let is_thai = |c: &char| ('\u{0E00}'..='\u{0E7F}').contains(c); + let draws_all = |f: &Face| chars.iter().filter(|c| is_thai(c)).all(|c| f.has_char(*c)); + let Some(f) = face() else { return }; + if draws_all(&f) { + return; + } + let thai = ["Noto Sans Thai", "Noto Sans Thai UI", "Leelawadee UI", "Thonburi", "Tahoma"]; + if !thai.iter().any(|t| find_face(t, "Regular").is_some_and(|t| draws_all(&t))) { + return; + } + let l = layout(&text(s, TextAutoResize::WidthAndHeight), None, None); + let glyphs: Vec<_> = l.glyphs.iter().filter(|g| chars.get(g.char_start).is_some_and(is_thai)).collect(); + assert!(!glyphs.is_empty()); + for g in &glyphs { + assert!(g.gid != 0, "char {}: missing glyph in {} {}", g.char_start, g.face.family, g.face.style); + let cluster = chars.get(g.char_start..g.char_end).unwrap_or(&[]); + assert!(cluster.iter().all(|c| g.face.has_char(*c)), "chars {}..{}: drawn by {}, which lacks some", g.char_start, g.char_end, g.face.family); + } + let drawn: std::collections::BTreeSet = glyphs.iter().flat_map(|g| g.char_start..g.char_end).collect(); + let want: std::collections::BTreeSet = (3..chars.len()).filter(|i| chars[*i] != ' ').collect(); + assert_eq!(drawn, want, "every Thai char is in a drawn cluster"); +} + #[test] fn symbols_come_from_a_symbol_font_when_one_is_installed() { // ⌖ (U+2316) and ⤒ (U+2912) are in no Latin text face. With a symbol font that has them (Apple diff --git a/crates/text/src/fontdb.rs b/crates/text/src/fontdb.rs index b250b122..935f7a36 100644 --- a/crates/text/src/fontdb.rs +++ b/crates/text/src/fontdb.rs @@ -408,7 +408,7 @@ fn names(f: &skrifa::FontRef) -> (Option, Option) { /// Families tried, in order, for characters a text's own font lacks. The symbol fonts come last: /// Apple Symbols (macOS), Segoe UI Symbol (Windows) and the Noto symbol and math fonts (Linux), for /// signs such as ⌖ and ⤒ that no text face has. -const FALLBACK_FAMILIES: [&str; 24] = [ +const FALLBACK_FAMILIES: [&str; 29] = [ "Inter", "Helvetica Neue", "Arial", @@ -427,6 +427,11 @@ const FALLBACK_FAMILIES: [&str; 24] = [ "Noto Sans CJK KR", "Noto Sans KR", "Noto Sans Arabic", + "Noto Sans Thai", + "Noto Sans Thai UI", + "Leelawadee UI", + "Thonburi", + "Tahoma", "Apple Symbols", "Segoe UI Symbol", "Noto Sans Symbols", diff --git a/crates/text/src/fontdb_tests.rs b/crates/text/src/fontdb_tests.rs index f227910e..c9094218 100644 --- a/crates/text/src/fontdb_tests.rs +++ b/crates/text/src/fontdb_tests.rs @@ -276,3 +276,15 @@ fn font_bytes_load_into_the_db() { assert_eq!(db().read().unwrap().face_count(), faces); } } + +#[test] +fn every_platform_has_a_thai_font_in_the_fallbacks() { + // Thai typed into a Latin layer (new text starts in Inter) is drawn by the first fallback that + // has it: Noto Sans Thai (craft-fonts, Linux), Leelawadee UI or Tahoma (Windows), Thonburi + // (macOS). They come after the Latin faces, which go on drawing Latin text. + let at = |family: &str| FALLBACK_FAMILIES.iter().position(|f| *f == family); + let latin = at("Segoe UI").unwrap(); + for thai in ["Noto Sans Thai", "Leelawadee UI", "Tahoma", "Thonburi"] { + assert!(at(thai).is_some_and(|i| i > latin), "{thai}"); + } +} diff --git a/crates/ui-egui/locales/fr.toml b/crates/ui-egui/locales/fr.toml index b8d64b43..c17375e3 100644 --- a/crates/ui-egui/locales/fr.toml +++ b/crates/ui-egui/locales/fr.toml @@ -154,7 +154,7 @@ status = "draft — needs native review" "Back to assets" = "Retour aux ressources" "Back to current" = "Revenir à l’actuelle" "Back to styles" = "Retour aux styles" -"Back to the canvas from a focused region" = "Revenir au canevas depuis une région active" +"Back to the canvas from a focused region" = "Revenir au canevas depuis une région qui a le focus" "Back to the current version" = "Revenir à la version actuelle" "Back to the values the font style implies" = "Revenir aux valeurs du style de police" "Background" = "Arrière-plan" @@ -191,7 +191,7 @@ status = "draft — needs native review" "Branch {name}" = "Branche {name}" "Branches" = "Branches" "Bring forward" = "Avancer" -"Bring the main file's changes into the branch" = "Apporte dans la branche les modifications du fichier principal" +"Bring the main file's changes into the branch" = "Apporter dans la branche les modifications du fichier principal" "Bring to front" = "Mettre au premier plan" "Bristle" = "Soies" "Browse" = "Parcourir" @@ -260,8 +260,8 @@ status = "draft — needs native review" "Collection" = "Collection" "Collections" = "Collections" "Color" = "Couleur" -"Color burn" = "Brûlage de couleur" -"Color dodge" = "Éclaircissement de couleur" +"Color burn" = "Densité couleur +" +"Color dodge" = "Densité couleur −" "Color format" = "Format de couleur" "Color profile" = "Profil colorimétrique" "Color styles" = "Styles de couleur" @@ -326,9 +326,9 @@ status = "draft — needs native review" "Corner radius" = "Rayon d’angle" "Corner smoothing" = "Lissage des angles" "Couldn't download the variables" = "Impossible de télécharger les variables" -"Couldn't reach {server}: showing what this computer remembers." = "Impossible de joindre {server} : affichage de ce dont cet ordinateur se souvient." +"Couldn't reach {server}: showing what this computer remembers." = "Impossible de joindre {server} : affichage de ce dont cet ordinateur se souvient." "Couldn't read {name}." = "Impossible de lire {name}." -"Couldn't save automatically: {error}" = "Impossible d’enregistrer automatiquement : {error}" +"Couldn't save automatically: {error}" = "Impossible d’enregistrer automatiquement : {error}" "Count" = "Nombre" "Create" = "Créer" "Create a variable collection first" = "Créez d’abord une collection de variables" @@ -460,14 +460,14 @@ status = "draft — needs native review" "Empty" = "Vide" "End" = "Fin" "End point" = "Point final" -"Ends the session; everyone keeps their copy" = "Met fin à la session ; chacun conserve sa copie" +"Ends the session; everyone keeps their copy" = "Met fin à la session ; chacun conserve sa copie" "Enter" = "Entrée" "Enter the address of a figcraft-server and press Browse to see the plugins it offers." = "Saisissez l’adresse d’un figcraft-server et appuyez sur Parcourir pour voir les plugins qu’il propose." "Enter to save · Esc to cancel" = "Entrée pour enregistrer · Échap pour annuler" "Eraser" = "Gomme" "Esc" = "Échap" "Essential" = "Essentiels" -"Every conflict has a choice." = "Chaque conflit a un choix." +"Every conflict has a choice." = "Un choix est fait pour chaque conflit." "Every top-level frame already starts a flow" = "Chaque cadre de premier niveau démarre déjà un flux" "Exact opacity (4 then 5: 45%)" = "Opacité exacte (4 puis 5 : 45 %)" "Exclude" = "Exclure" @@ -485,9 +485,9 @@ status = "draft — needs native review" "Export variables" = "Exporter les variables" "Export {name}" = "Exporter {name}" "Export {n} layers" = { one = "Exporter {n} calque", other = "Exporter {n} calques" } -"Exported {file}" = "Exporté : {file}" +"Exported {file}" = "{file} exporté" "Exported {name}" = "{name} exporté" -"Exported {name}; {n} kinds of content were left out" = { one = "{name} exporté ; {n} type de contenu a été omis", other = "{name} exporté ; {n} types de contenu ont été omis" } +"Exported {name}; {n} kinds of content were left out" = { one = "{name} exporté ; {n} type de contenu a été omis", other = "{name} exporté ; {n} types de contenu ont été omis" } "Exported {n} files" = { one = "{n} fichier exporté", other = "{n} fichiers exportés" } "Export…" = "Exporter…" "Expose properties to instances" = "Exposer les propriétés aux instances" @@ -507,7 +507,7 @@ status = "draft — needs native review" "File name" = "Nom du fichier" "File not found" = "Fichier introuvable" "File published" = "Fichier publié" -"Files aren't end-to-end encrypted: the server's operator can read them." = "Les fichiers ne sont pas chiffrés de bout en bout : l’opérateur du serveur peut les lire." +"Files aren't end-to-end encrypted: the server's operator can read them." = "Les fichiers ne sont pas chiffrés de bout en bout : l’opérateur du serveur peut les lire." "Files nobody opens for {n} days are deleted." = { one = "Les fichiers que personne n’ouvre pendant {n} jour sont supprimés.", other = "Les fichiers que personne n’ouvre pendant {n} jours sont supprimés." } "Fill" = "Remplissage" "Fill (fr)" = "Remplir (fr)" @@ -585,7 +585,7 @@ status = "draft — needs native review" "Groups" = "Groupes" "Gutter" = "Gouttière" "Hand tool" = "Outil Main" -"Hanging list" = "Liste en retrait" +"Hanging list" = "Liste à retrait suspendu" "Hanging punctuation" = "Ponctuation en débord" "Hard light" = "Lumière dure" "Heart" = "Cœur" @@ -593,7 +593,7 @@ status = "draft — needs native review" "Help" = "Aide" "Help and resources" = "Aide et ressources" "Help page" = "Page d’aide" -"Here: {names}" = "Présents : {names}" +"Here: {names}" = "Présents : {names}" "Hidden" = "Masqué" "Hide contents" = "Masquer le contenu" "Hide from publishing" = "Exclure de la publication" @@ -686,7 +686,7 @@ status = "draft — needs native review" "Joined" = "Connecté" "Joined {file}" = "Connecté à {file}" "Joining…" = "Connexion…" -"Join|stroke" = "Jointure" +"Join|stroke" = "Jonction" "Jul" = "juil." "Jun" = "juin" "Just now" = "À l’instant" @@ -712,7 +712,7 @@ status = "draft — needs native review" "Layer name" = "Nom du calque" "Layers" = "Calques" "Layers in an instance take their position from the main component" = "Les calques d’une instance prennent leur position du composant principal" -"Layout" = "Disposition" +"Layout" = "Mise en page" "Layout grid" = "Grille de mise en page" "Layout grid settings" = "Réglages de la grille de mise en page" "Layout grids" = "Grilles de mise en page" @@ -782,9 +782,9 @@ status = "draft — needs native review" "May" = "mai" "Measure to the selection" = "Mesurer jusqu’à la sélection" "Measurements" = "Mesures" -"Merge into main" = "Fusionner dans le principal" +"Merge into main" = "Fusionner dans le fichier principal" "Merge regions" = "Fusionner les régions" -"Merge the branch into the main file (one undo step) and archive it" = "Fusionne la branche dans le fichier principal (une seule étape d’annulation) et l’archive" +"Merge the branch into the main file (one undo step) and archive it" = "Fusionner la branche dans le fichier principal (une seule étape d’annulation) et l’archiver" "Merged" = "Fusionnée" "Merged PRs" = "PR fusionnées" "Min H" = "H min." @@ -805,7 +805,7 @@ status = "draft — needs native review" "Mobile app" = "Application mobile" "Mode" = "Mode" "Model" = "Modèle" -"Models" = "Modèles" +"Models" = "Modèles d’IA" "Mono" = "Mono" "More" = "Plus" "More instance actions" = "Autres actions d’instance" @@ -818,8 +818,8 @@ status = "draft — needs native review" "Move column left" = "Déplacer la colonne à gauche" "Move column right" = "Déplacer la colonne à droite" "Move down" = "Descendre" -"Move in" = "Entrée" -"Move out" = "Sortie" +"Move in" = "Déplacer vers l’intérieur" +"Move out" = "Déplacer vers l’extérieur" "Move tools" = "Outils de déplacement" "Move up" = "Monter" "Moved to {name}" = "Déplacé vers {name}" @@ -835,7 +835,7 @@ status = "draft — needs native review" "New design file" = "Nouveau fichier de design" "Newest first" = "Plus récent d’abord" "Next frame" = "Cadre suivant" -"Next or previous control in the focused region" = "Contrôle suivant ou précédent dans la région active" +"Next or previous control in the focused region" = "Contrôle suivant ou précédent dans la région qui a le focus" "Next ready for dev" = "Prêt pour le dev suivant" "Next size option" = "Option de taille suivante" "No assets: no export settings, icons or images. Add export settings in the Design tab." = "Aucune ressource : ni réglage d’exportation, ni icône, ni image. Ajoutez des réglages d’exportation dans l’onglet Design." @@ -854,7 +854,7 @@ status = "draft — needs native review" "No contributor data was built into this copy." = "Cette copie ne contient aucune donnée de contributeurs." "No device" = "Aucun appareil" "No fonts match" = "Aucune police ne correspond" -"No join code here (the server is older than join codes, or this address can't be put in one): share the link instead." = "Pas de code d’accès ici (le serveur est antérieur aux codes d’accès, ou cette adresse ne tient pas dans un code) : partagez plutôt le lien." +"No join code here (the server is older than join codes, or this address can't be put in one): share the link instead." = "Pas de code d’accès ici (le serveur est antérieur aux codes d’accès, ou cette adresse ne tient pas dans un code) : partagez plutôt le lien." "No layer changes to show." = "Aucune modification de calque à afficher." "No libraries yet. Add a .figcraft file that was published as a library, or paste a team library's link." = "Aucune bibliothèque pour l’instant. Ajoutez un fichier .figcraft publié en tant que bibliothèque, ou collez le lien d’une bibliothèque d’équipe." "No list" = "Aucune liste" @@ -868,7 +868,7 @@ status = "draft — needs native review" "No number variables fit here." = "Aucune variable numérique ne convient ici." "No number variables yet. Create them in Local variables." = "Aucune variable numérique pour l’instant. Créez-en dans Variables locales." "No other pages" = "Aucune autre page" -"No paints" = "Aucune peinture" +"No paints" = "Aucun remplissage" "No plugins on {server}." = "Aucun plugin sur {server}." "No plugins yet. Import a development plugin from its figcraft-plugin.json." = "Aucun plugin pour l’instant. Importez un plugin en développement depuis son figcraft-plugin.json." "No preferred components" = "Aucun composant préféré" @@ -912,7 +912,7 @@ status = "draft — needs native review" "Numbered list" = "Liste numérotée" "Object" = "Objet" "Oct" = "oct." -"Off" = "Non" +"Off" = "Désactivé" "Offered first when filling the slot (any content is allowed)" = "Proposés en premier pour remplir l’emplacement (tout contenu est autorisé)" "Offered first when swapping the instance (any component is allowed)" = "Proposés en premier pour permuter l’instance (tout composant est autorisé)" "Offset" = "Décalage" @@ -923,7 +923,7 @@ status = "draft — needs native review" "One announcement in five common sizes: square, portrait, story, link and banner" = "Une annonce en cinq formats courants : carré, portrait, story, lien et bannière" "Online" = "En ligne" "Online files" = "Fichiers en ligne" -"Online sharing is off: files are shared on the local network only." = "Le partage en ligne est désactivé : les fichiers ne sont partagés que sur le réseau local." +"Online sharing is off: files are shared on the local network only." = "Le partage en ligne est désactivé : les fichiers ne sont partagés que sur le réseau local." "Only the first changed layers are listed." = "Seuls les premiers calques modifiés sont listés." "Only the first {n} changed layers are listed." = "Seuls les {n} premiers calques modifiés sont listés." "Only your threads" = "Vos fils uniquement" @@ -954,7 +954,7 @@ status = "draft — needs native review" "Order" = "Ordre" "Other" = "Autre" "Other apps may not open every .fig file exported here." = "D’autres applications risquent de ne pas ouvrir tous les fichiers .fig exportés ici." -"Others lose access and keep their copy; the file stays on the server for you" = "Les autres perdent l’accès et conservent leur copie ; le fichier reste sur le serveur pour vous" +"Others lose access and keep their copy; the file stays on the server for you" = "Les autres perdent l’accès et conservent leur copie ; le fichier reste sur le serveur pour vous" "Others type this under Share ▸ Join with a code" = "Les autres le saisissent dans Partager ▸ Rejoindre avec un code" "Outline stroke" = "Convertir le contour en tracé" "Outline text" = "Convertir le texte en tracés" @@ -999,7 +999,7 @@ status = "draft — needs native review" "Pen" = "Plume" "Pencil" = "Crayon" "People in this file" = "Personnes dans ce fichier" -"People join from anywhere with a code. The file is kept on {server} while you share it." = "On vous rejoint de partout avec un code. Le fichier est conservé sur {server} tant que vous le partagez." +"People join from anywhere with a code. The file is kept on {server} while you share it." = "Les personnes vous rejoignent de partout avec un code. Le fichier est conservé sur {server} tant que vous le partagez." "People on your network join with a code. Nothing goes through a server." = "Les personnes de votre réseau vous rejoignent avec un code. Rien ne passe par un serveur." "Phone" = "Téléphone" "Pick color" = "Prélever une couleur" @@ -1075,7 +1075,7 @@ status = "draft — needs native review" "Ratio" = "Rapport" "Reading the library…" = "Lecture de la bibliothèque…" "Ready for dev" = "Prêt pour le dev" -"Real name" = "Vrai nom" +"Real name" = "Nom réel" "Recent" = "Récents" "Recent files" = "Fichiers récents" "Recents" = "Récents" @@ -1110,9 +1110,9 @@ status = "draft — needs native review" "Remove text from path" = "Retirer le texte du tracé" "Remove the else and its actions" = "Retirer le « sinon » et ses actions" "Remove this condition and its actions" = "Retirer cette condition et ses actions" -"Remove this file from the server? It can't be shared or opened from there again. Your copy stays open here." = "Retirer ce fichier du serveur ? Il ne pourra plus être partagé ni ouvert depuis celui-ci. Votre copie reste ouverte ici." +"Remove this file from the server? It can't be shared or opened from there again. Your copy stays open here." = "Retirer ce fichier du serveur ? Il ne pourra plus être partagé ni ouvert depuis ce serveur. Votre copie reste ouverte ici." "Remove {name} from session" = "Retirer {name} de la session" -"Remove {name} from {server}?" = "Retirer {name} de {server} ?" +"Remove {name} from {server}?" = "Retirer {name} de {server} ?" "Removed" = "Supprimé" "Removed from the server" = "Retiré du serveur" "Removed since it was marked" = "Supprimé depuis le marquage" @@ -1144,7 +1144,7 @@ status = "draft — needs native review" "Reset to style" = "Revenir au style" "Reset to the inherited value" = "Revenir à la valeur héritée" "Resize by the nudge amount" = "Redimensionner du pas de décalage" -"Resize frames to the device's screen (or the window without a device): their constraints and auto layout respond" = "Redimensionne les cadres à l’écran de l’appareil (ou à la fenêtre sans appareil) : leurs contraintes et leur mise en page auto s’adaptent" +"Resize frames to the device's screen (or the window without a device): their constraints and auto layout respond" = "Redimensionner les cadres à l’écran de l’appareil (ou à la fenêtre sans appareil) : leurs contraintes et leur mise en page auto s’adaptent" "Resize to fit" = "Redimensionner au contenu" "Resizing" = "Redimensionnement" "Resolve" = "Résoudre" @@ -1218,7 +1218,7 @@ status = "draft — needs native review" "Select a frame to preview it" = "Sélectionnez un cadre pour le prévisualiser" "Select a layer to see its properties and code" = "Sélectionnez un calque pour voir ses propriétés et son code" "Select all" = "Tout sélectionner" -"Select all with same" = "Sélectionner tout ce qui a le même" +"Select all with same" = "Sélectionner par propriété commune" "Select layer" = "Sélectionner le calque" "Select layers and they space themselves as their content changes" = "Sélectionnez des calques et leur espacement s’ajuste quand leur contenu change" "Select layers to apply the style to" = "Sélectionnez les calques auxquels appliquer le style" @@ -1252,12 +1252,12 @@ status = "draft — needs native review" "Share with a code" = "Partager avec un code" "Share: live session, export" = "Partager : session en direct, exportation" "Shared" = "Partagé" -"Shared: copy its code from the file's menu" = "Partagé : copiez son code depuis le menu du fichier" -"Sharing a file needs the desktop app; here you can join one." = "Le partage d’un fichier nécessite l’application de bureau ; ici, vous pouvez en rejoindre un." -"Sharing on the local network (online sharing is off: no server is set in Preferences)." = "Partage sur le réseau local (le partage en ligne est désactivé : aucun serveur n’est défini dans les Préférences)." +"Shared: copy its code from the file's menu" = "Partagé : copiez son code depuis le menu du fichier" +"Sharing a file needs the desktop app; here you can join one." = "Le partage d’un fichier nécessite l’application de bureau ; ici, vous pouvez en rejoindre un." +"Sharing on the local network (online sharing is off: no server is set in Preferences)." = "Partage sur le réseau local (le partage en ligne est désactivé : aucun serveur n’est défini dans les Préférences)." "Sharing on your network · {people}" = "Partage sur votre réseau · {people}" "Sharing online · {people}" = "Partage en ligne · {people}" -"Sharing stopped: the file stays on the server, open only to you" = "Partage arrêté : le fichier reste sur le serveur, accessible à vous seul" +"Sharing stopped: the file stays on the server, open only to you" = "Partage arrêté : le fichier reste sur le serveur, accessible à vous seul" "Show" = "Afficher" "Show all templates" = "Afficher tous les modèles" "Show as grid" = "Afficher en grille" @@ -1315,7 +1315,7 @@ status = "draft — needs native review" "Spam, stolen work, abuse…" = "Spam, travail volé, abus…" "Speech bubble" = "Bulle" "Splay" = "Évasement" -"Spotlight me" = "Me mettre sous les projecteurs" +"Spotlight me" = "Faire suivre ma vue" "Spread" = "Étendue" "Square" = "Carré" "Stamp" = "Tampon" @@ -1343,7 +1343,7 @@ status = "draft — needs native review" "Stop following {name}" = "Ne plus suivre {name}" "Stop session" = "Arrêter la session" "Stop sharing" = "Arrêter le partage" -"Stop spotlighting" = "Quitter les projecteurs" +"Stop spotlighting" = "Arrêter de faire suivre ma vue" "Stop {n} {hex}" = "Arrêt {n} {hex}" "Straight" = "Droit" "Stretch" = "Étirer" @@ -1399,7 +1399,7 @@ status = "draft — needs native review" "The destination document is unavailable. Nothing was imported." = "Le document de destination est indisponible. Rien n’a été importé." "The destination document was closed. Nothing was imported." = "Le document de destination a été fermé. Rien n’a été importé." "The destination page was removed. Nothing was imported." = "La page de destination a été supprimée. Rien n’a été importé." -"The file is deleted from the server: nobody can open or join it from there again, and this can't be undone. Copies that are open stay with whoever has them." = "Le fichier est supprimé du serveur : plus personne ne pourra l’ouvrir ni le rejoindre depuis celui-ci, et c’est irréversible. Les copies ouvertes restent à ceux qui les ont." +"The file is deleted from the server: nobody can open or join it from there again, and this can't be undone. Copies that are open stay with whoever has them." = "Le fichier est supprimé du serveur : plus personne ne pourra l’ouvrir ni le rejoindre depuis ce serveur, et c’est irréversible. Les copies ouvertes restent à ceux qui les ont." "The file now holds another library" = "Le fichier contient désormais une autre bibliothèque" "The file now holds another library (\"{name}\")" = "Le fichier contient désormais une autre bibliothèque (« {name} »)" "The files you shared or published online from this computer. They stay on their server until you remove them." = "Les fichiers que vous avez partagés ou publiés en ligne depuis cet ordinateur. Ils restent sur leur serveur jusqu’à ce que vous les retiriez." @@ -1415,16 +1415,16 @@ status = "draft — needs native review" "This branch can't be reviewed: {e}" = "Impossible de relire cette branche : {e}" "This branch was merged or archived." = "Cette branche a été fusionnée ou archivée." "This computer only" = "Cet ordinateur uniquement" -"This computer remembers the owner token, so the file can be managed from the home screen's Online list. Copy it to manage the file from somewhere else: it is a password for this file and won't be shown again." = "Cet ordinateur mémorise le jeton de propriétaire : le fichier se gère depuis la liste En ligne de l’écran d’accueil. Copiez-le pour gérer le fichier depuis un autre endroit : c’est un mot de passe pour ce fichier et il ne sera plus affiché." +"This computer remembers the owner token, so the file can be managed from the home screen's Online list. Copy it to manage the file from somewhere else: it is a password for this file and won't be shown again." = "Cet ordinateur mémorise le jeton de propriétaire : le fichier se gère depuis la liste En ligne de l’écran d’accueil. Copiez-le pour gérer le fichier depuis un autre endroit : c’est un mot de passe pour ce fichier et il ne sera plus affiché." "This condition is edited as an expression" = "Cette condition se modifie en tant qu’expression" "This file" = "Ce fichier" "This file has unsaved changes" = "Ce fichier contient des modifications non enregistrées" "This file is on {server}, open only to you. Share it to let others in with a new code." = "Ce fichier est sur {server}, accessible à vous seul. Partagez-le pour laisser entrer d’autres personnes avec un nouveau code." "This frame is new on the branch." = "Ce cadre est nouveau dans la branche." "This frame isn't on the branch." = "Ce cadre n’est pas dans la branche." -"This server is open to the public: no key needed." = "Ce serveur est ouvert au public : aucune clé nécessaire." -"This server needs its key: add ?key=… to the address (`figcraft-server key` prints it on the server's machine)." = "Ce serveur exige sa clé : ajoutez ?key=… à l’adresse (`figcraft-server key` l’affiche sur la machine du serveur)." -"This server takes files only with its key: see Advanced." = "Ce serveur n’accepte les fichiers qu’avec sa clé : voir Avancé." +"This server is open to the public: no key needed." = "Ce serveur est ouvert au public : aucune clé nécessaire." +"This server needs its key: add ?key=… to the address (`figcraft-server key` prints it on the server's machine)." = "Ce serveur exige sa clé : ajoutez ?key=… à l’adresse (`figcraft-server key` l’affiche sur la machine du serveur)." +"This server takes files only with its key: see Advanced." = "Ce serveur n’accepte les fichiers qu’avec sa clé : voir Avancé." "Thumbs up" = "Pouce levé" "This version can't be read." = "Cette version est illisible." "This version doesn't have it: all of it is new." = "Cette version ne le contient pas : tout est nouveau." @@ -1475,7 +1475,7 @@ status = "draft — needs native review" "Unsaved changes: save first, or close again with {key} to discard" = "Modifications non enregistrées : enregistrez d’abord ou fermez à nouveau avec {key} pour les abandonner" "Up" = "Haut" "Update" = "Mettre à jour" -"Update from main" = "Mettre à jour depuis le principal" +"Update from main" = "Mettre à jour depuis le fichier principal" "Update from selection" = "Mettre à jour depuis la sélection" "Update plugin" = "Mettre à jour le plugin" "Updated {components} and {styles}" = "{components} et {styles} mis à jour" @@ -1538,7 +1538,7 @@ status = "draft — needs native review" "Weight" = "Graisse" "What changed since a saved version" = "Ce qui a changé depuis une version enregistrée" "What changed since it was marked ready for dev" = "Ce qui a changé depuis le marquage prêt pour le dev" -"What is wrong with this file? The server's operator gets your report." = "Qu’est-ce qui ne va pas avec ce fichier ? L’opérateur du serveur reçoit votre signalement." +"What is wrong with this file? The server's operator gets your report." = "Qu’est-ce qui ne va pas avec ce fichier ? L’opérateur du serveur reçoit votre signalement." "What it's for" = "À quoi il sert" "What this changed from isn't known: it changed before FigCraft kept a copy of ready-for-dev frames. Resolve the change (or mark it ready again) to compare from now on." = "L’état d’origine de cette modification est inconnu : elle date d’avant que FigCraft ne conserve une copie des cadres prêts pour le dev. Résolvez la modification (ou marquez-la à nouveau comme prête) pour comparer à partir de maintenant." "When marked ready" = "Au marquage prêt" @@ -1561,9 +1561,9 @@ status = "draft — needs native review" "Yesterday" = "Hier" "You" = "Vous" "You'll get the shared file as a new tab and edit it together." = "Vous recevrez le fichier partagé dans un nouvel onglet et le modifierez ensemble." -"You're spotlighting: everyone follows your view" = "Vous êtes sous les projecteurs : tout le monde suit votre vue" +"You're spotlighting: everyone follows your view" = "Vous faites suivre votre vue : tout le monde vous suit" "Your copy of the file stays open" = "Votre copie du fichier reste ouverte" -"Your copy stays open; the file stays on the server" = "Votre copie reste ouverte ; le fichier reste sur le serveur" +"Your copy stays open; the file stays on the server" = "Votre copie reste ouverte ; le fichier reste sur le serveur" "Your name" = "Votre nom" "Your name in comments" = "Votre nom dans les commentaires" "Your system asks for reduced motion: animations stay off." = "Votre système demande de réduire les animations : elles restent désactivées." @@ -1713,12 +1713,12 @@ status = "draft — needs native review" "{name} ({tag}): {min} – {max}, default {default}. Double-click the slider to go back to the style's value; Escape while dragging cancels." = "{name} ({tag}) : {min} – {max}, par défaut {default}. Double-cliquez sur le curseur pour revenir à la valeur du style ; Échap pendant le glissement annule." "{name} cleared from the list" = "{name} effacé de la liste" "{name} from {path}" = "{name} depuis {path}" -"{name} is forgotten on this computer; the file stays on {server}" = "{name} est oublié sur cet ordinateur ; le fichier reste sur {server}" +"{name} is forgotten on this computer; the file stays on {server}" = "{name} est oublié sur cet ordinateur ; le fichier reste sur {server}" "{name} is on the server." = "{name} est sur le serveur." -"{name} is spotlighting" = "{name} est sous les projecteurs" +"{name} is spotlighting" = "{name} fait suivre sa vue" "{name} is too large to open in the browser (over 512 MB)." = "{name} est trop volumineux pour être ouvert dans le navigateur (plus de 512 Mo)." "{name} was removed from the server" = "{name} a été retiré du serveur" -"{name} · deleted on main" = "{name} · supprimé dans le principal" +"{name} · deleted on main" = "{name} · supprimé dans le fichier principal" "{name} · deleted on the branch" = "{name} · supprimé dans la branche" "{name} · not published" = "{name} · non publié" "{name} · on a server" = "{name} · sur un serveur" @@ -1828,7 +1828,7 @@ status = "draft — needs native review" "branch.merge" = "Fusionner la branche" "branch.open" = "Ouvrir la branche" "branch.review" = "Relire la branche" -"branch.updateFromMain" = "Mettre à jour depuis le principal" +"branch.updateFromMain" = "Mettre à jour depuis le fichier principal" "codeConnect.export" = "Exporter les liaisons au code" "codeConnect.import" = "Importer les liaisons au code" "codeConnect.remove" = "Délier du code" @@ -1852,7 +1852,7 @@ status = "draft — needs native review" "collab.report" = "Signaler le fichier à l’opérateur du serveur" "collab.serverInfo" = "Informations sur le serveur" "collab.share" = "Partager avec un code d’accès" -"collab.spotlight" = "Me mettre sous les projecteurs" +"collab.spotlight" = "Faire suivre ma vue" "collab.stop" = "Arrêter la session en direct" "command.batch" = "Exécuter plusieurs commandes" "comment.add" = "Ajouter un commentaire" @@ -2120,10 +2120,10 @@ status = "draft — needs native review" "select.inverse" = "Inverser la sélection" "select.layersAt" = "Calques en un point" "select.matching" = "Sélectionner les calques correspondants" -"select.next" = "Sélectionner l’élément suivant" +"select.next" = "Sélectionner l’élément suivant du même niveau" "select.none" = "Tout désélectionner" "select.parent" = "Sélectionner le parent" -"select.previous" = "Sélectionner l’élément précédent" +"select.previous" = "Sélectionner l’élément précédent du même niveau" "select.remove" = "Retirer de la sélection" "select.sameEffect" = "Sélectionner le même effet" "select.sameFill" = "Sélectionner le même remplissage" @@ -2212,7 +2212,7 @@ status = "draft — needs native review" "text.sizeDown" = "Réduire la taille de police" "text.sizeUp" = "Augmenter la taille de police" "text.strikethrough" = "Barré" -"text.style" = "Styler une plage de texte" +"text.style" = "Appliquer un style à une plage de texte" "text.trackingDown" = "Réduire l’interlettrage" "text.trackingUp" = "Augmenter l’interlettrage" "text.underline" = "Souligné" @@ -2302,7 +2302,7 @@ status = "draft — needs native review" "vector.edit.smooth" = "Lisser ou rendre anguleux les points" "vector.edit.state" = "État de la modification vectorielle" "vector.edit.strokeCap" = "Extrémité du contour au point" -"vector.edit.strokeJoin" = "Jointure du contour au point" +"vector.edit.strokeJoin" = "Jonction du contour au point" "vector.edit.tool" = "Outil de modification vectorielle" "vector.edit.transform" = "Transformer les points" "vector.edit.widthPoint" = "Point de largeur" @@ -2338,7 +2338,7 @@ status = "draft — needs native review" "view.multiplayerCursors" = "Curseurs des autres participants" "view.nextFrame" = "Zoomer sur le cadre suivant" "view.nextPage" = "Page suivante" -"view.nextRegion" = "Activer la région suivante" +"view.nextRegion" = "Placer le focus sur la région suivante" "view.outlines" = "Contours" "view.outlinesHiddenLayers" = "Inclure les calques masqués dans les contours" "view.outlinesObjectBounds" = "Inclure les limites des objets dans les contours" @@ -2349,7 +2349,7 @@ status = "draft — needs native review" "view.pixelPreview" = "Aperçu des pixels" "view.prevFrame" = "Zoomer sur le cadre précédent" "view.prevPage" = "Page précédente" -"view.previousRegion" = "Activer la région précédente" +"view.previousRegion" = "Placer le focus sur la région précédente" "view.proofSrgb" = "Épreuve écran sRGB" "view.fullscreen" = "Plein écran" "view.prototype" = "Panneau Prototype" diff --git a/crates/ui-egui/locales/it.toml b/crates/ui-egui/locales/it.toml new file mode 100644 index 00000000..8f86b145 --- /dev/null +++ b/crates/ui-egui/locales/it.toml @@ -0,0 +1,2363 @@ +# FigCraft interface strings: Italian. +# +# Draft — needs native review: machine-drafted from the English catalog. Brand names and keyboard +# glyphs stay as they are. See docs/translating.md. + +[meta] +name = "Italiano" +status = "draft — needs native review" + +[ui] +"% of all commits" = "% di tutti i commit" +"(no longer published)" = "(non più pubblicato)" +"({n} warnings: some entries were skipped)" = { one = "({n} avviso: alcune voci sono state saltate)", other = "({n} avvisi: alcune voci sono state saltate)" } +"+ Create variable" = "+ Crea variabile" +".fig (experimental)" = ".fig (sperimentale)" +"1…9, 0" = "1…9, 0" +"@{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} lines (Δ {delta}), +{bin_added} / −{bin_deleted} binary assets, {first} – {last}" = "@{login}: {prs} PR, {commits} commit, +{added} / −{deleted} righe (Δ {delta}), +{bin_added} / −{bin_deleted} asset binari, {first} – {last}" +"A collection of this file with these modes and variables: override their values per mode and add variables of its own" = "Una collezione di questo file con queste modalità e variabili: sovrascrivi i loro valori per modalità e aggiungi variabili proprie" +"A .figcraft file with this name already exists. Choose its full name to confirm replacement." = "Esiste già un file .figcraft con questo nome. Scegli il nome completo per confermare la sostituzione." +"A layer on its own aligns to its frame: select more layers, or one in a frame" = "Un livello da solo si allinea al proprio frame: seleziona più livelli, oppure uno dentro un frame" +"A responsive product page: hero, features, figures, a quote and a footer" = "Una pagina prodotto responsive: hero, funzionalità, figure, una citazione e un footer" +"A spring plays until it settles: its mass, stiffness and damping set how long" = "Una molla si muove finché non si assesta: massa, rigidità e smorzamento ne determinano la durata" +"A title, an agenda, a big figure, a chart and a closing slide" = "Un titolo, un'agenda, una cifra grande, un grafico e una slide di chiusura" +"A to Z" = "Dalla A alla Z" +"About" = "Informazioni" +"About FigCraft" = "Informazioni su FigCraft" +"About this server" = "Informazioni su questo server" +"Action" = "Azione" +"Actions" = "Azioni" +"Actions for {name}" = "Azioni per {name}" +"Actions…" = "Azioni…" +"Active" = "Attivo" +"Actual size (100%)" = "Dimensione reale (100%)" +"Add a category" = "Aggiungi una categoria" +"Add a comment" = "Aggiungi un commento" +"Add a description" = "Aggiungi una descrizione" +"Add a frame (F) or a shape (R) to get started" = "Aggiungi un frame (F) o una forma (R) per iniziare" +"Add a layer or component to tile first" = "Aggiungi prima un livello o un componente da affiancare" +"Add a note for developers" = "Aggiungi una nota per gli sviluppatori" +"Add action" = "Aggiungi azione" +"Add actions for when no condition holds" = "Aggiungi azioni per quando nessuna condizione è vera" +"Add an annotation for developers" = "Aggiungi un'annotazione per gli sviluppatori" +"Add another condition" = "Aggiungi un'altra condizione" +"Add auto layout" = "Aggiungi auto layout" +"Add background behind overlay" = "Aggiungi sfondo dietro la sovrapposizione" +"Add content to this slot" = "Aggiungi contenuto a questo slot" +"Add dev resource" = "Aggiungi risorsa per sviluppatori" +"Add effect" = "Aggiungi effetto" +"Add export setting" = "Aggiungi impostazione di esportazione" +"Add fill" = "Aggiungi riempimento" +"Add interaction" = "Aggiungi interazione" +"Add layout grid" = "Aggiungi griglia di layout" +"Add library" = "Aggiungi libreria" +"Add library file…" = "Aggiungi file libreria…" +"Add max height" = "Aggiungi altezza massima" +"Add max width" = "Aggiungi larghezza massima" +"Add min height" = "Aggiungi altezza minima" +"Add min width" = "Aggiungi larghezza minima" +"Add mode" = "Aggiungi modalità" +"Add new page" = "Aggiungi nuova pagina" +"Add or update collections from a variables JSON" = "Aggiungi o aggiorna collezioni da un JSON di variabili" +"Add properties to make instances configurable" = "Aggiungi proprietà per rendere le istanze configurabili" +"Add reaction" = "Aggiungi reazione" +"Add starting point" = "Aggiungi punto di partenza" +"Add stroke" = "Aggiungi contorno" +"Add variant" = "Aggiungi variante" +"Add variant with a new value" = "Aggiungi variante con un nuovo valore" +"Added" = "Aggiunto" +"Added the library \"{name}\"" = "Libreria \"{name}\" aggiunta" +"Adding to {name}" = "Aggiunta a {name}" +"Adjust" = "Regola" +"Adjustment presets" = "Preset di regolazione" +"Advanced" = "Avanzate" +"Advanced stroke" = "Contorno avanzato" +"After delay" = "Dopo un ritardo" +"After delay {ms}ms" = "Dopo un ritardo di {ms} ms" +"Align" = "Allinea" +"Align bottom" = "Allinea in basso" +"Align center" = "Allinea al centro" +"Align horizontal centers" = "Allinea i centri orizzontali" +"Align left" = "Allinea a sinistra" +"Align middle" = "Allinea a metà altezza" +"Align right" = "Allinea a destra" +"Align text baselines" = "Allinea le linee di base del testo" +"Align top" = "Allinea in alto" +"Align vertical centers" = "Allinea i centri verticali" +"Alignment" = "Allineamento" +"All" = "Tutti" +"All categories" = "Tutte le categorie" +"All comments" = "Tutti i commenti" +"All components" = "Tutti i componenti" +"All fonts" = "Tutti i font" +"All supported properties" = "Tutte le proprietà supportate" +"All the fonts this file uses are installed" = "Tutti i font usati da questo file sono installati" +"All variables" = "Tutte le variabili" +"Alpha" = "Alfa" +"Alphabetical" = "Alfabetico" +"Alt+drag" = "Alt+trascina" +"An analytics screen on a grid: figures, charts drawn from vectors and a table" = "Una schermata di analisi su una griglia: cifre, grafici disegnati con vettori e una tabella" +"An invite link that opens FigCraft and joins" = "Un link di invito che apre FigCraft e si unisce" +"Anchor" = "Ancora" +"Android" = "Android" +"Angular" = "Angolare" +"Animation" = "Animazione" +"Annotations" = "Annotazioni" +"Annotations shown on the canvas" = "Annotazioni mostrate sul canvas" +"Another server, or one that needs its key. The usual server is set in Preferences." = "Un altro server, oppure uno che richiede la sua chiave. Il server abituale si imposta nelle Preferenze." +"Any status" = "Qualsiasi stato" +"Anyone on your network with this code can edit the file, while this app stays open." = "Chiunque nella tua rete abbia questo codice può modificare il file, finché questa app resta aperta." +"Anyone with the edit link can change the file; the view link only lets people look. The links carry their secrets: share them like passwords. `figcraft-server links` makes more and revokes them." = "Chiunque abbia il link di modifica può cambiare il file; il link di visualizzazione permette solo di guardare. I link contengono i loro segreti: condividili come password. `figcraft-server links` ne crea altri e li revoca." +"Anyone with this code can edit the file." = "Chiunque abbia questo codice può modificare il file." +"Anyone with this code can look at the file, not change it." = "Chiunque abbia questo codice può vedere il file, ma non modificarlo." +"Appearance" = "Aspetto" +"Apply a component property" = "Applica una proprietà del componente" +"Apply variable" = "Applica variabile" +"Apply variable to text" = "Applica variabile al testo" +"Apply variable to visibility" = "Applica variabile alla visibilità" +"Apps" = "App" +"Apr" = "apr" +"Arc" = "Arco" +"Arc ratio" = "Rapporto dell'arco" +"Arc start" = "Inizio dell'arco" +"Arc sweep" = "Ampiezza dell'arco" +"Archive" = "Archivia" +"Archived" = "Archiviato" +"Arrange" = "Disponi" +"Arrow" = "Freccia" +"As typed" = "Come digitato" +"Asking the server…" = "Interrogo il server…" +"Assets" = "Asset" +"Aug" = "ago" +"Auto" = "Auto" +"Auto (space around)" = "Auto (spazio attorno)" +"Auto (space between)" = "Auto (spazio tra)" +"Auto (space evenly)" = "Auto (spazio uniforme)" +"Auto ({mode})" = "Auto ({mode})" +"Auto columns" = "Colonne automatiche" +"Auto gap" = "Spaziatura automatica" +"Auto height" = "Altezza automatica" +"Auto layout" = "Auto layout" +"Auto rows" = "Righe automatiche" +"Auto width" = "Larghezza automatica" +"Back to the FigCraft server" = "Torna al server FigCraft" +"Back to the font's value" = "Torna al valore del font" +"Back" = "Indietro" +"Back to Ready for dev" = "Torna a Pronto per lo sviluppo" +"Back to Ready for dev: the next change compares with how it is now" = "Torna a Pronto per lo sviluppo: la prossima modifica sarà confrontata con lo stato attuale" +"Back to assets" = "Torna agli asset" +"Back to current" = "Torna alla versione attuale" +"Back to styles" = "Torna agli stili" +"Back to the canvas from a focused region" = "Torna al canvas da un'area attiva" +"Back to the current version" = "Torna alla versione attuale" +"Back to the values the font style implies" = "Torna ai valori previsti dallo stile del font" +"Background" = "Sfondo" +"Background blur" = "Sfocatura dello sfondo" +"Basics" = "Nozioni di base" +"Before" = "Prima" +"Bend" = "Curva" +"Bevel" = "Smusso" +"Binary assets added" = "Asset binari aggiunti" +"Binary assets removed" = "Asset binari rimossi" +"Black & white" = "Bianco e nero" +"Bleed" = "Abbondanza" +"Bleed marks" = "Crocini di abbondanza" +"Bleed marks need a bleed" = "I crocini di abbondanza richiedono un'abbondanza" +"Blend" = "Fusione" +"Blend mode" = "Metodo di fusione" +"Blue" = "Blu" +"Blur" = "Sfocatura" +"Boolean" = "Booleano" +"Boolean groups" = "Gruppi booleani" +"Both directions" = "Entrambe le direzioni" +"Bottom" = "In basso" +"Bottom left radius" = "Raggio in basso a sinistra" +"Bottom padding" = "Margine interno inferiore" +"Bottom right radius" = "Raggio in basso a destra" +"Branch" = "Ramo" +"Branch archived" = "Ramo archiviato" +"Branch merged" = "Ramo unito" +"Branch name" = "Nome del ramo" +"Branch of {file}" = "Ramo di {file}" +"Branch of {file} · merged or archived" = "Ramo di {file} · unito o archiviato" +"Branch updated from main" = "Ramo aggiornato da main" +"Branch {name}" = "Ramo {name}" +"Branches" = "Rami" +"Bound to {name}. Click to detach the variable." = "Collegato a {name}. Clicca per scollegare la variabile." +"Bring forward" = "Porta avanti" +"Bring the main file's changes into the branch" = "Porta le modifiche del file principale nel ramo" +"Bring to front" = "Porta in primo piano" +"Bristle" = "Setola" +"Browse" = "Sfoglia" +"Brush" = "Pennello" +"Brush rotation jitter" = "Variazione casuale della rotazione del pennello" +"Brush scatter" = "Dispersione del pennello" +"Brush size jitter" = "Variazione casuale della dimensione del pennello" +"Brush spacing" = "Spaziatura del pennello" +"Bulleted list" = "Elenco puntato" +"C or Shift+C" = "C o Maiusc+C" +"C or ⇧C" = "C o ⇧C" +"CMYK colour" = "Colore CMYK" +"Can view" = "Può visualizzare" +"Checking the fonts installed on this computer…" = "Controllo dei font installati su questo computer…" +"Checking the server…" = "Controllo del server…" +"Clear from this list" = "Rimuovi da questo elenco" +"Code or invite link" = "Codice o link di invito" +"Color profile: {name}" = "Profilo colore: {name}" +"Colour and text styles, Light and Dark variables, buttons, inputs and cards" = "Stili di colore e di testo, variabili Chiaro e Scuro, pulsanti, campi di input e schede" +"Convert to frame" = "Converti in frame" +"Convert to section" = "Converti in sezione" +"CSS custom properties…" = "Proprietà personalizzate CSS…" +"Can edit" = "Può modificare" +"Can't read this library: {e}" = "Impossibile leggere questa libreria: {e}" +"Cancel" = "Annulla" +"Canvas" = "Canvas" +"Canvas stacking" = "Sovrapposizione nel canvas" +"Cap height to baseline" = "Altezza delle maiuscole fino alla linea di base" +"Case" = "Maiuscole/minuscole" +"Case-sensitive forms" = "Forme sensibili alle maiuscole" +"Categories" = "Categorie" +"Center" = "Centro" +"Change alias" = "Cambia alias" +"Change resolved" = "Cambia lo stato di risolto" +"Change style" = "Cambia stile" +"Change to" = "Cambia in" +"Change to needs a layer inside a variant" = "\"Cambia in\" richiede un livello dentro una variante" +"Changed" = "Modificato" +"Changed on both sides: keep one" = "Modificato su entrambi i lati: mantienine uno" +"Changes on main" = "Modifiche su main" +"Changes on the branch" = "Modifiche sul ramo" +"Changes unknown" = "Modifiche sconosciute" +"Choose React, Leptos, SwiftUI or Compose above to connect this component to its code." = "Scegli React, Leptos, SwiftUI o Compose qui sopra per collegare questo componente al suo codice." +"Choose another profile to change it." = "Scegli un altro profilo per cambiarlo." +"Choose collection" = "Scegli collezione" +"Choose image" = "Scegli immagine" +"Choose mode" = "Scegli modalità" +"Choose variable" = "Scegli variabile" +"Circle" = "Cerchio" +"Clear recent" = "Svuota recenti" +"Click + to replace mixed content" = "Clicca + per sostituire il contenuto misto" +"Click anywhere on the canvas to leave one." = "Clicca ovunque sul canvas per lasciarne uno." +"Click or drag to merge · {alt} removes" = "Clicca o trascina per unire · {alt} rimuove" +"Click or drag to remove · {alt} merges" = "Clicca o trascina per rimuovere · {alt} unisce" +"Click to record a key" = "Clicca per registrare un tasto" +"Clip content" = "Ritaglia il contenuto" +"Clip to shape" = "Ritaglia sulla forma" +"Close" = "Chiudi" +"Close overlay" = "Chiudi sovrapposizione" +"Close preview" = "Chiudi anteprima" +"Close tab" = "Chiudi scheda" +"Close version history" = "Chiudi cronologia versioni" +"Close when clicking outside" = "Chiudi cliccando all'esterno" +"Code" = "Codice" +"Code syntax" = "Sintassi del codice" +"Collapse" = "Comprimi" +"Collapse panel" = "Comprimi pannello" +"Collection" = "Collezione" +"Collections" = "Collezioni" +"Color" = "Colore" +"Color burn" = "Brucia colore" +"Color dodge" = "Scherma colore" +"Color format" = "Formato colore" +"Color profile" = "Profilo colore" +"Color styles" = "Stili di colore" +"Color values in this file are in this color space." = "I valori di colore in questo file sono in questo spazio colore." +"Color variables" = "Variabili di colore" +"Colour" = "Colore" +"Column" = "Colonna" +"Column gap" = "Spaziatura colonne" +"Column sizes" = "Dimensioni colonne" +"Column span" = "Estensione colonne" +"Columns" = "Colonne" +"Columns {count}" = "Colonne {count}" +"Comment" = "Commento" +"Comments" = "Commenti" +"Commits" = "Commit" +"Company" = "Azienda" +"Compare changes" = "Confronta modifiche" +"Compatibility notes" = "Note di compatibilità" +"Completed" = "Completato" +"Component" = "Componente" +"Component name" = "Nome del componente" +"Component property" = "Proprietà del componente" +"Component set" = "Set di componenti" +"Component sets" = "Set di componenti" +"Components" = "Componenti" +"Conditional" = "Condizionale" +"Confetti" = "Coriandoli" +"Conflicts" = "Conflitti" +"Connect to code" = "Collega al codice" +"Connected" = "Collegato" +"Connected to code: {from}" = "Collegato al codice: {from}" +"Connection lost, reconnecting (attempt {attempt})…" = "Connessione persa, riconnessione in corso (tentativo {attempt})…" +"Constrain proportions" = "Mantieni le proporzioni" +"Constraints" = "Vincoli" +"Content" = "Contenuto" +"Contents hidden" = "Contenuti nascosti" +"Contextual alternates" = "Alternative contestuali" +"Contrast" = "Contrasto" +"Contributors" = "Collaboratori" +"Contributors: {people} · Commits: {commits}" = "Collaboratori: {people} · Commit: {commits}" +"Controller" = "Controller" +"Cool" = "Freddo" +"Copied as CSS" = "Copiato come CSS" +"Copied as PNG" = "Copiato come PNG" +"Copied as SVG" = "Copiato come SVG" +"Copied as text" = "Copiato come testo" +"Copied {value}" = "Copiato {value}" +"Copy" = "Copia" +"Copy as" = "Copia come" +"Copy as CSS" = "Copia come CSS" +"Copy as PNG" = "Copia come PNG" +"Copy as SVG" = "Copia come SVG" +"Copy code" = "Copia codice" +"Copy link" = "Copia link" +"Copy the {label}" = "Copia {label}" +"Copy {value}" = "Copia {value}" +"Corner radius" = "Raggio degli angoli" +"Corner smoothing" = "Smussatura degli angoli" +"Couldn't download the variables" = "Impossibile scaricare le variabili" +"Couldn't reach {server}: showing what this computer remembers." = "Impossibile raggiungere {server}: mostro ciò che questo computer ha in memoria." +"Couldn't read {name}." = "Impossibile leggere {name}." +"Couldn't save automatically: {error}" = "Impossibile salvare automaticamente: {error}" +"Count" = "Numero" +"Create" = "Crea" +"Create a variable collection first" = "Crea prima una collezione di variabili" +"Create a variable first" = "Crea prima una variabile" +"Create alias" = "Crea alias" +"Create branch" = "Crea ramo" +"Create collection" = "Crea collezione" +"Create component" = "Crea componente" +"Create component property" = "Crea proprietà del componente" +"Create components, styles or variables to publish them as a library." = "Crea componenti, stili o variabili per pubblicarli come libreria." +"Create property…" = "Crea proprietà…" +"Create style" = "Crea stile" +"Create style from the selected text" = "Crea stile dal testo selezionato" +"Created" = "Creato" +"Crop" = "Ritaglia" +"Crop marks" = "Crocini di taglio" +"Ctrl+arrows" = "Ctrl+frecce" +"Ctrl+click" = "Ctrl+clic" +"Ctrl+scroll" = "Ctrl+scorrimento" +"Current name" = "Nome attuale" +"Current version" = "Versione attuale" +"Cursor chat" = "Chat del cursore" +"Custom" = "Personalizzato" +"Custom size" = "Dimensione personalizzata" +"Cut" = "Taglia" +"Dark" = "Scuro" +"Darken" = "Scurisci" +"Dash" = "Tratteggio" +"Dashboard" = "Dashboard" +"Date created" = "Data di creazione" +"Dec" = "dic" +"Decoration style" = "Stile della decorazione" +"Decoration" = "Decorazione" +"Deep select" = "Selezione profonda" +"Default" = "Predefinito" +"Default content" = "Contenuto predefinito" +"Default value (the top-left variant)" = "Valore predefinito (la variante in alto a sinistra)" +"Delay" = "Ritardo" +"Delete" = "Elimina" +"Delete category (its annotations stay)" = "Elimina categoria (le sue annotazioni restano)" +"Delete collection" = "Elimina collezione" +"Delete group" = "Elimina gruppo" +"Delete mode" = "Elimina modalità" +"Delete page" = "Elimina pagina" +"Delete preset" = "Elimina preset" +"Delete property" = "Elimina proprietà" +"Delete the style (layers keep their values)" = "Elimina lo stile (i livelli mantengono i propri valori)" +"Delete thread" = "Elimina discussione" +"Delete variable" = "Elimina variabile" +"Delete “{name}”? This can't be undone." = "Eliminare “{name}”? L'operazione non può essere annullata." +"Density" = "Densità" +"Depth" = "Profondità" +"Describe how and when to use this component" = "Descrivi come e quando usare questo componente" +"Describe what changed" = "Descrivi cosa è cambiato" +"Description" = "Descrizione" +"Description (optional)" = "Descrizione (facoltativa)" +"Design" = "Design" +"Design system starter" = "Starter per design system" +"Desktop" = "Desktop" +"Destination" = "Destinazione" +"Detach alias" = "Scollega alias" +"Detach instance" = "Scollega istanza" +"Detach property" = "Scollega proprietà" +"Detach style" = "Scollega stile" +"Detach styles" = "Scollega stili" +"Detach variable" = "Scollega variabile" +"Details" = "Dettagli" +"Dev Mode" = "Modalità sviluppo" +"Dev resources" = "Risorse per sviluppatori" +"Development" = "Sviluppo" +"Device" = "Dispositivo" +"Device at 100%" = "Dispositivo al 100%" +"Diamond" = "Rombo" +"Difference" = "Differenza" +"Dimensions" = "Dimensioni" +"Direction" = "Direzione" +"Disconnect from code" = "Scollega dal codice" +"Dispersion" = "Dispersione" +"Display name" = "Nome visualizzato" +"Dissolve" = "Dissolvenza" +"Distribute and tidy" = "Distribuisci e riordina" +"Distribute horizontal spacing" = "Distribuisci spaziatura orizzontale" +"Distribute vertical spacing" = "Distribuisci spaziatura verticale" +"Divider" = "Divisore" +"Don't truncate" = "Non troncare" +"Done" = "Fatto" +"Done editing" = "Modifica terminata" +"Dots" = "Punti" +"Dotted" = "Puntinato" +"Down" = "Giù" +"Drafts" = "Bozze" +"Drag the stroke to add a width point" = "Trascina il contorno per aggiungere un punto di larghezza" +"Draw" = "Disegna" +"Draw a frame" = "Disegna un frame" +"Drawing tools" = "Strumenti di disegno" +"Drop a .figcraft, .fig, .json, .svg or image file." = "Rilascia un file .figcraft, .fig, .json, .svg o un'immagine." +"Drop to import" = "Rilascia per importare" +"Drop to open" = "Rilascia per aprire" +"Drop shadow" = "Ombra esterna" +"Dual-licensed MIT OR Apache-2.0. Made by the ArtCraft team and community." = "Doppia licenza MIT OR Apache-2.0. Creato dal team e dalla community di ArtCraft." +"Duo" = "Duo" +"Duplicate" = "Duplica" +"Duplicate by dragging" = "Duplica trascinando" +"Duplicate mode" = "Duplica modalità" +"Duplicate page" = "Duplica pagina" +"Edit" = "Modifica" +"Edit as an expression" = "Modifica come espressione" +"Edit colour" = "Modifica colore" +"Edit comment" = "Modifica commento" +"Edit dev resource" = "Modifica risorsa per sviluppatori" +"Edit link" = "Link di modifica" +"Edit link copied" = "Link di modifica copiato" +"Edit style" = "Modifica stile" +"Edit variable" = "Modifica variabile" +"Edit with the builder" = "Modifica con il builder" +"Edited {when}" = "Modificato {when}" +"Effect settings" = "Impostazioni dell'effetto" +"Effect styles" = "Stili di effetto" +"Effects" = "Effetti" +"Ellipse" = "Ellisse" +"Ellipses" = "Ellissi" +"Else" = "Altrimenti" +"Else if" = "Altrimenti se" +"Embedded profile" = "Profilo incorporato" +"Empty" = "Vuoto" +"End" = "Fine" +"End point" = "Punto finale" +"Ends the session; everyone keeps their copy" = "Termina la sessione; ognuno conserva la propria copia" +"Enter" = "Invio" +"Enter the address of a figcraft-server and press Browse to see the plugins it offers." = "Inserisci l'indirizzo di un figcraft-server e premi Sfoglia per vedere i plugin che offre." +"Enter to save · Esc to cancel" = "Invio per salvare · Esc per annullare" +"Eraser" = "Gomma" +"Esc" = "Esc" +"Essential" = "Essenziale" +"Every conflict has a choice." = "Ogni conflitto ha una scelta." +"Every top-level frame already starts a flow" = "Ogni frame di primo livello avvia già un flusso" +"Exact opacity (4 then 5: 45%)" = "Opacità esatta (4 poi 5: 45%)" +"Exclude" = "Escludi" +"Excluded" = "Escluso" +"Excluded from layout" = "Escluso dal layout" +"Excludes" = "Esclude" +"Exclusion" = "Esclusione" +"Exit focus view (Esc)" = "Esci dalla vista focus (Esc)" +"Expand" = "Espandi" +"Export" = "Esporta" +"Export all" = "Esporta tutto" +"Export frames and layers as PNG, JPG, SVG or PDF" = "Esporta frame e livelli come PNG, JPG, SVG o PDF" +"Export page as PNG" = "Esporta pagina come PNG" +"Export settings" = "Impostazioni di esportazione" +"Export variables" = "Esporta variabili" +"Export {name}" = "Esporta {name}" +"Export {n} layers" = { one = "Esporta {n} livello", other = "Esporta {n} livelli" } +"Exported {file}" = "Esportato {file}" +"Exported {name}" = "Esportato {name}" +"Exported {name}; {n} kinds of content were left out" = { one = "Esportato {name}; {n} tipo di contenuto è stato escluso", other = "Esportato {name}; {n} tipi di contenuto sono stati esclusi" } +"Exported {n} files" = { one = "Esportato {n} file", other = "Esportati {n} file" } +"Export…" = "Esporta…" +"Expose properties to instances" = "Esponi le proprietà alle istanze" +"Exposure" = "Esposizione" +"Expression" = "Espressione" +"Extend collection" = "Estendi collezione" +"Extends \"{name}\": overridden values are marked" = "Estende \"{name}\": i valori sovrascritti sono evidenziati" +"Eye" = "Occhio" +"F" = "F" +"Fade" = "Sfuma" +"False" = "Falso" +"Feb" = "feb" +"Fetching from the server…" = "Recupero dal server…" +"FigCraft couldn't open its window: {error}" = "FigCraft non è riuscito ad aprire la sua finestra: {error}" +"FigCraft doesn't support every feature of these files yet. The notes list what was left out or changed in the imported copy; the original file is unchanged." = "FigCraft non supporta ancora tutte le funzionalità di questi file. Le note elencano ciò che è stato omesso o modificato nella copia importata; il file originale resta invariato." +"File" = "File" +"File name" = "Nome del file" +"File not found" = "File non trovato" +"File published" = "File pubblicato" +"Files aren't end-to-end encrypted: the server's operator can read them." = "I file non sono crittografati end-to-end: chi gestisce il server può leggerli." +"Files nobody opens for {n} days are deleted." = { one = "I file che nessuno apre da {n} giorno vengono eliminati.", other = "I file che nessuno apre da {n} giorni vengono eliminati." } +"Fill" = "Riempimento" +"Fill (fr)" = "Riempimento (fr)" +"Fill container" = "Riempi contenitore" +"Fill window" = "Riempi finestra" +"Fills" = "Riempimenti" +"Fill|sizing" = "Riempi" +"Filter and sort" = "Filtra e ordina" +"Find" = "Trova" +"Find and replace" = "Trova e sostituisci" +"Find layers" = "Trova livelli" +"First" = "Primo" +"First commit" = "Primo commit" +"First on top" = "Il primo in alto" +"Fit" = "Adatta" +"Fit device to window" = "Adatta il dispositivo alla finestra" +"Fit to contents" = "Adatta al contenuto" +"Fit to window" = "Adatta alla finestra" +"Fit width" = "Adatta alla larghezza" +"Fixed" = "Fisso" +"Fixed gap" = "Spaziatura fissa" +"Fixed size" = "Dimensione fissa" +"Flatten" = "Appiattisci" +"Flip" = "Rifletti" +"Flip horizontal" = "Rifletti orizzontalmente" +"Flip text to the other side" = "Rifletti il testo sull'altro lato" +"Flip vertical" = "Rifletti verticalmente" +"Flip width profile" = "Inverti il profilo di larghezza" +"Flow starting point" = "Punto di partenza del flusso" +"Flow starting points" = "Punti di partenza del flusso" +"Flows" = "Flussi" +"Focus view" = "Vista focus" +"Fold autosaves" = "Comprimi i salvataggi automatici" +"Folder: click to collapse" = "Cartella: clicca per comprimere" +"Folder: click to expand" = "Cartella: clicca per espandere" +"Follow" = "Segui" +"Follow {name}" = "Segui {name}" +"Following" = "Seguendo" +"Following {name} · {key} to stop" = "Stai seguendo {name} · {key} per smettere" +"Font" = "Font" +"Font not installed" = "Font non installato" +"Font size" = "Dimensione del font" +"Font style" = "Stile del font" +"Forget on this computer" = "Dimentica su questo computer" +"Four screens of a trail app with components and a prototype flow" = "Quattro schermate di un'app per sentieri con componenti e un flusso di prototipo" +"Fractions" = "Frazioni" +"Frame" = "Frame" +"Frame selection" = "Racchiudi la selezione in un frame" +"Frame tools" = "Strumenti frame" +"Frames" = "Frame" +"From the library \"{name}\": read-only" = "Dalla libreria \"{name}\": sola lettura" +"From {name}" = "Da {name}" +"From {server}" = "Da {server}" +"Frost" = "Brina" +"Gap" = "Spaziatura" +"Gap between columns" = "Spaziatura tra le colonne" +"Gap between items" = "Spaziatura tra gli elementi" +"Gap between rows" = "Spaziatura tra le righe" +"Getting started" = "Per iniziare" +"Give the address of a server open to the public, or a server's address with its key (`figcraft-server key` prints it)." = "Indica l'indirizzo di un server aperto al pubblico, oppure l'indirizzo di un server con la sua chiave (la stampa `figcraft-server key`)." +"Glass" = "Vetro" +"Go to main component" = "Vai al componente principale" +"Go to {name}" = "Vai a {name}" +"Grab bag" = "Varie" +"Gradient" = "Gradiente" +"Gray" = "Grigio" +"Green" = "Verde" +"Greyscale components and two example screens to sketch a flow with" = "Componenti in scala di grigi e due schermate di esempio con cui abbozzare un flusso" +"Grid" = "Griglia" +"Grid ({size})" = "Griglia ({size})" +"Grid styles" = "Stili di griglia" +"Grid {size}" = "Griglia {size}" +"Group" = "Gruppo" +"Group selection" = "Raggruppa selezione" +"Groups" = "Gruppi" +"Gutter" = "Intercolonna" +"Hand tool" = "Strumento mano" +"Hanging list" = "Elenco sporgente" +"Hanging punctuation" = "Punteggiatura sporgente" +"Hard light" = "Luce intensa" +"Height" = "Altezza" +"Help" = "Aiuto" +"Help and resources" = "Aiuto e risorse" +"Help page" = "Pagina di aiuto" +"Here: {names}" = "Qui: {names}" +"Hidden" = "Nascosto" +"Hide contents" = "Nascondi contenuti" +"Hide from publishing" = "Nascondi dalla pubblicazione" +"Hide section contents" = "Nascondi il contenuto della sezione" +"Hide these tips (Getting started in the Help menu shows them again)" = "Nascondi questi suggerimenti (Per iniziare nel menu Aiuto li mostra di nuovo)" +"Hide when publishing" = "Nascondi alla pubblicazione" +"Highlight layers on hover" = "Evidenzia i livelli al passaggio del mouse" +"Highlights" = "Luci" +"Hold Alt" = "Tieni premuto Alt" +"Hold Alt+Z" = "Tieni premuto Alt+Z" +"Hold Ctrl" = "Tieni premuto Ctrl" +"Hold Z" = "Tieni premuto Z" +"Hold ⌃ or ⌘" = "Tieni premuto ⌃ o ⌘" +"Hold ⌥" = "Tieni premuto ⌥" +"Hold ⌥Z" = "Tieni premuto ⌥Z" +"Home" = "Home" +"Horizontal" = "Orizzontale" +"Horizontal align" = "Allineamento orizzontale" +"Horizontal hex" = "Esagono orizzontale" +"Horizontal padding" = "Margine interno orizzontale" +"Horizontal spacing" = "Spaziatura orizzontale" +"Hosted by {operator}" = "Ospitato da {operator}" +"Hover a layer to see its properties" = "Passa sopra un livello per vederne le proprietà" +"Hover other layers to measure the distance" = "Passa sopra altri livelli per misurare la distanza" +"Hue" = "Tonalità" +"Hug" = "Aderisci" +"Hug contents" = "Aderisci al contenuto" +"Icon components on a 24 px grid with keylines and a size preview" = "Componenti icona su una griglia da 24 px con linee guida e anteprima delle dimensioni" +"Icon set starter" = "Starter per set di icone" +"If it keeps happening, please report it at {link} with this message." = "Se il problema persiste, segnalalo su {link} insieme a questo messaggio." +"If" = "Se" +"If {condition}" = "Se {condition}" +"Ignore auto layout (absolute position)" = "Ignora auto layout (posizione assoluta)" +"Image" = "Immagine" +"Images" = "Immagini" +"Import" = "Importa" +"Import path" = "Percorso di importazione" +"Import plugin from manifest" = "Importa plugin da manifest" +"Import plugin from manifest…" = "Importa plugin da manifest…" +"Import report" = "Resoconto di importazione" +"Import variables" = "Importa variabili" +"Imported document" = "Documento importato" +"Imported {name}" = "Importato {name}" +"Imported {n} new variables, updated {updated}" = { one = "Importata {n} nuova variabile, aggiornate {updated}", other = "Importate {n} nuove variabili, aggiornate {updated}" } +"Import…" = "Importa…" +"In an instance, this comes from the main component" = "In un'istanza, questo proviene dal componente principale" +"In the browser you can join sessions the desktop app hosts and files on a FigCraft server. A page opened over https reaches other computers only through wss:// links (a server behind TLS)." = "Nel browser puoi unirti alle sessioni ospitate dall'app desktop e ai file su un server FigCraft. Una pagina aperta tramite https raggiunge altri computer solo attraverso link wss:// (un server dietro TLS)." +"Include bounding box" = "Includi riquadro di delimitazione" +"Include id attribute" = "Includi attributo id" +"Included" = "Incluso" +"Included in layout" = "Incluso nel layout" +"Individual corners" = "Angoli singoli" +"Individual padding" = "Margini interni singoli" +"Individual strokes" = "Contorni singoli" +"Ink" = "Inchiostro" +"Inner shadow" = "Ombra interna" +"Inside" = "Dentro" +"Inspect" = "Ispeziona" +"Inspect the layer under the pointer: size, colours, typography" = "Ispeziona il livello sotto il puntatore: dimensioni, colori, tipografia" +"Install plugin" = "Installa plugin" +"Install" = "Installa" +"Installed {name}" = "Installato {name}" +"Installed" = "Installato" +"Installing…" = "Installazione…" +"Instance" = "Istanza" +"Instance properties" = "Proprietà dell'istanza" +"Instance swap" = "Scambio di istanza" +"Instance swap (no other component)" = "Scambio di istanza (nessun altro componente)" +"Instances" = "Istanze" +"Instant" = "Istantaneo" +"Intensity" = "Intensità" +"Interaction" = "Interazione" +"Interactions" = "Interazioni" +"Interface design and prototyping. A free, open-source, clean-room design tool written from scratch in Rust, part of the ArtCraft Crafting Apps." = "Progettazione e prototipazione di interfacce. Uno strumento di design gratuito e open source, sviluppato da zero in Rust con metodo clean-room, parte delle ArtCraft Crafting Apps." +"Interface size" = "Dimensione dell'interfaccia" +"Intersect" = "Interseca" +"Intersects" = "Interseca" +"Invalid color" = "Colore non valido" +"Invite link" = "Link di invito" +"Invite link copied" = "Link di invito copiato" +"It asks permission to:" = "Chiede il permesso di:" +"Jan" = "gen" +"Join" = "Unisciti" +"Join code copied" = "Codice di partecipazione copiato" +"Join code or invite link" = "Codice di partecipazione o link di invito" +"Join live session" = "Partecipa alla sessione live" +"Join us on Discord" = "Unisciti a noi su Discord" +"Join with a code" = "Partecipa con un codice" +"Joined" = "Unito" +"Joined {file}" = "Partecipazione a {file}" +"Joining…" = "Partecipazione…" +"Join|stroke" = "Giunzione" +"Jul" = "lug" +"Jun" = "giu" +"Just now" = "Adesso" +"Justify" = "Giustifica" +"Keep appearance" = "Mantieni l'aspetto" +"Keep appearance converts every color value to {profile} so colors look the same. Keep values leaves the numbers as they are, so colors shift." = "\"Mantieni l'aspetto\" converte ogni valore di colore in {profile} così i colori sembrano uguali. \"Mantieni i valori\" lascia i numeri invariati, quindi i colori cambiano." +"Keep tool selected after use" = "Mantieni lo strumento selezionato dopo l'uso" +"Keep values" = "Mantieni i valori" +"Key/Gamepad" = "Tasto/Gamepad" +"Key/Gamepad {key}" = "Tasto/Gamepad {key}" +"Keyboard shortcuts" = "Scorciatoie da tastiera" +"Kits" = "Kit" +"Landing page" = "Landing page" +"Language" = "Lingua" +"Lasso" = "Lazo" +"Last" = "Ultimo" +"Last change: {note}" = "Ultima modifica: {note}" +"Last commit" = "Ultimo commit" +"Last modified" = "Ultima modifica" +"Last on top" = "L'ultimo in alto" +"Layer blur" = "Sfocatura del livello" +"Layer name" = "Nome del livello" +"Layers" = "Livelli" +"Layers in an instance take their position from the main component" = "I livelli in un'istanza prendono la posizione dal componente principale" +"Layout" = "Layout" +"Layout grid" = "Griglia di layout" +"Layout grid settings" = "Impostazioni della griglia di layout" +"Layout grids" = "Griglie di layout" +"Leave session" = "Esci dalla sessione" +"Leaves" = "Foglie" +"Left" = "Sinistra" +"Left & right" = "Sinistra e destra" +"Left padding" = "Margine interno sinistro" +"Left side" = "Lato sinistro" +"Left side width" = "Larghezza del lato sinistro" +"Letter case" = "Maiuscole/minuscole" +"Letter spacing" = "Spaziatura tra lettere" +"Libraries" = "Librerie" +"Libraries in this file" = "Librerie in questo file" +"Library updates available" = "Aggiornamenti delle librerie disponibili" +"Library: click to collapse" = "Libreria: clicca per comprimere" +"Library: click to expand" = "Libreria: clicca per espandere" +"Library: its variables are read-only here; edit them in the library file and publish it" = "Libreria: qui le sue variabili sono di sola lettura; modificale nel file della libreria e pubblicalo" +"Light" = "Chiaro" +"Lighten" = "Schiarisci" +"Light|glass" = "Luce" +"Line" = "Linea" +"Line arrow" = "Freccia lineare" +"Line delta" = "Variazione righe" +"Line height" = "Interlinea" +"Linear" = "Lineare" +"Lines" = "Righe" +"Lines +/−" = "Righe +/−" +"Lines added" = "Righe aggiunte" +"Lines deleted" = "Righe eliminate" +"Link" = "Link" +"Link copied" = "Link copiato" +"Link docs, code or tickets for developers." = "Collega documentazione, codice o ticket per gli sviluppatori." +"Link frames on the Prototype tab, then present the flow" = "Collega i frame nella scheda Prototipo, poi presenta il flusso" +"Link this component to its code component: instances then show your real snippet with their values." = "Collega questo componente al suo componente nel codice: le istanze mostreranno il tuo snippet reale con i loro valori." +"List spacing" = "Spaziatura dell'elenco" +"List style" = "Stile dell'elenco" +"Live session" = "Sessione live" +"Live · {people}" = "Live · {people}" +"Local components" = "Componenti locali" +"Local network" = "Rete locale" +"Local styles" = "Stili locali" +"Local variables" = "Variabili locali" +"Lock/Unlock" = "Blocca/Sblocca" +"Locked" = "Bloccato" +"Lowercase" = "Minuscolo" +"Luminance" = "Luminanza" +"Luminosity" = "Luminosità" +"Main" = "Main" +"Main component deleted" = "Componente principale eliminato" +"Main menu" = "Menu principale" +"Make a component" = "Crea un componente" +"Make slot" = "Crea slot" +"Mar" = "mar" +"Margin" = "Margine" +"Mark all as read" = "Segna tutto come letto" +"Mark as resolved" = "Segna come risolto" +"Mark as unread" = "Segna come non letto" +"Mark as unresolved" = "Segna come non risolto" +"Mask" = "Maschera" +"Mass · Stiffness · Damping" = "Massa · Rigidità · Smorzamento" +"Match (empty: the whole name)" = "Corrispondenza (vuoto: l'intero nome)" +"Max H" = "Alt. max" +"Max W" = "Largh. max" +"Max lines" = "Righe massime" +"Maximize" = "Ingrandisci" +"May" = "mag" +"Measure to the selection" = "Misura fino alla selezione" +"Merge into main" = "Unisci in main" +"Merge regions" = "Unisci regioni" +"Merge the branch into the main file (one undo step) and archive it" = "Unisci il ramo nel file principale (un solo passaggio di annullamento) e archivialo" +"Merged" = "Unito" +"Merged PRs" = "PR uniti" +"Min H" = "Alt. min" +"Min W" = "Largh. min" +"Minimize" = "Riduci a icona" +"Mirror angle" = "Rifletti angolo" +"Mirror angle and length" = "Rifletti angolo e lunghezza" +"Mirroring" = "Riflessione" +"Missing" = "Mancante" +"Missing fonts" = "Font mancanti" +"Missing layer" = "Livello mancante" +"Missing style" = "Stile mancante" +"Missing variable" = "Variabile mancante" +"Miter" = "Spigolo vivo" +"Miter angle" = "Angolo dello spigolo" +"Mixed" = "Misto" +"Mixed fonts" = "Font misti" +"Mixed interactions: click + to replace them" = "Interazioni miste: clicca + per sostituirle" +"Mobile app" = "App mobile" +"Mode" = "Modalità" +"Model" = "Modello" +"Models" = "Modelli" +"Mono" = "Mono" +"More" = "Altro" +"More instance actions" = "Altre azioni per l'istanza" +"Mouse down" = "Pressione del mouse" +"Mouse enter" = "Ingresso del mouse" +"Mouse leave" = "Uscita del mouse" +"Mouse up" = "Rilascio del mouse" +"Move along the toolbar or the layer rows" = "Spostati lungo la barra degli strumenti o le righe dei livelli" +"Move" = "Sposta" +"Move column left" = "Sposta colonna a sinistra" +"Move column right" = "Sposta colonna a destra" +"Move down" = "Sposta giù" +"Move in" = "Sposta dentro" +"Move out" = "Sposta fuori" +"Move tools" = "Strumenti di spostamento" +"Move up" = "Sposta su" +"Moved to {name}" = "Spostato in {name}" +"Multi" = "Multi" +"Multiply" = "Moltiplica" +"Muted" = "Attenuato" +"Name" = "Nome" +"Name (A–Z)" = "Nome (A–Z)" +"Named instance" = "Istanza con nome" +"Navigate to" = "Vai a" +"Nested instance" = "Istanza annidata" +"New design file" = "Nuovo file di design" +"Newest first" = "Prima i più recenti" +"Next or previous control in the focused region" = "Controllo successivo o precedente nell'area attiva" +"Next frame" = "Frame successivo" +"Next ready for dev" = "Prossimo pronto per lo sviluppo" +"Next size option" = "Opzione di dimensione successiva" +"No assets: no export settings, icons or images. Add export settings in the Design tab." = "Nessun asset: nessuna impostazione di esportazione, icona o immagine. Aggiungi le impostazioni di esportazione nella scheda Design." +"No boolean variables fit here." = "Nessuna variabile booleana adatta qui." +"No boolean variables yet. Create them in Local variables." = "Ancora nessuna variabile booleana. Creale in Variabili locali." +"No branches. A branch is a copy of the file to try changes in, then review and merge." = "Nessun ramo. Un ramo è una copia del file in cui provare modifiche, da rivedere e unire poi." +"No category" = "Nessuna categoria" +"No changes" = "Nessuna modifica" +"No color styles" = "Nessuno stile di colore" +"No colour variables fit here." = "Nessuna variabile di colore adatta qui." +"No colour variables yet. Create them in Local variables." = "Ancora nessuna variabile di colore. Creale in Variabili locali." +"No comments yet" = "Ancora nessun commento" +"No compatibility warnings reported" = "Nessun avviso di compatibilità segnalato" +"No components yet. Select layers and press {keys}." = "Ancora nessun componente. Seleziona dei livelli e premi {keys}." +"No conflicts." = "Nessun conflitto." +"No contributor data was built into this copy." = "In questa copia non sono inclusi dati sui collaboratori." +"No device" = "Nessun dispositivo" +"No fonts match" = "Nessun font corrisponde" +"No files or templates match your search" = "Nessun file o modello corrisponde alla ricerca" +"No join code here (the server is older than join codes, or this address can't be put in one): share the link instead." = "Nessun codice di partecipazione qui (il server è più vecchio dei codici di partecipazione, oppure questo indirizzo non può essere inserito in uno): condividi invece il link." +"No layer changes to show." = "Nessuna modifica ai livelli da mostrare." +"No libraries yet. Add a .figcraft file that was published as a library, or paste a team library's link." = "Ancora nessuna libreria. Aggiungi un file .figcraft pubblicato come libreria, oppure incolla il link di una libreria del team." +"No list" = "Nessun elenco" +"No matching comments" = "Nessun commento corrispondente" +"No matching components" = "Nessun componente corrispondente" +"No matching layers" = "Nessun livello corrispondente" +"No matching styles" = "Nessuno stile corrispondente" +"No matching variables" = "Nessuna variabile corrispondente" +"No mirroring" = "Nessuna riflessione" +"No model credits were built into this copy." = "In questa copia non sono inclusi crediti dei modelli." +"No number variables fit here." = "Nessuna variabile numerica adatta qui." +"No number variables yet. Create them in Local variables." = "Ancora nessuna variabile numerica. Creale in Variabili locali." +"No other pages" = "Nessun'altra pagina" +"No paints" = "Nessun riempimento" +"No plugins on {server}." = "Nessun plugin su {server}." +"No plugins yet. Import a development plugin from its figcraft-plugin.json." = "Ancora nessun plugin. Importa un plugin di sviluppo dal suo figcraft-plugin.json." +"No preferred components" = "Nessun componente preferito" +"No preset" = "Nessun preset" +"No saved versions yet. Save a version to mark a point in your work you can come back to." = "Ancora nessuna versione salvata. Salva una versione per segnare un punto del tuo lavoro a cui poter tornare." +"No scrolling" = "Nessuno scorrimento" +"No shortcuts match" = "Nessuna scorciatoia corrisponde" +"No status" = "Nessuno stato" +"No string variables fit here." = "Nessuna variabile stringa adatta qui." +"No string variables yet. Create them in Local variables." = "Ancora nessuna variabile stringa. Creale in Variabili locali." +"No styles yet." = "Ancora nessuno stile." +"No styles yet. Click + to create one from the selection." = "Ancora nessuno stile. Clicca + per crearne uno dalla selezione." +"No templates match your search" = "Nessun modello corrisponde alla ricerca" +"No text styles yet. Click + to create one from the selected text." = "Ancora nessuno stile di testo. Clicca + per crearne uno dal testo selezionato." +"No variables fit here." = "Nessuna variabile adatta qui." +"No variables yet. Create them in Local variables." = "Ancora nessuna variabile. Creale in Variabili locali." +"Nobody else is here yet" = "Non c'è ancora nessun altro" +"Noise" = "Rumore" +"None" = "Nessuno" +"None." = "Nessuno." +"Normal" = "Normale" +"Not bound" = "Non collegato" +"Not in a live session" = "Non in una sessione live" +"Not in this font" = "Non presente in questo font" +"Not properties, kept as written: {names}" = "Non sono proprietà, mantenute come scritte: {names}" +"Not read yet" = "Non ancora letto" +"Not shared" = "Non condiviso" +"Note {n}" = "Nota {n}" +"Nothing changed since this was marked ready for dev." = "Nulla è cambiato da quando è stato segnato come pronto per lo sviluppo." +"Nothing here yet. Set a frame's status to list it." = "Ancora niente qui. Imposta lo stato di un frame per elencarlo." +"Nothing online yet. Use Share on a file to put it online and it is listed here." = "Ancora niente online. Usa Condividi su un file per metterlo online e comparirà qui." +"Nothing to export" = "Niente da esportare" +"Nov" = "nov" +"Now" = "Ora" +"Nudge (Shift: big nudge)" = "Sposta di poco (Maiusc: spostamento grande)" +"Nudge amount" = "Entità dello spostamento" +"Number" = "Numero" +"Number ↑" = "Numero ↑" +"Number ↓" = "Numero ↓" +"Numbered list" = "Elenco numerato" +"Object" = "Oggetto" +"Oct" = "ott" +"Off" = "Disattivato" +"Offered first when filling the slot (any content is allowed)" = "Proposto per primo quando si riempie lo slot (qualsiasi contenuto è ammesso)" +"Offered first when swapping the instance (any component is allowed)" = "Proposto per primo quando si scambia l'istanza (qualsiasi componente è ammesso)" +"Offset" = "Scostamento" +"Oldest first" = "Prima i più vecchi" +"On click" = "Al clic" +"On drag" = "Al trascinamento" +"On this page" = "In questa pagina" +"One announcement in five common sizes: square, portrait, story, link and banner" = "Un annuncio in cinque formati comuni: quadrato, verticale, storia, link e banner" +"Online" = "Online" +"Online files" = "File online" +"Online sharing is off: files are shared on the local network only." = "La condivisione online è disattivata: i file sono condivisi solo sulla rete locale." +"Only the first changed layers are listed." = "Sono elencati solo i primi livelli modificati." +"Only the first {n} changed layers are listed." = "Sono elencati solo i primi {n} livelli modificati." +"Only your threads" = "Solo le tue discussioni" +"Opacity" = "Opacità" +"Opacity & blend" = "Opacità e fusione" +"Opacity 10%…90%, 0: 100%" = "Opacità 10%…90%, 0: 100%" +"Open" = "Apri" +"Open .figcraft, .svg or images" = "Apri .figcraft, .svg o immagini" +"Open branch" = "Apri ramo" +"Open file" = "Apri file" +"Open files" = "Apri file" +"Open in focus view" = "Apri nella vista focus" +"Open in new window" = "Apri in una nuova finestra" +"Open invite links in FigCraft" = "Apri i link di invito in FigCraft" +"Open link" = "Apri link" +"Open link {url}" = "Apri link {url}" +"Open overlay" = "Apri sovrapposizione" +"Open prototype preview" = "Apri anteprima del prototipo" +"Open variables ({count})" = "Variabili aperte ({count})" +"Opened as a new document" = "Aperto come nuovo documento" +"Opened “{name}” in a new tab" = "“{name}” aperto in una nuova scheda" +"Opening the file…" = "Apertura del file…" +"Opening {n} files as new files" = { one = "Apertura come nuovo file", other = "Apertura di {n} file come nuovi file" } +"Open|comments" = "Aperti" +"Options" = "Opzioni" +"Orange" = "Arancione" +"Order" = "Ordine" +"Other apps may not open every .fig file exported here." = "Altre app potrebbero non aprire tutti i file .fig esportati qui." +"Other" = "Altro" +"Others lose access and keep their copy; the file stays on the server for you" = "Gli altri perdono l'accesso e conservano la propria copia; il file resta sul server per te" +"Others type this under Share ▸ Join with a code" = "Gli altri lo digitano in Condividi ▸ Partecipa con un codice" +"Outline stroke" = "Contorna tratto" +"Outline text" = "Contorna testo" +"Outlines" = "Contorni" +"Outside" = "Fuori" +"Overflow" = "Overflow" +"Overlay" = "Sovrapposizione" +"Overlay position" = "Posizione della sovrapposizione" +"Overridden here (the collection it extends has another value)" = "Sovrascritto qui (la collezione che estende ha un altro valore)" +"Overrides" = "Sovrascritture" +"Owner token" = "Token del proprietario" +"Owner token copied" = "Token del proprietario copiato" +"PDF print options" = "Opzioni di stampa PDF" +"PDF print settings" = "Impostazioni di stampa PDF" +"Padding" = "Margine interno" +"Page" = "Pagina" +"Page deleted" = "Pagina eliminata" +"Pages" = "Pagine" +"Pages added to the document" = "Pagine aggiunte al documento" +"Placed on the current page" = "Posizionato nella pagina corrente" +"Pages: {pages}" = "Pagine: {pages}" +"Paint bucket" = "Secchiello" +"Pan the canvas" = "Sposta il canvas" +"Pan with nothing selected (Shift: farther)" = "Sposta la vista senza selezione (Maiusc: più lontano)" +"Panels" = "Pannelli" +"Paper" = "Carta" +"Paragraph indent" = "Rientro del paragrafo" +"Paragraph indent applies to left-aligned text" = "Il rientro del paragrafo si applica al testo allineato a sinistra" +"Paragraph spacing" = "Spaziatura dei paragrafi" +"Pass through" = "Passa attraverso" +"Paste a link (https://…)" = "Incolla un link (https://…)" +"Paste a team library link (a view link)" = "Incolla il link di una libreria del team (un link di visualizzazione)" +"Paste here" = "Incolla qui" +"Paste to replace" = "Incolla per sostituire" +"Pasted design layers; {n} kinds of content were left out" = { one = "Livelli di design incollati; {n} tipo di contenuto è stato escluso", other = "Livelli di design incollati; {n} tipi di contenuto sono stati esclusi" } +"Pattern" = "Motivo" +"Pattern alignment" = "Allineamento del motivo" +"Pattern scale" = "Scala del motivo" +"Pattern source" = "Sorgente del motivo" +"Pen" = "Penna" +"Pencil" = "Matita" +"People in this file" = "Persone in questo file" +"People join from anywhere with a code. The file is kept on {server} while you share it." = "Le persone partecipano da ovunque con un codice. Il file è conservato su {server} finché lo condividi." +"People on your network join with a code. Nothing goes through a server." = "Le persone nella tua rete partecipano con un codice. Niente passa da un server." +"Phone" = "Telefono" +"Pick color" = "Scegli colore" +"Pick color (I)" = "Scegli colore (I)" +"Pick one font family first" = "Scegli prima una famiglia di font" +"Pick the Frame tool, then drag on the canvas or choose a device size" = "Scegli lo strumento Frame, poi trascina sul canvas o scegli le dimensioni di un dispositivo" +"Pin a property" = "Fissa una proprietà" +"Pinch" = "Pizzico" +"Pixel grid" = "Griglia di pixel" +"Pixel preview 1x" = "Anteprima pixel 1x" +"Pixel preview 2x" = "Anteprima pixel 2x" +"Place a shape or frame (its tool chosen)" = "Inserisci una forma o un frame (con il suo strumento scelto)" +"Place automatically" = "Posiziona automaticamente" +"Place image" = "Inserisci immagine" +"Plugin" = "Plugin" +"Plugins run in FigCraft's sandbox, without files, network or clock. Install only plugins you trust." = "I plugin vengono eseguiti nella sandbox di FigCraft, senza file, rete o orologio. Installa solo plugin di cui ti fidi." +"Plugins" = "Plugin" +"Plugins & widgets" = "Plugin e widget" +"Plus darker" = "Più scuro" +"Plus lighter" = "Più chiaro" +"Point" = "Punto" +"Points" = "Punti" +"Polygon" = "Poligono" +"Polygons" = "Poligoni" +"Port" = "Porta" +"Position" = "Posizione" +"Post" = "Invia" +"Preferences" = "Preferenze" +"Preferred" = "Preferito" +"Present" = "Presenta" +"Present or preview" = "Presenta o anteprima" +"Present this flow" = "Presenta questo flusso" +"Presentation" = "Presentazione" +"Presentation slides" = "Slide di presentazione" +"Presenting: comments" = "In presentazione: commenti" +"Presenting: cycle the size options" = "In presentazione: scorri le opzioni di dimensione" +"Presenting: full screen" = "In presentazione: schermo intero" +"Presenting: next frame" = "In presentazione: frame successivo" +"Presenting: previous frame" = "In presentazione: frame precedente" +"Presenting: restart" = "In presentazione: ricomincia" +"Preserve scroll position" = "Mantieni la posizione di scorrimento" +"Presets" = "Preset" +"Press a key…" = "Premi un tasto…" +"Playground" = "Playground" +"Preview" = "Anteprima" +"Preview this flow" = "Anteprima di questo flusso" +"Preview this frame" = "Anteprima di questo frame" +"Previous frame" = "Frame precedente" +"Previous ready for dev" = "Precedente pronto per lo sviluppo" +"Progressive" = "Progressivo" +"Properties" = "Proprietà" +"Prototype" = "Prototipo" +"Prototype preview" = "Anteprima del prototipo" +"Prototype settings" = "Impostazioni del prototipo" +"Publish" = "Pubblica" +"Publish changes" = "Pubblica modifiche" +"Publish to another server…" = "Pubblica su un altro server…" +"Publish to server" = "Pubblica sul server" +"Published version {v}" = "Versione {v} pubblicata" +"Published version {v} to the server" = "Versione {v} pubblicata sul server" +"Published version {v}: save the file so other files can use it" = "Versione {v} pubblicata: salva il file perché altri file possano usarla" +"Published · {people}" = "Pubblicato · {people}" +"Publishing to a server needs the desktop app; in the browser you can join a server's files" = "Per pubblicare su un server serve l'app desktop; nel browser puoi unirti ai file di un server" +"Publishing with the server's key." = "Pubblicazione con la chiave del server." +"Publishing…" = "Pubblicazione…" +"Push" = "Invia" +"Push changes to main component" = "Invia le modifiche al componente principale" +"Put text on path" = "Metti il testo su tracciato" +"R" = "R" +"Radial" = "Radiale" +"Radius" = "Raggio" +"Ratio" = "Rapporto" +"Reading the library…" = "Lettura della libreria…" +"Ready for dev" = "Pronto per lo sviluppo" +"Real name" = "Nome reale" +"Recent" = "Recenti" +"Recent files" = "File recenti" +"Recents" = "Recenti" +"Recovered {n} unsaved files after the app quit unexpectedly: save them to keep them" = { one = "Recuperato un file non salvato dopo la chiusura imprevista dell'app: salvalo per conservarlo", other = "Recuperati {n} file non salvati dopo la chiusura imprevista dell'app: salvali per conservarli" } +"Rectangle" = "Rettangolo" +"Rectangles" = "Rettangoli" +"Red" = "Rosso" +"Reduce motion" = "Riduci animazioni" +"Refraction" = "Rifrazione" +"Regular expression" = "Espressione regolare" +"Relative to your system's display scaling (100% is the system's own size). Document zoom is separate." = "Relativo alla scala dello schermo del sistema (100% è la dimensione del sistema). Lo zoom del documento è separato." +"Remove" = "Rimuovi" +"Remove all" = "Rimuovi tutto" +"Remove annotation" = "Rimuovi annotazione" +"Remove auto layout" = "Rimuovi auto layout" +"Remove dev resource" = "Rimuovi risorsa per sviluppatori" +"Remove export setting" = "Rimuovi impostazione di esportazione" +"Remove from recents" = "Rimuovi dai recenti" +"Remove from server" = "Rimuovi dal server" +"Remove from server…" = "Rimuovi dal server…" +"Remove guide" = "Rimuovi guida" +"Remove interaction" = "Rimuovi interazione" +"Remove library from this file" = "Rimuovi la libreria da questo file" +"Remove min and max" = "Rimuovi minimo e massimo" +"Remove min and max height" = "Rimuovi altezza minima e massima" +"Remove min and max width" = "Rimuovi larghezza minima e massima" +"Remove regions" = "Rimuovi regioni" +"Remove slot" = "Rimuovi slot" +"Remove starting point" = "Rimuovi punto di partenza" +"Remove stop" = "Rimuovi stop" +"Remove text from path" = "Rimuovi il testo dal tracciato" +"Remove the else and its actions" = "Rimuovi \"Altrimenti\" e le sue azioni" +"Remove this condition and its actions" = "Rimuovi questa condizione e le sue azioni" +"Remove this file from the server? It can't be shared or opened from there again. Your copy stays open here." = "Rimuovere questo file dal server? Non potrà più essere condiviso né aperto da lì. La tua copia resta aperta qui." +"Remove {name} from session" = "Rimuovi {name} dalla sessione" +"Remove {name} from {server}?" = "Rimuovere {name} da {server}?" +"Removed" = "Rimosso" +"Removed from the server" = "Rimosso dal server" +"Removed since it was marked" = "Rimosso da quando è stato segnato" +"Rename" = "Rinomina" +"Rename duplicated layers" = "Rinomina i livelli duplicati" +"Rename folder" = "Rinomina cartella" +"Rename group" = "Rinomina gruppo" +"Rename layers" = "Rinomina livelli" +"Rename mode" = "Rinomina modalità" +"Rename on the server" = "Rinomina sul server" +"Rename on the server…" = "Rinomina sul server…" +"Rename page" = "Rinomina pagina" +"Rename to" = "Rinomina in" +"Reorder selection" = "Riordina selezione" +"Replace all" = "Sostituisci tutto" +"Replace mixed export settings" = "Sostituisci le impostazioni di esportazione miste" +"Replace with" = "Sostituisci con" +"Replace with a new interaction" = "Sostituisci con una nuova interazione" +"Reply" = "Rispondi" +"Report" = "Segnala" +"Report this file…" = "Segnala questo file…" +"Reset" = "Ripristina" +"Reset adjustments" = "Ripristina regolazioni" +"Reset all changes" = "Ripristina tutte le modifiche" +"Reset all overrides on this layer" = "Ripristina tutte le sovrascritture su questo livello" +"Reset overrides" = "Ripristina sovrascritture" +"Reset slot" = "Ripristina slot" +"Reset this override" = "Ripristina questa sovrascrittura" +"Reset to style" = "Ripristina allo stile" +"Reset to the inherited value" = "Ripristina al valore ereditato" +"Resize by the nudge amount" = "Ridimensiona dell'entità dello spostamento" +"Resize frames to the device's screen (or the window without a device): their constraints and auto layout respond" = "Ridimensiona i frame allo schermo del dispositivo (o alla finestra senza dispositivo): vincoli e auto layout reagiscono" +"Resize to fit" = "Ridimensiona per adattare" +"Resizing" = "Ridimensionamento" +"Resolve" = "Risolvi" +"Resolve change" = "Risolvi la modifica" +"Resolved" = "Risolto" +"Resources" = "Risorse" +"Responsive" = "Responsive" +"Restart" = "Riavvia" +"Restore" = "Ripristina" +"Restore main component" = "Ripristina componente principale" +"Restore this version" = "Ripristina questa versione" +"Restored “{name}”" = "“{name}” ripristinato" +"Reverse the order" = "Inverti l'ordine" +"Reversed triangle" = "Triangolo rovesciato" +"Review and merge" = "Rivedi e unisci" +"Ribbon" = "Nastro" +"Right" = "Destra" +"Right padding" = "Margine interno destro" +"Right side" = "Lato destro" +"Right side width" = "Larghezza del lato destro" +"Rotate 90°" = "Ruota di 90°" +"Rotate 90° right" = "Ruota di 90° a destra" +"Rotate to landscape" = "Ruota in orizzontale" +"Rotate to portrait" = "Ruota in verticale" +"Rotation" = "Rotazione" +"Rotation jitter" = "Variazione casuale della rotazione" +"Round" = "Arrotondato" +"Rounded" = "Arrotondato" +"Row" = "Riga" +"Row gap" = "Spaziatura righe" +"Row sizes" = "Dimensioni righe" +"Row span" = "Estensione righe" +"Rows" = "Righe" +"Rows {count}" = "Righe {count}" +"Rulers" = "Righelli" +"Run" = "Esegui" +"Run {label}" = "Esegui {label}" +"Save files automatically" = "Salva i file automaticamente" +"Search templates" = "Cerca modelli" +"See all templates" = "Vedi tutti i modelli" +"Select layers and they space themselves as their content changes" = "Seleziona dei livelli e si distanzieranno da soli al variare del contenuto" +"SHA-256: {sha}…" = "SHA-256: {sha}…" +"Same as layer" = "Come il livello" +"Saturation" = "Saturazione" +"Save" = "Salva" +"Save as" = "Salva come" +"Save as preset…" = "Salva come preset…" +"Save version {key}" = "Salva versione {key}" +"Saved" = "Salvato" +"Saved {name}" = "Salvato {name}" +"Say something" = "Scrivi qualcosa" +"Scale" = "Scala" +"Scatter" = "Dispersione" +"Scoping: where pickers offer it" = "Ambito: dove i selettori lo propongono" +"Screen" = "Schermo" +"Scroll behavior" = "Comportamento di scorrimento" +"Scroll to" = "Scorri fino a" +"Scroll to layer" = "Scorri fino al livello" +"Scroll with parent" = "Scorri con il genitore" +"Search plugins" = "Cerca plugin" +"Search" = "Cerca" +"Search actions, layers and tools" = "Cerca azioni, livelli e strumenti" +"Search assets" = "Cerca asset" +"Search comments" = "Cerca commenti" +"Search files and templates" = "Cerca file e modelli" +"Search results" = "Risultati della ricerca" +"Search fonts" = "Cerca font" +"Search layers" = "Cerca livelli" +"Search pages" = "Cerca pagine" +"Search shortcuts" = "Cerca scorciatoie" +"Section" = "Sezione" +"Section color" = "Colore della sezione" +"Sections" = "Sezioni" +"Select a frame and add a starting point to present it as a flow." = "Seleziona un frame e aggiungi un punto di partenza per presentarlo come flusso." +"Select a frame to preview it" = "Seleziona un frame per vederne l'anteprima" +"Select a layer to see its properties and code" = "Seleziona un livello per vederne proprietà e codice" +"Select all" = "Seleziona tutto" +"Select all with same" = "Seleziona tutto con lo stesso" +"Select layer" = "Seleziona livello" +"Select layers to apply the style to" = "Seleziona i livelli a cui applicare lo stile" +"Select layers with this color" = "Seleziona i livelli con questo colore" +"Select overlapping shapes" = "Seleziona forme sovrapposte" +"Select points to edit their position, radius and mirroring" = "Seleziona dei punti per modificarne posizione, raggio e riflessione" +"Select slot" = "Seleziona slot" +"Selection colors" = "Colori della selezione" +"Selection" = "Selezione" +"Selection box, {w} by {h} at {x}, {y}: arrows move it, Shift+arrows or {cmd}+arrows size it, Enter selects, Escape cancels" = "Riquadro di selezione, {w} per {h} a {x}, {y}: le frecce lo spostano, Maiusc+frecce o {cmd}+frecce lo ridimensionano, Invio seleziona, Esc annulla" +"Selection box: arrows move it, Shift+arrows or {cmd}+arrows size it, Enter selects, Escape cancels" = "Riquadro di selezione: le frecce lo spostano, Maiusc+frecce o {cmd}+frecce lo ridimensionano, Invio seleziona, Esc annulla" +"Send backward" = "Porta indietro" +"Send report" = "Invia segnalazione" +"Send to back" = "Porta in fondo" +"Sep" = "set" +"Server address (ws://host:47900/)" = "Indirizzo del server (ws://host:47900/)" +"Server address" = "Indirizzo del server" +"Server for online sharing" = "Server per la condivisione online" +"Set as default" = "Imposta come predefinito" +"Set variable" = "Imposta variabile" +"Set variable mode" = "Imposta modalità della variabile" +"Set variable {name}" = "Imposta variabile {name}" +"Set {name} to {value}" = "Imposta {name} su {value}" +"Shadows" = "Ombre" +"Shape" = "Forma" +"Shape builder" = "Generatore di forme" +"Shape tools" = "Strumenti forma" +"Share" = "Condividi" +"Share this file" = "Condividi questo file" +"Share with a code" = "Condividi con un codice" +"Share: live session, export" = "Condividi: sessione live, esportazione" +"Shared" = "Condiviso" +"Shared: copy its code from the file's menu" = "Condiviso: copia il suo codice dal menu del file" +"Sharing a file needs the desktop app; here you can join one." = "Per condividere un file serve l'app desktop; qui puoi partecipare a uno." +"Sharing on the local network (online sharing is off: no server is set in Preferences)." = "Condivisione sulla rete locale (la condivisione online è disattivata: nelle Preferenze non è impostato alcun server)." +"Sharing on your network · {people}" = "Condivisione sulla tua rete · {people}" +"Sharing online · {people}" = "Condivisione online · {people}" +"Sharing stopped: the file stays on the server, open only to you" = "Condivisione interrotta: il file resta sul server, aperto solo a te" +"Show" = "Mostra" +"Show all templates" = "Mostra tutti i modelli" +"Show as grid" = "Mostra come griglia" +"Show as list" = "Mostra come elenco" +"Show autosaves" = "Mostra salvataggi automatici" +"Show behind transparent areas" = "Mostra dietro le aree trasparenti" +"Show comments (Shift+C)" = "Mostra commenti (Maiusc+C)" +"Show device frame" = "Mostra cornice del dispositivo" +"Show dimensions on objects" = "Mostra dimensioni sugli oggetti" +"Show hotspot hints" = "Mostra suggerimenti sulle aree interattive" +"Show in Explorer" = "Mostra in Esplora risorse" +"Show in Finder" = "Mostra nel Finder" +"Show in folder" = "Mostra nella cartella" +"Show only this on the canvas" = "Mostra solo questo sul canvas" +"Show open" = "Mostra aperti" +"Show section contents" = "Mostra il contenuto della sezione" +"Show this layer" = "Mostra questo livello" +"Show usage" = "Mostra utilizzo" +"Show/Hide" = "Mostra/Nascondi" +"Show/Hide UI" = "Mostra/Nascondi interfaccia" +"Side by side" = "Affiancati" +"Sidebar" = "Barra laterale" +"Simplify stroke" = "Semplifica tratto" +"Size" = "Dimensione" +"Size jitter" = "Variazione casuale della dimensione" +"Skip ink" = "Evita i discendenti" +"Slashed zero" = "Zero barrato" +"Slice" = "Slice" +"Slices" = "Slice" +"Slide in" = "Scorrimento in entrata" +"Slide out" = "Scorrimento in uscita" +"Slot" = "Slot" +"Slot content" = "Contenuto dello slot" +"Small capitals" = "Maiuscoletto" +"Small caps" = "Maiuscoletto" +"Smart animate" = "Animazione intelligente" +"Snap to pixel grid" = "Aggancia alla griglia di pixel" +"Social and slides" = "Social e slide" +"Social media" = "Social media" +"Social post sizes" = "Dimensioni dei post social" +"Soft light" = "Luce soffusa" +"Solid" = "Pieno" +"Some variants have the same property values: give each one a unique combination" = "Alcune varianti hanno gli stessi valori di proprietà: assegna a ciascuna una combinazione unica" +"Sort" = "Ordina" +"Sort by" = "Ordina per" +"Sort by date" = "Ordina per data" +"Sort by page" = "Ordina per pagina" +"Sort by {column}" = "Ordina per {column}" +"Space around" = "Spazio attorno" +"Space between" = "Spazio tra" +"Space drag" = "Trascinamento con Spazio" +"Space evenly" = "Spazio uniforme" +"Space+drag" = "Spazio+trascina" +"Spacing" = "Spaziatura" +"Spam, stolen work, abuse…" = "Spam, lavoro rubato, abusi…" +"Splay" = "Svasatura" +"Spotlight me" = "Metti me in evidenza" +"Spread" = "Diffusione" +"Square" = "Quadrato" +"Standard" = "Standard" +"Standard ligatures" = "Legature standard" +"Star" = "Stella" +"Stars" = "Stelle" +"Start" = "Inizio" +"Start ascending from / stop descending at" = "Inizia a salire da / smetti di scendere a" +"Start from a blank canvas" = "Inizia da un canvas vuoto" +"Start from a template" = "Inizia da un modello" +"Start on path" = "Inizio sul tracciato" +"Start on the Home screen" = "Avvia dalla schermata Home" +"Start point" = "Punto iniziale" +"Start sharing" = "Inizia a condividere" +"Start sharing with the Share button, then pass on the code it gives" = "Inizia a condividere con il pulsante Condividi, poi passa il codice che ti dà" +"Starting to share…" = "Avvio della condivisione…" +"Status" = "Stato" +"Sticky" = "Sticky" +"Stop" = "Interrompi" +"Stop following {name}" = "Smetti di seguire {name}" +"Stop session" = "Termina sessione" +"Stop sharing" = "Interrompi condivisione" +"Stop spotlighting" = "Smetti di mettere in evidenza" +"Stop {n} {hex}" = "Stop {n} {hex}" +"Stretch" = "Allunga" +"Strikethrough" = "Barrato" +"String" = "Stringa" +"Stroke" = "Contorno" +"Stroke style" = "Stile del contorno" +"Stroke weight" = "Spessore del contorno" +"Strokes" = "Contorni" +"Style" = "Stile" +"Styles" = "Stili" +"Stylistic set 1" = "Set stilistico 1" +"Stylistic set 2" = "Set stilistico 2" +"Stylistic set 3" = "Set stilistico 3" +"Stylistic set 4" = "Set stilistico 4" +"Stylistic set 5" = "Set stilistico 5" +"Subtract" = "Sottrai" +"Subtracts" = "Sottrae" +"Suffix" = "Suffisso" +"Suspend snapping while dragging" = "Sospendi l'aggancio durante il trascinamento" +"Swap instance" = "Scambia istanza" +"Swap library: use another library's assets of the same names" = "Scambia libreria: usa gli asset con gli stessi nomi di un'altra libreria" +"Swap orientation" = "Scambia orientamento" +"Swap overlay" = "Scambia sovrapposizione" +"Swap to {name}" = "Scambia con {name}" +"Swap width and height" = "Scambia larghezza e altezza" +"Swapped instance" = "Istanza scambiata" +"Swapped {n} assets" = { one = "Scambiato {n} asset", other = "Scambiati {n} asset" } +"Swapped {n} assets; {missed} had no match and still use the old library" = { one = "Scambiato {n} asset; mancate corrispondenze: {missed}, che usano ancora la vecchia libreria", other = "Scambiati {n} asset; mancate corrispondenze: {missed}, che usano ancora la vecchia libreria" } +"Sweep" = "Ampiezza" +"System" = "Sistema" +"Tab / Shift+Tab" = "Tab / Maiusc+Tab" +"Table" = "Tabella" +"Tablet" = "Tablet" +"Tabular numbers" = "Numeri tabulari" +"Take the values from the first selected layer" = "Prendi i valori dal primo livello selezionato" +"Taper" = "Rastremazione" +"Taper both ends" = "Rastrema entrambe le estremità" +"Tell the server's operator what is wrong with it" = "Spiega a chi gestisce il server cosa c'è che non va" +"Temperature" = "Temperatura" +"Template" = "Modello" +"Templates" = "Modelli" +"Terms" = "Termini" +"Text" = "Testo" +"Text align" = "Allineamento del testo" +"Text content" = "Contenuto del testo" +"Text style" = "Stile di testo" +"Text styles" = "Stili di testo" +"Texts" = "Testi" +"Texture" = "Texture" +"The canvas previews colors in sRGB; exported PNG, JPG and WebP files are tagged Display P3." = "Il canvas mostra i colori in sRGB; i file PNG, JPG e WebP esportati sono contrassegnati come Display P3." +"The destination document is unavailable. Nothing was imported." = "Il documento di destinazione non è disponibile. Non è stato importato nulla." +"The destination document was closed. Nothing was imported." = "Il documento di destinazione è stato chiuso. Non è stato importato nulla." +"The destination page was removed. Nothing was imported." = "La pagina di destinazione è stata rimossa. Non è stato importato nulla." +"The file is deleted from the server: nobody can open or join it from there again, and this can't be undone. Copies that are open stay with whoever has them." = "Il file viene eliminato dal server: nessuno potrà più aprirlo o parteciparvi da lì, e l'operazione non può essere annullata. Le copie aperte restano a chi le ha." +"The file now holds another library" = "Il file contiene ora un'altra libreria" +"The file now holds another library (\"{name}\")" = "Il file contiene ora un'altra libreria (\"{name}\")" +"The files you shared or published online from this computer. They stay on their server until you remove them." = "I file che hai condiviso o pubblicato online da questo computer. Restano sul loro server finché non li rimuovi." +"The link carries the session's secret: share it only with people you invite." = "Il link contiene il segreto della sessione: condividilo solo con le persone che inviti." +"The name in your online files and on the server. The document inside keeps its own name." = "Il nome nei tuoi file online e sul server. Il documento al suo interno mantiene il proprio nome." +"The selection uses several text styles: click to apply one" = "La selezione usa più stili di testo: clicca per applicarne uno" +"The server deletes this file if nobody opens it before {date}." = "Il server elimina questo file se nessuno lo apre prima del {date}." +"The whole file" = "L'intero file" +"The window is too small for {size}%, so the interface is drawn at {drawn}%. Make the window larger to use {size}%." = "La finestra è troppo piccola per {size}%, quindi l'interfaccia è disegnata al {drawn}%. Ingrandisci la finestra per usare {size}%." +"Theme" = "Tema" +"There's no destination for that action" = "Non c'è nessuna destinazione per quell'azione" +"Thickness" = "Spessore" +"This branch can't be reviewed: {e}" = "Questo ramo non può essere rivisto: {e}" +"This branch was merged or archived." = "Questo ramo è stato unito o archiviato." +"This computer only" = "Solo questo computer" +"This computer remembers the owner token, so the file can be managed from the home screen's Online list. Copy it to manage the file from somewhere else: it is a password for this file and won't be shown again." = "Questo computer ricorda il token del proprietario, quindi il file può essere gestito dall'elenco Online della schermata Home. Copialo per gestire il file da un altro posto: è una password per questo file e non verrà mostrato di nuovo." +"This condition is edited as an expression" = "Questa condizione viene modificata come espressione" +"This file" = "Questo file" +"This file has unsaved changes" = "Questo file ha modifiche non salvate" +"This file is too large to join here (up to {mb} MB)" = "Questo file è troppo grande per parteciparvi da qui (fino a {mb} MB)" +"This file uses fonts that aren't installed on this computer. Their text is drawn in a fallback font until you install them." = "Questo file usa font che non sono installati su questo computer. Il loro testo è disegnato con un font di riserva finché non li installi." +"This file is on {server}, open only to you. Share it to let others in with a new code." = "Questo file è su {server}, aperto solo a te. Condividilo per far entrare altri con un nuovo codice." +"This frame is new on the branch." = "Questo frame è nuovo nel ramo." +"This frame isn't on the branch." = "Questo frame non è nel ramo." +"This server is open to the public: no key needed." = "Questo server è aperto al pubblico: non serve nessuna chiave." +"This server needs its key: add ?key=… to the address (`figcraft-server key` prints it on the server's machine)." = "Questo server richiede la sua chiave: aggiungi ?key=… all'indirizzo (`figcraft-server key` la stampa sul computer del server)." +"This server takes files only with its key: see Advanced." = "Questo server accetta file solo con la sua chiave: vedi Avanzate." +"Tidy up" = "Riordina" +"Tile" = "Affianca" +"Tile type" = "Tipo di affiancamento" +"Tint" = "Tinta" +"Title" = "Titolo" +"Title case" = "Iniziali maiuscole" +"To share this library with your team, publish the file to a server (Share ▸ Publish to server…), then publish the library there." = "Per condividere questa libreria con il tuo team, pubblica il file su un server (Condividi ▸ Pubblica sul server…), poi pubblica lì la libreria." +"Toggle" = "Attiva/Disattiva" +"Toggle light/dark theme" = "Passa dal tema chiaro a quello scuro" +"Toggle visibility" = "Attiva/Disattiva visibilità" +"Tools" = "Strumenti" +"Top" = "In alto" +"Top & bottom" = "In alto e in basso" +"Top left radius" = "Raggio in alto a sinistra" +"Top padding" = "Margine interno superiore" +"Top right radius" = "Raggio in alto a destra" +"Transform" = "Trasforma" +"Triangle arrow" = "Freccia triangolare" +"Trigger" = "Trigger" +"True" = "Vero" +"Truncate text" = "Tronca testo" +"Truncate with an ellipsis" = "Tronca con i puntini di sospensione" +"Try again" = "Riprova" +"Try another search or filter." = "Prova un'altra ricerca o un altro filtro." +"Try another search, or browse templates." = "Prova un'altra ricerca, oppure sfoglia i modelli." +"Try another word, or look at every kind." = "Prova un'altra parola, oppure guarda tutti i tipi." +"Turn a layer into a component, reuse it, and change every copy at once" = "Trasforma un livello in componente, riutilizzalo e cambia tutte le copie in una volta" +"Two digits" = "Due cifre" +"Type" = "Tipo" +"Type settings" = "Impostazioni tipografiche" +"Type the join code you were given, or paste an invite link." = "Digita il codice di partecipazione che ti è stato dato, oppure incolla un link di invito." +"Typography" = "Tipografia" +"Underline" = "Sottolineato" +"Undo takes back only your own changes; what others changed since stays." = "Annulla ripristina solo le tue modifiche; quelle fatte da altri nel frattempo restano." +"Ungroup" = "Separa" +"Uniform" = "Uniforme" +"Union" = "Unione" +"Unions" = "Unioni" +"Unknown — treated as sRGB" = "Sconosciuto — trattato come sRGB" +"Unread" = "Non letto" +"Unresolve" = "Riapri" +"Unsaved changes" = "Modifiche non salvate" +"Unsaved changes: save first ({save}), or close the tab with {alt}-click to discard" = "Modifiche non salvate: salva prima ({save}), oppure chiudi la scheda con {alt}+clic per scartarle" +"Unsaved changes: save first, or close again with {key} to discard" = "Modifiche non salvate: salva prima, oppure chiudi di nuovo con {key} per scartarle" +"Up" = "Su" +"Update plugin" = "Aggiorna plugin" +"Update" = "Aggiorna" +"Update from main" = "Aggiorna da main" +"Update from selection" = "Aggiorna dalla selezione" +"Updated {components} and {styles}" = "Aggiornati {components} e {styles}" +"Updated {name}" = "Aggiornato {name}" +"Updating the graphics driver often helps. To use OpenGL only, start FigCraft from a Command Prompt with:" = "Aggiornare il driver grafico spesso aiuta. Per usare solo OpenGL, avvia FigCraft dal Prompt dei comandi con:" +"Updating the graphics driver often helps. WGPU_BACKEND (vulkan, gl, metal) names another graphics API to try." = "Aggiornare il driver grafico spesso aiuta. WGPU_BACKEND (vulkan, gl, metal) indica un'altra API grafica da provare." +"Uploads this file to a FigCraft server and keeps this tab in sync with it. You get a link to edit and a link to view." = "Carica questo file su un server FigCraft e mantiene questa scheda sincronizzata con esso. Ricevi un link per modificare e un link per visualizzare." +"Uppercase" = "Maiuscolo" +"Use a fixed value" = "Usa un valore fisso" +"Use an expression, e.g. count + 1" = "Usa un'espressione, ad es. count + 1" +"Use as mask" = "Usa come maschera" +"Use auto layout" = "Usa auto layout" +"Use number keys for opacity" = "Usa i tasti numerici per l'opacità" +"Use the .figcraft or .fig extension." = "Usa l'estensione .figcraft o .fig." +"Use the layer's fill" = "Usa il riempimento del livello" +"Use your browser's zoom to change the interface size." = "Usa lo zoom del browser per cambiare la dimensione dell'interfaccia." +"Used in {files} · {instances}" = "Usato in {files} · {instances}" +"Username" = "Nome utente" +"Value" = "Valore" +"Uses variables: each layer shows them in its own mode" = "Usa variabili: ogni livello le mostra nella propria modalità" +"Variable" = "Variabile" +"Variable collections" = "Collezioni di variabili" +"Variable modes" = "Modalità delle variabili" +"Variable width" = "Larghezza variabile" +"Variables" = "Variabili" +"Variables JSON…" = "JSON delle variabili…" +"Variables store reusable values (colours, numbers, strings, booleans) with modes such as Light and Dark." = "Le variabili memorizzano valori riutilizzabili (colori, numeri, stringhe, booleani) con modalità come Chiaro e Scuro." +"Variant" = "Variante" +"Vector" = "Vettore" +"Vectors" = "Vettori" +"Version" = "Versione" +"Version history" = "Cronologia versioni" +"Version {version}" = "Versione {version}" +"Version {v} · {components} · {styles} · {variables}" = "Versione {v} · {components} · {styles} · {variables}" +"Vertical" = "Verticale" +"Vertical align" = "Allineamento verticale" +"Vertical hex" = "Esagono verticale" +"Vertical padding" = "Margine interno verticale" +"Vertical spacing" = "Spaziatura verticale" +"Vertical trim" = "Rifilatura verticale" +"View" = "Vista" +"View link" = "Link di visualizzazione" +"View link copied" = "Link di visualizzazione copiato" +"View only" = "Sola visualizzazione" +"View only: you see every change as it happens but can't edit. Leave to keep a copy you can edit." = "Sola visualizzazione: vedi ogni modifica mentre avviene ma non puoi modificare. Esci per conservare una copia modificabile." +"Viewing “{name}”" = "Visualizzazione di “{name}”" +"Violet" = "Viola" +"Visibility" = "Visibilità" +"Visible" = "Visibile" +"Vivid" = "Vivido" +"WGPU_BACKEND is set and names the graphics API to use; unsetting it lets FigCraft choose." = "WGPU_BACKEND è impostata e indica l'API grafica da usare; rimuovendola FigCraft sceglie da sé." +"Warm" = "Caldo" +"Watch" = "Osserva" +"Wavy" = "Ondulato" +"Web" = "Web" +"Websites" = "Siti web" +"WebSocket URL" = "URL WebSocket" +"WebSocket URL copied" = "URL WebSocket copiato" +"Wedge" = "Spicchio" +"Weight" = "Peso" +"What you try here changes this preview and the code below, not the file" = "Ciò che provi qui cambia questa anteprima e il codice sotto, non il file" +"What changed since it was marked ready for dev" = "Cosa è cambiato da quando è stato segnato come pronto per lo sviluppo" +"What is wrong with this file? The server's operator gets your report." = "Cosa c'è che non va in questo file? Chi gestisce il server riceve la tua segnalazione." +"What it's for" = "A cosa serve" +"What this changed from isn't known: it changed before FigCraft kept a copy of ready-for-dev frames. Resolve the change (or mark it ready again) to compare from now on." = "Non si sa da cosa sia cambiato: è cambiato prima che FigCraft conservasse una copia dei frame pronti per lo sviluppo. Risolvi la modifica (o segnalo di nuovo come pronto) per confrontare da ora in poi." +"While hovering" = "Durante il passaggio del mouse" +"While pressing" = "Durante la pressione" +"White" = "Bianco" +"Who runs it, its terms and how long it keeps files" = "Chi lo gestisce, i suoi termini e per quanto tempo conserva i file" +"Why it changed" = "Perché è cambiato" +"Widget" = "Widget" +"Widgets" = "Widget" +"Width" = "Larghezza" +"Width point" = "Punto di larghezza" +"Width profile" = "Profilo di larghezza" +"Wireframe kit" = "Kit wireframe" +"With the layers' export settings" = "Con le impostazioni di esportazione dei livelli" +"Wrap" = "A capo" +"Wrap in new section" = "Racchiudi in una nuova sezione" +"Yellow" = "Giallo" +"Yesterday" = "Ieri" +"You" = "Tu" +"You'll get the shared file as a new tab and edit it together." = "Riceverai il file condiviso in una nuova scheda e lo modificherete insieme." +"You're spotlighting: everyone follows your view" = "Stai mettendo in evidenza: tutti seguono la tua vista" +"Your copy of the file stays open" = "La tua copia del file resta aperta" +"Your copy stays open; the file stays on the server" = "La tua copia resta aperta; il file resta sul server" +"Your name" = "Il tuo nome" +"Your name in comments" = "Il tuo nome nei commenti" +"Your system asks for reduced motion: animations stay off." = "Il tuo sistema richiede movimento ridotto: le animazioni restano disattivate." +"Z" = "Z" +"Z to A" = "Dalla Z alla A" +"Zoom" = "Zoom" +"Zoom around the pointer" = "Zoom attorno al puntatore" +"Zoom device to fill window" = "Ingrandisci il dispositivo per riempire la finestra" +"Zoom in" = "Ingrandisci" +"Zoom out" = "Riduci" +"Zoom to 100%" = "Zoom al 100%" +"Zoom to 200%" = "Zoom al 200%" +"Zoom to 50%" = "Zoom al 50%" +"Zoom to fit" = "Zoom per adattare" +"Zoom to selection" = "Zoom sulla selezione" +"Zoom tool: click to zoom in, drag an area" = "Strumento zoom: clicca per ingrandire, trascina un'area" +"Zoom tool: zoom out" = "Strumento zoom: riduci" +"and {n} more" = { one = "e altro {n}", other = "e altri {n}" } +"bleed marks" = "crocini di abbondanza" +"by {author}" = "di {author}" +"call any command its access allows" = "chiama qualsiasi comando consentito dal suo accesso" +"call only: {commands}" = "chiama solo: {commands}" +"can edit the document" = "può modificare il documento" +"conditional prototype actions nested too deeply were skipped" = "le azioni condizionali del prototipo annidate troppo in profondità sono state saltate" +"conditional prototype actions with conditions that can't be evaluated were skipped" = "le azioni condizionali del prototipo con condizioni non valutabili sono state saltate" +"connector label background was skipped: style data is missing" = "lo sfondo dell'etichetta del connettore è stato saltato: mancano i dati di stile" +"connector label was skipped: background bounds are invalid" = "l'etichetta del connettore è stata saltata: i limiti dello sfondo non sono validi" +"connector label was skipped: background transform is invalid" = "l'etichetta del connettore è stata saltata: la trasformazione dello sfondo non è valida" +"connector label was skipped: composed text transform is invalid" = "l'etichetta del connettore è stata saltata: la trasformazione del testo composto non è valida" +"connector label was skipped: derived background bounds are missing" = "l'etichetta del connettore è stata saltata: mancano i limiti dello sfondo derivati" +"connector label was skipped: derived text bounds are missing" = "l'etichetta del connettore è stata saltata: mancano i limiti del testo derivati" +"connector label was skipped: text bounds are invalid" = "l'etichetta del connettore è stata saltata: i limiti del testo non sono validi" +"connector label was skipped: text style data is missing" = "l'etichetta del connettore è stata saltata: mancano i dati di stile del testo" +"connector label was skipped: text transform is invalid" = "l'etichetta del connettore è stata saltata: la trasformazione del testo non è valida" +"connector labels were skipped" = "le etichette dei connettori sono state saltate" +"connector layers import as static frame/vector snapshots with labels when present; live endpoint bindings are not retained" = "i livelli connettore vengono importati come istantanee statiche di frame/vettori con le etichette, se presenti; i collegamenti dinamici alle estremità non vengono mantenuti" +"connector snapshot vector data is missing; connector was left empty" = "mancano i dati vettoriali dell'istantanea del connettore; il connettore è stato lasciato vuoto" +"connectors whose ends can't be placed are empty" = "i connettori le cui estremità non possono essere posizionate sono vuoti" +"crop marks" = "crocini di taglio" +"damaged vector geometry was skipped" = "la geometria vettoriale danneggiata è stata saltata" +"damaged vector networks import as outlines" = "le reti vettoriali danneggiate vengono importate come contorni" +"development: {path}" = "sviluppo: {path}" +"document children other than pages were skipped" = "i figli del documento diversi dalle pagine sono stati saltati" +"duplicate grid track sizing entries use equal tracks" = "le voci duplicate di dimensionamento delle tracce della griglia usano tracce uguali" +"e.g. count + 1" = "ad es. count + 1" +"e.g. count >= 3 and not done" = "ad es. count >= 3 and not done" +"elbowed and curved connectors without a saved path import as straight lines" = "i connettori ad angolo e curvi senza un tracciato salvato vengono importati come linee rette" +"entries without a node type were skipped" = "le voci senza tipo di nodo sono state saltate" +"figcraft:// links from a browser, chat or email open this app and join the session." = "i link figcraft:// da un browser, una chat o un'email aprono questa app e partecipano alla sessione." +"filled areas of a vector with their own fill use the layer's fill" = "le aree riempite di un vettore con riempimento proprio usano il riempimento del livello" +"from {server}" = "da {server}" +"gamepad triggers without exactly one known controller button were skipped" = "i trigger del gamepad senza esattamente un pulsante noto del controller sono stati saltati" +"grid track list exceeds 1000 entries; the tracks use the default equal sizing" = "l'elenco delle tracce della griglia supera 1000 voci; le tracce usano il dimensionamento uniforme predefinito" +"grid track sizing is unsupported or malformed; affected tracks use equal sizing" = "il dimensionamento delle tracce della griglia non è supportato o è malformato; le tracce interessate usano dimensioni uguali" +"grid track sizing list exceeds 1000 entries; the tracks use the default equal sizing" = "l'elenco di dimensionamento delle tracce della griglia supera 1000 voci; le tracce usano il dimensionamento uniforme predefinito" +"hidden" = "nascosto" +"iOS" = "iOS" +"image filters are ambiguous because both filter containers are present; filter conversion was skipped" = "i filtri immagine sono ambigui perché sono presenti entrambi i contenitori di filtri; la conversione dei filtri è stata saltata" +"image filters use FigCraft's approximate renderer" = "i filtri immagine usano il renderer approssimato di FigCraft" +"instance swap properties whose default component isn't in the file were skipped" = "le proprietà di scambio istanza il cui componente predefinito non è nel file sono state saltate" +"instances inside their own main component were imported as frames" = "le istanze all'interno del proprio componente principale sono state importate come frame" +"instances of components that aren't in the file show no layers" = "le istanze di componenti non presenti nel file non mostrano livelli" +"instances of components that aren't in the file were imported as frames" = "le istanze di componenti non presenti nel file sono state importate come frame" +"instances that would nest a component inside itself were imported as frames" = "le istanze che anniderebbero un componente dentro se stesso sono state importate come frame" +"instances without a main component were imported as empty frames" = "le istanze senza componente principale sono state importate come frame vuoti" +"invalid internal .fig text path data was ignored" = "i dati interni non validi del tracciato di testo .fig sono stati ignorati" +"invalid or oversized vector networks import as outlines" = "le reti vettoriali non valide o troppo grandi vengono importate come contorni" +"invalid or oversized vector path data was skipped" = "i dati di tracciato vettoriale non validi o troppo grandi sono stati saltati" +"just now" = "adesso" +"key triggers without a supported key were skipped" = "i trigger da tasto senza un tasto supportato sono stati saltati" +"key up triggers import as key presses" = "i trigger di rilascio del tasto vengono importati come pressioni del tasto" +"layers without a position were placed at their parent's origin" = "i livelli senza posizione sono stati posti all'origine del loro genitore" +"layers without a type were skipped" = "i livelli senza tipo sono stati saltati" +"legacy image filter container is unsupported; filter conversion was skipped" = "il contenitore di filtri immagine legacy non è supportato; la conversione dei filtri è stata saltata" +"locked" = "bloccato" +"malformed image filters container was skipped" = "il contenitore di filtri immagine malformato è stato saltato" +"malformed image paintFilter container was skipped" = "il contenitore paintFilter dell'immagine malformato è stato saltato" +"malformed slot content assignment was skipped" = "l'assegnazione di contenuto dello slot malformata è stata saltata" +"no bleed" = "nessuna abbondanza" +"nobody else here yet" = "ancora nessun altro qui" +"non-default slot settings are not represented and were skipped" = "le impostazioni dello slot non predefinite non sono rappresentate e sono state saltate" +"now" = "ora" +"only in a text style or variable" = "solo in uno stile di testo o in una variabile" +"or" = "oppure" +"point stroke joins were preserved; dashed or variable-width strokes render with the layer join" = "le giunzioni dei punti del contorno sono state mantenute; i contorni tratteggiati o di larghezza variabile vengono resi con la giunzione del livello" +"preferred slot component keys cannot be resolved to file nodes and were skipped" = "le chiavi dei componenti preferiti dello slot non possono essere risolte in nodi del file e sono state saltate" +"prototype actions of unknown types were skipped" = "le azioni del prototipo di tipo sconosciuto sono state saltate" +"prototype actions without a destination in the file were skipped" = "le azioni del prototipo senza una destinazione nel file sono state saltate" +"prototype expressions on variables that aren't in the variables JSON were skipped" = "le espressioni del prototipo su variabili non presenti nel JSON delle variabili sono state saltate" +"prototype expressions with unsupported functions were skipped" = "le espressioni del prototipo con funzioni non supportate sono state saltate" +"prototype interactions without supported actions were skipped" = "le interazioni del prototipo senza azioni supportate sono state saltate" +"prototype navigations of unknown types were skipped" = "le navigazioni del prototipo di tipo sconosciuto sono state saltate" +"prototype transitions of unknown types play instantly" = "le transizioni del prototipo di tipo sconosciuto vengono eseguite istantaneamente" +"prototype triggers of unknown types were skipped" = "i trigger del prototipo di tipo sconosciuto sono stati saltati" +"put a widget on the canvas" = "inserire un widget sul canvas" +"read and edit the document" = "leggere e modificare il documento" +"read the document" = "leggere il documento" +"reads the document" = "legge il documento" +"set-variable actions on variables that aren't in the variables JSON were skipped" = "le azioni \"imposta variabile\" su variabili non presenti nel JSON delle variabili sono state saltate" +"set-variable actions with values that can't be evaluated were skipped" = "le azioni \"imposta variabile\" con valori non valutabili sono state saltate" +"set-variable actions with values that don't fit the variable were skipped" = "le azioni \"imposta variabile\" con valori non adatti alla variabile sono state saltate" +"set-variable actions without a value were skipped" = "le azioni \"imposta variabile\" senza valore sono state saltate" +"set-variable-mode actions on collections or modes that aren't in the variables JSON were skipped" = "le azioni \"imposta modalità variabile\" su collezioni o modalità non presenti nel JSON delle variabili sono state saltate" +"slot content assignment target is missing or is not slot content" = "la destinazione dell'assegnazione del contenuto dello slot manca o non è un contenuto di slot" +"slot content assignment uses the empty GUID sentinel and was skipped" = "l'assegnazione del contenuto dello slot usa il GUID sentinella vuoto ed è stata saltata" +"slot layers without a matching slot property were imported as frames" = "i livelli slot senza una proprietà slot corrispondente sono stati importati come frame" +"some vector points refer to point settings missing from the file; they use the layer's joins and plain corners" = "alcuni punti vettoriali fanno riferimento a impostazioni di punto mancanti nel file; usano le giunzioni del livello e angoli semplici" +"some vector segments refer to style settings missing from the file; they use the layer's stroke" = "alcuni segmenti vettoriali fanno riferimento a impostazioni di stile mancanti nel file; usano il contorno del livello" +"stale or invalid grid track sizing entries were ignored" = "le voci di dimensionamento delle tracce della griglia obsolete o non valide sono state ignorate" +"stars and polygons without point counts or geometry use the default point count" = "stelle e poligoni senza numero di punte o geometria usano il numero di punte predefinito" +"style settings of some vector segments aren't supported; those segments use the layer's stroke" = "le impostazioni di stile di alcuni segmenti vettoriali non sono supportate; quei segmenti usano il contorno del livello" +"text path underline or strikethrough decoration is not rendered" = "la sottolineatura o il barrato del testo su tracciato non viene reso" +"text style overrides refer to missing table entries" = "le sovrascritture dello stile di testo fanno riferimento a voci di tabella mancanti" +"the file has no pages" = "il file non ha pagine" +"the plugin" = "il plugin" +"the variables JSON has no `variableCollections` (expected the local variables response) and was ignored" = "il JSON delle variabili non ha `variableCollections` (era attesa la risposta delle variabili locali) ed è stato ignorato" +"this session only" = "solo questa sessione" +"unknown component property types were skipped" = "i tipi di proprietà del componente sconosciuti sono stati saltati" +"variable aliases to variables that aren't in the variables JSON (or of another type) were skipped" = "gli alias di variabile verso variabili non presenti nel JSON delle variabili (o di un altro tipo) sono stati saltati" +"variable bindings to properties the layer doesn't have were skipped" = "i collegamenti di variabile a proprietà che il livello non possiede sono stati saltati" +"variable bindings to variables that aren't in the variables JSON (library variables) were skipped" = "i collegamenti di variabile a variabili non presenti nel JSON delle variabili (variabili di libreria) sono stati saltati" +"variable bindings were skipped: the file JSON has no variable definitions (pass the local variables JSON as `variables`, or embed its `meta` next to the file JSON)" = "i collegamenti di variabile sono stati saltati: il JSON del file non ha definizioni di variabili (passa il JSON delle variabili locali come `variables`, oppure incorpora il suo `meta` accanto al JSON del file)" +"variable bindings whose variable type doesn't fit the property were skipped" = "i collegamenti di variabile il cui tipo non è adatto alla proprietà sono stati saltati" +"variable collections without modes were skipped" = "le collezioni di variabili senza modalità sono state saltate" +"variable mode lookups in prototype expressions that aren't a variable and one of its modes were skipped" = "le ricerche di modalità di variabile nelle espressioni del prototipo che non sono una variabile e una delle sue modalità sono state saltate" +"variable modes of collections that aren't in the file were skipped" = "le modalità di variabile di collezioni non presenti nel file sono state saltate" +"variable modes of collections that aren't in the variables JSON were skipped" = "le modalità di variabile di collezioni non presenti nel JSON delle variabili sono state saltate" +"variable values that don't match their type (or composed colours) were skipped" = "i valori di variabile che non corrispondono al loro tipo (o i colori composti) sono stati saltati" +"variables of unknown types were skipped" = "le variabili di tipo sconosciuto sono state saltate" +"variables whose collection isn't in the variables JSON were skipped" = "le variabili la cui collezione non è nel JSON delle variabili sono state saltate" +"variables without readable values were skipped" = "le variabili senza valori leggibili sono state saltate" +"vector points with unknown stroke joins use the layer join" = "i punti vettoriali con giunzioni di contorno sconosciute usano la giunzione del livello" +"vectors without geometry are empty" = "i vettori senza geometria sono vuoti" +"vectors without geometry are empty (export the file JSON with geometry=paths)" = "i vettori senza geometria sono vuoti (esporta il JSON del file con geometry=paths)" +"very long text was cut to 1,048,576 characters" = "un testo molto lungo è stato troncato a 1.048.576 caratteri" +"very long vector paths were skipped" = "i tracciati vettoriali molto lunghi sono stati saltati" +"video actions are not supported and were skipped" = "le azioni video non sono supportate e sono state saltate" +"video triggers are not supported and were skipped" = "i trigger video non sono supportati e sono stati saltati" +"yesterday" = "ieri" +"{instances} in {files}" = "{instances} in {files}" +"{language}: draft translation, waiting for a native speaker's review." = "{language}: traduzione provvisoria, in attesa della revisione di un madrelingua." +"{length} bleed" = "abbondanza di {length}" +"{month} {day}, {year}" = "{day} {month} {year}" +"{name} cleared from the list" = "{name} rimosso dall'elenco" +"{name} is forgotten on this computer; the file stays on {server}" = "{name} è dimenticato su questo computer; il file resta su {server}" +"{name} is on the server." = "{name} è sul server." +"{name} is spotlighting" = "{name} sta mettendo in evidenza" +"{name} is too large to open in the browser (over 512 MB)." = "{name} è troppo grande per essere aperto nel browser (oltre 512 MB)." +"{name}\nClick to apply to the selection" = "{name}\nClicca per applicare alla selezione" +"{name}\nDrag onto the canvas, or click to place in the middle of the view.\nHold Alt (Option) while dropping onto an instance to swap it." = "{name}\nTrascina sul canvas, oppure clicca per posizionare al centro della vista.\nTieni premuto Alt (Opzione) mentre rilasci su un'istanza per scambiarla." +"{name} (from {library})" = "{name} (da {library})" +"{name} (you)" = "{name} (tu)" +"{name} ({layers} layers)" = "{name} ({layers} livelli)" +"{name} ({tag}): {min} – {max}, default {default}. Double-click the slider to go back to the style's value; Escape while dragging cancels." = "{name} ({tag}): {min} – {max}, predefinito {default}. Fai doppio clic sul cursore per tornare al valore dello stile; Esc durante il trascinamento annulla." +"{name} from {path}" = "{name} da {path}" +"{name} was removed from the server" = "{name} è stato rimosso dal server" +"{name} · deleted on main" = "{name} · eliminato su main" +"{name} · deleted on the branch" = "{name} · eliminato sul ramo" +"{name} · not published" = "{name} · non pubblicato" +"{name} · on a server" = "{name} · su un server" +"{name} · published version {v}" = "{name} · versione {v} pubblicata" +"{name} · version {v} on the server" = "{name} · versione {v} sul server" +"{name}, in a live session" = "{name}, in una sessione live" +"{name}, online, open only to you" = "{name}, online, aperto solo a te" +"{name}, {kind}, {w} by {h}, selected" = "{name}, {kind}, {w} per {h}, selezionato" +"{n} added" = { one = "{n} aggiunto", other = "{n} aggiunti" } +"{n} autosave versions" = { one = "{n} versione di salvataggio automatico", other = "{n} versioni di salvataggio automatico" } +"{n} changed" = { one = "{n} modificato", other = "{n} modificati" } +"{n} columns" = { one = "{n} colonna", other = "{n} colonne" } +"{n} components" = { one = "{n} componente", other = "{n} componenti" } +"{n} components and styles have newer versions" = { one = "{n} componente o stile ha una versione più recente", other = "{n} componenti e stili hanno versioni più recenti" } +"{n} conflicts to resolve" = { one = "{n} conflitto da risolvere", other = "{n} conflitti da risolvere" } +"{n} days ago" = { one = "{n} giorno fa", other = "{n} giorni fa" } +"{n} files" = { one = "{n} file", other = "{n} file" } +"{n} hours ago" = { one = "{n} ora fa", other = "{n} ore fa" } +"{n} instances" = { one = "{n} istanza", other = "{n} istanze" } +"{n} layers" = { one = "{n} livello", other = "{n} livelli" } +"{n} layers as one file" = { one = "{n} livello come un unico file", other = "{n} livelli come un unico file" } +"{n} layers selected" = { one = "{n} livello selezionato", other = "{n} livelli selezionati" } +"{n} layers · {parts}" = { one = "{n} livello · {parts}", other = "{n} livelli · {parts}" } +"{n} matches" = { one = "{n} corrispondenza", other = "{n} corrispondenze" } +"{n} minutes ago" = { one = "{n} minuto fa", other = "{n} minuti fa" } +"{n} months ago" = { one = "{n} mese fa", other = "{n} mesi fa" } +"{n} more notes omitted from this report" = { one = "{n} altra nota omessa da questo resoconto", other = "{n} altre note omesse da questo resoconto" } +"{n} of {total}" = "{n} di {total}" +"{n} people joined" = { one = "{n} persona si è unita", other = "{n} persone si sono unite" } +"{n} preferred components" = { one = "{n} componente preferito", other = "{n} componenti preferiti" } +"{n} ready" = { one = "{n} pronto", other = "{n} pronti" } +"{n} removed" = { one = "{n} rimosso", other = "{n} rimossi" } +"{n} replies" = { one = "{n} risposta", other = "{n} risposte" } +"{n} rows" = { one = "{n} riga", other = "{n} righe" } +"{n} styles" = { one = "{n} stile", other = "{n} stili" } +"{n} text layers" = { one = "{n} livello di testo", other = "{n} livelli di testo" } +"{n} variables" = { one = "{n} variabile", other = "{n} variabili" } +"{n} weeks ago" = { one = "{n} settimana fa", other = "{n} settimane fa" } +"{n} years ago" = { one = "{n} anno fa", other = "{n} anni fa" } +"{n}d" = "{n}g" +"{n}h" = "{n}h" +"{n}m" = "{n}m" +"{n}w" = "{n}sett" +"{page}, nothing selected" = "{page}, nessuna selezione" +"{percent}%" = "{percent}%" +"{profile} (current)" = "{profile} (attuale)" +"{property} is its value; {property ? \"a\" : \"b\"} picks a string. Other {…} stays as written." = "{property} è il suo valore; {property ? \"a\" : \"b\"} sceglie una stringa. Qualsiasi altro {…} resta come scritto." +"{server} can't say what became of your files (it is older than this app)." = "{server} non sa dire che fine abbiano fatto i tuoi file (è più vecchio di questa app)." +"{server} · No longer on the server" = "{server} · Non più sul server" +"{server} · {shared} · Opened {date}" = "{server} · {shared} · Aperto {date}" +"{tool} hold {key}" = "{tool} tieni premuto {key}" +"{value} isn't a colour" = "{value} non è un colore" +"{when} · edited" = "{when} · modificato" +"← ↑ → ↓" = "← ↑ → ↓" +"← → ↑ ↓" = "← → ↑ ↓" +"←" = "←" +"→, Space or N" = "→, Spazio o N" +"↵" = "↵" +"⇥ / ⇧⇥" = "⇥ / ⇧⇥" +"⌘ arrows" = "⌘ frecce" +"⌘ click" = "⌘ clic" +"⌘ scroll" = "⌘ scorrimento" +"⌥ drag" = "⌥ trascina" + +[commands] +"align.bottom" = "Allinea in basso" +"align.hcenter" = "Allinea i centri orizzontali" +"align.left" = "Allinea a sinistra" +"align.right" = "Allinea a destra" +"align.top" = "Allinea in alto" +"align.vcenter" = "Allinea i centri verticali" +"annotation.add" = "Aggiungi annotazione" +"annotation.addCategory" = "Aggiungi categoria di annotazione" +"annotation.categories" = "Categorie di annotazione" +"annotation.edit" = "Modifica annotazione" +"annotation.editCategory" = "Modifica categoria di annotazione" +"annotation.list" = "Elenca annotazioni" +"annotation.remove" = "Rimuovi annotazione" +"annotation.removeCategory" = "Elimina categoria di annotazione" +"app.actions" = "Azioni" +"app.preferences" = "Preferenze" +"arrange.back" = "Porta in fondo" +"arrange.backward" = "Porta indietro" +"arrange.forward" = "Porta avanti" +"arrange.front" = "Porta in primo piano" +"arrange.packH" = "Compatta in orizzontale" +"arrange.packV" = "Compatta in verticale" +"arrange.reorder" = "Riordina selezione" +"arrange.roundToPixels" = "Arrotonda ai pixel" +"arrange.spacing" = "Spaziatura" +"arrange.tidy" = "Riordina" +"bool.exclude" = "Escludi selezione" +"bool.flatten" = "Appiattisci booleano" +"bool.intersect" = "Interseca selezione" +"bool.subtract" = "Sottrai selezione" +"bool.union" = "Unisci selezione" +"branch.archive" = "Archivia ramo" +"branch.create" = "Crea ramo" +"branch.list" = "Elenca rami" +"branch.merge" = "Unisci ramo" +"branch.open" = "Apri ramo" +"branch.review" = "Rivedi ramo" +"branch.updateFromMain" = "Aggiorna da main" +"codeConnect.export" = "Esporta collegamenti al codice" +"codeConnect.import" = "Importa collegamenti al codice" +"codeConnect.remove" = "Scollega dal codice" +"codeConnect.set" = "Collega al codice" +"collab.cursor" = "Mostra cursore a" +"collab.cursorChat" = "Chat del cursore" +"collab.diff" = "Mostra modifiche di sincronizzazione" +"collab.files" = "I tuoi file online" +"collab.follow" = "Segui" +"collab.forgetFile" = "Dimentica file online" +"collab.host" = "Avvia sessione live" +"collab.join" = "Partecipa alla sessione live" +"collab.leave" = "Esci dalla sessione live" +"collab.manageFile" = "Gestisci file pubblicato" +"collab.openFile" = "Apri file online" +"collab.peers" = "Persone in questo file" +"collab.publish" = "Pubblica sul server" +"collab.recoverFile" = "Recupera file online" +"collab.registerLinks" = "Apri i link di invito in FigCraft" +"collab.removePerson" = "Rimuovi dalla sessione" +"collab.report" = "Segnala il file a chi gestisce il server" +"collab.serverInfo" = "Informazioni sul server" +"collab.share" = "Condividi con un codice di partecipazione" +"collab.spotlight" = "Metti me in evidenza" +"collab.stop" = "Termina sessione live" +"command.batch" = "Esegui più comandi" +"comment.add" = "Aggiungi commento" +"comment.delete" = "Elimina commento" +"comment.edit" = "Modifica commento" +"comment.link" = "Copia link al commento" +"comment.list" = "Elenca commenti" +"comment.markRead" = "Segna commento come letto" +"comment.move" = "Sposta commento" +"comment.next" = "Commento successivo" +"comment.open" = "Apri commento" +"comment.people" = "Persone nei commenti" +"comment.react" = "Reagisci al commento" +"comment.reply" = "Rispondi al commento" +"comment.resolve" = "Risolvi commento" +"comment.zoom" = "Zoom sui commenti" +"component.addProperty" = "Aggiungi proprietà del componente" +"component.addVariant" = "Aggiungi variante" +"component.bindProperty" = "Collega livello alla proprietà" +"component.combineVariants" = "Combina come varianti" +"component.create" = "Crea componente" +"component.createMultiple" = "Crea più componenti" +"component.deleteProperty" = "Elimina proprietà del componente" +"component.detach" = "Scollega istanza" +"component.editProperty" = "Modifica proprietà del componente" +"component.exposeInstance" = "Esponi istanza annidata" +"component.goToMain" = "Vai al componente principale" +"component.hideWhenPublishing" = "Nascondi alla pubblicazione" +"component.list" = "Elenca componenti" +"component.makeSlot" = "Crea slot" +"component.playground" = "Playground dei componenti" +"component.properties" = "Proprietà del componente" +"component.pushChanges" = "Invia le modifiche al componente principale" +"component.removeSlot" = "Rimuovi slot" +"component.resetInstance" = "Ripristina tutte le modifiche" +"component.resetOverride" = "Ripristina sovrascrittura" +"component.restoreMain" = "Ripristina componente principale" +"dev.compare" = "Confronta modifiche" +"dev.focus" = "Vista focus" +"dev.resolveChange" = "Risolvi la modifica" +"dev.unfocus" = "Esci dalla vista focus" +"devResource.add" = "Aggiungi risorsa per sviluppatori" +"devResource.edit" = "Modifica risorsa per sviluppatori" +"devResource.list" = "Elenca risorse per sviluppatori" +"devResource.remove" = "Rimuovi risorsa per sviluppatori" +"distribute.horizontal" = "Distribuisci spaziatura orizzontale" +"distribute.vertical" = "Distribuisci spaziatura verticale" +"doc.home" = "Home" +"doc.list" = "Elenca documenti" +"doc.nextTab" = "Scheda successiva" +"doc.previousTab" = "Scheda precedente" +"doc.switch" = "Cambia documento" +"edit.copy" = "Copia" +"edit.copyAsCss" = "Copia come CSS" +"edit.copyAsPng" = "Copia come PNG" +"edit.copyAsSvg" = "Copia come SVG" +"edit.copyAsText" = "Copia come testo" +"edit.copyLink" = "Copia link alla selezione" +"edit.copyProperties" = "Copia proprietà" +"edit.cut" = "Taglia" +"edit.delete" = "Elimina" +"edit.duplicate" = "Duplica" +"edit.findReplace" = "Trova e sostituisci" +"edit.paste" = "Incolla" +"edit.pasteDesignLayers" = "Incolla livelli di design" +"edit.pasteOver" = "Incolla sopra la selezione" +"edit.pasteProperties" = "Incolla proprietà" +"edit.pasteReplace" = "Incolla per sostituire" +"edit.pickColor" = "Scegli colore" +"edit.redo" = "Ripeti" +"edit.repeat" = "Ripeti l'ultimo comando" +"edit.setDefaultProperties" = "Imposta proprietà predefinite" +"edit.undo" = "Annulla" +"effect.add" = "Aggiungi effetto" +"effect.move" = "Sposta effetto" +"effect.remove" = "Rimuovi effetto" +"effect.set" = "Imposta effetto" +"export.add" = "Aggiungi impostazione di esportazione" +"export.remove" = "Rimuovi impostazione di esportazione" +"export.update" = "Modifica impostazione di esportazione" +"file.close" = "Chiudi scheda" +"file.colorProfile" = "Profilo colore" +"file.duplicate" = "Duplica file" +"file.export" = "Esporta" +"file.exportFramesPdf" = "Esporta frame in PDF" +"file.exportLayers" = "Esporta livelli" +"file.exportSvg" = "Esporta selezione come SVG" +"file.import" = "Importa" +"file.moveTab" = "Sposta scheda" +"file.new" = "Nuovo file di design" +"file.newFromTemplate" = "Nuovo da modello" +"file.open" = "Apri" +"file.openRecent" = "Apri recente" +"file.placeImage" = "Inserisci immagine" +"file.recover" = "Recupera file" +"file.rename" = "Rinomina file" +"file.revert" = "Ripristina l'ultima versione salvata" +"file.save" = "Salva" +"file.saveAs" = "Salva come" +"file.saveVersion" = "Salva nella cronologia versioni" +"file.versionHistory" = "Mostra cronologia versioni" +"fill.add" = "Aggiungi riempimento" +"fill.move" = "Sposta riempimento" +"fill.pattern" = "Riempimento a motivo" +"fill.remove" = "Rimuovi riempimento" +"fill.set" = "Imposta riempimento" +"frame.create" = "Crea frame" +"frame.presets" = "Elenca preset di frame" +"frame.swapOrientation" = "Scambia orientamento" +"grid.add" = "Aggiungi griglia di layout" +"grid.move" = "Sposta griglia di layout" +"grid.remove" = "Rimuovi griglia di layout" +"guide.add" = "Aggiungi guida" +"guide.clear" = "Rimuovi tutte le guide" +"guide.move" = "Sposta guida" +"guide.remove" = "Rimuovi guida" +"guide.select" = "Seleziona guida" +"help.gettingStarted" = "Per iniziare" +"help.about" = "Informazioni su FigCraft" +"help.page" = "Pagina di aiuto" +"help.shortcuts" = "Scorciatoie da tastiera" +"image.adjust" = "Regola immagine" +"image.deletePreset" = "Elimina preset di immagine" +"image.place" = "Inserisci immagine" +"image.presets" = "Preset di immagine" +"image.savePreset" = "Salva preset di immagine" +"inspect.assets" = "Ispeziona asset" +"inspect.code" = "Codice del livello" +"inspect.componentCode" = "Codice del componente" +"inspect.properties" = "Ispeziona proprietà" +"inspect.readyForDev" = "Pronto per lo sviluppo" +"instance.create" = "Crea istanza" +"instance.exposed" = "Istanze annidate esposte" +"instance.place" = "Inserisci istanza" +"instance.resetSlot" = "Ripristina slot" +"instance.setProperties" = "Imposta proprietà dell'istanza" +"instance.slotInsert" = "Inserisci nello slot" +"instance.swap" = "Scambia istanza" +"instance.swapAt" = "Scambia istanza nel punto" +"layers.collapseAll" = "Comprimi livelli" +"layout.add" = "Aggiungi auto layout" +"layout.remove" = "Rimuovi auto layout" +"layout.setTrack" = "Imposta dimensione traccia della griglia" +"layout.suggest" = "Suggerisci auto layout" +"libraries.open" = "Librerie" +"libraries.publish" = "Pubblica libreria…" +"library.acceptUpdates" = "Accetta aggiornamenti della libreria" +"library.add" = "Aggiungi libreria" +"library.aliasVariable" = "Alias di variabile di libreria" +"library.applyStyle" = "Applica stile di libreria" +"library.bindVariable" = "Collega variabile di libreria" +"library.extendCollection" = "Estendi collezione di libreria" +"library.import" = "Importa componente di libreria" +"library.importVariable" = "Importa variabile di libreria" +"library.list" = "Elenca librerie" +"library.place" = "Inserisci componente di libreria" +"library.publish" = "Pubblica libreria" +"library.publishToServer" = "Pubblica libreria sul server" +"library.remove" = "Rimuovi libreria" +"library.setVariableMode" = "Imposta modalità della variabile di libreria" +"library.swap" = "Scambia libreria" +"library.swapAt" = "Scambia istanza con componente di libreria" +"library.updates" = "Aggiornamenti della libreria" +"library.usage" = "Utilizzo della libreria" +"measure.distances" = "Misura distanze" +"measure.padding" = "Misura margini interni dell'auto layout" +"node.create" = "Crea livello" +"node.get" = "Ottieni livello" +"node.reorder" = "Riordina livello" +"node.reparent" = "Sposta livello dentro" +"node.set" = "Imposta proprietà" +"object.batchRename" = "Rinomina in blocco" +"object.convertToFrame" = "Converti in frame" +"object.convertToGroup" = "Converti in gruppo" +"object.convertToSection" = "Converti in sezione" +"object.flipH" = "Rifletti orizzontalmente" +"object.flipV" = "Rifletti verticalmente" +"object.frameSelection" = "Racchiudi la selezione in un frame" +"object.group" = "Raggruppa selezione" +"object.hideOthers" = "Nascondi altri livelli" +"object.mask" = "Usa come maschera" +"object.moveToPage" = "Sposta nella pagina" +"object.opacityKeys" = "Opacità dai tasti numerici" +"object.rasterize" = "Rasterizza selezione" +"object.releaseMask" = "Rilascia maschera" +"object.removeFill" = "Rimuovi riempimento" +"object.removeFrame" = "Rimuovi frame" +"object.removeStroke" = "Rimuovi contorno" +"object.rename" = "Rinomina" +"object.resizeToFit" = "Ridimensiona per adattare" +"object.rotate180" = "Ruota di 180°" +"object.rotateLeft" = "Ruota di 90° a sinistra" +"object.rotateRight" = "Ruota di 90° a destra" +"object.setThumbnail" = "Imposta come miniatura" +"object.swapFillStroke" = "Scambia riempimento e contorno" +"object.toggleLock" = "Blocca/Sblocca selezione" +"object.toggleVisible" = "Mostra/Nascondi selezione" +"object.ungroup" = "Separa selezione" +"object.wrapInSection" = "Racchiudi in una nuova sezione" +"page.add" = "Aggiungi pagina" +"page.delete" = "Elimina pagina" +"page.duplicate" = "Duplica pagina" +"page.list" = "Elenca pagine" +"page.move" = "Sposta pagina" +"page.rename" = "Rinomina pagina" +"page.select" = "Vai alla pagina" +"page.setBackground" = "Sfondo della pagina" +"paint.documentColors" = "Colori del documento" +"plugin.close" = "Chiudi pannello del plugin" +"plugin.directory.install" = "Installa plugin dalla directory" +"plugin.directory.list" = "Sfoglia la directory dei plugin" +"plugin.directory.publish" = "Pubblica plugin nella directory" +"plugin.directory.unpublish" = "Rimuovi plugin dalla directory" +"plugin.event" = "Usa controllo del pannello del plugin" +"plugin.import" = "Importa plugin da manifest…" +"plugin.list" = "Elenca plugin" +"plugin.remove" = "Rimuovi plugin" +"plugin.run" = "Esegui plugin" +"plugin.ui" = "Pannello del plugin" +"plugins.manage" = "Gestisci plugin…" +"plugins.runLast" = "Esegui l'ultimo plugin" +"pref.autoSave" = "Salva i file automaticamente" +"pref.highlightHover" = "Evidenzia i livelli al passaggio del mouse" +"pref.keepTool" = "Mantieni lo strumento selezionato dopo l'uso" +"pref.language" = "Lingua dell'interfaccia" +"pref.nudge" = "Entità dello spostamento" +"pref.numberKeysOpacity" = "Usa i tasti numerici per l'opacità" +"pref.publishServer" = "Server per la condivisione online" +"pref.reduceMotion" = "Riduci animazioni" +"pref.renameDuplicates" = "Rinomina i livelli duplicati" +"pref.showDimensions" = "Mostra dimensioni sugli oggetti" +"pref.startOnHome" = "Avvia dalla schermata Home" +"pref.uiScale" = "Dimensione dell'interfaccia" +"pref.userName" = "Il tuo nome nei commenti" +"proto.action.add" = "Aggiungi azione del prototipo" +"proto.action.move" = "Riordina azione del prototipo" +"proto.action.remove" = "Rimuovi azione del prototipo" +"proto.action.set" = "Modifica azione del prototipo" +"proto.closePreview" = "Chiudi anteprima del prototipo" +"proto.conditional.addBlock" = "Aggiungi \"altrimenti se\" / \"altrimenti\"" +"proto.conditional.removeBlock" = "Rimuovi \"se\" / \"altrimenti se\" / \"altrimenti\"" +"proto.conditional.setCondition" = "Modifica condizione" +"proto.evaluate" = "Valuta espressione del prototipo" +"proto.flow.add" = "Aggiungi punto di partenza del flusso" +"proto.flow.remove" = "Rimuovi flusso" +"proto.flow.rename" = "Rinomina flusso" +"proto.interaction.add" = "Aggiungi interazione" +"proto.interaction.clear" = "Rimuovi tutte le interazioni" +"proto.interaction.remove" = "Rimuovi interazione" +"proto.interaction.set" = "Modifica interazione" +"proto.present" = "Presenta" +"proto.presentOptions" = "Opzioni di presentazione" +"proto.preview" = "Apri anteprima del prototipo" +"proto.settings" = "Impostazioni del prototipo" +"section.toggleContents" = "Nascondi/Mostra il contenuto della sezione" +"select.add" = "Aggiungi alla selezione" +"select.all" = "Seleziona tutto" +"select.area" = "Seleziona area" +"select.byName" = "Seleziona livelli per nome" +"select.children" = "Seleziona figli" +"select.inverse" = "Inverti selezione" +"select.layersAt" = "Livelli in un punto" +"select.matching" = "Seleziona livelli corrispondenti" +"select.next" = "Seleziona fratello successivo" +"select.none" = "Deseleziona tutto" +"select.parent" = "Seleziona genitore" +"select.previous" = "Seleziona fratello precedente" +"select.remove" = "Rimuovi dalla selezione" +"select.sameEffect" = "Seleziona stesso effetto" +"select.sameFill" = "Seleziona stesso riempimento" +"select.sameFont" = "Seleziona stesso font" +"select.sameInstance" = "Seleziona stessa istanza" +"select.sameProperties" = "Seleziona stesse proprietà" +"select.sameStroke" = "Seleziona stesso contorno" +"select.sameText" = "Seleziona stesse proprietà del testo" +"select.set" = "Seleziona" +"selection.applyColorStyle" = "Applica stile di colore della selezione" +"selection.colors" = "Colori della selezione" +"selection.selectColor" = "Seleziona livelli con il colore" +"selection.setPaint" = "Cambia colore della selezione" +"shape.arrow" = "Freccia" +"shape.ellipse" = "Ellisse" +"shape.line" = "Linea" +"shape.polygon" = "Poligono" +"shape.rectangle" = "Rettangolo" +"shape.star" = "Stella" +"shapeBuilder.merge" = "Unisci regioni" +"shapeBuilder.mode" = "Modalità generatore di forme" +"shapeBuilder.regions" = "Regioni del generatore di forme" +"shapeBuilder.remove" = "Rimuovi regioni" +"stroke.add" = "Aggiungi contorno" +"stroke.brush" = "Pennello" +"stroke.move" = "Sposta contorno" +"stroke.remove" = "Rimuovi contorno" +"stroke.set" = "Imposta contorno" +"stroke.widthProfile" = "Profilo di larghezza" +"style.apply" = "Applica stile" +"style.bind" = "Collega variabile nello stile" +"style.create" = "Crea stile" +"style.delete" = "Elimina stile" +"style.detach" = "Scollega stile" +"style.duplicate" = "Duplica stile" +"style.list" = "Elenca stili" +"style.move" = "Sposta stili" +"style.renameFolder" = "Rinomina cartella di stili" +"style.update" = "Modifica stile" +"text.alignCenter" = "Centra" +"text.alignJustify" = "Giustifica" +"text.alignLeft" = "Allinea a sinistra" +"text.alignRight" = "Allinea a destra" +"text.bold" = "Grassetto" +"text.bulletList" = "Elenco puntato" +"text.case" = "Maiuscole/minuscole" +"text.caseLower" = "Minuscolo" +"text.caseOriginal" = "Originale" +"text.caseSmallCaps" = "Maiuscoletto" +"text.caseTitle" = "Iniziali maiuscole" +"text.caseUpper" = "Maiuscolo" +"text.create" = "Testo" +"text.decoration" = "Decorazione" +"text.edit.begin" = "Modifica testo" +"text.edit.caret" = "Imposta cursore" +"text.edit.end" = "Termina modifica del testo" +"text.edit.insert" = "Digita testo" +"text.edit.key" = "Tasto del testo" +"text.edit.preedit" = "Componi testo (IME)" +"text.edit.selectLine" = "Seleziona riga" +"text.edit.selectWord" = "Seleziona parola" +"text.edit.selection" = "Selezione del testo" +"text.endPreview" = "Termina anteprima del testo" +"text.fonts" = "Elenca font" +"text.italic" = "Corsivo" +"text.lineHeightDown" = "Riduci interlinea" +"text.lineHeightUp" = "Aumenta interlinea" +"text.link" = "Crea link" +"text.listIndent" = "Aumenta rientro elenco" +"text.listOutdent" = "Riduci rientro elenco" +"text.listStyle" = "Stile dell'elenco" +"text.loadFont" = "Carica file di font" +"text.maxLines" = "Righe massime" +"text.missingFonts" = "Font mancanti" +"text.numberList" = "Elenco numerato" +"text.onPath" = "Testo su tracciato" +"text.paragraphIndent" = "Rientro del paragrafo" +"text.paragraphSpacing" = "Spaziatura dei paragrafi" +"text.pathOptions" = "Opzioni del tracciato di testo" +"text.preview" = "Anteprima della modifica del testo" +"text.refit" = "Adatta il testo ai font" +"text.removeFromPath" = "Rimuovi il testo dal tracciato" +"text.rescanFonts" = "Riesamina i font installati" +"text.set" = "Imposta testo" +"text.setFeatures" = "Funzionalità OpenType" +"text.setVariations" = "Assi del font variabile" +"text.sizeDown" = "Riduci dimensione del font" +"text.sizeUp" = "Aumenta dimensione del font" +"text.strikethrough" = "Barrato" +"text.style" = "Applica stile a un intervallo di testo" +"text.trackingDown" = "Riduci spaziatura tra lettere" +"text.trackingUp" = "Aumenta spaziatura tra lettere" +"text.underline" = "Sottolineato" +"text.weightDown" = "Riduci peso del font" +"text.weightUp" = "Aumenta peso del font" +"tool.arrow" = "Freccia" +"tool.comment" = "Commento" +"tool.ellipse" = "Ellisse" +"tool.frame" = "Frame" +"tool.hand" = "Mano" +"tool.line" = "Linea" +"tool.move" = "Sposta" +"tool.pen" = "Penna" +"tool.pencil" = "Matita" +"tool.pencilBrush" = "Pennello matita" +"tool.place" = "Inserisci con lo strumento" +"tool.polygon" = "Poligono" +"tool.rectangle" = "Rettangolo" +"tool.scale" = "Scala" +"tool.scaleAnchor" = "Ancora di scala" +"tool.section" = "Sezione" +"tool.set" = "Scegli strumento" +"tool.shapeBuilder" = "Generatore di forme" +"tool.slice" = "Slice" +"tool.star" = "Stella" +"tool.text" = "Testo" +"transform.move" = "Sposta" +"transform.moveTo" = "Sposta in" +"transform.nudge" = "Sposta di poco" +"transform.nudgeSize" = "Ridimensiona di poco" +"transform.resize" = "Ridimensiona" +"transform.rotate" = "Ruota" +"transform.rotationOrigin" = "Sposta origine di rotazione" +"transform.scale" = "Scala" +"transform.setRotationOrigin" = "Imposta origine di rotazione" +"variable.addMode" = "Aggiungi modalità" +"variable.bind" = "Collega variabile" +"variable.create" = "Crea variabile" +"variable.createCollection" = "Crea collezione di variabili" +"variable.delete" = "Elimina variabile" +"variable.deleteCollection" = "Elimina collezione" +"variable.duplicate" = "Duplica variabile" +"variable.export" = "Esporta variabili" +"variable.import" = "Importa variabili" +"variable.list" = "Elenca variabili" +"variable.moveMode" = "Sposta modalità" +"variable.removeMode" = "Elimina modalità" +"variable.renameCollection" = "Rinomina collezione" +"variable.renameMode" = "Rinomina modalità" +"variable.resetOverride" = "Ripristina sovrascrittura della variabile" +"variable.set" = "Imposta valore della variabile" +"variable.setMode" = "Imposta modalità della variabile" +"variable.update" = "Modifica variabile" +"vector.create" = "Vettore" +"vector.deleteHeal" = "Elimina e ricuci selezione" +"vector.edit.addVertex" = "Aggiungi punto" +"vector.edit.begin" = "Modifica vettore" +"vector.edit.bend" = "Curva segmento" +"vector.edit.bounds" = "Limiti dei punti selezionati" +"vector.edit.connect" = "Collega punti" +"vector.edit.cornerRadius" = "Raggio dell'angolo del punto" +"vector.edit.cut" = "Taglia tracciato" +"vector.edit.delete" = "Elimina punti" +"vector.edit.end" = "Modifica terminata" +"vector.edit.erase" = "Cancella" +"vector.edit.lasso" = "Selezione a lazo" +"vector.edit.mirroring" = "Riflessione delle maniglie" +"vector.edit.moveHandles" = "Sposta maniglie" +"vector.edit.moveVertices" = "Sposta punti" +"vector.edit.nudge" = "Sposta punti di poco" +"vector.edit.paint" = "Secchiello" +"vector.edit.pen" = "Punto penna" +"vector.edit.penEnd" = "Termina tracciato penna" +"vector.edit.position" = "Posizione del punto" +"vector.edit.resize" = "Ridimensiona punti" +"vector.edit.rotate" = "Ruota punti" +"vector.edit.select" = "Seleziona punti" +"vector.edit.selectAll" = "Seleziona tutti i punti" +"vector.edit.setHandle" = "Imposta maniglia" +"vector.edit.smooth" = "Ammorbidisci o appuntisci punti" +"vector.edit.state" = "Stato di modifica del vettore" +"vector.edit.strokeCap" = "Terminazione del contorno del punto" +"vector.edit.strokeJoin" = "Giunzione del contorno del punto" +"vector.edit.tool" = "Strumento di modifica vettoriale" +"vector.edit.transform" = "Trasforma punti" +"vector.edit.widthPoint" = "Punto di larghezza" +"vector.flatten" = "Appiattisci" +"vector.join" = "Unisci selezione" +"vector.offset" = "Contorno parallelo" +"vector.outlineStroke" = "Contorna tratto" +"vector.reverse" = "Inverti direzione del tracciato" +"vector.simplify" = "Semplifica vettore" +"vector.smoothJoin" = "Unisci selezione in modo uniforme" +"version.autosave" = "Versione di salvataggio automatico" +"version.delete" = "Elimina versione" +"version.duplicate" = "Duplica versione" +"version.exitPreview" = "Torna alla versione attuale" +"version.list" = "Elenca versioni" +"version.preview" = "Visualizza versione" +"version.rename" = "Rinomina versione" +"version.restore" = "Ripristina versione" +"view.assets" = "Pannello degli asset" +"view.comments" = "Commenti" +"view.contextMenu" = "Mostra menu contestuale" +"view.design" = "Pannello Design" +"view.designPrototype" = "Passa tra Design e Prototipo" +"view.devMode" = "Modalità sviluppo" +"view.frameOutlines" = "Contorni dei frame" +"view.get" = "Ottieni vista" +"view.layers" = "Pannello dei livelli" +"view.layoutGrids" = "Griglie di layout" +"view.leftSidebar" = "Mostra/Nascondi barra laterale sinistra" +"view.maskOutlines" = "Contorni delle maschere" +"view.multiplayerCursors" = "Cursori multiutente" +"view.nextFrame" = "Zoom sul frame successivo" +"view.nextPage" = "Pagina successiva" +"view.nextRegion" = "Sposta il focus sull'area successiva" +"view.outlines" = "Contorni" +"view.outlinesHiddenLayers" = "Includi i livelli nascosti nei contorni" +"view.outlinesObjectBounds" = "Includi i limiti degli oggetti nei contorni" +"view.outlinesSelection" = "Contorni della selezione" +"view.pan" = "Sposta la vista" +"view.panelWidths" = "Ripristina larghezze dei pannelli" +"view.pixelGrid" = "Griglia di pixel" +"view.pixelPreview" = "Anteprima pixel" +"view.prevFrame" = "Zoom sul frame precedente" +"view.prevPage" = "Pagina precedente" +"view.previousRegion" = "Sposta il focus sull'area precedente" +"view.proofSrgb" = "Soft proof sRGB" +"view.fullscreen" = "Schermo intero" +"view.prototype" = "Pannello Prototipo" +"view.rulers" = "Righelli" +"view.set" = "Imposta vista" +"view.slices" = "Slice" +"view.snapGeometry" = "Aggancia alla geometria" +"view.snapObjects" = "Aggancia agli oggetti" +"view.snapPixel" = "Aggancia alla griglia di pixel" +"view.themeDark" = "Tema scuro" +"view.themeLight" = "Tema chiaro" +"view.themeSystem" = "Segui il tema del sistema" +"view.toggleUi" = "Mostra/Nascondi interfaccia" +"view.zoom100" = "Zoom al 100%" +"view.zoom200" = "Zoom al 200%" +"view.zoom50" = "Zoom al 50%" +"view.zoomAt" = "Strumento zoom" +"view.zoomFit" = "Zoom per adattare" +"view.zoomIn" = "Ingrandisci" +"view.zoomOut" = "Riduci" +"view.zoomSelection" = "Zoom sulla selezione" +"widget.click" = "Clicca widget" +"widget.insert" = "Inserisci widget" +"widget.list" = "Elenca widget" +"window.focus" = "Porta la finestra in primo piano" +"window.new" = "Nuova finestra" +"template.list" = "Elenca modelli" + +[menu] +"bool.exclude" = "Escludi" +"bool.flatten" = "Appiattisci" +"bool.intersect" = "Interseca" +"bool.subtract" = "Sottrai" +"bool.union" = "Unisci" +"file.duplicate" = "Duplica" +"file.import" = "Importa da file…" +"file.rename" = "Rinomina" +"libraries.open" = "Apri librerie" +"select.sameEffect" = "Effetto" +"select.sameFill" = "Riempimento" +"select.sameFont" = "Font" +"select.sameInstance" = "Istanza" +"select.sameProperties" = "Proprietà" +"select.sameStroke" = "Contorno" +"select.sameText" = "Proprietà del testo" +"text.alignCenter" = "Centra testo" +"text.alignJustify" = "Giustifica testo" +"text.alignLeft" = "Allinea testo a sinistra" +"text.alignRight" = "Allinea testo a destra" +"view.assets" = "Asset" +"view.design" = "Design" +"view.layers" = "Livelli" +"view.outlines" = "Mostra contorni" +"view.outlinesHiddenLayers" = "Includi livelli nascosti" +"view.outlinesObjectBounds" = "Includi limiti degli oggetti" +"view.outlinesSelection" = "Mostra contorni della selezione" +"view.prototype" = "Prototipo" +"view.themeDark" = "Scuro" +"view.themeLight" = "Chiaro" +"view.themeSystem" = "Segui il sistema" diff --git a/crates/ui-egui/src/design_paste_tests.rs b/crates/ui-egui/src/design_paste_tests.rs index 35743a81..82c366c7 100644 --- a/crates/ui-egui/src/design_paste_tests.rs +++ b/crates/ui-egui/src/design_paste_tests.rs @@ -45,6 +45,34 @@ fn paste_takes_layers_copied_in_another_design_app() { assert_eq!(selected_names(&app), ["Copied layer"]); } +struct HtmlAndImage(String); + +impl Services for HtmlAndImage { + fn clipboard_get_html(&mut self) -> Option { + Some(self.0.clone()) + } + fn clipboard_get_image(&mut self) -> Option<(u32, u32, Vec)> { + Some((2, 2, vec![255; 16])) + } +} + +#[test] +fn editable_design_layers_take_priority_over_a_png_representation() { + let mut session = Session::new(); + let selected = session.execute("shape.ellipse", &json!({"x": 100, "y": 100, "width": 80, "height": 80})).unwrap()["id"].as_u64().unwrap(); + let mut app = FigApp::new(session, Box::new(HtmlAndImage(copied_html()))); + let before = app.session.doc().unwrap().node_count(); + + crate::menus::paste(&mut app, None, None); + + assert_eq!(selected_names(&app), ["Copied layer"]); + assert_eq!(app.session.doc().unwrap().node_count(), before + 1); + assert!(matches!( + app.session.doc().unwrap().node(figcraft_doc::NodeId(selected)).unwrap().fills[0].kind, + figcraft_color::PaintKind::Solid { .. } + )); +} + #[test] fn paste_here_puts_copied_design_layers_at_the_point() { let html = copied_html(); diff --git a/crates/ui-egui/src/i18n_tests.rs b/crates/ui-egui/src/i18n_tests.rs index eb829942..0d8e6f94 100644 --- a/crates/ui-egui/src/i18n_tests.rs +++ b/crates/ui-egui/src/i18n_tests.rs @@ -382,6 +382,18 @@ fn system_languages_pick_the_first_one_with_a_catalog() { assert_eq!(code("fr", Some("de-DE")), "fr", "a chosen language beats the system's"); } +#[test] +fn italian_is_picked_for_italian_locales() { + let code = |pref: &str, system: Option<&str>| LANGS[resolve(pref, system)].code; + for tag in ["it", "it-IT", "it-CH", "it_IT.UTF-8"] { + assert_eq!(code("system", Some(tag)), "it", "{tag}"); + } + let it = LANGS.iter().position(|l| l.code == "it").unwrap(); + assert_eq!(LANGS[it].name, "Italiano"); + assert!(LANGS[it].draft); + assert!(!plural_one(it, 0) && plural_one(it, 1) && !plural_one(it, 2)); +} + #[test] fn swedish_is_picked_for_swedish_locales() { let code = |pref: &str, system: Option<&str>| LANGS[resolve(pref, system)].code; diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index b7aca146..b366395e 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -307,6 +307,11 @@ pub trait Services { fn reduce_motion(&self) -> bool { false } + /// The system asks for the dark interface (the freedesktop colour scheme): for platforms whose + /// windowing stack doesn't report a theme (winit gives none on Linux and the BSDs). + fn system_dark(&self) -> Option { + None + } /// After a panic in a frame: keep recovery copies of unsaved files. fn emergency_save(&mut self, _session: &Session) {} /// Keep the preferences now (the installed plugins or their settings changed), as the app @@ -1719,9 +1724,10 @@ impl FigApp { } let dark = match self.session.prefs.theme.as_str() { "dark" => true, - // The active visuals were set by us last frame (possibly for a manual theme). - // Read the integration's OS theme instead; without one, keep the light fallback. - "system" => ctx.system_theme() == Some(egui::Theme::Dark), + // The active visuals were set by us last frame (possibly for a manual theme). Read the + // integration's OS theme; winit reports none on Linux and the BSDs, where the platform + // services' colour scheme answers instead. Without either, keep the light fallback. + "system" => ctx.system_theme().map(|t| t == egui::Theme::Dark).or_else(|| self.services.system_dark()).unwrap_or(false), _ => false, }; self.ui.dark = dark; diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 4b3483af..abe1fec6 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -82,6 +82,14 @@ pub fn paste(app: &mut FigApp, event_text: Option<&str>, at: Option<(f64, f64)>) } use base64::Engine as _; let b64 = base64::engine::general_purpose::STANDARD.encode(png); + // One selected layer that can have fills takes the image as its top fill. + if let [target] = app.session.selection()[..] + && at.is_none() + && app.session.doc().ok().and_then(|d| d.node(target)).is_some_and(|n| figcraft_engine::cmd::create::accepts_image_fill(&n.kind)) + { + app.run("image.place", json!({"data": b64, "target": target})); + return; + } // Centred in the view; Paste here puts its top-left at the point, as it does layers. let (x, y) = if at.is_some() { (cx, cy) } else { ((cx - f64::from(w) / 2.0).round(), (cy - f64::from(h) / 2.0).round()) }; app.run("file.import", json!({"data": b64, "name": "Pasted image.png", "x": x, "y": y})); diff --git a/crates/ui-egui/src/paste_image_tests.rs b/crates/ui-egui/src/paste_image_tests.rs index b6775f93..a76eae79 100644 --- a/crates/ui-egui/src/paste_image_tests.rs +++ b/crates/ui-egui/src/paste_image_tests.rs @@ -58,6 +58,7 @@ impl Win { fn new() -> Self { let mut s = Session::new(); s.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 100, "height": 50})).unwrap(); + s.execute("select.none", &json!({})).unwrap(); let ctx = egui::Context::default(); let clip = Rc::new(RefCell::new(Clip::default())); let app = FigApp::new(s, Box::new(Desktop(clip.clone()))); @@ -176,10 +177,80 @@ fn cmd_v_pastes_an_image_copied_elsewhere() { w.copy_image_elsewhere(); w.cmd_v(CMD); assert_eq!(w.named("Pasted image"), 1, "the image was pasted from the key's release"); + w.app.session.execute("select.none", &json!({})).unwrap(); w.cmd_v(CMD); assert_eq!(w.named("Pasted image"), 2, "and again"); } +#[test] +fn cmd_v_fills_a_selected_oval_without_adding_a_layer() { + let mut w = Win::new(); + let oval = + NodeId(w.app.session.execute("shape.ellipse", &json!({"x": 200, "y": 100, "width": 120, "height": 80})).unwrap()["id"].as_u64().unwrap()); + let before = w.app.session.doc().unwrap().node_count(); + let original_fill = w.app.session.doc().unwrap().node(oval).unwrap().fills.clone(); + w.copy_image_elsewhere(); + w.cmd_v(CMD); + + let doc = w.app.session.doc().unwrap(); + let node = doc.node(oval).unwrap(); + assert_eq!(doc.node_count(), before); + assert!(matches!(node.kind, figcraft_doc::NodeKind::Ellipse { .. })); + // Added on top: the oval's own fill stays under it. + assert_eq!(node.fills.len(), original_fill.len() + 1); + assert_eq!(node.fills[..original_fill.len()], original_fill[..]); + assert!(matches!(node.fills.last().unwrap().kind, figcraft_color::PaintKind::Image(_))); + assert_eq!((node.width, node.height), (120.0, 80.0)); + assert_eq!(w.app.session.selection(), vec![oval]); + let png = figcraft_engine::cmd::file::export_bytes(doc, &[oval], figcraft_doc::ExportFormat::Png, 1.0).unwrap(); + let pixels = image::load_from_memory(&png).unwrap().to_rgba8(); + assert_eq!(pixels.get_pixel(0, 0)[3], 0, "the picture stays inside the oval"); + assert_eq!(pixels.get_pixel(60, 40)[3], 255, "the oval contains the picture"); + + w.app.session.undo().unwrap(); + assert_eq!(w.app.session.doc().unwrap().node(oval).unwrap().fills, original_fill); +} + +#[test] +fn pasting_an_image_keeps_the_other_fills_variables() { + let mut w = Win::new(); + let oval = + NodeId(w.app.session.execute("shape.ellipse", &json!({"x": 200, "y": 100, "width": 120, "height": 80})).unwrap()["id"].as_u64().unwrap()); + w.app.session.execute("variable.createCollection", &json!({"name": "Theme"})).unwrap(); + w.app.session.execute("variable.create", &json!({"collection": "Theme", "name": "brand", "type": "color", "value": "#FF0000"})).unwrap(); + w.app.session.execute("variable.bind", &json!({"ids": [oval.0], "field": "fill", "variable": "brand"})).unwrap(); + assert!(!w.app.session.doc().unwrap().node(oval).unwrap().bound.is_empty()); + + w.copy_image_elsewhere(); + w.cmd_v(CMD); + let node = w.app.session.doc().unwrap().node(oval).unwrap(); + assert!(matches!(node.fills.last().unwrap().kind, figcraft_color::PaintKind::Image(_))); + assert!(matches!(node.fills[0].kind, figcraft_color::PaintKind::Solid { .. })); + assert!(!node.bound.is_empty(), "the colour fill under the image keeps its variable"); +} + +/// An image layer copied in FigCraft pastes as a layer, a selected shape or not. +#[test] +fn an_image_layer_copied_in_figcraft_pastes_as_a_layer() { + let mut w = Win::new(); + w.copy_image_elsewhere(); + w.cmd_v(CMD); + let source = w.app.session.selection()[0]; + crate::menus::invoke(&mut w.app, "edit.copy"); + + let oval = + NodeId(w.app.session.execute("shape.ellipse", &json!({"x": 200, "y": 100, "width": 120, "height": 80})).unwrap()["id"].as_u64().unwrap()); + let fills = w.app.session.doc().unwrap().node(oval).unwrap().fills.clone(); + let before = w.app.session.doc().unwrap().node_count(); + w.cmd_v(CMD); + + let doc = w.app.session.doc().unwrap(); + assert_eq!(doc.node_count(), before + 1); + assert!(doc.contains(source)); + assert_eq!(doc.node(oval).unwrap().fills, fills); + assert_ne!(w.app.session.selection(), vec![oval]); +} + #[test] fn cmd_released_before_v_still_pastes_the_image() { let mut w = Win::new(); @@ -247,6 +318,7 @@ fn a_paste_event_from_another_key_does_not_block_the_next_cmd_v() { // Shift+Insert on Windows. w.frame(vec![Event::Paste("text".into())]); w.frame(vec![key(Key::Insert, false, Modifiers::SHIFT)]); + w.app.session.execute("select.none", &json!({})).unwrap(); w.copy_image_elsewhere(); w.cmd_v(CMD); assert_eq!(w.named("Pasted image"), 1); diff --git a/crates/ui-egui/src/theme.rs b/crates/ui-egui/src/theme.rs index 9a384294..acad6f8f 100644 --- a/crates/ui-egui/src/theme.rs +++ b/crates/ui-egui/src/theme.rs @@ -308,7 +308,8 @@ pub fn install_fonts(ctx: &Context, system: bool, cjk: u8) { brand.extend(fonts.families.get(&FontFamily::Name("semibold".into())).cloned().unwrap_or_default()); fonts.families.insert(FontFamily::Name("brand".into()), brand); // CJK and other scripts from craft-fonts as fallbacks (Noto Sans CJK only with FIGCRAFT_EMBED_CJK). - const BUILT_IN: [&str; 6] = ["Inter", "BIZ UDPGothic", "Noto Sans CJK SC", "Noto Sans Arabic", "Noto Sans Hebrew", "Noto Sans Georgian"]; + const BUILT_IN: [&str; 7] = + ["Inter", "BIZ UDPGothic", "Noto Sans CJK SC", "Noto Sans Arabic", "Noto Sans Hebrew", "Noto Sans Georgian", "Noto Sans Thai"]; let lang = crate::i18n::current().map_or("en", |l| l.code); // A Chinese UI draws Han in a Chinese face: the built-in Japanese one goes after the system faces. let late = |family: &str| family == "BIZ UDPGothic" && crate::cjk::language(lang) == crate::cjk::HAN; @@ -524,4 +525,26 @@ mod tests { } }); } + + #[test] + fn ui_font_draws_thai_with_the_built_in_thai_face() { + // The web build has no system fonts: Thai layer names, frame labels and field text are drawn + // by craft-fonts' Noto Sans Thai, a built-in fallback, when the build embeds it. + if !figcraft_text::CRAFT_FONTS.iter().any(|f| f.family == "Noto Sans Thai") { + eprintln!("built without craft-fonts' Noto Sans Thai; skipping"); + return; + } + let ctx = Context::default(); + install_fonts(&ctx, false, 0); + let input = + egui::RawInput { screen_rect: Some(egui::Rect::from_min_size(egui::pos2(0.0, 0.0), egui::vec2(100.0, 100.0))), ..Default::default() }; + ctx.run_ui(input, |_| {}).textures_delta.clear(); + // "ภาษาไทย" (the Thai language) and "น้ำ" (water: a tone mark over SARA AM). + let thai = "\u{0E20}\u{0E32}\u{0E29}\u{0E32}\u{0E44}\u{0E17}\u{0E22} \u{0E19}\u{0E49}\u{0E33}"; + ctx.fonts_mut(|f| { + for font in [regular(11.0), semibold(11.0)] { + assert!(f.has_glyphs(&font, thai), "UI font draws {thai:?}"); + } + }); + } } diff --git a/crates/ui-egui/src/theme_startup_tests.rs b/crates/ui-egui/src/theme_startup_tests.rs index c128a597..60587acb 100644 --- a/crates/ui-egui/src/theme_startup_tests.rs +++ b/crates/ui-egui/src/theme_startup_tests.rs @@ -4,7 +4,7 @@ use egui::{Context, RawInput, Theme}; use figcraft_engine::{Prefs, Session}; use serde_json::json; -use crate::{FigApp, NoServices, theme::Tokens}; +use crate::{FigApp, NoServices, Services, theme::Tokens}; fn frame(ctx: &Context, app: &mut FigApp, system: Option) { let mut output = ctx.run_ui(RawInput { system_theme: system, ..Default::default() }, |ui| app.ui(ui)); @@ -84,6 +84,47 @@ fn unavailable_system_theme_uses_the_existing_light_fallback() { assert_eq!(Tokens::get(&ctx), Tokens::light()); } +/// Services whose colour scheme answer stands in for the platform's (winit gives no theme on Linux +/// and the BSDs, where the freedesktop colour scheme takes over). +struct Scheme(Option); +impl Services for Scheme { + fn system_dark(&self) -> Option { + self.0 + } +} + +#[test] +fn without_a_system_theme_the_colour_scheme_services_decide() { + for (dark, expected) in [(Some(true), Tokens::dark()), (Some(false), Tokens::light())] { + let ctx = Context::default(); + let mut app = FigApp::new(Session::empty(), Box::new(Scheme(dark))); + app.session.execute("view.themeSystem", &json!({})).unwrap(); + frame(&ctx, &mut app, None); + assert_eq!(Tokens::get(&ctx), expected); + assert_eq!(app.ui.dark, expected.dark); + } +} + +#[test] +fn the_os_theme_wins_over_the_colour_scheme_services() { + for (os, scheme) in [(Theme::Light, Some(true)), (Theme::Dark, Some(false))] { + let ctx = Context::default(); + let mut app = FigApp::new(Session::empty(), Box::new(Scheme(scheme))); + app.session.execute("view.themeSystem", &json!({})).unwrap(); + frame(&ctx, &mut app, Some(os)); + assert_eq!(Tokens::get(&ctx).dark, os == Theme::Dark); + } +} + +#[test] +fn manual_themes_ignore_the_colour_scheme_services() { + let ctx = Context::default(); + let mut app = FigApp::new(Session::empty(), Box::new(Scheme(Some(false)))); + app.session.execute("view.themeDark", &json!({})).unwrap(); + frame(&ctx, &mut app, None); + assert_eq!(Tokens::get(&ctx), Tokens::dark()); +} + /// Checks that the test graph enables egui's `default_fonts` and `monochrome_emoji_fonts` /// features by asserting both a Latin and an emoji glyph are available. #[test] diff --git a/docs/releasing.md b/docs/releasing.md index 3dbc0fbf..8ad7e614 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -68,6 +68,22 @@ FIGCRAFT_DEFAULT_SERVER=wss://design.example.org/ cargo build --release -p figcr The release workflow leaves it unset. The public server's terms are in [`online-sharing-terms.md`](online-sharing-terms.md). +## The default font + +New text starts in Inter (`figcraft_doc::DEFAULT_FONT`). A distribution whose users mostly write a +script Inter lacks (Thai, say) can name another family with `FIGCRAFT_DEFAULT_FONT` when building; +unset or empty means Inter. Pick a family the build embeds from craft-fonts (the web build has no +other fonts) or one its users have installed: + +```sh +FIGCRAFT_DEFAULT_FONT="Noto Sans Thai" cargo build --release -p figcraft +``` + +Files record each text's family, so the variable only changes what new text starts in: a file +saved by one build keeps its fonts in a build with another default. Like any other family, it +renders only where it is available (embedded or installed); elsewhere the text falls back to the +faces that are. The release workflow leaves it unset. + ## What gets built | Platform | Artifacts | Built on | diff --git a/docs/translating.md b/docs/translating.md index df4866c6..f44a90fc 100644 --- a/docs/translating.md +++ b/docs/translating.md @@ -17,7 +17,7 @@ Simplified. The catalogs: English (`en`, the source), and Spanish (`es`, with the Latin American overlay `es-419`), French (`fr`), German (`de`), Dutch (`nl`), Brazilian Portuguese (`pt-BR`), Japanese (`ja`), Korean (`ko`), Simplified Chinese (`zh-Hans`), Czech (`cs`), Indonesian (`id`), Finnish -(`fi`), Bengali (`bn`) and Vietnamese (`vi`), machine-drafted and waiting for native speakers' review. Polish (`pl`, +(`fi`), Bengali (`bn`), Italian (`it`) and Vietnamese (`vi`), machine-drafted and waiting for native speakers' review. Polish (`pl`, Polski) was reviewed and approved by Lucas (@lukasz-sklad). Georgian (`ka`) was written with AI assistance and reviewed by a native speaker, and stays a draft until a maintainer changes its status. Russian (`ru`, Русский) was reviewed and approved by VanTan; its catalog keeps Frame, Dev Mode and