diff --git a/windows/codenotch/src/config.rs b/windows/codenotch/src/config.rs index 480894250..dc3dde66d 100644 --- a/windows/codenotch/src/config.rs +++ b/windows/codenotch/src/config.rs @@ -27,7 +27,7 @@ pub struct TraySlot { pub struct Config { #[serde(default = "default_port")] pub port: u16, - /// "auto" | "zh" | "zh-Hant" | "en" | "ja" | "ko" | "ru" | "uk" + /// "auto" | "zh" | "zh-Hant" | "en" | "ja" | "ko" | "ru" | "uk" | "pt" #[serde(default = "default_lang")] pub lang: String, #[serde(default)] diff --git a/windows/codenotch/src/i18n.rs b/windows/codenotch/src/i18n.rs index 0b9e726c4..643fa2dba 100644 --- a/windows/codenotch/src/i18n.rs +++ b/windows/codenotch/src/i18n.rs @@ -11,6 +11,9 @@ pub fn resolve_auto() -> &'static str { if let Some(lang) = language_from_windows_locale(&name) { return lang; } + if name.starts_with("pt") { + return "pt"; + } } } "en" @@ -127,6 +130,7 @@ pub fn tr(lang: &str, key: &str) -> &'static str { ("ko", "open_data") => "데이터 폴더 열기 (로그 / 아이콘)", ("ru", "open_data") => "Открыть папку данных (журналы / значки)", ("uk", "open_data") => "Відкрити теку даних (журнали / значки)", + ("pt", "open_data") => "Abrir pasta de dados (logs / ícones)", (_, "open_data") => "Open data folder (logs / icons)", ("ja", "refresh_all") => "すべて更新", ("ko", "refresh_all") => "모두 새로 고침", @@ -162,6 +166,16 @@ pub fn tr(lang: &str, key: &str) -> &'static str { ("uk", "autostart") => "Запускати разом із Windows (у фоні)", ("ru", "refresh_all") => "Обновить всё", ("uk", "refresh_all") => "Оновити все", + ("pt", "install") => "Instalar hooks do Claude Code", + ("pt", "uninstall") => "Remover hooks", + ("pt", "language") => "Idioma", + ("pt", "lang_auto") => "Seguir o sistema", + ("pt", "reset_pos") => "Redefinir posição da barra", + ("pt", "quit") => "Sair", + ("pt", "hooks_missing") => "Hooks não instalados: clique com o botão direito no ícone da bandeja → Instalar hooks do Claude Code (a versão desktop já tem um substituto automático)", + ("pt", "autostart") => "Iniciar com o Windows (em segundo plano)", + ("pt", "refresh_all") => "Atualizar tudo", + ("pt", "settings") => "Configurações…", (_, "install") => "Install Claude Code hooks", (_, "uninstall") => "Uninstall hooks", (_, "language") => "Language", @@ -329,6 +343,32 @@ mod tests { assert_eq!(language_from_windows_locale("zh"), Some("zh")); } + const PORTUGUESE_KEYS: &[(&str, &str)] = &[ + ("settings", "Configurações…"), + ("refresh_all", "Atualizar tudo"), + ("quit", "Sair"), + ("install", "Instalar hooks do Claude Code"), + ("uninstall", "Remover hooks"), + ("language", "Idioma"), + ("lang_auto", "Seguir o sistema"), + ("reset_pos", "Redefinir posição da barra"), + ("hooks_missing", "Hooks não instalados: clique com o botão direito no ícone da bandeja → Instalar hooks do Claude Code (a versão desktop já tem um substituto automático)"), + ("autostart", "Iniciar com o Windows (em segundo plano)"), + ("open_data", "Abrir pasta de dados (logs / ícones)"), + ]; + + #[test] + fn portuguese_translates_every_known_key() { + for (key, value) in PORTUGUESE_KEYS { + assert_eq!( + tr("pt", key), + *value, + "missing Portuguese translation for {key}" + ); + assert_ne!(tr("pt", key), "?", "unknown Portuguese key {key}"); + } + } + #[test] fn unknown_language_keeps_the_english_fallback() { assert_eq!(tr("xx", "settings"), "Settings…"); diff --git a/windows/codenotch/ui/notch.html b/windows/codenotch/ui/notch.html index 0bf1275ef..c1ef5eec3 100644 --- a/windows/codenotch/ui/notch.html +++ b/windows/codenotch/ui/notch.html @@ -498,6 +498,46 @@ 'needs your input':'等待你的輸入', 'Working in':'工作於', }, + pt:{ + 'Included usage':'Uso incluído', + 'API usage':'Uso da API', + 'On demand':'Sob demanda', + 'Usage':'Uso', + 'Current session':'Sessão atual', + 'Weekly (all models)':'Semanal (todos os modelos)', + 'Weekly (Opus)':'Semanal (Opus)', + 'Weekly (model-scoped)':'Semanal (por modelo)', + 'Weekly limit':'Limite semanal', + 'Weekly Limit':'Limite semanal', + '5-Hour Limit':'Limite de 5 horas', + '5-hour Limit':'Limite de 5 horas', + 'Gemini Models':'Modelos Gemini', + 'Claude and GPT models':'Modelos Claude e GPT', + 'Monthly limit':'Limite mensal', + 'Monthly Limit':'Limite mensal', + 'Longer window':'Janela mais longa', + 'Requests today · no limit published':'Solicitações hoje · sem limite publicado', + 'Updated':'Atualizado', + 'Resetting…':'Reiniciando…', + 'Resets at':'Reinicia às', + 'Resets':'Reinicia', + 'Working':'Trabalhando', + 'Waiting':'Aguardando', + 'Streaming (network)':'Transmitindo (rede)', + 'Rate limited, retrying in':'Limite atingido, tentando novamente em', + 'Rate limited — retrying in':'Limite atingido — tentando novamente em', + 'request today':'solicitação hoje', + 'requests today':'solicitações hoje', + 'no requests today':'nenhuma solicitação hoje', + 'Waiting for first reading…':'Aguardando a primeira leitura…', + 'Sign in to Claude Code to see usage.':'Faça login no Claude Code para ver o uso.', + 'Sign in to Cursor to see usage.':'Faça login no Cursor para ver o uso.', + 'Sign in to Codex to see usage.':'Faça login no Codex para ver o uso.', + 'Sign in to Antigravity to see usage.':'Faça login no Antigravity para ver o uso.', + 'Run grok login to see usage.':'Execute grok login para ver o uso.', + 'needs your input':'precisa da sua resposta', + 'Working in':'Trabalhando em', + }, }; // Kept as patterns rather than exact keys: each one carries a number, a plan name or an error // the provider wrote, so the whole string can never be a dictionary key. @@ -654,6 +694,44 @@ [/Sign in to Cursor \(the editor\) to see usage\./g,'請登入 Cursor(編輯器)以查看用量。'], [/Cursor session was rejected — sign in again in the editor/g,'Cursor 工作階段被拒絕 — 請在編輯器中重新登入'], ], + pt:[ + [/^(\d+)m limit$/,'Limite de $1 min'], + [/^(\d+)h limit$/,'Limite de $1 h'], + [/^(\d+)d limit$/,'Limite de $1 d'], + [/^Rate limited, retrying in (\d+)s$/,'Limite atingido, tentando novamente em $1 s'], + [/^Rate limited — retrying in (\d+)s$/,'Limite atingido — tentando novamente em $1 s'], + [/^Unlimited on the (.+) plan — nothing to meter$/,'Ilimitado no plano $1 — nada para medir'], + [/^The (.+) plan has nothing for Cursor to meter yet$/,'O plano $1 ainda não tem nada para o Cursor medir'], + [/^(.+) · Google publishes no quota for this account$/,'$1 · o Google não publica cota para esta conta'], + [/^Live read failed \((.+)\)$/,'Falha na leitura ao vivo ($1)'], + [/Weekly \(all models\)/g,'Semanal (todos os modelos)'], + [/Weekly \(Opus\)/g,'Semanal (Opus)'], + [/Weekly \(model-scoped\)/g,'Semanal (por modelo)'], + [/Weekly limit/g,'Limite semanal'], + [/Monthly limit/g,'Limite mensal'], + [/\bWeekly\b/g,'Semanal'], + [/\bMonthly\b/g,'Mensal'], + [/ · via /g,' · via '], + [/needs your input/g,'precisa da sua resposta'], + [/^Working in /,'Trabalhando em '], + [/via Antigravity CLI/g,'via CLI do Antigravity'], + [/via Antigravity/g,'via Antigravity'], + [/via Google/g,'via Google'], + [/No Claude Code credential found/g,'Nenhuma credencial do Claude Code encontrada'], + [/Credential expired — run claude once in a terminal to renew it/g,'Credencial expirada — rode claude uma vez no terminal para renová-la'], + [/Credential rejected \(switched accounts\?\)/g,'Credencial rejeitada (trocou de conta?)'], + [/Codex sign-in expired — open Codex once to refresh it/g,'Login do Codex expirado — abra o Codex uma vez para renová-lo'], + [/Codex rejected its sign-in — sign in to Codex again/g,'O Codex rejeitou o login — faça login no Codex novamente'], + [/Codex reported no usage windows/g,'O Codex não reportou nenhuma janela de uso'], + [/Codex has not recorded a usage snapshot yet/g,'O Codex ainda não registrou um snapshot de uso'], + [/from last Codex run/g,'da última execução do Codex'], + [/Waiting for Antigravity CLI quota/g,'Aguardando a cota da CLI do Antigravity'], + [/Open Antigravity to read its quota/g,'Abra o Antigravity para ler sua cota'], + [/Antigravity's Google session was rejected — sign in again in Antigravity/g,'A sessão do Google no Antigravity foi rejeitada — faça login novamente no Antigravity'], + [/Antigravity is closed — last reading kept/g,'Antigravity está fechado — mantendo a última leitura'], + [/Sign in to Cursor \(the editor\) to see usage\./g,'Faça login no Cursor (o editor) para ver o uso.'], + [/Cursor session was rejected — sign in again in the editor/g,'A sessão do Cursor foi rejeitada — faça login novamente no editor'], + ], }; // Grammar differs enough that a format string would not carry it — Russian puts the verb last, // Chinese puts it after the time — so each language writes its own short functions. @@ -678,6 +756,10 @@ resetsAt:t=>`${t} 重置`,resetsOn:(d,t)=>`${d} ${t} 重置`,resetsDate:d=>`${d} 重置`, ago:m=>m<60?`${m} 分鐘前`:`${Math.round(m/60)} 小時前`, usedLeft:(used,left)=>`已用 ${used}% · 剩餘 ${left}%`,andMore:n=>`另有 ${n} 個`}, + pt:{locale:'pt-BR',title:n=>'Uso de '+n,resetting:'Reiniciando…',resetsIn:m=>`Reinicia em ${m} min`, + resetsAt:t=>`Reinicia às ${t}`,resetsOn:(d,t)=>`Reinicia ${d} ${t}`,resetsDate:d=>`Reinicia ${d}`, + ago:m=>m<60?`há ${m} min`:`há ${Math.round(m/60)} h`, + usedLeft:(used,left)=>`${used}% usado · ${left}% restante`,andMore:n=>`e mais ${n}`}, }; function ui(){return UI[uiLang]||UI.en;} function textCopy(value){ diff --git a/windows/codenotch/ui/settings.html b/windows/codenotch/ui/settings.html index 6d3ba6389..4861af787 100644 --- a/windows/codenotch/ui/settings.html +++ b/windows/codenotch/ui/settings.html @@ -177,6 +177,7 @@

Accounts

+
Sets the language used by Codenotch. “Follow system” uses the language Windows itself is set to.
@@ -721,7 +722,95 @@

Accounts

'No icon in the system tray. Open Codenotch again from the Start menu to bring these settings back.':'Значка в області сповіщень немає. Відкрийте Codenotch знову з меню «Пуск», щоб повернути ці налаштування.', 'Model data':'Дані моделі' }; -const STATIC_TEXT = { ru: RU_STATIC, zh: ZH_STATIC, 'zh-Hant': ZH_HANT_STATIC, ja: JA_STATIC, ko: KO_STATIC, uk: UK_STATIC }; +const PT_STATIC = { + 'Updates':'Atualizações', + 'Check for updates':'Verificar atualizações', + 'Update':'Atualizar', + 'Checking…':'Verificando…', + 'Installing…':'Instalando…', + 'Up to date':'Atualizado', + 'Could not check for updates':'Não foi possível verificar atualizações', + 'Codenotch looks for a new version shortly after it starts, and can install it for you. The download comes from GitHub, and its signature is checked before anything is run.':'O Codenotch procura uma nova versão logo depois de abrir, e pode instalá-la para você. O download vem do GitHub, e sua assinatura é verificada antes de qualquer coisa ser executada.', + 'Settings sections':'Seções de configurações', + 'Accounts':'Contas', + 'Appearance':'Aparência', + 'General':'Geral', + 'Quit Codenotch':'Sair do Codenotch', + 'Connected':'Conectado', + 'Not connected':'Não conectado', + 'These have no ring to place. Switch one on and it joins the end of the list above.':'Estes não têm anel para exibir. Ative um e ele entra no fim da lista acima.', + 'One provider always stays ticked, so the pill is never empty.':'Pelo menos um provedor fica sempre marcado, então a barra nunca fica vazia.', + 'At least one provider stays on the notch':'Pelo menos um provedor permanece no notch', + 'Notch reads':'O notch mostra', + 'Automatic':'Automático', + '5-Hour Limit':'Limite de 5 horas', + 'Weekly Limit':'Limite semanal', + 'Gemini Models':'Modelos Gemini', + 'Claude and GPT models':'Modelos Claude e GPT', + 'Let Claude Code notify Codenotch':'Permitir que o Claude Code avise o Codenotch', + 'Turning this on adds a few lines to your Claude Code settings file so that Claude Code sends Codenotch a short message when a session starts, when it is working, when it is waiting for your answer, and when it finishes. That is what makes the ring spin and the amber ring pulse.':'Ativar isso adiciona algumas linhas ao arquivo de configurações do Claude Code, para que ele envie uma mensagem curta ao Codenotch quando uma sessão começa, quando está trabalhando, quando espera sua resposta e quando termina. É isso que faz o anel girar e o anel âmbar pulsar.', + 'Notch':'Notch', + 'Show':'Exibir', + 'Always show':'Sempre exibir', + 'Hide':'Ocultar', + 'Size':'Tamanho', + 'Small':'Pequeno', + 'Medium':'Médio', + 'Large':'Grande', + 'Takes the least room on the edge. Readable, but not from across the desk.':'Ocupa o mínimo de espaço na borda. Legível, mas não do outro lado da mesa.', + 'The size the notch was drawn at.':'O tamanho em que o notch foi desenhado originalmente.', + 'Easier to read at a glance, and harder to ignore.':'Mais fácil de ler rapidamente, e mais difícil de ignorar.', + 'Weekly ring':'Anel semanal', + 'Off':'Desligado', + 'Inside':'Por dentro', + 'Outside':'Por fora', + 'One ring per provider, showing the headline limit. The weekly allowance stays in the hover card.':'Um anel por provedor, mostrando o limite principal. A cota semanal fica no cartão que aparece ao passar o mouse.', + 'A thinner ring for the weekly limit, drawn inside the main one. It shares the gap with the working indicator.':'Um anel mais fino para o limite semanal, desenhado dentro do anel principal. Ele divide o espaço com o indicador de atividade.', + 'A thinner ring for the weekly limit, drawn around the main one, in the margin between the ring and the notch edge.':'Um anel mais fino para o limite semanal, desenhado ao redor do anel principal, na margem entre o anel e a borda do notch.', + 'Show on hover':'Exibir ao passar o mouse', + 'A small pill at the screen edge that opens when you reach it.':'Uma pequena barra na borda da tela, que se abre quando o cursor a alcança.', + 'The notch stays open with every reading visible.':'O notch permanece aberto com todas as leituras visíveis.', + 'Edge':'Borda', + 'Left':'Esquerda', + 'Right':'Direita', + 'Top':'Superior', + 'Bottom':'Inferior', + 'Screen':'Tela', + 'Main':'Principal', + 'Stands upright on the left-hand edge, with the card opening to its right.':'Fica em pé na borda esquerda, com o cartão abrindo à direita.', + 'Stands upright on the right-hand edge, the side it was drawn for.':'Fica em pé na borda direita, o lado para o qual foi desenhado originalmente.', + 'Lies flat along the top edge, with the card opening below it.':'Fica deitado ao longo da borda superior, com o cartão abrindo abaixo dele.', + 'Lies flat along the bottom edge, with the card opening above it.':'Fica deitado ao longo da borda inferior, com o cartão abrindo acima dele.', + 'Which screen the notch lives on.':'Em qual tela o notch vive.', + 'this screen cannot be remembered by name':'esta tela não pode ser lembrada pelo nome', + 'Hold Alt and drag the notch to slide it along its edge. Each edge remembers where you left it. Recentre puts it back in the middle of the edge it is on, or on the main screen\'s right-hand edge if the screen it was on has been unplugged.':'Segure Alt e arraste o notch para deslizá-lo ao longo da borda. Cada borda lembra onde você o deixou. Recentralizar o devolve ao meio da borda em que está, ou à borda direita da tela principal, se a tela em que ele estava foi desconectada.', + 'The black pill against the right-hand edge of the screen, with one ring per tool. Turn it off and the pill disappears; Codenotch keeps counting your usage either way.':'A barra preta encostada na borda direita da tela, com um anel por ferramenta. Desligue e a barra desaparece; o Codenotch continua contando seu uso de qualquer forma.', + 'Recentre':'Recentralizar', + 'The pill can be dragged up and down the right-hand edge. If it has ended up somewhere you cannot see it — off the bottom of a screen you have unplugged, say — this drops it back into the middle of the edge.':'A barra pode ser arrastada para cima e para baixo na borda direita. Se ela parou em algum lugar que você não consegue ver — fora da parte de baixo de uma tela que você desconectou, por exemplo — isso a devolve para o meio da borda.', + 'The notch is back in the middle':'O notch voltou para o meio', + 'App':'App', + 'App icon':'Ícone do app', + 'Neither':'Nenhum dos dois', + 'The small icon down by the clock. Right-clicking it opens this window, refreshes the readings, or quits Codenotch.':'O pequeno ícone perto do relógio. Clicar com o botão direito nele abre esta janela, atualiza as leituras ou encerra o Codenotch.', + 'Held on: the notch is hidden, so this icon is the only way left to reach Codenotch. Switch the notch back on first if you want to hide it.':'Mantido ligado: o notch está oculto, então este ícone é a única forma de acessar o Codenotch. Ligue o notch novamente primeiro se quiser ocultar este ícone.', + 'The taskbar icon has been kept on. With the notch hidden it is the only way left to open this window or to quit Codenotch, so Codenotch will not let both of them be switched off at the same time.':'O ícone da barra de tarefas foi mantido ligado. Com o notch oculto, ele é a única forma de abrir esta janela ou encerrar o Codenotch, por isso o Codenotch não permite desligar os dois ao mesmo tempo.', + 'Language':'Idioma', + 'Follow system':'Seguir o sistema', + 'Sets the language used by Codenotch. “Follow system” uses the language Windows itself is set to.':'Define o idioma usado pelo Codenotch. "Seguir o sistema" usa o idioma configurado no próprio Windows.', + 'Open Codenotch at login':'Abrir o Codenotch ao entrar no Windows', + 'Codenotch opens by itself every time you sign in to this computer, and waits quietly in the background until a coding session starts. Turn it off and you have to open Codenotch yourself.':'O Codenotch abre sozinho toda vez que você entra neste computador, e espera silenciosamente em segundo plano até que uma sessão de programação comece. Desligue e você precisará abrir o Codenotch manualmente.', + 'Version':'Versão', + 'Data folder':'Pasta de dados', + 'Open folder':'Abrir pasta', + 'Everything Codenotch remembers lives in one folder: your settings, its log file, and the "glyphs" folder where you can drop your own icons. Opens in File Explorer.':'Tudo o que o Codenotch guarda fica em uma única pasta: suas configurações, o arquivo de log e a pasta "glyphs", onde você pode colocar seus próprios ícones. Abre no Explorador de Arquivos.', + 'App designed and developed by':'App projetado e desenvolvido por', + 'Saved':'Salvo', + 'System tray':'Bandeja do sistema', + 'Close':'Fechar', + 'No icon in the system tray. Open Codenotch again from the Start menu to bring these settings back.':'Sem ícone na bandeja do sistema. Abra o Codenotch novamente pelo menu Iniciar para acessar estas configurações de novo.', + 'Model data':'Dados do modelo' +}; +const STATIC_TEXT = { ru: RU_STATIC, zh: ZH_STATIC, 'zh-Hant': ZH_HANT_STATIC, ja: JA_STATIC, ko: KO_STATIC, uk: UK_STATIC, pt: PT_STATIC }; const STATUS_TEXT = { en:{ ok:'', stale:'last reading is old', needsAuth:'not signed in', absent:'not installed', none:'nothing to report', error:'not reachable' }, ru:{ ok:'', stale:'данные устарели', needsAuth:'не выполнен вход', absent:'не установлен', none:'нет данных', error:'недоступен' }, @@ -729,7 +818,8 @@

Accounts

'zh-Hant':{ ok:'', stale:'上次讀數已過期', needsAuth:'未登入', absent:'未安裝', none:'暫無報告', error:'無法連線' }, ja:{ ok:'', stale:'最後の読み取りが古いです', needsAuth:'サインインしていません', absent:'インストールされていません', none:'報告するものがありません', error:'到達できません' }, ko:{ ok:'', stale:'마지막 읽기가 오래되었습니다', needsAuth:'로그인되지 않음', absent:'설치되지 않음', none:'보고할 내용 없음', error:'연결할 수 없음' }, - uk:{ ok:'', stale:'останній показник застарів', needsAuth:'не виконано вхід', absent:'не встановлено', none:'немає даних', error:'недоступний' } + uk:{ ok:'', stale:'останній показник застарів', needsAuth:'не виконано вхід', absent:'не встановлено', none:'немає даних', error:'недоступний' }, + pt:{ ok:'', stale:'última leitura desatualizada', needsAuth:'não conectado', absent:'não instalado', none:'nada para reportar', error:'inacessível' } }; let uiLang = 'en'; function ui(key, fallback){ const table = STATIC_TEXT[uiLang]; return (table && table[key]) || fallback; } @@ -1243,7 +1333,7 @@

Accounts

}); /* ---- Appearance: language ------------------------------------------------ */ -const LANGS = ['auto', 'en', 'zh', 'zh-Hant', 'ja', 'ko', 'ru', 'uk']; +const LANGS = ['auto', 'en', 'zh', 'zh-Hant', 'ja', 'ko', 'ru', 'uk', 'pt']; document.getElementById('lang').addEventListener('change', e => { const previous = e.target.dataset.previous || 'auto'; const v = LANGS.indexOf(e.target.value) >= 0 ? e.target.value : 'auto';