chore(i18n): finish translating code comments and scripts to English
CI / Format and clippy (push) Successful in 1m13s
CI / Dependency audit (push) Successful in 13s
CI / Tests (Linux) (push) Successful in 1m35s
Release / Create release (push) Successful in 7s
CI / Check desktop app (Linux) (push) Successful in 3m19s
Release / CLI (linux-aarch64) (push) Successful in 1m3s
Release / CLI (linux-x86_64) (push) Successful in 1m28s
Release / Desktop app (linux-aarch64) (push) Successful in 7m16s
Release / Desktop app (linux-x86_64) (push) Successful in 10m17s
Release / Desktop app (windows) (push) Successful in 4m54s

Completes the English-only standardization for code, comments, scripts
and CI templates (UI/CLI-facing strings stay bilingual pt-BR/en,
untouched). Covers the remaining crates/, apps/gui/ui/ and
apps/gui/src-tauri/ comments, .gitea/ workflow and template comments,
.githooks/ hooks, and the scripts/ source comments for the files
already renamed in a prior commit (check.sh, generate-logo.py,
install-hooks.sh).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VAyr8d7HuAdUGB6aA5xmDD
This commit is contained in:
Lucas RayanandClaude Sonnet 5 committed 2026-10-30 20:44:39 -03:00
1 parent bf059fd0bc
commit 55e19a8e5b
59 files changed
+935 -879

No files matched your search

+65 -67
View File
@@ -53,11 +53,11 @@ function applyTheme(t) {
function updateThemePref(t) {
applyTheme(t);
try { localStorage.setItem('sabia-theme', t); } catch { /* sem armazenamento */ }
try { localStorage.setItem('sabia-theme', t); } catch { /* storage unavailable */ }
}
let themePref = 'auto';
try { themePref = localStorage.getItem('sabia-theme') || 'auto'; } catch { /* sem armazenamento */ }
try { themePref = localStorage.getItem('sabia-theme') || 'auto'; } catch { /* storage unavailable */ }
applyTheme(themePref);
document.querySelectorAll('[data-theme-pick]').forEach((b) => {
@@ -67,7 +67,7 @@ document.querySelectorAll('[data-theme-pick]').forEach((b) => {
try {
const sort = localStorage.getItem('sabia-sort');
if (sort && [...els.sortSel.options].some((o) => o.value === sort)) els.sortSel.value = sort;
} catch { /* sem armazenamento */ }
} catch { /* storage unavailable */ }
els.settingsBtn.addEventListener('click', () => {
if (!els.settingsDlg.open) els.settingsDlg.showModal();
@@ -75,14 +75,14 @@ els.settingsBtn.addEventListener('click', () => {
function applyLangPref() {
let pref = 'auto';
try { pref = localStorage.getItem('sabia-lang') || 'auto'; } catch { /* ignora */ }
try { pref = localStorage.getItem('sabia-lang') || 'auto'; } catch { /* ignore */ }
els.langSel.value = pref;
setLang(pref === 'auto' ? undefined : pref);
}
els.langSel.addEventListener('change', () => {
const pref = els.langSel.value;
try { localStorage.setItem('sabia-lang', pref); } catch { /* ignora */ }
try { localStorage.setItem('sabia-lang', pref); } catch { /* ignore */ }
setLang(pref === 'auto' ? undefined : pref);
});
@@ -90,12 +90,12 @@ let ifaces = [];
let current = null;
let scanning = false;
// Ordem de chegada estável por rede/BSSID: sem isso, cada poll reordenava a
// lista pelo sinal instantâneo e as linhas ficavam "flutuando" de posição a
// cada atualização, tornando impossível clicar no alvo certo. A ordem de
// chegada serve de critério de desempate (e de ordenação padrão), então um
// item só muda de posição se o usuário trocar o modo de ordenação de
// verdade — não por uma flutuação de -1 dBm entre um poll e outro.
// Stable arrival order per network/BSSID: without this, each poll resorted the
// list by instantaneous RSSI and rows jumped around on every
// update, making it hard to click a target. Arrival order
// serves as the tiebreaker (and default sort), so an
// item only moves when the user explicitly changes the sort mode
// rather than on a 1 dBm RSSI fluctuation between polls.
const firstSeenOrder = new Map();
let firstSeenSeq = 0;
function orderOf(key) {
@@ -115,8 +115,8 @@ async function api(path, opts) {
return r.json();
}
// No app desktop não existe servidor HTTP: as mesmas rotas viram chamadas
// IPC diretas pro núcleo, pelos comandos registrados em src-tauri/src/main.rs.
// In the desktop app there is no HTTP server: the same routes map to direct
// IPC calls into the core via the Tauri commands in src-tauri/src/main.rs.
async function tauriApi(path, opts = {}) {
const [p, qs] = path.split('?');
const q = new URLSearchParams(qs || '');
@@ -280,8 +280,8 @@ function renderEmptyResult(message, hint) {
els.resultArea.innerHTML = `<div class="ap-card empty-state">${ICON_EMPTY}<h2>${message}</h2><p>${hint}</p></div>`;
}
// Só troca o HTML quando muda de verdade: o poll de 1 s não deve
// reconstruir a tabela (e perder foco/hover) à toa.
// Only update innerHTML when content actually changes: the 1s poll must not
// rebuild the DOM table (and drop focus/hover) unnecessarily.
function setResultsHtml(html) {
if (els.resultArea._h === html) return;
els.resultArea._h = html;
@@ -352,9 +352,9 @@ els.btnScan.addEventListener('click', async () => {
if (!scanning) {
const res = await api(`/api/scan/start?iface=${encodeURIComponent(current)}`, { method: 'POST' });
if (res && res.ok === false) {
// Sem isso, uma falha ao abrir a captura (permissão, interface saiu do
// modo monitor, etc.) ficava muda: o botão virava "parar captura" e a
// tela mostrava "escutando o ar" pra sempre, sem nenhuma rede chegar.
// Without this, a failure to start capture (permissions, interface left
// monitor mode, etc.) was silent: the button flipped to "stop capture" and
// the screen showed "listening" forever without any networks arriving.
alert('Não foi possível iniciar a captura: ' + (res.error || 'erro desconhecido'));
return;
}
@@ -398,7 +398,7 @@ function signalOf(ap) {
}
function signalBars(dbm) {
// dBm tipicamente entre -30 (ótimo) e -90 (péssimo)
// dBm typically ranges from -30 (excellent) to -90 (weak)
let level = 0;
if (dbm != null) {
if (dbm >= -55) level = 4;
@@ -414,9 +414,9 @@ function signalBars(dbm) {
return `<span class="signal-bars">${bars}</span>`;
}
// Agrupa por ESSID: uma rede pode ter vários BSSIDs (rádios) e todos
// pertencem ao mesmo alvo. Redes ocultas ficam separadas, pois sem SSID
// não há como afirmar que dois BSSIDs são a mesma rede.
// Group by ESSID: a network may have multiple BSSIDs (radios) belonging
// to the same target. Hidden networks stay separate since without an SSID
// two BSSIDs cannot be assumed to belong to the same network.
function buildGroups(aps) {
const map = new Map();
for (const ap of aps) {
@@ -460,9 +460,9 @@ function filterGroups(groups) {
g.aps.some((a) => a.bssid.toLowerCase().includes(q)));
}
// Dessensibiliza o sinal antes de comparar: arredondar pro m\u00faltiplo de 4
// dBm mais pr\u00f3ximo evita que uma oscila\u00e7\u00e3o de 1-2 dBm entre dois polls
// inverta a ordem de dois alvos com sinal parecido a cada atualiza\u00e7\u00e3o.
// Quantize RSSI before comparing: rounding to the nearest multiple of 4
// dBm prevents a 1-2 dBm fluctuation between polls from
// flipping the order of two targets with similar signal strength.
function signalBucket(v) {
return v == null ? -1000 : Math.round(v / 4) * 4;
}
@@ -535,7 +535,7 @@ function renderGroup(g) {
let lastAps = [];
const openGroups = new Set();
let viewMode = 'nets';
try { viewMode = localStorage.getItem('sabia-view') || 'nets'; } catch { /* sem armazenamento */ }
try { viewMode = localStorage.getItem('sabia-view') || 'nets'; } catch { /* storage unavailable */ }
let channelBand = null;
let reportState = { iface: null, pages: 1, zoom: 1, url: null };
let reportMeasureTimer = null;
@@ -632,8 +632,8 @@ function spectrumSvg(band) {
const CHAN_TONE_WORD = { ok: 'Boa', warn: 'Atenção', bad: 'Saturado' };
/** Estado geral da banda: mesma lógica de "saúde" usada nos tons por canal,
* só que aplicada ao canal recomendado — é o que decide o tom do card inteiro. */
/** Overall band status: applies the per-channel health logic to the
* recommended channel to determine the summary card tone. */
function bandHealth(band, rec) {
if (!rec) return { tone: 'bad', label: 'Sem canal livre nessa banda' };
if (rec.score >= 1.5) return { tone: 'bad', label: 'Até o melhor canal está saturado' };
@@ -752,7 +752,7 @@ function renderResults(aps) {
function setView(mode) {
viewMode = mode === 'channels' ? 'channels' : 'nets';
try { localStorage.setItem('sabia-view', viewMode); } catch { /* ignora */ }
try { localStorage.setItem('sabia-view', viewMode); } catch { /* ignore */ }
document.querySelectorAll('#viewNav [data-view]').forEach((b) => {
b.setAttribute('aria-pressed', String(b.dataset.view === viewMode));
});
@@ -779,7 +779,7 @@ els.resultArea.addEventListener('click', (e) => {
els.searchInput.addEventListener('input', () => renderResults(lastAps));
els.sortSel.addEventListener('change', () => {
try { localStorage.setItem('sabia-sort', els.sortSel.value); } catch { /* sem armazenamento */ }
try { localStorage.setItem('sabia-sort', els.sortSel.value); } catch { /* storage unavailable */ }
renderResults(lastAps);
});
els.resultArea.addEventListener('click', (e) => {
@@ -803,7 +803,7 @@ els.resultArea.addEventListener('keydown', (e) => {
openTarget(target.dataset.bssid);
});
// ---------------------------------------------------------------- alvo
// ---------------------------------------------------------------- target
let lastSnapshot = null;
let selectedBssid = null;
let activeTab = 'overview';
@@ -1056,8 +1056,8 @@ const EVENT_LABEL = {
deauth: 'desautenticado',
};
/** Sparkline em tamanho real (não o mini de 62px da tabela), pra caber bem
* sozinho dentro do card de sinal do detalhe do cliente. */
/** Full-size RSSI sparkline (wider than the 62px table mini-chart) for the
* client detail signal card. */
function bigSparkline(history) {
const points = (history || []).slice(-60);
if (points.length < 2) return '<p class="hint">Sem amostras de sinal suficientes ainda.</p>';
@@ -1077,9 +1077,8 @@ function bigSparkline(history) {
return `<svg class="spark spark-big" viewBox="0 0 ${W} ${H}" preserveAspectRatio="none" aria-hidden="true"><path d="${d}"/></svg>`;
}
/** Visão completa de um cliente: tudo que o Sabiá já capturou sobre ele —
* identificação, rede, sinal e a linha do tempo de eventos — num painel só,
* em vez de ter que adivinhar olhando a linha compacta da tabela. */
/** Detailed client view: consolidates everything captured about a station —
* identity hints, associated AP, signal history, and event timeline — in one modal. */
function renderClientDetail(clients) {
const client = clients.find((c) => c.mac === attack.timelineMac);
if (!client) return '';
@@ -1522,11 +1521,11 @@ async function loadSnapshot() {
}
}
// Enquanto startDeauthJob está religando a captura e esperando o AP
// reaparecer na varredura, a sessão nova responde vazia por alguns ciclos
// de poll — sem essa trava, renderTarget() interpreta "AP não visto ainda"
// como "AP não existe mais" e fecha o diálogo debaixo do usuário no meio
// da própria ação que ele acabou de pedir.
// While startDeauthJob is restarting capture and waiting for the AP
// to reappear in the scan, the fresh session returns empty for a few poll
// cycles — without this guard, renderTarget() treats "AP not seen yet"
// as "AP gone" and closes the modal underneath the user mid-action.
//
let resumingForBssid = null;
function renderTarget(force) {
@@ -1671,13 +1670,13 @@ async function waitForBssidChannel(bssid, timeoutMs) {
}
async function startDeauthJob(handshake) {
// Fixa o alvo no início: o resto desta função faz esperas longas
// (religar a captura, aguardar a rede reaparecer na varredura), e nesse
// meio tempo o poll global pode não achar mais o AP na primeira resposta
// vazia da sessão recém-reiniciada e fechar o diálogo, zerando
// `selectedBssid`. Sem essa cópia local, o job acabava sendo disparado
// com bssid=null ("MAC inválido: null") mesmo depois de localizar a rede
// com sucesso.
// Snapshot the target BSSID upfront: the rest of this function awaits
// capture restart and AP rediscovery, during which the global poll could
// see an empty initial snapshot and close the modal, clearing
// `selectedBssid`.
//
//
//
const bssid = selectedBssid;
const station = document.getElementById('deauthStation')?.value || '';
const direction = document.getElementById('deauthDirection')?.value || attack.direction;
@@ -1688,10 +1687,10 @@ async function startDeauthJob(handshake) {
setResult('deauthResult', 'Escolha um cliente para desautenticar nas direções sta/both.', true);
return;
}
// Essa ação exige uma sessão de captura ativa (é o que guarda o handshake
// montado); sem isso o backend recusa com "inicie a captura antes do
// job". Em vez de obrigar o usuário a voltar e religar manualmente, retoma
// a captura aqui — do jeito que ele descreveu querer.
// This action requires an active capture session (which stores the assembled
// handshake); rather than forcing the user to manually restart capture,
// resume capture automatically here.
//
if (!scanning) {
const scanRes = await api(`/api/scan/start?iface=${encodeURIComponent(current)}`, { method: 'POST' });
if (scanRes && scanRes.ok === false) {
@@ -1702,12 +1701,12 @@ async function startDeauthJob(handshake) {
els.btnScan.textContent = 'parar captura';
els.btnScan.classList.add('live');
els.wave.classList.add('scanning');
// A captura acabou de reiniciar: a sessão ainda não sabe em qual canal
// este BSSID está, e o job travaria o canal errado (o canal em que o
// scanner por acaso já estava saltando) em vez do canal real do AP —
// os quadros de deauth saem no vácuo e o job termina sem efeito. Espera
// o AP reaparecer no snapshot (ele já esteve no ar, é questão de
// segundos de varredura) antes de disparar o job.
// Capture just restarted: the session does not yet know which channel
// this BSSID is on, so starting the job immediately would lock the wrong
// channel. Wait briefly for the AP to reappear in the scan snapshot
// before launching the job.
//
//
setResult('deauthResult', 'Retomando a captura e localizando a rede…');
resumingForBssid = bssid;
let found;
@@ -2370,10 +2369,9 @@ async function poll() {
if (scanning && current) {
const snap = await api(`/api/scan/snapshot?iface=${encodeURIComponent(current)}`);
if (snap?.access_points && snap.running === false) {
// A sessão existe e respondeu normal (200), mas o próprio engine
// marcou running=false: a thread de captura morreu sozinha (erro de
// I/O, adaptador caiu da USB, etc.). Sem isso, a UI continuava achando
// que ainda estava capturando e nunca contava o motivo real.
// The session exists and returned HTTP 200, but the engine marked
// running=false because the capture thread died on its own (I/O error,
// USB adapter unplugged, etc.). Surface the actual reason to the UI.
scanning = false;
els.btnScan.textContent = 'iniciar captura';
els.btnScan.classList.remove('live');
@@ -2386,10 +2384,10 @@ async function poll() {
renderResults(snap.access_points);
if (selectedBssid) renderTarget(false);
} else if (snap && snap.ok === false) {
// A captura morreu no meio (interface saiu do monitor, processo externo
// tomou o adaptador, etc.). Antes disso caía no fallback de baixo, que
// sempre responde 200 com lista vazia — a UI continuava "escutando o
// ar" indefinidamente sem nenhuma rede aparecer e sem avisar por quê.
// Capture stopped mid-flight (interface left monitor mode, external
// process claimed the adapter, etc.). Without this branch, the fallback
// below returns 200 with an empty list and the UI keeps listening
// indefinitely without any network appearing or explaining why.
scanning = false;
els.btnScan.textContent = 'iniciar captura';
els.btnScan.classList.remove('live');
@@ -2405,7 +2403,7 @@ async function poll() {
}
async function checkElevation() {
if (!tauri) return; // via `sabia ui` a elevação é responsabilidade do terminal (sudo)
if (!tauri) return; // in `sabia ui` mode privilege elevation is handled by the terminal (sudo)
const notice = document.getElementById('elevateNotice');
const msg = document.getElementById('elevateMsg');
const retry = document.getElementById('btnElevateRetry');