From c8fde702fe957f6cf16cd8bdd982e5b13fa93ee3 Mon Sep 17 00:00:00 2001 From: RicSchonfelder Date: Wed, 7 Oct 2026 18:46:11 -0300 Subject: [PATCH] i18n: add Brazilian Portuguese (pt-br) UI translation --- crates/engine/src/prefs.rs | 10 +- crates/engine/src/tests_settings.rs | 4 + crates/ui-egui/src/i18n.rs | 797 ++++++++++++++++++++++++++-- crates/ui-egui/src/native_menu.rs | 13 +- docs/preferences.md | 2 +- 5 files changed, 778 insertions(+), 48 deletions(-) diff --git a/crates/engine/src/prefs.rs b/crates/engine/src/prefs.rs index a0d305e9..e7925eb7 100644 --- a/crates/engine/src/prefs.rs +++ b/crates/engine/src/prefs.rs @@ -44,7 +44,7 @@ macro_rules! page { } page!(General { - /// Interface language (`en` or `ja`). + /// Interface language (`en`, `ja` or `pt-br`). language: String = "en".into(), /// Levels of Undo (1–99). undo_levels: u32 = 32, @@ -417,7 +417,7 @@ impl Prefs { pub fn normalize(&mut self) { self.version = PREFS_VERSION; let g = &mut self.general; - if !matches!(g.language.as_str(), "en" | "ja") { + if !matches!(g.language.as_str(), "en" | "ja" | "pt-br") { g.language = "en".into(); } g.undo_levels = g.undo_levels.clamp(1, 99); @@ -458,8 +458,8 @@ impl Prefs { /// Set the value at a dotted key. The key must exist and the value must have its type. pub fn set(&mut self, key: &str, value: Value) -> Result<(), String> { - if key == "general.language" && !matches!(value.as_str(), Some("en" | "ja")) { - return Err("`general.language` expects `en` or `ja`".into()); + if key == "general.language" && !matches!(value.as_str(), Some("en" | "ja" | "pt-br")) { + return Err("`general.language` expects `en`, `ja` or `pt-br`".into()); } let mut v = serde_json::to_value(&*self).map_err(|e| e.to_string())?; let ptr = format!("/{}", key.replace('.', "/")); @@ -885,7 +885,7 @@ pub fn pages() -> Vec { id: "general", title: "General", items: vec![ - s("general.language", "Language", Kind::Choice(&[("English", "en"), ("日本語", "ja")]), true), + s("general.language", "Language", Kind::Choice(&[("English", "en"), ("日本語", "ja"), ("Português (Brasil)", "pt-br")]), true), s("general.undoLevels", "Levels of Undo", Kind::Int(1, 99, ""), true), s("general.pathPointSize", "Path Point and Handle Size", Kind::Int(3, 20, "px"), true), s("general.recentItems", "Recent Projects Shown", Kind::Int(1, 30, ""), true), diff --git a/crates/engine/src/tests_settings.rs b/crates/engine/src/tests_settings.rs index 4afa08fc..c3c5a1dc 100644 --- a/crates/engine/src/tests_settings.rs +++ b/crates/engine/src/tests_settings.rs @@ -494,6 +494,10 @@ fn interface_language_is_validated_persisted_and_backward_compatible() { assert_eq!(s.prefs.general.language, "en"); s.execute("prefs.set", json!({"key": "general.language", "value": "ja"})).unwrap(); assert_eq!(s.execute("prefs.get", json!({"key": "general.language"})).unwrap(), json!("ja")); + // pt-br is registered alongside en and ja, and round-trips through the file like any other. + s.execute("prefs.set", json!({"key": "general.language", "value": "pt-br"})).unwrap(); + assert_eq!(s.execute("prefs.get", json!({"key": "general.language"})).unwrap(), json!("pt-br")); + s.execute("prefs.set", json!({"key": "general.language", "value": "ja"})).unwrap(); let saved = store.read(PREFS_FILE).unwrap(); for bad in [json!("fr"), json!(""), json!(17), json!(null)] { assert!(s.execute("prefs.set", json!({"key": "general.language", "value": bad})).is_err()); diff --git a/crates/ui-egui/src/i18n.rs b/crates/ui-egui/src/i18n.rs index bdbeafb3..c29708e0 100644 --- a/crates/ui-egui/src/i18n.rs +++ b/crates/ui-egui/src/i18n.rs @@ -1,26 +1,49 @@ -//! Japanese menu labels for EffectCraft's own engine tree. Commands remain locale-independent. -//! A command plus its source label distinguishes parameterised entries (e.g. blend modes). -//! Empty command keys identify submenus. Dynamic filenames, user names and effect plug-in names -//! remain verbatim; this catalog only translates the fixed menu tree. +//! Menu labels for EffectCraft's own engine tree in each translated language. Commands remain +//! locale-independent. A command plus its source label distinguishes parameterised entries (e.g. +//! blend modes). Empty command keys identify submenus. Dynamic filenames, user names and effect +//! plug-in names remain verbatim; these catalogs only translate the fixed menu tree. use crate::EffectcraftApp; use effectcraft_engine::menus::MenuEntry; +/// The active UI language: the stored preference when it is one of the translated languages, +/// otherwise `en` (English is the source of every label and needs no lookup). +pub(crate) fn language(app: &EffectcraftApp) -> &str { + let lang = app.session.prefs.general.language.as_str(); + if matches!(lang, "ja" | "pt-br") { lang } else { "en" } +} + +/// The UI is in Japanese: the fast path for the menu builders. pub(crate) fn japanese(app: &EffectcraftApp) -> bool { - app.session.prefs.general.language == "ja" + language(app) == "ja" +} + +/// `source` looked up in `catalog` by (command, English label); the source itself when the pair +/// is absent, so computed labels and user names stay verbatim. +fn lookup<'a>(catalog: &[(&str, &str, &'a str)], command: &str, source: &'a str) -> &'a str { + catalog.iter().find(|(id, en, _)| *id == command && *en == source).map(|(_, _, tr)| *tr).unwrap_or(source) } pub(crate) fn label<'a>(app: &EffectcraftApp, command: &str, source: &'a str) -> &'a str { - if japanese(app) { JAPANESE.iter().find(|(id, en, _)| *id == command && *en == source).map(|(_, _, ja)| *ja).unwrap_or(source) } else { source } + match language(app) { + "ja" => lookup(JAPANESE, command, source), + "pt-br" => lookup(PORTUGUESE, command, source), + _ => source, + } } pub(crate) fn entry(app: &EffectcraftApp, e: &MenuEntry, shown: String) -> String { - if !japanese(app) { - return shown; - } + // Undo/redo names come from the history, so they are formatted per language here rather + // than looked up in the catalog. + let t = match language(app) { + "ja" => ("取り消し", "取り消しできません", "やり直し", "やり直しできません"), + "pt-br" => ("Desfazer", "Não há nada para desfazer", "Refazer", "Não há nada para refazer"), + _ => return shown, + }; + let (undo, no_undo, redo, no_redo) = t; match e.command.as_str() { - "edit.undo" => app.session.history.undo.last().map(|u| format!("取り消し {}", u.0)).unwrap_or_else(|| "取り消しできません".into()), - "edit.redo" => app.session.history.redo.last().map(|u| format!("やり直し {}", u.0)).unwrap_or_else(|| "やり直しできません".into()), + "edit.undo" => app.session.history.undo.last().map(|u| format!("{undo} {}", u.0)).unwrap_or_else(|| no_undo.into()), + "edit.redo" => app.session.history.redo.last().map(|u| format!("{redo} {}", u.0)).unwrap_or_else(|| no_redo.into()), "window.panel" => { let prefix = format!("{}: ", e.label); if let Some(name) = shown.strip_prefix(&prefix) { @@ -37,16 +60,30 @@ pub(crate) fn entry(app: &EffectcraftApp, e: &MenuEntry, shown: String) -> Strin } pub(crate) fn submenu(app: &EffectcraftApp, source: &str, shown: String) -> String { - if japanese(app) { - if source == "Assign Shortcut to Workspace" { - if let Some(name) = shown.strip_prefix("Assign Shortcut to “").and_then(|s| s.strip_suffix("” Workspace")) { - return format!("ワークスペース「{name}」にショートカットを割り当て"); + match language(app) { + "ja" => { + if source == "Assign Shortcut to Workspace" { + if let Some(name) = shown.strip_prefix("Assign Shortcut to “").and_then(|s| s.strip_suffix("” Workspace")) { + return format!("ワークスペース「{name}」にショートカットを割り当て"); + } + } else if source == "Assign Shortcut to 3D View" + && let Some(name) = shown.strip_prefix("Assign Shortcut to “").and_then(|s| s.strip_suffix('”')) + { + return format!("「{name}」にショートカットを割り当て"); } - } else if source == "Assign Shortcut to 3D View" - && let Some(name) = shown.strip_prefix("Assign Shortcut to “").and_then(|s| s.strip_suffix('”')) - { - return format!("「{name}」にショートカットを割り当て"); } + "pt-br" => { + if source == "Assign Shortcut to Workspace" { + if let Some(name) = shown.strip_prefix("Assign Shortcut to “").and_then(|s| s.strip_suffix("” Workspace")) { + return format!("Atribuir atalho à área de trabalho “{name}”"); + } + } else if source == "Assign Shortcut to 3D View" + && let Some(name) = shown.strip_prefix("Assign Shortcut to “").and_then(|s| s.strip_suffix('”')) + { + return format!("Atribuir atalho à visualização 3D “{name}”"); + } + } + _ => {} } label(app, "", &shown).into() } @@ -671,6 +708,628 @@ const JAPANESE: &[(&str, &str, &str)] = &[ ("file.openDemoProject", "Open Demo Project", "デモプロジェクトを開く"), ]; +/// Brazilian Portuguese: the same (command, English label) keys as `JAPANESE`, one entry +/// per fixed menu row of the engine's menu tree. +const PORTUGUESE: &[(&str, &str, &str)] = &[ + ("", "EffectCraft", "EffectCraft"), + ("app.about", "About EffectCraft...", "Sobre o EffectCraft..."), + ("", "Settings...", "Configurações..."), + ("app.settings", "General...", "Geral..."), + ("app.settings", "Startup & Repair...", "Inicialização e reparo..."), + ("app.settings", "Project...", "Projeto..."), + ("app.settings", "Composition...", "Composição..."), + ("app.settings", "Previews...", "Pré-visualizações..."), + ("app.settings", "Appearance...", "Aparência..."), + ("app.settings", "Grids & Guides...", "Grade e guias..."), + ("app.settings", "Labels...", "Rótulos..."), + ("app.settings", "Type...", "Texto..."), + ("app.settings", "Import...", "Importação..."), + ("app.settings", "Export...", "Exportação..."), + ("app.settings", "Audio...", "Áudio..."), + ("app.settings", "Disk...", "Disco..."), + ("app.settings", "Memory & CPU...", "Memória e CPU..."), + ("app.settings", "Video...", "Vídeo..."), + ("app.settings", "3D...", "3D..."), + ("app.settings", "Scripting & Expressions...", "Scripts e expressões..."), + ("app.hide", "Hide EffectCraft", "Ocultar EffectCraft"), + ("app.hideOthers", "Hide Others", "Ocultar outros"), + ("app.showAll", "Show All", "Mostrar tudo"), + ("app.quit", "Quit EffectCraft", "Sair do EffectCraft"), + ("", "File", "Arquivo"), + ("", "New", "Novo"), + ("file.newProject", "New Project", "Novo projeto"), + ("file.newFromTemplate", "New Project from Template...", "Novo projeto a partir de modelo..."), + ("project.newFolder", "New Folder", "Nova pasta"), + ("file.open", "Open Project...", "Abrir projeto..."), + ("", "Open Recent", "Abrir recentes"), + ("file.clearRecent", "Clear Recent Projects", "Limpar projetos recentes"), + ("file.close", "Close", "Fechar"), + ("file.closeProject", "Close Project", "Fechar projeto"), + ("file.save", "Save", "Salvar"), + ("", "Save As", "Salvar como"), + ("file.saveAs", "Save As...", "Salvar como..."), + ("file.saveCopy", "Save a Copy...", "Salvar uma cópia..."), + ("file.saveCopyAsXml", "Save a Copy As XML...", "Salvar uma cópia como XML..."), + ("templates.saveAs", "Save as Template...", "Salvar como modelo..."), + ("file.incrementAndSave", "Increment and Save", "Incrementar e salvar"), + ("file.revert", "Revert", "Reverter"), + ("", "Import", "Importar"), + ("file.import", "File...", "Arquivo..."), + ("file.importMultiple", "Multiple Files...", "Vários arquivos..."), + ("file.importTimeline", "Adobe Premiere Pro Project...", "Projeto do Adobe Premiere Pro..."), + ("file.importPlaceholder", "Placeholder...", "Espaço reservado..."), + ("file.importSolid", "Solid...", "Sólido..."), + ("file.importLottie", "Lottie...", "Lottie..."), + ("file.importVanishingPoint", "Vanishing Point (.vpe)...", "Vanishing Point (.vpe)..."), + ("essential.importTemplate", "Essential Graphics Template...", "Modelo de gráficos essenciais..."), + ("", "Import Recent Footage", "Importar mídias recentes"), + ("file.clearRecentFootage", "Clear Recent Footage", "Limpar mídias recentes"), + ("", "Export", "Exportar"), + ("renderQueue.add", "Add to Render Queue", "Adicionar à fila de renderização"), + ("file.exportTimeline", "Adobe Premiere Pro Project...", "Projeto do Adobe Premiere Pro..."), + ("file.exportLottie", "Lottie JSON...", "Lottie JSON..."), + ("essential.exportTemplate", "Essential Graphics Template...", "Modelo de gráficos essenciais..."), + ("app.find", "Find", "Localizar"), + ("layer.addItem", "Add Footage to Comp", "Adicionar mídia à composição"), + ("file.newCompFromSelection", "New Comp from Selection...", "Nova composição a partir da seleção..."), + ("", "Dependencies", "Dependências"), + ("file.collectFiles", "Collect Files...", "Coletar arquivos..."), + ("file.consolidateFootage", "Consolidate All Footage", "Consolidar todas as mídias"), + ("file.removeUnusedFootage", "Remove Unused Footage", "Remover mídias não utilizadas"), + ("file.reduceProject", "Reduce Project", "Reduzir projeto"), + ("file.findMissing", "Find Missing Effects", "Localizar efeitos ausentes"), + ("file.findMissing", "Find Missing Fonts", "Localizar fontes ausentes"), + ("file.findMissing", "Find Missing Footage", "Localizar mídias ausentes"), + ("file.watchFolder", "Watch Folder...", "Pasta monitorada..."), + ("", "Scripts", "Scripts"), + ("file.installScript", "Install Script File...", "Instalar arquivo de script..."), + ("file.installScriptUIPanel", "Install ScriptUI Panel...", "Instalar painel ScriptUI..."), + ("file.runScript", "Run Script File...", "Executar arquivo de script..."), + ("", "Create Proxy", "Criar proxy"), + ("file.createProxy", "Still...", "Imagem fixa..."), + ("file.createProxy", "Movie...", "Filme..."), + ("", "Set Proxy", "Definir proxy"), + ("file.setProxy", "File...", "Arquivo..."), + ("file.setProxyNone", "None", "Nenhum"), + ("", "Interpret Footage", "Interpretar mídia"), + ("file.interpretFootage", "Main...", "Principal..."), + ("file.interpretProxy", "Proxy...", "Proxy..."), + ("file.rememberInterpretation", "Remember Interpretation", "Lembrar interpretação"), + ("file.applyInterpretation", "Apply Interpretation", "Aplicar interpretação"), + ("", "Replace Footage", "Substituir mídia"), + ("file.replaceFootage", "File...", "Arquivo..."), + ("file.replaceWithPlaceholder", "Placeholder...", "Espaço reservado..."), + ("file.replaceWithSolid", "Solid...", "Sólido..."), + ("file.replaceWithLayeredComp", "With Layered Comp", "Por composição com camadas"), + ("file.reloadFootage", "Reload Footage", "Recarregar mídia"), + ("file.revealInFinder", "Reveal in Finder", "Mostrar no Finder"), + ("file.projectSettings", "Project Settings...", "Configurações do projeto..."), + ("app.quit", "Exit", "Sair"), + ("", "Edit", "Editar"), + ("edit.undo", "Undo", "Desfazer"), + ("edit.redo", "Redo", "Refazer"), + ("", "History", "Histórico"), + ("app.commandPalette", "Quick Apply...", "Aplicação rápida..."), + ("edit.cut", "Cut", "Recortar"), + ("edit.copy", "Copy", "Copiar"), + ("edit.copyWithPropertyLinks", "Copy with Property Links", "Copiar com vínculos de propriedade"), + ("edit.copyWithRelativePropertyLinks", "Copy with Relative Property Links", "Copiar com vínculos de propriedade relativos"), + ("edit.copyExpressionOnly", "Copy Expression Only", "Copiar apenas a expressão"), + ("edit.paste", "Paste", "Colar"), + ("edit.pasteReversedKeyframes", "Paste Reversed Keyframes", "Colar quadros-chave em ordem inversa"), + ("edit.pasteTextMatchFormatting", "Paste Text and Match Formatting", "Colar texto e manter a formatação"), + ("edit.pasteTextFormattingOnly", "Paste Text Formatting Only", "Colar apenas a formatação do texto"), + ("edit.clear", "Clear", "Limpar"), + ("edit.duplicate", "Duplicate", "Duplicar"), + ("edit.splitLayer", "Split Layer", "Dividir camada"), + ("edit.liftWorkArea", "Lift Work Area", "Retirar área de trabalho"), + ("edit.extractWorkArea", "Extract Work Area", "Extrair área de trabalho"), + ("edit.selectAll", "Select All", "Selecionar tudo"), + ("edit.deselectAll", "Deselect All", "Desmarcar tudo"), + ("", "Label", "Rótulo"), + ("edit.selectLabelGroup", "Select Label Group", "Selecionar grupo de rótulos"), + ("edit.label", "None", "Nenhum"), + ("edit.label", "Red", "Vermelho"), + ("edit.label", "Yellow", "Amarelo"), + ("edit.label", "Aqua", "Aqua"), + ("edit.label", "Pink", "Rosa"), + ("edit.label", "Lavender", "Lavanda"), + ("edit.label", "Peach", "Pêssego"), + ("edit.label", "Sea Foam", "Espuma do mar"), + ("edit.label", "Blue", "Azul"), + ("edit.label", "Green", "Verde"), + ("edit.label", "Purple", "Roxo"), + ("edit.label", "Orange", "Laranja"), + ("edit.label", "Brown", "Marrom"), + ("edit.label", "Fuchsia", "Fúcsia"), + ("edit.label", "Cyan", "Ciano"), + ("edit.label", "Sandstone", "Arenito"), + ("edit.label", "Dark Green", "Verde escuro"), + ("app.settings", "Edit Label Colors...", "Editar cores dos rótulos..."), + ("", "Select Keyframe Label Group", "Selecionar grupo de rótulos de quadros-chave"), + ("keys.selectLabelGroup", "On Selected Layers", "Nas camadas selecionadas"), + ("keys.selectLabelGroup", "On All Layers", "Em todas as camadas"), + ("keys.selectLabelGroup", "Visible Keyframes on Selected Layers", "Quadros-chave visíveis nas camadas selecionadas"), + ("keys.selectLabelGroup", "Visible Keyframes on All Layers", "Quadros-chave visíveis em todas as camadas"), + ("", "Purge", "Limpar cache"), + ("edit.purge", "All Cache...", "Todo o cache..."), + ("edit.purge", "All Memory & Disk Cache...", "Toda a memória e o cache do disco..."), + ("edit.purge", "All Memory", "Toda a memória"), + ("edit.purge", "All Disk Cache...", "Todo o cache do disco..."), + ("edit.purge", "All 3D Cache...", "Todo o cache 3D..."), + ("edit.purgeUndo", "Undo", "Desfazer"), + ("edit.purge", "Image Cache Memory", "Memória de cache de imagens"), + ("edit.purge", "Snapshot", "Instantâneo"), + ("edit.editOriginal", "Edit Original...", "Editar original..."), + ("", "Templates", "Modelos"), + ("app.templates", "Render Settings...", "Configurações de renderização..."), + ("app.templates", "Output Module...", "Módulo de saída..."), + ("app.keyboardShortcuts", "Keyboard Shortcuts", "Atalhos de teclado"), + ("", "Preferences", "Preferências"), + ("", "Composition", "Composição"), + ("comp.new", "New Composition...", "Nova composição..."), + ("comp.settings", "Composition Settings...", "Configurações da composição..."), + ("comp.setPosterTime", "Set Poster Time", "Definir tempo do pôster"), + ("comp.trimToWorkArea", "Trim Comp to Work Area", "Ajustar composição à área de trabalho"), + ("comp.cropToRegionOfInterest", "Crop Comp to Region of Interest", "Recortar composição para a região de interesse"), + ("comp.cropToLayerBounds", "Crop Comp to Selected Layer(s) Bounds", "Recortar composição aos limites das camadas selecionada(s)"), + ("render.addOutputModule", "Add Output Module", "Adicionar módulo de saída"), + ("", "Preview", "Pré-visualização"), + ("playback.toggle", "Play Current Preview", "Reproduzir pré-visualização atual"), + ("playback.cacheWhenIdle", "Cache Frames When Idle", "Armazenar quadros quando ocioso"), + ("playback.audio", "Audio", "Áudio"), + ("", "Save Frame As", "Salvar quadro como"), + ("comp.saveFrameAs", "File...", "Arquivo..."), + ("comp.saveFrameAsPsd", "Photoshop Layers...", "Camadas do Photoshop..."), + ("comp.saveFrameAsExr", "ProEXR...", "ProEXR..."), + ("render.preRender", "Pre-render...", "Pré-renderização..."), + ("render.saveCurrentPreview", "Save Current Preview...", "Salvar pré-visualização atual..."), + ("comp.openInEssentialGraphics", "Open in Essential Graphics", "Abrir em Gráficos essenciais"), + ("", "Responsive Design — Time", "Design responsivo — Tempo"), + ("comp.responsiveTime", "Create Intro", "Criar introdução"), + ("comp.responsiveTime", "Create Outro", "Criar encerramento"), + ("comp.responsiveTime", "Create Protected Region from Work Area", "Criar região protegida a partir da área de trabalho"), + ("comp.flowchart", "Composition Flowchart", "Fluxograma da composição"), + ("comp.miniFlowchart", "Composition Mini-Flowchart", "Minifluxograma da composição"), + ("", "VR", "VR"), + ("comp.vr.createEnvironment", "Create VR Environment...", "Criar ambiente VR..."), + ("comp.vr.extractCubemap", "Extract Cubemap...", "Extrair cubemapa..."), + ("", "Layer", "Camada"), + ("layer.newText", "Text", "Texto"), + ("layer.newSolid", "Solid...", "Sólido..."), + ("layer.newLight", "Light...", "Luz..."), + ("layer.newCamera", "Camera...", "Câmera..."), + ("layer.newNull", "Null Object", "Objeto nulo"), + ("layer.newShape", "Shape Layer", "Camada de forma"), + ("layer.newAdjustment", "Adjustment Layer", "Camada de ajuste"), + ("layer.newContentAwareFill", "Content-Aware Fill Layer...", "Camada de preenchimento sensível ao conteúdo..."), + ("layer.new3dPrimitive", "Cube", "Cubo"), + ("layer.new3dPrimitive", "Sphere", "Esfera"), + ("layer.new3dPrimitive", "Plane", "Plano"), + ("layer.new3dPrimitive", "Torus", "Toro"), + ("layer.new3dPrimitive", "Cone", "Cone"), + ("layer.new3dPrimitive", "Cylinder", "Cilindro"), + ("layer.settings", "Layer Settings...", "Configurações da camada..."), + ("layer.openLayer", "Open Layer", "Abrir camada"), + ("layer.openSource", "Open Layer Source", "Abrir a fonte da camada"), + ("layer.revealInFinder", "Reveal in Finder", "Mostrar no Finder"), + ("", "Mask", "Máscara"), + ("layer.addMask", "New Mask", "Nova máscara"), + ("layer.mask.shape", "Mask Shape...", "Forma da máscara..."), + ("layer.mask.set", "Mask Feather...", "Pena da máscara..."), + ("layer.mask.set", "Mask Opacity...", "Opacidade da máscara..."), + ("layer.mask.set", "Mask Expansion...", "Expansão da máscara..."), + ("layer.mask.reset", "Reset Mask", "Redefinir máscara"), + ("layer.mask.remove", "Remove Mask", "Remover máscara"), + ("layer.mask.removeAll", "Remove All Masks", "Remover todas as máscaras"), + ("track.mask", "Track Mask", "Rastrear máscara"), + ("", "Mode", "Modo"), + ("layer.mask.mode", "None", "Nenhum"), + ("layer.mask.mode", "Add", "Adicionar"), + ("layer.mask.mode", "Subtract", "Subtrair"), + ("layer.mask.mode", "Intersect", "Interseção"), + ("layer.mask.mode", "Lighten", "Clarear"), + ("layer.mask.mode", "Darken", "Escurecer"), + ("layer.mask.mode", "Difference", "Diferença"), + ("layer.mask.invert", "Inverted", "Invertida"), + ("layer.mask.lock", "Locked", "Bloqueada"), + ("", "Motion Blur", "Borrão de movimento"), + ("layer.mask.motionBlur", "Same As Layer", "Igual à camada"), + ("layer.mask.motionBlur", "On", "Ativado"), + ("layer.mask.motionBlur", "Off", "Desativado"), + ("", "Feather Falloff", "Decaimento da pena"), + ("layer.mask.featherFalloff", "Smooth", "Suavizar"), + ("layer.mask.featherFalloff", "Linear", "Linear"), + ("layer.mask.unlockAll", "Unlock All Masks", "Desbloquear todas as máscaras"), + ("layer.mask.lockOthers", "Lock Other Masks", "Bloquear outras máscaras"), + ("layer.mask.hideLocked", "Hide Locked Masks", "Ocultar máscaras bloqueadas"), + ("", "Mask and Shape Path", "Caminho da máscara e da forma"), + ("path.rotoBezier", "RotoBezier", "RotoBezier"), + ("mask.setClosed", "Closed", "Fechado"), + ("path.convertToBezier", "Convert To Bezier Path", "Converter em caminho Bézier"), + ("path.setFirstVertex", "Set First Vertex", "Definir primeiro vértice"), + ("path.freeTransform", "Free Transform Points", "Pontos de transformação livre"), + ("", "Quality", "Qualidade"), + ("layer.quality", "Best", "Melhor"), + ("layer.quality", "Draft", "Rascunho"), + ("layer.quality", "Wireframe", "Wireframe"), + ("layer.sampling", "Bilinear", "Bilinear"), + ("layer.sampling", "Bicubic", "Bicúbico"), + ("", "Switches", "Interruptores"), + ("layer.hideOtherVideo", "Hide Other Video", "Ocultar os outros vídeos"), + ("layer.showAllVideo", "Show All Video", "Mostrar todos os vídeos"), + ("layer.unlockAll", "Unlock All Layers", "Desbloquear todas as camadas"), + ("layer.expressions", "Enable Expressions", "Ativar expressões"), + ("layer.expressions", "Disable Expressions", "Desativar expressões"), + ("layer.setSwitch", "Shy", "Escondida"), + ("layer.setSwitch", "Lock", "Bloquear"), + ("layer.setSwitch", "Audio", "Áudio"), + ("layer.setSwitch", "Video", "Vídeo"), + ("layer.setSwitch", "Solo", "Solo"), + ("layer.setSwitch", "Effect", "Efeito"), + ("layer.setSwitch", "Collapse", "Colapsar"), + ("layer.setSwitch", "Motion Blur", "Borrão de movimento"), + ("layer.setSwitch", "Adjustment Layer", "Camada de ajuste"), + ("", "Transform", "Transformar"), + ("layer.transform", "Reset", "Redefinir"), + ("layer.setTransform", "Anchor Point...", "Ponto de ancoragem..."), + ("layer.setTransform", "Position...", "Posição..."), + ("layer.setTransform", "Scale...", "Escala..."), + ("layer.setTransform", "Orientation...", "Orientação..."), + ("layer.setTransform", "Rotation...", "Rotação..."), + ("layer.setTransform", "Opacity...", "Opacidade..."), + ("layer.transform", "Flip Horizontal", "Espelhar horizontalmente"), + ("layer.transform", "Flip Vertical", "Espelhar verticalmente"), + ("layer.transform", "Center In View", "Centralizar na visualização"), + ("layer.centerAnchor", "Center Anchor Point in Layer Content", "Centralizar ponto de ancoragem no conteúdo da camada"), + ("layer.transform", "Fit to Comp", "Ajustar à composição"), + ("layer.transform", "Fit to Comp Width", "Ajustar à largura da composição"), + ("layer.transform", "Fit to Comp Height", "Ajustar à altura da composição"), + ("layer.autoOrient", "Auto-Orient...", "Auto-orientar..."), + ("", "Time", "Tempo"), + ("layer.enableTimeRemap", "Enable Time Remapping", "Ativar remapeamento de tempo"), + ("layer.timeReverse", "Time-Reverse Layer", "Inverter tempo da camada"), + ("layer.timeStretch", "Time Stretch...", "Redefinir duração..."), + ("layer.freezeFrame", "Freeze Frame", "Congelar quadro"), + ("layer.freezeOnLastFrame", "Freeze On Last Frame", "Congelar no último quadro"), + ("layer.alignVideoToData", "Align Video to Data", "Alinhar vídeo aos dados"), + ("", "Frame Blending", "Blenda de quadros"), + ("layer.frameBlending", "Off", "Desativado"), + ("layer.frameBlending", "Frame Mix", "Mistura de quadros"), + ("layer.frameBlending", "Pixel Motion", "Movimento de pixels"), + ("layer.setSwitch", "3D Layer", "Camada 3D"), + ("layer.setSwitch", "Guide Layer", "Camada de guia"), + ("layer.environment", "Environment Layer", "Camada de ambiente"), + ("", "Markers", "Marcadores"), + ("layer.addMarker", "Add Marker", "Adicionar marcador"), + ("layer.updateMarkersFromSource", "Update Markers From Source", "Atualizar marcadores da origem"), + ("layer.markersLock", "Lock Markers", "Bloquear marcadores"), + ("layer.deleteAllMarkers", "Delete All Markers", "Excluir todos os marcadores"), + ("layer.setSwitch", "Preserve Transparency", "Preservar transparência"), + ("", "Blending Mode", "Modo de mesclagem"), + ("layer.setBlendMode", "Normal", "Normal"), + ("layer.setBlendMode", "Dissolve", "Dissolver"), + ("layer.setBlendMode", "Dancing Dissolve", "Dissolver dinâmico"), + ("layer.setBlendMode", "Darken", "Escurecer"), + ("layer.setBlendMode", "Multiply", "Multiplicar"), + ("layer.setBlendMode", "Color Burn", "Superexposição de cores"), + ("layer.setBlendMode", "Classic Color Burn", "Superexposição de cores clássica"), + ("layer.setBlendMode", "Linear Burn", "Superexposição linear"), + ("layer.setBlendMode", "Darker Color", "Cor mais escura"), + ("layer.setBlendMode", "Add", "Adição"), + ("layer.setBlendMode", "Lighten", "Clarear"), + ("layer.setBlendMode", "Screen", "Divisão"), + ("layer.setBlendMode", "Color Dodge", "Subexposição de cores"), + ("layer.setBlendMode", "Classic Color Dodge", "Subexposição de cores clássica"), + ("layer.setBlendMode", "Linear Dodge", "Subexposição linear"), + ("layer.setBlendMode", "Lighter Color", "Cor mais clara"), + ("layer.setBlendMode", "Overlay", "Sobrepor"), + ("layer.setBlendMode", "Soft Light", "Luz suave"), + ("layer.setBlendMode", "Hard Light", "Luz forte"), + ("layer.setBlendMode", "Linear Light", "Luz linear"), + ("layer.setBlendMode", "Vivid Light", "Luz viva"), + ("layer.setBlendMode", "Pin Light", "Luz pontual"), + ("layer.setBlendMode", "Hard Mix", "Mistura dura"), + ("layer.setBlendMode", "Difference", "Diferença"), + ("layer.setBlendMode", "Classic Difference", "Diferença clássica"), + ("layer.setBlendMode", "Exclusion", "Exclusão"), + ("layer.setBlendMode", "Subtract", "Subtrair"), + ("layer.setBlendMode", "Divide", "Dividir"), + ("layer.setBlendMode", "Hue", "Matiz"), + ("layer.setBlendMode", "Saturation", "Saturação"), + ("layer.setBlendMode", "Color", "Cor"), + ("layer.setBlendMode", "Luminosity", "Luminosidade"), + ("layer.setBlendMode", "Stencil Alpha", "Estêncil alfa"), + ("layer.setBlendMode", "Stencil Luma", "Estêncil luminância"), + ("layer.setBlendMode", "Silhouette Alpha", "Silhueta alfa"), + ("layer.setBlendMode", "Silhouette Luma", "Silhueta luminância"), + ("layer.setBlendMode", "Alpha Add", "Adição alfa"), + ("layer.setBlendMode", "Luminescent Premul", "Pré-multiplicação luminescente"), + ("layer.setBlendMode", "Next Blending Mode", "Modo de mesclagem seguinte"), + ("layer.setBlendMode", "Previous Blending Mode", "Modo de mesclagem anterior"), + ("", "Track Matte", "Track matte"), + ("layer.trackMatte", "No Track Matte", "Sem track matte"), + ("layer.trackMatte", "Alpha Matte", "Matte alfa"), + ("layer.trackMatte", "Alpha Inverted Matte", "Matte alfa invertida"), + ("layer.trackMatte", "Luma Matte", "Matte de luminância"), + ("layer.trackMatte", "Luma Inverted Matte", "Matte de luminância invertida"), + ("layer.trackMatte", "Matte with Layer Above", "Matte com a camada acima"), + ("layer.trackMatte", "Matte with Layer Below", "Matte com a camada abaixo"), + ("", "Layer Styles", "Estilos de camada"), + ("layer.style.options", "Layer Style Options...", "Opções de estilo de camada..."), + ("layer.style.convertToEditable", "Convert to Editable Styles", "Converter em estilos editáveis"), + ("layer.style.showAll", "Show All", "Mostrar todos"), + ("layer.style.removeAll", "Remove All", "Remover todos"), + ("layer.style.dropShadow", "Drop Shadow", "Sombra projetada"), + ("layer.style.innerShadow", "Inner Shadow", "Sombra interna"), + ("layer.style.outerGlow", "Outer Glow", "Brilho externo"), + ("layer.style.innerGlow", "Inner Glow", "Brilho interno"), + ("layer.style.bevelEmboss", "Bevel and Emboss", "Chanfro e relevo"), + ("layer.style.satin", "Satin", "Satin"), + ("layer.style.colorOverlay", "Color Overlay", "Sobreposição de cores"), + ("layer.style.gradientOverlay", "Gradient Overlay", "Sobreposição de gradiente"), + ("layer.style.stroke", "Stroke", "Traçado"), + ("path.groupShapes", "Group Shapes", "Agrupar formas"), + ("path.ungroupShapes", "Ungroup Shapes", "Desagrupar formas"), + ("", "Arrange", "Organizar"), + ("layer.arrange", "Bring Layer to Front", "Trazer camada para o topo"), + ("layer.arrange", "Bring Layer Forward", "Trazer camada para a frente"), + ("layer.arrange", "Send Layer Backward", "Enviar camada para trás"), + ("layer.arrange", "Send Layer to Back", "Enviar camada para o fundo"), + ("", "Reveal", "Revelar"), + ("layer.revealSource", "Reveal Layer Source in Project", "Revelar origem da camada no projeto"), + ("comp.flowchart", "Reveal Layer in Project Flowchart", "Revelar camada no fluxograma do projeto"), + ("comp.revealInProject", "Reveal Composition in Project", "Revelar composição no projeto"), + ("layer.revealExpressionErrors", "Reveal Expression Errors", "Revelar erros de expressão"), + ("", "Create", "Criar"), + ("layer.create", "Convert to Editable Text", "Converter em texto editável"), + ("layer.create", "Create Shapes from Text", "Criar formas a partir do texto"), + ("layer.create", "Create Masks from Text", "Criar máscaras a partir do texto"), + ("layer.create", "Create Shapes from Vector Layer", "Criar formas a partir da camada vetorial"), + ("layer.create", "Create Keyframes from Data", "Criar quadros-chave a partir de dados"), + ("layer.create", "Null Controllers for Positional Points", "Controles null para pontos posicionais"), + ("layer.create", "Null Controllers for Path Points", "Controles null para pontos do caminho"), + ("layer.create", "Nulls Following Path Points", "Nulls seguindo pontos do caminho"), + ("layer.create", "Null Tracing Along Path", "Null traçando ao longo do caminho"), + ("layer.create", "Create 3D Layer Instance", "Criar instância de camada 3D"), + ("", "Camera", "Câmera"), + ("camera.fromView", "Create Camera from 3D View", "Criar câmera a partir da visualização 3D"), + ("camera.stereoRig", "Create Stereo 3D Rig", "Criar estrutura 3D estéreo"), + ("camera.orbitNull", "Create Orbit Null", "Criar null de órbita"), + ("camera.fromModel", "Create Cameras from 3D Model", "Criar câmeras a partir de modelo 3D"), + ("camera.linkFocusToPoi", "Link Focus Distance to Point of Interest", "Vincular distância de foco ao ponto de interesse"), + ("camera.linkFocusToLayer", "Link Focus Distance to Layer", "Vincular distância de foco à camada"), + ("camera.setFocusToLayer", "Set Focus Distance to Layer", "Definir distância de foco para a camada"), + ("layer.cameraSettings", "Camera Settings...", "Configurações da câmera..."), + ("view.reset3DView", "Reset 3D View", "Redefinir visualização 3D"), + ("", "Light", "Luz"), + ("light.fromModel", "Create Lights from 3D Model", "Criar luzes a partir de modelo 3D"), + ("light.controlWithCamera", "Control Light with Camera", "Controlar luz com a câmera"), + ("light.environmentBackground", "Create Environment Light Background Layer", "Criar camada de fundo de luz ambiente"), + ("", "Material", "Material"), + ("material.revealSource", "Reveal Material Source in Project", "Revelar origem do material no projeto"), + ("material.reset", "Reset Material", "Redefinir material"), + ("material.duplicateAssign", "Duplicate and Assign Material", "Duplicar e atribuir material"), + ("layer.autoTrace", "Auto-trace...", "Traçado automático..."), + ("layer.precompose", "Pre-compose...", "Pré-compor..."), + ("layer.sceneEditDetection", "Scene Edit Detection...", "Detecção de edição de cena..."), + ("", "Effect", "Efeito"), + ("window.panel", "Effect Controls", "Controles de efeito"), + ("effect.applyLast", "Last Effect", "Último efeito"), + ("effect.removeAll", "Remove All", "Remover todos"), + ("effect.manage", "Manage Effects...", "Gerenciar efeitos..."), + ("effect.plugins.load", "Load Effect Plug-in...", "Carregar plug-in de efeito..."), + ("", "Animation", "Animação"), + ("anim.savePreset", "Save Animation Preset...", "Salvar predefinição de animação..."), + ("anim.applyPreset", "Apply Animation Preset...", "Aplicar predefinição de animação..."), + ("", "Recent Animation Presets", "Predefinições de animação recentes"), + ("anim.clearRecentPresets", "Clear Recent Presets", "Limpar predefinições recentes"), + ("anim.browsePresets", "Browse Presets...", "Procurar predefinições..."), + ("", "Text Animation Presets", "Predefinições de animação de texto"), + ("layer.applyTextPreset", "Typewriter", "Máquina de escrever"), + ("layer.applyTextPreset", "Fade Up Characters", "Aparecer caractere a caractere"), + ("layer.applyTextPreset", "Bounce In Words", "Entrar quicando nas palavras"), + ("layer.applyTextPreset", "Tracking In", "Entrar com espaçamento"), + ("layer.applyTextPreset", "Scramble", "Embaralhar"), + ("layer.applyTextPreset", "Blur In", "Entrar desfocado"), + ("layer.applyTextPreset", "Jitter", "Tremor"), + ("layer.applyTextPreset", "Drop In Lines", "Entrar com queda de linhas"), + ("anim.addKeyframe", "Add Keyframe", "Adicionar quadro-chave"), + ("keys.toggleHold", "Toggle Hold Keyframe", "Alternar quadro-chave de retenção"), + ("keys.interpolation", "Keyframe Interpolation...", "Interpolação de quadro-chave..."), + ("keys.velocity", "Keyframe Velocity...", "Velocidade do quadro-chave..."), + ("", "Keyframe Assistant", "Assistente de quadro-chave"), + ("keys.audioToKeyframes", "Convert Audio to Keyframes", "Converter áudio em quadros-chave"), + ("prop.convertExpressionToKeyframes", "Convert Expression to Keyframes", "Converter expressão em quadros-chave"), + ("keys.easyEase", "Easy Ease", "Easy Ease"), + ("keys.easyEaseIn", "Easy Ease In", "Easy Ease In"), + ("keys.easyEaseOut", "Easy Ease Out", "Easy Ease Out"), + ("keys.exponentialScale", "Exponential Scale", "Escala exponencial"), + ("keys.rpfCameraImport", "RPF Camera Import", "Importação de câmera RPF"), + ("layer.sequence", "Sequence Layers...", "Sequenciar camadas..."), + ("keys.timeReverse", "Time-Reverse Keyframes", "Inverter ordem dos quadros-chave"), + ("", "Animate Text", "Animar texto"), + ("layer.enablePerChar3D", "Enable Per-character 3D", "Ativar 3D por caractere"), + ("layer.addTextAnimator", "Anchor Point", "Ponto de ancoragem"), + ("layer.addTextAnimator", "Position", "Posição"), + ("layer.addTextAnimator", "Scale", "Escala"), + ("layer.addTextAnimator", "Skew", "Distorção"), + ("layer.addTextAnimator", "Rotation", "Rotação"), + ("layer.addTextAnimator", "Opacity", "Opacidade"), + ("layer.addTextAnimator", "All Transform Properties", "Todas as propriedades de transformação"), + ("", "Fill Color", "Cor de preenchimento"), + ("layer.addTextAnimator", "RGB", "RGB"), + ("layer.addTextAnimator", "Hue", "Matiz"), + ("layer.addTextAnimator", "Saturation", "Saturação"), + ("layer.addTextAnimator", "Brightness", "Brilho"), + ("", "Stroke Color", "Cor do traçado"), + ("layer.addTextAnimator", "Stroke Width", "Largura do traçado"), + ("layer.addTextAnimator", "Tracking", "Tracking"), + ("layer.addTextAnimator", "Line Anchor", "Âncora da linha"), + ("layer.addTextAnimator", "Line Spacing", "Espaçamento entre linhas"), + ("layer.addTextAnimator", "Character Offset", "Deslocamento de caractere"), + ("layer.addTextAnimator", "Character Value", "Valor do caractere"), + ("layer.addTextAnimator", "Blur", "Desfoque"), + ("text.animatorFontAxes", "Variable Font Axes", "Eixos de fonte variável"), + ("", "Add Text Selector", "Adicionar seletor de texto"), + ("text.addSelector", "Range", "Intervalo"), + ("text.addSelector", "Wiggly", "Oscilante"), + ("text.addSelector", "Expression", "Expressão"), + ("text.removeAllAnimators", "Remove All Text Animators", "Remover todos os animadores de texto"), + ("prop.setExpression", "Add Expression", "Adicionar expressão"), + ("essential.addProperty", "Add Property to Essential Graphics", "Adicionar propriedade ao Gráficos essenciais"), + ("prop.separateDimensions", "Separate Dimensions", "Dimensões separadas"), + ("track.camera", "Track Camera", "Rastrear câmera"), + ("track.warpStabilizer", "Warp Stabilizer VFX", "Warp Stabilizer VFX"), + ("track.motion", "Track Motion", "Rastrear movimento"), + ("track.stabilize", "Stabilize Motion", "Estabilizar movimento"), + ("track.property", "Track this Property", "Rastrear esta propriedade"), + ("anim.reveal", "Reveal Properties with Keyframes", "Revelar propriedades com quadros-chave"), + ("anim.reveal", "Reveal Properties with Animation", "Revelar propriedades com animação"), + ("anim.reveal", "Reveal All Modified Properties", "Revelar todas as propriedades modificadas"), + ("", "View", "Exibir"), + ("view.newViewer", "New Viewer", "Nova visualização"), + ("view.splitLockedViewer", "Split with New Locked Viewer", "Dividir com nova visualização bloqueada"), + ("view.zoomIn", "Zoom In", "Aproximar"), + ("view.zoomOut", "Zoom Out", "Afastar"), + ("", "Resolution", "Resolução"), + ("view.res.full", "Full", "Total"), + ("view.res.half", "Half", "Meio"), + ("view.res.third", "Third", "Um terço"), + ("view.res.quarter", "Quarter", "Um quarto"), + ("view.res.custom", "Custom...", "Personalizado..."), + ("view.displayColorManagement", "Use Display Color Management", "Usar gerenciamento de cores do monitor"), + ("", "Simulate Output", "Simular saída"), + ("view.simulateOutput", "No Output Simulation", "Sem simulação de saída"), + ("view.simulateOutput", "HDTV (Rec. 709)", "HDTV (Rec. 709)"), + ("view.simulateOutput", "SDTV NTSC", "SDTV NTSC"), + ("view.simulateOutput", "SDTV PAL", "SDTV PAL"), + ("view.simulateOutput", "Legacy Macintosh RGB (Gamma 1.8)", "RGB legado do Macintosh (Gamma 1.8)"), + ("view.simulateOutput", "Internet Standard RGB (sRGB)", "RGB padrão da internet (sRGB)"), + ("view.simulateOutput", "UHDTV (Rec. 2020)", "UHDTV (Rec. 2020)"), + ("view.simulateOutput", "Display P3", "Display P3"), + ("view.simulateOutput", "Linear (1.0 Gamma)", "Linear (Gamma 1.0)"), + ("view.customRgb", "My Custom RGB...", "Meu RGB personalizado..."), + ("view.simulateOutput", "Custom...", "Personalizado..."), + ("view.rulers", "Show Rulers", "Mostrar réguas"), + ("", "Panel Background Color", "Cor de fundo do painel"), + ("view.panelBackground", "Black", "Preto"), + ("view.panelBackground", "Dark Gray", "Cinza escuro"), + ("view.panelBackground", "Medium Gray (Default)", "Cinza médio (padrão)"), + ("view.panelBackground", "Light Gray", "Cinza claro"), + ("view.panelBackground", "White", "Branco"), + ("view.panelBackground", "Custom", "Personalizado"), + ("view.panelBackground", "Select Custom Background Color...", "Selecionar cor de fundo personalizada..."), + ("view.guides", "Show Guides", "Mostrar guias"), + ("view.snapToGuides", "Snap to Guides", "Encaixar nas guias"), + ("view.lockGuides", "Lock Guides", "Bloquear guias"), + ("view.addGuide", "Add Guide...", "Adicionar guia..."), + ("view.clearGuides", "Clear Guides", "Limpar guias"), + ("view.importGuides", "Import Guides...", "Importar guias..."), + ("view.exportGuides", "Export Guides...", "Exportar guias..."), + ("view.grid", "Show Grid", "Mostrar grade"), + ("view.snapToGrid", "Snap to Grid", "Encaixar na grade"), + ("view.snapping", "Snapping", "Encaixe"), + ("view.options", "View Options...", "Opções de visualização..."), + ("view.layerControls", "Show Layer Controls", "Mostrar controles da camada"), + ("", "Switch View Layout", "Alternar layout de visualização"), + ("view.layout", "1 View", "1 visualização"), + ("view.layout", "2 Views", "2 visualizações"), + ("view.layout", "4 Views", "4 visualizações"), + ("view.shareViewOptions", "Share View Options", "Compartilhar opções de visualização"), + ("", "Switch 3D View", "Alternar visualização 3D"), + ("view.3d.activeCamera", "Active Camera", "Câmera ativa"), + ("view.3d.default", "Default", "Padrão"), + ("view.3d.front", "Front", "Frente"), + ("view.3d.left", "Left", "Esquerda"), + ("view.3d.top", "Top", "Topo"), + ("view.3d.back", "Back", "Atrás"), + ("view.3d.right", "Right", "Direita"), + ("view.3d.bottom", "Bottom", "Base"), + ("view.3d.custom1", "Custom View 1", "Visualização personalizada 1"), + ("view.3d.custom2", "Custom View 2", "Visualização personalizada 2"), + ("view.3d.custom3", "Custom View 3", "Visualização personalizada 3"), + ("", "Assign Shortcut to 3D View", "Atribuir atalho à visualização 3D"), + ("view.3d.last", "Switch to Last 3D View", "Alternar para a última visualização 3D"), + ("view.lookAtSelected", "Look at Selected Layers", "Olhar para as camadas selecionadas"), + ("view.lookAtAll", "Look at All Layers", "Olhar para todas as camadas"), + ("time.set", "Go to Time...", "Ir para o tempo..."), + ("view.fullScreen", "Enter Full Screen", "Entrar em tela cheia"), + ("", "Window", "Janela"), + ("", "Workspace", "Área de trabalho"), + ("window.workspace", "Default", "Inicial"), + ("window.workspace", "Review", "Revisão"), + ("window.workspace", "Learn", "Aprender"), + ("window.workspace", "Small Screen", "Tela pequena"), + ("window.workspace", "Standard", "Padrão"), + ("window.workspace", "All Panels", "Todos os painéis"), + ("window.workspace", "Animation", "Animação"), + ("window.workspace", "Color", "Cores"), + ("window.workspace", "Effects", "Efeitos"), + ("window.workspace", "Essential Graphics", "Gráficos essenciais"), + ("window.workspace", "Minimal", "Mínimo"), + ("window.workspace", "Motion Tracking", "Rastreamento de movimento"), + ("window.workspace", "Paint", "Pintura"), + ("window.workspace", "Text", "Texto"), + ("window.workspace", "Undocked Panels", "Painéis desacoplados"), + ("window.resetWorkspace", "Reset to Saved Layout", "Redefinir para o layout salvo"), + ("window.saveWorkspace", "Save Changes to this Workspace", "Salvar alterações nesta área de trabalho"), + ("window.saveWorkspaceAs", "Save as New Workspace...", "Salvar como nova área de trabalho..."), + ("window.editWorkspaces", "Edit Workspaces...", "Editar áreas de trabalho..."), + ("", "Assign Shortcut to Workspace", "Atribuir atalho à área de trabalho"), + ("window.panel", "Align", "Alinhar"), + ("window.panel", "Audio", "Áudio"), + ("window.panel", "Brushes", "Pincéis"), + ("window.panel", "Character", "Caractere"), + ("window.panel", "Content-Aware Fill", "Preenchimento sensível ao conteúdo"), + ("window.panel", "Effects & Presets", "Efeitos e predefinições"), + ("window.panel", "Essential Graphics", "Gráficos essenciais"), + ("window.panel", "Info", "Informações"), + ("help.inAppTutorials", "Learn", "Aprender"), + ("window.panel", "Lumetri Scopes", "Escopos Lumetri"), + ("window.panel", "Mask Interpolation", "Interpolação de máscara"), + ("window.panel", "Media Browser", "Navegador de mídia"), + ("window.panel", "Metadata", "Metadados"), + ("window.panel", "Motion Sketch", "Esboço de movimento"), + ("window.panel", "Paint", "Pintura"), + ("window.panel", "Paragraph", "Parágrafo"), + ("window.panel", "Preview", "Pré-visualização"), + ("window.panel", "Progress", "Progresso"), + ("window.panel", "Properties", "Propriedades"), + ("window.panel", "Script Console", "Console de script"), + ("window.panel", "Smoother", "Suavizador"), + ("window.panel", "Tools", "Ferramentas"), + ("window.panel", "Tracker", "Rastreador"), + ("window.panel", "Wiggler", "Oscilador"), + ("window.panel", "Composition", "Composição"), + ("window.panel", "Flowchart", "Fluxograma"), + ("window.panel", "Footage", "Mídia"), + ("window.panel", "Layer", "Camada"), + ("window.panel", "Project", "Projeto"), + ("window.panel", "Render Queue", "Fila de renderização"), + ("window.panel", "Timeline", "Linha do tempo"), + ("window.panel", "Create Nulls From Paths", "Criar nulls a partir de caminhos"), + ("window.panel", "VR Comp Editor", "Editor de composição VR"), + ("", "Help", "Ajuda"), + ("help.docs", "EffectCraft Help...", "Ajuda do EffectCraft..."), + ("help.docs", "Scripting Help...", "Ajuda de scripting..."), + ("help.docs", "Expression Reference...", "Referência de expressões..."), + ("help.docs", "Effect Reference...", "Referência de efeitos..."), + ("anim.browsePresets", "Animation Presets...", "Predefinições de animação..."), + ("app.keyboardShortcuts", "Keyboard Shortcuts...", "Atalhos de teclado..."), + ("help.inAppTutorials", "In-App Tutorials...", "Tutoriais no aplicativo..."), + ("help.onlineTutorials", "Online Tutorials...", "Tutoriais online..."), + ("help.systemReport", "System Compatibility Report...", "Relatório de compatibilidade do sistema..."), + ("help.enableLogging", "Enable Logging", "Ativar registro em log"), + ("help.revealLogFile", "Reveal Logging File", "Revelar arquivo de log"), + ("help.discord", "Join the ArtCraft Discord...", "Entrar no Discord do ArtCraft..."), + ("help.reportIssue", "Provide Feedback...", "Enviar comentários..."), + ("help.website", "ArtCraft Website", "Site do ArtCraft"), + ("help.appPage", "EffectCraft Home Page", "Página inicial do EffectCraft"), + ("help.github", "EffectCraft on GitHub", "EffectCraft no GitHub"), + ("file.openDemoProject", "Open Demo Project", "Abrir projeto de demonstração"), +]; + #[cfg(test)] mod tests { use super::*; @@ -699,25 +1358,75 @@ mod tests { for mac in [false, true] { keys(&parse(TREE, mac).unwrap(), &mut actual); } - let mut unique = BTreeSet::new(); - for (command, en, ja) in JAPANESE { - let key = (command.to_string(), en.to_string()); - assert!(actual.contains(&key), "stale translation: {key:?}"); - assert!(unique.insert(key), "duplicate translation: {command} / {en}"); - assert!(!ja.is_empty()); - } - // All fixed TREE entries are covered; @effects is a generated plug-in catalog, - // whose product/effect names are intentionally left as registered. - for line in TREE.lines() { - let line = line.trim().trim_start_matches("[mac] ").trim_start_matches("[!mac] "); - if line.is_empty() || line == "---" || line.starts_with('@') { - continue; + let mut japanese_keys = BTreeSet::new(); + let mut portuguese_keys = BTreeSet::new(); + for (catalog, unique) in [(JAPANESE, &mut japanese_keys), (PORTUGUESE, &mut portuguese_keys)] { + for (command, en, translated) in catalog { + let key = (command.to_string(), en.to_string()); + assert!(actual.contains(&key), "stale translation: {key:?}"); + assert!(unique.insert(key), "duplicate translation: {command} / {en}"); + assert!(!translated.is_empty()); + } + // All fixed TREE entries are covered; @effects is a generated plug-in catalog, + // whose product/effect names are intentionally left as registered. + for line in TREE.lines() { + let line = line.trim().trim_start_matches("[mac] ").trim_start_matches("[!mac] "); + if line.is_empty() || line == "---" || line.starts_with('@') { + continue; + } + let mut parts = line.split(" | "); + let en = parts.next().unwrap(); + let command = parts.next().and_then(|p| p.split_whitespace().next()).unwrap_or(""); + assert!(unique.contains(&(command.into(), en.into())), "untranslated fixed entry: {line}"); } - let mut parts = line.split(" | "); - let en = parts.next().unwrap(); - let command = parts.next().and_then(|p| p.split_whitespace().next()).unwrap_or(""); - assert!(unique.contains(&(command.into(), en.into())), "untranslated fixed entry: {line}"); } + // Every translated language covers exactly the same entries. + assert_eq!(japanese_keys, portuguese_keys, "the languages must translate the same entries"); + } + + #[test] + fn every_portuguese_entry_keeps_the_english_shape() { + for (command, en, pt) in PORTUGUESE { + assert_eq!(en.contains("..."), pt.contains("..."), "{command} / {en}: {pt}"); + let slots = |s: &str| s.chars().filter(|c| *c == '{' || *c == '}').count(); + assert_eq!(slots(en), slots(pt), "{command} / {en}: {pt}"); + let brackets = |s: &str| s.chars().filter(|c| *c == '(' || *c == ')').count(); + assert_eq!(brackets(en), brackets(pt), "{command} / {en}: {pt}"); + assert!(!pt.contains(" "), "{command} / {en}: {pt}"); + } + } + + #[test] + fn pt_br_translates_labels_computed_titles_and_keeps_names_verbatim() { + let mut app = EffectcraftApp::new(effectcraft_engine::Session::default()); + app.session.execute("prefs.set", json!({"key":"general.language", "value":"pt-br"})).unwrap(); + assert_eq!(language(&app), "pt-br"); + assert!(!japanese(&app)); + // Fixed menu rows come from the catalog. + assert_eq!(label(&app, "", "Composition"), "Composição"); + assert_eq!(label(&app, "", "Effect"), "Efeito"); + assert_eq!(label(&app, "comp.new", "New Composition..."), "Nova composição..."); + assert_eq!(label(&app, "layer.newText", "Text"), "Texto"); + // Untranslated keys and dynamic labels stay verbatim. + assert_eq!(label(&app, "unknown.command", "Text"), "Text"); + assert_eq!( + entry(&app, &MenuEntry { label: "Undo".into(), command: "edit.undo".into(), params: json!({}), shortcut: None }, "Undo Comp1".into()), + "Não há nada para desfazer" + ); + assert_eq!( + entry( + &app, + &MenuEntry { label: "Layer".into(), command: "window.panel".into(), params: json!({"panel":"layer"}), shortcut: None }, + "Layer: File".into() + ), + "Camada: File" + ); + // The submenu titles that name the current workspace / view. + assert_eq!( + submenu(&app, "Assign Shortcut to Workspace", "Assign Shortcut to “Default” Workspace".into()), + "Atribuir atalho à área de trabalho “Default”" + ); + assert_eq!(submenu(&app, "Assign Shortcut to 3D View", "Assign Shortcut to “Top”".into()), "Atribuir atalho à visualização 3D “Top”"); } #[test] @@ -745,7 +1454,21 @@ mod tests { assert!(ja.items().iter().any(|i| i.command == "layer.newText" && i.label == "テキスト")); assert!(ja.items().iter().any(|i| i.command == "keys.easyEase" && i.label == "イージーイーズ")); app.session.prefs.push_recent("/tmp/File.ecproj"); - assert!(crate::native_menu::build(&app).items().iter().any(|i| i.command == "file.openRecent" && i.label == "File.ecproj")); + let ja_recent = crate::native_menu::build(&app); + assert!(ja_recent.items().iter().any(|i| i.command == "file.openRecent" && i.label == "File.ecproj")); + // Portuguese (Brazil): same commands, parameters and shortcuts, translated labels. + app.session.execute("prefs.set", json!({"key":"general.language", "value":"pt-br"})).unwrap(); + let pt = crate::native_menu::build(&app); + assert_ne!(crate::native_menu::state_key(&app), state); + // `ja_recent` was captured after the recent-file push, so it matches the pt tree (same commands). + assert_eq!(bindings(&ja_recent), bindings(&pt)); + assert_eq!(label(&app, "", "Composition"), "Composição"); + assert!(pt.items().iter().any(|i| i.command == "comp.new" && i.label == "Nova composição...")); + assert!(pt.items().iter().any(|i| i.command == "layer.newText" && i.label == "Texto")); + // Easy Ease keeps its English name in every language. + assert!(pt.items().iter().any(|i| i.command == "keys.easyEase" && i.label == "Easy Ease")); + // A dynamic filename is never translated. + assert!(pt.items().iter().any(|i| i.command == "file.openRecent" && i.label == "File.ecproj")); app.session.execute("prefs.set", json!({"key":"general.language", "value":"en"})).unwrap(); assert_eq!(label(&app, "", "Composition"), "Composition"); } diff --git a/crates/ui-egui/src/native_menu.rs b/crates/ui-egui/src/native_menu.rs index f7332aa5..04e6605c 100644 --- a/crates/ui-egui/src/native_menu.rs +++ b/crates/ui-egui/src/native_menu.rs @@ -182,11 +182,14 @@ pub fn build(app: &EffectcraftApp) -> NativeMenu { if let Some(role) = role { // Services sits just above Hide, as in every Mac app. if role == Role::Hide && !services_added { - out.push(NativeNode::Predefined { - role: Role::Services, - label: if crate::i18n::japanese(app) { "サービス" } else { "Services" }.into(), - command: String::new(), - }); + let services = if crate::i18n::japanese(app) { + "サービス" + } else if crate::i18n::language(app) == "pt-br" { + "Serviços" + } else { + "Services" + }; + out.push(NativeNode::Predefined { role: Role::Services, label: services.into(), command: String::new() }); out.push(NativeNode::Separator); services_added = true; } diff --git a/docs/preferences.md b/docs/preferences.md index ee1fe80c..2ef50864 100644 --- a/docs/preferences.md +++ b/docs/preferences.md @@ -29,7 +29,7 @@ Commands (CLI, MCP, control channel): ### Settings that change behaviour -- `general.language`: interface language (`en` / `ja`), Settings ▸ General ▸ Language; menu labels change immediately. Native Japanese UI uses installed system fonts; the web host must supply a Japanese font. +- `general.language`: interface language (`en` / `ja` / `pt-br`), Settings ▸ General ▸ Language; menu labels change immediately. Native Japanese UI uses installed system fonts; the web host must supply a Japanese font. - `general.undoLevels`: Levels of Undo - `general.pathPointSize`: Path Point and Handle Size - `general.recentItems`: Recent Projects Shown