diff --git a/contributors/people.toml b/contributors/people.toml index 2e6b83b..78683ae 100644 --- a/contributors/people.toml +++ b/contributors/people.toml @@ -25,3 +25,6 @@ sync_github_name = false [people.nihilist-narwhal] display_name = "nihilist-narwhal" sync_github_name = false + +[people.edurbs] +sync_github_name = true diff --git a/crates/ui-egui/examples/snapshot.rs b/crates/ui-egui/examples/snapshot.rs index f384d18..93359f0 100644 --- a/crates/ui-egui/examples/snapshot.rs +++ b/crates/ui-egui/examples/snapshot.rs @@ -1,7 +1,7 @@ //! Renders the whole GridCraft window offscreen (wgpu, no window) to a PNG. //! //! `cargo run --release -p gridcraft-ui-egui --example snapshot -- [--sample sales] [--in file.xlsx] -//! [--size 1440x900] [--scale 2] [--cmd 'id={json}']... [--tab Insert] [--dark] [--language ja] +//! [--size 1440x900] [--scale 2] [--cmd 'id={json}']... [--tab Insert] [--dark] [--language en|zh|ja|ko|ru|pt] //! [--dialog 'name={json}'] out.png` use gridcraft_engine::Session; diff --git a/crates/ui-egui/src/control.rs b/crates/ui-egui/src/control.rs index 360d596..9dd4ae6 100644 --- a/crates/ui-egui/src/control.rs +++ b/crates/ui-egui/src/control.rs @@ -163,7 +163,7 @@ pub fn handle(app: &mut SheetApp, ctx: &egui::Context, method: &str, p: &Json) - ("view.theme", "Display Theme", ""), ("view.zoom100", "100%", ""), ("ui.dialog", "Open Dialog", ""), - ("app.language.set", "Interface Language", "{language: \"en\"|\"zh\"|\"ja\"|\"ko\"|\"ru\"} (alias: code)"), + ("app.language.set", "Interface Language", "{language: \"en\"|\"zh\"|\"ja\"|\"ko\"|\"ru\"|\"pt\"} (alias: code)"), ("app.language.english", "Interface Language: English", ""), ("app.language.japanese", "Interface Language: Japanese", ""), ] { diff --git a/crates/ui-egui/src/i18n.rs b/crates/ui-egui/src/i18n.rs index 2bd840c..296bf38 100644 --- a/crates/ui-egui/src/i18n.rs +++ b/crates/ui-egui/src/i18n.rs @@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize}; mod ko; +mod pt; mod ru; mod zh; @@ -22,10 +23,12 @@ pub enum Language { Ja, Ko, Ru, + #[serde(rename = "pt")] + PtBr, } impl Language { - pub const ALL: [Self; 5] = [Self::En, Self::Zh, Self::Ja, Self::Ko, Self::Ru]; + pub const ALL: [Self; 6] = [Self::En, Self::Zh, Self::Ja, Self::Ko, Self::Ru, Self::PtBr]; /// The language's own name, shown in the switcher. pub fn name(self) -> &'static str { @@ -35,6 +38,7 @@ impl Language { Self::Ja => "日本語", Self::Ko => "한국어", Self::Ru => "Русский", + Self::PtBr => "Português (Brasil)", } } @@ -46,6 +50,7 @@ impl Language { Self::Ja => "ja", Self::Ko => "ko", Self::Ru => "ru", + Self::PtBr => "pt", } } @@ -60,6 +65,7 @@ impl Language { "ja" => Some(Self::Ja), "ko" => Some(Self::Ko), "ru" => Some(Self::Ru), + "pt" => Some(Self::PtBr), _ => None, } } @@ -88,6 +94,7 @@ impl Language { Self::Zh => zh::translate(text), Self::Ko => ko::translate(text), Self::Ru => ru::translate(text), + Self::PtBr => pt::translate(text), }; translated.unwrap_or_else(|| text.rsplit_once('|').map_or(text, |(_, label)| label)) } @@ -418,11 +425,15 @@ mod tests { assert!(zh::translate(english).is_some_and(|text| !text.is_empty()), "missing Simplified Chinese: {english}"); assert!(ko::translate(english).is_some_and(|text| !text.is_empty()), "missing Korean: {english}"); assert!(ru::translate(english).is_some_and(|text| !text.is_empty()), "missing Russian: {english}"); + assert!(pt::translate(english).is_some_and(|text| !text.is_empty()), "missing Portuguese: {english}"); } assert_eq!(Language::Ja.tr("Data"), "データ"); assert_eq!(Language::Zh.tr("Data"), "数据"); assert_eq!(Language::Ko.tr("Data"), "데이터"); assert_eq!(Language::Ru.tr("Data"), "Данные"); + assert_eq!(Language::PtBr.tr("Data"), "Dados"); + assert_eq!(Language::PtBr.tr("Date"), "Data"); + assert_eq!(Language::PtBr.tr("Sheet1!A1"), "Sheet1!A1"); assert_eq!(Language::Ja.tr("Sheet1!A1"), "Sheet1!A1"); assert_eq!(Language::Zh.tr("Sheet1!A1"), "Sheet1!A1"); assert_eq!(Language::Ko.tr("Sheet1!A1"), "Sheet1!A1"); @@ -457,6 +468,9 @@ mod tests { assert_eq!(Language::from_tag("ko-KR"), Some(Language::Ko)); assert_eq!(Language::from_tag("RU_ru"), Some(Language::Ru)); assert_eq!(Language::from_tag("de-DE"), None); + assert_eq!(Language::from_tag("pt-BR"), Some(Language::PtBr)); + assert_eq!(Language::parse("pt"), Some(Language::PtBr)); + assert_eq!(Language::parse("pt-br"), Some(Language::PtBr)); } #[test] diff --git a/crates/ui-egui/src/i18n/pt.rs b/crates/ui-egui/src/i18n/pt.rs new file mode 100644 index 0000000..1bdc2c3 --- /dev/null +++ b/crates/ui-egui/src/i18n/pt.rs @@ -0,0 +1,274 @@ +//! Brazilian Portuguese translations for the interface strings in i18n.rs. Labels follow Excel's +//! pt-BR wording; anything absent falls back to English. + +pub(super) fn translate(text: &str) -> Option<&'static str> { + Some(match text { + "Paste" => "Colar", + "Paste Values" => "Colar Valores", + "Paste Formulas" => "Colar Fórmulas", + "Paste Formatting" => "Colar Formatação", + "Transpose" => "Transpor", + "Paste Link" => "Colar Vínculo", + "Paste Special…" => "Colar Especial…", + "Cut (⌘X)" => "Recortar (⌘X)", + "Copy (⌘C)" => "Copiar (⌘C)", + "Format Painter (double-click to keep it on)" => "Pincel de Formatação (clique duas vezes para mantê-lo ativo)", + "Bold (⌘B)" => "Negrito (⌘B)", + "Italic (⌘I)" => "Itálico (⌘I)", + "Underline (⌘U)" => "Sublinhado (⌘U)", + "Double Underline" => "Sublinhado Duplo", + "Strikethrough" => "Riscado", + "Underline" => "Sublinhado", + "Increase Font Size" => "Aumentar Tamanho da Fonte", + "Decrease Font Size" => "Diminuir Tamanho da Fonte", + "Wrap Text" => "Quebrar Texto Automaticamente", + "Merge & Center" => "Mesclar e Centralizar", + "Merge Cells" => "Mesclar Células", + "Merge Across" => "Mesclar Através", + "Unmerge Cells" => "Desmesclar Células", + "Fill Color" => "Cor de Preenchimento", + "Font Color" => "Cor da Fonte", + "Borders" => "Bordas", + "Top\nAlign" => "Alinhar\nno Topo", + "Middle\nAlign" => "Alinhar\nno Meio", + "Bottom\nAlign" => "Alinhar\nem Baixo", + "Left" => "Esquerda", + "Right" => "Direita", + "Center" => "Centro", + "Horizontal" => "Horizontal", + "Vertical Text" => "Texto Vertical", + "Decrease Indent" => "Diminuir Recuo", + "Increase Indent" => "Aumentar Recuo", + "Orientation" => "Orientação", + "Angle Counterclockwise" => "Ângulo Anti-Horário", + "Angle Clockwise" => "Ângulo Horário", + "Rotate Text Up" => "Girar Texto para Cima", + "Rotate Text Down" => "Girar Texto para Baixo", + "Accounting Number Format" => "Formato de Número Contábil", + "Percent Style" => "Estilo de Porcentagem", + "Comma Style" => "Estilo de Vírgula", + "Increase Decimal" => "Aumentar Casas Decimais", + "Decrease Decimal" => "Diminuir Casas Decimais", + "More Accounting Formats…" => "Mais Formatos Contábeis…", + "Conditional\nFormatting" => "Formatação\nCondicional", + "Conditional Formatting" => "Formatação Condicional", + "Format\nas Table" => "Formatar\ncomo Tabela", + "Cell\nStyles" => "Estilos\nde Célula", + "Format Cells…" => "Formatar Células…", + "Format Cell Alignment…" => "Formatar Alinhamento de Células…", + "Format\nPane" => "Painel de\nFormatação", + "Format" => "Formatar", + "Insert" => "Inserir", + "Insert Cells…" => "Inserir Células…", + "Delete" => "Excluir", + "Delete Cells…" => "Excluir Células…", + "Insert Sheet Rows" => "Inserir Linhas de Planilha", + "Insert Sheet Columns" => "Inserir Colunas de Planilha", + "Delete Sheet Rows" => "Excluir Linhas de Planilha", + "Delete Sheet Columns" => "Excluir Colunas de Planilha", + "Row Height…" => "Altura da Linha…", + "AutoFit Row Height" => "Ajustar Altura da Linha", + "Column Width…" => "Largura da Coluna…", + "AutoFit Column Width" => "Ajustar Largura da Coluna", + "Default Width…" => "Largura Padrão…", + "Hide Rows" => "Ocultar Linhas", + "Unhide Rows" => "Reexibir Linhas", + "Hide Columns" => "Ocultar Colunas", + "Unhide Columns" => "Reexibir Colunas", + "Lock Cell" => "Bloquear Célula", + "Clear" => "Limpar", + "Clear All" => "Limpar Tudo", + "Clear Formats" => "Limpar Formatos", + "Clear Contents" => "Limpar Conteúdo", + "Clear Comments and Notes" => "Limpar Comentários e Notas", + "Clear Hyperlinks" => "Limpar Hiperlinks", + "AutoSum" => "Soma Automática", + "AutoSum (⌘⇧T)" => "Soma Automática (⌘⇧T)", + "Flash\nFill" => "Preenchimento\nRelâmpago", + "Flash Fill" => "Preenchimento Relâmpago", + "Fill" => "Preencher", + "Clear Print Area" => "Limpar Área de Impressão", + "Find &\nSelect" => "Localizar e\nSelecionar", + "Find…" => "Localizar…", + "Replace…" => "Substituir…", + "Go To…" => "Ir Para…", + "Go To Special…" => "Ir Para Especial…", + "PivotTable" => "Tabela Dinâmica", + "Recommended\nCharts" => "Gráficos\nRecomendados", + "Table" => "Tabela", + "Pictures" => "Imagens", + "Shapes" => "Formas", + "Icons" => "Ícones", + "Text\nBox" => "Caixa\nde Texto", + "Comment" => "Comentário", + "New\nComment" => "Novo\nComentário", + "New Note" => "Nova Nota", + "Show/Hide Note" => "Mostrar/Ocultar Nota", + "Header &\nFooter" => "Cabeçalho e\nRodapé", + "Text to\nColumns" => "Texto para\nColunas", + "Link" => "Vínculo", + "Symbol" => "Símbolo", + "Sparklines" => "Minigráficos", + "Header & Footer" => "Cabeçalho e Rodapé", + "Draw with ink" => "Desenhar com Tinta", + "Eraser" => "Borracha", + "Ink to\nShape" => "Tinta para\nForma", + "Select\nObjects" => "Selecionar\nObjetos", + "Themes" => "Temas", + "Margins" => "Margens", + "Size" => "Tamanho", + "Print\nArea" => "Área de\nImpressão", + "Set Print Area" => "Definir Área de Impressão", + "Breaks" => "Quebras", + "Insert Page Break" => "Inserir Quebra de Página", + "Remove Page Break" => "Remover Quebra de Página", + "Reset All Page Breaks" => "Redefinir Todas as Quebras de Página", + "Print\nTitles" => "Imprimir\nTítulos", + "Narrow" => "Estreita", + "Wide" => "Ampla", + "Normal" => "Normal", + "Portrait" => "Retrato", + "Landscape" => "Paisagem", + "Custom Margins…" => "Margens Personalizadas…", + "Insert\nFunction" => "Inserir\nFunção", + "Name\nManager" => "Gerenciador\nde Nomes", + "Define Name" => "Definir Nome", + "Use in Formula" => "Usar na Fórmula", + "Create from Selection" => "Criar a Partir da Seleção", + "Trace Precedents" => "Rastrear Precedentes", + "Trace Dependents" => "Rastrear Dependentes", + "Remove Arrows" => "Remover Setas", + "Show Formulas" => "Exibir Fórmulas", + "Error Checking" => "Verificação de Erros", + "Evaluate Formula" => "Avaliar Fórmula", + "Watch Window" => "Janela de Inspeção", + "Calculation\nOptions" => "Opções de\nCálculo", + "Calculate Now" => "Calcular Agora", + "Calculate Sheet" => "Calcular Planilha", + "Automatic" => "Automático", + "Automatic Except for Data Tables" => "Automático Exceto Tabelas de Dados", + "Manual" => "Manual", + "Sort &\nFilter" => "Classificar e\nFiltrar", + "Sort" => "Classificar", + "Sort A to Z" => "Classificar de A a Z", + "Sort Z to A" => "Classificar de Z a A", + "Custom Sort…" => "Classificação Personalizada…", + "Filter" => "Filtrar", + "Reapply" => "Aplicar Novamente", + "Get Data\n(Text/CSV)" => "Obter Dados\n(Texto/CSV)", + "Remove\nDuplicates" => "Remover\nDuplicatas", + "Data\nValidation" => "Validação\nde Dados", + "Data Validation" => "Validação de Dados", + "Data Validation…" => "Validação de Dados…", + "Circle Invalid Data" => "Circundar Dados Inválidos", + "Clear Validation" => "Limpar Validação", + "Group" => "Agrupar", + "Ungroup" => "Desagrupar", + "Subtotal" => "Subtotal", + "What-If\nAnalysis" => "Análise de\nHipóteses", + "Data Table…" => "Tabela de Dados…", + "Scenario Manager…" => "Gerenciador de Cenários…", + "Goal Seek…" => "Buscar Objetivo…", + "Spelling" => "Ortografia", + "Check\nAccessibility" => "Verificar\nAcessibilidade", + "Threaded Comments" => "Comentários Encadeados", + "Show\nComments" => "Mostrar\nComentários", + "Protect\nWorkbook" => "Proteger\nPasta de Trabalho", + "Unprotect\nSheet" => "Desproteger\nPlanilha", + "Protect Sheet…" => "Proteger Planilha…", + "Comments" => "Comentários", + "Notes" => "Notas", + "Page Break\nPreview" => "Visualização\nde Quebras de Página", + "Page\nLayout" => "Layout\nde Página", + "Formula Bar" => "Barra de Fórmulas", + "Gridlines" => "Linhas de Grade", + "Headings" => "Títulos", + "Zoom" => "Zoom", + "100%" => "100%", + "Zoom to\nSelection" => "Zoom na\nSeleção", + "Freeze\nPanes" => "Congelar\nPainéis", + "Freeze Panes" => "Congelar Painéis", + "Freeze Top Row" => "Congelar Linha Superior", + "Freeze First Column" => "Congelar Primeira Coluna", + "Unfreeze Panes" => "Descongelar Painéis", + "Dark Mode" => "Modo Escuro", + "Interface language" => "Idioma da interface", + "Command\nPalette" => "Paleta de\nComandos", + "Agent\nControl" => "Controle\ndo Agente", + "Action\nJournal" => "Registro\nde Ações", + "About\nGridCraft" => "Sobre o\nGridCraft", + "Share" => "Compartilhar", + "Add Chart\nElement" => "Adicionar\nElemento de Gráfico", + "Change\nChart Type" => "Alterar\nTipo de Gráfico", + "Switch\nRow/Column" => "Alternar\nLinha/Coluna", + "Data Labels: None" => "Rótulos de Dados: Nenhum", + "Data Labels: Show" => "Rótulos de Dados: Exibir", + "Legend: None" => "Legenda: Nenhuma", + "Legend: Bottom" => "Legenda: Abaixo", + "Legend: Top" => "Legenda: Acima", + "Legend: Right" => "Legenda: À Direita", + "Gridlines: None" => "Linhas de Grade: Nenhuma", + "Gridlines: Show" => "Linhas de Grade: Exibir", + "Chart\nTitle" => "Título\ndo Gráfico", + "Delete\nChart" => "Excluir\nGráfico", + "Convert\nto Range" => "Converter\nem Intervalo", + "Table\nStyles" => "Estilos\nde Tabela", + "Top 10 Items" => "10 Primeiros Itens", + "Top 10%" => "10% Primeiros", + "Bottom 10 Items" => "10 Últimos Itens", + "Bottom 10%" => "10% Últimos", + "Above Average" => "Acima da Média", + "Below Average" => "Abaixo da Média", + "Clear Rules from Entire Sheet" => "Limpar Regras de Toda a Planilha", + "Clear Rules from Selected Cells" => "Limpar Regras das Células Selecionadas", + "Constants" => "Constantes", + "Data Bars" => "Barras de Dados", + "Color Scales" => "Escalas de Cores", + "Icon Sets" => "Conjuntos de Ícones", + "Home" => "Página Inicial", + "Draw" => "Desenhar", + "Page Layout" => "Layout da Página", + "Formulas" => "Fórmulas", + "Data" => "Dados", + "Review" => "Revisão", + "View" => "Exibir", + "Automate" => "Automatizar", + "Table Design" => "Design de Tabela", + "Chart Design" => "Design de Gráfico", + "Ready" => "Pronto", + "Enter" => "Inserir", + "Edit" => "Editar", + "Point" => "Apontar", + "Calculate" => "Calcular", + "Select destination and press Enter or choose Paste" => "Selecione o destino e pressione Enter ou escolha Colar", + "General" => "Geral", + "Number" => "Número", + "Currency" => "Moeda", + "Accounting" => "Contábil", + "Date" => "Data", + "Time" => "Hora", + "Percentage" => "Porcentagem", + "Fraction" => "Fração", + "Scientific" => "Científico", + "Text" => "Texto", + "Special" => "Especial", + "Custom" => "Personalizado", + "Short Date" => "Data Abreviada", + "Long Date" => "Data Por Extenso", + "More Number Formats…" => "Mais Formatos de Número…", + "Financial" => "Financeira", + "Logical" => "Lógica", + "Date &\nTime" => "Data e\nHora", + "Lookup &\nReference" => "Pesquisa e\nReferência", + "Math &\nTrig" => "Matemática e\nTrigonometria", + "More\nFunctions" => "Mais\nFunções", + "Page Layout|Orientation" => "Orientação", + "Display theme" => "Tema de exibição", + "System" => "Sistema", + "Light" => "Claro", + "Dark" => "Escuro", + "Sheet Options|View" => "Exibir", + "Print" => "Imprimir", + _ => return None, + }) +} diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 4588e6c..aa5f1db 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -303,7 +303,7 @@ impl SheetApp { self.ui.language = l; Ok(json!({"language": l})) } - None => Err(format!("unknown language {code:?} (use \"en\", \"zh\", \"ja\", \"ko\" or \"ru\")")), + None => Err(format!("unknown language {code:?} (use \"en\", \"zh\", \"ja\", \"ko\", \"ru\" or \"pt\")")), } } "app.language.english" => { diff --git a/crates/ui-egui/src/theme.rs b/crates/ui-egui/src/theme.rs index 968d952..219e2d2 100644 --- a/crates/ui-egui/src/theme.rs +++ b/crates/ui-egui/src/theme.rs @@ -587,7 +587,7 @@ mod tests { assert_eq!(keys(HanOrder::Japanese), ["sys-cjk-han-ja", "sys-cjk-han-zh", "sys-cjk-hangul", "sys-thai", "sys-hebrew"]); assert_eq!(keys(HanOrder::Chinese), ["sys-cjk-han-zh", "sys-cjk-han-ja", "sys-cjk-hangul", "sys-thai", "sys-hebrew"]); assert_eq!(HanOrder::of(Language::Ja), HanOrder::Japanese); - for l in [Language::En, Language::Zh, Language::Ko, Language::Ru] { + for l in [Language::En, Language::Zh, Language::Ko, Language::Ru, Language::PtBr] { assert_eq!(HanOrder::of(l), HanOrder::Chinese, "{l:?} shares the default order (no font rebuild)"); } } diff --git a/crates/ui-egui/tests/interaction.rs b/crates/ui-egui/tests/interaction.rs index f08e34b..84f20a6 100644 --- a/crates/ui-egui/tests/interaction.rs +++ b/crates/ui-egui/tests/interaction.rs @@ -199,7 +199,9 @@ fn language_switch_translates_the_ribbon_and_persists() { for id in ["app.language.set", "app.language.english", "app.language.japanese"] { assert!(ids.contains(&id), "engine.commands lists {id}"); } - for (language, expected) in [(Language::Zh, "数据"), (Language::Ja, "データ"), (Language::Ko, "데이터"), (Language::Ru, "Данные")] { + for (language, expected) in + [(Language::Zh, "数据"), (Language::Ja, "データ"), (Language::Ko, "데이터"), (Language::Ru, "Данные"), (Language::PtBr, "Dados")] + { h.state_mut().run("app.language.set", json!({"language": language.code()})).unwrap(); h.run_steps(2); // every locale renders the ribbon without panicking assert_eq!(h.state().ui.language, language);