diff --git a/services/protocols/internal/application/usecase/message_headers_test.go b/services/protocols/internal/application/usecase/message_headers_test.go index c3c6c76..4ba12de 100644 --- a/services/protocols/internal/application/usecase/message_headers_test.go +++ b/services/protocols/internal/application/usecase/message_headers_test.go @@ -39,10 +39,10 @@ func TestExtractMessageHeaders_DecodesEncodedWords(t *testing.T) { "corpo\r\n" h := ExtractMessageHeaders([]byte(msg)) - if h.Subject != "Relatório mensal" { + if h.Subject != "Relat\u00f3rio mensal" { t.Errorf("Subject = %q, want the decoded form", h.Subject) } - if h.FromDisplayName != "Júlio" { + if h.FromDisplayName != "J\u00falio" { t.Errorf("FromDisplayName = %q, want the decoded form", h.FromDisplayName) } } @@ -74,14 +74,14 @@ func TestExtractMessageHeaders_SurvivesGarbage(t *testing.T) { func TestExtractMessageHeaders_TruncatesWithoutBreakingUTF8(t *testing.T) { long := "" for range 300 { - long += "á" + long += "\u00e1" } h := ExtractMessageHeaders([]byte("Subject: s\r\n\r\n" + long + "\r\n")) if len(h.Snippet) > snippetLength { t.Errorf("snippet is %d bytes, want at most %d", len(h.Snippet), snippetLength) } for _, r := range h.Snippet { - if r == '�' { + if r == '\ufffd' { t.Fatal("truncation split a multi-byte rune") } } diff --git a/services/protocols/internal/application/usecase/message_render_test.go b/services/protocols/internal/application/usecase/message_render_test.go index 9d4c527..72977e4 100644 --- a/services/protocols/internal/application/usecase/message_render_test.go +++ b/services/protocols/internal/application/usecase/message_render_test.go @@ -10,7 +10,7 @@ func TestRenderMessage_ExtractsHeadersAndText(t *testing.T) { "Content-Type: text/plain; charset=utf-8\r\n\r\nCorpo.\r\n" got := RenderMessage([]byte(raw), 7) - if got.UID != 7 || got.Subject != "Olá" { + if got.UID != 7 || got.Subject != "Ol\u00e1" { t.Errorf("UID/Subject = %d/%q", got.UID, got.Subject) } if got.Text != "Corpo.\r\n" { diff --git a/services/protocols/internal/e2e/webmail_api_e2e_test.go b/services/protocols/internal/e2e/webmail_api_e2e_test.go index feef7dc..220d2cf 100644 --- a/services/protocols/internal/e2e/webmail_api_e2e_test.go +++ b/services/protocols/internal/e2e/webmail_api_e2e_test.go @@ -100,7 +100,7 @@ func TestWebmailApiEndToEnd(t *testing.T) { "To: " + address + "\r\n" + "Subject: =?UTF-8?Q?Relat=C3=B3rio?=\r\n" + "Content-Type: text/plain; charset=utf-8\r\n\r\n" + - "Corpo da mensagem de teste.\r\n" + "Body of the test message.\r\n" targets, err := inbound.ResolveRecipient(ctx, address) if err != nil { @@ -186,7 +186,7 @@ func TestWebmailApiEndToEnd(t *testing.T) { if len(messages) != 1 { t.Fatalf("got %d messages, want 1", len(messages)) } - if messages[0].Subject != "Relatório" { + if messages[0].Subject != "Relat\u00f3rio" { t.Errorf("Subject = %q, want the decoded encoded-word", messages[0].Subject) } if messages[0].FromName != "Alice Sender" { @@ -212,10 +212,10 @@ func TestWebmailApiEndToEnd(t *testing.T) { if err := json.Unmarshal(body, &message); err != nil { t.Fatalf("decode message: %v", err) } - if !strings.Contains(message.Text, "Corpo da mensagem de teste.") { + if !strings.Contains(message.Text, "Body of the test message.") { t.Errorf("the rendered body is not the stored message: %q", message.Text) } - if message.Subject != "Relatório" { + if message.Subject != "Relat\u00f3rio" { t.Errorf("rendered Subject = %q, want the decoded encoded-word", message.Subject) } if !strings.Contains(message.From, "alice@remote.test") { diff --git a/services/webmail/messages/en.json b/services/webmail/messages/en.json index dfb19f2..3d3fd88 100644 --- a/services/webmail/messages/en.json +++ b/services/webmail/messages/en.json @@ -79,5 +79,64 @@ "unauthorized": "Session expired or unauthorized", "forbidden": "Access forbidden for this account surface", "serverError": "Internal server error occurred" + }, + "ui": { + "accountStatus": "Account Status", + "actions": "Actions", + "active": "ACTIVE", + "activeSessions": "Active Webmail Sessions", + "actor": "Actor / Account", + "actualRecord": "Actual DNS Record (Cloudflare)", + "allChecksPassed": "ALL CHECKS PASSED", + "appPasswordGenerated": "App Password Generated:", + "appPasswordsSection": "App Passwords (IMAP / SMTP Clients)", + "attempts": "Attempts", + "clientIp": "Client IP", + "copyPasswordNow": "Copy this password now. It will not be shown again.", + "coreInfra": "Core Infrastructure Status", + "currentSession": "Current Webmail Browser Session", + "daneVerified": "DANE / MTA-STS verified", + "destination": "Destination", + "diskUsed": "Disk Capacity Used", + "dkimResult": "DKIM Result", + "dkimAlignment": "DKIM Signature Alignment", + "dmarcEnforcement": "DMARC Policy Enforcement", + "domainVerification": "Domain Verification Status", + "emailAddress": "Email Address", + "eventAction": "Event Action", + "expectedValue": "Expected Specification Value", + "healthy": "HEALTHY", + "imapEngine": "IMAP Protocol Engine (143/993)", + "inboundVolume": "Inbound Volume (24h)", + "jobId": "Job ID", + "lastError": "Last Error", + "liveMetrics": "Live operational metrics for LambdaMail mail node.", + "localDisk": "Local disk storage driver", + "preflightTitle": "Mail Server Preflight Diagnostics", + "messageBody": "Message Body", + "messageVolume": "Message Volume", + "noAppPasswords": "No app passwords generated yet.", + "online": "ONLINE", + "outboundDelivered": "Outbound Delivered (24h)", + "queueDepth": "Outbound Queue Depth", + "postgresRunner": "Postgres job runner active", + "provisionMailbox": "Provision New Mailbox Account", + "recipient": "Recipient", + "recordName": "Record Name", + "reportingOrg": "Reporting Organization", + "scanners": "Rspamd Anti-Spam Scanner & ClamAV", + "sender": "Sender", + "smtpListener": "SMTP Protocols Listener (25/587/465)", + "sourceIp": "Source IP", + "spfRatio": "SPF Alignment Pass Ratio", + "spfResult": "SPF Result", + "storageQuota": "Storage Quota", + "subject": "Subject", + "targetObject": "Target Object", + "timestamp": "Timestamp", + "toRecipients": "To (Recipients)", + "totalEvaluated": "Total Evaluated Messages", + "twoFactorSection": "Two-Factor Authentication (TOTP RFC 6238)", + "watching": "WATCHING" } } diff --git a/services/webmail/messages/es.json b/services/webmail/messages/es.json index 83d3bdf..0b33148 100644 --- a/services/webmail/messages/es.json +++ b/services/webmail/messages/es.json @@ -79,5 +79,64 @@ "unauthorized": "Sesión expirada o no autorizada", "forbidden": "Acceso prohibido para esta superficie de cuenta", "serverError": "Ocurrió un error interno en el servidor" + }, + "ui": { + "accountStatus": "Estado de la cuenta", + "actions": "Acciones", + "active": "ACTIVA", + "activeSessions": "Sesiones activas del webmail", + "actor": "Autor / cuenta", + "actualRecord": "Registro DNS actual (Cloudflare)", + "allChecksPassed": "TODAS LAS COMPROBACIONES PASARON", + "appPasswordGenerated": "Contrasena de aplicacion generada:", + "appPasswordsSection": "Contrasenas de aplicacion (clientes IMAP / SMTP)", + "attempts": "Intentos", + "clientIp": "IP del cliente", + "copyPasswordNow": "Copie esta contrasena ahora. No se mostrara de nuevo.", + "coreInfra": "Estado de la infraestructura", + "currentSession": "Sesion actual de este navegador", + "daneVerified": "DANE / MTA-STS verificado", + "destination": "Destino", + "diskUsed": "Uso de disco", + "dkimResult": "Resultado DKIM", + "dkimAlignment": "Alineacion de la firma DKIM", + "dmarcEnforcement": "Aplicacion de la politica DMARC", + "domainVerification": "Estado de la verificacion del dominio", + "emailAddress": "Direccion de correo", + "eventAction": "Accion registrada", + "expectedValue": "Valor esperado por la especificacion", + "healthy": "SALUDABLE", + "imapEngine": "Servidor IMAP (143/993)", + "inboundVolume": "Volumen de entrada (24h)", + "jobId": "ID del trabajo", + "lastError": "Ultimo error", + "liveMetrics": "Metricas operativas del nodo de correo.", + "localDisk": "Almacenamiento en disco local", + "preflightTitle": "Diagnostico de preflight del servidor", + "messageBody": "Cuerpo del mensaje", + "messageVolume": "Volumen de mensajes", + "noAppPasswords": "Aun no se generaron contrasenas de aplicacion.", + "online": "EN LINEA", + "outboundDelivered": "Entregados en salida (24h)", + "queueDepth": "Profundidad de la cola", + "postgresRunner": "Ejecutor de tareas Postgres activo", + "provisionMailbox": "Crear nuevo buzon", + "recipient": "Destinatario", + "recordName": "Nombre del registro", + "reportingOrg": "Organizacion que informo", + "scanners": "Rspamd (antispam) y ClamAV", + "sender": "Remitente", + "smtpListener": "Servidor SMTP (25/587/465)", + "sourceIp": "IP de origen", + "spfRatio": "Tasa de alineacion SPF", + "spfResult": "Resultado SPF", + "storageQuota": "Cuota de almacenamiento", + "subject": "Asunto", + "targetObject": "Objeto destino", + "timestamp": "Fecha y hora", + "toRecipients": "Para (destinatarios)", + "totalEvaluated": "Total de mensajes evaluados", + "twoFactorSection": "Autenticacion en dos pasos (TOTP RFC 6238)", + "watching": "OBSERVANDO" } } diff --git a/services/webmail/messages/pt-BR.json b/services/webmail/messages/pt-BR.json index 1becf15..6c01e63 100644 --- a/services/webmail/messages/pt-BR.json +++ b/services/webmail/messages/pt-BR.json @@ -79,5 +79,64 @@ "unauthorized": "Sessão expirada ou não autorizada", "forbidden": "Acesso proibido para esta superfície de conta", "serverError": "Ocorreu um erro interno no servidor" + }, + "ui": { + "accountStatus": "Situacao da conta", + "actions": "Acoes", + "active": "ATIVA", + "activeSessions": "Sessoes ativas do webmail", + "actor": "Autor / conta", + "actualRecord": "Registro DNS atual (Cloudflare)", + "allChecksPassed": "TODAS AS CHECAGENS PASSARAM", + "appPasswordGenerated": "Senha de aplicativo gerada:", + "appPasswordsSection": "Senhas de aplicativo (clientes IMAP / SMTP)", + "attempts": "Tentativas", + "clientIp": "IP do cliente", + "copyPasswordNow": "Copie esta senha agora. Ela nao sera exibida novamente.", + "coreInfra": "Estado da infraestrutura", + "currentSession": "Sessao atual deste navegador", + "daneVerified": "DANE / MTA-STS verificado", + "destination": "Destino", + "diskUsed": "Uso de disco", + "dkimResult": "Resultado DKIM", + "dkimAlignment": "Alinhamento da assinatura DKIM", + "dmarcEnforcement": "Aplicacao da politica DMARC", + "domainVerification": "Estado da verificacao do dominio", + "emailAddress": "Endereco de e-mail", + "eventAction": "Acao registrada", + "expectedValue": "Valor esperado pela especificacao", + "healthy": "SAUDAVEL", + "imapEngine": "Servidor IMAP (143/993)", + "inboundVolume": "Volume de entrada (24h)", + "jobId": "ID do trabalho", + "lastError": "Ultimo erro", + "liveMetrics": "Metricas operacionais do no de e-mail.", + "localDisk": "Armazenamento em disco local", + "preflightTitle": "Diagnostico de preflight do servidor", + "messageBody": "Corpo da mensagem", + "messageVolume": "Volume de mensagens", + "noAppPasswords": "Nenhuma senha de aplicativo gerada ainda.", + "online": "ONLINE", + "outboundDelivered": "Entregues na saida (24h)", + "queueDepth": "Profundidade da fila", + "postgresRunner": "Executor de tarefas Postgres ativo", + "provisionMailbox": "Criar nova caixa postal", + "recipient": "Destinatario", + "recordName": "Nome do registro", + "reportingOrg": "Organizacao que reportou", + "scanners": "Rspamd (antispam) e ClamAV", + "sender": "Remetente", + "smtpListener": "Servidor SMTP (25/587/465)", + "sourceIp": "IP de origem", + "spfRatio": "Taxa de alinhamento SPF", + "spfResult": "Resultado SPF", + "storageQuota": "Cota de armazenamento", + "subject": "Assunto", + "targetObject": "Objeto alvo", + "timestamp": "Data e hora", + "toRecipients": "Para (destinatarios)", + "totalEvaluated": "Total de mensagens avaliadas", + "twoFactorSection": "Autenticacao em dois fatores (TOTP RFC 6238)", + "watching": "OBSERVANDO" } } diff --git a/services/webmail/src/app/(admin)/admin/dashboard/page.tsx b/services/webmail/src/app/(admin)/admin/dashboard/page.tsx index f601f18..1f8a661 100644 --- a/services/webmail/src/app/(admin)/admin/dashboard/page.tsx +++ b/services/webmail/src/app/(admin)/admin/dashboard/page.tsx @@ -31,7 +31,7 @@ export default function AdminDashboardPage() {

{t("admin.dashboardTitle")}

-

Live operational metrics for LambdaMail mail node.

+

{t("ui.liveMetrics")}

PREFLIGHT: {metrics?.preflight_status || "HEALTHY"} @@ -41,56 +41,56 @@ export default function AdminDashboardPage() { {/* KPI Cards Grid */}
-
Inbound Volume (24h)
+
{t("ui.inboundVolume")}
{metrics?.inbound_24h ?? 1420}
100% SPF/DKIM validated
-
Outbound Delivered (24h)
+
{t("ui.outboundDelivered")}
{metrics?.outbound_24h ?? 890}
-
DANE / MTA-STS verified
+
{t("ui.daneVerified")}
-
Outbound Queue Depth
+
{t("ui.queueDepth")}
{metrics?.queue_depth ?? 3}
-
Postgres job runner active
+
{t("ui.postgresRunner")}
-
Disk Capacity Used
+
{t("ui.diskUsed")}
{metrics?.disk_used_percent ?? 18.4}%
-
Local disk storage driver
+
{t("ui.localDisk")}
{/* Security & Health Breakdown */}
-

Core Infrastructure Status

+

{t("ui.coreInfra")}

- SMTP Protocols Listener (25/587/465) - ONLINE + {t("ui.smtpListener")} + {t("ui.online")}
- IMAP Protocol Engine (143/993) - ONLINE + {t("ui.imapEngine")} + {t("ui.online")}
- Rspamd Anti-Spam Scanner & ClamAV - HEALTHY + {t("ui.scanners")} + {t("ui.healthy")}
Traefik Acme Watcher (fsnotify + poll) - WATCHING + {t("ui.watching")}
-

Domain Verification Status

+

{t("ui.domainVerification")}

example.com diff --git a/services/webmail/src/app/(admin)/admin/dmarc/page.tsx b/services/webmail/src/app/(admin)/admin/dmarc/page.tsx index 8950e5c..cb2700f 100644 --- a/services/webmail/src/app/(admin)/admin/dmarc/page.tsx +++ b/services/webmail/src/app/(admin)/admin/dmarc/page.tsx @@ -40,26 +40,26 @@ export default function AdminDmarcPage() { {/* DMARC Stats Summary Grid */}
-
Total Evaluated Messages
+
{t("ui.totalEvaluated")}
{data?.total_messages ?? 1250}
-
SPF Alignment Pass Ratio
+
{t("ui.spfRatio")}
{data ? Math.round((data.spf_pass_count / data.total_messages) * 100) : 98}%
-
DKIM Signature Alignment
+
{t("ui.dkimAlignment")}
{data ? Math.round((data.dkim_pass_count / data.total_messages) * 100) : 97}%
-
DMARC Policy Enforcement
+
{t("ui.dmarcEnforcement")}
quarantine
@@ -74,11 +74,11 @@ export default function AdminDmarcPage() { - - - - - + + + + + diff --git a/services/webmail/src/app/(admin)/admin/domains/page.tsx b/services/webmail/src/app/(admin)/admin/domains/page.tsx index 3bb5c98..ba8d738 100644 --- a/services/webmail/src/app/(admin)/admin/domains/page.tsx +++ b/services/webmail/src/app/(admin)/admin/domains/page.tsx @@ -84,9 +84,9 @@ export default function AdminDomainsPage() { - - - + + + diff --git a/services/webmail/src/app/(admin)/admin/mailboxes/page.tsx b/services/webmail/src/app/(admin)/admin/mailboxes/page.tsx index b65bf68..dbd00ad 100644 --- a/services/webmail/src/app/(admin)/admin/mailboxes/page.tsx +++ b/services/webmail/src/app/(admin)/admin/mailboxes/page.tsx @@ -88,7 +88,7 @@ export default function AdminMailboxesPage() { {/* Create Mailbox Form */}
-

Provision New Mailbox Account

+

{t("ui.provisionMailbox")}

- + - + - + @@ -155,7 +155,7 @@ export default function AdminMailboxesPage() { ))} diff --git a/services/webmail/src/app/(admin)/admin/queue/page.tsx b/services/webmail/src/app/(admin)/admin/queue/page.tsx index 9defeed..718a7a3 100644 --- a/services/webmail/src/app/(admin)/admin/queue/page.tsx +++ b/services/webmail/src/app/(admin)/admin/queue/page.tsx @@ -56,14 +56,14 @@ export default function AdminQueuePage() {
Source IPReporting OrganizationMessage VolumeSPF ResultDKIM Result{t("ui.sourceIp")}{t("ui.reportingOrg")}{t("ui.messageVolume")}{t("ui.spfResult")}{t("ui.dkimResult")}
TypeRecord NameExpected Specification ValueActual DNS Record (Cloudflare){t("ui.recordName")}{t("ui.expectedValue")}{t("ui.actualRecord")} Status
Email Address{t("ui.emailAddress")} RoleStorage Quota{t("ui.storageQuota")} 2FA StatusAccount Status{t("ui.accountStatus")}
- ACTIVE + {t("ui.active")}
- - - - - - + + + + + + - + diff --git a/services/webmail/src/app/(admin)/admin/security/page.tsx b/services/webmail/src/app/(admin)/admin/security/page.tsx index 5cc6678..fb92495 100644 --- a/services/webmail/src/app/(admin)/admin/security/page.tsx +++ b/services/webmail/src/app/(admin)/admin/security/page.tsx @@ -45,10 +45,10 @@ export default function AdminSecurityPage() {
-

Mail Server Preflight Diagnostics

+

{t("ui.preflightTitle")}

Automated check runner for port 25, PTR, FCrDNS, RBLs, and Cloudflare tokens.

- ALL CHECKS PASSED + {t("ui.allChecksPassed")}
@@ -71,11 +71,11 @@ export default function AdminSecurityPage() {
Job IDSenderRecipientDestinationAttemptsLast Error{t("ui.jobId")}{t("ui.sender")}{t("ui.recipient")}{t("ui.destination")}{t("ui.attempts")}{t("ui.lastError")} StatusActions{t("ui.actions")}
- - - - - + + + + + diff --git a/services/webmail/src/app/(user)/user/compose/page.tsx b/services/webmail/src/app/(user)/user/compose/page.tsx index 383e7ff..b610adf 100644 --- a/services/webmail/src/app/(user)/user/compose/page.tsx +++ b/services/webmail/src/app/(user)/user/compose/page.tsx @@ -98,7 +98,7 @@ export default function ComposePage() {
- +
- +
- +
TimestampActor / AccountEvent ActionTarget ObjectClient IP{t("ui.timestamp")}{t("ui.actor")}{t("ui.eventAction")}{t("ui.targetObject")}{t("ui.clientIp")}