chore(i18n): finish translating code comments and scripts to English
CI / Format and clippy (push) Successful in 1m57s
CI / Dependency audit (push) Successful in 13s
CI / Check desktop app (Linux) (push) Successful in 3m54s
CI / Tests (Linux) (push) Failing after 7m25s
Release / Create release (push) Successful in 6s
Release / CLI (linux-aarch64) (push) Successful in 1m48s
Release / CLI (linux-x86_64) (push) Successful in 2m39s
Release / Desktop app (linux-aarch64) (push) Successful in 10m9s
Release / Desktop app (linux-x86_64) (push) Successful in 12m20s
Release / Desktop app (windows) (push) Successful in 7m32s
Release / Build latest.json (push) Successful in 5s

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:43:02 -03:00
1 parent da212e6926
commit 6328220c60
129 files changed
+1961 -1834

No files matched your search

+21 -22
View File
@@ -1,13 +1,12 @@
#!/usr/bin/env node
// Confere se o dicionário inglês cobre os textos da interface.
// Verifies that the English dictionary covers all user-facing interface strings.
//
// A varredura é estática e sem dependências: lê os arquivos da interface
// (JS e HTML), os rótulos que o núcleo em Rust envia para a tela (metadados,
// tipos de arquivo e saúde do disco) e falha quando encontra uma string que
// parece português e não tem tradução. Também recusa chaves duplicadas no
// dicionário (num objeto JS a última vence em silêncio).
// Static zero-dependency scanner: reads the UI files (JS and HTML) and the
// labels emitted by the Rust core (metadata, file types, disk health), failing
// whenever a string looks like Portuguese and lacks a translation. Also rejects
// duplicate keys in the dictionary (in a JS object literal the last key wins silently).
//
// Uso: node scripts/check-ui-i18n.mjs
// Usage: node scripts/check-ui-i18n.mjs
import { readFileSync, readdirSync } from 'node:fs';
import { join, relative } from 'node:path';
@@ -16,7 +15,7 @@ import { fileURLToPath } from 'node:url';
const root = join(fileURLToPath(new URL('.', import.meta.url)), '..');
const rel = (p) => relative(root, p).replaceAll('\\', '/');
// ---------------------------------------------------------------- dicionário
// ---------------------------------------------------------------- dictionary
const dictPath = join(root, 'apps/gui/ui/i18n-en.js');
const dictSource = readFileSync(dictPath, 'utf8');
@@ -33,8 +32,8 @@ while ((m = keyRe.exec(dictSource))) {
}
if (duplicates.length) {
console.error('Chaves duplicadas em apps/gui/ui/i18n-en.js:');
for (const d of duplicates) console.error(` ${JSON.stringify(d.key)} (linhas ${d.first} e ${d.again})`);
console.error('Duplicate keys in apps/gui/ui/i18n-en.js:');
for (const d of duplicates) console.error(` ${JSON.stringify(d.key)} (lines ${d.first} and ${d.again})`);
process.exitCode = 1;
}
@@ -53,7 +52,7 @@ function translated(text) {
return patterns.some((re) => re.test(text));
}
// ---------------------------------------------------------------- heurística
// ---------------------------------------------------------------- heuristic
const ACCENT = /[áàâãäéèêëíìîïóòôõöúùûüçÁÀÂÃÄÉÈÊËÍÌÎÏÓÒÔÕÖÚÙÛÜÇ]/;
const WORDS = [
@@ -92,13 +91,13 @@ function suspicious(text) {
if (ALLOW.has(s) || ALLOW_EXACT.has(s)) return false;
if (ALLOW_PREFIX.some((p) => s.startsWith(p))) return false;
if (!isPortuguese(s)) return false;
// técnico: seletores, classes, extensões, expressões
// Technical tokens: CSS selectors, classes, extensions, expressions
if (/^[.#\w-]+(\s*[,>+~]\s*[.#\w-]+)+$/.test(s)) return false;
if (/^[a-z0-9_.:/@%{}$-]+$/i.test(s) && !ACCENT.test(s)) return false;
return true;
}
// ---------------------------------------------------------------- extratores
// ---------------------------------------------------------------- extractors
function jsStrings(source, file, baseLine = 1) {
const out = [];
@@ -212,14 +211,14 @@ function jsStrings(source, file, baseLine = 1) {
function htmlStrings(source, file, baseLine = 1) {
const out = [];
const lineAt = (idx) => baseLine + source.slice(0, idx).split('\n').length - 1;
// todo texto entre tags (inclusive começo e fim) — nós de texto reais
// Text between tags (including leading and trailing text nodes)
let last = 0;
for (const m of source.matchAll(/<[^>]*>/g)) {
out.push({ text: source.slice(last, m.index), file, line: lineAt(last) });
last = m.index + m[0].length;
}
out.push({ text: source.slice(last), file, line: lineAt(last) });
// atributos traduzíveis (com aspas simples ou duplas)
// Translatable attributes (single or double quotes)
for (const m of source.matchAll(/\b(placeholder|title|aria-label|alt|data-empty)\s*=\s*(?:"([^"]*)"|'([^']*)')/g)) {
out.push({ text: m[2] ?? m[3], file, line: lineAt(m.index) });
}
@@ -229,16 +228,16 @@ function htmlStrings(source, file, baseLine = 1) {
function rustLabels(source, file) {
const out = [];
const lineAt = (idx) => source.slice(0, idx).split('\n').length;
// m.add("Rótulo", ...) e m.add(\n "Rótulo", ...)
// m.add("Label", ...) and m.add(\n "Label", ...)
for (const m of source.matchAll(/\.add\(\s*"([^"]+)"/g)) out.push({ text: m[1], file, line: lineAt(m.index) });
// m.kind = "Tipo".into()
// m.kind = "Kind".into()
for (const m of source.matchAll(/\.kind\s*=\s*"([^"]+)"/g)) out.push({ text: m[1], file, line: lineAt(m.index) });
// rótulos de listas (saúde do disco, tipos de arquivo) dentro de arrays de tuplas
// List labels (disk health, file types) inside tuple arrays
for (const m of source.matchAll(/\(\s*"([^"]{2,60})"\s*,\s*"(?:[^"]*)"\s*\)/g)) out.push({ text: m[1], file, line: lineAt(m.index) });
return out;
}
// ---------------------------------------------------------------- coleta
// ---------------------------------------------------------------- collection
const uiFiles = readdirSync(join(root, 'apps/gui/ui'))
.filter((f) => f.endsWith('.js') && f !== 'i18n-en.js')
@@ -278,12 +277,12 @@ for (const { text, file, line } of found) {
}
if (missing.length) {
console.error('Textos em português sem tradução em apps/gui/ui/i18n-en.js:\n');
console.error('Untranslated Portuguese strings in apps/gui/ui/i18n-en.js:\n');
for (const x of missing) console.error(` ${x.file}:${x.line} ${JSON.stringify(x.text)}`);
console.error(`\n${missing.length} texto(s) sem tradução.`);
console.error(`\n${missing.length} untranslated string(s).`);
process.exitCode = 1;
}
if (!process.exitCode) {
console.log(`i18n ok: ${found.length} textos verificados, ${Object.keys(EN).length} chaves no dicionário.`);
console.log(`i18n ok: ${found.length} strings checked, ${Object.keys(EN).length} dictionary keys.`);
}