From 235ef4f9ab899b3da4f3c6568f906df8b11c68e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wladmir=20J=C3=BAnior?= Date: Sun, 11 Oct 2026 13:34:47 -0300 Subject: [PATCH] Add Brazilian Portuguese (pt-BR) interface language (#539) Squash of PR #539 by @WladmirJunior. Co-Authored-By: Claude Opus 5.5 --- ATTRIBUTION.md | 1 + contributors/people.toml | 4 + crates/ui-egui/src/i18n/mod.rs | 44 +- crates/ui-egui/src/i18n/pt_br.tsv | 995 ++++++++++++++++++++++++++++++ crates/ui-egui/src/i18n/tests.rs | 78 ++- docs/localization.md | 22 +- 6 files changed, 1120 insertions(+), 24 deletions(-) create mode 100644 crates/ui-egui/src/i18n/pt_br.tsv diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index bbdb6900..8556475c 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -57,3 +57,4 @@ Generated-in-code assets are original and have no file to list: | Asset | Author | Source | Licence | Notes | |---|---|---|---|---| | `crates/ui-egui/src/i18n/uk.tsv` | @dmatviichuk | Original Ukrainian translations of CADCraft English strings | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | No proprietary localization resources copied | +| `crates/ui-egui/src/i18n/pt_br.tsv` | @WladmirJunior | Original Brazilian Portuguese translations of CADCraft English strings | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | No proprietary localization resources copied | diff --git a/contributors/people.toml b/contributors/people.toml index 114c3adb..3f54de7a 100644 --- a/contributors/people.toml +++ b/contributors/people.toml @@ -28,3 +28,7 @@ sync_github_name = false [people.vangel76] sync_github_name = true + +[people.WladmirJunior] +real_name = "Wladmir Júnior" +sync_github_name = true diff --git a/crates/ui-egui/src/i18n/mod.rs b/crates/ui-egui/src/i18n/mod.rs index ea170184..1fb09370 100644 --- a/crates/ui-egui/src/i18n/mod.rs +++ b/crates/ui-egui/src/i18n/mod.rs @@ -17,10 +17,12 @@ pub enum Preference { English, #[serde(rename = "uk")] Ukrainian, + #[serde(rename = "pt-BR")] + BrazilianPortuguese, } impl Preference { - pub const ALL: [Self; 3] = [Self::Auto, Self::English, Self::Ukrainian]; + pub const ALL: [Self; 4] = [Self::Auto, Self::English, Self::Ukrainian, Self::BrazilianPortuguese]; /// The codes a preference can be given as, for messages ("auto, en or uk"). pub fn codes() -> String { @@ -36,6 +38,7 @@ impl Preference { Self::Auto => "auto", Self::English => "en", Self::Ukrainian => "uk", + Self::BrazilianPortuguese => "pt-BR", } } @@ -44,6 +47,7 @@ impl Preference { Self::Auto => t("Auto (system language)"), Self::English => "English", Self::Ukrainian => "Українська", + Self::BrazilianPortuguese => "Português (Brasil)", } } @@ -60,6 +64,7 @@ impl Preference { match self { Self::English => "en", Self::Ukrainian => "uk", + Self::BrazilianPortuguese => "pt-BR", Self::Auto => locales.iter().find_map(|s| locale_language(s)).unwrap_or("en"), } } @@ -69,18 +74,33 @@ pub struct Language { pub code: &'static str, pub name: &'static str, pub source: &'static str, + /// Index of the `@plural` form for an integer count. + pub plural: fn(u64) -> usize, + /// Number of `|`-separated forms every `@plural` row must carry. + pub plural_forms: usize, catalog: OnceLock>, } -pub static LANGUAGES: [Language; 2] = [ - Language { code: "en", name: "English", source: "", catalog: OnceLock::new() }, - Language { code: "uk", name: "Українська", source: include_str!("uk.tsv"), catalog: OnceLock::new() }, +pub static LANGUAGES: [Language; 3] = [ + Language { code: "en", name: "English", source: "", plural: plural_en, plural_forms: 2, catalog: OnceLock::new() }, + Language { + code: "uk", name: "Українська", source: include_str!("uk.tsv"), plural: plural_uk, plural_forms: 3, catalog: OnceLock::new() + }, + Language { + code: "pt-BR", + name: "Português (Brasil)", + source: include_str!("pt_br.tsv"), + plural: plural_pt, + plural_forms: 2, + catalog: OnceLock::new(), + }, ]; -/// BCP 47 and POSIX locale tags, including `uk-UA` and `uk_UA.UTF-8`. +/// BCP 47 and POSIX locale tags, including `uk-UA`, `uk_UA.UTF-8` and `pt_BR.UTF-8`. +/// A catalog is chosen by the tag's primary language, so any `pt-*` tag selects `pt-BR`. pub fn locale_language(tag: &str) -> Option<&'static str> { let primary = tag.trim().split(['-', '_', '.', '@']).next()?; - LANGUAGES.iter().find(|l| l.code.eq_ignore_ascii_case(primary)).map(|l| l.code) + LANGUAGES.iter().find(|l| l.code.split('-').next().is_some_and(|p| p.eq_ignore_ascii_case(primary))).map(|l| l.code) } thread_local! { @@ -170,10 +190,20 @@ pub fn plural_uk(n: u64) -> usize { } } +/// Portuguese integer plurals (CLDR `pt`): one (0, 1), other (2 and up). +pub fn plural_pt(n: u64) -> usize { + usize::from(n > 1) +} + +fn plural_en(n: u64) -> usize { + usize::from(n != 1) +} + pub fn count(n: u64, one: &str, other: &str) -> String { let language = CURRENT.get(); let source = catalog(language).and_then(|c| c.get(&("@plural", one))).copied(); - let form = source.and_then(|s| s.split('|').nth(plural_uk(n))).unwrap_or(if n == 1 { one } else { other }); + let plural = LANGUAGES.iter().find(|l| l.code == language).map_or(plural_en as fn(u64) -> usize, |l| l.plural); + let form = source.and_then(|s| s.split('|').nth(plural(n))).unwrap_or(if n == 1 { one } else { other }); format(form, &[("n", n.to_string())]) } diff --git a/crates/ui-egui/src/i18n/pt_br.tsv b/crates/ui-egui/src/i18n/pt_br.tsv new file mode 100644 index 00000000..183e7ed0 --- /dev/null +++ b/crates/ui-egui/src/i18n/pt_br.tsv @@ -0,0 +1,995 @@ +# CADCraft Brazilian Portuguese (pt-BR) interface catalog. +# License: MIT OR Apache-2.0 (the repository LICENSE-MIT and LICENSE-APACHE). +# Written from CADCraft's English strings; no proprietary translations were copied. +# UTF-8: contextEnglish sourcePortuguese translation. +# Empty context = ordinary label; @plural = one|other forms with {n}. +# Keep command IDs, keywords, technology/product names and user data unchanged. + Cancel Cancelar + OK OK + or [ ou [ + % of all commits % de todos os commits + * Wildcard Match * Correspondência com curinga + *VARIES* *VARIADO* + + | Top | 2D Wireframe + | Superior | Wireframe 2D + +Bin +bin. + +LOC +linhas + 1 Viewport 1 viewport + 2 Points 2 pontos + 2 Viewports 2 viewports + 2D Solid Sólido 2D + 3 Points 3 pontos + 3 Viewports 3 viewports + 3D Face Face 3D + 3D Operations Operações 3D + 3D Polyline Polilinha 3D + 4 Viewports 4 viewports + < Less than < Menor que + <> Not Equal <> Diferente de + = Equals = Igual a + > Greater than > Maior que + About Sobre + About CADCraft Sobre o CADCraft + Absolute Absoluto + Add Leader Adicionar linha de chamada + Align Alinhar + Align Text Alinhar texto + Aligned Alinhada + All Tudo + All Used Layers Todas as camadas em uso + All files Todos os arquivos + Angle Ângulo + Angular Angular + Annotation Visibility Visibilidade de anotações + Annotation scale Escala de anotação + Apparent Intersection Interseção aparente + Append to current selection set Acrescentar ao conjunto de seleção atual + Apply to: Aplicar a: + Apply true color Aplicar cor verdadeira + Arc Arco + Arc Length Comprimento do arco + Area Área + Arrange Panel Organizar painel + Array Matriz + Attach Anexar + Attribute Atributo + Attribute Definition Definição de atributo + Attribute Display Exibição de atributos + Audit Auditar desenho + Auto (system language) Automático (idioma do sistema) + Auto Constrain Restringir automaticamente + AutoConstrain Restringir automaticamente + Axis, End Eixo, extremidade + Base Base + Baseline Linha de base + Batch Publish Publicação em lote + Binary assets added Recursos binários adicionados + Binary assets removed Recursos binários removidos + Blend Mesclar + Block Bloco + Block Attribute Manager... Gerenciador de atributos de bloco... + Block Editor Editor de blocos + Block Reference Referência de bloco + Block... Bloco... + Blocks Blocos + Blue Azul + Bold Negrito + Boolean Operações booleanas + Boundary Contorno + Boundary... Contorno... + Box Caixa + Break Quebrar + Break At Point Quebrar no ponto + Bring to Front Trazer para a frente + ByBlock PorBloco + ByColor PorCor + ByLayer PorCamada + CADCraft Help Ajuda do CADCraft + Cancel Cancelar + Cartesian (x, y) Cartesiano (x, y) + Center Centro + Center Line Linha de centro + Center Mark Marca de centro + Center X Centro X + Center Y Centro Y + Center Z Centro Z + Center, Diameter Centro, diâmetro + Center, Radius Centro, raio + Center, Start, Angle Centro, início, ângulo + Center, Start, End Centro, início, fim + Center, Start, Length Centro, início, comprimento + Chamfer Chanfro + Change Space Alterar espaço + Change to Current Layer Mover para a camada atual + Circle Círculo + Circumference Circunferência + Clear Limpar + Clipboard Área de transferência + Close Fechar + Close All Fechar tudo + Closed Fechada + Coincident Coincidente + Collapse Tool Sets Recolher conjuntos de ferramentas + Collinear Colinear + Color Cor + Color 8 Cor 8 + Color 9 Cor 9 + Color Index Índice de cor + Color... Cor... + Command History Histórico de comandos + Command Line Linha de comando + Command Line History Lines Linhas do histórico da linha de comando + Command Reference Referência de comandos + Commits Commits + Company Empresa + Computer-aided design and drafting: an open-source, clean-room CAD application written in pure Rust. Projeto e desenho assistidos por computador: um aplicativo CAD de código aberto, desenvolvido em clean-room e escrito em Rust puro. + Concentric Concêntrico + Cone Cone + Constraint Bars Barras de restrição + Constraint Settings Configurações de restrição + Construction Line Linha de construção + Contents Conteúdo + Continue Continuar + Contributors Colaboradores + Control Vertices Vértices de controle + Copy Copiar + Copy Layout Copiar layout + Copy Nested Objects Copiar objetos aninhados + Copy Selection Copiar seleção + Copy with Base Point Copiar com ponto base + Count Contagem + Create Block Criar bloco + Creates a user parameter Cria um parâmetro de usuário + Ctrl+Enter to finish Ctrl+Enter para concluir + Current layer Camada atual + Current selection Seleção atual + Current: {color} Atual: {color} + Cut Recortar + Cyan Ciano + Cylinder Cilindro + DWG DWG + DXF DXF + Dark Escuro + Default Padrão + Default Save Format Formato de salvamento padrão + Define Attributes Definir atributos + Define Attributes... Definir atributos... + Defined width Largura definida + Degree Grau + Delete Excluir + Delete Column Excluir coluna + Delete Constraints Excluir restrições + Delete Dimension Style Excluir estilo de cota + Delete Duplicate Objects Excluir objetos duplicados + Delete Layer Excluir camada + Delete Layer State Excluir estado de camada + Delete Layout Excluir layout + Delete Row Excluir linha + Delete Text Style Excluir estilo de texto + Delta X Delta X + Delta Y Delta Y + Description Descrição + Deselect All Desmarcar tudo + Diameter Diâmetro + Dim style Estilo de cota + Dimension Cota + Dimension Space Espaçar cotas + Dimension Style Override Substituição de estilo de cota + Dimension Style... Estilo de cota... + Dimension Text Edit Editar texto da cota + Dimension style Estilo de cota + Dimensional Constraint Restrição dimensional + Dimensional Constraint Parameters Parâmetros de restrição dimensional + Dimensional Constraints Restrições dimensionais + Disassociate Dimensions Desassociar cotas + Display name Nome de exibição + Distance Distância + Divide Dividir + Do not plot (click: plot) Não plotar (clique para plotar) + Donut Anel + Double-click to edit the description Clique duas vezes para editar a descrição + Down Para baixo + Drafting Desenho + Drafting Settings Configurações de desenho + Drafting Settings... Configurações de desenho... + Draw Desenhar + Draw Order Ordem de exibição + Drawing (DWG) Desenho (DWG) + Drawing (DXF) Desenho (DXF) + Drawing Limits Limites do desenho + Drawing Utilities Utilitários de desenho + Drawings (DWG, DXF) Desenhos (DWG, DXF) + Dynamic Dimensions Cotas dinâmicas + Dynamic Input Entrada dinâmica + Dynamic Input (F12) Entrada dinâmica (F12) + E L + Edit Editar + Edit Text... Editar texto... + Elevation Elevação + Ellipse Elipse + Elliptical Arc Arco elíptico + Enable Dynamic Input (F12) Ativar entrada dinâmica (F12) + End Width Largura final + End X X final + End Y Y final + End angle Ângulo final + Endpoint Extremidade + Enter Top, Zero or Bottom. Digite Superior, Zero ou Inferior. + Enter a fit data option Digite uma opção de dados de ajuste + Enter a pattern name Digite o nome de um padrão + Enter a vertex editing option Digite uma opção de edição de vértice + Enter an option Digite uma opção + Enter angle of xline Digite o ângulo da linha infinita + Enter angle of xline (0) or Digite o ângulo da linha infinita (0) ou + Enter area of rectangle in current units Digite a área do retângulo nas unidades atuais + Enter attribute prompt Digite o texto de solicitação do atributo + Enter attribute tag name Digite o nome do rótulo do atributo + Enter attribute value Digite o valor do atributo + Enter block name Digite o nome do bloco + Enter constraint type Digite o tipo de restrição + Enter continuity Digite a continuidade + Enter default attribute value Digite o valor padrão do atributo + Enter degree of spline Digite o grau da spline + Enter delta angle Digite o ângulo delta + Enter delta length or Digite o comprimento delta ou + Enter dimension text Digite o texto da cota + Enter dimension text (<> = measurement) Digite o texto da cota (<> = medida) + Enter dimension value or [name=expression] Digite o valor da cota ou [nome=expressão] + Enter dimension variable name to override Digite o nome da variável de cota a substituir + Enter justification type Digite o tipo de justificação + Enter leader text Digite o texto da linha de chamada + Enter mline scale Digite a escala da multilinha + Enter mode Digite o modo + Enter name of block to insert Digite o nome do bloco a inserir + Enter new text Digite o novo texto + Enter next point Digite o próximo ponto + Enter number of columns Digite o número de colunas + Enter number of data rows Digite o número de linhas de dados + Enter number of items Digite o número de itens + Enter number of items along path Digite o número de itens ao longo do caminho + Enter number of sides Digite o número de lados + Enter number of turns Digite o número de voltas + Enter percentage length Digite o comprimento percentual + Enter rectangle length Digite o comprimento do retângulo + Enter rectangle width Digite a largura do retângulo + Enter scale factor Digite o fator de escala + Enter text Digite o texto + Enter text (use \P for new paragraphs) Digite o texto (use \P para novos parágrafos) + Enter the number of segments Digite o número de segmentos + Enter twist direction of helix Digite o sentido de giro da hélice + Entire drawing Desenho inteiro + Equal Igual + Erase Apagar + Exclude from new selection set Excluir do novo conjunto de seleção + Explode Decompor + Export Exportar + Export to PDF... Exportar para PDF... + Expression Expressão + Extend Estender + Extension Extensão + Extents Extensão total + Extrude Extrudar + File Arquivo + File Tab Guia de arquivo + Fillet Concordância + Filter Filtro + Filters Filtros + Find and Replace Localizar e substituir + Find... Localizar... + First Primeiro + First commit Primeiro commit + Fit Points Pontos de ajuste + Fix Fixo + Flatten Objects Achatar objetos + Format Formato + Freehand Mão livre + Freeze Congelar + Freeze Layer Congelar camada + Freeze in new viewports Congelar em novos viewports + Freeze/Thaw Congelar/descongelar + Freeze/Thaw in all viewports Congelar/descongelar em todos os viewports + Freeze/Thaw in the current viewport Congelar/descongelar no viewport atual + From De + General Geral + Geometric Center Centro geométrico + Geometric Constraint Restrição geométrica + Geometric Constraints Restrições geométricas + Geometry Geometria + Get Variable Obter variável + Get View Obter vista + Global Global + Global width Largura global + Grab bag Lista + Gradient Gradiente + Gradient... Gradiente... + Green Verde + Grid (F7) Grade (F7) + Grid Display Exibição da grade + Grid On (F7) Grade ativada (F7) + Grid spacing Espaçamento da grade + Grip Mirror Espelhar por pegador + Grip Rotate Girar por pegador + Grip Scale Escalar por pegador + Grip Stretch Esticar por pegador + Group Grupo + Group Edit Editar grupo + Hatch Hachura + Hatch Edit Editar hachura + Hatch... Hachura... + Height Altura + Height: {height} Altura: {height} + Helix Hélice + Help Ajuda + Hide All Ocultar tudo + Hide All Constraint Bars Ocultar todas as barras de restrição + Hide All Dynamic Dimensions Ocultar todas as cotas dinâmicas + Hide Layer List Ocultar lista de camadas + Hide Palettes Ocultar paletas + Home Vista inicial + Horizontal Horizontal + HorizontalDistance DistânciaHorizontal + How to apply: Como aplicar: + ID Point Coordenadas do ponto + Import Importar + In Aproximar + In-window Menu Bar Barra de menus na janela + Include in new selection set Incluir no novo conjunto de seleção + Increment angle Ângulo de incremento + Index Color Cor indexada + Index color: {index} Cor indexada: {index} + Inquiry Consulta + Insert Inserir + Insert Block Inserir bloco + Insert Column Inserir coluna + Insert Row Inserir linha + Insert this block Inserir este bloco + Insertion Inserção + Insertion X Inserção X + Insertion Y Inserção Y + Insertion Z Inserção Z + Inspect Constraints Inspecionar restrições + Inspect Drawing Inspecionar desenho + Interface Language Idioma da interface + Interface Theme Tema da interface + Intersect Interseção + Intersection Interseção + Isolate Isolar + Isolate Layer Isolar camada + Isometric Drafting Desenho isométrico + Join Unir + Join the ArtCraft Discord… Entrar no Discord do ArtCraft… + Join us on Discord Junte-se a nós no Discord + Language Idioma + Last Último + Last commit Último commit + Layer Camada + Layer Delete Excluir camada + Layer Match Corresponder camada + Layer Merge Mesclar camadas + Layer Off Desativar camada + Layer Properties Propriedades da camada + Layer Properties Manager Gerenciador de propriedades de camadas + Layer Properties Manager — current viewport Gerenciador de propriedades de camadas — viewport atual + Layer State Estado de camada + Layer States Manager... Gerenciador de estados de camada... + Layer Tools Ferramentas de camada + Layer contains objects (double-click to make current) A camada contém objetos (clique duas vezes para torná-la atual) + Layer is empty (double-click to make current) A camada está vazia (clique duas vezes para torná-la atual) + Layers Camadas + Layers Palette Paleta de camadas + Layers displayed: {shown} of {total}. Current layer: {layer} Camadas exibidas: {shown} de {total}. Camada atual: {layer} + Layout Layout + Layout list Lista de layouts + Leader Linha de chamada + Left Esquerda + Length Comprimento + Length X Comprimento X + Length Y Comprimento Y + Lengthen Alongar + Light Claro + Line Linha + Line delta Delta da linha + Linear Linear + Lines +/− Linhas +/− + Lines added Linhas adicionadas + Lines deleted Linhas excluídas + Linetype Tipo de linha + Linetype Scale Escala do tipo de linha + Linetype scale Escala do tipo de linha + Ltscale Escala do tipo de linha + Linetype... Tipo de linha... + Lineweight Espessura de linha + Lineweight... Espessura de linha... + List Listar + List Blocks Listar blocos + List Dimension Styles Listar estilos de cota + List Groups Listar grupos + List Layer States Listar estados de camada + List Layouts Listar layouts + List System Variables Listar variáveis do sistema + List Text Styles Listar estilos de texto + Load all Carregar tudo + Load... Carregar... + Loaded linetypes Tipos de linha carregados + Lock Bloquear + Lock Layer Bloquear camada + Lock/Unlock Bloquear/desbloquear + Loft Loft + MIT OR Apache-2.0. Not affiliated with Autodesk, Inc. MIT OU Apache-2.0. Sem afiliação com a Autodesk, Inc. + MODEL MODELO + MText MText + Magenta Magenta + Major line every Linha principal a cada + Make Current Tornar atual + Make Object's Layer Current Tornar atual a camada do objeto + Make... Criar... + Match Layer Corresponder camada + Match Properties Copiar propriedades + Measure Medir + Measure Distance Medir distância + Measure Geometry Medir geometria + Merge Cells Mesclar células + Merged PRs PRs mesclados + Mid Between 2 Points Ponto médio entre 2 pontos + Midpoint Ponto médio + Mirror Espelhar + Model Modelo + Model Space (in viewport) Espaço do modelo (no viewport) + Modeling Modelagem + Models Modelos + Modify Modificar + Move Mover + Multileader Linha de chamada múltipla + Multileader Style... Estilo de linha de chamada múltipla... + Multileader style Estilo de linha de chamada múltipla + Multiline Multilinha + Multiline Text Texto multilinha + Multiline Text... Texto multilinha... + Multiple Múltiplo + Multiple Point Ponto múltiplo + My Minhas + My properties: not available yet Minhas propriedades: ainda não disponível + N N + Name Nome + Name (A–Z) Nome (A–Z) + Nearest Mais próximo + New Novo + New (metric) Novo (métrico) + New Drawing Novo desenho + New Drawing (⌘N) Novo desenho (⌘N) + New Drawing... Novo desenho... + New Layer Nova camada + New Layout Novo layout + New VP Freeze Congelar em novo VP + New Viewports... Novos viewports... + New layout Novo layout + New state name Nome do novo estado + No Não + No blocks defined in this drawing. Nenhum bloco definido neste desenho. + No contributor data was built into this copy. Nenhum dado de colaboradores foi incluído nesta cópia. + No model credits were built into this copy. Nenhum crédito de modelos foi incluído nesta cópia. + No saved layer states Nenhum estado de camada salvo + No selection Nenhuma seleção + Node Nó + None Nenhum + Not available Não disponível + Not available yet Ainda não disponível + OK OK + Object Objeto + Object Snap Snap a objetos + Object Snap (F3) Snap a objetos (F3) + Object Snap On (F3) Snap a objetos ativado (F3) + Object Snap Tracking Rastreamento de snap a objetos + Object Snap Tracking (F11) Rastreamento de snap a objetos (F11) + Object type: Tipo de objeto: + Off Desativado + Offset Deslocar + On Ativado + On/Off Ativar/desativar + One of the Crafting Apps by the ArtCraft team and community. Um dos Crafting Apps da equipe e da comunidade ArtCraft. + Open (⌘O) Abrir (⌘O) + Open Sample Drawing Abrir desenho de exemplo + Open sample drawing Abrir desenho de exemplo + Open... Abrir... + Operator: Operador: + Orbit Órbita + Ordinate Ordenada + Ortho (F8) Orto (F8) + Ortho Mode Modo orto + Ortho Mode (F8) Modo orto (F8) + Osnap Settings... Configurações de snap a objetos... + Out Afastar + Override Substituir + PAPER PAPEL + Percent Porcentagem + Polar (distance < angle) Polar (distância < ângulo) + PRs PRs + Page Setup Manager Gerenciador de configurações de página + Page Setup Manager... Gerenciador de configurações de página... + Pan Pan + Pan Down Pan para baixo + Pan Left Pan para a esquerda + Pan Right Pan para a direita + Pan Up Pan para cima + Paper Space Espaço do papel + Parallel Paralelo + Parameters Manager Gerenciador de parâmetros + Parametric Paramétrico + Paste Colar + Paste as Block Colar como bloco + Paste to Original Coordinates Colar nas coordenadas originais + Path Array Matriz em trajetória + Pattern Padrão + Perpendicular Perpendicular + Plot Plotar + Plot (click: do not plot) Plotar (clique para não plotar) + Plot Preview Visualizar plotagem + Plot style Estilo de plotagem + Plot style attached to Estilo de plotagem anexado a + Plot style table Tabela de estilos de plotagem + Plot Style... Estilo de plotagem... + Plot Style Manager Gerenciador de estilos de plotagem + Plot Style Manager... Gerenciador de estilos de plotagem... + Plot Styles... Estilos de plotagem... + New CTB Novo CTB + New STB Novo STB + New style Novo estilo + Reset Panel Layout Redefinir layout dos painéis + Use for this layout Usar neste layout + Display plot styles Exibir estilos de plotagem + No plot style tables. Nenhuma tabela de estilos de plotagem. + A built-in table: editing it keeps an edited copy in the drawing. Uma tabela integrada: ao editá-la, uma cópia editada é mantida no desenho. + Keep an editable copy of this table in the drawing Manter uma cópia editável desta tabela no desenho + Layout {name}: {table} Layout {name}: {table} + Grayscale Escala de cinza + Screening Intensidade + Use object linetype Usar o tipo de linha do objeto + Use object lineweight Usar a espessura de linha do objeto + Remove Remover + Add Adicionar + Open a drawing first. Abra um desenho primeiro. + Plot table type Tipo de tabela de plotagem + Color-dependent Dependente de cor + Named Nomeado + Plus/minus Mais/menos + Point Ponto + Point Filters Filtros de ponto + Point Style... Estilo de ponto... + Polar Array Matriz polar + Polar Tracking Rastreamento polar + Polar Tracking (F10) Rastreamento polar (F10) + Polar Tracking On (F10) Rastreamento polar ativado (F10) + Polar: {distance} < {angle}° Polar: {distance} < {angle}° + Polygon Polígono + Polygonal Poligonal + Polyline Polilinha + Position X Posição X + Position Y Posição Y + Position Z Posição Z + Previous Anterior + Previous Layer Camada anterior + Primitives Primitivas + Print Imprimir + Print... Imprimir... + Properties Propriedades + Properties Inspector Inspetor de propriedades + Properties: Propriedades: + Proxy Objeto proxy + Purge Limpar itens não usados + Pyramid Pirâmide + Quadrant Quadrante + Query Entities Consultar entidades + Quick Rápido + Quick Dimension Cota rápida + Quick Properties Propriedades rápidas + Quick Select Seleção rápida + Quick Select Properties Propriedades da seleção rápida + Quick Select... Seleção rápida... + QuickCalc Calculadora + Quit CADCraft Sair do CADCraft + Radius Raio + Raster Image Imagem raster + Ray Semirreta + Read-only Somente leitura + Real name Nome real + Realtime Tempo real + Reassociate Dimensions Reassociar cotas + Recent Recentes + Recover... Recuperar... + Rectangle Retângulo + Rectangular Retangular + Rectangular Array Matriz retangular + Red Vermelho + Redo Refazer + Redo (⇧⌘Z) Refazer (⇧⌘Z) + Redraw Redesenhar + Regen Regenerar + Regen All Regenerar tudo + Region Região + Region/Mass Properties Propriedades de região/massa + Relative Relativo + Remove Leader Remover linha de chamada + Remove the viewport override Remover a substituição do viewport + Rename Dimension Style Renomear estilo de cota + Rename Layer Renomear camada + Rename Layer State Renomear estado de camada + Rename Layout Renomear layout + Rename Text Style Renomear estilo de texto + Rename... Renomear... + Repeat {command} Repetir {command} + Requires a point (x,y, @dx,dy or @d Especifique o segundo ponto ou + Specify second source point Especifique o segundo ponto de origem + Specify side to offset Especifique o lado do deslocamento + Specify start angle Especifique o ângulo inicial + Specify start point Especifique o ponto inicial + Specify start point of arc Especifique o ponto inicial do arco + Specify start point of polyline Especifique o ponto inicial da polilinha + Specify start point of text Especifique o ponto inicial do texto + Specify starting width Especifique a largura inicial + Specify starting width for next segment Especifique a largura inicial do próximo segmento + Specify tangent direction for the start point of arc Especifique a direção da tangente no ponto inicial do arco + Specify the elevation for rectangles Especifique a elevação para retângulos + Specify thickness for rectangles Especifique a espessura para retângulos + Specify third point on circle Especifique o terceiro ponto no círculo + Specify third point on circle: _tan to Especifique o terceiro ponto no círculo: _tan para + Specify third source point or Especifique o terceiro ponto de origem ou + Specify through point Especifique o ponto de passagem + Specify top diameter Especifique o diâmetro superior + Specify top radius or Especifique o raio superior ou + Specify total angle Especifique o ângulo total + Specify total length or Especifique o comprimento total ou + Specify width for rectangles Especifique a largura para retângulos + Sphere Esfera + Spline Spline + Stack (type 1/2 then select) Empilhar (digite 1/2 e selecione) + Standard Padrão + Start Início + Start Width Largura inicial + Start X X inicial + Start Y Y inicial + Start angle Ângulo inicial + Start, Center, Angle Início, centro, ângulo + Start, Center, End Início, centro, fim + Start, Center, Length Início, centro, comprimento + Start, End, Angle Início, fim, ângulo + Start, End, Direction Início, fim, direção + Start, End, Radius Início, fim, raio + Status Status + Status Bar Barra de status + Stretch Esticar + Style: Standard Estilo: Standard + Subtract Subtrair + Sweep Varredura + Switch Drawing Alternar desenho + Switch Layout Alternar layout + Switch between model space in a viewport and paper space Alternar entre o espaço do modelo em um viewport e o espaço do papel + Symmetric Simétrico + Synchronize Attributes Sincronizar atributos + Toggle PICKADD (off: each pick replaces the selection) Alternar PICKADD (desativado: cada seleção substitui a anterior) + Toggle PICKADD (on: picks add to the selection) Alternar PICKADD (ativado: cada seleção é adicionada à anterior) + TOP SUPERIOR + Table Tabela + Table Style... Estilo de tabela... + Table style Estilo de tabela + Table... Tabela... + Tan, Tan, Radius Tan, tan, raio + Tan, Tan, Tan Tan, tan, tan + Tangent Tangente + Text Texto + Text Editor Editor de texto + Text Height Altura do texto + Text Style... Estilo de texto... + Text height Altura do texto + Text override Substituição de texto + Text style Estilo de texto + Thaw All Layers Descongelar todas as camadas + There is nothing here yet. Ainda não há nada aqui. + Thickness Espessura + Time Hora + To get started, create or open a drawing. Para começar, crie ou abra um desenho. + Tool Bar Barra de ferramentas + Tool Sets Conjuntos de ferramentas + Tools Ferramentas + Torus Toro + Trace Traço + Transparency Transparência + Trim Aparar + True Color Cor verdadeira + Turn All Layers On Ativar todas as camadas + Turn Off Layer Desativar camada + Turn Quick Properties off (QPMODE) Desativar propriedades rápidas (QPMODE) + Type Tipo + Type a command Digite um comando + Type text; Enter starts a new paragraph Digite o texto; Enter inicia um novo parágrafo + UCS Icon Ícone do SCU + Undo Desfazer + Undo (⌘Z) Desfazer (⌘Z) + Ungroup Desagrupar + Union União + Unisolate Layer Desfazer isolamento da camada + Units... Unidades... + Unlock Layer Desbloquear camada + Unmerge Cells Desfazer mesclagem de células + Unsaved Layer State Estado de camada não salvo + Up Para cima + Update Atualizar + Use System Setting Usar configuração do sistema + User Usuário + User Parameters Parâmetros do usuário + Username Nome de usuário + VP Color Cor do VP + VP Freeze Congelar no VP + Value Valor + Value: Valor: + Version Versão + Version {version} Versão {version} + Vertical Vertical + VerticalDistance DistânciaVertical + View Exibir + ViewCube ViewCube + Viewport Viewport + Viewport Layer Freeze Congelamento de camada no viewport + Viewport Properties Propriedades do viewport + Viewport {id} Viewport {id} + Viewports Viewports + W O + WCS ⌄ SCM ⌄ + White Branco + Width factor Fator de largura + Window Janela + Wipeout Máscara + Workspace Switching Troca de espaço de trabalho + Write Block Gravar bloco + Xline Linha infinita + Yellow Amarelo + Yes Sim + Zoom Zoom + Zoom All Zoom total + Zoom Extents Zoom extensão + Zoom In Aumentar zoom + Zoom Object Zoom no objeto + Zoom Out Diminuir zoom + Zoom Previous Zoom anterior + Zoom Window Zoom janela + error erro + {contributors} · {commits} {contributors} · {commits} + {kind} (constraint {id}) {kind} (restrição {id}) + {kind} constraint {name} = {expression} (double-click to edit) Restrição {kind} {name} = {expression} (clique duas vezes para editar) + {name}: {distance} < {angle}° {name}: {distance} < {angle}° + ƒx New User Parameter ƒx Novo parâmetro do usuário + ΔLOC Δlinhas + −Bin −bin. + −LOC −linhas + ⌘↩ to finish ⌘↩ para concluir +@plural {n} commit {n} commit|{n} commits +@plural {n} contributor {n} colaborador|{n} colaboradores + Drawing as bytes Desenho como bytes + Services Serviços + Hide CADCraft Ocultar o CADCraft + Hide Others Ocultar outros + Accessibility Acessibilidade + Show System Cursor Exibir cursor do sistema diff --git a/crates/ui-egui/src/i18n/tests.rs b/crates/ui-egui/src/i18n/tests.rs index 1c0ada5f..5261d4b0 100644 --- a/crates/ui-egui/src/i18n/tests.rs +++ b/crates/ui-egui/src/i18n/tests.rs @@ -18,7 +18,7 @@ fn catalog_is_valid_and_preserves_placeholders_and_ellipses() { assert!(["", "@plural"].contains(&context), "unknown context: {context}"); let forms: Vec<_> = if context == "@plural" { value.split('|').collect() } else { vec![value] }; if context == "@plural" { - assert_eq!(forms.len(), 3, "{key}"); + assert_eq!(forms.len(), language.plural_forms, "{}: {key}", language.code); } for form in forms { assert_eq!(placeholders(key), placeholders(form), "{key}"); @@ -35,9 +35,29 @@ fn catalog_is_valid_and_preserves_placeholders_and_ellipses() { assert_eq!(tr("unknown", "Open"), "Open"); } +/// Ukrainian is the reference translation and must cover everything. Other languages fall back +/// to English for what they don't cover yet; their gaps are reported, not enforced, so adding a +/// command doesn't require a row in every language. +const REQUIRED: &[&str] = &["uk"]; +const REPORTED: &[&str] = &["pt-BR"]; + +fn check(language: &str, what: &str, missing: Vec) { + if REQUIRED.contains(&language) { + assert!(missing.is_empty(), "{language}: missing {what}: {missing:?}"); + } else if !missing.is_empty() { + eprintln!("{language}: {} {what} fall back to English: {missing:?}", missing.len()); + } +} + #[test] -fn ukrainian_covers_commands_menus_tools_and_snap_labels() { - let catalog = catalog("uk").unwrap(); +fn translations_cover_commands_menus_tools_and_snap_labels() { + for language in REQUIRED.iter().chain(REPORTED) { + check(language, "translations", missing_commands_menus_tools_and_snap_labels(language)); + } +} + +fn missing_commands_menus_tools_and_snap_labels(language: &str) -> Vec { + let catalog = catalog(language).unwrap(); let mut sources = BTreeSet::new(); sources.extend(crate::menus::MENUS.iter().copied()); for c in cadcraft_engine::command_specs() { @@ -55,13 +75,18 @@ fn ukrainian_covers_commands_menus_tools_and_snap_labels() { } } sources.extend(cadcraft_engine::snap::mode::ALL.iter().map(|(_, name)| *name)); - let missing: Vec<_> = sources.into_iter().filter(|s| !s.is_empty() && !catalog.contains_key(&("", *s))).collect(); - assert!(missing.is_empty(), "missing translations: {missing:?}"); + sources.into_iter().filter(|s| !s.is_empty() && !catalog.contains_key(&("", *s))).map(str::to_string).collect() } #[test] fn every_marked_ui_literal_has_a_translation() { - let catalog = catalog("uk").unwrap(); + for language in REQUIRED.iter().chain(REPORTED) { + check(language, "UI translations", missing_marked_ui_literals(language).into_iter().collect()); + } +} + +fn missing_marked_ui_literals(language: &str) -> BTreeSet { + let catalog = catalog(language).unwrap(); let sources = [ include_str!("../xrefs.rs"), include_str!("../canvas.rs"), @@ -107,7 +132,7 @@ fn every_marked_ui_literal_has_a_translation() { } } } - assert!(missing.is_empty(), "missing UI translations: {missing:?}"); + missing } #[test] @@ -118,6 +143,13 @@ fn locale_and_saved_preference_resolution() { for tag in ["ua", "ukulele", "ru-RU", "", "garbage", "💙"] { assert_eq!(locale_language(tag), None, "{tag}"); } + for tag in ["pt", "pt-BR", "pt_BR.UTF-8", "PT-br", "pt-PT"] { + assert_eq!(locale_language(tag), Some("pt-BR"), "{tag}"); + } + assert_eq!(locale_language("ptolemy"), None); + assert_eq!(Preference::Auto.resolve(&["es-ES".into(), "pt-BR".into()]), "pt-BR"); + assert_eq!(Preference::BrazilianPortuguese.resolve(&["uk-UA".into()]), "pt-BR"); + assert_eq!(Preference::parse("pt-BR"), Some(Preference::BrazilianPortuguese)); let locales = vec!["pl-PL".into(), "uk-UA".into(), "en-US".into()]; assert_eq!(Preference::Auto.resolve(&locales), "uk"); assert_eq!(Preference::English.resolve(&locales), "en"); @@ -179,6 +211,38 @@ fn plural_forms_and_templates_preserve_inserted_user_data() { assert_eq!(count(2, "{n} contributor", "{n} contributors"), "2 contributors"); } +#[test] +fn brazilian_portuguese_plurals_and_accents() { + for (n, form) in [(0, 0), (1, 0), (2, 1), (5, 1), (21, 1), (u64::MAX, 1)] { + assert_eq!(plural_pt(n), form, "{n}"); + } + with_language("pt-BR", || { + assert_eq!(count(1, "{n} contributor", "{n} contributors"), "1 colaborador"); + assert_eq!(count(3, "{n} contributor", "{n} contributors"), "3 colaboradores"); + assert_eq!(t("Layers"), "Camadas"); + assert_eq!(t("Dimension"), "Cota"); + assert_eq!(format("Height: {height}", &[("height", "{n}".into())]), "Altura: {n}"); + }); + assert_eq!(count(2, "{n} contributor", "{n} contributors"), "2 contributors"); +} + +#[test] +fn live_switch_to_brazilian_portuguese_translates_the_shell() { + let mut app = CadApp::new(Session::new(), Services::default()); + app.ui.in_window_menu = true; + let ctx = egui::Context::default(); + app.logic(&ctx); + app.start("line"); + app.run("ui.language", json!({"lang":"pt-BR"})).unwrap(); + let text = render(&mut app, &ctx); + assert!(text.contains("Propriedades"), "{text}"); + assert!(text.contains("Arquivo"), "{text}"); + assert_eq!(app.language(), "pt-BR"); + assert_eq!(t("Properties"), "Properties", "language must not leak out of the render scope"); + app.run("ui.language", json!({"lang":"en"})).unwrap(); + assert!(render(&mut app, &ctx).contains("Properties")); +} + fn painted_text(shapes: &[egui::epaint::ClippedShape]) -> String { fn collect(shape: &egui::Shape, text: &mut String) { match shape { diff --git a/docs/localization.md b/docs/localization.md index 395ac056..9c3bceb2 100644 --- a/docs/localization.md +++ b/docs/localization.md @@ -1,15 +1,16 @@ # Interface languages -Choose **Edit → Interface Language** and select **Auto (system language)**, **English**, or -**Українська**. The interface updates without restarting, including the native macOS menus. +Choose **Edit → Interface Language** and select **Auto (system language)**, **English**, +**Українська**, or **Português (Brasil)**. The interface updates without restarting, including the native macOS menus. The choice is saved with the other interface preferences (`cadcraft.prefs`, beside the theme): eframe storage on the desktop, browser local storage on the web. Installations without a saved choice use Auto. Auto uses the first supported language in the operating system's preferred locales, or the browser's `navigator.languages` on the web. Ukrainian tags such as `uk-UA` and `uk_UA.UTF-8` -resolve to `uk`; unsupported languages fall back to English. Set `CADCRAFT_LOCALE=uk-UA` to -override the desktop's locale for a launch. An explicit saved English or Ukrainian choice +resolve to `uk`, and Portuguese tags such as `pt-BR` and `pt_BR.UTF-8` resolve to `pt-BR`; +unsupported languages fall back to English. Set `CADCRAFT_LOCALE=uk-UA` to +override the desktop's locale for a launch. An explicit saved English, Ukrainian or Brazilian Portuguese choice takes precedence over that override. Automation can select the language through the existing control channel: @@ -19,15 +20,15 @@ Automation can select the language through the existing control channel: ``` The `ui.set` method also accepts `{"interfaceLanguage":"uk"}`. Supported values are `auto`, -`en`, and `uk`; invalid values are rejected without changing the current selection. Resetting +`en`, `uk`, and `pt-BR`; invalid values are rejected without changing the current selection. Resetting palettes keeps the language choice. ## Translation coverage -Ukrainian covers command and menu labels, tool groups and tooltips, the Start page, file and +The Ukrainian and Brazilian Portuguese catalogs cover command and menu labels, tool groups and tooltips, the Start page, file and model-space tabs, status controls, object snaps, drafting prompts, property labels, dialogs, and the About window's controls. The command reference can be searched by English command -ID, English label, or Ukrainian label. +ID, English label, or translated label. Drawing titles, layer/layout/block/style names, parameter names and expressions, text in a drawing, and command-line input are user data and retain their original spelling. Clickable @@ -46,11 +47,12 @@ The implementation follows the display-time catalog pattern used by [PdfCraft #106](https://github.com/storytold/pdfcraft/pull/106), and [FilmCraft #238](https://github.com/storytold/filmcraft/pull/238). Its code is local to CADCraft. -- `crates/ui-egui/src/i18n/uk.tsv` is UTF-8 with three tab-separated columns: +- `crates/ui-egui/src/i18n/uk.tsv` and `pt_br.tsv` are UTF-8 with three tab-separated columns: `context`, `English source`, and `translation`. Ordinary labels have an empty context. - `@plural` entries hold Ukrainian `one|few|many` forms for integer counts. + `@plural` entries hold the language's plural forms for integer counts: Ukrainian + `one|few|many`, Brazilian Portuguese `one|other` (0 and 1 are `one`). - Register a new catalog in `i18n::LANGUAGES`, add its preference variant and code/name, - and supply its integer plural rule if it uses `@plural` rows. + and set its integer plural rule and form count on the `Language` row. - Use `crate::tl!("English label")` for a literal, `i18n::t(label)` for a known dynamic UI label, and `crate::tf!("Version {version}", version = value)` for named placeholders. Translate only at the display boundary; never look up arbitrary user names or identifiers.