release: 1.1.0
Release / Cria a release (push) Canceled after 0s
Release / CLI (linux-aarch64) (push) Canceled after 0s
Release / CLI (linux-x86_64) (push) Canceled after 0s
Release / CLI (macos-aarch64) (push) Canceled after 0s
Release / CLI (macos-x86_64) (push) Canceled after 0s
Release / CLI (windows-x86_64) (push) Canceled after 0s
Release / App desktop (linux-x86_64) (push) Canceled after 0s
Release / App desktop (macos-aarch64) (push) Canceled after 0s
Release / App desktop (macos-x86_64) (push) Canceled after 0s
Release / App desktop (windows-x86_64) (push) Canceled after 0s
Release / Publica a release (push) Canceled after 0s
Release / Cria a release (push) Canceled after 0s
Release / CLI (linux-aarch64) (push) Canceled after 0s
Release / CLI (linux-x86_64) (push) Canceled after 0s
Release / CLI (macos-aarch64) (push) Canceled after 0s
Release / CLI (macos-x86_64) (push) Canceled after 0s
Release / CLI (windows-x86_64) (push) Canceled after 0s
Release / App desktop (linux-x86_64) (push) Canceled after 0s
Release / App desktop (macos-aarch64) (push) Canceled after 0s
Release / App desktop (macos-x86_64) (push) Canceled after 0s
Release / App desktop (windows-x86_64) (push) Canceled after 0s
Release / Publica a release (push) Canceled after 0s
This commit is contained in:
1 parent
5e43fa6d20
commit
b4a85722a6
28 files changed
+706
-160
No files matched your search
+5
-3
@@ -2,7 +2,7 @@
|
||||
// • app Tauri: invoke('rpc') • modo `guara ui`: POST /rpc/<método> com token.
|
||||
|
||||
import { createExplorer } from './explorer.js';
|
||||
import { setLang, LOC } from './i18n.js';
|
||||
import { setLang, LOC, currentLang } from './i18n.js';
|
||||
|
||||
const tauri = window.__TAURI__;
|
||||
const params = new URLSearchParams(location.search);
|
||||
@@ -53,7 +53,7 @@ document.querySelectorAll('[data-theme-pick]').forEach((b) => (b.onclick = async
|
||||
|
||||
// ------------------------------------------------------------------ configurações
|
||||
// (declarado antes do uso: `S` é definido mais abaixo, por isso o objeto fica aqui)
|
||||
const SETTINGS = { theme: 'auto', lang: 'auto', notify: true, maps: false };
|
||||
const SETTINGS = { theme: 'auto', lang: 'auto', ui_lang: null, notify: true, maps: false };
|
||||
async function saveSettings(patch) {
|
||||
Object.assign(SETTINGS, patch);
|
||||
try { await rpc('settings_set', SETTINGS); } catch { /* segue só com o armazenamento local */ }
|
||||
@@ -750,8 +750,9 @@ $('#settingsBtn')?.addEventListener('click', () => {
|
||||
d.onclose = () => {
|
||||
if (d.returnValue !== 'ok') return;
|
||||
const patch = { lang: d.querySelector('[name=lang]').value, notify: d.querySelector('[name=notify]').checked, maps: d.querySelector('[name=maps]').checked };
|
||||
saveSettings(patch);
|
||||
setLang(patch.lang);
|
||||
patch.ui_lang = currentLang();
|
||||
saveSettings(patch);
|
||||
EX?.relang?.();
|
||||
toast('Salvo');
|
||||
};
|
||||
@@ -804,6 +805,7 @@ function watchClose() {
|
||||
try { localStorage.setItem('guara-theme', st.theme); } catch { /* */ }
|
||||
} catch { /* usa o local */ }
|
||||
setLang(SETTINGS.lang);
|
||||
if (SETTINGS.ui_lang !== currentLang()) saveSettings({ ui_lang: currentLang() });
|
||||
watchClose();
|
||||
tauri?.event?.listen?.('elevate-failed', (e) => { toast(String(e.payload || 'Não foi possível reiniciar como administrador')); });
|
||||
} catch (e) {
|
||||
|
||||
@@ -150,6 +150,9 @@ export function createExplorer(deps) {
|
||||
: new Date(k + '-01T00:00:00Z').toLocaleDateString(LOC(), { timeZone: 'UTC', month: 'long', year: 'numeric' });
|
||||
};
|
||||
const fmtDate = (t) => (t == null ? 'sem data' : new Date(t * 1000).toLocaleString(LOC(), { timeZone: 'UTC', dateStyle: 'medium', timeStyle: 'short' }));
|
||||
// Metadados do núcleo trazem datas em ISO 8601; o idioma decide a ordem.
|
||||
const ISO_DATE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?$/;
|
||||
const fmtMeta = (v) => ISO_DATE.test(v) ? new Date(v.endsWith('Z') ? v : v + 'Z').toLocaleString(LOC(), { timeZone: 'UTC', dateStyle: 'medium', timeStyle: 'medium' }) : v;
|
||||
const fmtDay = (t) => (t == null ? '' : new Date(t * 1000).toLocaleDateString(LOC(), { timeZone: 'UTC', day: '2-digit', month: 'short', year: 'numeric' }));
|
||||
|
||||
function geometry() {
|
||||
@@ -591,7 +594,7 @@ export function createExplorer(deps) {
|
||||
if (my !== metaToken || !$('#xpMeta')) return;
|
||||
const rows = (m.fields || []).filter(([k]) => !['Dimensões'].includes(k));
|
||||
if (!rows.length) return;
|
||||
$('#xpMeta').innerHTML = `<h5>Metadados${m.kind ? ` · ${esc(m.kind)}` : ''}</h5><dl class="kv">${rows.map(([k, v]) => `<dt>${esc(k)}</dt><dd>${esc(v)}</dd>`).join('')}</dl>`
|
||||
$('#xpMeta').innerHTML = `<h5>Metadados${m.kind ? ` · ${esc(m.kind)}` : ''}</h5><dl class="kv">${rows.map(([k, v]) => `<dt>${esc(k)}</dt><dd>${esc(fmtMeta(v))}</dd>`).join('')}</dl>`
|
||||
+ (m.gps ? gpsHtml(m.gps) : '');
|
||||
$('#xpOsm')?.addEventListener('click', () => rpc('open_url', { url: `https://www.openstreetmap.org/?mlat=${m.gps[0]}&mlon=${m.gps[1]}#map=15/${m.gps[0]}/${m.gps[1]}` }).catch(() => {}));
|
||||
}).catch(() => {});
|
||||
|
||||
+80
-10
@@ -1,7 +1,6 @@
|
||||
// Dicionário português -> inglês da interface. {n} marca um valor variável.
|
||||
export const EN = {
|
||||
// saúde do disco (SMART)
|
||||
'Saúde do disco': 'Disk health',
|
||||
'O SMART não pôde ser lido. Instale o pacote smartmontools (comando smartctl) e rode como administrador para ver a saúde do disco.': 'SMART could not be read. Install the smartmontools package (smartctl command) and run as administrator to see the disk health.',
|
||||
'Setores realocados': 'Reallocated sectors',
|
||||
'Setores pendentes de realocação': 'Pending sectors',
|
||||
@@ -13,7 +12,6 @@ export const EN = {
|
||||
'Erros de mídia': 'Media errors',
|
||||
'Reserva de blocos abaixo do limite': 'Spare blocks below threshold',
|
||||
'Erros de leitura/gravação vistos pelo sistema': 'Read/write errors seen by the system',
|
||||
'O sistema já registrou erros de E/S neste disco.': 'The system has already logged I/O errors on this disk.',
|
||||
'Estado': 'Status',
|
||||
'Bom': 'Good',
|
||||
'Atenção': 'Caution',
|
||||
@@ -40,9 +38,6 @@ export const EN = {
|
||||
'O SMART é do disco inteiro: os dados abaixo são de': 'SMART belongs to the whole disk: the data below is for',
|
||||
', que contém': ', which contains',
|
||||
'Imagem de disco: não há SMART para avaliar.': 'Disk image: there is no SMART to check.',
|
||||
'Nenhum sinal de defeito encontrado pelo SMART.': 'SMART found no signs of failure.',
|
||||
'Este disco mostra sinais sérios de defeito.': 'This disk shows serious signs of failure.',
|
||||
'Este disco mostra sinais de desgaste ou erros.': 'This disk shows signs of wear or errors.',
|
||||
// barra lateral e estrutura
|
||||
'Recuperação de arquivos': 'File recovery',
|
||||
'Dispositivos': 'Devices',
|
||||
@@ -50,9 +45,7 @@ export const EN = {
|
||||
'Atualizar lista': 'Refresh list',
|
||||
'Lista atualizada': 'List refreshed',
|
||||
'Abrir arquivo de imagem…': 'Open disk image file…',
|
||||
'Abrir arquivo de imagem': 'Open disk image file',
|
||||
'Abrir sessão salva…': 'Open saved session…',
|
||||
'Abrir sessão salva': 'Open saved session',
|
||||
'Sessões': 'Sessions',
|
||||
'Sessão': 'Session',
|
||||
'Sessão:': 'Session:',
|
||||
@@ -181,7 +174,6 @@ export const EN = {
|
||||
'Por assinatura': 'By signature',
|
||||
'Sem data': 'No date',
|
||||
'sem data': 'no date',
|
||||
'Confiança {n}': 'Confidence {n}',
|
||||
'Com localização (GPS)': 'With location (GPS)',
|
||||
'Com GPS': 'With GPS',
|
||||
'Ocultar duplicadas': 'Hide duplicates',
|
||||
@@ -308,7 +300,6 @@ export const EN = {
|
||||
// recuperar
|
||||
'Recuperar para…': 'Recover to…',
|
||||
'Recuperar': 'Recover',
|
||||
'Escolha uma pasta em <b>outro disco</b>. O Guará nunca grava no disco de origem, para não sobrescrever o que ainda dá para salvar.': 'Choose a folder on <b>another disk</b>. Guará never writes to the source disk, so it does not overwrite what can still be saved.',
|
||||
'Pasta de destino': 'Destination folder',
|
||||
'Escolher…': 'Choose…',
|
||||
'Organizar os arquivos': 'Organize the files',
|
||||
@@ -346,7 +337,6 @@ export const EN = {
|
||||
'Se a recuperação for interrompida, escolha a mesma pasta de novo: o Guará pula o que já foi conferido.': 'If the recovery is interrupted, pick the same folder again: Guará skips what was already verified.',
|
||||
'{n} já estavam recuperados e conferidos (pulados).': '{n} were already recovered and verified (skipped).',
|
||||
// imagem, hash, busca
|
||||
'Onde salvar o arquivo de imagem? Use <b>outro disco</b> com espaço suficiente e um nome que ainda não exista.': 'Where to save the image file? Use <b>another disk</b> with enough space and a name that does not exist yet.',
|
||||
'Criando imagem do disco': 'Creating disk image',
|
||||
'Nenhum setor ruim encontrado.': 'No bad sectors found.',
|
||||
'Calculando hashes da origem': 'Computing source hashes',
|
||||
@@ -505,4 +495,84 @@ export const EN = {
|
||||
'Tabela virtual: o conteúdo não fica armazenado como linhas comuns.': 'Virtual table: the content is not stored as ordinary rows.',
|
||||
'Tabela WITHOUT ROWID: as linhas ficam numa árvore de índice, ainda não exibida.': 'WITHOUT ROWID table: the rows live in an index tree, not shown yet.',
|
||||
'Arquivo truncado: o cabeçalho indica {n} páginas, mas só há {n}. Parte dos dados pode estar faltando.': 'Truncated file: the header says {n} pages, but there are only {n}. Part of the data may be missing.',
|
||||
|
||||
// mensagens de erro e avisos do núcleo (valores variáveis usam {n})
|
||||
'job {n} não existe': 'job {n} does not exist',
|
||||
'a varredura não está pausada': 'the scan is not paused',
|
||||
'A origem desta sessão não está disponível ({n}). Reconecte o disco ou escolha a origem para continuar.': 'The source of this session is unavailable ({n}). Reconnect the disk or choose the source to continue.',
|
||||
'arquivo não encontrado': 'file not found',
|
||||
'nenhum arquivo selecionado': 'no file selected',
|
||||
'informe pelo menos um termo': 'enter at least one term',
|
||||
'Espaço insuficiente no destino: os arquivos somam {n} e há {n} livres. Escolha outro disco ou recupere menos arquivos.': 'Not enough space at the destination: the files total {n} and {n} are free. Choose another disk or recover fewer files.',
|
||||
'Sem permissão para ler a sessão {n}. Se ela foi criada como administrador (por exemplo em /root), abra o Guará como administrador ou copie o arquivo para a sua pasta.': 'Permission denied reading the session {n}. If it was created as administrator (for example in /root), open Guará as administrator or copy the file to your folder.',
|
||||
'Não foi possível ler a sessão {n}: {n}': 'Could not read the session {n}: {n}',
|
||||
'O arquivo {n} não é uma sessão do Guará válida (ou foi criado por uma versão incompatível): {n}': 'The file {n} is not a valid Guará session (or was created by an incompatible version): {n}',
|
||||
'disco não encontrado': 'disk not found',
|
||||
'Essa origem tem {n} bytes, mas a sessão foi feita sobre um disco de {n} bytes. Escolha o mesmo disco ou uma imagem dele.': 'This source has {n} bytes, but the session was made on a {n}-byte disk. Choose the same disk or an image of it.',
|
||||
'O conteúdo inicial dessa origem é diferente do disco da sessão (outra tabela de partições ou outro disco). Se tem certeza, use a opção de usar assim mesmo.': 'The initial content of this source differs from the session disk (another partition table or another disk). If you are sure, use the \'use anyway\' option.',
|
||||
'só sessões automáticas podem ser apagadas por aqui': 'only automatic sessions can be deleted here',
|
||||
'parâmetro \'{n}\' ausente': 'missing parameter \'{n}\'',
|
||||
'endereço não permitido': 'address not allowed',
|
||||
'id ausente': 'missing id',
|
||||
'método desconhecido: {n}': 'unknown method: {n}',
|
||||
'A origem desta sessão não está disponível. Reconecte o disco ou escolha a origem para pré-visualizar e recuperar.': 'The source of this session is unavailable. Reconnect the disk or choose the source to preview and recover.',
|
||||
'Sem permissão para ler {n}. Discos exigem privilégios de administrador: no Linux/macOS rode com sudo; no Windows, execute como administrador.': 'Permission denied reading {n}. Disks need administrator privileges: on Linux/macOS run with sudo; on Windows, run as administrator.',
|
||||
'Não foi possível abrir {n}: {n}': 'Could not open {n}: {n}',
|
||||
'O destino {n} fica em {n} (montado a partir da origem {n}). Escolha outro disco para não sobrescrever dados recuperáveis.': 'The destination {n} is on {n} (mounted from the source {n}). Choose another disk so you do not overwrite recoverable data.',
|
||||
'A origem {n} contém o sistema (/). Recupere para um disco externo ou use uma imagem.': 'The source {n} contains the system (/). Recover to an external disk or use an image.',
|
||||
'leitura além do fim da origem': 'read beyond the end of the source',
|
||||
'senha ou chave incorreta': 'wrong password or key',
|
||||
'não suportado: {n}': 'not supported: {n}',
|
||||
'cabeçalho inválido: {n}': 'invalid header: {n}',
|
||||
'Não é um banco SQLite (cabeçalho ausente). Se o arquivo é um banco, ele pode estar criptografado (SQLCipher) ou ter o início sobrescrito.': 'Not an SQLite database (missing header). If the file is a database, it may be encrypted (SQLCipher) or have its beginning overwritten.',
|
||||
'Tamanho de página inválido no cabeçalho do banco.': 'Invalid page size in the database header.',
|
||||
'página {n} fora do arquivo (banco truncado)': 'page {n} outside the file (truncated database)',
|
||||
'página {n} incompleta': 'page {n} incomplete',
|
||||
'página 0 inválida': 'invalid page 0',
|
||||
'tabela {n} não encontrada': 'table {n} not found',
|
||||
'célula ilegível na página {n}': 'unreadable cell on page {n}',
|
||||
'página {n} de tipo inesperado (0x{n})': 'page {n} of unexpected type (0x{n})',
|
||||
'Arquivo grande demais para abrir direto. Recupere-o e abra pelo seu gerenciador de arquivos.': 'File too large to open directly. Recover it and open it with your file manager.',
|
||||
'não foi possível criar {n}: {n} (o arquivo não pode existir)': 'could not create {n}: {n} (the file must not exist)',
|
||||
'não foi possível ler o arquivo de chave {n}: {n}': 'could not read the key file {n}: {n}',
|
||||
'chave do volume em hexadecimal inválida': 'invalid hexadecimal volume key',
|
||||
'não foi possível descobrir os parâmetros do RAID (nenhuma combinação produziu um sistema de arquivos coerente)': 'could not discover the RAID parameters (no combination produced a coherent file system)',
|
||||
'não é uma imagem EWF (E01/Ex01)': 'not an EWF (E01/Ex01) image',
|
||||
'imagem EWF sem tabela de chunks': 'EWF image without a chunk table',
|
||||
'tabela EWF inconsistente': 'inconsistent EWF table',
|
||||
'cabeçalho de volume EWF ausente': 'missing EWF volume header',
|
||||
'segmento Ex01 ausente: sequência de chunks com lacuna': 'missing Ex01 segment: chunk sequence has a gap',
|
||||
'tamanho de chunk EWF inválido': 'invalid EWF chunk size',
|
||||
'imagem grande demais para o formato E01': 'image too large for the E01 format',
|
||||
'não foi possível criar {n}: {n}': 'could not create {n}: {n}',
|
||||
'AFF4 inválido (ZIP): {n}': 'invalid AFF4 (ZIP): {n}',
|
||||
'AFF4: entrada sem início de dados: {n}': 'AFF4: entry without a data start: {n}',
|
||||
'AFF4 sem information.turtle': 'AFF4 without information.turtle',
|
||||
'AFF4 sem objeto de imagem (aff4:Image com aff4:dataStream)': 'AFF4 without an image object (aff4:Image with aff4:dataStream)',
|
||||
'AFF4: fluxo de dados da imagem não encontrado': 'AFF4: image data stream not found',
|
||||
'AFF4: fluxo simbólico inválido': 'AFF4: invalid symbolic stream',
|
||||
'AFF4: fluxo {n} não descrito': 'AFF4: stream {n} not described',
|
||||
'AFF4: entrada {n} ausente': 'AFF4: entry {n} missing',
|
||||
'AFF4/ZIP: {n}': 'AFF4/ZIP: {n}',
|
||||
'cifra {n} (apenas AES)': 'cipher {n} (only AES)',
|
||||
'chave inválida para {n}': 'invalid key for {n}',
|
||||
'IV {n}': 'IV {n}',
|
||||
'{n} arquivos · {n} · {n}': '{n} files · {n} · {n}',
|
||||
'Discos brutos exigem administrador. Rode': 'Raw disks require administrator. Run',
|
||||
'(Linux/macOS) ou abra o Guará como administrador (Windows). Você ainda pode abrir um arquivo de imagem pelo menu.': '(Linux/macOS) or open Guará as administrator (Windows). You can still open an image file from the menu.',
|
||||
'(Linux/macOS) ou como administrador (Windows).': '(Linux/macOS) or as administrator (Windows).',
|
||||
'cifrada ({n}) · só detecção': 'encrypted ({n}) · detection only',
|
||||
'Onde salvar o arquivo de imagem? Use': 'Where to save the image file? Use',
|
||||
'outro disco': 'another disk',
|
||||
'com espaço suficiente e um nome que ainda não exista.': 'with enough space and a name that does not exist yet.',
|
||||
'Criar imagem': 'Create image',
|
||||
'{n} trecho(s) ilegível(is) foram preenchidos com zeros. Veja o mapa de erros ao lado da imagem.': '{n} unreadable chunk(s) were filled with zeros. See the error map next to the image.',
|
||||
'{n}. Escolha o disco que voltou ou uma imagem dele.': '{n}. Choose the disk that came back or an image of it.',
|
||||
'Escolha uma pasta em': 'Choose a folder on',
|
||||
'(todos desta vista)': '(all in this view)',
|
||||
'Recuperar {n}': 'Recover {n}',
|
||||
'Dimensões': 'Dimensions',
|
||||
'{n} · {n}{n} · confiança {n} · {n} de {n}': '{n} · {n}{n} · confidence {n} · {n} of {n}',
|
||||
'e mais {n}…': 'and {n} more…',
|
||||
'abre no seu player: o navegador embutido do Linux não traz os codecs e travaria a janela.': 'opens in your player: the embedded Linux browser does not include the codecs and would freeze the window.',
|
||||
};
|
||||
@@ -7,6 +7,7 @@ import { EN } from './i18n-en.js';
|
||||
|
||||
let lang = 'pt';
|
||||
export const isEn = () => lang === 'en';
|
||||
export const currentLang = () => lang;
|
||||
export const LOC = () => (lang === 'en' ? 'en-US' : 'pt-BR');
|
||||
|
||||
export function resolveLang(pref) {
|
||||
|
||||
Reference in new issue
Block a user